From 5a37c04c3466f6fe4fc5fb6e79b0f6318820c14c Mon Sep 17 00:00:00 2001 From: Anes Belfodil Date: Sat, 12 Sep 2020 20:29:46 -0400 Subject: [PATCH 01/56] Add base Flutter dependencies --- mobile/pubspec.lock | 120 ++++++++++++++++++++++++++++++++++++++++++++ mobile/pubspec.yaml | 8 +++ 2 files changed, 128 insertions(+) diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 65a95a92..03161685 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -1,6 +1,20 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + archive: + dependency: "direct main" + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.13" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.0" async: dependency: transitive description: @@ -8,6 +22,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.5.0-nullsafety" + battery: + dependency: "direct main" + description: + name: battery + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + battery_platform_interface: + dependency: transitive + description: + name: battery_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + bloc: + dependency: "direct main" + description: + name: bloc + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.3" boolean_selector: dependency: transitive description: @@ -43,6 +78,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.15.0-nullsafety.2" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.5" cupertino_icons: dependency: "direct main" description: @@ -69,11 +118,32 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.5" + flutter_blue: + dependency: "direct main" + description: + name: flutter_blue + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.2" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + hive: + dependency: "direct main" + description: + name: hive + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.4" matcher: dependency: transitive description: @@ -88,6 +158,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0-nullsafety.2" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4" path: dependency: transitive description: @@ -95,6 +179,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0-nullsafety" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" protobuf: dependency: "direct main" description: @@ -102,6 +193,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.1" + provider: + dependency: transitive + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.2+2" + rxdart: + dependency: transitive + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.24.1" + share: + dependency: "direct main" + description: + name: share + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.5" sky_engine: dependency: transitive description: flutter @@ -156,6 +268,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0-nullsafety.2" + usb_serial: + dependency: "direct main" + description: + name: usb_serial + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.4" vector_math: dependency: transitive description: @@ -165,3 +284,4 @@ packages: version: "2.1.0-nullsafety.2" sdks: dart: ">=2.10.0-0.0.dev <2.10.0" + flutter: ">=1.16.0 <2.0.0" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 4c083390..5409c88d 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -28,6 +28,14 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.3 protobuf: ^1.0.1 + hive: ^1.4.4 + usb_serial: ^0.2.4 + flutter_blue: ^0.7.2 + bloc: ^6.0.3 + flutter_bloc: ^6.0.5 + battery: ^1.0.5 + share: ^0.6.5 + archive: ^2.0.13 dev_dependencies: flutter_test: From 65cf38dfe9c451307cceca67321d613183cb7a4a Mon Sep 17 00:00:00 2001 From: Anes Belfodil Date: Mon, 14 Sep 2020 16:11:13 -0400 Subject: [PATCH 02/56] Bump minimal android sdk version --- mobile/android/app/build.gradle | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mobile/android/app/build.gradle b/mobile/android/app/build.gradle index fb5a1026..4e62f489 100644 --- a/mobile/android/app/build.gradle +++ b/mobile/android/app/build.gradle @@ -37,9 +37,8 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.Polydodo" - minSdkVersion 16 + applicationId "com.PolyCortex.Polydodo" + minSdkVersion 19 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName From 0c53addf6b0d5bade3ac520b90a9dc776e545229 Mon Sep 17 00:00:00 2001 From: William Harvey Date: Mon, 14 Sep 2020 23:04:18 -0400 Subject: [PATCH 03/56] Increment and decrement with counter cubit & create main/app/locator structure --- mobile/lib/main.dart | 116 +----------------- mobile/lib/src/app.dart | 25 ++++ .../application/counter/counter_cubit.dart | 8 ++ mobile/lib/src/locator.dart | 10 ++ .../counter_route/counter_route.dart | 39 ++++++ .../counter_route/counter_text.dart | 21 ++++ mobile/lib/src/theme.dart | 6 + mobile/pubspec.lock | 43 +++---- mobile/pubspec.yaml | 2 +- 9 files changed, 131 insertions(+), 139 deletions(-) create mode 100644 mobile/lib/src/app.dart create mode 100644 mobile/lib/src/application/counter/counter_cubit.dart create mode 100644 mobile/lib/src/locator.dart create mode 100644 mobile/lib/src/presentation/counter_route/counter_route.dart create mode 100644 mobile/lib/src/presentation/counter_route/counter_text.dart create mode 100644 mobile/lib/src/theme.dart diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 11655b66..ad72d679 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -1,117 +1,7 @@ import 'package:flutter/material.dart'; -void main() { - runApp(MyApp()); -} - -class MyApp extends StatelessWidget { - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // Try running your application with "flutter run". You'll see the - // application has a blue toolbar. Then, without quitting the app, try - // changing the primarySwatch below to Colors.green and then invoke - // "hot reload" (press "r" in the console where you ran "flutter run", - // or simply save your changes to "hot reload" in a Flutter IDE). - // Notice that the counter didn't reset back to zero; the application - // is not restarted. - primarySwatch: Colors.blue, - // This makes the visual density adapt to the platform that you run - // the app on. For desktop platforms, the controls will be smaller and - // closer together (more dense) than on mobile platforms. - visualDensity: VisualDensity.adaptivePlatformDensity, - ), - home: MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - MyHomePage({Key key, this.title}) : super(key: key); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". +import 'src/app.dart'; - final String title; - - @override - _MyHomePageState createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headline4, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. - ); - } +void main() { + runApp(App()); } diff --git a/mobile/lib/src/app.dart b/mobile/lib/src/app.dart new file mode 100644 index 00000000..168bc419 --- /dev/null +++ b/mobile/lib/src/app.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'presentation/counter_route/counter_route.dart'; +import 'locator.dart'; +import 'theme.dart'; + +class App extends StatelessWidget { + @override + Widget build(BuildContext context) { + /// Cubits are provided globally down all of the context tree + return MultiBlocProvider( + providers: createBlocProviders(), + child: MaterialApp( + title: 'PolyDodo', + theme: theme, + home: CounterRoute(), + initialRoute: CounterRoute.name, + routes: { + CounterRoute.name: (context) => CounterRoute(), + }, + ), + ); + } +} diff --git a/mobile/lib/src/application/counter/counter_cubit.dart b/mobile/lib/src/application/counter/counter_cubit.dart new file mode 100644 index 00000000..70bd952a --- /dev/null +++ b/mobile/lib/src/application/counter/counter_cubit.dart @@ -0,0 +1,8 @@ +import 'package:bloc/bloc.dart'; + +class CounterCubit extends Cubit { + CounterCubit() : super(0); + + void increment() => emit(state + 1); + void decrement() => emit(state - 1); +} diff --git a/mobile/lib/src/locator.dart b/mobile/lib/src/locator.dart new file mode 100644 index 00000000..098b21c9 --- /dev/null +++ b/mobile/lib/src/locator.dart @@ -0,0 +1,10 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'application/counter/counter_cubit.dart'; + +/// This function creates all the BlocProviders used in this app +List createBlocProviders() => [ + BlocProvider( + create: (context) => CounterCubit(), + ), + ]; diff --git a/mobile/lib/src/presentation/counter_route/counter_route.dart b/mobile/lib/src/presentation/counter_route/counter_route.dart new file mode 100644 index 00000000..2c502ee4 --- /dev/null +++ b/mobile/lib/src/presentation/counter_route/counter_route.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:polydodo/src/application/counter/counter_cubit.dart'; + +import 'counter_text.dart'; + +class CounterRoute extends StatelessWidget { + static const name = 'counterRoute'; + + CounterRoute({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Polydodo')), + body: Center( + child: CounterText(), + ), + floatingActionButton: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + FloatingActionButton( + key: const Key('counterRoute_increment_fab'), + child: const Icon(Icons.add), + onPressed: () => BlocProvider.of(context).increment(), + ), + const SizedBox(height: 8), + FloatingActionButton( + key: const Key('counterRoute_decrement_fab'), + child: const Icon(Icons.remove), + onPressed: () => BlocProvider.of(context).decrement(), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/src/presentation/counter_route/counter_text.dart b/mobile/lib/src/presentation/counter_route/counter_text.dart new file mode 100644 index 00000000..dc430777 --- /dev/null +++ b/mobile/lib/src/presentation/counter_route/counter_text.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:polydodo/src/application/counter/counter_cubit.dart'; + +class CounterText extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('You have pushed the button this many times:'), + Text('$state', style: Theme.of(context).textTheme.headline2), + ], + ); + }, + ); + } +} diff --git a/mobile/lib/src/theme.dart b/mobile/lib/src/theme.dart new file mode 100644 index 00000000..893c05cd --- /dev/null +++ b/mobile/lib/src/theme.dart @@ -0,0 +1,6 @@ +import 'package:flutter/material.dart'; + +final theme = ThemeData( + primarySwatch: Colors.blue, + visualDensity: VisualDensity.adaptivePlatformDensity, +); diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 03161685..67166363 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -21,7 +21,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety" + version: "2.4.1" battery: dependency: "direct main" description: @@ -49,35 +49,28 @@ packages: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0-nullsafety.2" + version: "2.0.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety" + version: "1.1.3" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety" + version: "1.0.1" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.2" + version: "1.14.12" convert: dependency: transitive description: @@ -105,7 +98,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety" + version: "1.1.0" fixnum: dependency: transitive description: @@ -150,14 +143,14 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety" + version: "0.12.6" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.2" + version: "1.1.8" mime: dependency: transitive description: @@ -178,7 +171,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety" + version: "1.7.0" plugin_platform_interface: dependency: transitive description: @@ -225,49 +218,49 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety" + version: "1.7.0" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety" + version: "1.9.3" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety" + version: "2.0.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety" + version: "1.0.5" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety" + version: "1.1.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety" + version: "0.2.16" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.2" + version: "1.1.6" usb_serial: dependency: "direct main" description: @@ -281,7 +274,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.2" + version: "2.0.8" sdks: - dart: ">=2.10.0-0.0.dev <2.10.0" + dart: ">=2.7.0 <3.0.0" flutter: ">=1.16.0 <2.0.0" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 5409c88d..8ae20b40 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -1,4 +1,4 @@ -name: Polydodo +name: polydodo description: A new Flutter project. # The following line prevents the package from being accidentally published to From 8697b3514ff137721d0ea32850e08dbd925ca8c6 Mon Sep 17 00:00:00 2001 From: William Harvey Date: Tue, 15 Sep 2020 00:02:41 -0400 Subject: [PATCH 04/56] create and implement domain layer --- mobile/lib/src/domain/unique_id.dart | 20 +++++++++ .../domain/wallet/i_wallet_repository.dart | 8 ++++ mobile/lib/src/domain/wallet/money.dart | 41 +++++++++++++++++++ mobile/lib/src/domain/wallet/owner.dart | 26 ++++++++++++ mobile/lib/src/domain/wallet/wallet.dart | 29 +++++++++++++ mobile/pubspec.lock | 14 +++++++ mobile/pubspec.yaml | 16 ++++---- 7 files changed, 147 insertions(+), 7 deletions(-) create mode 100644 mobile/lib/src/domain/unique_id.dart create mode 100644 mobile/lib/src/domain/wallet/i_wallet_repository.dart create mode 100644 mobile/lib/src/domain/wallet/money.dart create mode 100644 mobile/lib/src/domain/wallet/owner.dart create mode 100644 mobile/lib/src/domain/wallet/wallet.dart diff --git a/mobile/lib/src/domain/unique_id.dart b/mobile/lib/src/domain/unique_id.dart new file mode 100644 index 00000000..9b301f89 --- /dev/null +++ b/mobile/lib/src/domain/unique_id.dart @@ -0,0 +1,20 @@ +import 'package:equatable/equatable.dart'; +import 'package:uuid/uuid.dart'; + +class UniqueId extends Equatable { + final String _id; + + UniqueId() : _id = Uuid().v4(); + + UniqueId.from(String unique) + : assert(unique != null), + _id = unique; + + String get() => _id; + + @override + List get props => [_id]; + + @override + bool get stringify => true; +} diff --git a/mobile/lib/src/domain/wallet/i_wallet_repository.dart b/mobile/lib/src/domain/wallet/i_wallet_repository.dart new file mode 100644 index 00000000..b03ae674 --- /dev/null +++ b/mobile/lib/src/domain/wallet/i_wallet_repository.dart @@ -0,0 +1,8 @@ +import 'package:polydodo/src/domain/unique_id.dart'; + +import 'wallet.dart'; + +abstract class IWalletRepository { + Future write(Wallet wallet); + Stream watch(UniqueId walletId); +} diff --git a/mobile/lib/src/domain/wallet/money.dart b/mobile/lib/src/domain/wallet/money.dart new file mode 100644 index 00000000..0408acf4 --- /dev/null +++ b/mobile/lib/src/domain/wallet/money.dart @@ -0,0 +1,41 @@ +import 'package:equatable/equatable.dart'; + +enum Currency { usd, cad } + +// Value object usually extends equatable +class Money extends Equatable { + static const _min = 0; + static const _usdToCadRate = 1.32; + + final double value; + final Currency currency; + + Money(this.value, this.currency) + : assert(value != null), + assert(currency != null) { + // Validation that money cannot be in an impossible state + if (value <= _min) { + Exception('Money amount cannot be inferior to $_min'); + } + } + + double get usd { + if (currency == Currency.cad) { + return value / _usdToCadRate; + } + return value; + } + + double get cad { + if (currency == Currency.usd) { + return value * _usdToCadRate; + } + return value; + } + + Money operator +(Money other) => Money(this.usd + other.usd, Currency.usd); + Money operator -(Money other) => Money(this.usd - other.usd, Currency.usd); + + @override + List get props => [usd]; +} diff --git a/mobile/lib/src/domain/wallet/owner.dart b/mobile/lib/src/domain/wallet/owner.dart new file mode 100644 index 00000000..c4c19c4a --- /dev/null +++ b/mobile/lib/src/domain/wallet/owner.dart @@ -0,0 +1,26 @@ +import 'package:polydodo/src/domain/unique_id.dart'; + +class Owner { + static const _legalAge = 16; + + final UniqueId id; + final String firstName; + final String lastName; + final int age; + + Owner(this.id, this.firstName, this.lastName, this.age) + : assert(id != null), + assert(firstName != null), + assert(lastName != null) { + if (firstName.length <= 0 || lastName.length <= 0) { + throw Exception('First and last name cannot be empty'); + } + if (!_hasLegalAge()) { + throw Exception('This owner does not have the legal age'); + } + } + + bool _hasLegalAge() => age >= _legalAge; + + // In a good model entity, we should have behavorial method... +} diff --git a/mobile/lib/src/domain/wallet/wallet.dart b/mobile/lib/src/domain/wallet/wallet.dart new file mode 100644 index 00000000..1866899a --- /dev/null +++ b/mobile/lib/src/domain/wallet/wallet.dart @@ -0,0 +1,29 @@ +import 'package:polydodo/src/domain/unique_id.dart'; + +import 'money.dart'; +import 'owner.dart'; + +// We call Wallet an aggregate root because it own other entities +class Wallet { + final UniqueId id; + final Owner owner; + Money money; + + Wallet(this.id, this.owner, this.money); + + // Wallet is an entity and therefore it must have a behavior. It is here that + // business logic appears. + void transfer(Money amount, Wallet receiver) { + if (money.usd - amount.usd < 0) { + throw Exception( + 'Not enough money in this wallet to perform this transaction', + ); + } + money -= amount; + receiver.receive(amount); + } + + void receive(Money amount) { + money += amount; + } +} diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 67166363..f3a4a78e 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -92,6 +92,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.3" + equatable: + dependency: "direct main" + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.5" fake_async: dependency: transitive description: @@ -268,6 +275,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.4" + uuid: + dependency: "direct main" + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.2" vector_math: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 8ae20b40..dd617208 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -26,16 +26,18 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.3 - protobuf: ^1.0.1 - hive: ^1.4.4 - usb_serial: ^0.2.4 - flutter_blue: ^0.7.2 + archive: ^2.0.13 + battery: ^1.0.5 bloc: ^6.0.3 + cupertino_icons: ^0.1.3 + equatable: ^1.2.5 flutter_bloc: ^6.0.5 - battery: ^1.0.5 + flutter_blue: ^0.7.2 + hive: ^1.4.4 + protobuf: ^1.0.1 share: ^0.6.5 - archive: ^2.0.13 + usb_serial: ^0.2.4 + uuid: ^2.2.2 dev_dependencies: flutter_test: From 38e939eb783db980e7ad93c8f3580692258ee569 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Thu, 17 Sep 2020 13:10:12 -0400 Subject: [PATCH 05/56] Connection bluetooth avec le ganglion --- .../android/app/src/main/AndroidManifest.xml | 3 + mobile/lib/main.dart | 142 ++++++++++++------ mobile/pubspec.lock | 38 ++--- 3 files changed, 121 insertions(+), 62 deletions(-) diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index 515ad969..61ea027c 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -44,4 +44,7 @@ android:name="flutterEmbedding" android:value="2" /> + + + diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 11655b66..9c4ca0bd 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_blue/flutter_blue.dart'; void main() { runApp(MyApp()); @@ -44,25 +45,114 @@ class MyHomePage extends StatefulWidget { // always marked "final". final String title; + final FlutterBlue flutterBlue = FlutterBlue.instance; + final List devicesList = new List(); + final Map> readValues = new Map>(); @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State { - int _counter = 0; - void _incrementCounter() { + static String BLE_SERVICE = "fe84"; + static String BLE_RECEIVE = "2d30c082"; + static String BLE_SEND = "2d30c083"; + static String BLE_DISCONNECT = "2d30c084"; + + + BluetoothDevice _connectedDevice; + List _services; + List _characteristics; + + void addDeviceTolist(final BluetoothDevice device) { + if (!widget.devicesList.contains(device)) { + setState(() { + widget.devicesList.add(device); + print(widget.devicesList); + }); + } + } + + void connectDevice(BluetoothDevice device) async { + try { + await device.connect(); + } catch (e) { + if (e.code != 'already_connected') { + throw e; + } + } finally { + _services = await device.discoverServices(); + } setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; + _connectedDevice = device; }); } + @override + void initState() { + super.initState(); + widget.flutterBlue.connectedDevices + .asStream() + .listen((List devices) { + for (BluetoothDevice device in devices) { + addDeviceTolist(device); + } + }); + widget.flutterBlue.scanResults.listen((List results) { + for (ScanResult result in results) { + addDeviceTolist(result.device); + } + }); + widget.flutterBlue.startScan(); + } + + ListView buildListViewOfDevices() { + return ListView.builder( + itemCount: widget.devicesList.length, + itemBuilder: (context, index) { + return Card( + child: ListTile( + onTap: () => connectDevice(widget.devicesList[index]), + title: Text(widget.devicesList[index].name), + subtitle: Text(widget.devicesList[index].id.toString())), + ); + }); + } + + ListView buildListViewOfConnectedDevice() { + return ListView.builder( + itemCount: _services.length, + itemBuilder: (context, index) { + return Card( + child: ListTile( + onTap: () => setState(() => {_characteristics = _services[index].characteristics}), + title: Text(_services[index].uuid.toString().contains("fe84") ? "YES" : "NO"), + )); + }); + } + + ListView buildListViewOfCharacteristics() { + return ListView.builder( + itemCount: _characteristics.length, + itemBuilder: (context, index) { + return Card( + child: ListTile( + title: Text(_characteristics[index].uuid.toString().contains("2d30c082") ? "YES" : "NO"), + )); + }); + } + + ListView _buildView() { + if (_connectedDevice != null) { + if (_characteristics != null) + return buildListViewOfCharacteristics(); + else + return buildListViewOfConnectedDevice(); + } + return buildListViewOfDevices(); + } + @override Widget build(BuildContext context) { // This method is rerun every time setState is called, for instance as done @@ -77,41 +167,7 @@ class _MyHomePageState extends State { // the App.build method, and use it to set our appbar title. title: Text(widget.title), ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headline4, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. + body: _buildView(), ); } } diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 03161685..f30995f0 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -21,7 +21,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety" + version: "2.4.2" battery: dependency: "direct main" description: @@ -49,35 +49,35 @@ packages: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety" + version: "2.0.0" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.2" + version: "1.0.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety" + version: "1.1.3" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety" + version: "1.0.1" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.2" + version: "1.14.13" convert: dependency: transitive description: @@ -105,7 +105,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety" + version: "1.1.0" fixnum: dependency: transitive description: @@ -150,14 +150,14 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety" + version: "0.12.8" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.2" + version: "1.1.8" mime: dependency: transitive description: @@ -178,7 +178,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety" + version: "1.7.0" plugin_platform_interface: dependency: transitive description: @@ -225,49 +225,49 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety" + version: "1.7.0" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety" + version: "1.9.5" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety" + version: "2.0.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety" + version: "1.0.5" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety" + version: "1.1.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety" + version: "0.2.17" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.2" + version: "1.2.0" usb_serial: dependency: "direct main" description: @@ -281,7 +281,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.2" + version: "2.0.8" sdks: - dart: ">=2.10.0-0.0.dev <2.10.0" + dart: ">=2.9.0-14.0.dev <3.0.0" flutter: ">=1.16.0 <2.0.0" From 15e4ab8fe7822d2e7ebf97ab8887dd3214120ac4 Mon Sep 17 00:00:00 2001 From: William Harvey Date: Sat, 19 Sep 2020 00:28:39 -0400 Subject: [PATCH 06/56] Create Flutter code structure example based on this [arch](https://github.com/PolyCortex/polydodo/wiki/Mobile-Architecture) --- mobile/lib/src/app.dart | 12 ++-- .../application/counter/counter_cubit.dart | 8 --- .../application/wallets/wallets_cubit.dart | 63 +++++++++++++++++++ .../application/wallets/wallets_state.dart | 25 ++++++++ mobile/lib/src/domain/wallet/constants.dart | 1 + .../domain/wallet/i_wallet_repository.dart | 6 +- mobile/lib/src/domain/wallet/money.dart | 26 ++++++-- mobile/lib/src/domain/wallet/owner.dart | 10 ++- mobile/lib/src/domain/wallet/wallet.dart | 10 ++- .../mock_wallet_repository.dart | 22 +++++++ mobile/lib/src/locator.dart | 21 ++++++- .../counter_route/counter_route.dart | 39 ------------ .../counter_route/counter_text.dart | 21 ------- .../presentation/wallets/wallet_widget.dart | 23 +++++++ .../presentation/wallets/wallets_route.dart | 62 ++++++++++++++++++ .../presentation/wallets/wallets_widget.dart | 32 ++++++++++ mobile/pubspec.lock | 9 ++- mobile/pubspec.yaml | 2 + 18 files changed, 303 insertions(+), 89 deletions(-) delete mode 100644 mobile/lib/src/application/counter/counter_cubit.dart create mode 100644 mobile/lib/src/application/wallets/wallets_cubit.dart create mode 100644 mobile/lib/src/application/wallets/wallets_state.dart create mode 100644 mobile/lib/src/domain/wallet/constants.dart create mode 100644 mobile/lib/src/infrastructure/mock_wallet_repository.dart delete mode 100644 mobile/lib/src/presentation/counter_route/counter_route.dart delete mode 100644 mobile/lib/src/presentation/counter_route/counter_text.dart create mode 100644 mobile/lib/src/presentation/wallets/wallet_widget.dart create mode 100644 mobile/lib/src/presentation/wallets/wallets_route.dart create mode 100644 mobile/lib/src/presentation/wallets/wallets_widget.dart diff --git a/mobile/lib/src/app.dart b/mobile/lib/src/app.dart index 168bc419..be3bfef4 100644 --- a/mobile/lib/src/app.dart +++ b/mobile/lib/src/app.dart @@ -1,11 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'presentation/counter_route/counter_route.dart'; +import 'presentation/wallets/wallets_route.dart'; import 'locator.dart'; import 'theme.dart'; class App extends StatelessWidget { + App() { + registerServices(); + } + @override Widget build(BuildContext context) { /// Cubits are provided globally down all of the context tree @@ -14,10 +18,10 @@ class App extends StatelessWidget { child: MaterialApp( title: 'PolyDodo', theme: theme, - home: CounterRoute(), - initialRoute: CounterRoute.name, + home: WalletsRoute(), + initialRoute: WalletsRoute.name, routes: { - CounterRoute.name: (context) => CounterRoute(), + WalletsRoute.name: (context) => WalletsRoute(), }, ), ); diff --git a/mobile/lib/src/application/counter/counter_cubit.dart b/mobile/lib/src/application/counter/counter_cubit.dart deleted file mode 100644 index 70bd952a..00000000 --- a/mobile/lib/src/application/counter/counter_cubit.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:bloc/bloc.dart'; - -class CounterCubit extends Cubit { - CounterCubit() : super(0); - - void increment() => emit(state + 1); - void decrement() => emit(state - 1); -} diff --git a/mobile/lib/src/application/wallets/wallets_cubit.dart b/mobile/lib/src/application/wallets/wallets_cubit.dart new file mode 100644 index 00000000..6ab7d3e3 --- /dev/null +++ b/mobile/lib/src/application/wallets/wallets_cubit.dart @@ -0,0 +1,63 @@ +import 'dart:math'; + +import 'package:bloc/bloc.dart'; + +import 'package:polydodo/src/application/wallets/wallets_state.dart'; +import 'package:polydodo/src/domain/unique_id.dart'; +import 'package:polydodo/src/domain/wallet/i_wallet_repository.dart'; +import 'package:polydodo/src/domain/wallet/money.dart'; +import 'package:polydodo/src/domain/wallet/owner.dart'; +import 'package:polydodo/src/domain/wallet/wallet.dart'; + +class WalletsCubit extends Cubit { + final IWalletRepository _walletRepository; + + WalletsCubit(this._walletRepository) : super(WalletsInitial()) { + createNewWallets(); + emit(WalletsLoadInProgress()); + _walletRepository + .watch() + .listen((wallets) => emit(WalletsLoadSuccess(wallets))) + .onError((e) => emit(WalletsLoadFailure(e))); + } + + // Does not yield another state + void transfer(Wallet sender, Wallet receiver, Money amount) async { + try { + sender.transfer(amount, receiver); + } catch (e) { + emit(WalletsTransferFailure(e)); + } + await Future.wait([ + _walletRepository.store(sender), + _walletRepository.store(receiver), + ]).catchError((e) => emit(WalletsTransferFailure(e))); + } + + Future> createNewWallets() => Future.wait([ + _walletRepository.store( + Wallet( + UniqueId(), + Owner( + id: UniqueId.from('1'), + firstName: 'Bob', + lastName: 'Skiridovsky', + age: 25, + ), + Money(Random().nextDouble() * 10, Currency.cad), + ), + ), + _walletRepository.store( + Wallet( + UniqueId(), + Owner( + id: UniqueId.from('2'), + firstName: 'Alice', + lastName: 'Pogo', + age: 35, + ), + Money(Random().nextDouble() * 1000, Currency.cad), + ), + ) + ]); +} diff --git a/mobile/lib/src/application/wallets/wallets_state.dart b/mobile/lib/src/application/wallets/wallets_state.dart new file mode 100644 index 00000000..01788a39 --- /dev/null +++ b/mobile/lib/src/application/wallets/wallets_state.dart @@ -0,0 +1,25 @@ +import 'package:polydodo/src/domain/wallet/wallet.dart'; + +abstract class WalletsState {} + +class WalletsInitial extends WalletsState {} + +class WalletsLoadInProgress extends WalletsState {} + +class WalletsLoadSuccess extends WalletsState { + final List wallets; + + WalletsLoadSuccess(this.wallets); +} + +class WalletsLoadFailure extends WalletsState { + final Exception cause; + + WalletsLoadFailure(this.cause); +} + +class WalletsTransferFailure extends WalletsState { + final Exception cause; + + WalletsTransferFailure(this.cause); +} diff --git a/mobile/lib/src/domain/wallet/constants.dart b/mobile/lib/src/domain/wallet/constants.dart new file mode 100644 index 00000000..595ccd29 --- /dev/null +++ b/mobile/lib/src/domain/wallet/constants.dart @@ -0,0 +1 @@ +const cadToUsdRate = 1.32; diff --git a/mobile/lib/src/domain/wallet/i_wallet_repository.dart b/mobile/lib/src/domain/wallet/i_wallet_repository.dart index b03ae674..d1f87cff 100644 --- a/mobile/lib/src/domain/wallet/i_wallet_repository.dart +++ b/mobile/lib/src/domain/wallet/i_wallet_repository.dart @@ -1,8 +1,6 @@ -import 'package:polydodo/src/domain/unique_id.dart'; - import 'wallet.dart'; abstract class IWalletRepository { - Future write(Wallet wallet); - Stream watch(UniqueId walletId); + Future store(Wallet wallet); + Stream> watch(); } diff --git a/mobile/lib/src/domain/wallet/money.dart b/mobile/lib/src/domain/wallet/money.dart index 0408acf4..1f760cea 100644 --- a/mobile/lib/src/domain/wallet/money.dart +++ b/mobile/lib/src/domain/wallet/money.dart @@ -1,11 +1,17 @@ import 'package:equatable/equatable.dart'; +import 'constants.dart'; + enum Currency { usd, cad } +Map currencyToString = { + Currency.usd: 'USD', + Currency.cad: 'CAD', +}; + // Value object usually extends equatable class Money extends Equatable { static const _min = 0; - static const _usdToCadRate = 1.32; final double value; final Currency currency; @@ -21,20 +27,30 @@ class Money extends Equatable { double get usd { if (currency == Currency.cad) { - return value / _usdToCadRate; + return value / cadToUsdRate; } return value; } double get cad { if (currency == Currency.usd) { - return value * _usdToCadRate; + return value * cadToUsdRate; } return value; } - Money operator +(Money other) => Money(this.usd + other.usd, Currency.usd); - Money operator -(Money other) => Money(this.usd - other.usd, Currency.usd); + Money convertTo(Currency currency) { + if (currency == Currency.usd) { + return Money(usd, currency); + } + return Money(cad, currency); + } + + /// Gives back result into left operand's currency + Money operator +(Money other) => + Money(this.usd + other.usd, Currency.usd).convertTo(this.currency); + Money operator -(Money other) => + Money(this.usd - other.usd, Currency.usd).convertTo(this.currency); @override List get props => [usd]; diff --git a/mobile/lib/src/domain/wallet/owner.dart b/mobile/lib/src/domain/wallet/owner.dart index c4c19c4a..a1360dbf 100644 --- a/mobile/lib/src/domain/wallet/owner.dart +++ b/mobile/lib/src/domain/wallet/owner.dart @@ -1,3 +1,5 @@ +import 'package:meta/meta.dart'; + import 'package:polydodo/src/domain/unique_id.dart'; class Owner { @@ -8,8 +10,12 @@ class Owner { final String lastName; final int age; - Owner(this.id, this.firstName, this.lastName, this.age) - : assert(id != null), + Owner({ + @required this.id, + @required this.firstName, + @required this.lastName, + @required this.age, + }) : assert(id != null), assert(firstName != null), assert(lastName != null) { if (firstName.length <= 0 || lastName.length <= 0) { diff --git a/mobile/lib/src/domain/wallet/wallet.dart b/mobile/lib/src/domain/wallet/wallet.dart index 1866899a..0c1dffcf 100644 --- a/mobile/lib/src/domain/wallet/wallet.dart +++ b/mobile/lib/src/domain/wallet/wallet.dart @@ -20,10 +20,16 @@ class Wallet { ); } money -= amount; - receiver.receive(amount); + receiver._receive(amount); } - void receive(Money amount) { + void _receive(Money amount) { money += amount; } + + @override + bool operator ==(other) => this.id == other.id; + + @override + int get hashCode => super.hashCode; } diff --git a/mobile/lib/src/infrastructure/mock_wallet_repository.dart b/mobile/lib/src/infrastructure/mock_wallet_repository.dart new file mode 100644 index 00000000..3e8cc516 --- /dev/null +++ b/mobile/lib/src/infrastructure/mock_wallet_repository.dart @@ -0,0 +1,22 @@ +import 'dart:async'; + +import 'package:polydodo/src/domain/wallet/i_wallet_repository.dart'; +import 'package:polydodo/src/domain/wallet/wallet.dart'; + +class MockWalletRepository implements IWalletRepository { + static List walletsMockPersistency = []; + final streamController = StreamController>(); + + @override + Future store(Wallet wallet) async { + await Future.delayed(Duration(milliseconds: 400)); + final idx = walletsMockPersistency.indexOf(wallet); + idx == -1 + ? walletsMockPersistency.add(wallet) + : walletsMockPersistency[idx] = wallet; + streamController.add(walletsMockPersistency); + } + + @override + Stream> watch() => streamController.stream; +} diff --git a/mobile/lib/src/locator.dart b/mobile/lib/src/locator.dart index 098b21c9..bac78b6f 100644 --- a/mobile/lib/src/locator.dart +++ b/mobile/lib/src/locator.dart @@ -1,10 +1,25 @@ import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; -import 'application/counter/counter_cubit.dart'; +import 'package:polydodo/src/domain/wallet/i_wallet_repository.dart'; +import 'package:polydodo/src/infrastructure/mock_wallet_repository.dart'; + +import 'application/wallets/wallets_cubit.dart'; + +/// Private GetIt instance as we want all DI to be performed here in this file +final _serviceLocator = GetIt.asNewInstance(); + +void registerServices() { + _serviceLocator.registerSingleton( + MockWalletRepository(), + ); +} /// This function creates all the BlocProviders used in this app List createBlocProviders() => [ - BlocProvider( - create: (context) => CounterCubit(), + BlocProvider( + create: (context) => WalletsCubit( + _serviceLocator.get(), + ), ), ]; diff --git a/mobile/lib/src/presentation/counter_route/counter_route.dart b/mobile/lib/src/presentation/counter_route/counter_route.dart deleted file mode 100644 index 2c502ee4..00000000 --- a/mobile/lib/src/presentation/counter_route/counter_route.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import 'package:polydodo/src/application/counter/counter_cubit.dart'; - -import 'counter_text.dart'; - -class CounterRoute extends StatelessWidget { - static const name = 'counterRoute'; - - CounterRoute({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text('Polydodo')), - body: Center( - child: CounterText(), - ), - floatingActionButton: Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - FloatingActionButton( - key: const Key('counterRoute_increment_fab'), - child: const Icon(Icons.add), - onPressed: () => BlocProvider.of(context).increment(), - ), - const SizedBox(height: 8), - FloatingActionButton( - key: const Key('counterRoute_decrement_fab'), - child: const Icon(Icons.remove), - onPressed: () => BlocProvider.of(context).decrement(), - ), - ], - ), - ); - } -} diff --git a/mobile/lib/src/presentation/counter_route/counter_text.dart b/mobile/lib/src/presentation/counter_route/counter_text.dart deleted file mode 100644 index dc430777..00000000 --- a/mobile/lib/src/presentation/counter_route/counter_text.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import 'package:polydodo/src/application/counter/counter_cubit.dart'; - -class CounterText extends StatelessWidget { - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('You have pushed the button this many times:'), - Text('$state', style: Theme.of(context).textTheme.headline2), - ], - ); - }, - ); - } -} diff --git a/mobile/lib/src/presentation/wallets/wallet_widget.dart b/mobile/lib/src/presentation/wallets/wallet_widget.dart new file mode 100644 index 00000000..426e3dc6 --- /dev/null +++ b/mobile/lib/src/presentation/wallets/wallet_widget.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:polydodo/src/domain/wallet/money.dart'; +import 'package:polydodo/src/domain/wallet/wallet.dart'; + +class WalletWidget extends StatelessWidget { + final Wallet wallet; + + const WalletWidget({Key key, @required this.wallet}) : super(key: key); + + @override + Widget build(BuildContext context) { + final currency = currencyToString[wallet.money.currency]; + return Container( + child: Column( + children: [ + Text('${wallet.owner.firstName} ${wallet.owner.lastName}'), + Text('ID: ${wallet.owner.id.get()}'), + Text('${wallet.money.value.toStringAsFixed(2)} $currency'), + ], + ), + ); + } +} diff --git a/mobile/lib/src/presentation/wallets/wallets_route.dart b/mobile/lib/src/presentation/wallets/wallets_route.dart new file mode 100644 index 00000000..842784d5 --- /dev/null +++ b/mobile/lib/src/presentation/wallets/wallets_route.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:polydodo/src/application/wallets/wallets_cubit.dart'; +import 'package:polydodo/src/application/wallets/wallets_state.dart'; +import 'package:polydodo/src/domain/wallet/constants.dart'; +import 'package:polydodo/src/domain/wallet/money.dart'; + +import 'wallets_widget.dart'; + +class WalletsRoute extends StatelessWidget { + static const name = 'walletsRoute'; + + WalletsRoute({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Polydodo')), + body: BlocConsumer( + listener: (context, state) { + print(state.runtimeType); + if (state is WalletsLoadFailure) { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text('Unable to load Wallets because ${state.cause}'), + )); + } else if (state is WalletsTransferFailure) { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text('Unable to transfer money because ${state.cause}'), + )); + } + }, + builder: (context, state) { + return Center( + child: Column( + children: [ + Text('CAD to USD Rate: $cadToUsdRate'), + WalletsWidget(state: state), + if (state is WalletsLoadSuccess) + _buildTransferButton(context, state), + ], + ), + ); + }, + ), + ); + } + + Widget _buildTransferButton(BuildContext context, WalletsLoadSuccess state) { + final sender = state.wallets[0]; + final receiver = state.wallets[1]; + return RaisedButton( + child: Text( + 'Send 1 USD from ${sender.owner.firstName} to ${receiver.owner.firstName}', + ), + onPressed: () => BlocProvider.of(context).transfer( + sender, + receiver, + Money(1, Currency.usd), + ), + ); + } +} diff --git a/mobile/lib/src/presentation/wallets/wallets_widget.dart b/mobile/lib/src/presentation/wallets/wallets_widget.dart new file mode 100644 index 00000000..3e8b643d --- /dev/null +++ b/mobile/lib/src/presentation/wallets/wallets_widget.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:polydodo/src/application/wallets/wallets_state.dart'; + +import 'wallet_widget.dart'; + +class WalletsWidget extends StatelessWidget { + final WalletsState state; + + const WalletsWidget({Key key, @required this.state}) : super(key: key); + + @override + Widget build(BuildContext context) { + if (state is WalletsLoadSuccess) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: (state as WalletsLoadSuccess) + .wallets + .map((wallet) => Container( + child: WalletWidget(wallet: wallet), + margin: EdgeInsets.all(24), + )) + .toList(), + ); + } + + return SizedBox( + child: CircularProgressIndicator(), + height: 50, + width: 50, + ); + } +} diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index f3a4a78e..6c957e89 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -137,6 +137,13 @@ packages: description: flutter source: sdk version: "0.0.0" + get_it: + dependency: "direct main" + description: + name: get_it + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.4" hive: dependency: "direct main" description: @@ -152,7 +159,7 @@ packages: source: hosted version: "0.12.6" meta: - dependency: transitive + dependency: "direct main" description: name: meta url: "https://pub.dartlang.org" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index dd617208..5cce6d6d 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -33,7 +33,9 @@ dependencies: equatable: ^1.2.5 flutter_bloc: ^6.0.5 flutter_blue: ^0.7.2 + get_it: ^4.0.4 hive: ^1.4.4 + meta: ^1.1.8 protobuf: ^1.0.1 share: ^0.6.5 usb_serial: ^0.2.4 From 428d554239c6ca88041b22f00768d9b0d6902be1 Mon Sep 17 00:00:00 2001 From: William Harvey Date: Sat, 19 Sep 2020 13:32:32 -0400 Subject: [PATCH 07/56] Remove widget test specific to counter app --- mobile/test/widget_test.dart | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 mobile/test/widget_test.dart diff --git a/mobile/test/widget_test.dart b/mobile/test/widget_test.dart deleted file mode 100644 index 53a930a0..00000000 --- a/mobile/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:Polydodo/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} From 118ed8586a1e44e3c5f59481178f4fcba31594e5 Mon Sep 17 00:00:00 2001 From: William Harvey Date: Sat, 19 Sep 2020 14:19:55 -0400 Subject: [PATCH 08/56] Create an empty test for pipeline to perform build --- mobile/.gitignore | 1 + mobile/lib/test/widget_test.dart | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 mobile/lib/test/widget_test.dart diff --git a/mobile/.gitignore b/mobile/.gitignore index 9d532b18..eca61f42 100644 --- a/mobile/.gitignore +++ b/mobile/.gitignore @@ -8,6 +8,7 @@ .buildlog/ .history .svn/ +.vscode/ # IntelliJ related *.iml diff --git a/mobile/lib/test/widget_test.dart b/mobile/lib/test/widget_test.dart new file mode 100644 index 00000000..25ac0578 --- /dev/null +++ b/mobile/lib/test/widget_test.dart @@ -0,0 +1,5 @@ +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async {}); +} From baebeabfbdb4864db765151c2c526cfc27930884 Mon Sep 17 00:00:00 2001 From: William Harvey Date: Sat, 19 Sep 2020 14:28:47 -0400 Subject: [PATCH 09/56] Create an empty test in order for build pipeline to pass --- mobile/lib/test/widget_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/test/widget_test.dart b/mobile/lib/test/widget_test.dart index 25ac0578..295d8358 100644 --- a/mobile/lib/test/widget_test.dart +++ b/mobile/lib/test/widget_test.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async {}); + testWidgets('Test', (WidgetTester tester) async {}); } From 0c2397572395b9c19602a6431507951f00cdaaba Mon Sep 17 00:00:00 2001 From: William Harvey Date: Sat, 19 Sep 2020 16:55:26 -0400 Subject: [PATCH 10/56] fix mistake: move test/ under mobile/ --- mobile/pubspec.lock | 43 +++++++++++++++----------- mobile/pubspec.yaml | 4 +-- mobile/{lib => }/test/widget_test.dart | 0 3 files changed, 27 insertions(+), 20 deletions(-) rename mobile/{lib => }/test/widget_test.dart (100%) diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 6c957e89..cf28699b 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -21,7 +21,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.1" + version: "2.5.0-nullsafety" battery: dependency: "direct main" description: @@ -49,28 +49,35 @@ packages: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0-nullsafety" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0-nullsafety.2" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" + version: "1.2.0-nullsafety" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.1.0-nullsafety" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.12" + version: "1.15.0-nullsafety.2" convert: dependency: transitive description: @@ -105,7 +112,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.0-nullsafety" fixnum: dependency: transitive description: @@ -157,14 +164,14 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.6" + version: "0.12.10-nullsafety" meta: dependency: "direct main" description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.8" + version: "1.3.0-nullsafety.2" mime: dependency: transitive description: @@ -185,7 +192,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0-nullsafety" plugin_platform_interface: dependency: transitive description: @@ -232,49 +239,49 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0-nullsafety.1" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.10.0-nullsafety" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0-nullsafety" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0-nullsafety" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0-nullsafety" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.16" + version: "0.2.19-nullsafety" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.3.0-nullsafety.2" usb_serial: dependency: "direct main" description: @@ -295,7 +302,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.0-nullsafety.2" sdks: - dart: ">=2.7.0 <3.0.0" + dart: ">=2.10.0-137.0 <2.10.0" flutter: ">=1.16.0 <2.0.0" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 5cce6d6d..c7f7dd29 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -3,7 +3,7 @@ description: A new Flutter project. # The following line prevents the package from being accidentally published to # pub.dev using `pub publish`. This is preferred for private packages. -publish_to: "none" # Remove this line if you wish to publish to pub.dev +publish_to: 'none' # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 @@ -18,7 +18,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: '>=2.7.0 <3.0.0' dependencies: flutter: diff --git a/mobile/lib/test/widget_test.dart b/mobile/test/widget_test.dart similarity index 100% rename from mobile/lib/test/widget_test.dart rename to mobile/test/widget_test.dart From 37b8cc24139e8afccc525939dcf8f774f7308ba2 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Sun, 20 Sep 2020 19:36:23 -0400 Subject: [PATCH 11/56] Requete de donnees et enregistrement sur csv --- .../android/app/src/main/AndroidManifest.xml | 1 + mobile/lib/main.dart | 110 ++++++++++++++++-- mobile/pubspec.lock | 98 ++++++++++++++++ mobile/pubspec.yaml | 3 + 4 files changed, 204 insertions(+), 8 deletions(-) diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index 61ea027c..98c8e964 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -47,4 +47,5 @@ + diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 9c4ca0bd..508da0ce 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -1,5 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_blue/flutter_blue.dart'; +import 'package:binary/binary.dart'; +import 'package:csv/csv.dart'; +import 'dart:io'; +import 'package:path_provider/path_provider.dart'; void main() { runApp(MyApp()); @@ -54,16 +58,17 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - static String BLE_SERVICE = "fe84"; static String BLE_RECEIVE = "2d30c082"; static String BLE_SEND = "2d30c083"; static String BLE_DISCONNECT = "2d30c084"; - BluetoothDevice _connectedDevice; List _services; - List _characteristics; + BluetoothCharacteristic _sendCharacteristics; + BluetoothCharacteristic _receiveCharacteristics; + List _values = new List(); + int sampleCounter = 0; void addDeviceTolist(final BluetoothDevice device) { if (!widget.devicesList.contains(device)) { @@ -89,6 +94,92 @@ class _MyHomePageState extends State { }); } + List formatData(event) { + List data = new List(10); + + data[0] = sampleCounter++; + data[1] = (event[1] << 11) | (event[2] << 3) | (event[3] >> 5); + data[2] = ((event[3] & 31) << 14) | (event[4] << 6) | (event[5] >> 2); + data[3] = ((event[5] & 3) << 17) | + (event[6] << 9) | + (event[7] << 1) | + (event[8] >> 7); + data[4] = ((event[8] & 127) << 12) | (event[9] << 4) | (event[10] >> 4); + data[5] = sampleCounter++; + data[6] = ((event[10] & 15) << 15) | (event[11] << 7) | (event[12] >> 1); + data[7] = ((event[12] & 1) << 18) | + (event[13] << 10) | + (event[14] << 2) | + (event[15] >> 6); + data[8] = ((event[15] & 63) << 13) | (event[16] << 5) | (event[17] >> 3); + data[9] = ((event[17] & 7) << 16) | (event[18] << 8) | (event[19]); + + return data; + } + + List handleNegativesAndConvertToVolts(data) { + for (int i = 1; i < data.length; ++i) { + if (i == 5) continue; + + String binary = data[i].toRadixString(2); + + if (binary[binary.length - 1] == '1') { + data[i] = (~data[i] & 524287 | 1) * -1; + } + + // Convert to volts using the scale factor + data[i] = data[i].toDouble() * 1.2 * 8388607.0 * 1.5 * 51.0; + } + + return data; + } + + void addData(event) async { + int packetID = event[0]; + + if (packetID >= 101 && packetID <= 200) { + List data = formatData(event); + data = handleNegativesAndConvertToVolts(data); + + _values.add(data.sublist(0, 5)); + _values.add(data.sublist(5, 10)); + } + } + + void updateData(event) async { + if (_values.length > 300) { + final directory = await getExternalStorageDirectory(); + final pathOfTheFileToWrite = directory.path + "/myCsvFile.csv"; + File file = File(pathOfTheFileToWrite); + String csv = const ListToCsvConverter().convert(_values); + file.writeAsString(csv); + + String s = 's'; + print("stopping"); + print(pathOfTheFileToWrite); + print("____________________________________________________"); + await _sendCharacteristics.write(s.codeUnits); + } + + setState(() => {addData(event)}); + } + + void startDataStream(List c) async { + for (BluetoothCharacteristic char in c) { + if (char.toString().contains(BLE_RECEIVE)) { + _receiveCharacteristics = char; + await char.setNotifyValue(true); + char.value.listen((event) { + updateData(event); + }); + } else if (char.toString().contains(BLE_SEND)) { + _sendCharacteristics = char; + String b = 'b'; + await _sendCharacteristics.write(b.codeUnits); + } + } + } + @override void initState() { super.initState(); @@ -126,26 +217,29 @@ class _MyHomePageState extends State { itemBuilder: (context, index) { return Card( child: ListTile( - onTap: () => setState(() => {_characteristics = _services[index].characteristics}), - title: Text(_services[index].uuid.toString().contains("fe84") ? "YES" : "NO"), + onTap: () => setState( + () => startDataStream(_services[index].characteristics)), + title: Text(_services[index].uuid.toString().contains(BLE_SERVICE) + ? BLE_SERVICE + : "NO"), )); }); } ListView buildListViewOfCharacteristics() { return ListView.builder( - itemCount: _characteristics.length, + itemCount: _values.length, itemBuilder: (context, index) { return Card( child: ListTile( - title: Text(_characteristics[index].uuid.toString().contains("2d30c082") ? "YES" : "NO"), + title: Text(_values[index].toString()), )); }); } ListView _buildView() { if (_connectedDevice != null) { - if (_characteristics != null) + if (_values.isNotEmpty) return buildListViewOfCharacteristics(); else return buildListViewOfConnectedDevice(); diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index f30995f0..3104c9a9 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -36,6 +36,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + binary: + dependency: "direct main" + description: + name: binary + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" bloc: dependency: "direct main" description: @@ -92,6 +99,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.5" + csv: + dependency: "direct main" + description: + name: csv + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.3" cupertino_icons: dependency: "direct main" description: @@ -106,6 +120,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "5.2.1" fixnum: dependency: transitive description: @@ -144,6 +172,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.4.4" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.1" matcher: dependency: transitive description: @@ -179,6 +214,48 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.7.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.16" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+2" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4+4" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.3" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" plugin_platform_interface: dependency: transitive description: @@ -186,6 +263,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.13" protobuf: dependency: "direct main" description: @@ -282,6 +366,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.2+1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" sdks: dart: ">=2.9.0-14.0.dev <3.0.0" flutter: ">=1.16.0 <2.0.0" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 5409c88d..5539a3e5 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -36,6 +36,9 @@ dependencies: battery: ^1.0.5 share: ^0.6.5 archive: ^2.0.13 + binary: ^2.0.0 + csv: ^4.0.3 + path_provider: ^1.6.16 dev_dependencies: flutter_test: From a7565324199456051dad443603b75391b8a4fbc1 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Wed, 23 Sep 2020 15:23:59 -0400 Subject: [PATCH 12/56] Changement du format du fichier output pour openBCI --- mobile/lib/main.dart | 238 ++++++++++++++++++++++++++----------------- 1 file changed, 142 insertions(+), 96 deletions(-) diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 508da0ce..38ac1385 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -64,7 +64,6 @@ class _MyHomePageState extends State { static String BLE_DISCONNECT = "2d30c084"; BluetoothDevice _connectedDevice; - List _services; BluetoothCharacteristic _sendCharacteristics; BluetoothCharacteristic _receiveCharacteristics; List _values = new List(); @@ -87,17 +86,47 @@ class _MyHomePageState extends State { throw e; } } finally { - _services = await device.discoverServices(); + List services = await device.discoverServices(); + + for (BluetoothService service in services) { + if (service.toString().contains(BLE_SERVICE)) { + for (BluetoothCharacteristic characteristic + in service.characteristics) { + if (characteristic.toString().contains(BLE_RECEIVE)) { + _receiveCharacteristics = characteristic; + } else if (characteristic.toString().contains(BLE_SEND)) { + _sendCharacteristics = characteristic; + } + } + } + } } + setState(() { _connectedDevice = device; }); } + List getListForCSV() { + List data = new List(30); + + for (int i = 5; i < 15; ++i) { + data[i] = 0; + data[i + 15] = 0; + } + return data; + } + List formatData(event) { - List data = new List(10); + // Test event, comment scale factor + // event = [ 101, 0, 0, 0, 0, 8, 0, 5, 0, 0, 72, 0, 9, 240, 1, 176, 0, 48, 0, 8]; // Positive Test + // Expected [[0, 2, 10, 4], [262148, 507910, 393222, 8]] + // event = [ 101, 255, 255, 191, 255, 239, 255, 252, 255, 255, 88, 0, 11, 62, 56, 224, 0, 63, 240, 1 ]; // Negative Test + // Expected [[-3, -5, -7, -11], [-262139, -198429, -262137, -4095]] - data[0] = sampleCounter++; + List data = getListForCSV(); + + data[0] = sampleCounter; data[1] = (event[1] << 11) | (event[2] << 3) | (event[3] >> 5); data[2] = ((event[3] & 31) << 14) | (event[4] << 6) | (event[5] >> 2); data[3] = ((event[5] & 3) << 17) | @@ -105,30 +134,32 @@ class _MyHomePageState extends State { (event[7] << 1) | (event[8] >> 7); data[4] = ((event[8] & 127) << 12) | (event[9] << 4) | (event[10] >> 4); - data[5] = sampleCounter++; - data[6] = ((event[10] & 15) << 15) | (event[11] << 7) | (event[12] >> 1); - data[7] = ((event[12] & 1) << 18) | + data[15] = sampleCounter++; + data[16] = ((event[10] & 15) << 15) | (event[11] << 7) | (event[12] >> 1); + data[17] = ((event[12] & 1) << 18) | (event[13] << 10) | (event[14] << 2) | (event[15] >> 6); - data[8] = ((event[15] & 63) << 13) | (event[16] << 5) | (event[17] >> 3); - data[9] = ((event[17] & 7) << 16) | (event[18] << 8) | (event[19]); + data[18] = ((event[15] & 63) << 13) | (event[16] << 5) | (event[17] >> 3); + data[19] = ((event[17] & 7) << 16) | (event[18] << 8) | (event[19]); return data; } List handleNegativesAndConvertToVolts(data) { - for (int i = 1; i < data.length; ++i) { - if (i == 5) continue; - - String binary = data[i].toRadixString(2); - - if (binary[binary.length - 1] == '1') { - data[i] = (~data[i] & 524287 | 1) * -1; + for (int i = 1; i < 5; ++i) { + for (int j = 0; j < 2; ++j) { + int offset = 15 * j; + String binary = data[i + offset].toRadixString(2); + + if (binary[binary.length - 1] == '1') { + data[i + offset] = (~data[i + offset] & 524287 | 1) * -1; + } + + // Convert to microvolts using the scale factor + data[i + offset] = + data[i + offset].toDouble() * (1200000 / (8388607.0 * 1.5 * 51.0)); } - - // Convert to volts using the scale factor - data[i] = data[i].toDouble() * 1.2 * 8388607.0 * 1.5 * 51.0; } return data; @@ -141,43 +172,65 @@ class _MyHomePageState extends State { List data = formatData(event); data = handleNegativesAndConvertToVolts(data); - _values.add(data.sublist(0, 5)); - _values.add(data.sublist(5, 10)); + _values.add(data.sublist(0, 15)); + _values.add(data.sublist(15, 30)); } } void updateData(event) async { - if (_values.length > 300) { - final directory = await getExternalStorageDirectory(); - final pathOfTheFileToWrite = directory.path + "/myCsvFile.csv"; - File file = File(pathOfTheFileToWrite); - String csv = const ListToCsvConverter().convert(_values); - file.writeAsString(csv); - - String s = 's'; - print("stopping"); - print(pathOfTheFileToWrite); - print("____________________________________________________"); - await _sendCharacteristics.write(s.codeUnits); - } - setState(() => {addData(event)}); } - void startDataStream(List c) async { - for (BluetoothCharacteristic char in c) { - if (char.toString().contains(BLE_RECEIVE)) { - _receiveCharacteristics = char; - await char.setNotifyValue(true); - char.value.listen((event) { - updateData(event); - }); - } else if (char.toString().contains(BLE_SEND)) { - _sendCharacteristics = char; - String b = 'b'; - await _sendCharacteristics.write(b.codeUnits); - } - } + void initializeData() { + _values.add(["%OpenBCI Raw EEG Data"]); + _values.add(["%Number of channels = 4"]); + _values.add(["%Sample Rate = 200 Hz"]); + _values.add(["%Board = OpenBCI_GUI\$BoardGanglionBLE"]); + _values.add([ + "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)" + ]); + } + + void startDataStream() async { + initializeData(); + + await _receiveCharacteristics.setNotifyValue(true); + _receiveCharacteristics.value.listen((event) { + updateData(event); + }); + + String b = 'b'; + await _sendCharacteristics.write(b.codeUnits); + } + + void stopDataStream() async { + String s = 's'; + await _receiveCharacteristics.setNotifyValue(false); + await _sendCharacteristics.write(s.codeUnits); + await _connectedDevice.disconnect(); + _connectedDevice = null; + + final directory = await getExternalStorageDirectory(); + final pathOfTheFileToWrite = directory.path + "/myCsvFile.txt"; + File file = File(pathOfTheFileToWrite); + String csv = const ListToCsvConverter().convert(_values); + await file.writeAsString(csv); + + _values.clear(); } @override @@ -198,51 +251,51 @@ class _MyHomePageState extends State { widget.flutterBlue.startScan(); } - ListView buildListViewOfDevices() { - return ListView.builder( - itemCount: widget.devicesList.length, - itemBuilder: (context, index) { - return Card( - child: ListTile( - onTap: () => connectDevice(widget.devicesList[index]), - title: Text(widget.devicesList[index].name), - subtitle: Text(widget.devicesList[index].id.toString())), - ); - }); - } - - ListView buildListViewOfConnectedDevice() { - return ListView.builder( - itemCount: _services.length, - itemBuilder: (context, index) { - return Card( + Scaffold buildListViewOfDevices() { + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: ListView.builder( + itemCount: widget.devicesList.length, + itemBuilder: (context, index) { + return Card( child: ListTile( - onTap: () => setState( - () => startDataStream(_services[index].characteristics)), - title: Text(_services[index].uuid.toString().contains(BLE_SERVICE) - ? BLE_SERVICE - : "NO"), - )); - }); + onTap: () => connectDevice(widget.devicesList[index]), + title: Text(widget.devicesList[index].name), + subtitle: Text(widget.devicesList[index].id.toString())), + ); + }), + ); } - ListView buildListViewOfCharacteristics() { - return ListView.builder( - itemCount: _values.length, - itemBuilder: (context, index) { - return Card( - child: ListTile( - title: Text(_values[index].toString()), - )); - }); + Scaffold buildRecordingMenu() { + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + RaisedButton( + child: Text("Start"), + onPressed: () => startDataStream(), + ), + RaisedButton( + child: Text("Stop"), onPressed: () => stopDataStream()) + ]), + ), + ); } - ListView _buildView() { + Scaffold _buildScaffold() { if (_connectedDevice != null) { - if (_values.isNotEmpty) - return buildListViewOfCharacteristics(); - else - return buildListViewOfConnectedDevice(); + return buildRecordingMenu(); } return buildListViewOfDevices(); } @@ -255,13 +308,6 @@ class _MyHomePageState extends State { // The Flutter framework has been optimized to make rerunning build methods // fast, so that you can just rebuild anything that needs updating rather // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: _buildView(), - ); + return _buildScaffold(); } } From c7929d56ee86ccfc796309f78da5c30999bf9098 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Wed, 23 Sep 2020 15:52:51 -0400 Subject: [PATCH 13/56] Fixed some duplicates --- mobile/pubspec.lock | 17 +++++------------ mobile/pubspec.yaml | 7 ------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index fc6ff793..a50af143 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -53,24 +53,17 @@ packages: boolean_selector: dependency: transitive description: - name: battery_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - bloc: - dependency: "direct main" - description: - name: bloc + name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "6.0.3" - boolean_selector: + version: "2.0.0" + characters: dependency: transitive description: - name: boolean_selector + name: characters url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "1.0.0" charcode: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 49f93c25..70de37d0 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -37,14 +37,7 @@ dependencies: protobuf: ^1.0.1 share: ^0.6.5 uuid: ^2.2.2 - hive: ^1.4.4 usb_serial: ^0.2.4 - flutter_blue: ^0.7.2 - bloc: ^6.0.3 - flutter_bloc: ^6.0.5 - battery: ^1.0.5 - share: ^0.6.5 - archive: ^2.0.13 binary: ^2.0.0 csv: ^4.0.3 path_provider: ^1.6.16 From 0809597f8e66b838250e15a94e4d49eef6a11429 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Thu, 24 Sep 2020 11:22:47 -0400 Subject: [PATCH 14/56] Finished updating bluetooth files. Missing csv feature --- mobile/lib/src/app.dart | 6 +- .../Bluetooth/bluetooth_cubit.dart | 70 +++++++++++++++++++ .../Bluetooth/bluetooth_state.dart | 27 +++++++ .../lib/src/domain/bluetooth/bluetooth.dart | 38 ++++++++++ .../bluetooth/i_bluetooth_repository.dart | 7 ++ .../infrastructure/bluetooth_repository.dart | 22 ++++++ mobile/lib/src/locator.dart | 14 +++- .../bluetooth_route/bluetooth_route.dart | 54 ++++++++++++++ 8 files changed, 233 insertions(+), 5 deletions(-) create mode 100644 mobile/lib/src/application/Bluetooth/bluetooth_cubit.dart create mode 100644 mobile/lib/src/application/Bluetooth/bluetooth_state.dart create mode 100644 mobile/lib/src/domain/bluetooth/bluetooth.dart create mode 100644 mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart create mode 100644 mobile/lib/src/infrastructure/bluetooth_repository.dart create mode 100644 mobile/lib/src/presentation/bluetooth_route/bluetooth_route.dart diff --git a/mobile/lib/src/app.dart b/mobile/lib/src/app.dart index be3bfef4..b6dd7bea 100644 --- a/mobile/lib/src/app.dart +++ b/mobile/lib/src/app.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'presentation/wallets/wallets_route.dart'; +import 'presentation/bluetooth_route/bluetooth_route.dart'; import 'locator.dart'; import 'theme.dart'; @@ -18,10 +19,11 @@ class App extends StatelessWidget { child: MaterialApp( title: 'PolyDodo', theme: theme, - home: WalletsRoute(), - initialRoute: WalletsRoute.name, + home: BluetoothRoute(), + initialRoute: BluetoothRoute.name, routes: { WalletsRoute.name: (context) => WalletsRoute(), + BluetoothRoute.name: (context) => BluetoothRoute(), }, ), ); diff --git a/mobile/lib/src/application/Bluetooth/bluetooth_cubit.dart b/mobile/lib/src/application/Bluetooth/bluetooth_cubit.dart new file mode 100644 index 00000000..a54ebb84 --- /dev/null +++ b/mobile/lib/src/application/Bluetooth/bluetooth_cubit.dart @@ -0,0 +1,70 @@ +import 'package:bloc/bloc.dart'; +import 'package:polydodo/src/domain/bluetooth/bluetooth.dart'; +import 'package:polydodo/src/domain/bluetooth/i_bluetooth_repository.dart'; +import 'package:flutter_blue/flutter_blue.dart'; +import 'bluetooth_state.dart'; + +class BluetoothCubit extends Cubit { + final IBluetoothRepository _bluetoothRepository; + + BluetoothCubit(this._bluetoothRepository) : super(BluetoothInitial()) { + startSearching(); + } + + void startSearching() { + FlutterBlue flutterBlue = FlutterBlue.instance; + flutterBlue.connectedDevices + .asStream() + .listen((List devices) { + for (BluetoothDevice device in devices) { + _bluetoothRepository.addDevice(device); + } + }); + flutterBlue.scanResults.listen((List results) { + for (ScanResult result in results) { + _bluetoothRepository.addDevice(result.device); + } + }); + flutterBlue.startScan(); + _bluetoothRepository + .watch() + .listen((devices) => emit(BluetoothSearching(devices))) + .onError((e) => emit(BluetoothSearchError(e))); + } + + void connect(Bluetooth bluetoothDevice) async { + try { + emit(BluetoothConnecting()); + await bluetoothDevice.device.connect().then((value) => findRelevantCharacteristics(bluetoothDevice)); + } catch (e) { + if (e.code != 'already_connected') { + emit(BluetoothConnectionFailure(e)); + } + } + } + + void findRelevantCharacteristics(Bluetooth bluetoothDevice) { + emit(BluetoothConnectionSucess()); + + bluetoothDevice.device.discoverServices().then((services) => { + for (BluetoothService service in services) + { + if (service.toString().contains(Bluetooth.BLE_SERVICE)) + { + for (BluetoothCharacteristic characteristic + in service.characteristics) + { + if (characteristic + .toString() + .contains(Bluetooth.BLE_RECEIVE)) + bluetoothDevice.setReceiveCharacteristic(characteristic) + else if (characteristic + .toString() + .contains(Bluetooth.BLE_SEND)) + bluetoothDevice.setReceiveCharacteristic(characteristic) + } + } + } + }); + } +} diff --git a/mobile/lib/src/application/Bluetooth/bluetooth_state.dart b/mobile/lib/src/application/Bluetooth/bluetooth_state.dart new file mode 100644 index 00000000..4d457c8d --- /dev/null +++ b/mobile/lib/src/application/Bluetooth/bluetooth_state.dart @@ -0,0 +1,27 @@ +import 'package:polydodo/src/domain/bluetooth/bluetooth.dart'; + +abstract class BluetoothStates {} + +class BluetoothInitial extends BluetoothStates {} + +class BluetoothSearching extends BluetoothInitial { + final List devices; + + BluetoothSearching(this.devices); +} + +class BluetoothSearchError extends BluetoothStates { + final Exception cause; + + BluetoothSearchError(this.cause); +} + +class BluetoothConnecting extends BluetoothStates {} + +class BluetoothConnectionSucess extends BluetoothStates {} + +class BluetoothConnectionFailure extends BluetoothStates { + final Exception cause; + + BluetoothConnectionFailure(this.cause); +} diff --git a/mobile/lib/src/domain/bluetooth/bluetooth.dart b/mobile/lib/src/domain/bluetooth/bluetooth.dart new file mode 100644 index 00000000..5a06e5be --- /dev/null +++ b/mobile/lib/src/domain/bluetooth/bluetooth.dart @@ -0,0 +1,38 @@ +import 'package:flutter_blue/flutter_blue.dart'; + +class Bluetooth { + static const String BLE_SERVICE = "fe84"; + static const String BLE_RECEIVE = "2d30c082"; + static const String BLE_SEND = "2d30c083"; + + String id; + String name; + BluetoothDevice device; + BluetoothCharacteristic sendCharacteristic; + BluetoothCharacteristic receiveCharacteristic; + + Bluetooth(this.device) : assert(device != null) { + this.id = this.device.id.toString(); + this.name = this.device.name; + } + + BluetoothCharacteristic get send { + if (sendCharacteristic == null) + throw Exception('Proper Ganglion Send characteristic was not found'); + return sendCharacteristic; + } + + void setSendCharacteristic(BluetoothCharacteristic s) { + sendCharacteristic = s; + } + + BluetoothCharacteristic get receive { + if (receiveCharacteristic == null) + throw Exception('Proper Ganglion Receive characteristic was not found'); + return receiveCharacteristic; + } + + void setReceiveCharacteristic(BluetoothCharacteristic r) { + receiveCharacteristic = r; + } +} diff --git a/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart b/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart new file mode 100644 index 00000000..bdeef796 --- /dev/null +++ b/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart @@ -0,0 +1,7 @@ +import 'bluetooth.dart'; +import 'package:flutter_blue/flutter_blue.dart'; + +abstract class IBluetoothRepository { + Future addDevice(BluetoothDevice device); + Stream> watch(); +} diff --git a/mobile/lib/src/infrastructure/bluetooth_repository.dart b/mobile/lib/src/infrastructure/bluetooth_repository.dart new file mode 100644 index 00000000..4a029ff5 --- /dev/null +++ b/mobile/lib/src/infrastructure/bluetooth_repository.dart @@ -0,0 +1,22 @@ +import 'dart:async'; + +import 'package:flutter_blue/flutter_blue.dart'; +import 'package:polydodo/src/domain/bluetooth/i_bluetooth_repository.dart'; +import 'package:polydodo/src/domain/bluetooth/bluetooth.dart'; + +class BluetoothRepository implements IBluetoothRepository { + static List bluetoothPersistency = []; + final streamController = StreamController>(); + + @override + Future addDevice(BluetoothDevice device) async { + await Future.delayed(Duration(milliseconds: 400)); + Bluetooth d = new Bluetooth(device); + final idx = bluetoothPersistency.indexOf(d); + idx == -1 ? bluetoothPersistency.add(d) : bluetoothPersistency[idx] = d; + streamController.add(bluetoothPersistency); + } + + @override + Stream> watch() => streamController.stream; +} diff --git a/mobile/lib/src/locator.dart b/mobile/lib/src/locator.dart index bac78b6f..b7d14b45 100644 --- a/mobile/lib/src/locator.dart +++ b/mobile/lib/src/locator.dart @@ -1,8 +1,11 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; +import 'package:polydodo/src/application/Bluetooth/bluetooth_cubit.dart'; +import 'package:polydodo/src/domain/bluetooth/i_bluetooth_repository.dart'; import 'package:polydodo/src/domain/wallet/i_wallet_repository.dart'; import 'package:polydodo/src/infrastructure/mock_wallet_repository.dart'; +import 'package:polydodo/src/infrastructure/bluetooth_repository.dart'; import 'application/wallets/wallets_cubit.dart'; @@ -10,9 +13,9 @@ import 'application/wallets/wallets_cubit.dart'; final _serviceLocator = GetIt.asNewInstance(); void registerServices() { - _serviceLocator.registerSingleton( - MockWalletRepository(), - ); + _serviceLocator.registerSingleton(MockWalletRepository()); + _serviceLocator + .registerSingleton(BluetoothRepository()); } /// This function creates all the BlocProviders used in this app @@ -22,4 +25,9 @@ List createBlocProviders() => [ _serviceLocator.get(), ), ), + BlocProvider( + create: (context) => BluetoothCubit( + _serviceLocator.get(), + ), + ), ]; diff --git a/mobile/lib/src/presentation/bluetooth_route/bluetooth_route.dart b/mobile/lib/src/presentation/bluetooth_route/bluetooth_route.dart new file mode 100644 index 00000000..b64559ba --- /dev/null +++ b/mobile/lib/src/presentation/bluetooth_route/bluetooth_route.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:polydodo/src/application/Bluetooth/bluetooth_cubit.dart'; +import 'package:polydodo/src/application/Bluetooth/bluetooth_state.dart'; + +class BluetoothRoute extends StatelessWidget { + static const name = 'bluetoothRoute'; + + BluetoothRoute({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Polydodo')), + body: BlocConsumer( + listener: (context, state) { + print(state.runtimeType); + if (state is BluetoothSearchError) { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text( + 'Unable to search for bluetooth devices Wallets because ${state.cause}'), + )); + } else if (state is BluetoothConnectionFailure) { + Scaffold.of(context).showSnackBar(SnackBar( + content: + Text('Unable to connect to device because ${state.cause}'), + )); + } + }, + builder: (context, state) { + if (state is BluetoothSearching) + return ListView.builder( + itemCount: state.devices.length, + itemBuilder: (context, index) { + return Card( + child: ListTile( + onTap: () => BlocProvider.of(context) + .connect( + state.devices[index]), + title: Text( + state.devices[index].name), + subtitle: Text(state + .devices[index] + .id + .toString())), + ); + }); + else + return new ListView(); + }, + ), + ); + } +} From 437ff840ef954748c4c5255e83075418adb3decc Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Thu, 24 Sep 2020 14:21:06 -0400 Subject: [PATCH 15/56] Added recording feature --- mobile/lib/main2.dart | 206 ++++++++++++++++++ mobile/lib/src/app.dart | 9 +- .../Bluetooth/bluetooth_cubit.dart | 70 ------ mobile/lib/src/application/application.dart | 6 + .../bluetooth/bluetoothSelector_cubit.dart | 30 +++ .../bluetoothSelector_state.dart} | 0 .../src/application/eeg_data/data_cubit.dart | 113 ++++++++++ .../src/application/eeg_data/data_states.dart | 5 + .../bluetooth/i_bluetooth_repository.dart | 7 + mobile/lib/src/domain/domain.dart | 6 + mobile/lib/src/domain/eeg_data/eeg_data.dart | 48 ++++ .../infrastructure/bluetooth_repository.dart | 77 +++++++ .../src/infrastructure/infrastructure.dart | 2 + mobile/lib/src/locator.dart | 21 +- ...oute.dart => bluetoothSelector_route.dart} | 29 +-- mobile/lib/src/presentation/presentation.dart | 3 + .../recording/recording_route.dart | 50 +++++ 17 files changed, 583 insertions(+), 99 deletions(-) create mode 100644 mobile/lib/main2.dart delete mode 100644 mobile/lib/src/application/Bluetooth/bluetooth_cubit.dart create mode 100644 mobile/lib/src/application/application.dart create mode 100644 mobile/lib/src/application/bluetooth/bluetoothSelector_cubit.dart rename mobile/lib/src/application/{Bluetooth/bluetooth_state.dart => bluetooth/bluetoothSelector_state.dart} (100%) create mode 100644 mobile/lib/src/application/eeg_data/data_cubit.dart create mode 100644 mobile/lib/src/application/eeg_data/data_states.dart create mode 100644 mobile/lib/src/domain/domain.dart create mode 100644 mobile/lib/src/domain/eeg_data/eeg_data.dart create mode 100644 mobile/lib/src/infrastructure/infrastructure.dart rename mobile/lib/src/presentation/bluetooth_route/{bluetooth_route.dart => bluetoothSelector_route.dart} (60%) create mode 100644 mobile/lib/src/presentation/presentation.dart create mode 100644 mobile/lib/src/presentation/recording/recording_route.dart diff --git a/mobile/lib/main2.dart b/mobile/lib/main2.dart new file mode 100644 index 00000000..5db1c586 --- /dev/null +++ b/mobile/lib/main2.dart @@ -0,0 +1,206 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_blue/flutter_blue.dart'; +import 'package:binary/binary.dart'; +import 'package:csv/csv.dart'; +import 'dart:io'; +import 'package:path_provider/path_provider.dart'; + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + // This is the theme of your application. + // + // Try running your application with "flutter run". You'll see the + // application has a blue toolbar. Then, without quitting the app, try + // changing the primarySwatch below to Colors.green and then invoke + // "hot reload" (press "r" in the console where you ran "flutter run", + // or simply save your changes to "hot reload" in a Flutter IDE). + // Notice that the counter didn't reset back to zero; the application + // is not restarted. + primarySwatch: Colors.blue, + // This makes the visual density adapt to the platform that you run + // the app on. For desktop platforms, the controls will be smaller and + // closer together (more dense) than on mobile platforms. + visualDensity: VisualDensity.adaptivePlatformDensity, + ), + home: MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + MyHomePage({Key key, this.title}) : super(key: key); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + final FlutterBlue flutterBlue = FlutterBlue.instance; + final List devicesList = new List(); + final Map> readValues = new Map>(); + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + static String BLE_SERVICE = "fe84"; + static String BLE_RECEIVE = "2d30c082"; + static String BLE_SEND = "2d30c083"; + static String BLE_DISCONNECT = "2d30c084"; + + BluetoothDevice _connectedDevice; + BluetoothCharacteristic _sendCharacteristics; + BluetoothCharacteristic _receiveCharacteristics; + List _values = new List(); + int sampleCounter = 0; + + + + + + List handleNegativesAndConvertToVolts(data) { + for (int i = 1; i < 5; ++i) { + for (int j = 0; j < 2; ++j) { + int offset = 15 * j; + String binary = data[i + offset].toRadixString(2); + + if (binary[binary.length - 1] == '1') { + data[i + offset] = (~data[i + offset] & 524287 | 1) * -1; + } + + // Convert to microvolts using the scale factor + data[i + offset] = + data[i + offset].toDouble() * (1200000 / (8388607.0 * 1.5 * 51.0)); + } + } + + return data; + } + + + + void updateData(event) async { + setState(() => {addData(event)}); + } + + void startDataStream() async { + initializeData(); + + await _receiveCharacteristics.setNotifyValue(true); + _receiveCharacteristics.value.listen((event) { + updateData(event); + }); + + String b = 'b'; + await _sendCharacteristics.write(b.codeUnits); + } + + void stopDataStream() async { + String s = 's'; + await _receiveCharacteristics.setNotifyValue(false); + await _sendCharacteristics.write(s.codeUnits); + await _connectedDevice.disconnect(); + _connectedDevice = null; + + final directory = await getExternalStorageDirectory(); + final pathOfTheFileToWrite = directory.path + "/myCsvFile.txt"; + File file = File(pathOfTheFileToWrite); + String csv = const ListToCsvConverter().convert(_values); + await file.writeAsString(csv); + + _values.clear(); + } + + @override + void initState() { + super.initState(); + widget.flutterBlue.connectedDevices + .asStream() + .listen((List devices) { + for (BluetoothDevice device in devices) { + addDeviceTolist(device); + } + }); + widget.flutterBlue.scanResults.listen((List results) { + for (ScanResult result in results) { + addDeviceTolist(result.device); + } + }); + widget.flutterBlue.startScan(); + } + + Scaffold buildListViewOfDevices() { + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: ListView.builder( + itemCount: widget.devicesList.length, + itemBuilder: (context, index) { + return Card( + child: ListTile( + onTap: () => connectDevice(widget.devicesList[index]), + title: Text(widget.devicesList[index].name), + subtitle: Text(widget.devicesList[index].id.toString())), + ); + }), + ); + } + + Scaffold buildRecordingMenu() { + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + RaisedButton( + child: Text("Start"), + onPressed: () => startDataStream(), + ), + RaisedButton( + child: Text("Stop"), onPressed: () => stopDataStream()) + ]), + ), + ); + } + + Scaffold _buildScaffold() { + if (_connectedDevice != null) { + return buildRecordingMenu(); + } + return buildListViewOfDevices(); + } + + @override + Widget build(BuildContext context) { + // This method is rerun every time setState is called, for instance as done + // by the _incrementCounter method above. + // + // The Flutter framework has been optimized to make rerunning build methods + // fast, so that you can just rebuild anything that needs updating rather + // than having to individually change instances of widgets. + return _buildScaffold(); + } +} diff --git a/mobile/lib/src/app.dart b/mobile/lib/src/app.dart index b6dd7bea..a306e957 100644 --- a/mobile/lib/src/app.dart +++ b/mobile/lib/src/app.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'presentation/wallets/wallets_route.dart'; -import 'presentation/bluetooth_route/bluetooth_route.dart'; +import 'presentation/presentation.dart'; import 'locator.dart'; import 'theme.dart'; @@ -19,11 +18,11 @@ class App extends StatelessWidget { child: MaterialApp( title: 'PolyDodo', theme: theme, - home: BluetoothRoute(), - initialRoute: BluetoothRoute.name, + home: BluetoothSelectorRoute(), + initialRoute: BluetoothSelectorRoute.name, routes: { WalletsRoute.name: (context) => WalletsRoute(), - BluetoothRoute.name: (context) => BluetoothRoute(), + BluetoothSelectorRoute.name: (context) => BluetoothSelectorRoute(), }, ), ); diff --git a/mobile/lib/src/application/Bluetooth/bluetooth_cubit.dart b/mobile/lib/src/application/Bluetooth/bluetooth_cubit.dart deleted file mode 100644 index a54ebb84..00000000 --- a/mobile/lib/src/application/Bluetooth/bluetooth_cubit.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:bloc/bloc.dart'; -import 'package:polydodo/src/domain/bluetooth/bluetooth.dart'; -import 'package:polydodo/src/domain/bluetooth/i_bluetooth_repository.dart'; -import 'package:flutter_blue/flutter_blue.dart'; -import 'bluetooth_state.dart'; - -class BluetoothCubit extends Cubit { - final IBluetoothRepository _bluetoothRepository; - - BluetoothCubit(this._bluetoothRepository) : super(BluetoothInitial()) { - startSearching(); - } - - void startSearching() { - FlutterBlue flutterBlue = FlutterBlue.instance; - flutterBlue.connectedDevices - .asStream() - .listen((List devices) { - for (BluetoothDevice device in devices) { - _bluetoothRepository.addDevice(device); - } - }); - flutterBlue.scanResults.listen((List results) { - for (ScanResult result in results) { - _bluetoothRepository.addDevice(result.device); - } - }); - flutterBlue.startScan(); - _bluetoothRepository - .watch() - .listen((devices) => emit(BluetoothSearching(devices))) - .onError((e) => emit(BluetoothSearchError(e))); - } - - void connect(Bluetooth bluetoothDevice) async { - try { - emit(BluetoothConnecting()); - await bluetoothDevice.device.connect().then((value) => findRelevantCharacteristics(bluetoothDevice)); - } catch (e) { - if (e.code != 'already_connected') { - emit(BluetoothConnectionFailure(e)); - } - } - } - - void findRelevantCharacteristics(Bluetooth bluetoothDevice) { - emit(BluetoothConnectionSucess()); - - bluetoothDevice.device.discoverServices().then((services) => { - for (BluetoothService service in services) - { - if (service.toString().contains(Bluetooth.BLE_SERVICE)) - { - for (BluetoothCharacteristic characteristic - in service.characteristics) - { - if (characteristic - .toString() - .contains(Bluetooth.BLE_RECEIVE)) - bluetoothDevice.setReceiveCharacteristic(characteristic) - else if (characteristic - .toString() - .contains(Bluetooth.BLE_SEND)) - bluetoothDevice.setReceiveCharacteristic(characteristic) - } - } - } - }); - } -} diff --git a/mobile/lib/src/application/application.dart b/mobile/lib/src/application/application.dart new file mode 100644 index 00000000..061b6221 --- /dev/null +++ b/mobile/lib/src/application/application.dart @@ -0,0 +1,6 @@ +export 'bluetooth/bluetoothSelector_cubit.dart'; +export 'bluetooth/bluetoothSelector_state.dart'; +export 'eeg_data/data_cubit.dart'; +export 'eeg_data/data_states.dart'; +export 'wallets/wallets_cubit.dart'; +export 'wallets/wallets_state.dart'; diff --git a/mobile/lib/src/application/bluetooth/bluetoothSelector_cubit.dart b/mobile/lib/src/application/bluetooth/bluetoothSelector_cubit.dart new file mode 100644 index 00000000..d2dca79c --- /dev/null +++ b/mobile/lib/src/application/bluetooth/bluetoothSelector_cubit.dart @@ -0,0 +1,30 @@ +import 'package:bloc/bloc.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:polydodo/src/domain/domain.dart'; +import 'bluetoothSelector_state.dart'; + +class BluetoothSelectorCubit extends Cubit { + final IBluetoothRepository _bluetoothRepository; + + BluetoothSelectorCubit(this._bluetoothRepository) + : super(BluetoothInitial()) { + startSearching(); + } + + void startSearching() { + _bluetoothRepository.initializeBluetooth(); + _bluetoothRepository + .watch() + .listen((devices) => emit(BluetoothSearching(devices))) + .onError((e) => emit(BluetoothSearchError(e))); + } + + void connect(Bluetooth bluetoothDevice) async { + emit(BluetoothConnecting()); + + _bluetoothRepository + .connect(bluetoothDevice) + .then((value) => emit(BluetoothConnectionSucess())) + .catchError((e) => {emit(BluetoothConnectionFailure(e))}); + } +} diff --git a/mobile/lib/src/application/Bluetooth/bluetooth_state.dart b/mobile/lib/src/application/bluetooth/bluetoothSelector_state.dart similarity index 100% rename from mobile/lib/src/application/Bluetooth/bluetooth_state.dart rename to mobile/lib/src/application/bluetooth/bluetoothSelector_state.dart diff --git a/mobile/lib/src/application/eeg_data/data_cubit.dart b/mobile/lib/src/application/eeg_data/data_cubit.dart new file mode 100644 index 00000000..5d7d009d --- /dev/null +++ b/mobile/lib/src/application/eeg_data/data_cubit.dart @@ -0,0 +1,113 @@ +import 'dart:io'; + +import 'package:csv/csv.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:polydodo/src/domain/domain.dart'; + +import 'data_states.dart'; +import 'package:bloc/bloc.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class DataCubit extends Cubit { + final IBluetoothRepository _bluetoothRepository; + // todo: create repo for eeg data + // final IDataRepository _dataRepository; + EEGData recordingData; + + DataCubit(this._bluetoothRepository) + : super(DataStateInitial()); + + void startStreaming() { + emit(DataStateRecording()); + recordingData = new EEGData(); + _bluetoothRepository + .startDataStream() + .then((stream) => stream.listen((value) { + addData(value); + })); + } + + void stopStreaming() async { + // todo: SAVE TO REPOS + emit(DataStateInitial()); + _bluetoothRepository.stopDataStream(); + + // todo: move save future to another file + final directory = await getExternalStorageDirectory(); + final pathOfTheFileToWrite = directory.path + '/' + recordingData.fileName + ".txt"; + File file = File(pathOfTheFileToWrite); + String csv = const ListToCsvConverter().convert(recordingData.values); + await file.writeAsString(csv); + } + + void addData(event) async { + int packetID = event[0]; + + // todo: handle packet id 0 (raw data) and possibly impedence for signal validation + if (packetID >= 101 && packetID <= 200) { + List data = formatData(event); + data = handleNegativesAndConvertToVolts(data); + + recordingData.addValues(data.sublist(0, 15)); + recordingData.addValues(data.sublist(15, 30)); + } + } + + List formatData(event) { + // Test event, comment scale factor + // event = [ 101, 0, 0, 0, 0, 8, 0, 5, 0, 0, 72, 0, 9, 240, 1, 176, 0, 48, 0, 8]; // Positive Test + // Expected [[0, 2, 10, 4], [262148, 507910, 393222, 8]] + // event = [ 101, 255, 255, 191, 255, 239, 255, 252, 255, 255, 88, 0, 11, 62, 56, 224, 0, 63, 240, 1 ]; // Negative Test + // Expected [[-3, -5, -7, -11], [-262139, -198429, -262137, -4095]] + + List data = getListForCSV(); + + data[0] = recordingData.sampleCounter; + data[1] = (event[1] << 11) | (event[2] << 3) | (event[3] >> 5); + data[2] = ((event[3] & 31) << 14) | (event[4] << 6) | (event[5] >> 2); + data[3] = ((event[5] & 3) << 17) | + (event[6] << 9) | + (event[7] << 1) | + (event[8] >> 7); + data[4] = ((event[8] & 127) << 12) | (event[9] << 4) | (event[10] >> 4); + data[15] = recordingData.sampleCounter++; + data[16] = ((event[10] & 15) << 15) | (event[11] << 7) | (event[12] >> 1); + data[17] = ((event[12] & 1) << 18) | + (event[13] << 10) | + (event[14] << 2) | + (event[15] >> 6); + data[18] = ((event[15] & 63) << 13) | (event[16] << 5) | (event[17] >> 3); + data[19] = ((event[17] & 7) << 16) | (event[18] << 8) | (event[19]); + + return data; + } + + List getListForCSV() { + List data = new List(30); + + for (int i = 5; i < 15; ++i) { + data[i] = 0; + data[i + 15] = 0; + } + return data; + } + + List handleNegativesAndConvertToVolts(data) { + for (int i = 1; i < 5; ++i) { + for (int j = 0; j < 2; ++j) { + int offset = 15 * j; + String binary = data[i + offset].toRadixString(2); + + if (binary[binary.length - 1] == '1') { + data[i + offset] = (~data[i + offset] & 524287 | 1) * -1; + } + + // Convert to microvolts using the scale factor + data[i + offset] = + data[i + offset].toDouble() * (1200000 / (8388607.0 * 1.5 * 51.0)); + } + } + + return data; + } +} diff --git a/mobile/lib/src/application/eeg_data/data_states.dart b/mobile/lib/src/application/eeg_data/data_states.dart new file mode 100644 index 00000000..054daed9 --- /dev/null +++ b/mobile/lib/src/application/eeg_data/data_states.dart @@ -0,0 +1,5 @@ +abstract class DataState {} + +class DataStateInitial extends DataState {} + +class DataStateRecording extends DataState {} \ No newline at end of file diff --git a/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart b/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart index bdeef796..81a20ab5 100644 --- a/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart +++ b/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart @@ -2,6 +2,13 @@ import 'bluetooth.dart'; import 'package:flutter_blue/flutter_blue.dart'; abstract class IBluetoothRepository { + Bluetooth selectedDevice; + void initializeBluetooth(); Future addDevice(BluetoothDevice device); + Future connect(Bluetooth bluetoothDevice); + void disconnect(); + void findRelevantCharacteristics(); + Future>> startDataStream(); + void stopDataStream(); Stream> watch(); } diff --git a/mobile/lib/src/domain/domain.dart b/mobile/lib/src/domain/domain.dart new file mode 100644 index 00000000..c9995f41 --- /dev/null +++ b/mobile/lib/src/domain/domain.dart @@ -0,0 +1,6 @@ +export 'bluetooth/i_bluetooth_repository.dart'; +export 'bluetooth/bluetooth.dart'; +export 'eeg_data/eeg_data.dart'; +export 'wallet/i_wallet_repository.dart'; +export 'wallet/money.dart'; +export 'wallet/owner.dart'; diff --git a/mobile/lib/src/domain/eeg_data/eeg_data.dart b/mobile/lib/src/domain/eeg_data/eeg_data.dart new file mode 100644 index 00000000..38829705 --- /dev/null +++ b/mobile/lib/src/domain/eeg_data/eeg_data.dart @@ -0,0 +1,48 @@ +class EEGData { + String id; + List _values; + int sampleCounter; + + EEGData() { + this.id = DateTime.now().toString(); + sampleCounter = 0; + initializeData(); + } + + void addValues(List value) { + _values.add(value); + } + + List get values { + return _values; + } + + String get fileName { + return id; + } + + void initializeData() { + _values = new List(); + _values.add(["%OpenBCI Raw EEG Data"]); + _values.add(["%Number of channels = 4"]); + _values.add(["%Sample Rate = 200 Hz"]); + _values.add(["%Board = OpenBCI_GUI\$BoardGanglionBLE"]); + _values.add([ + "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)" + ]); + } +} diff --git a/mobile/lib/src/infrastructure/bluetooth_repository.dart b/mobile/lib/src/infrastructure/bluetooth_repository.dart index 4a029ff5..5d6ed09e 100644 --- a/mobile/lib/src/infrastructure/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/bluetooth_repository.dart @@ -5,9 +5,27 @@ import 'package:polydodo/src/domain/bluetooth/i_bluetooth_repository.dart'; import 'package:polydodo/src/domain/bluetooth/bluetooth.dart'; class BluetoothRepository implements IBluetoothRepository { + Bluetooth selectedDevice; static List bluetoothPersistency = []; final streamController = StreamController>(); + void initializeBluetooth() { + FlutterBlue flutterBlue = FlutterBlue.instance; + flutterBlue.connectedDevices + .asStream() + .listen((List devices) { + for (BluetoothDevice device in devices) { + addDevice(device); + } + }); + flutterBlue.scanResults.listen((List results) { + for (ScanResult result in results) { + addDevice(result.device); + } + }); + flutterBlue.startScan(); + } + @override Future addDevice(BluetoothDevice device) async { await Future.delayed(Duration(milliseconds: 400)); @@ -17,6 +35,65 @@ class BluetoothRepository implements IBluetoothRepository { streamController.add(bluetoothPersistency); } + Future connect(Bluetooth bluetoothDevice) async { + await bluetoothDevice.device.connect().then((value) => + {selectedDevice = bluetoothDevice, findRelevantCharacteristics()}); + return; + } + + void disconnect() async { + await selectedDevice.device.disconnect(); + } + + void findRelevantCharacteristics() { + selectedDevice.device.discoverServices().then((services) => { + for (BluetoothService service in services) + { + print("Found Service"), + if (service.toString().contains(Bluetooth.BLE_SERVICE)) + { + for (BluetoothCharacteristic characteristic + in service.characteristics) + { + if (characteristic + .toString() + .contains(Bluetooth.BLE_RECEIVE)) + { + print("Found receive"), + selectedDevice + .setReceiveCharacteristic(characteristic) + } + else if (characteristic + .toString() + .contains(Bluetooth.BLE_SEND)) + { + print("Found send"), + selectedDevice + .setSendCharacteristic(characteristic) + } + } + } + } + }); + } + + Future>> startDataStream() async { + print(selectedDevice.receive); + print("haha"); + print(selectedDevice.send); + await selectedDevice.receive.setNotifyValue(true); + + String b = 'b'; + await selectedDevice.send.write(b.codeUnits); + return selectedDevice.receive.value; + } + + void stopDataStream() async { + String s = 's'; + await selectedDevice.receive.setNotifyValue(false); + await selectedDevice.send.write(s.codeUnits); + } + @override Stream> watch() => streamController.stream; } diff --git a/mobile/lib/src/infrastructure/infrastructure.dart b/mobile/lib/src/infrastructure/infrastructure.dart new file mode 100644 index 00000000..f2454e8b --- /dev/null +++ b/mobile/lib/src/infrastructure/infrastructure.dart @@ -0,0 +1,2 @@ +export 'bluetooth_repository.dart'; +export 'mock_wallet_repository.dart'; \ No newline at end of file diff --git a/mobile/lib/src/locator.dart b/mobile/lib/src/locator.dart index b7d14b45..354662d7 100644 --- a/mobile/lib/src/locator.dart +++ b/mobile/lib/src/locator.dart @@ -1,13 +1,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; -import 'package:polydodo/src/application/Bluetooth/bluetooth_cubit.dart'; -import 'package:polydodo/src/domain/bluetooth/i_bluetooth_repository.dart'; - -import 'package:polydodo/src/domain/wallet/i_wallet_repository.dart'; -import 'package:polydodo/src/infrastructure/mock_wallet_repository.dart'; -import 'package:polydodo/src/infrastructure/bluetooth_repository.dart'; - -import 'application/wallets/wallets_cubit.dart'; +import 'package:polydodo/src/application/eeg_data/data_cubit.dart'; +import 'package:polydodo/src/domain/domain.dart'; +import 'package:polydodo/src/infrastructure/infrastructure.dart'; +import 'application/application.dart'; /// Private GetIt instance as we want all DI to be performed here in this file final _serviceLocator = GetIt.asNewInstance(); @@ -25,8 +21,13 @@ List createBlocProviders() => [ _serviceLocator.get(), ), ), - BlocProvider( - create: (context) => BluetoothCubit( + BlocProvider( + create: (context) => BluetoothSelectorCubit( + _serviceLocator.get(), + ), + ), + BlocProvider( + create: (context) => DataCubit( _serviceLocator.get(), ), ), diff --git a/mobile/lib/src/presentation/bluetooth_route/bluetooth_route.dart b/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart similarity index 60% rename from mobile/lib/src/presentation/bluetooth_route/bluetooth_route.dart rename to mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart index b64559ba..f3f1f52e 100644 --- a/mobile/lib/src/presentation/bluetooth_route/bluetooth_route.dart +++ b/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart @@ -1,18 +1,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:polydodo/src/application/Bluetooth/bluetooth_cubit.dart'; -import 'package:polydodo/src/application/Bluetooth/bluetooth_state.dart'; +import 'package:polydodo/src/application/application.dart'; +import 'package:polydodo/src/presentation/presentation.dart'; -class BluetoothRoute extends StatelessWidget { +class BluetoothSelectorRoute extends StatelessWidget { static const name = 'bluetoothRoute'; - BluetoothRoute({Key key}) : super(key: key); + BluetoothSelectorRoute({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Polydodo')), - body: BlocConsumer( + body: BlocConsumer( listener: (context, state) { print(state.runtimeType); if (state is BluetoothSearchError) { @@ -25,6 +25,12 @@ class BluetoothRoute extends StatelessWidget { content: Text('Unable to connect to device because ${state.cause}'), )); + } else if (state is BluetoothConnectionSucess) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => RecordingRoute(), + )); } }, builder: (context, state) { @@ -34,15 +40,10 @@ class BluetoothRoute extends StatelessWidget { itemBuilder: (context, index) { return Card( child: ListTile( - onTap: () => BlocProvider.of(context) - .connect( - state.devices[index]), - title: Text( - state.devices[index].name), - subtitle: Text(state - .devices[index] - .id - .toString())), + onTap: () => BlocProvider.of(context) + .connect(state.devices[index]), + title: Text(state.devices[index].name), + subtitle: Text(state.devices[index].id.toString())), ); }); else diff --git a/mobile/lib/src/presentation/presentation.dart b/mobile/lib/src/presentation/presentation.dart new file mode 100644 index 00000000..6ff51758 --- /dev/null +++ b/mobile/lib/src/presentation/presentation.dart @@ -0,0 +1,3 @@ +export 'bluetooth_route/bluetoothSelector_route.dart'; +export 'recording/recording_route.dart'; +export 'wallets/wallets_route.dart'; \ No newline at end of file diff --git a/mobile/lib/src/presentation/recording/recording_route.dart b/mobile/lib/src/presentation/recording/recording_route.dart new file mode 100644 index 00000000..e7ea0f26 --- /dev/null +++ b/mobile/lib/src/presentation/recording/recording_route.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:polydodo/src/application/application.dart'; + + +class RecordingRoute extends StatelessWidget { + static const name = 'recordingRoute'; + + RecordingRoute({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text("Recording"), + ), + body: BlocConsumer( + listener: (context, state) { + print(state.runtimeType); + }, + builder: (context, state) { + if (state is DataStateInitial) + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + RaisedButton( + child: Text("Start"), + onPressed: () => BlocProvider.of(context).startStreaming(), + ), + ]), + ); + else + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + RaisedButton( + child: Text("Stop"), + onPressed: () => BlocProvider.of(context).stopStreaming(), + ), + ]), + ); + }, + ), + ); + } +} From dd515c8319a998289cbfa85f33beb0f5dca94460 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Thu, 24 Sep 2020 14:23:32 -0400 Subject: [PATCH 16/56] Removed main2 temporary file --- mobile/lib/main2.dart | 206 ------------------------------------------ 1 file changed, 206 deletions(-) delete mode 100644 mobile/lib/main2.dart diff --git a/mobile/lib/main2.dart b/mobile/lib/main2.dart deleted file mode 100644 index 5db1c586..00000000 --- a/mobile/lib/main2.dart +++ /dev/null @@ -1,206 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_blue/flutter_blue.dart'; -import 'package:binary/binary.dart'; -import 'package:csv/csv.dart'; -import 'dart:io'; -import 'package:path_provider/path_provider.dart'; - -void main() { - runApp(MyApp()); -} - -class MyApp extends StatelessWidget { - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // Try running your application with "flutter run". You'll see the - // application has a blue toolbar. Then, without quitting the app, try - // changing the primarySwatch below to Colors.green and then invoke - // "hot reload" (press "r" in the console where you ran "flutter run", - // or simply save your changes to "hot reload" in a Flutter IDE). - // Notice that the counter didn't reset back to zero; the application - // is not restarted. - primarySwatch: Colors.blue, - // This makes the visual density adapt to the platform that you run - // the app on. For desktop platforms, the controls will be smaller and - // closer together (more dense) than on mobile platforms. - visualDensity: VisualDensity.adaptivePlatformDensity, - ), - home: MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - MyHomePage({Key key, this.title}) : super(key: key); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - final FlutterBlue flutterBlue = FlutterBlue.instance; - final List devicesList = new List(); - final Map> readValues = new Map>(); - - @override - _MyHomePageState createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - static String BLE_SERVICE = "fe84"; - static String BLE_RECEIVE = "2d30c082"; - static String BLE_SEND = "2d30c083"; - static String BLE_DISCONNECT = "2d30c084"; - - BluetoothDevice _connectedDevice; - BluetoothCharacteristic _sendCharacteristics; - BluetoothCharacteristic _receiveCharacteristics; - List _values = new List(); - int sampleCounter = 0; - - - - - - List handleNegativesAndConvertToVolts(data) { - for (int i = 1; i < 5; ++i) { - for (int j = 0; j < 2; ++j) { - int offset = 15 * j; - String binary = data[i + offset].toRadixString(2); - - if (binary[binary.length - 1] == '1') { - data[i + offset] = (~data[i + offset] & 524287 | 1) * -1; - } - - // Convert to microvolts using the scale factor - data[i + offset] = - data[i + offset].toDouble() * (1200000 / (8388607.0 * 1.5 * 51.0)); - } - } - - return data; - } - - - - void updateData(event) async { - setState(() => {addData(event)}); - } - - void startDataStream() async { - initializeData(); - - await _receiveCharacteristics.setNotifyValue(true); - _receiveCharacteristics.value.listen((event) { - updateData(event); - }); - - String b = 'b'; - await _sendCharacteristics.write(b.codeUnits); - } - - void stopDataStream() async { - String s = 's'; - await _receiveCharacteristics.setNotifyValue(false); - await _sendCharacteristics.write(s.codeUnits); - await _connectedDevice.disconnect(); - _connectedDevice = null; - - final directory = await getExternalStorageDirectory(); - final pathOfTheFileToWrite = directory.path + "/myCsvFile.txt"; - File file = File(pathOfTheFileToWrite); - String csv = const ListToCsvConverter().convert(_values); - await file.writeAsString(csv); - - _values.clear(); - } - - @override - void initState() { - super.initState(); - widget.flutterBlue.connectedDevices - .asStream() - .listen((List devices) { - for (BluetoothDevice device in devices) { - addDeviceTolist(device); - } - }); - widget.flutterBlue.scanResults.listen((List results) { - for (ScanResult result in results) { - addDeviceTolist(result.device); - } - }); - widget.flutterBlue.startScan(); - } - - Scaffold buildListViewOfDevices() { - return Scaffold( - appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: ListView.builder( - itemCount: widget.devicesList.length, - itemBuilder: (context, index) { - return Card( - child: ListTile( - onTap: () => connectDevice(widget.devicesList[index]), - title: Text(widget.devicesList[index].name), - subtitle: Text(widget.devicesList[index].id.toString())), - ); - }), - ); - } - - Scaffold buildRecordingMenu() { - return Scaffold( - appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - RaisedButton( - child: Text("Start"), - onPressed: () => startDataStream(), - ), - RaisedButton( - child: Text("Stop"), onPressed: () => stopDataStream()) - ]), - ), - ); - } - - Scaffold _buildScaffold() { - if (_connectedDevice != null) { - return buildRecordingMenu(); - } - return buildListViewOfDevices(); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return _buildScaffold(); - } -} From 8a563840afc95c23a4de9a79df2581ef0702f7ca Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Mon, 28 Sep 2020 15:13:58 -0400 Subject: [PATCH 17/56] Removed unecessary prints --- mobile/lib/src/infrastructure/bluetooth_repository.dart | 6 ------ 1 file changed, 6 deletions(-) diff --git a/mobile/lib/src/infrastructure/bluetooth_repository.dart b/mobile/lib/src/infrastructure/bluetooth_repository.dart index 5d6ed09e..c11c72ed 100644 --- a/mobile/lib/src/infrastructure/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/bluetooth_repository.dart @@ -49,7 +49,6 @@ class BluetoothRepository implements IBluetoothRepository { selectedDevice.device.discoverServices().then((services) => { for (BluetoothService service in services) { - print("Found Service"), if (service.toString().contains(Bluetooth.BLE_SERVICE)) { for (BluetoothCharacteristic characteristic @@ -59,7 +58,6 @@ class BluetoothRepository implements IBluetoothRepository { .toString() .contains(Bluetooth.BLE_RECEIVE)) { - print("Found receive"), selectedDevice .setReceiveCharacteristic(characteristic) } @@ -67,7 +65,6 @@ class BluetoothRepository implements IBluetoothRepository { .toString() .contains(Bluetooth.BLE_SEND)) { - print("Found send"), selectedDevice .setSendCharacteristic(characteristic) } @@ -78,9 +75,6 @@ class BluetoothRepository implements IBluetoothRepository { } Future>> startDataStream() async { - print(selectedDevice.receive); - print("haha"); - print(selectedDevice.send); await selectedDevice.receive.setNotifyValue(true); String b = 'b'; From 475aef078069a72d2f6876f71d25d9df41c03a90 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Mon, 28 Sep 2020 15:30:54 -0400 Subject: [PATCH 18/56] Extracted variable and renamed for clarity --- .../src/infrastructure/bluetooth_repository.dart | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/mobile/lib/src/infrastructure/bluetooth_repository.dart b/mobile/lib/src/infrastructure/bluetooth_repository.dart index c11c72ed..6985a46a 100644 --- a/mobile/lib/src/infrastructure/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/bluetooth_repository.dart @@ -5,6 +5,10 @@ import 'package:polydodo/src/domain/bluetooth/i_bluetooth_repository.dart'; import 'package:polydodo/src/domain/bluetooth/bluetooth.dart'; class BluetoothRepository implements IBluetoothRepository { + + static const startStreamChar = 'b'; + static const stopStreamChar = 's'; + Bluetooth selectedDevice; static List bluetoothPersistency = []; final streamController = StreamController>(); @@ -64,10 +68,7 @@ class BluetoothRepository implements IBluetoothRepository { else if (characteristic .toString() .contains(Bluetooth.BLE_SEND)) - { - selectedDevice - .setSendCharacteristic(characteristic) - } + {selectedDevice.setSendCharacteristic(characteristic)} } } } @@ -77,15 +78,13 @@ class BluetoothRepository implements IBluetoothRepository { Future>> startDataStream() async { await selectedDevice.receive.setNotifyValue(true); - String b = 'b'; - await selectedDevice.send.write(b.codeUnits); + await selectedDevice.send.write(startStreamChar.codeUnits); return selectedDevice.receive.value; } void stopDataStream() async { - String s = 's'; await selectedDevice.receive.setNotifyValue(false); - await selectedDevice.send.write(s.codeUnits); + await selectedDevice.send.write(stopStreamChar.codeUnits); } @override From 346834319c729650526f8e44d91c8c347b0709f3 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Mon, 28 Sep 2020 16:44:48 -0400 Subject: [PATCH 19/56] Removed unecessary imports and exposed methods in bluetooth interface --- mobile/lib/main.dart | 6 ------ .../lib/src/domain/bluetooth/i_bluetooth_repository.dart | 7 +++---- mobile/lib/src/infrastructure/bluetooth_repository.dart | 1 - 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 481e318d..e048b141 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -1,10 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_blue/flutter_blue.dart'; -import 'package:binary/binary.dart'; -import 'package:csv/csv.dart'; -import 'dart:io'; -import 'package:path_provider/path_provider.dart'; - import 'src/app.dart'; void main() { diff --git a/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart b/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart index 81a20ab5..ec2ca6e2 100644 --- a/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart +++ b/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart @@ -1,14 +1,13 @@ import 'bluetooth.dart'; -import 'package:flutter_blue/flutter_blue.dart'; abstract class IBluetoothRepository { - Bluetooth selectedDevice; void initializeBluetooth(); - Future addDevice(BluetoothDevice device); + Future connect(Bluetooth bluetoothDevice); void disconnect(); - void findRelevantCharacteristics(); + Future>> startDataStream(); void stopDataStream(); + Stream> watch(); } diff --git a/mobile/lib/src/infrastructure/bluetooth_repository.dart b/mobile/lib/src/infrastructure/bluetooth_repository.dart index 6985a46a..239a37bd 100644 --- a/mobile/lib/src/infrastructure/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/bluetooth_repository.dart @@ -30,7 +30,6 @@ class BluetoothRepository implements IBluetoothRepository { flutterBlue.startScan(); } - @override Future addDevice(BluetoothDevice device) async { await Future.delayed(Duration(milliseconds: 400)); Bluetooth d = new Bluetooth(device); From 9680850fbe436f557d73307ecf40b2196293cda3 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Mon, 28 Sep 2020 16:52:02 -0400 Subject: [PATCH 20/56] Removed unecessary bluetooth from domain --- .../bluetooth/bluetoothSelector_cubit.dart | 3 +- .../bluetooth/bluetoothSelector_state.dart | 4 +- .../lib/src/domain/bluetooth/bluetooth.dart | 38 -------------- .../bluetooth/i_bluetooth_repository.dart | 9 ++-- mobile/lib/src/domain/domain.dart | 1 - .../infrastructure/bluetooth_repository.dart | 52 ++++++++++--------- 6 files changed, 37 insertions(+), 70 deletions(-) delete mode 100644 mobile/lib/src/domain/bluetooth/bluetooth.dart diff --git a/mobile/lib/src/application/bluetooth/bluetoothSelector_cubit.dart b/mobile/lib/src/application/bluetooth/bluetoothSelector_cubit.dart index d2dca79c..b529c128 100644 --- a/mobile/lib/src/application/bluetooth/bluetoothSelector_cubit.dart +++ b/mobile/lib/src/application/bluetooth/bluetoothSelector_cubit.dart @@ -1,5 +1,6 @@ import 'package:bloc/bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_blue/flutter_blue.dart'; import 'package:polydodo/src/domain/domain.dart'; import 'bluetoothSelector_state.dart'; @@ -19,7 +20,7 @@ class BluetoothSelectorCubit extends Cubit { .onError((e) => emit(BluetoothSearchError(e))); } - void connect(Bluetooth bluetoothDevice) async { + void connect(BluetoothDevice bluetoothDevice) async { emit(BluetoothConnecting()); _bluetoothRepository diff --git a/mobile/lib/src/application/bluetooth/bluetoothSelector_state.dart b/mobile/lib/src/application/bluetooth/bluetoothSelector_state.dart index 4d457c8d..67b2090d 100644 --- a/mobile/lib/src/application/bluetooth/bluetoothSelector_state.dart +++ b/mobile/lib/src/application/bluetooth/bluetoothSelector_state.dart @@ -1,11 +1,11 @@ -import 'package:polydodo/src/domain/bluetooth/bluetooth.dart'; +import 'package:flutter_blue/flutter_blue.dart'; abstract class BluetoothStates {} class BluetoothInitial extends BluetoothStates {} class BluetoothSearching extends BluetoothInitial { - final List devices; + final List devices; BluetoothSearching(this.devices); } diff --git a/mobile/lib/src/domain/bluetooth/bluetooth.dart b/mobile/lib/src/domain/bluetooth/bluetooth.dart deleted file mode 100644 index 5a06e5be..00000000 --- a/mobile/lib/src/domain/bluetooth/bluetooth.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter_blue/flutter_blue.dart'; - -class Bluetooth { - static const String BLE_SERVICE = "fe84"; - static const String BLE_RECEIVE = "2d30c082"; - static const String BLE_SEND = "2d30c083"; - - String id; - String name; - BluetoothDevice device; - BluetoothCharacteristic sendCharacteristic; - BluetoothCharacteristic receiveCharacteristic; - - Bluetooth(this.device) : assert(device != null) { - this.id = this.device.id.toString(); - this.name = this.device.name; - } - - BluetoothCharacteristic get send { - if (sendCharacteristic == null) - throw Exception('Proper Ganglion Send characteristic was not found'); - return sendCharacteristic; - } - - void setSendCharacteristic(BluetoothCharacteristic s) { - sendCharacteristic = s; - } - - BluetoothCharacteristic get receive { - if (receiveCharacteristic == null) - throw Exception('Proper Ganglion Receive characteristic was not found'); - return receiveCharacteristic; - } - - void setReceiveCharacteristic(BluetoothCharacteristic r) { - receiveCharacteristic = r; - } -} diff --git a/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart b/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart index ec2ca6e2..4f11a7a9 100644 --- a/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart +++ b/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart @@ -1,13 +1,14 @@ -import 'bluetooth.dart'; + +import 'package:flutter_blue/flutter_blue.dart'; abstract class IBluetoothRepository { void initializeBluetooth(); - - Future connect(Bluetooth bluetoothDevice); + + Future connect(BluetoothDevice bluetoothDevice); void disconnect(); Future>> startDataStream(); void stopDataStream(); - Stream> watch(); + Stream> watch(); } diff --git a/mobile/lib/src/domain/domain.dart b/mobile/lib/src/domain/domain.dart index c9995f41..4a9840e1 100644 --- a/mobile/lib/src/domain/domain.dart +++ b/mobile/lib/src/domain/domain.dart @@ -1,5 +1,4 @@ export 'bluetooth/i_bluetooth_repository.dart'; -export 'bluetooth/bluetooth.dart'; export 'eeg_data/eeg_data.dart'; export 'wallet/i_wallet_repository.dart'; export 'wallet/money.dart'; diff --git a/mobile/lib/src/infrastructure/bluetooth_repository.dart b/mobile/lib/src/infrastructure/bluetooth_repository.dart index 239a37bd..866d1a0b 100644 --- a/mobile/lib/src/infrastructure/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/bluetooth_repository.dart @@ -2,16 +2,20 @@ import 'dart:async'; import 'package:flutter_blue/flutter_blue.dart'; import 'package:polydodo/src/domain/bluetooth/i_bluetooth_repository.dart'; -import 'package:polydodo/src/domain/bluetooth/bluetooth.dart'; class BluetoothRepository implements IBluetoothRepository { - + 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'; - Bluetooth selectedDevice; - static List bluetoothPersistency = []; - final streamController = StreamController>(); + BluetoothDevice selectedDevice; + BluetoothCharacteristic sendCharacteristic; + BluetoothCharacteristic receiveCharacteristic; + + static List bluetoothPersistency = []; + final streamController = StreamController>(); void initializeBluetooth() { FlutterBlue flutterBlue = FlutterBlue.instance; @@ -32,42 +36,42 @@ class BluetoothRepository implements IBluetoothRepository { Future addDevice(BluetoothDevice device) async { await Future.delayed(Duration(milliseconds: 400)); - Bluetooth d = new Bluetooth(device); - final idx = bluetoothPersistency.indexOf(d); - idx == -1 ? bluetoothPersistency.add(d) : bluetoothPersistency[idx] = d; + final idx = bluetoothPersistency.indexOf(device); + idx == -1 ? bluetoothPersistency.add(device) : bluetoothPersistency[idx] = device; streamController.add(bluetoothPersistency); } - Future connect(Bluetooth bluetoothDevice) async { - await bluetoothDevice.device.connect().then((value) => + Future connect(BluetoothDevice bluetoothDevice) async { + await bluetoothDevice.connect().then((value) => {selectedDevice = bluetoothDevice, findRelevantCharacteristics()}); return; } void disconnect() async { - await selectedDevice.device.disconnect(); + await selectedDevice.disconnect(); } void findRelevantCharacteristics() { - selectedDevice.device.discoverServices().then((services) => { + selectedDevice.discoverServices().then((services) => { for (BluetoothService service in services) { - if (service.toString().contains(Bluetooth.BLE_SERVICE)) + if (service.toString().contains(BLE_SERVICE)) { for (BluetoothCharacteristic characteristic in service.characteristics) { if (characteristic .toString() - .contains(Bluetooth.BLE_RECEIVE)) + .contains(BLE_RECEIVE)) { - selectedDevice - .setReceiveCharacteristic(characteristic) + receiveCharacteristic = characteristic } else if (characteristic .toString() - .contains(Bluetooth.BLE_SEND)) - {selectedDevice.setSendCharacteristic(characteristic)} + .contains(BLE_SEND)) + { + sendCharacteristic = characteristic + } } } } @@ -75,17 +79,17 @@ class BluetoothRepository implements IBluetoothRepository { } Future>> startDataStream() async { - await selectedDevice.receive.setNotifyValue(true); + await receiveCharacteristic.setNotifyValue(true); - await selectedDevice.send.write(startStreamChar.codeUnits); - return selectedDevice.receive.value; + await sendCharacteristic.write(startStreamChar.codeUnits); + return receiveCharacteristic.value; } void stopDataStream() async { - await selectedDevice.receive.setNotifyValue(false); - await selectedDevice.send.write(stopStreamChar.codeUnits); + await receiveCharacteristic.setNotifyValue(false); + await sendCharacteristic.write(stopStreamChar.codeUnits); } @override - Stream> watch() => streamController.stream; + Stream> watch() => streamController.stream; } From 71c36f2534f271d1ff019dbf3d5352f048a711f4 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Mon, 28 Sep 2020 17:18:05 -0400 Subject: [PATCH 21/56] Created eeg data repository --- mobile/lib/src/application/application.dart | 4 +- ...bit.dart => bluetooth_selector_cubit.dart} | 2 +- ...ate.dart => bluetooth_selector_state.dart} | 0 .../src/application/eeg_data/data_cubit.dart | 97 +------------ mobile/lib/src/domain/domain.dart | 1 + mobile/lib/src/domain/eeg_data/eeg_data.dart | 48 +------ .../eeg_data/i_eeg_data_repository.dart | 8 ++ .../infrastructure/bluetooth_repository.dart | 2 +- .../infrastructure/eeg_data_repository.dart | 128 ++++++++++++++++++ mobile/lib/src/locator.dart | 4 + 10 files changed, 157 insertions(+), 137 deletions(-) rename mobile/lib/src/application/bluetooth/{bluetoothSelector_cubit.dart => bluetooth_selector_cubit.dart} (95%) rename mobile/lib/src/application/bluetooth/{bluetoothSelector_state.dart => bluetooth_selector_state.dart} (100%) create mode 100644 mobile/lib/src/domain/eeg_data/i_eeg_data_repository.dart create mode 100644 mobile/lib/src/infrastructure/eeg_data_repository.dart diff --git a/mobile/lib/src/application/application.dart b/mobile/lib/src/application/application.dart index 061b6221..147e1400 100644 --- a/mobile/lib/src/application/application.dart +++ b/mobile/lib/src/application/application.dart @@ -1,5 +1,5 @@ -export 'bluetooth/bluetoothSelector_cubit.dart'; -export 'bluetooth/bluetoothSelector_state.dart'; +export 'bluetooth/bluetooth_selector_cubit.dart'; +export 'bluetooth/bluetooth_selector_state.dart'; export 'eeg_data/data_cubit.dart'; export 'eeg_data/data_states.dart'; export 'wallets/wallets_cubit.dart'; diff --git a/mobile/lib/src/application/bluetooth/bluetoothSelector_cubit.dart b/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart similarity index 95% rename from mobile/lib/src/application/bluetooth/bluetoothSelector_cubit.dart rename to mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart index b529c128..e374d9ef 100644 --- a/mobile/lib/src/application/bluetooth/bluetoothSelector_cubit.dart +++ b/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart @@ -2,7 +2,7 @@ import 'package:bloc/bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_blue/flutter_blue.dart'; import 'package:polydodo/src/domain/domain.dart'; -import 'bluetoothSelector_state.dart'; +import 'bluetooth_selector_state.dart'; class BluetoothSelectorCubit extends Cubit { final IBluetoothRepository _bluetoothRepository; diff --git a/mobile/lib/src/application/bluetooth/bluetoothSelector_state.dart b/mobile/lib/src/application/bluetooth/bluetooth_selector_state.dart similarity index 100% rename from mobile/lib/src/application/bluetooth/bluetoothSelector_state.dart rename to mobile/lib/src/application/bluetooth/bluetooth_selector_state.dart diff --git a/mobile/lib/src/application/eeg_data/data_cubit.dart b/mobile/lib/src/application/eeg_data/data_cubit.dart index 5d7d009d..dd6ac919 100644 --- a/mobile/lib/src/application/eeg_data/data_cubit.dart +++ b/mobile/lib/src/application/eeg_data/data_cubit.dart @@ -1,7 +1,3 @@ -import 'dart:io'; - -import 'package:csv/csv.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:polydodo/src/domain/domain.dart'; import 'data_states.dart'; @@ -10,104 +6,21 @@ import 'package:flutter_bloc/flutter_bloc.dart'; class DataCubit extends Cubit { final IBluetoothRepository _bluetoothRepository; - // todo: create repo for eeg data - // final IDataRepository _dataRepository; - EEGData recordingData; + final IEEGDataRepository _eegDataRepository; - DataCubit(this._bluetoothRepository) + DataCubit(this._bluetoothRepository, this._eegDataRepository) : super(DataStateInitial()); void startStreaming() { emit(DataStateRecording()); - recordingData = new EEGData(); _bluetoothRepository .startDataStream() - .then((stream) => stream.listen((value) { - addData(value); - })); + .then((stream) => _eegDataRepository.createRecordingFromStream(stream)); } - void stopStreaming() async { - // todo: SAVE TO REPOS + void stopStreaming() { emit(DataStateInitial()); _bluetoothRepository.stopDataStream(); - - // todo: move save future to another file - final directory = await getExternalStorageDirectory(); - final pathOfTheFileToWrite = directory.path + '/' + recordingData.fileName + ".txt"; - File file = File(pathOfTheFileToWrite); - String csv = const ListToCsvConverter().convert(recordingData.values); - await file.writeAsString(csv); - } - - void addData(event) async { - int packetID = event[0]; - - // todo: handle packet id 0 (raw data) and possibly impedence for signal validation - if (packetID >= 101 && packetID <= 200) { - List data = formatData(event); - data = handleNegativesAndConvertToVolts(data); - - recordingData.addValues(data.sublist(0, 15)); - recordingData.addValues(data.sublist(15, 30)); - } - } - - List formatData(event) { - // Test event, comment scale factor - // event = [ 101, 0, 0, 0, 0, 8, 0, 5, 0, 0, 72, 0, 9, 240, 1, 176, 0, 48, 0, 8]; // Positive Test - // Expected [[0, 2, 10, 4], [262148, 507910, 393222, 8]] - // event = [ 101, 255, 255, 191, 255, 239, 255, 252, 255, 255, 88, 0, 11, 62, 56, 224, 0, 63, 240, 1 ]; // Negative Test - // Expected [[-3, -5, -7, -11], [-262139, -198429, -262137, -4095]] - - List data = getListForCSV(); - - data[0] = recordingData.sampleCounter; - data[1] = (event[1] << 11) | (event[2] << 3) | (event[3] >> 5); - data[2] = ((event[3] & 31) << 14) | (event[4] << 6) | (event[5] >> 2); - data[3] = ((event[5] & 3) << 17) | - (event[6] << 9) | - (event[7] << 1) | - (event[8] >> 7); - data[4] = ((event[8] & 127) << 12) | (event[9] << 4) | (event[10] >> 4); - data[15] = recordingData.sampleCounter++; - data[16] = ((event[10] & 15) << 15) | (event[11] << 7) | (event[12] >> 1); - data[17] = ((event[12] & 1) << 18) | - (event[13] << 10) | - (event[14] << 2) | - (event[15] >> 6); - data[18] = ((event[15] & 63) << 13) | (event[16] << 5) | (event[17] >> 3); - data[19] = ((event[17] & 7) << 16) | (event[18] << 8) | (event[19]); - - return data; - } - - List getListForCSV() { - List data = new List(30); - - for (int i = 5; i < 15; ++i) { - data[i] = 0; - data[i + 15] = 0; - } - return data; - } - - List handleNegativesAndConvertToVolts(data) { - for (int i = 1; i < 5; ++i) { - for (int j = 0; j < 2; ++j) { - int offset = 15 * j; - String binary = data[i + offset].toRadixString(2); - - if (binary[binary.length - 1] == '1') { - data[i + offset] = (~data[i + offset] & 524287 | 1) * -1; - } - - // Convert to microvolts using the scale factor - data[i + offset] = - data[i + offset].toDouble() * (1200000 / (8388607.0 * 1.5 * 51.0)); - } - } - - return data; + _eegDataRepository.stopRecordingFromStream(); } } diff --git a/mobile/lib/src/domain/domain.dart b/mobile/lib/src/domain/domain.dart index 4a9840e1..4d8ef56c 100644 --- a/mobile/lib/src/domain/domain.dart +++ b/mobile/lib/src/domain/domain.dart @@ -1,5 +1,6 @@ export 'bluetooth/i_bluetooth_repository.dart'; export 'eeg_data/eeg_data.dart'; +export 'eeg_data/i_eeg_data_repository.dart'; export 'wallet/i_wallet_repository.dart'; export 'wallet/money.dart'; export 'wallet/owner.dart'; diff --git a/mobile/lib/src/domain/eeg_data/eeg_data.dart b/mobile/lib/src/domain/eeg_data/eeg_data.dart index 38829705..832753ac 100644 --- a/mobile/lib/src/domain/eeg_data/eeg_data.dart +++ b/mobile/lib/src/domain/eeg_data/eeg_data.dart @@ -1,48 +1,14 @@ +// EEGData can be extended later to add our metrics class EEGData { String id; List _values; - int sampleCounter; + int sampleCounter = 0; - EEGData() { - this.id = DateTime.now().toString(); - sampleCounter = 0; - initializeData(); - } + EEGData(this.id, this._values) + : assert(id != null), + assert(_values != null); - void addValues(List value) { - _values.add(value); - } + List get values => _values; - List get values { - return _values; - } - - String get fileName { - return id; - } - - void initializeData() { - _values = new List(); - _values.add(["%OpenBCI Raw EEG Data"]); - _values.add(["%Number of channels = 4"]); - _values.add(["%Sample Rate = 200 Hz"]); - _values.add(["%Board = OpenBCI_GUI\$BoardGanglionBLE"]); - _values.add([ - "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)" - ]); - } + String get fileName => id; } diff --git a/mobile/lib/src/domain/eeg_data/i_eeg_data_repository.dart b/mobile/lib/src/domain/eeg_data/i_eeg_data_repository.dart new file mode 100644 index 00000000..d1e12b38 --- /dev/null +++ b/mobile/lib/src/domain/eeg_data/i_eeg_data_repository.dart @@ -0,0 +1,8 @@ +abstract class IEEGDataRepository { + void createRecordingFromStream(Stream> stream); + void stopRecordingFromStream(); + + // todo: implement export and import + void importData(); + void exportData(); +} diff --git a/mobile/lib/src/infrastructure/bluetooth_repository.dart b/mobile/lib/src/infrastructure/bluetooth_repository.dart index 866d1a0b..ba5eb4f5 100644 --- a/mobile/lib/src/infrastructure/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/bluetooth_repository.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:flutter_blue/flutter_blue.dart'; -import 'package:polydodo/src/domain/bluetooth/i_bluetooth_repository.dart'; +import 'package:polydodo/src/domain/domain.dart'; class BluetoothRepository implements IBluetoothRepository { static const String BLE_SERVICE = "fe84"; diff --git a/mobile/lib/src/infrastructure/eeg_data_repository.dart b/mobile/lib/src/infrastructure/eeg_data_repository.dart new file mode 100644 index 00000000..3b9da341 --- /dev/null +++ b/mobile/lib/src/infrastructure/eeg_data_repository.dart @@ -0,0 +1,128 @@ +import 'dart:io'; + +import 'package:csv/csv.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:polydodo/src/domain/domain.dart'; + +class EEGDataRepository implements IEEGDataRepository { + EEGData recordingData; + + void createRecordingFromStream(Stream> stream) { + recordingData = new EEGData(DateTime.now().toString(), initializeData()); + stream.listen((value) { + addData(value); + }); + } + + void stopRecordingFromStream() async { + // todo: move save future to another file + final directory = await getExternalStorageDirectory(); + final pathOfTheFileToWrite = + directory.path + '/' + recordingData.fileName + ".txt"; + File file = File(pathOfTheFileToWrite); + String csv = const ListToCsvConverter().convert(recordingData.values); + await file.writeAsString(csv); + } + + // todo: implement export and import + void importData() {} + void exportData() {} + + List initializeData() { + List values = new List(); + values.add(["%OpenBCI Raw EEG Data"]); + values.add(["%Number of channels = 4"]); + values.add(["%Sample Rate = 200 Hz"]); + values.add(["%Board = OpenBCI_GUI\$BoardGanglionBLE"]); + values.add([ + "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)" + ]); + + return values; + } + + void addData(event) async { + int packetID = event[0]; + + // todo: handle packet id 0 (raw data) and possibly impedence for signal validation + if (packetID >= 101 && packetID <= 200) { + List data = formatData(event); + data = handleNegativesAndConvertToVolts(data); + + recordingData.values.add(data.sublist(0, 15)); + recordingData.values.add(data.sublist(15, 30)); + } + } + + List formatData(event) { + // Test event, comment scale factor + // event = [ 101, 0, 0, 0, 0, 8, 0, 5, 0, 0, 72, 0, 9, 240, 1, 176, 0, 48, 0, 8]; // Positive Test + // Expected [[0, 2, 10, 4], [262148, 507910, 393222, 8]] + // event = [ 101, 255, 255, 191, 255, 239, 255, 252, 255, 255, 88, 0, 11, 62, 56, 224, 0, 63, 240, 1 ]; // Negative Test + // Expected [[-3, -5, -7, -11], [-262139, -198429, -262137, -4095]] + + List data = getListForCSV(); + + data[0] = recordingData.sampleCounter; + data[1] = (event[1] << 11) | (event[2] << 3) | (event[3] >> 5); + data[2] = ((event[3] & 31) << 14) | (event[4] << 6) | (event[5] >> 2); + data[3] = ((event[5] & 3) << 17) | + (event[6] << 9) | + (event[7] << 1) | + (event[8] >> 7); + data[4] = ((event[8] & 127) << 12) | (event[9] << 4) | (event[10] >> 4); + data[15] = recordingData.sampleCounter++; + data[16] = ((event[10] & 15) << 15) | (event[11] << 7) | (event[12] >> 1); + data[17] = ((event[12] & 1) << 18) | + (event[13] << 10) | + (event[14] << 2) | + (event[15] >> 6); + data[18] = ((event[15] & 63) << 13) | (event[16] << 5) | (event[17] >> 3); + data[19] = ((event[17] & 7) << 16) | (event[18] << 8) | (event[19]); + + return data; + } + + List getListForCSV() { + List data = new List(30); + + for (int i = 5; i < 15; ++i) { + data[i] = 0; + data[i + 15] = 0; + } + return data; + } + + List handleNegativesAndConvertToVolts(data) { + for (int i = 1; i < 5; ++i) { + for (int j = 0; j < 2; ++j) { + int offset = 15 * j; + String binary = data[i + offset].toRadixString(2); + + if (binary[binary.length - 1] == '1') { + data[i + offset] = (~data[i + offset] & 524287 | 1) * -1; + } + + // Convert to microvolts using the scale factor + data[i + offset] = + data[i + offset].toDouble() * (1200000 / (8388607.0 * 1.5 * 51.0)); + } + } + + return data; + } +} diff --git a/mobile/lib/src/locator.dart b/mobile/lib/src/locator.dart index 354662d7..fa1ff061 100644 --- a/mobile/lib/src/locator.dart +++ b/mobile/lib/src/locator.dart @@ -2,6 +2,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; import 'package:polydodo/src/application/eeg_data/data_cubit.dart'; import 'package:polydodo/src/domain/domain.dart'; +import 'package:polydodo/src/infrastructure/eeg_data_repository.dart'; import 'package:polydodo/src/infrastructure/infrastructure.dart'; import 'application/application.dart'; @@ -12,6 +13,8 @@ void registerServices() { _serviceLocator.registerSingleton(MockWalletRepository()); _serviceLocator .registerSingleton(BluetoothRepository()); + _serviceLocator + .registerSingleton(EEGDataRepository()); } /// This function creates all the BlocProviders used in this app @@ -29,6 +32,7 @@ List createBlocProviders() => [ BlocProvider( create: (context) => DataCubit( _serviceLocator.get(), + _serviceLocator.get(), ), ), ]; From 698567237816150557f0920c3e000b5546f3181e Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Tue, 29 Sep 2020 16:25:51 -0400 Subject: [PATCH 22/56] Changed string id to UniqueID, fixed state inconsistency --- .../src/application/bluetooth/bluetooth_selector_state.dart | 2 +- mobile/lib/src/domain/domain.dart | 1 + mobile/lib/src/domain/eeg_data/eeg_data.dart | 6 ++++-- mobile/lib/src/infrastructure/eeg_data_repository.dart | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/mobile/lib/src/application/bluetooth/bluetooth_selector_state.dart b/mobile/lib/src/application/bluetooth/bluetooth_selector_state.dart index 67b2090d..604265ba 100644 --- a/mobile/lib/src/application/bluetooth/bluetooth_selector_state.dart +++ b/mobile/lib/src/application/bluetooth/bluetooth_selector_state.dart @@ -4,7 +4,7 @@ abstract class BluetoothStates {} class BluetoothInitial extends BluetoothStates {} -class BluetoothSearching extends BluetoothInitial { +class BluetoothSearching extends BluetoothStates { final List devices; BluetoothSearching(this.devices); diff --git a/mobile/lib/src/domain/domain.dart b/mobile/lib/src/domain/domain.dart index 4d8ef56c..8bd22a81 100644 --- a/mobile/lib/src/domain/domain.dart +++ b/mobile/lib/src/domain/domain.dart @@ -4,3 +4,4 @@ export 'eeg_data/i_eeg_data_repository.dart'; export 'wallet/i_wallet_repository.dart'; export 'wallet/money.dart'; export 'wallet/owner.dart'; +export 'unique_id.dart'; \ No newline at end of file diff --git a/mobile/lib/src/domain/eeg_data/eeg_data.dart b/mobile/lib/src/domain/eeg_data/eeg_data.dart index 832753ac..da8aaacc 100644 --- a/mobile/lib/src/domain/eeg_data/eeg_data.dart +++ b/mobile/lib/src/domain/eeg_data/eeg_data.dart @@ -1,6 +1,8 @@ // EEGData can be extended later to add our metrics +import 'package:polydodo/src/domain/domain.dart'; + class EEGData { - String id; + UniqueId id; List _values; int sampleCounter = 0; @@ -10,5 +12,5 @@ class EEGData { List get values => _values; - String get fileName => id; + String get fileName => id.toString(); } diff --git a/mobile/lib/src/infrastructure/eeg_data_repository.dart b/mobile/lib/src/infrastructure/eeg_data_repository.dart index 3b9da341..409f52b3 100644 --- a/mobile/lib/src/infrastructure/eeg_data_repository.dart +++ b/mobile/lib/src/infrastructure/eeg_data_repository.dart @@ -8,7 +8,7 @@ class EEGDataRepository implements IEEGDataRepository { EEGData recordingData; void createRecordingFromStream(Stream> stream) { - recordingData = new EEGData(DateTime.now().toString(), initializeData()); + recordingData = new EEGData(UniqueId.from(DateTime.now().toString()), initializeData()); stream.listen((value) { addData(value); }); From 3bead5f70a30cb5f0d9fb621bc7f22afbdedef78 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Wed, 30 Sep 2020 19:07:15 -0400 Subject: [PATCH 23/56] Fixed typo, removed unecessary delay --- .../src/application/bluetooth/bluetooth_selector_cubit.dart | 2 +- .../src/application/bluetooth/bluetooth_selector_state.dart | 2 +- mobile/lib/src/infrastructure/bluetooth_repository.dart | 3 +-- .../presentation/bluetooth_route/bluetoothSelector_route.dart | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart b/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart index e374d9ef..bee30843 100644 --- a/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart +++ b/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart @@ -25,7 +25,7 @@ class BluetoothSelectorCubit extends Cubit { _bluetoothRepository .connect(bluetoothDevice) - .then((value) => emit(BluetoothConnectionSucess())) + .then((value) => emit(BluetoothConnectionSuccess())) .catchError((e) => {emit(BluetoothConnectionFailure(e))}); } } diff --git a/mobile/lib/src/application/bluetooth/bluetooth_selector_state.dart b/mobile/lib/src/application/bluetooth/bluetooth_selector_state.dart index 604265ba..61602b4b 100644 --- a/mobile/lib/src/application/bluetooth/bluetooth_selector_state.dart +++ b/mobile/lib/src/application/bluetooth/bluetooth_selector_state.dart @@ -18,7 +18,7 @@ class BluetoothSearchError extends BluetoothStates { class BluetoothConnecting extends BluetoothStates {} -class BluetoothConnectionSucess extends BluetoothStates {} +class BluetoothConnectionSuccess extends BluetoothStates {} class BluetoothConnectionFailure extends BluetoothStates { final Exception cause; diff --git a/mobile/lib/src/infrastructure/bluetooth_repository.dart b/mobile/lib/src/infrastructure/bluetooth_repository.dart index ba5eb4f5..e0880949 100644 --- a/mobile/lib/src/infrastructure/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/bluetooth_repository.dart @@ -34,8 +34,7 @@ class BluetoothRepository implements IBluetoothRepository { flutterBlue.startScan(); } - Future addDevice(BluetoothDevice device) async { - await Future.delayed(Duration(milliseconds: 400)); + void addDevice(BluetoothDevice device) { final idx = bluetoothPersistency.indexOf(device); idx == -1 ? bluetoothPersistency.add(device) : bluetoothPersistency[idx] = device; streamController.add(bluetoothPersistency); diff --git a/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart b/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart index f3f1f52e..0c0eb467 100644 --- a/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart +++ b/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart @@ -25,7 +25,7 @@ class BluetoothSelectorRoute extends StatelessWidget { content: Text('Unable to connect to device because ${state.cause}'), )); - } else if (state is BluetoothConnectionSucess) { + } else if (state is BluetoothConnectionSuccess) { Navigator.push( context, MaterialPageRoute( From 707a85f8177665ddd58e86be31767c2abd3dfe0e Mon Sep 17 00:00:00 2001 From: Anes Belfodil Date: Thu, 1 Oct 2020 11:39:50 -0400 Subject: [PATCH 24/56] format --- .../src/application/eeg_data/data_states.dart | 2 +- .../bluetooth/i_bluetooth_repository.dart | 1 - mobile/lib/src/domain/domain.dart | 2 +- .../infrastructure/bluetooth_repository.dart | 20 +++++++------------ .../infrastructure/eeg_data_repository.dart | 3 ++- .../bluetoothSelector_route.dart | 5 +++-- mobile/lib/src/presentation/presentation.dart | 2 +- .../recording/recording_route.dart | 7 ++++--- 8 files changed, 19 insertions(+), 23 deletions(-) diff --git a/mobile/lib/src/application/eeg_data/data_states.dart b/mobile/lib/src/application/eeg_data/data_states.dart index 054daed9..69453e90 100644 --- a/mobile/lib/src/application/eeg_data/data_states.dart +++ b/mobile/lib/src/application/eeg_data/data_states.dart @@ -2,4 +2,4 @@ abstract class DataState {} class DataStateInitial extends DataState {} -class DataStateRecording extends DataState {} \ No newline at end of file +class DataStateRecording extends DataState {} diff --git a/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart b/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart index 4f11a7a9..7e009267 100644 --- a/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart +++ b/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart @@ -1,4 +1,3 @@ - import 'package:flutter_blue/flutter_blue.dart'; abstract class IBluetoothRepository { diff --git a/mobile/lib/src/domain/domain.dart b/mobile/lib/src/domain/domain.dart index 8bd22a81..82c8c7f6 100644 --- a/mobile/lib/src/domain/domain.dart +++ b/mobile/lib/src/domain/domain.dart @@ -4,4 +4,4 @@ export 'eeg_data/i_eeg_data_repository.dart'; export 'wallet/i_wallet_repository.dart'; export 'wallet/money.dart'; export 'wallet/owner.dart'; -export 'unique_id.dart'; \ No newline at end of file +export 'unique_id.dart'; diff --git a/mobile/lib/src/infrastructure/bluetooth_repository.dart b/mobile/lib/src/infrastructure/bluetooth_repository.dart index e0880949..fd21fa4e 100644 --- a/mobile/lib/src/infrastructure/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/bluetooth_repository.dart @@ -36,7 +36,9 @@ class BluetoothRepository implements IBluetoothRepository { void addDevice(BluetoothDevice device) { final idx = bluetoothPersistency.indexOf(device); - idx == -1 ? bluetoothPersistency.add(device) : bluetoothPersistency[idx] = device; + idx == -1 + ? bluetoothPersistency.add(device) + : bluetoothPersistency[idx] = device; streamController.add(bluetoothPersistency); } @@ -59,18 +61,10 @@ class BluetoothRepository implements IBluetoothRepository { for (BluetoothCharacteristic characteristic in service.characteristics) { - if (characteristic - .toString() - .contains(BLE_RECEIVE)) - { - receiveCharacteristic = characteristic - } - else if (characteristic - .toString() - .contains(BLE_SEND)) - { - sendCharacteristic = characteristic - } + if (characteristic.toString().contains(BLE_RECEIVE)) + {receiveCharacteristic = characteristic} + else if (characteristic.toString().contains(BLE_SEND)) + {sendCharacteristic = characteristic} } } } diff --git a/mobile/lib/src/infrastructure/eeg_data_repository.dart b/mobile/lib/src/infrastructure/eeg_data_repository.dart index 409f52b3..4732d648 100644 --- a/mobile/lib/src/infrastructure/eeg_data_repository.dart +++ b/mobile/lib/src/infrastructure/eeg_data_repository.dart @@ -8,7 +8,8 @@ class EEGDataRepository implements IEEGDataRepository { EEGData recordingData; void createRecordingFromStream(Stream> stream) { - recordingData = new EEGData(UniqueId.from(DateTime.now().toString()), initializeData()); + recordingData = + new EEGData(UniqueId.from(DateTime.now().toString()), initializeData()); stream.listen((value) { addData(value); }); diff --git a/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart b/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart index 0c0eb467..5bd66ba3 100644 --- a/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart +++ b/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart @@ -40,8 +40,9 @@ class BluetoothSelectorRoute extends StatelessWidget { itemBuilder: (context, index) { return Card( child: ListTile( - onTap: () => BlocProvider.of(context) - .connect(state.devices[index]), + onTap: () => + BlocProvider.of(context) + .connect(state.devices[index]), title: Text(state.devices[index].name), subtitle: Text(state.devices[index].id.toString())), ); diff --git a/mobile/lib/src/presentation/presentation.dart b/mobile/lib/src/presentation/presentation.dart index 6ff51758..09a7172a 100644 --- a/mobile/lib/src/presentation/presentation.dart +++ b/mobile/lib/src/presentation/presentation.dart @@ -1,3 +1,3 @@ export 'bluetooth_route/bluetoothSelector_route.dart'; export 'recording/recording_route.dart'; -export 'wallets/wallets_route.dart'; \ No newline at end of file +export 'wallets/wallets_route.dart'; diff --git a/mobile/lib/src/presentation/recording/recording_route.dart b/mobile/lib/src/presentation/recording/recording_route.dart index e7ea0f26..3f743cb5 100644 --- a/mobile/lib/src/presentation/recording/recording_route.dart +++ b/mobile/lib/src/presentation/recording/recording_route.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:polydodo/src/application/application.dart'; - class RecordingRoute extends StatelessWidget { static const name = 'recordingRoute'; @@ -28,7 +27,8 @@ class RecordingRoute extends StatelessWidget { children: [ RaisedButton( child: Text("Start"), - onPressed: () => BlocProvider.of(context).startStreaming(), + onPressed: () => + BlocProvider.of(context).startStreaming(), ), ]), ); @@ -39,7 +39,8 @@ class RecordingRoute extends StatelessWidget { children: [ RaisedButton( child: Text("Stop"), - onPressed: () => BlocProvider.of(context).stopStreaming(), + onPressed: () => + BlocProvider.of(context).stopStreaming(), ), ]), ); From 3c67918083d3c182531a003b5453db100fde4bc5 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Thu, 1 Oct 2020 11:46:05 -0400 Subject: [PATCH 25/56] Fixed typo --- .../bluetooth_route/bluetoothSelector_route.dart | 7 ++++--- mobile/pubspec.lock | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart b/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart index 0c0eb467..09a7414f 100644 --- a/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart +++ b/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart @@ -18,7 +18,7 @@ class BluetoothSelectorRoute extends StatelessWidget { if (state is BluetoothSearchError) { Scaffold.of(context).showSnackBar(SnackBar( content: Text( - 'Unable to search for bluetooth devices Wallets because ${state.cause}'), + 'Unable to search for bluetooth devices because ${state.cause}'), )); } else if (state is BluetoothConnectionFailure) { Scaffold.of(context).showSnackBar(SnackBar( @@ -40,8 +40,9 @@ class BluetoothSelectorRoute extends StatelessWidget { itemBuilder: (context, index) { return Card( child: ListTile( - onTap: () => BlocProvider.of(context) - .connect(state.devices[index]), + onTap: () => + BlocProvider.of(context) + .connect(state.devices[index]), title: Text(state.devices[index].name), subtitle: Text(state.devices[index].id.toString())), ); diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 64d91338..76bacfff 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -402,5 +402,5 @@ packages: source: hosted version: "0.1.0" sdks: - dart: ">=2.10.0-137.0 <2.10.0" + dart: ">=2.9.0-14.0.dev <3.0.0" flutter: ">=1.16.0 <2.0.0" From 82c7b1e2277db6f1e28a794062b3384e59420b2f Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Thu, 1 Oct 2020 12:03:43 -0400 Subject: [PATCH 26/56] Removed general export files in favor of individual import --- mobile/lib/src/app.dart | 3 ++- mobile/lib/src/application/application.dart | 6 ------ .../bluetooth/bluetooth_selector_cubit.dart | 8 ++++---- .../bluetooth/bluetooth_selector_state.dart | 10 +++++----- mobile/lib/src/application/eeg_data/data_cubit.dart | 3 ++- mobile/lib/src/domain/domain.dart | 7 ------- mobile/lib/src/domain/eeg_data/eeg_data.dart | 2 +- .../lib/src/infrastructure/bluetooth_repository.dart | 2 +- mobile/lib/src/infrastructure/eeg_data_repository.dart | 4 +++- mobile/lib/src/infrastructure/infrastructure.dart | 3 --- .../bluetooth_route/bluetoothSelector_route.dart | 9 +++++---- mobile/lib/src/presentation/presentation.dart | 3 --- .../src/presentation/recording/recording_route.dart | 5 +++++ 13 files changed, 28 insertions(+), 37 deletions(-) delete mode 100644 mobile/lib/src/application/application.dart delete mode 100644 mobile/lib/src/domain/domain.dart delete mode 100644 mobile/lib/src/infrastructure/infrastructure.dart delete mode 100644 mobile/lib/src/presentation/presentation.dart diff --git a/mobile/lib/src/app.dart b/mobile/lib/src/app.dart index a306e957..2f36cc41 100644 --- a/mobile/lib/src/app.dart +++ b/mobile/lib/src/app.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:polydodo/src/presentation/bluetooth_route/bluetoothSelector_route.dart'; +import 'package:polydodo/src/presentation/wallets/wallets_route.dart'; -import 'presentation/presentation.dart'; import 'locator.dart'; import 'theme.dart'; diff --git a/mobile/lib/src/application/application.dart b/mobile/lib/src/application/application.dart deleted file mode 100644 index 147e1400..00000000 --- a/mobile/lib/src/application/application.dart +++ /dev/null @@ -1,6 +0,0 @@ -export 'bluetooth/bluetooth_selector_cubit.dart'; -export 'bluetooth/bluetooth_selector_state.dart'; -export 'eeg_data/data_cubit.dart'; -export 'eeg_data/data_states.dart'; -export 'wallets/wallets_cubit.dart'; -export 'wallets/wallets_state.dart'; diff --git a/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart b/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart index bee30843..2ad82aae 100644 --- a/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart +++ b/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart @@ -1,7 +1,7 @@ import 'package:bloc/bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_blue/flutter_blue.dart'; -import 'package:polydodo/src/domain/domain.dart'; +import 'package:polydodo/src/domain/bluetooth/i_bluetooth_repository.dart'; import 'bluetooth_selector_state.dart'; class BluetoothSelectorCubit extends Cubit { @@ -16,12 +16,12 @@ class BluetoothSelectorCubit extends Cubit { _bluetoothRepository.initializeBluetooth(); _bluetoothRepository .watch() - .listen((devices) => emit(BluetoothSearching(devices))) - .onError((e) => emit(BluetoothSearchError(e))); + .listen((devices) => emit(BluetoothSearchInProgress(devices))) + .onError((e) => emit(BluetoothSearchFailure(e))); } void connect(BluetoothDevice bluetoothDevice) async { - emit(BluetoothConnecting()); + emit(BluetoothConnectionInProgress()); _bluetoothRepository .connect(bluetoothDevice) diff --git a/mobile/lib/src/application/bluetooth/bluetooth_selector_state.dart b/mobile/lib/src/application/bluetooth/bluetooth_selector_state.dart index 61602b4b..349b83b0 100644 --- a/mobile/lib/src/application/bluetooth/bluetooth_selector_state.dart +++ b/mobile/lib/src/application/bluetooth/bluetooth_selector_state.dart @@ -4,19 +4,19 @@ abstract class BluetoothStates {} class BluetoothInitial extends BluetoothStates {} -class BluetoothSearching extends BluetoothStates { +class BluetoothSearchInProgress extends BluetoothStates { final List devices; - BluetoothSearching(this.devices); + BluetoothSearchInProgress(this.devices); } -class BluetoothSearchError extends BluetoothStates { +class BluetoothSearchFailure extends BluetoothStates { final Exception cause; - BluetoothSearchError(this.cause); + BluetoothSearchFailure(this.cause); } -class BluetoothConnecting extends BluetoothStates {} +class BluetoothConnectionInProgress extends BluetoothStates {} class BluetoothConnectionSuccess extends BluetoothStates {} diff --git a/mobile/lib/src/application/eeg_data/data_cubit.dart b/mobile/lib/src/application/eeg_data/data_cubit.dart index dd6ac919..b0aad056 100644 --- a/mobile/lib/src/application/eeg_data/data_cubit.dart +++ b/mobile/lib/src/application/eeg_data/data_cubit.dart @@ -1,4 +1,5 @@ -import 'package:polydodo/src/domain/domain.dart'; +import 'package:polydodo/src/domain/bluetooth/i_bluetooth_repository.dart'; +import 'package:polydodo/src/domain/eeg_data/i_eeg_data_repository.dart'; import 'data_states.dart'; import 'package:bloc/bloc.dart'; diff --git a/mobile/lib/src/domain/domain.dart b/mobile/lib/src/domain/domain.dart deleted file mode 100644 index 82c8c7f6..00000000 --- a/mobile/lib/src/domain/domain.dart +++ /dev/null @@ -1,7 +0,0 @@ -export 'bluetooth/i_bluetooth_repository.dart'; -export 'eeg_data/eeg_data.dart'; -export 'eeg_data/i_eeg_data_repository.dart'; -export 'wallet/i_wallet_repository.dart'; -export 'wallet/money.dart'; -export 'wallet/owner.dart'; -export 'unique_id.dart'; diff --git a/mobile/lib/src/domain/eeg_data/eeg_data.dart b/mobile/lib/src/domain/eeg_data/eeg_data.dart index da8aaacc..4f6b38ab 100644 --- a/mobile/lib/src/domain/eeg_data/eeg_data.dart +++ b/mobile/lib/src/domain/eeg_data/eeg_data.dart @@ -1,5 +1,5 @@ // EEGData can be extended later to add our metrics -import 'package:polydodo/src/domain/domain.dart'; +import '../unique_id.dart'; class EEGData { UniqueId id; diff --git a/mobile/lib/src/infrastructure/bluetooth_repository.dart b/mobile/lib/src/infrastructure/bluetooth_repository.dart index fd21fa4e..988fa97c 100644 --- a/mobile/lib/src/infrastructure/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/bluetooth_repository.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:flutter_blue/flutter_blue.dart'; -import 'package:polydodo/src/domain/domain.dart'; +import 'package:polydodo/src/domain/bluetooth/i_bluetooth_repository.dart'; class BluetoothRepository implements IBluetoothRepository { static const String BLE_SERVICE = "fe84"; diff --git a/mobile/lib/src/infrastructure/eeg_data_repository.dart b/mobile/lib/src/infrastructure/eeg_data_repository.dart index 4732d648..faabce30 100644 --- a/mobile/lib/src/infrastructure/eeg_data_repository.dart +++ b/mobile/lib/src/infrastructure/eeg_data_repository.dart @@ -2,7 +2,9 @@ import 'dart:io'; import 'package:csv/csv.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:polydodo/src/domain/domain.dart'; +import 'package:polydodo/src/domain/eeg_data/eeg_data.dart'; +import 'package:polydodo/src/domain/eeg_data/i_eeg_data_repository.dart'; +import 'package:polydodo/src/domain/unique_id.dart'; class EEGDataRepository implements IEEGDataRepository { EEGData recordingData; diff --git a/mobile/lib/src/infrastructure/infrastructure.dart b/mobile/lib/src/infrastructure/infrastructure.dart deleted file mode 100644 index f689eead..00000000 --- a/mobile/lib/src/infrastructure/infrastructure.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'bluetooth_repository.dart'; -export 'mock_wallet_repository.dart'; -export 'eeg_data_repository.dart'; diff --git a/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart b/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart index 09a7414f..c1a4d549 100644 --- a/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart +++ b/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:polydodo/src/application/application.dart'; -import 'package:polydodo/src/presentation/presentation.dart'; +import 'package:polydodo/src/application/bluetooth/bluetooth_selector_cubit.dart'; +import 'package:polydodo/src/application/bluetooth/bluetooth_selector_state.dart'; +import 'package:polydodo/src/presentation/recording/recording_route.dart'; class BluetoothSelectorRoute extends StatelessWidget { static const name = 'bluetoothRoute'; @@ -15,7 +16,7 @@ class BluetoothSelectorRoute extends StatelessWidget { body: BlocConsumer( listener: (context, state) { print(state.runtimeType); - if (state is BluetoothSearchError) { + if (state is BluetoothSearchFailure) { Scaffold.of(context).showSnackBar(SnackBar( content: Text( 'Unable to search for bluetooth devices because ${state.cause}'), @@ -34,7 +35,7 @@ class BluetoothSelectorRoute extends StatelessWidget { } }, builder: (context, state) { - if (state is BluetoothSearching) + if (state is BluetoothSearchInProgress) return ListView.builder( itemCount: state.devices.length, itemBuilder: (context, index) { diff --git a/mobile/lib/src/presentation/presentation.dart b/mobile/lib/src/presentation/presentation.dart deleted file mode 100644 index 09a7172a..00000000 --- a/mobile/lib/src/presentation/presentation.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'bluetooth_route/bluetoothSelector_route.dart'; -export 'recording/recording_route.dart'; -export 'wallets/wallets_route.dart'; diff --git a/mobile/lib/src/presentation/recording/recording_route.dart b/mobile/lib/src/presentation/recording/recording_route.dart index 3f743cb5..f8dc2e12 100644 --- a/mobile/lib/src/presentation/recording/recording_route.dart +++ b/mobile/lib/src/presentation/recording/recording_route.dart @@ -1,6 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +<<<<<<< Updated upstream import 'package:polydodo/src/application/application.dart'; +======= +import 'package:polydodo/src/application/eeg_data/data_cubit.dart'; +import 'package:polydodo/src/application/eeg_data/data_states.dart'; +>>>>>>> Stashed changes class RecordingRoute extends StatelessWidget { static const name = 'recordingRoute'; From 798d10a3ad3f3b3b497060db41f4c9780e11f51c Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Thu, 1 Oct 2020 12:10:06 -0400 Subject: [PATCH 27/56] Added missing files for last commit --- mobile/lib/src/locator.dart | 13 ++++++++++--- .../src/presentation/recording/recording_route.dart | 4 ---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/mobile/lib/src/locator.dart b/mobile/lib/src/locator.dart index 33f63e98..691fc184 100644 --- a/mobile/lib/src/locator.dart +++ b/mobile/lib/src/locator.dart @@ -1,8 +1,15 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; -import 'package:polydodo/src/domain/domain.dart'; -import 'package:polydodo/src/infrastructure/infrastructure.dart'; -import 'application/application.dart'; +import 'package:polydodo/src/application/eeg_data/data_cubit.dart'; +import 'package:polydodo/src/infrastructure/mock_wallet_repository.dart'; + +import 'application/bluetooth/bluetooth_selector_cubit.dart'; +import 'application/wallets/wallets_cubit.dart'; +import 'domain/bluetooth/i_bluetooth_repository.dart'; +import 'domain/eeg_data/i_eeg_data_repository.dart'; +import 'domain/wallet/i_wallet_repository.dart'; +import 'infrastructure/bluetooth_repository.dart'; +import 'infrastructure/eeg_data_repository.dart'; /// Private GetIt instance as we want all DI to be performed here in this file final _serviceLocator = GetIt.asNewInstance(); diff --git a/mobile/lib/src/presentation/recording/recording_route.dart b/mobile/lib/src/presentation/recording/recording_route.dart index f8dc2e12..bcb475ac 100644 --- a/mobile/lib/src/presentation/recording/recording_route.dart +++ b/mobile/lib/src/presentation/recording/recording_route.dart @@ -1,11 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -<<<<<<< Updated upstream -import 'package:polydodo/src/application/application.dart'; -======= import 'package:polydodo/src/application/eeg_data/data_cubit.dart'; import 'package:polydodo/src/application/eeg_data/data_states.dart'; ->>>>>>> Stashed changes class RecordingRoute extends StatelessWidget { static const name = 'recordingRoute'; From dd694c5702351dd6fec3506eca316af360c11e54 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Thu, 1 Oct 2020 14:35:35 -0400 Subject: [PATCH 28/56] Extracted openbci header and only adding it at export time --- mobile/lib/src/domain/unique_id.dart | 2 +- mobile/lib/src/infrastructure/constants.dart | 23 ++++++++++++++ .../infrastructure/eeg_data_repository.dart | 31 ++----------------- 3 files changed, 27 insertions(+), 29 deletions(-) create mode 100644 mobile/lib/src/infrastructure/constants.dart diff --git a/mobile/lib/src/domain/unique_id.dart b/mobile/lib/src/domain/unique_id.dart index 9b301f89..56895b7e 100644 --- a/mobile/lib/src/domain/unique_id.dart +++ b/mobile/lib/src/domain/unique_id.dart @@ -16,5 +16,5 @@ class UniqueId extends Equatable { List get props => [_id]; @override - bool get stringify => true; + toString() => _id; } diff --git a/mobile/lib/src/infrastructure/constants.dart b/mobile/lib/src/infrastructure/constants.dart new file mode 100644 index 00000000..cc7f7932 --- /dev/null +++ b/mobile/lib/src/infrastructure/constants.dart @@ -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)" + ] +]; diff --git a/mobile/lib/src/infrastructure/eeg_data_repository.dart b/mobile/lib/src/infrastructure/eeg_data_repository.dart index faabce30..62f8bf75 100644 --- a/mobile/lib/src/infrastructure/eeg_data_repository.dart +++ b/mobile/lib/src/infrastructure/eeg_data_repository.dart @@ -5,13 +5,14 @@ import 'package:path_provider/path_provider.dart'; import 'package:polydodo/src/domain/eeg_data/eeg_data.dart'; import 'package:polydodo/src/domain/eeg_data/i_eeg_data_repository.dart'; import 'package:polydodo/src/domain/unique_id.dart'; +import 'constants.dart'; class EEGDataRepository implements IEEGDataRepository { EEGData recordingData; void createRecordingFromStream(Stream> stream) { recordingData = - new EEGData(UniqueId.from(DateTime.now().toString()), initializeData()); + new EEGData(UniqueId.from(DateTime.now().toString()), new List()); stream.listen((value) { addData(value); }); @@ -23,6 +24,7 @@ class EEGDataRepository implements IEEGDataRepository { final pathOfTheFileToWrite = directory.path + '/' + recordingData.fileName + ".txt"; File file = File(pathOfTheFileToWrite); + recordingData.values.insertAll(0, OPEN_BCI_HEADER); String csv = const ListToCsvConverter().convert(recordingData.values); await file.writeAsString(csv); } @@ -31,33 +33,6 @@ class EEGDataRepository implements IEEGDataRepository { void importData() {} void exportData() {} - List initializeData() { - List values = new List(); - values.add(["%OpenBCI Raw EEG Data"]); - values.add(["%Number of channels = 4"]); - values.add(["%Sample Rate = 200 Hz"]); - values.add(["%Board = OpenBCI_GUI\$BoardGanglionBLE"]); - values.add([ - "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)" - ]); - - return values; - } - void addData(event) async { int packetID = event[0]; From d9a554a4c950d3af72831a6e5154a518efd7119c Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Thu, 1 Oct 2020 17:34:07 -0400 Subject: [PATCH 29/56] Added error handling for failed connections --- .../bluetooth/bluetooth_selector_cubit.dart | 23 ++++++-- .../infrastructure/bluetooth_repository.dart | 58 ++++++++++++++----- 2 files changed, 60 insertions(+), 21 deletions(-) diff --git a/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart b/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart index 2ad82aae..6e18c0be 100644 --- a/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart +++ b/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:bloc/bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_blue/flutter_blue.dart'; @@ -6,6 +8,7 @@ import 'bluetooth_selector_state.dart'; class BluetoothSelectorCubit extends Cubit { final IBluetoothRepository _bluetoothRepository; + bool subscriptionInitialized = false; BluetoothSelectorCubit(this._bluetoothRepository) : super(BluetoothInitial()) { @@ -14,10 +17,15 @@ class BluetoothSelectorCubit extends Cubit { void startSearching() { _bluetoothRepository.initializeBluetooth(); - _bluetoothRepository - .watch() - .listen((devices) => emit(BluetoothSearchInProgress(devices))) - .onError((e) => emit(BluetoothSearchFailure(e))); + + if (!subscriptionInitialized) { + subscriptionInitialized = true; + _bluetoothRepository + .watch() + .asBroadcastStream() + .listen((devices) => emit(BluetoothSearchInProgress(devices))) + .onError((e) => emit(BluetoothSearchFailure(e))); + } } void connect(BluetoothDevice bluetoothDevice) async { @@ -26,6 +34,11 @@ class BluetoothSelectorCubit extends Cubit { _bluetoothRepository .connect(bluetoothDevice) .then((value) => emit(BluetoothConnectionSuccess())) - .catchError((e) => {emit(BluetoothConnectionFailure(e))}); + .catchError( + (e) => {emit(BluetoothConnectionFailure(e)), resetSearch()}); + } + + void resetSearch() { + startSearching(); } } diff --git a/mobile/lib/src/infrastructure/bluetooth_repository.dart b/mobile/lib/src/infrastructure/bluetooth_repository.dart index 988fa97c..546884d0 100644 --- a/mobile/lib/src/infrastructure/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/bluetooth_repository.dart @@ -14,23 +14,31 @@ class BluetoothRepository implements IBluetoothRepository { BluetoothCharacteristic sendCharacteristic; BluetoothCharacteristic receiveCharacteristic; + FlutterBlue flutterBlue; + StreamSubscription> flutterSubscription; static List bluetoothPersistency = []; final streamController = StreamController>(); void initializeBluetooth() { - FlutterBlue flutterBlue = FlutterBlue.instance; - flutterBlue.connectedDevices - .asStream() - .listen((List devices) { - for (BluetoothDevice device in devices) { - addDevice(device); - } - }); - flutterBlue.scanResults.listen((List results) { - for (ScanResult result in results) { - addDevice(result.device); - } - }); + if (flutterSubscription == null) { + flutterBlue = FlutterBlue.instance; + flutterBlue.connectedDevices + .asStream() + .asBroadcastStream() + .listen((List devices) { + for (BluetoothDevice device in devices) { + addDevice(device); + } + }); + flutterSubscription = + flutterBlue.scanResults.listen((List results) { + for (ScanResult result in results) { + addDevice(result.device); + } + }); + } else { + flutterSubscription.resume(); + } flutterBlue.startScan(); } @@ -43,13 +51,26 @@ class BluetoothRepository implements IBluetoothRepository { } Future connect(BluetoothDevice bluetoothDevice) async { - await bluetoothDevice.connect().then((value) => - {selectedDevice = bluetoothDevice, findRelevantCharacteristics()}); + bluetoothPersistency.clear(); + flutterSubscription.pause(); + flutterBlue.stopScan(); + + selectedDevice = bluetoothDevice; + await bluetoothDevice + .connect() + .then((value) => findRelevantCharacteristics()) + .timeout(Duration(seconds: 4), + onTimeout: () => + {disconnect(), throw Exception("Connection Timed out")}); + return; } void disconnect() async { - await selectedDevice.disconnect(); + if (selectedDevice != null) { + await selectedDevice.disconnect(); + selectedDevice = null; + } } void findRelevantCharacteristics() { @@ -69,6 +90,11 @@ class BluetoothRepository implements IBluetoothRepository { } } }); + + if (receiveCharacteristic == null) + throw Exception('Device is missing receive Characteristic'); + if (sendCharacteristic == null) + throw Exception('Device is missing send Characteristic'); } Future>> startDataStream() async { From 226d8e63336c131955183e4cfeee95e3c66e0c31 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Thu, 1 Oct 2020 18:59:35 -0400 Subject: [PATCH 30/56] Handling already connected error and cleaned slightly findCharacteristics --- .../bluetooth/bluetooth_selector_cubit.dart | 3 +- .../infrastructure/bluetooth_repository.dart | 57 ++++++++++--------- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart b/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart index 6e18c0be..e79455ac 100644 --- a/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart +++ b/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart @@ -17,7 +17,7 @@ class BluetoothSelectorCubit extends Cubit { void startSearching() { _bluetoothRepository.initializeBluetooth(); - + if (!subscriptionInitialized) { subscriptionInitialized = true; _bluetoothRepository @@ -39,6 +39,7 @@ class BluetoothSelectorCubit extends Cubit { } void resetSearch() { + _bluetoothRepository.disconnect(); startSearching(); } } diff --git a/mobile/lib/src/infrastructure/bluetooth_repository.dart b/mobile/lib/src/infrastructure/bluetooth_repository.dart index 546884d0..1543272f 100644 --- a/mobile/lib/src/infrastructure/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/bluetooth_repository.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/services.dart'; import 'package:flutter_blue/flutter_blue.dart'; import 'package:polydodo/src/domain/bluetooth/i_bluetooth_repository.dart'; @@ -51,17 +52,28 @@ class BluetoothRepository implements IBluetoothRepository { } Future connect(BluetoothDevice bluetoothDevice) async { + selectedDevice = bluetoothDevice; + bluetoothPersistency.clear(); flutterSubscription.pause(); flutterBlue.stopScan(); - selectedDevice = bluetoothDevice; - await bluetoothDevice - .connect() - .then((value) => findRelevantCharacteristics()) - .timeout(Duration(seconds: 4), - onTimeout: () => - {disconnect(), throw Exception("Connection Timed out")}); + try { + await bluetoothDevice + .connect() + .then((value) => findRelevantCharacteristics()) + .timeout(Duration(seconds: 6), + onTimeout: () => + {disconnect(), throw Exception("Connection Timed out")}); + } catch (e) { + print(e); + if (e is PlatformException) { + if (e.code != "already_connected") + throw Exception(e.details); + } + else + throw e; + } return; } @@ -75,26 +87,19 @@ class BluetoothRepository implements IBluetoothRepository { void findRelevantCharacteristics() { selectedDevice.discoverServices().then((services) => { - for (BluetoothService service in services) - { - if (service.toString().contains(BLE_SERVICE)) - { - for (BluetoothCharacteristic characteristic - in service.characteristics) - { - if (characteristic.toString().contains(BLE_RECEIVE)) - {receiveCharacteristic = characteristic} - else if (characteristic.toString().contains(BLE_SEND)) - {sendCharacteristic = characteristic} - } - } - } + 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') }); - - if (receiveCharacteristic == null) - throw Exception('Device is missing receive Characteristic'); - if (sendCharacteristic == null) - throw Exception('Device is missing send Characteristic'); } Future>> startDataStream() async { From b4192adf905575d97b9271018f227bceac6fc579 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Sat, 3 Oct 2020 13:59:55 -0400 Subject: [PATCH 31/56] Switched to generic acquisiton device in application layer --- .../bluetooth/bluetooth_selector_cubit.dart | 45 ------------------ .../bluetooth/bluetooth_selector_state.dart | 27 ----------- .../device/device_selector_cubit.dart | 43 +++++++++++++++++ .../device/device_selector_state.dart | 27 +++++++++++ .../src/application/eeg_data/data_cubit.dart | 10 ++-- .../acquisition_device.dart | 14 ++++++ .../i_acquisition_device_repository.dart | 13 ++++++ .../bluetooth/i_bluetooth_repository.dart | 13 ------ .../infrastructure/bluetooth_repository.dart | 46 +++++++++++-------- .../src/infrastructure/serial_repository.dart | 0 mobile/lib/src/locator.dart | 14 +++--- .../bluetoothSelector_route.dart | 16 +++---- 12 files changed, 143 insertions(+), 125 deletions(-) delete mode 100644 mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart delete mode 100644 mobile/lib/src/application/bluetooth/bluetooth_selector_state.dart create mode 100644 mobile/lib/src/application/device/device_selector_cubit.dart create mode 100644 mobile/lib/src/application/device/device_selector_state.dart create mode 100644 mobile/lib/src/domain/acquisition_device/acquisition_device.dart create mode 100644 mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart delete mode 100644 mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart create mode 100644 mobile/lib/src/infrastructure/serial_repository.dart diff --git a/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart b/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart deleted file mode 100644 index e79455ac..00000000 --- a/mobile/lib/src/application/bluetooth/bluetooth_selector_cubit.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'dart:async'; - -import 'package:bloc/bloc.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_blue/flutter_blue.dart'; -import 'package:polydodo/src/domain/bluetooth/i_bluetooth_repository.dart'; -import 'bluetooth_selector_state.dart'; - -class BluetoothSelectorCubit extends Cubit { - final IBluetoothRepository _bluetoothRepository; - bool subscriptionInitialized = false; - - BluetoothSelectorCubit(this._bluetoothRepository) - : super(BluetoothInitial()) { - startSearching(); - } - - void startSearching() { - _bluetoothRepository.initializeBluetooth(); - - if (!subscriptionInitialized) { - subscriptionInitialized = true; - _bluetoothRepository - .watch() - .asBroadcastStream() - .listen((devices) => emit(BluetoothSearchInProgress(devices))) - .onError((e) => emit(BluetoothSearchFailure(e))); - } - } - - void connect(BluetoothDevice bluetoothDevice) async { - emit(BluetoothConnectionInProgress()); - - _bluetoothRepository - .connect(bluetoothDevice) - .then((value) => emit(BluetoothConnectionSuccess())) - .catchError( - (e) => {emit(BluetoothConnectionFailure(e)), resetSearch()}); - } - - void resetSearch() { - _bluetoothRepository.disconnect(); - startSearching(); - } -} diff --git a/mobile/lib/src/application/bluetooth/bluetooth_selector_state.dart b/mobile/lib/src/application/bluetooth/bluetooth_selector_state.dart deleted file mode 100644 index 349b83b0..00000000 --- a/mobile/lib/src/application/bluetooth/bluetooth_selector_state.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter_blue/flutter_blue.dart'; - -abstract class BluetoothStates {} - -class BluetoothInitial extends BluetoothStates {} - -class BluetoothSearchInProgress extends BluetoothStates { - final List devices; - - BluetoothSearchInProgress(this.devices); -} - -class BluetoothSearchFailure extends BluetoothStates { - final Exception cause; - - BluetoothSearchFailure(this.cause); -} - -class BluetoothConnectionInProgress extends BluetoothStates {} - -class BluetoothConnectionSuccess extends BluetoothStates {} - -class BluetoothConnectionFailure extends BluetoothStates { - final Exception cause; - - BluetoothConnectionFailure(this.cause); -} diff --git a/mobile/lib/src/application/device/device_selector_cubit.dart b/mobile/lib/src/application/device/device_selector_cubit.dart new file mode 100644 index 00000000..6263fcd2 --- /dev/null +++ b/mobile/lib/src/application/device/device_selector_cubit.dart @@ -0,0 +1,43 @@ +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 { + final IAcquisitionDeviceRepository _deviceRepository; + bool subscriptionInitialized = false; + + DeviceSelectorCubit(this._deviceRepository) + : super(DeviceInitial()) { + startSearching(); + } + + void startSearching() { + _deviceRepository.initializeBluetooth(); + + if (!subscriptionInitialized) { + subscriptionInitialized = true; + _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) => emit(DeviceConnectionSuccess())) + .catchError( + (e) => {emit(DeviceConnectionFailure(e)), resetSearch()}); + } + + void resetSearch() { + _deviceRepository.disconnect(); + startSearching(); + } +} diff --git a/mobile/lib/src/application/device/device_selector_state.dart b/mobile/lib/src/application/device/device_selector_state.dart new file mode 100644 index 00000000..76677bb6 --- /dev/null +++ b/mobile/lib/src/application/device/device_selector_state.dart @@ -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 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); +} diff --git a/mobile/lib/src/application/eeg_data/data_cubit.dart b/mobile/lib/src/application/eeg_data/data_cubit.dart index b0aad056..5f06fa8f 100644 --- a/mobile/lib/src/application/eeg_data/data_cubit.dart +++ b/mobile/lib/src/application/eeg_data/data_cubit.dart @@ -1,4 +1,4 @@ -import 'package:polydodo/src/domain/bluetooth/i_bluetooth_repository.dart'; +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'; @@ -6,22 +6,22 @@ import 'package:bloc/bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class DataCubit extends Cubit { - final IBluetoothRepository _bluetoothRepository; + final IAcquisitionDeviceRepository _deviceRepository; final IEEGDataRepository _eegDataRepository; - DataCubit(this._bluetoothRepository, this._eegDataRepository) + DataCubit(this._deviceRepository, this._eegDataRepository) : super(DataStateInitial()); void startStreaming() { emit(DataStateRecording()); - _bluetoothRepository + _deviceRepository .startDataStream() .then((stream) => _eegDataRepository.createRecordingFromStream(stream)); } void stopStreaming() { emit(DataStateInitial()); - _bluetoothRepository.stopDataStream(); + _deviceRepository.stopDataStream(); _eegDataRepository.stopRecordingFromStream(); } } diff --git a/mobile/lib/src/domain/acquisition_device/acquisition_device.dart b/mobile/lib/src/domain/acquisition_device/acquisition_device.dart new file mode 100644 index 00000000..251fd4db --- /dev/null +++ b/mobile/lib/src/domain/acquisition_device/acquisition_device.dart @@ -0,0 +1,14 @@ +import 'package:equatable/equatable.dart'; + +import '../unique_id.dart'; + +class AcquisitionDevice extends Equatable { + final UniqueId id; + final String name; + final interface; + + AcquisitionDevice(this.id, this.name, this.interface); + + @override + List get props => [id]; +} diff --git a/mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart b/mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart new file mode 100644 index 00000000..84b3f2ac --- /dev/null +++ b/mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart @@ -0,0 +1,13 @@ +import 'acquisition_device.dart'; + +abstract class IAcquisitionDeviceRepository { + void initializeBluetooth(); + + Future connect(AcquisitionDevice dvice); + void disconnect(); + + Future>> startDataStream(); + void stopDataStream(); + + Stream> watch(); +} diff --git a/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart b/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart deleted file mode 100644 index 7e009267..00000000 --- a/mobile/lib/src/domain/bluetooth/i_bluetooth_repository.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:flutter_blue/flutter_blue.dart'; - -abstract class IBluetoothRepository { - void initializeBluetooth(); - - Future connect(BluetoothDevice bluetoothDevice); - void disconnect(); - - Future>> startDataStream(); - void stopDataStream(); - - Stream> watch(); -} diff --git a/mobile/lib/src/infrastructure/bluetooth_repository.dart b/mobile/lib/src/infrastructure/bluetooth_repository.dart index 1543272f..e479b0f9 100644 --- a/mobile/lib/src/infrastructure/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/bluetooth_repository.dart @@ -2,9 +2,11 @@ import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter_blue/flutter_blue.dart'; -import 'package:polydodo/src/domain/bluetooth/i_bluetooth_repository.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 IBluetoothRepository { +class BluetoothRepository implements IAcquisitionDeviceRepository { static const String BLE_SERVICE = "fe84"; static const String BLE_RECEIVE = "2d30c082"; static const String BLE_SEND = "2d30c083"; @@ -17,8 +19,8 @@ class BluetoothRepository implements IBluetoothRepository { FlutterBlue flutterBlue; StreamSubscription> flutterSubscription; - static List bluetoothPersistency = []; - final streamController = StreamController>(); + static List bluetoothPersistency = []; + final streamController = StreamController>(); void initializeBluetooth() { if (flutterSubscription == null) { @@ -43,7 +45,11 @@ class BluetoothRepository implements IBluetoothRepository { flutterBlue.startScan(); } - void addDevice(BluetoothDevice device) { + void addDevice(BluetoothDevice bluetoothDevice) { + AcquisitionDevice device = AcquisitionDevice( + UniqueId.from(bluetoothDevice.id.toString()), + bluetoothDevice.name, + bluetoothDevice); final idx = bluetoothPersistency.indexOf(device); idx == -1 ? bluetoothPersistency.add(device) @@ -51,15 +57,15 @@ class BluetoothRepository implements IBluetoothRepository { streamController.add(bluetoothPersistency); } - Future connect(BluetoothDevice bluetoothDevice) async { - selectedDevice = bluetoothDevice; + Future connect(AcquisitionDevice device) async { + selectedDevice = device.interface; bluetoothPersistency.clear(); flutterSubscription.pause(); flutterBlue.stopScan(); try { - await bluetoothDevice + await device.interface .connect() .then((value) => findRelevantCharacteristics()) .timeout(Duration(seconds: 6), @@ -68,10 +74,8 @@ class BluetoothRepository implements IBluetoothRepository { } catch (e) { print(e); if (e is PlatformException) { - if (e.code != "already_connected") - throw Exception(e.details); - } - else + if (e.code != "already_connected") throw Exception(e.details); + } else throw e; } @@ -88,13 +92,15 @@ class BluetoothRepository implements IBluetoothRepository { 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} - }, + (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) @@ -115,5 +121,5 @@ class BluetoothRepository implements IBluetoothRepository { } @override - Stream> watch() => streamController.stream; + Stream> watch() => streamController.stream; } diff --git a/mobile/lib/src/infrastructure/serial_repository.dart b/mobile/lib/src/infrastructure/serial_repository.dart new file mode 100644 index 00000000..e69de29b diff --git a/mobile/lib/src/locator.dart b/mobile/lib/src/locator.dart index 691fc184..021bdb1f 100644 --- a/mobile/lib/src/locator.dart +++ b/mobile/lib/src/locator.dart @@ -1,11 +1,11 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; +import 'package:polydodo/src/application/device/device_selector_cubit.dart'; import 'package:polydodo/src/application/eeg_data/data_cubit.dart'; +import 'package:polydodo/src/domain/acquisition_device/i_acquisition_device_repository.dart'; import 'package:polydodo/src/infrastructure/mock_wallet_repository.dart'; -import 'application/bluetooth/bluetooth_selector_cubit.dart'; import 'application/wallets/wallets_cubit.dart'; -import 'domain/bluetooth/i_bluetooth_repository.dart'; import 'domain/eeg_data/i_eeg_data_repository.dart'; import 'domain/wallet/i_wallet_repository.dart'; import 'infrastructure/bluetooth_repository.dart'; @@ -17,7 +17,7 @@ final _serviceLocator = GetIt.asNewInstance(); void registerServices() { _serviceLocator.registerSingleton(MockWalletRepository()); _serviceLocator - .registerSingleton(BluetoothRepository()); + .registerSingleton(BluetoothRepository()); _serviceLocator.registerSingleton(EEGDataRepository()); } @@ -28,14 +28,14 @@ List createBlocProviders() => [ _serviceLocator.get(), ), ), - BlocProvider( - create: (context) => BluetoothSelectorCubit( - _serviceLocator.get(), + BlocProvider( + create: (context) => DeviceSelectorCubit( + _serviceLocator.get(), ), ), BlocProvider( create: (context) => DataCubit( - _serviceLocator.get(), + _serviceLocator.get(), _serviceLocator.get(), ), ), diff --git a/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart b/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart index c1a4d549..30894f09 100644 --- a/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart +++ b/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:polydodo/src/application/bluetooth/bluetooth_selector_cubit.dart'; -import 'package:polydodo/src/application/bluetooth/bluetooth_selector_state.dart'; +import 'package:polydodo/src/application/device/device_selector_cubit.dart'; +import 'package:polydodo/src/application/device/device_selector_state.dart'; import 'package:polydodo/src/presentation/recording/recording_route.dart'; class BluetoothSelectorRoute extends StatelessWidget { @@ -13,20 +13,20 @@ class BluetoothSelectorRoute extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Polydodo')), - body: BlocConsumer( + body: BlocConsumer( listener: (context, state) { print(state.runtimeType); - if (state is BluetoothSearchFailure) { + if (state is DeviceSearchFailure) { Scaffold.of(context).showSnackBar(SnackBar( content: Text( 'Unable to search for bluetooth devices because ${state.cause}'), )); - } else if (state is BluetoothConnectionFailure) { + } else if (state is DeviceConnectionFailure) { Scaffold.of(context).showSnackBar(SnackBar( content: Text('Unable to connect to device because ${state.cause}'), )); - } else if (state is BluetoothConnectionSuccess) { + } else if (state is DeviceConnectionSuccess) { Navigator.push( context, MaterialPageRoute( @@ -35,14 +35,14 @@ class BluetoothSelectorRoute extends StatelessWidget { } }, builder: (context, state) { - if (state is BluetoothSearchInProgress) + if (state is DeviceSearchInProgress) return ListView.builder( itemCount: state.devices.length, itemBuilder: (context, index) { return Card( child: ListTile( onTap: () => - BlocProvider.of(context) + BlocProvider.of(context) .connect(state.devices[index]), title: Text(state.devices[index].name), subtitle: Text(state.devices[index].id.toString())), From b95d5b294e02fea8046288e26c2c114aff0c0191 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Sat, 3 Oct 2020 14:03:11 -0400 Subject: [PATCH 32/56] Reverted merge with master --- mobile/lib/main.dart | 22 +------------------- mobile/pubspec.lock | 49 +++++++++++++++++++------------------------- 2 files changed, 22 insertions(+), 49 deletions(-) diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 11699fb8..e048b141 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -1,26 +1,6 @@ -import 'package:polydodo/protos/helloworld.pbgrpc.dart'; -import 'package:grpc/grpc.dart'; import 'package:flutter/material.dart'; - -import 'protos/helloworld.pb.dart'; import 'src/app.dart'; -void main() async { - // GRPC TEST - final channel = ClientChannel( - const String.fromEnvironment('HOST'), - port: 9090, - options: const ChannelOptions(credentials: ChannelCredentials.insecure()), - ); - final stub = GreeterClient(channel); - - try { - final response = await stub.sayHello(HelloRequest()..name = 'world'); - print('Greeter client received: ${response.message}'); - } catch (e) { - print('Caught error: $e'); - } - await channel.shutdown(); - // END OF TEST +void main() { runApp(App()); } diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 79614086..786c9358 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -28,7 +28,7 @@ packages: name: battery url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.0.6" battery_platform_interface: dependency: transitive description: @@ -199,16 +199,7 @@ packages: name: hive url: "https://pub.dartlang.org" source: hosted - version: "1.4.4" -<<<<<<< HEAD - intl: - dependency: transitive - description: - name: intl - url: "https://pub.dartlang.org" - source: hosted - version: "0.16.1" -======= + version: "1.4.4+1" http: dependency: transitive description: @@ -222,7 +213,7 @@ packages: name: http2 url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" http_parser: dependency: transitive description: @@ -230,7 +221,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.4" ->>>>>>> master + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.1" matcher: dependency: transitive description: @@ -265,7 +262,6 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted -<<<<<<< HEAD version: "1.7.0" path_provider: dependency: "direct main" @@ -273,7 +269,7 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.6.16" + version: "1.6.18" path_provider_linux: dependency: transitive description: @@ -301,31 +297,28 @@ packages: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.3" - platform: + version: "0.0.4+1" + pedantic: dependency: transitive description: - name: platform + name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" -======= - version: "1.8.0-nullsafety" - pedantic: + version: "1.9.0" + platform: dependency: transitive description: - name: pedantic + name: platform url: "https://pub.dartlang.org" source: hosted - version: "1.9.2" ->>>>>>> master + version: "2.2.1" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" + version: "1.0.3" process: dependency: transitive description: @@ -360,7 +353,7 @@ packages: name: share url: "https://pub.dartlang.org" source: hosted - version: "0.6.5+1" + version: "0.6.5+2" sky_engine: dependency: transitive description: flutter @@ -442,7 +435,7 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "1.7.2+1" + version: "1.7.3" xdg_directories: dependency: transitive description: From 351c57b0d56d6919eb23d99ed8be809ed5f25f7d Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Sat, 3 Oct 2020 16:13:59 -0400 Subject: [PATCH 33/56] Added handling for packet id 0 and fixed data values --- .../infrastructure/eeg_data_repository.dart | 76 ++++++++++++++++--- 1 file changed, 64 insertions(+), 12 deletions(-) diff --git a/mobile/lib/src/infrastructure/eeg_data_repository.dart b/mobile/lib/src/infrastructure/eeg_data_repository.dart index 62f8bf75..0eabb1bd 100644 --- a/mobile/lib/src/infrastructure/eeg_data_repository.dart +++ b/mobile/lib/src/infrastructure/eeg_data_repository.dart @@ -8,14 +8,24 @@ import 'package:polydodo/src/domain/unique_id.dart'; import 'constants.dart'; class EEGDataRepository implements IEEGDataRepository { + bool initializeStream = false; EEGData recordingData; + List lastSampleData = [0, 0, 0, 0, 0]; + int packetLoss; + int totalPackets; + int nextPacketId; void createRecordingFromStream(Stream> stream) { recordingData = new EEGData(UniqueId.from(DateTime.now().toString()), new List()); - stream.listen((value) { - addData(value); - }); + packetLoss = 0; + totalPackets = 0; + if (!initializeStream) { + initializeStream = true; + stream.listen((value) { + addData(value); + }); + } } void stopRecordingFromStream() async { @@ -24,8 +34,10 @@ class EEGDataRepository implements IEEGDataRepository { final pathOfTheFileToWrite = directory.path + '/' + recordingData.fileName + ".txt"; File file = File(pathOfTheFileToWrite); - recordingData.values.insertAll(0, OPEN_BCI_HEADER); - String csv = const ListToCsvConverter().convert(recordingData.values); + List fileContent = new List(); + fileContent.addAll(OPEN_BCI_HEADER); + fileContent.addAll(recordingData.values); + String csv = const ListToCsvConverter().convert(fileContent); await file.writeAsString(csv); } @@ -33,20 +45,52 @@ class EEGDataRepository implements IEEGDataRepository { void importData() {} void exportData() {} - void addData(event) async { + void addData(List event) async { + //print("Lost packets: " + packetLoss.toString()); + //print("Total packets: " + totalPackets.toString()); + //print("Lost percentage: " + + // (packetLoss.toDouble() / totalPackets.toDouble()).toString()); + if (event.length != 20) { + print("Invalid Event"); + return; + } + totalPackets++; int packetID = event[0]; // todo: handle packet id 0 (raw data) and possibly impedence for signal validation - if (packetID >= 101 && packetID <= 200) { - List data = formatData(event); - data = handleNegativesAndConvertToVolts(data); + if (packetID == 0) { + nextPacketId = 101; + + List data = parseRaw(event); + data = convertToMicrovolts(data, false); + + recordingData.values.add(data.sublist(0, 15)); + } else if (packetID >= 101 && packetID <= 200) { + // print(packetID); + // print(nextPacketId); + packetLoss += packetID - nextPacketId; + nextPacketId = packetID + 1; + List data = parse19Bit(event); + data = convertToMicrovolts(data, true); recordingData.values.add(data.sublist(0, 15)); recordingData.values.add(data.sublist(15, 30)); } } - List formatData(event) { + List parseRaw(event) { + List data = getListForCSV(); + + data[0] = recordingData.sampleCounter++; + data[1] = (event[1] << 16) | (event[2] << 8) | event[3]; + data[2] = (event[4] << 16) | (event[5] << 8) | event[6]; + data[3] = (event[7] << 16) | (event[8] << 8) | event[9]; + data[4] = (event[10] << 16) | (event[11] << 8) | event[12]; + + return data; + } + + List parse19Bit(event) { // Test event, comment scale factor // event = [ 101, 0, 0, 0, 0, 8, 0, 5, 0, 0, 72, 0, 9, 240, 1, 176, 0, 48, 0, 8]; // Positive Test // Expected [[0, 2, 10, 4], [262148, 507910, 393222, 8]] @@ -85,19 +129,27 @@ class EEGDataRepository implements IEEGDataRepository { return data; } - List handleNegativesAndConvertToVolts(data) { + List convertToMicrovolts(List data, bool isDelta) { for (int i = 1; i < 5; ++i) { for (int j = 0; j < 2; ++j) { + if (j == 1 && !isDelta) break; + int offset = 15 * j; String binary = data[i + offset].toRadixString(2); - if (binary[binary.length - 1] == '1') { + // Handle negatives + if (isDelta && binary[binary.length - 1] == '1') { data[i + offset] = (~data[i + offset] & 524287 | 1) * -1; } // Convert to microvolts using the scale factor data[i + offset] = data[i + offset].toDouble() * (1200000 / (8388607.0 * 1.5 * 51.0)); + + // Convert delta + if (isDelta) data[i + offset] = lastSampleData[i] - data[i + offset]; + + lastSampleData[i] = data[i + offset]; } } From bc62be0d43b1a9a6b2f0884db4cded6c4bad51c9 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Sat, 3 Oct 2020 16:41:53 -0400 Subject: [PATCH 34/56] Returning Container in view instead of new ListView --- .../presentation/bluetooth_route/bluetoothSelector_route.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart b/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart index 30894f09..eaef0c99 100644 --- a/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart +++ b/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart @@ -49,7 +49,7 @@ class BluetoothSelectorRoute extends StatelessWidget { ); }); else - return new ListView(); + return Container(); }, ), ); From 6056901ac6afa8d7b0c5fcb04669e5a41ff2a4fa Mon Sep 17 00:00:00 2001 From: William Harvey Date: Sat, 3 Oct 2020 17:04:28 -0400 Subject: [PATCH 35/56] Remove default formatter for workspace because it prevents dartfmt to perform formating on dart files. --- polydodo.code-workspace | 1 - 1 file changed, 1 deletion(-) diff --git a/polydodo.code-workspace b/polydodo.code-workspace index 2f0d9cfd..78d8ea37 100644 --- a/polydodo.code-workspace +++ b/polydodo.code-workspace @@ -27,7 +27,6 @@ ], "settings": { "prettier.configPath": "../.prettierrc.yaml", - "editor.defaultFormatter": "esbenp.prettier-vscode", "eslint.packageManager": "yarn", "editor.codeActionsOnSave": { "source.fixAll.eslint": true From 7a463c1cd01d1e283d4c2ce95b38694b2b796354 Mon Sep 17 00:00:00 2001 From: William Harvey Date: Sat, 3 Oct 2020 17:13:56 -0400 Subject: [PATCH 36/56] Reset esbenp.prettier-vscode as default formatter, but specified dartfmt as dart formatter --- polydodo.code-workspace | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/polydodo.code-workspace b/polydodo.code-workspace index 78d8ea37..d408ad7d 100644 --- a/polydodo.code-workspace +++ b/polydodo.code-workspace @@ -27,6 +27,7 @@ ], "settings": { "prettier.configPath": "../.prettierrc.yaml", + "editor.defaultFormatter": "esbenp.prettier-vscode", "eslint.packageManager": "yarn", "editor.codeActionsOnSave": { "source.fixAll.eslint": true @@ -40,6 +41,9 @@ "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[dart]": { + "editor.defaultFormatter": "Dart-Code.dart-code" + }, "protoc": { "compile_on_save": true, "compile_all_path": "protos", From bbe2475924d326e10b259ca12ab943670c1476ad Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Sun, 4 Oct 2020 15:33:54 -0400 Subject: [PATCH 37/56] Changes based on last pull request comments (injecting flutter blue into repository, and small formatting changes) --- mobile/lib/src/app.dart | 1 - .../device/device_selector_cubit.dart | 28 ++++++++++--------- .../i_acquisition_device_repository.dart | 2 +- .../infrastructure/bluetooth_repository.dart | 15 +++++----- mobile/lib/src/locator.dart | 5 ++-- 5 files changed, 27 insertions(+), 24 deletions(-) diff --git a/mobile/lib/src/app.dart b/mobile/lib/src/app.dart index 2f36cc41..f8056bce 100644 --- a/mobile/lib/src/app.dart +++ b/mobile/lib/src/app.dart @@ -19,7 +19,6 @@ class App extends StatelessWidget { child: MaterialApp( title: 'PolyDodo', theme: theme, - home: BluetoothSelectorRoute(), initialRoute: BluetoothSelectorRoute.name, routes: { WalletsRoute.name: (context) => WalletsRoute(), diff --git a/mobile/lib/src/application/device/device_selector_cubit.dart b/mobile/lib/src/application/device/device_selector_cubit.dart index 6263fcd2..ffbe105d 100644 --- a/mobile/lib/src/application/device/device_selector_cubit.dart +++ b/mobile/lib/src/application/device/device_selector_cubit.dart @@ -1,3 +1,5 @@ +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'; @@ -6,34 +8,34 @@ import 'device_selector_state.dart'; class DeviceSelectorCubit extends Cubit { final IAcquisitionDeviceRepository _deviceRepository; - bool subscriptionInitialized = false; - DeviceSelectorCubit(this._deviceRepository) - : super(DeviceInitial()) { + StreamSubscription> _acquisitionDeviceStream; + + DeviceSelectorCubit(this._deviceRepository) : super(DeviceInitial()) { startSearching(); } void startSearching() { _deviceRepository.initializeBluetooth(); - if (!subscriptionInitialized) { - subscriptionInitialized = true; - _deviceRepository + if (_acquisitionDeviceStream == null) { + _acquisitionDeviceStream = _deviceRepository .watch() .asBroadcastStream() - .listen((devices) => emit(DeviceSearchInProgress(devices))) - .onError((e) => emit(DeviceSearchFailure(e))); + .listen((devices) => emit(DeviceSearchInProgress(devices)), + onError: (e) => emit(DeviceSearchFailure(e))); } } void connect(AcquisitionDevice device) async { emit(DeviceConnectionInProgress()); - _deviceRepository - .connect(device) - .then((value) => emit(DeviceConnectionSuccess())) - .catchError( - (e) => {emit(DeviceConnectionFailure(e)), resetSearch()}); + _deviceRepository.connect(device).then( + (value) => { + _acquisitionDeviceStream.cancel(), + emit(DeviceConnectionSuccess()) + }, + onError: (e) => {emit(DeviceConnectionFailure(e)), resetSearch()}); } void resetSearch() { diff --git a/mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart b/mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart index 84b3f2ac..964eec0b 100644 --- a/mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart +++ b/mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart @@ -3,7 +3,7 @@ import 'acquisition_device.dart'; abstract class IAcquisitionDeviceRepository { void initializeBluetooth(); - Future connect(AcquisitionDevice dvice); + Future connect(AcquisitionDevice device); void disconnect(); Future>> startDataStream(); diff --git a/mobile/lib/src/infrastructure/bluetooth_repository.dart b/mobile/lib/src/infrastructure/bluetooth_repository.dart index e479b0f9..1e787701 100644 --- a/mobile/lib/src/infrastructure/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/bluetooth_repository.dart @@ -18,13 +18,14 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { BluetoothCharacteristic receiveCharacteristic; FlutterBlue flutterBlue; - StreamSubscription> flutterSubscription; - static List bluetoothPersistency = []; + StreamSubscription> _bluetoothScanSubscription; + List bluetoothPersistency = []; final streamController = StreamController>(); + BluetoothRepository(this.flutterBlue); + void initializeBluetooth() { - if (flutterSubscription == null) { - flutterBlue = FlutterBlue.instance; + if (_bluetoothScanSubscription == null) { flutterBlue.connectedDevices .asStream() .asBroadcastStream() @@ -33,14 +34,14 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { addDevice(device); } }); - flutterSubscription = + _bluetoothScanSubscription = flutterBlue.scanResults.listen((List results) { for (ScanResult result in results) { addDevice(result.device); } }); } else { - flutterSubscription.resume(); + _bluetoothScanSubscription.resume(); } flutterBlue.startScan(); } @@ -61,7 +62,7 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { selectedDevice = device.interface; bluetoothPersistency.clear(); - flutterSubscription.pause(); + _bluetoothScanSubscription.pause(); flutterBlue.stopScan(); try { diff --git a/mobile/lib/src/locator.dart b/mobile/lib/src/locator.dart index 021bdb1f..e1310718 100644 --- a/mobile/lib/src/locator.dart +++ b/mobile/lib/src/locator.dart @@ -1,4 +1,5 @@ import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_blue/flutter_blue.dart'; import 'package:get_it/get_it.dart'; import 'package:polydodo/src/application/device/device_selector_cubit.dart'; import 'package:polydodo/src/application/eeg_data/data_cubit.dart'; @@ -16,8 +17,8 @@ final _serviceLocator = GetIt.asNewInstance(); void registerServices() { _serviceLocator.registerSingleton(MockWalletRepository()); - _serviceLocator - .registerSingleton(BluetoothRepository()); + _serviceLocator.registerSingleton( + BluetoothRepository(FlutterBlue.instance)); _serviceLocator.registerSingleton(EEGDataRepository()); } From fdda64df14671880d146625536cd8cd5ef8ec1f8 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Sun, 4 Oct 2020 16:56:03 -0400 Subject: [PATCH 38/56] Changed Acquisition initialization method to more generic name instead of bluetooth --- mobile/lib/src/application/device/device_selector_cubit.dart | 2 +- .../acquisition_device/i_acquisition_device_repository.dart | 2 +- mobile/lib/src/infrastructure/bluetooth_repository.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mobile/lib/src/application/device/device_selector_cubit.dart b/mobile/lib/src/application/device/device_selector_cubit.dart index ffbe105d..6d876880 100644 --- a/mobile/lib/src/application/device/device_selector_cubit.dart +++ b/mobile/lib/src/application/device/device_selector_cubit.dart @@ -16,7 +16,7 @@ class DeviceSelectorCubit extends Cubit { } void startSearching() { - _deviceRepository.initializeBluetooth(); + _deviceRepository.initializeRepository(); if (_acquisitionDeviceStream == null) { _acquisitionDeviceStream = _deviceRepository diff --git a/mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart b/mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart index 964eec0b..04067b8e 100644 --- a/mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart +++ b/mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart @@ -1,7 +1,7 @@ import 'acquisition_device.dart'; abstract class IAcquisitionDeviceRepository { - void initializeBluetooth(); + void initializeRepository(); Future connect(AcquisitionDevice device); void disconnect(); diff --git a/mobile/lib/src/infrastructure/bluetooth_repository.dart b/mobile/lib/src/infrastructure/bluetooth_repository.dart index 1e787701..24f32212 100644 --- a/mobile/lib/src/infrastructure/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/bluetooth_repository.dart @@ -24,7 +24,7 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { BluetoothRepository(this.flutterBlue); - void initializeBluetooth() { + void initializeRepository() { if (_bluetoothScanSubscription == null) { flutterBlue.connectedDevices .asStream() From 6bd202867d1bc64621ce388833b1aefb710b0229 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Sun, 4 Oct 2020 16:56:46 -0400 Subject: [PATCH 39/56] Initial connection with usb serial achieved, missing data formatting --- .../src/infrastructure/serial_repository.dart | 70 +++++++++++++++++++ mobile/lib/src/locator.dart | 5 +- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/mobile/lib/src/infrastructure/serial_repository.dart b/mobile/lib/src/infrastructure/serial_repository.dart index e69de29b..fc0b240b 100644 --- a/mobile/lib/src/infrastructure/serial_repository.dart +++ b/mobile/lib/src/infrastructure/serial_repository.dart @@ -0,0 +1,70 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:polydodo/src/domain/unique_id.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:usb_serial/usb_serial.dart'; + +class SerialRepository implements IAcquisitionDeviceRepository { + static const startStreamChar = 'b'; + static const stopStreamChar = 's'; + + UsbDevice _selectedDevice; + UsbPort _usbPort; + List serialPersistency = []; + final streamController = StreamController>(); + + void initializeRepository() { + serialPersistency.clear(); + UsbSerial.listDevices().then((devices) => addDevice(devices)); + } + + void addDevice(List serialDevices) { + for (UsbDevice serialDevice in serialDevices) { + AcquisitionDevice device = AcquisitionDevice( + UniqueId.from(serialDevice.deviceId.toString()), + serialDevice.productName, + serialDevice); + + serialPersistency.add(device); + } + streamController.add(serialPersistency); + } + + Future connect(AcquisitionDevice device) async { + _selectedDevice = device.interface; + _usbPort = await _selectedDevice.create(); + bool openResult = await _usbPort.open(); + + if (!openResult) { + print("Could not open port"); + } + + await _usbPort.setDTR(true); + await _usbPort.setRTS(true); + + _usbPort.setPortParameters( + 115200, UsbPort.DATABITS_8, UsbPort.STOPBITS_1, UsbPort.PARITY_NONE); + } + + void disconnect() async { + if (_selectedDevice != null && _usbPort != null) { + await _usbPort.close(); + _selectedDevice = null; + _usbPort = null; + } + } + + Future>> startDataStream() async { + _usbPort.write(Uint8List.fromList(startStreamChar.codeUnits)); + + return _usbPort.inputStream; + } + + void stopDataStream() { + _usbPort.write(Uint8List.fromList(stopStreamChar.codeUnits)); + } + + Stream> watch() => streamController.stream; +} diff --git a/mobile/lib/src/locator.dart b/mobile/lib/src/locator.dart index e1310718..2ad235e4 100644 --- a/mobile/lib/src/locator.dart +++ b/mobile/lib/src/locator.dart @@ -5,6 +5,7 @@ import 'package:polydodo/src/application/device/device_selector_cubit.dart'; import 'package:polydodo/src/application/eeg_data/data_cubit.dart'; import 'package:polydodo/src/domain/acquisition_device/i_acquisition_device_repository.dart'; import 'package:polydodo/src/infrastructure/mock_wallet_repository.dart'; +import 'package:polydodo/src/infrastructure/serial_repository.dart'; import 'application/wallets/wallets_cubit.dart'; import 'domain/eeg_data/i_eeg_data_repository.dart'; @@ -17,8 +18,8 @@ final _serviceLocator = GetIt.asNewInstance(); void registerServices() { _serviceLocator.registerSingleton(MockWalletRepository()); - _serviceLocator.registerSingleton( - BluetoothRepository(FlutterBlue.instance)); + _serviceLocator + .registerSingleton(SerialRepository()); _serviceLocator.registerSingleton(EEGDataRepository()); } From 4a4fecd204e2beb062e8d032e3cdb3fc76c68cb5 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Tue, 6 Oct 2020 13:13:57 -0400 Subject: [PATCH 40/56] Renamed serial port --- .../src/infrastructure/serial_repository.dart | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/mobile/lib/src/infrastructure/serial_repository.dart b/mobile/lib/src/infrastructure/serial_repository.dart index fc0b240b..b92e044e 100644 --- a/mobile/lib/src/infrastructure/serial_repository.dart +++ b/mobile/lib/src/infrastructure/serial_repository.dart @@ -11,7 +11,7 @@ class SerialRepository implements IAcquisitionDeviceRepository { static const stopStreamChar = 's'; UsbDevice _selectedDevice; - UsbPort _usbPort; + UsbPort _serialPort; List serialPersistency = []; final streamController = StreamController>(); @@ -34,36 +34,36 @@ class SerialRepository implements IAcquisitionDeviceRepository { Future connect(AcquisitionDevice device) async { _selectedDevice = device.interface; - _usbPort = await _selectedDevice.create(); - bool openResult = await _usbPort.open(); + _serialPort = await _selectedDevice.create(); + bool openResult = await _serialPort.open(); if (!openResult) { print("Could not open port"); } - await _usbPort.setDTR(true); - await _usbPort.setRTS(true); + await _serialPort.setDTR(true); + await _serialPort.setRTS(true); - _usbPort.setPortParameters( + _serialPort.setPortParameters( 115200, UsbPort.DATABITS_8, UsbPort.STOPBITS_1, UsbPort.PARITY_NONE); } void disconnect() async { - if (_selectedDevice != null && _usbPort != null) { - await _usbPort.close(); + if (_selectedDevice != null && _serialPort != null) { + await _serialPort.close(); _selectedDevice = null; - _usbPort = null; + _serialPort = null; } } Future>> startDataStream() async { - _usbPort.write(Uint8List.fromList(startStreamChar.codeUnits)); + _serialPort.write(Uint8List.fromList(startStreamChar.codeUnits)); - return _usbPort.inputStream; + return _serialPort.inputStream; } void stopDataStream() { - _usbPort.write(Uint8List.fromList(stopStreamChar.codeUnits)); + _serialPort.write(Uint8List.fromList(stopStreamChar.codeUnits)); } Stream> watch() => streamController.stream; From 7561a4d03b0239155d2fc8ab320065c7101c56e6 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Tue, 6 Oct 2020 14:06:47 -0400 Subject: [PATCH 41/56] Moved back flutter instance into repository initialization and created generic entity extending equatable with unique id --- .../acquisition_device.dart | 13 ++-- mobile/lib/src/domain/entity.dart | 11 +++ .../infrastructure/bluetooth_repository.dart | 69 +++++++++++-------- mobile/lib/src/locator.dart | 4 +- 4 files changed, 58 insertions(+), 39 deletions(-) create mode 100644 mobile/lib/src/domain/entity.dart diff --git a/mobile/lib/src/domain/acquisition_device/acquisition_device.dart b/mobile/lib/src/domain/acquisition_device/acquisition_device.dart index 251fd4db..f15efc14 100644 --- a/mobile/lib/src/domain/acquisition_device/acquisition_device.dart +++ b/mobile/lib/src/domain/acquisition_device/acquisition_device.dart @@ -1,14 +1,13 @@ import 'package:equatable/equatable.dart'; +import 'package:polydodo/src/domain/entity.dart'; import '../unique_id.dart'; -class AcquisitionDevice extends Equatable { - final UniqueId id; +class AcquisitionDevice extends Entity { final String name; - final interface; - AcquisitionDevice(this.id, this.name, this.interface); - - @override - List get props => [id]; + AcquisitionDevice( + id, + this.name, + ) : super(id); } diff --git a/mobile/lib/src/domain/entity.dart b/mobile/lib/src/domain/entity.dart new file mode 100644 index 00000000..844514b4 --- /dev/null +++ b/mobile/lib/src/domain/entity.dart @@ -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 get props => [id]; +} diff --git a/mobile/lib/src/infrastructure/bluetooth_repository.dart b/mobile/lib/src/infrastructure/bluetooth_repository.dart index 24f32212..cc087ab3 100644 --- a/mobile/lib/src/infrastructure/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/bluetooth_repository.dart @@ -13,19 +13,22 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { static const startStreamChar = 'b'; static const stopStreamChar = 's'; - BluetoothDevice selectedDevice; - BluetoothCharacteristic sendCharacteristic; - BluetoothCharacteristic receiveCharacteristic; + BluetoothDevice _selectedDevice; + BluetoothCharacteristic _sendCharacteristic; + BluetoothCharacteristic _receiveCharacteristic; FlutterBlue flutterBlue; StreamSubscription> _bluetoothScanSubscription; - List bluetoothPersistency = []; + List _acquisitionDevicePersistency = []; + List _bluetoothDevices = []; final streamController = StreamController>(); - BluetoothRepository(this.flutterBlue); + BluetoothRepository(); void initializeRepository() { if (_bluetoothScanSubscription == null) { + flutterBlue = FlutterBlue.instance; + flutterBlue.connectedDevices .asStream() .asBroadcastStream() @@ -48,32 +51,38 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { void addDevice(BluetoothDevice bluetoothDevice) { AcquisitionDevice device = AcquisitionDevice( - UniqueId.from(bluetoothDevice.id.toString()), - bluetoothDevice.name, - bluetoothDevice); - final idx = bluetoothPersistency.indexOf(device); - idx == -1 - ? bluetoothPersistency.add(device) - : bluetoothPersistency[idx] = device; - streamController.add(bluetoothPersistency); + 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 connect(AcquisitionDevice device) async { - selectedDevice = device.interface; + _selectedDevice = + _bluetoothDevices[_acquisitionDevicePersistency.indexOf(device)]; - bluetoothPersistency.clear(); + _acquisitionDevicePersistency.clear(); + _bluetoothDevices.clear(); _bluetoothScanSubscription.pause(); flutterBlue.stopScan(); try { - await device.interface + await _selectedDevice .connect() .then((value) => findRelevantCharacteristics()) .timeout(Duration(seconds: 6), onTimeout: () => {disconnect(), throw Exception("Connection Timed out")}); } catch (e) { - print(e); if (e is PlatformException) { if (e.code != "already_connected") throw Exception(e.details); } else @@ -84,41 +93,41 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { } void disconnect() async { - if (selectedDevice != null) { - await selectedDevice.disconnect(); - selectedDevice = null; + if (_selectedDevice != null) { + await _selectedDevice.disconnect(); + _selectedDevice = null; } } void findRelevantCharacteristics() { - selectedDevice.discoverServices().then((services) => { + _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} + {_receiveCharacteristic = characteristic} else if (characteristic.uuid.toString().contains(BLE_SEND)) - {sendCharacteristic = characteristic} + {_sendCharacteristic = characteristic} }, - if (receiveCharacteristic == null) + if (_receiveCharacteristic == null) throw Exception('Device is missing receive Characteristic'), - if (sendCharacteristic == null) + if (_sendCharacteristic == null) throw Exception('Device is missing send Characteristic') }); } Future>> startDataStream() async { - await receiveCharacteristic.setNotifyValue(true); + await _receiveCharacteristic.setNotifyValue(true); - await sendCharacteristic.write(startStreamChar.codeUnits); - return receiveCharacteristic.value; + await _sendCharacteristic.write(startStreamChar.codeUnits); + return _receiveCharacteristic.value; } void stopDataStream() async { - await receiveCharacteristic.setNotifyValue(false); - await sendCharacteristic.write(stopStreamChar.codeUnits); + await _receiveCharacteristic.setNotifyValue(false); + await _sendCharacteristic.write(stopStreamChar.codeUnits); } @override diff --git a/mobile/lib/src/locator.dart b/mobile/lib/src/locator.dart index e1310718..560237f3 100644 --- a/mobile/lib/src/locator.dart +++ b/mobile/lib/src/locator.dart @@ -17,8 +17,8 @@ final _serviceLocator = GetIt.asNewInstance(); void registerServices() { _serviceLocator.registerSingleton(MockWalletRepository()); - _serviceLocator.registerSingleton( - BluetoothRepository(FlutterBlue.instance)); + _serviceLocator + .registerSingleton(BluetoothRepository()); _serviceLocator.registerSingleton(EEGDataRepository()); } From f3719b67d5cf13f1dcc35ac89f70e72eec39423f Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Thu, 8 Oct 2020 10:57:31 -0400 Subject: [PATCH 42/56] Switched to AcquisitionDevice interface usage --- .../android/app/src/main/AndroidManifest.xml | 5 ++++ .../app/src/main/res/xml/device_filter.xml | 7 ++++++ .../acquisition_device.dart | 3 --- .../infrastructure/eeg_data_repository.dart | 2 ++ .../src/infrastructure/serial_repository.dart | 24 +++++++++---------- 5 files changed, 26 insertions(+), 15 deletions(-) create mode 100644 mobile/android/app/src/main/res/xml/device_filter.xml diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index 98c8e964..bd49c12b 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -33,9 +33,14 @@ android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/launch_background" /> + + + + + diff --git a/mobile/lib/src/domain/acquisition_device/acquisition_device.dart b/mobile/lib/src/domain/acquisition_device/acquisition_device.dart index f15efc14..9721ec3b 100644 --- a/mobile/lib/src/domain/acquisition_device/acquisition_device.dart +++ b/mobile/lib/src/domain/acquisition_device/acquisition_device.dart @@ -1,8 +1,5 @@ -import 'package:equatable/equatable.dart'; import 'package:polydodo/src/domain/entity.dart'; -import '../unique_id.dart'; - class AcquisitionDevice extends Entity { final String name; diff --git a/mobile/lib/src/infrastructure/eeg_data_repository.dart b/mobile/lib/src/infrastructure/eeg_data_repository.dart index 0eabb1bd..f42c6a0b 100644 --- a/mobile/lib/src/infrastructure/eeg_data_repository.dart +++ b/mobile/lib/src/infrastructure/eeg_data_repository.dart @@ -52,6 +52,8 @@ class EEGDataRepository implements IEEGDataRepository { // (packetLoss.toDouble() / totalPackets.toDouble()).toString()); if (event.length != 20) { print("Invalid Event"); + print(event.length); + print(event); return; } totalPackets++; diff --git a/mobile/lib/src/infrastructure/serial_repository.dart b/mobile/lib/src/infrastructure/serial_repository.dart index b92e044e..e0659d19 100644 --- a/mobile/lib/src/infrastructure/serial_repository.dart +++ b/mobile/lib/src/infrastructure/serial_repository.dart @@ -12,28 +12,31 @@ class SerialRepository implements IAcquisitionDeviceRepository { UsbDevice _selectedDevice; UsbPort _serialPort; - List serialPersistency = []; + List _acquisitionDevicePersistency = []; + List _serialDevices = []; final streamController = StreamController>(); void initializeRepository() { - serialPersistency.clear(); - UsbSerial.listDevices().then((devices) => addDevice(devices)); + _acquisitionDevicePersistency.clear(); + _serialDevices.clear(); + UsbSerial.listDevices().then((devices) => addDevices(devices)); } - void addDevice(List serialDevices) { + void addDevices(List serialDevices) { for (UsbDevice serialDevice in serialDevices) { AcquisitionDevice device = AcquisitionDevice( UniqueId.from(serialDevice.deviceId.toString()), - serialDevice.productName, - serialDevice); + serialDevice.productName); - serialPersistency.add(device); + _acquisitionDevicePersistency.add(device); + _serialDevices.add(serialDevice); } - streamController.add(serialPersistency); + streamController.add(_acquisitionDevicePersistency); } Future connect(AcquisitionDevice device) async { - _selectedDevice = device.interface; + _selectedDevice = + _serialDevices[_acquisitionDevicePersistency.indexOf(device)]; _serialPort = await _selectedDevice.create(); bool openResult = await _serialPort.open(); @@ -41,9 +44,6 @@ class SerialRepository implements IAcquisitionDeviceRepository { print("Could not open port"); } - await _serialPort.setDTR(true); - await _serialPort.setRTS(true); - _serialPort.setPortParameters( 115200, UsbPort.DATABITS_8, UsbPort.STOPBITS_1, UsbPort.PARITY_NONE); } From c68745b3aa3bb7dd4773997d8ead9e4ae7f2f8be Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Mon, 12 Oct 2020 16:03:26 -0400 Subject: [PATCH 43/56] Seperated data formatting into stream transformer --- .../eeg_data/i_eeg_data_transformer.dart | 7 + .../src/infrastructure/baseTransformer.dart | 102 ++++++++++++++ .../infrastructure/eeg_data_repository.dart | 125 +++--------------- .../infrastructure/ganglionTransformer.dart | 69 ++++++++++ mobile/lib/src/locator.dart | 3 +- 5 files changed, 195 insertions(+), 111 deletions(-) create mode 100644 mobile/lib/src/domain/eeg_data/i_eeg_data_transformer.dart create mode 100644 mobile/lib/src/infrastructure/baseTransformer.dart create mode 100644 mobile/lib/src/infrastructure/ganglionTransformer.dart diff --git a/mobile/lib/src/domain/eeg_data/i_eeg_data_transformer.dart b/mobile/lib/src/domain/eeg_data/i_eeg_data_transformer.dart new file mode 100644 index 00000000..6f55f8c3 --- /dev/null +++ b/mobile/lib/src/domain/eeg_data/i_eeg_data_transformer.dart @@ -0,0 +1,7 @@ +import 'dart:async'; + +abstract class IEEGDataTransformer implements StreamTransformer { + void reset(); + void onData(S data); + Stream bind(Stream stream); +} diff --git a/mobile/lib/src/infrastructure/baseTransformer.dart b/mobile/lib/src/infrastructure/baseTransformer.dart new file mode 100644 index 00000000..503ce093 --- /dev/null +++ b/mobile/lib/src/infrastructure/baseTransformer.dart @@ -0,0 +1,102 @@ +import 'dart:async'; + +import 'package:polydodo/src/domain/eeg_data/i_eeg_data_transformer.dart'; + +class BaseTransformer implements IEEGDataTransformer { + StreamController controller; + StreamSubscription _subscription; + bool cancelOnError; + Stream _stream; + + List lastSampleData; + int sampleCounter; + + BaseTransformer({bool syncc: false, this.cancelOnError}) { + controller = new StreamController( + onListen: _onListen, + onCancel: _onCancel, + onPause: () { + _subscription.pause(); + }, + onResume: () { + _subscription.resume(); + }, + sync: syncc); + } + + BaseTransformer.broadcast({bool syncc: false, this.cancelOnError}) { + controller = new StreamController.broadcast( + onListen: _onListen, onCancel: _onCancel, sync: syncc); + } + + void reset() { + lastSampleData = [0, 0, 0, 0, 0]; + sampleCounter = 0; + } + + void _onListen() { + reset(); + + _subscription = _stream.listen(onData, + onError: controller.addError, + onDone: controller.close, + cancelOnError: cancelOnError); + } + + void _onCancel() { + _subscription.cancel(); + _subscription = null; + } + + void onData(S data) { + controller.add(data); + } + + @override + Stream bind(Stream stream) { + this._stream = stream; + return controller.stream; + } + + @override + StreamTransformer cast() { + return StreamTransformer.castFrom(this); + } + + List getListForCSV() { + List data = List(30); + + for (int i = 5; i < 15; ++i) { + data[i] = 0; + data[i + 15] = 0; + } + return data; + } + + List convertToMicrovolts(List data, bool isDelta) { + for (int i = 1; i < 5; ++i) { + for (int j = 0; j < 2; ++j) { + if (j == 1 && !isDelta) break; + + int offset = 15 * j; + String binary = data[i + offset].toRadixString(2); + + // Handle negatives + if (isDelta && binary[binary.length - 1] == '1') { + data[i + offset] = (~data[i + offset] & 524287 | 1) * -1; + } + + // Convert to microvolts using the scale factor + data[i + offset] = + data[i + offset].toDouble() * (1200000 / (8388607.0 * 1.5 * 51.0)); + + // Convert delta + if (isDelta) data[i + offset] = lastSampleData[i] - data[i + offset]; + + lastSampleData[i] = data[i + offset]; + } + } + + return data; + } +} diff --git a/mobile/lib/src/infrastructure/eeg_data_repository.dart b/mobile/lib/src/infrastructure/eeg_data_repository.dart index c24e2a26..32662e7d 100644 --- a/mobile/lib/src/infrastructure/eeg_data_repository.dart +++ b/mobile/lib/src/infrastructure/eeg_data_repository.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:csv/csv.dart'; @@ -5,23 +6,27 @@ import 'package:path_provider/path_provider.dart'; import 'package:polydodo/src/domain/eeg_data/eeg_data.dart'; import 'package:polydodo/src/domain/eeg_data/i_eeg_data_repository.dart'; import 'package:polydodo/src/domain/unique_id.dart'; +import 'package:polydodo/src/infrastructure/baseTransformer.dart'; +import 'package:polydodo/src/infrastructure/ganglionTransformer.dart'; import 'constants.dart'; class EEGDataRepository implements IEEGDataRepository { - bool _streamInitialized = false; EEGData _recordingData; - List _lastSampleData = [0, 0, 0, 0, 0]; - int _sampleCounter; + StreamTransformer streamTransformer; void createRecordingFromStream(Stream> stream) { _recordingData = EEGData(UniqueId.from(DateTime.now().toString()), List()); - _sampleCounter = 0; - if (!_streamInitialized) { - _streamInitialized = true; - stream.listen((value) { - addData(value); - }); + + if (streamTransformer == null) { + //todo: dynamically change transformer + streamTransformer = new GanglionTransformer, List>(); + stream + .asBroadcastStream() + .transform(streamTransformer) + .listen((data) => _recordingData.values.add(data)); + } else { + (streamTransformer as BaseTransformer).reset(); } } @@ -32,7 +37,7 @@ class EEGDataRepository implements IEEGDataRepository { final pathOfTheFileToWrite = directory.path + '/' + _recordingData.fileName + ".txt"; File file = File(pathOfTheFileToWrite); - var fileContent = []; + List fileContent = []; fileContent.addAll(OPEN_BCI_HEADER); fileContent.addAll(_recordingData.values); String csv = const ListToCsvConverter().convert(fileContent); @@ -42,104 +47,4 @@ class EEGDataRepository implements IEEGDataRepository { // todo: implement export and import void importData() {} void exportData() {} - - Future addData(List event) async { - if (event.length != 20) { - print("Invalid Event"); - return; - } - int packetID = event[0]; - - // todo: handle packet id 0 (raw data) and possibly impedence for signal validation - if (packetID == 0) { - List data = parseRaw(event); - data = convertToMicrovolts(data, false); - - _recordingData.values.add(data.sublist(0, 15)); - } else if (packetID >= 101 && packetID <= 200) { - List data = parse19Bit(event); - data = convertToMicrovolts(data, true); - - _recordingData.values.add(data.sublist(0, 15)); - _recordingData.values.add(data.sublist(15, 30)); - } - } - - List parseRaw(event) { - List data = getListForCSV(); - - data[0] = _sampleCounter++; - data[1] = (event[1] << 16) | (event[2] << 8) | event[3]; - data[2] = (event[4] << 16) | (event[5] << 8) | event[6]; - data[3] = (event[7] << 16) | (event[8] << 8) | event[9]; - data[4] = (event[10] << 16) | (event[11] << 8) | event[12]; - - return data; - } - - List parse19Bit(event) { - // Test event, comment scale factor - // event = [ 101, 0, 0, 0, 0, 8, 0, 5, 0, 0, 72, 0, 9, 240, 1, 176, 0, 48, 0, 8]; // Positive Test - // Expected [[0, 2, 10, 4], [262148, 507910, 393222, 8]] - // event = [ 101, 255, 255, 191, 255, 239, 255, 252, 255, 255, 88, 0, 11, 62, 56, 224, 0, 63, 240, 1 ]; // Negative Test - // Expected [[-3, -5, -7, -11], [-262139, -198429, -262137, -4095]] - - List data = getListForCSV(); - - data[0] = _sampleCounter; - data[1] = (event[1] << 11) | (event[2] << 3) | (event[3] >> 5); - data[2] = ((event[3] & 31) << 14) | (event[4] << 6) | (event[5] >> 2); - data[3] = ((event[5] & 3) << 17) | - (event[6] << 9) | - (event[7] << 1) | - (event[8] >> 7); - data[4] = ((event[8] & 127) << 12) | (event[9] << 4) | (event[10] >> 4); - data[15] = _sampleCounter++; - data[16] = ((event[10] & 15) << 15) | (event[11] << 7) | (event[12] >> 1); - data[17] = ((event[12] & 1) << 18) | - (event[13] << 10) | - (event[14] << 2) | - (event[15] >> 6); - data[18] = ((event[15] & 63) << 13) | (event[16] << 5) | (event[17] >> 3); - data[19] = ((event[17] & 7) << 16) | (event[18] << 8) | (event[19]); - - return data; - } - - List getListForCSV() { - List data = List(30); - - for (int i = 5; i < 15; ++i) { - data[i] = 0; - data[i + 15] = 0; - } - return data; - } - - List convertToMicrovolts(List data, bool isDelta) { - for (int i = 1; i < 5; ++i) { - for (int j = 0; j < 2; ++j) { - if (j == 1 && !isDelta) break; - - int offset = 15 * j; - String binary = data[i + offset].toRadixString(2); - - // Handle negatives - if (isDelta && binary[binary.length - 1] == '1') { - data[i + offset] = (~data[i + offset] & 524287 | 1) * -1; - } - - // Convert to microvolts using the scale factor - data[i + offset] = - data[i + offset].toDouble() * (1200000 / (8388607.0 * 1.5 * 51.0)); - - // Convert delta - if (isDelta) data[i + offset] = _lastSampleData[i] - data[i + offset]; - - _lastSampleData[i] = data[i + offset]; - } - } - - return data; - } } diff --git a/mobile/lib/src/infrastructure/ganglionTransformer.dart b/mobile/lib/src/infrastructure/ganglionTransformer.dart new file mode 100644 index 00000000..d52f94f1 --- /dev/null +++ b/mobile/lib/src/infrastructure/ganglionTransformer.dart @@ -0,0 +1,69 @@ +import 'baseTransformer.dart'; + +class GanglionTransformer extends BaseTransformer { + void onData(S data) { + List event = data as List; + + if (event.length != 20) { + print("Invalid Event"); + print(event); + return; + } + + int packetID = event[0]; + + if (packetID == 0) { + List data = parseRaw(event); + data = convertToMicrovolts(data, false); + + controller.add(data.sublist(0, 15)); + } else if (packetID >= 101 && packetID <= 200) { + List data = parse19Bit(event); + data = convertToMicrovolts(data, true); + + controller.add(data.sublist(0, 15)); + controller.add(data.sublist(15, 30)); + } + } + + List parseRaw(event) { + List data = getListForCSV(); + + data[0] = sampleCounter++; + data[1] = (event[1] << 16) | (event[2] << 8) | event[3]; + data[2] = (event[4] << 16) | (event[5] << 8) | event[6]; + data[3] = (event[7] << 16) | (event[8] << 8) | event[9]; + data[4] = (event[10] << 16) | (event[11] << 8) | event[12]; + + return data; + } + + List parse19Bit(event) { + // Test event, comment scale factor + // event = [ 101, 0, 0, 0, 0, 8, 0, 5, 0, 0, 72, 0, 9, 240, 1, 176, 0, 48, 0, 8]; // Positive Test + // Expected [[0, 2, 10, 4], [262148, 507910, 393222, 8]] + // event = [ 101, 255, 255, 191, 255, 239, 255, 252, 255, 255, 88, 0, 11, 62, 56, 224, 0, 63, 240, 1 ]; // Negative Test + // Expected [[-3, -5, -7, -11], [-262139, -198429, -262137, -4095]] + + List data = getListForCSV(); + + data[0] = sampleCounter; + data[1] = (event[1] << 11) | (event[2] << 3) | (event[3] >> 5); + data[2] = ((event[3] & 31) << 14) | (event[4] << 6) | (event[5] >> 2); + data[3] = ((event[5] & 3) << 17) | + (event[6] << 9) | + (event[7] << 1) | + (event[8] >> 7); + data[4] = ((event[8] & 127) << 12) | (event[9] << 4) | (event[10] >> 4); + data[15] = sampleCounter++; + data[16] = ((event[10] & 15) << 15) | (event[11] << 7) | (event[12] >> 1); + data[17] = ((event[12] & 1) << 18) | + (event[13] << 10) | + (event[14] << 2) | + (event[15] >> 6); + data[18] = ((event[15] & 63) << 13) | (event[16] << 5) | (event[17] >> 3); + data[19] = ((event[17] & 7) << 16) | (event[18] << 8) | (event[19]); + + return data; + } +} diff --git a/mobile/lib/src/locator.dart b/mobile/lib/src/locator.dart index e97ed63c..82520327 100644 --- a/mobile/lib/src/locator.dart +++ b/mobile/lib/src/locator.dart @@ -3,6 +3,7 @@ import 'package:get_it/get_it.dart'; import 'package:polydodo/src/application/device/device_selector_cubit.dart'; import 'package:polydodo/src/application/eeg_data/data_cubit.dart'; import 'package:polydodo/src/domain/acquisition_device/i_acquisition_device_repository.dart'; +import 'package:polydodo/src/infrastructure/bluetooth_repository.dart'; import 'package:polydodo/src/infrastructure/serial_repository.dart'; import 'domain/eeg_data/i_eeg_data_repository.dart'; @@ -13,7 +14,7 @@ final _serviceLocator = GetIt.asNewInstance(); void registerServices() { _serviceLocator - .registerSingleton(SerialRepository()); + .registerSingleton(BluetoothRepository()); _serviceLocator.registerSingleton(EEGDataRepository()); } From 9c08814e346a61c066d3e7e43f0583d7bad5abb1 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Tue, 13 Oct 2020 17:23:01 -0400 Subject: [PATCH 44/56] Implemented Cyton transformer --- .../eeg_data/i_eeg_data_transformer.dart | 1 + .../src/infrastructure/baseTransformer.dart | 102 ------------------ .../bluetooth_repository.dart | 7 +- .../eeg_data_repository.dart | 17 +-- .../serial_repository.dart | 12 +-- mobile/lib/src/infrastructure/constants.dart | 48 ++++++++- .../baseTransformer.dart | 59 ++++++++++ .../cytonTransformer.dart | 79 ++++++++++++++ .../ganglionTransformer.dart | 61 +++++++++-- mobile/lib/src/locator.dart | 11 +- polydodo.code-workspace | 14 --- 11 files changed, 263 insertions(+), 148 deletions(-) delete mode 100644 mobile/lib/src/infrastructure/baseTransformer.dart rename mobile/lib/src/infrastructure/{ => connection_repositories}/bluetooth_repository.dart (96%) rename mobile/lib/src/infrastructure/{ => connection_repositories}/eeg_data_repository.dart (69%) rename mobile/lib/src/infrastructure/{ => connection_repositories}/serial_repository.dart (87%) create mode 100644 mobile/lib/src/infrastructure/eeg_data_transformers/baseTransformer.dart create mode 100644 mobile/lib/src/infrastructure/eeg_data_transformers/cytonTransformer.dart rename mobile/lib/src/infrastructure/{ => eeg_data_transformers}/ganglionTransformer.dart (57%) diff --git a/mobile/lib/src/domain/eeg_data/i_eeg_data_transformer.dart b/mobile/lib/src/domain/eeg_data/i_eeg_data_transformer.dart index 6f55f8c3..425c4128 100644 --- a/mobile/lib/src/domain/eeg_data/i_eeg_data_transformer.dart +++ b/mobile/lib/src/domain/eeg_data/i_eeg_data_transformer.dart @@ -4,4 +4,5 @@ abstract class IEEGDataTransformer implements StreamTransformer { void reset(); void onData(S data); Stream bind(Stream stream); + IEEGDataTransformer.broadcast(); } diff --git a/mobile/lib/src/infrastructure/baseTransformer.dart b/mobile/lib/src/infrastructure/baseTransformer.dart deleted file mode 100644 index 503ce093..00000000 --- a/mobile/lib/src/infrastructure/baseTransformer.dart +++ /dev/null @@ -1,102 +0,0 @@ -import 'dart:async'; - -import 'package:polydodo/src/domain/eeg_data/i_eeg_data_transformer.dart'; - -class BaseTransformer implements IEEGDataTransformer { - StreamController controller; - StreamSubscription _subscription; - bool cancelOnError; - Stream _stream; - - List lastSampleData; - int sampleCounter; - - BaseTransformer({bool syncc: false, this.cancelOnError}) { - controller = new StreamController( - onListen: _onListen, - onCancel: _onCancel, - onPause: () { - _subscription.pause(); - }, - onResume: () { - _subscription.resume(); - }, - sync: syncc); - } - - BaseTransformer.broadcast({bool syncc: false, this.cancelOnError}) { - controller = new StreamController.broadcast( - onListen: _onListen, onCancel: _onCancel, sync: syncc); - } - - void reset() { - lastSampleData = [0, 0, 0, 0, 0]; - sampleCounter = 0; - } - - void _onListen() { - reset(); - - _subscription = _stream.listen(onData, - onError: controller.addError, - onDone: controller.close, - cancelOnError: cancelOnError); - } - - void _onCancel() { - _subscription.cancel(); - _subscription = null; - } - - void onData(S data) { - controller.add(data); - } - - @override - Stream bind(Stream stream) { - this._stream = stream; - return controller.stream; - } - - @override - StreamTransformer cast() { - return StreamTransformer.castFrom(this); - } - - List getListForCSV() { - List data = List(30); - - for (int i = 5; i < 15; ++i) { - data[i] = 0; - data[i + 15] = 0; - } - return data; - } - - List convertToMicrovolts(List data, bool isDelta) { - for (int i = 1; i < 5; ++i) { - for (int j = 0; j < 2; ++j) { - if (j == 1 && !isDelta) break; - - int offset = 15 * j; - String binary = data[i + offset].toRadixString(2); - - // Handle negatives - if (isDelta && binary[binary.length - 1] == '1') { - data[i + offset] = (~data[i + offset] & 524287 | 1) * -1; - } - - // Convert to microvolts using the scale factor - data[i + offset] = - data[i + offset].toDouble() * (1200000 / (8388607.0 * 1.5 * 51.0)); - - // Convert delta - if (isDelta) data[i + offset] = lastSampleData[i] - data[i + offset]; - - lastSampleData[i] = data[i + offset]; - } - } - - return data; - } -} diff --git a/mobile/lib/src/infrastructure/bluetooth_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart similarity index 96% rename from mobile/lib/src/infrastructure/bluetooth_repository.dart rename to mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart index 57e3f812..cce95c8f 100644 --- a/mobile/lib/src/infrastructure/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart @@ -3,14 +3,13 @@ import 'dart:async'; import 'package:flutter_reactive_ble/flutter_reactive_ble.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/infrastructure/constants.dart'; import 'package:polydodo/src/domain/unique_id.dart'; class BluetoothRepository implements IAcquisitionDeviceRepository { static const String BLE_SERVICE = "0000fe84-0000-1000-8000-00805f9b34fb"; static const String BLE_RECEIVE = "2d30c082-f39f-4ce6-923f-3484ea480596"; static const String BLE_SEND = "2d30c083-f39f-4ce6-923f-3484ea480596"; - static const startStreamChar = 'b'; - static const stopStreamChar = 's'; AcquisitionDevice _selectedDevice; QualifiedCharacteristic _sendCharacteristic; @@ -96,7 +95,7 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { priority: ConnectionPriority.highPerformance); flutterReactiveBle.writeCharacteristicWithoutResponse(_sendCharacteristic, - value: startStreamChar.codeUnits); + value: START_STREAM_CHAR.codeUnits); return flutterReactiveBle.subscribeToCharacteristic(_receiveCharacteristic); } @@ -106,7 +105,7 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { deviceId: _selectedDevice.id.toString(), priority: ConnectionPriority.balanced); flutterReactiveBle.writeCharacteristicWithoutResponse(_sendCharacteristic, - value: stopStreamChar.codeUnits); + value: STOP_STREAM_CHAR.codeUnits); } @override diff --git a/mobile/lib/src/infrastructure/eeg_data_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart similarity index 69% rename from mobile/lib/src/infrastructure/eeg_data_repository.dart rename to mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart index 32662e7d..dafe627c 100644 --- a/mobile/lib/src/infrastructure/eeg_data_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart @@ -1,18 +1,19 @@ import 'dart:async'; import 'dart:io'; +import 'dart:typed_data'; import 'package:csv/csv.dart'; import 'package:path_provider/path_provider.dart'; import 'package:polydodo/src/domain/eeg_data/eeg_data.dart'; import 'package:polydodo/src/domain/eeg_data/i_eeg_data_repository.dart'; +import 'package:polydodo/src/domain/eeg_data/i_eeg_data_transformer.dart'; import 'package:polydodo/src/domain/unique_id.dart'; -import 'package:polydodo/src/infrastructure/baseTransformer.dart'; -import 'package:polydodo/src/infrastructure/ganglionTransformer.dart'; -import 'constants.dart'; +import 'package:polydodo/src/infrastructure/eeg_data_transformers/cytonTransformer.dart'; +import 'package:polydodo/src/infrastructure/constants.dart'; class EEGDataRepository implements IEEGDataRepository { EEGData _recordingData; - StreamTransformer streamTransformer; + IEEGDataTransformer, List> streamTransformer; void createRecordingFromStream(Stream> stream) { _recordingData = @@ -20,13 +21,14 @@ class EEGDataRepository implements IEEGDataRepository { if (streamTransformer == null) { //todo: dynamically change transformer - streamTransformer = new GanglionTransformer, List>(); + // new GanglionTransformer, List>.broadcast() + streamTransformer = new CytonTransformer.broadcast(); stream .asBroadcastStream() .transform(streamTransformer) .listen((data) => _recordingData.values.add(data)); } else { - (streamTransformer as BaseTransformer).reset(); + streamTransformer.reset(); } } @@ -38,7 +40,8 @@ class EEGDataRepository implements IEEGDataRepository { directory.path + '/' + _recordingData.fileName + ".txt"; File file = File(pathOfTheFileToWrite); List fileContent = []; - fileContent.addAll(OPEN_BCI_HEADER); + //todo: dynamically change header when we change transformer + fileContent.addAll(OPEN_BCI_CYTON_HEADER); fileContent.addAll(_recordingData.values); String csv = const ListToCsvConverter().convert(fileContent); await file.writeAsString(csv); diff --git a/mobile/lib/src/infrastructure/serial_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart similarity index 87% rename from mobile/lib/src/infrastructure/serial_repository.dart rename to mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart index f0d3c3df..ea2f0227 100644 --- a/mobile/lib/src/infrastructure/serial_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart @@ -4,12 +4,10 @@ import 'dart:typed_data'; import 'package:polydodo/src/domain/unique_id.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/infrastructure/constants.dart'; import 'package:usb_serial/usb_serial.dart'; class SerialRepository implements IAcquisitionDeviceRepository { - static const startStreamChar = 'b'; - static const stopStreamChar = 's'; - UsbDevice _selectedDevice; UsbPort _serialPort; List _acquisitionDevicePersistency = []; @@ -42,11 +40,13 @@ class SerialRepository implements IAcquisitionDeviceRepository { bool openResult = await _serialPort.open(); if (!openResult) { - print("Could not open port"); + callback(false, Exception("Could not open port")); } _serialPort.setPortParameters( 115200, UsbPort.DATABITS_8, UsbPort.STOPBITS_1, UsbPort.PARITY_NONE); + + callback(true, null); } void disconnect() async { @@ -58,13 +58,13 @@ class SerialRepository implements IAcquisitionDeviceRepository { } Future>> startDataStream() async { - _serialPort.write(Uint8List.fromList(startStreamChar.codeUnits)); + _serialPort.write(Uint8List.fromList(START_STREAM_CHAR.codeUnits)); return _serialPort.inputStream; } void stopDataStream() { - _serialPort.write(Uint8List.fromList(stopStreamChar.codeUnits)); + _serialPort.write(Uint8List.fromList(STOP_STREAM_CHAR.codeUnits)); } Stream> watch() => streamController.stream; diff --git a/mobile/lib/src/infrastructure/constants.dart b/mobile/lib/src/infrastructure/constants.dart index cc7f7932..dd30eddf 100644 --- a/mobile/lib/src/infrastructure/constants.dart +++ b/mobile/lib/src/infrastructure/constants.dart @@ -1,4 +1,11 @@ -const OPEN_BCI_HEADER = [ +const START_STREAM_CHAR = 'b'; +const STOP_STREAM_CHAR = 's'; + +const GANGLION_NUMBER_CHANNELS = 4; +const GANGLION_PACKET_SIZE = 20; +const GANGLION_NUMBER_COLUMNS = 15; +const GANGLION_EXTRA_COLUMNS = 10; +const OPEN_BCI_GANGLION_HEADER = [ ["%OpenBCI Raw EEG Data"], ["%Number of channels = 4"], ["%Sample Rate = 200 Hz"], @@ -21,3 +28,42 @@ const OPEN_BCI_HEADER = [ " Timestamp (Formatted)" ] ]; + +const CYTON_NUMBER_CHANNELS = 8; +const CYTON_PACKET_SIZE = 33; +const CYTON_NUMBER_COLUMNS = 24; +const CYTON_EXTRA_COLUMNS = 15; +const CYTON_HEADER = 160; +const CYTON_FOOTER_MINIMUM = 192; +const OPEN_BCI_CYTON_HEADER = [ + ["%OpenBCI Raw EEG Data"], + ["%Number of channels = 8"], + ["%Sample Rate = 250 Hz"], + ["%Board = OpenBCI_GUI\$BoardCytonSerial"], + [ + "Sample Index", + " EXG Channel 0", + " EXG Channel 1", + " EXG Channel 2", + " EXG Channel 3", + " EXG Channel 4", + " EXG Channel 5", + " EXG Channel 6", + " EXG Channel 7", + " Accel Channel 0", + " Accel Channel 1", + " Accel Channel 2", + " Other", + " Other", + " Other", + " Other", + " Other", + " Other", + " Other", + " Analog Channel 0", + " Analog Channel 1", + " Analog Channel 2", + " Timestamp", + " Timestamp (Formatted)" + ] +]; diff --git a/mobile/lib/src/infrastructure/eeg_data_transformers/baseTransformer.dart b/mobile/lib/src/infrastructure/eeg_data_transformers/baseTransformer.dart new file mode 100644 index 00000000..7ff87f0e --- /dev/null +++ b/mobile/lib/src/infrastructure/eeg_data_transformers/baseTransformer.dart @@ -0,0 +1,59 @@ +import 'dart:async'; + +import 'package:polydodo/src/domain/eeg_data/i_eeg_data_transformer.dart'; + +class BaseTransformer implements IEEGDataTransformer { + StreamController controller; + StreamSubscription _subscription; + bool cancelOnError; + Stream _stream; + + BaseTransformer({bool synchronous: false, this.cancelOnError}) { + controller = new StreamController( + onListen: _onListen, + onCancel: _onCancel, + onPause: () { + _subscription.pause(); + }, + onResume: () { + _subscription.resume(); + }, + sync: synchronous); + } + + BaseTransformer.broadcast({bool synchronous: false, this.cancelOnError}) { + controller = new StreamController.broadcast( + onListen: _onListen, onCancel: _onCancel, sync: synchronous); + } + + void reset() {} + + void _onListen() { + reset(); + + _subscription = _stream.listen(onData, + onError: controller.addError, + onDone: controller.close, + cancelOnError: cancelOnError); + } + + void _onCancel() { + _subscription.cancel(); + _subscription = null; + } + + void onData(S data) { + controller.add(data); + } + + @override + Stream bind(Stream stream) { + this._stream = stream; + return controller.stream; + } + + @override + StreamTransformer cast() { + return StreamTransformer.castFrom(this); + } +} diff --git a/mobile/lib/src/infrastructure/eeg_data_transformers/cytonTransformer.dart b/mobile/lib/src/infrastructure/eeg_data_transformers/cytonTransformer.dart new file mode 100644 index 00000000..3eb383cf --- /dev/null +++ b/mobile/lib/src/infrastructure/eeg_data_transformers/cytonTransformer.dart @@ -0,0 +1,79 @@ +import 'dart:math'; + +import 'package:polydodo/src/infrastructure/constants.dart'; +import 'baseTransformer.dart'; + +class CytonTransformer extends BaseTransformer { + List packet; + + CytonTransformer.broadcast({bool synchronous: false, cancelOnError}) + : super.broadcast(synchronous: synchronous, cancelOnError: cancelOnError); + + void reset() { + packet = new List(); + } + + void onData(S data) { + List event = data as List; + + for (var i in event) { + if (packet.length == 0) { + if (i != CYTON_HEADER) { + print("Missing header byte"); + continue; + } + } + packet.add(i); + + if (packet.length == CYTON_PACKET_SIZE) { + if (packet[CYTON_PACKET_SIZE - 1] < CYTON_FOOTER_MINIMUM) { + print("Invalid packet"); + packet.clear(); + continue; + } + + List data = parsePacket(); + + data = convertToMicrovolts(data); + + controller.add(data); + } + } + } + + List parsePacket() { + List data = getListForCSV(); + + data[0] = packet[1]; + data[1] = (packet[2] << 16) | (packet[3] << 8) | packet[4]; + data[2] = (packet[5] << 16) | (packet[6] << 8) | packet[7]; + data[3] = (packet[8] << 16) | (packet[9] << 8) | packet[10]; + data[4] = (packet[11] << 16) | (packet[12] << 8) | packet[13]; + data[5] = (packet[14] << 16) | (packet[15] << 8) | packet[16]; + data[6] = (packet[17] << 16) | (packet[18] << 8) | packet[19]; + data[7] = (packet[20] << 16) | (packet[21] << 8) | packet[22]; + data[8] = (packet[23] << 16) | (packet[24] << 8) | packet[25]; + + packet.clear(); + return data; + } + + List getListForCSV() { + List data = List(CYTON_NUMBER_COLUMNS); + + for (int i = 1; i < CYTON_EXTRA_COLUMNS + 1; ++i) { + data[CYTON_NUMBER_COLUMNS - i] = 0; + } + + return data; + } + + List convertToMicrovolts(List data) { + for (int i = 1; i < CYTON_NUMBER_CHANNELS + 1; ++i) { + // Convert to microvolts using the scale factor + data[i] = data[i].toDouble() * (4500000 / (24 * (pow(2, 23) - 1))); + } + + return data; + } +} diff --git a/mobile/lib/src/infrastructure/ganglionTransformer.dart b/mobile/lib/src/infrastructure/eeg_data_transformers/ganglionTransformer.dart similarity index 57% rename from mobile/lib/src/infrastructure/ganglionTransformer.dart rename to mobile/lib/src/infrastructure/eeg_data_transformers/ganglionTransformer.dart index d52f94f1..0c1a9e3f 100644 --- a/mobile/lib/src/infrastructure/ganglionTransformer.dart +++ b/mobile/lib/src/infrastructure/eeg_data_transformers/ganglionTransformer.dart @@ -1,14 +1,22 @@ +import 'package:polydodo/src/infrastructure/constants.dart'; import 'baseTransformer.dart'; class GanglionTransformer extends BaseTransformer { + List _lastSampleData; + int _sampleCounter; + + GanglionTransformer.broadcast({bool synchronous: false, cancelOnError}) + : super.broadcast(synchronous: synchronous, cancelOnError: cancelOnError); + + void reset() { + _lastSampleData = [0, 0, 0, 0, 0]; + _sampleCounter = 0; + } + void onData(S data) { List event = data as List; - if (event.length != 20) { - print("Invalid Event"); - print(event); - return; - } + if (event.length != GANGLION_PACKET_SIZE) return; int packetID = event[0]; @@ -29,7 +37,7 @@ class GanglionTransformer extends BaseTransformer { List parseRaw(event) { List data = getListForCSV(); - data[0] = sampleCounter++; + data[0] = _sampleCounter++; data[1] = (event[1] << 16) | (event[2] << 8) | event[3]; data[2] = (event[4] << 16) | (event[5] << 8) | event[6]; data[3] = (event[7] << 16) | (event[8] << 8) | event[9]; @@ -47,7 +55,7 @@ class GanglionTransformer extends BaseTransformer { List data = getListForCSV(); - data[0] = sampleCounter; + data[0] = _sampleCounter; data[1] = (event[1] << 11) | (event[2] << 3) | (event[3] >> 5); data[2] = ((event[3] & 31) << 14) | (event[4] << 6) | (event[5] >> 2); data[3] = ((event[5] & 3) << 17) | @@ -55,7 +63,7 @@ class GanglionTransformer extends BaseTransformer { (event[7] << 1) | (event[8] >> 7); data[4] = ((event[8] & 127) << 12) | (event[9] << 4) | (event[10] >> 4); - data[15] = sampleCounter++; + data[15] = _sampleCounter++; data[16] = ((event[10] & 15) << 15) | (event[11] << 7) | (event[12] >> 1); data[17] = ((event[12] & 1) << 18) | (event[13] << 10) | @@ -66,4 +74,41 @@ class GanglionTransformer extends BaseTransformer { return data; } + + List getListForCSV() { + List data = List(GANGLION_NUMBER_COLUMNS * 2); + + for (int i = 1; i < GANGLION_EXTRA_COLUMNS + 1; ++i) { + data[GANGLION_NUMBER_COLUMNS - i] = 0; + data[GANGLION_NUMBER_COLUMNS - i + 15] = 0; + } + return data; + } + + List convertToMicrovolts(List data, bool isDelta) { + for (int i = 1; i < GANGLION_NUMBER_CHANNELS + 1; ++i) { + for (int j = 0; j < 2; ++j) { + if (j == 1 && !isDelta) break; + + int offset = 15 * j; + String binary = data[i + offset].toRadixString(2); + + // Handle negatives + if (isDelta && binary[binary.length - 1] == '1') { + data[i + offset] = (~data[i + offset] & 524287 | 1) * -1; + } + + // Convert to microvolts using the scale factor + data[i + offset] = + data[i + offset].toDouble() * (1200000 / (8388607.0 * 1.5 * 51.0)); + + // Convert delta + if (isDelta) data[i + offset] = _lastSampleData[i] - data[i + offset]; + + _lastSampleData[i] = data[i + offset]; + } + } + + return data; + } } diff --git a/mobile/lib/src/locator.dart b/mobile/lib/src/locator.dart index 82520327..4ee71c88 100644 --- a/mobile/lib/src/locator.dart +++ b/mobile/lib/src/locator.dart @@ -3,18 +3,17 @@ import 'package:get_it/get_it.dart'; import 'package:polydodo/src/application/device/device_selector_cubit.dart'; import 'package:polydodo/src/application/eeg_data/data_cubit.dart'; import 'package:polydodo/src/domain/acquisition_device/i_acquisition_device_repository.dart'; -import 'package:polydodo/src/infrastructure/bluetooth_repository.dart'; -import 'package:polydodo/src/infrastructure/serial_repository.dart'; - -import 'domain/eeg_data/i_eeg_data_repository.dart'; -import 'infrastructure/eeg_data_repository.dart'; +import 'package:polydodo/src/domain/eeg_data/i_eeg_data_repository.dart'; +import 'package:polydodo/src/infrastructure/connection_repositories/eeg_data_repository.dart'; +import 'package:polydodo/src/infrastructure/connection_repositories/serial_repository.dart'; /// Private GetIt instance as we want all DI to be performed here in this file final _serviceLocator = GetIt.asNewInstance(); void registerServices() { + // todo: dynamically change repository _serviceLocator - .registerSingleton(BluetoothRepository()); + .registerSingleton(SerialRepository()); _serviceLocator.registerSingleton(EEGDataRepository()); } diff --git a/polydodo.code-workspace b/polydodo.code-workspace index 7f2416ba..c191fdab 100644 --- a/polydodo.code-workspace +++ b/polydodo.code-workspace @@ -38,20 +38,6 @@ }, "[dart]": { "editor.defaultFormatter": "Dart-Code.dart-code" -<<<<<<< HEAD - }, - "protoc": { - "compile_on_save": true, - "compile_all_path": "protos", - "options": [ - "--dart_out=grpc:mobile/lib", - "--python_out=backend", - "--grpc_python_out=backend", - "--js_out=import_style=commonjs:web/src", - "--grpc-web_out=import_style=commonjs,mode=grpcwebtext:web/src" - ] -======= ->>>>>>> master }, "python.linting.enabled": true, "python.linting.flake8Enabled": true, From 469cd05c1236a2231e53a303bf17154ec3a0b57b Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Wed, 14 Oct 2020 16:21:52 -0400 Subject: [PATCH 45/56] Small convention changes and changed BaseTransformer to an abstract class --- .../bluetooth_repository.dart | 6 ++---- .../serial_repository.dart | 16 +++++++--------- ...nsformer.dart => baseOpenBCITransformer.dart} | 14 +++++--------- .../eeg_data_transformers/cytonTransformer.dart | 4 ++-- .../ganglionTransformer.dart | 4 ++-- 5 files changed, 18 insertions(+), 26 deletions(-) rename mobile/lib/src/infrastructure/eeg_data_transformers/{baseTransformer.dart => baseOpenBCITransformer.dart} (79%) diff --git a/mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart index cce95c8f..693466e0 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart @@ -71,10 +71,8 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { } void disconnect() async { - if (_selectedDevice != null) { - _selectedDevice = null; - _connectedDeviceStream.cancel(); - } + _selectedDevice = null; + _connectedDeviceStream?.cancel(); } void setupCharacteristics() async { diff --git a/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart index ea2f0227..3e636ce5 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart @@ -37,9 +37,9 @@ class SerialRepository implements IAcquisitionDeviceRepository { _selectedDevice = _serialDevices[_acquisitionDevicePersistency.indexOf(device)]; _serialPort = await _selectedDevice.create(); - bool openResult = await _serialPort.open(); + bool openSuccessful = await _serialPort.open(); - if (!openResult) { + if (!openSuccessful) { callback(false, Exception("Could not open port")); } @@ -50,11 +50,9 @@ class SerialRepository implements IAcquisitionDeviceRepository { } void disconnect() async { - if (_selectedDevice != null && _serialPort != null) { - await _serialPort.close(); - _selectedDevice = null; - _serialPort = null; - } + await _serialPort?.close(); + _selectedDevice = null; + _serialPort = null; } Future>> startDataStream() async { @@ -63,8 +61,8 @@ class SerialRepository implements IAcquisitionDeviceRepository { return _serialPort.inputStream; } - void stopDataStream() { - _serialPort.write(Uint8List.fromList(STOP_STREAM_CHAR.codeUnits)); + Future stopDataStream() async { + await _serialPort.write(Uint8List.fromList(STOP_STREAM_CHAR.codeUnits)); } Stream> watch() => streamController.stream; diff --git a/mobile/lib/src/infrastructure/eeg_data_transformers/baseTransformer.dart b/mobile/lib/src/infrastructure/eeg_data_transformers/baseOpenBCITransformer.dart similarity index 79% rename from mobile/lib/src/infrastructure/eeg_data_transformers/baseTransformer.dart rename to mobile/lib/src/infrastructure/eeg_data_transformers/baseOpenBCITransformer.dart index 7ff87f0e..2ac7fa36 100644 --- a/mobile/lib/src/infrastructure/eeg_data_transformers/baseTransformer.dart +++ b/mobile/lib/src/infrastructure/eeg_data_transformers/baseOpenBCITransformer.dart @@ -2,13 +2,14 @@ import 'dart:async'; import 'package:polydodo/src/domain/eeg_data/i_eeg_data_transformer.dart'; -class BaseTransformer implements IEEGDataTransformer { +abstract class BaseOpenBCITransformer + implements IEEGDataTransformer { StreamController controller; StreamSubscription _subscription; bool cancelOnError; Stream _stream; - BaseTransformer({bool synchronous: false, this.cancelOnError}) { + BaseOpenBCITransformer({bool synchronous: false, this.cancelOnError}) { controller = new StreamController( onListen: _onListen, onCancel: _onCancel, @@ -21,13 +22,12 @@ class BaseTransformer implements IEEGDataTransformer { sync: synchronous); } - BaseTransformer.broadcast({bool synchronous: false, this.cancelOnError}) { + BaseOpenBCITransformer.broadcast( + {bool synchronous: false, this.cancelOnError}) { controller = new StreamController.broadcast( onListen: _onListen, onCancel: _onCancel, sync: synchronous); } - void reset() {} - void _onListen() { reset(); @@ -42,10 +42,6 @@ class BaseTransformer implements IEEGDataTransformer { _subscription = null; } - void onData(S data) { - controller.add(data); - } - @override Stream bind(Stream stream) { this._stream = stream; diff --git a/mobile/lib/src/infrastructure/eeg_data_transformers/cytonTransformer.dart b/mobile/lib/src/infrastructure/eeg_data_transformers/cytonTransformer.dart index 3eb383cf..f90742bd 100644 --- a/mobile/lib/src/infrastructure/eeg_data_transformers/cytonTransformer.dart +++ b/mobile/lib/src/infrastructure/eeg_data_transformers/cytonTransformer.dart @@ -1,9 +1,9 @@ import 'dart:math'; import 'package:polydodo/src/infrastructure/constants.dart'; -import 'baseTransformer.dart'; +import 'baseOpenBCITransformer.dart'; -class CytonTransformer extends BaseTransformer { +class CytonTransformer extends BaseOpenBCITransformer { List packet; CytonTransformer.broadcast({bool synchronous: false, cancelOnError}) diff --git a/mobile/lib/src/infrastructure/eeg_data_transformers/ganglionTransformer.dart b/mobile/lib/src/infrastructure/eeg_data_transformers/ganglionTransformer.dart index 0c1a9e3f..8f9ada9f 100644 --- a/mobile/lib/src/infrastructure/eeg_data_transformers/ganglionTransformer.dart +++ b/mobile/lib/src/infrastructure/eeg_data_transformers/ganglionTransformer.dart @@ -1,7 +1,7 @@ import 'package:polydodo/src/infrastructure/constants.dart'; -import 'baseTransformer.dart'; +import 'baseOpenBCITransformer.dart'; -class GanglionTransformer extends BaseTransformer { +class GanglionTransformer extends BaseOpenBCITransformer { List _lastSampleData; int _sampleCounter; From 3b865b7febeca15c03a85299bf617228fe1f7e67 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Fri, 16 Oct 2020 13:17:38 -0400 Subject: [PATCH 46/56] Made async function into future --- .../connection_repositories/serial_repository.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart index 3e636ce5..0d032aa8 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart @@ -49,7 +49,7 @@ class SerialRepository implements IAcquisitionDeviceRepository { callback(true, null); } - void disconnect() async { + Future disconnect() async { await _serialPort?.close(); _selectedDevice = null; _serialPort = null; From 6abde11ca07005fef9310bd68b626a932ef4c62b Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Fri, 16 Oct 2020 13:50:40 -0400 Subject: [PATCH 47/56] Removed transformer interface --- .../lib/src/domain/eeg_data/i_eeg_data_transformer.dart | 8 -------- .../connection_repositories/eeg_data_repository.dart | 4 ++-- .../eeg_data_transformers/baseOpenBCITransformer.dart | 9 +++++---- 3 files changed, 7 insertions(+), 14 deletions(-) delete mode 100644 mobile/lib/src/domain/eeg_data/i_eeg_data_transformer.dart diff --git a/mobile/lib/src/domain/eeg_data/i_eeg_data_transformer.dart b/mobile/lib/src/domain/eeg_data/i_eeg_data_transformer.dart deleted file mode 100644 index 425c4128..00000000 --- a/mobile/lib/src/domain/eeg_data/i_eeg_data_transformer.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'dart:async'; - -abstract class IEEGDataTransformer implements StreamTransformer { - void reset(); - void onData(S data); - Stream bind(Stream stream); - IEEGDataTransformer.broadcast(); -} diff --git a/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart index dafe627c..32470efb 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart @@ -6,14 +6,14 @@ import 'package:csv/csv.dart'; import 'package:path_provider/path_provider.dart'; import 'package:polydodo/src/domain/eeg_data/eeg_data.dart'; import 'package:polydodo/src/domain/eeg_data/i_eeg_data_repository.dart'; -import 'package:polydodo/src/domain/eeg_data/i_eeg_data_transformer.dart'; import 'package:polydodo/src/domain/unique_id.dart'; +import 'package:polydodo/src/infrastructure/eeg_data_transformers/baseOpenBCITransformer.dart'; import 'package:polydodo/src/infrastructure/eeg_data_transformers/cytonTransformer.dart'; import 'package:polydodo/src/infrastructure/constants.dart'; class EEGDataRepository implements IEEGDataRepository { EEGData _recordingData; - IEEGDataTransformer, List> streamTransformer; + BaseOpenBCITransformer, List> streamTransformer; void createRecordingFromStream(Stream> stream) { _recordingData = diff --git a/mobile/lib/src/infrastructure/eeg_data_transformers/baseOpenBCITransformer.dart b/mobile/lib/src/infrastructure/eeg_data_transformers/baseOpenBCITransformer.dart index 2ac7fa36..3e3bd609 100644 --- a/mobile/lib/src/infrastructure/eeg_data_transformers/baseOpenBCITransformer.dart +++ b/mobile/lib/src/infrastructure/eeg_data_transformers/baseOpenBCITransformer.dart @@ -1,9 +1,6 @@ import 'dart:async'; -import 'package:polydodo/src/domain/eeg_data/i_eeg_data_transformer.dart'; - -abstract class BaseOpenBCITransformer - implements IEEGDataTransformer { +abstract class BaseOpenBCITransformer implements StreamTransformer { StreamController controller; StreamSubscription _subscription; bool cancelOnError; @@ -28,6 +25,10 @@ abstract class BaseOpenBCITransformer onListen: _onListen, onCancel: _onCancel, sync: synchronous); } + void reset(); + + void onData(S data); + void _onListen() { reset(); From b63d3b9100bd63441cba689a49295bf424bbf464 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Fri, 16 Oct 2020 18:28:34 -0400 Subject: [PATCH 48/56] Dynamically changing repositories and transformers --- .../device/device_selector_cubit.dart | 13 +++++ .../eeg_data/i_eeg_data_repository.dart | 1 + .../bluetooth_repository.dart | 3 +- .../eeg_data_repository.dart | 47 +++++++++++----- mobile/lib/src/locator.dart | 17 ++---- .../bluetoothSelector_route.dart | 3 ++ mobile/pubspec.lock | 54 +++++++++++++++++++ mobile/pubspec.yaml | 1 + 8 files changed, 111 insertions(+), 28 deletions(-) diff --git a/mobile/lib/src/application/device/device_selector_cubit.dart b/mobile/lib/src/application/device/device_selector_cubit.dart index 90412b90..8db63920 100644 --- a/mobile/lib/src/application/device/device_selector_cubit.dart +++ b/mobile/lib/src/application/device/device_selector_cubit.dart @@ -4,12 +4,15 @@ 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 'package:streaming_shared_preferences/streaming_shared_preferences.dart'; import 'device_selector_state.dart'; class DeviceSelectorCubit extends Cubit { final IAcquisitionDeviceRepository _deviceRepository; StreamSubscription> _acquisitionDeviceStream; + // todo: remove this variable, also test that switch works correctly once UI is done + bool usingBluetooth = true; DeviceSelectorCubit(this._deviceRepository) : super(DeviceInitial()) { startSearching(); @@ -47,4 +50,14 @@ class DeviceSelectorCubit extends Cubit { _deviceRepository.disconnect(); startSearching(); } + + // todo: change bluetooth preferences in the preference section of the app + void swapBluetooth() async { + print("swap"); + usingBluetooth = !usingBluetooth; + StreamingSharedPreferences _prefs = + await StreamingSharedPreferences.instance; + + _prefs.setBool('using_bluetooth', usingBluetooth); + } } diff --git a/mobile/lib/src/domain/eeg_data/i_eeg_data_repository.dart b/mobile/lib/src/domain/eeg_data/i_eeg_data_repository.dart index d1e12b38..12c3781c 100644 --- a/mobile/lib/src/domain/eeg_data/i_eeg_data_repository.dart +++ b/mobile/lib/src/domain/eeg_data/i_eeg_data_repository.dart @@ -1,4 +1,5 @@ abstract class IEEGDataRepository { + void initialize(); void createRecordingFromStream(Stream> stream); void stopRecordingFromStream(); diff --git a/mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart index 693466e0..b9a2500e 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart @@ -21,8 +21,6 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { List _acquisitionDevicePersistency = []; final streamController = StreamController>(); - BluetoothRepository(); - void initializeRepository() { if (_bluetoothScanSubscription == null) { flutterReactiveBle = FlutterReactiveBle(); @@ -31,6 +29,7 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { withServices: []).listen((device) => addDevice(device)); } else { _bluetoothScanSubscription.resume(); + _acquisitionDevicePersistency.clear(); } } diff --git a/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart index 32470efb..68c10cce 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart @@ -10,30 +10,53 @@ import 'package:polydodo/src/domain/unique_id.dart'; import 'package:polydodo/src/infrastructure/eeg_data_transformers/baseOpenBCITransformer.dart'; import 'package:polydodo/src/infrastructure/eeg_data_transformers/cytonTransformer.dart'; import 'package:polydodo/src/infrastructure/constants.dart'; +import 'package:polydodo/src/infrastructure/eeg_data_transformers/ganglionTransformer.dart'; +import 'package:streaming_shared_preferences/streaming_shared_preferences.dart'; class EEGDataRepository implements IEEGDataRepository { EEGData _recordingData; - BaseOpenBCITransformer, List> streamTransformer; + BaseOpenBCITransformer, List> currentStreamTransformer; + + final GanglionTransformer, List> _ganglionTransformer = + new GanglionTransformer, List>.broadcast(); + + final CytonTransformer, List> _cytonTransformer = + new CytonTransformer.broadcast(); + + BaseOpenBCITransformer, List> _currentTransformer; + StreamSubscription _currentTransformerStream; + + StreamingSharedPreferences _preferences; + + EEGDataRepository() { + initialize(); + } + + void initialize() async { + if (_preferences == null) { + _preferences = await StreamingSharedPreferences.instance; + } + + _currentTransformer = + _preferences.getBool('using_bluetooth', defaultValue: false).getValue() + ? _ganglionTransformer + : _cytonTransformer; + } void createRecordingFromStream(Stream> stream) { _recordingData = EEGData(UniqueId.from(DateTime.now().toString()), List()); - if (streamTransformer == null) { - //todo: dynamically change transformer - // new GanglionTransformer, List>.broadcast() - streamTransformer = new CytonTransformer.broadcast(); - stream - .asBroadcastStream() - .transform(streamTransformer) - .listen((data) => _recordingData.values.add(data)); - } else { - streamTransformer.reset(); - } + _currentTransformer.reset(); + _currentTransformerStream = stream + .asBroadcastStream() + .transform(_currentTransformer) + .listen((data) => _recordingData.values.add(data)); } Future stopRecordingFromStream() async { // todo: move save future to another file + _currentTransformerStream.cancel(); final directory = await getExternalStorageDirectory(); final pathOfTheFileToWrite = diff --git a/mobile/lib/src/locator.dart b/mobile/lib/src/locator.dart index 4ee71c88..9b347f04 100644 --- a/mobile/lib/src/locator.dart +++ b/mobile/lib/src/locator.dart @@ -4,16 +4,16 @@ import 'package:polydodo/src/application/device/device_selector_cubit.dart'; import 'package:polydodo/src/application/eeg_data/data_cubit.dart'; 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 'package:polydodo/src/infrastructure/connection_repositories/acquisition_device_repository.dart'; import 'package:polydodo/src/infrastructure/connection_repositories/eeg_data_repository.dart'; -import 'package:polydodo/src/infrastructure/connection_repositories/serial_repository.dart'; /// Private GetIt instance as we want all DI to be performed here in this file final _serviceLocator = GetIt.asNewInstance(); void registerServices() { // todo: dynamically change repository - _serviceLocator - .registerSingleton(SerialRepository()); + _serviceLocator.registerSingleton( + AcquisitionDeviceRepository()); _serviceLocator.registerSingleton(EEGDataRepository()); } @@ -30,15 +30,4 @@ List createBlocProviders() => [ _serviceLocator.get(), ), ), - BlocProvider( - create: (context) => DeviceSelectorCubit( - _serviceLocator.get(), - ), - ), - BlocProvider( - create: (context) => DataCubit( - _serviceLocator.get(), - _serviceLocator.get(), - ), - ), ]; diff --git a/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart b/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart index eaef0c99..97b2eef3 100644 --- a/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart +++ b/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart @@ -52,6 +52,9 @@ class BluetoothSelectorRoute extends StatelessWidget { return Container(); }, ), + floatingActionButton: FloatingActionButton( + onPressed: BlocProvider.of(context).swapBluetooth, + ), ); } } diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 5c26b06c..731ffd89 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -172,6 +172,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" functional_data: dependency: transitive description: @@ -326,6 +331,48 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.5+2" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.12+2" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.2+2" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+10" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2+7" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+1" sky_engine: dependency: transitive description: flutter @@ -352,6 +399,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + streaming_shared_preferences: + dependency: "direct main" + description: + name: streaming_shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" string_scanner: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 10bc9657..b76a979a 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -40,6 +40,7 @@ dependencies: meta: ^1.1.8 path_provider: ^1.6.16 share: ^0.6.5 + streaming_shared_preferences: ^1.0.1 uuid: ^2.2.2 usb_serial: ^0.2.4 From 326db2eb169f45dedfa1fa7b3049fafebafa0aff Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Sun, 18 Oct 2020 16:18:21 -0400 Subject: [PATCH 49/56] Added missing file --- .../acquisition_device_repository.dart | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 mobile/lib/src/infrastructure/connection_repositories/acquisition_device_repository.dart diff --git a/mobile/lib/src/infrastructure/connection_repositories/acquisition_device_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/acquisition_device_repository.dart new file mode 100644 index 00000000..44a52fbd --- /dev/null +++ b/mobile/lib/src/infrastructure/connection_repositories/acquisition_device_repository.dart @@ -0,0 +1,72 @@ +import 'dart:async'; + +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/infrastructure/connection_repositories/bluetooth_repository.dart'; +import 'package:polydodo/src/infrastructure/connection_repositories/serial_repository.dart'; +import 'package:streaming_shared_preferences/streaming_shared_preferences.dart'; + +class AcquisitionDeviceRepository implements IAcquisitionDeviceRepository { + final BluetoothRepository _bluetoothRepository = new BluetoothRepository(); + final SerialRepository _serialRepository = new SerialRepository(); + IAcquisitionDeviceRepository _currentRepository; + + StreamSubscription _bluetoothStream; + StreamSubscription _serialStream; + StreamSubscription _currentStream; + + StreamController> _acquisitionDeviceController; + StreamingSharedPreferences _preferences; + + AcquisitionDeviceRepository() { + _currentRepository = _serialRepository; + _acquisitionDeviceController = new StreamController(); + } + + Future initializeRepository() async { + if (_preferences == null) { + _preferences = await StreamingSharedPreferences.instance; + _preferences + .getBool('using_bluetooth', defaultValue: false) + .listen((usingBluetooth) { + disconnect(); + _currentStream.pause(); + _currentRepository = + usingBluetooth ? _bluetoothRepository : _serialRepository; + _currentRepository.initializeRepository(); + _currentStream = usingBluetooth ? _bluetoothStream : _serialStream; + _currentStream.resume(); + }); + + _serialStream = _serialRepository.watch().listen((event) { + _acquisitionDeviceController.add(event); + }); + _bluetoothStream = _bluetoothRepository.watch().listen((event) { + _acquisitionDeviceController.add(event); + }); + + _currentStream = _bluetoothStream; + _serialStream.pause(); + } + } + + void connect(AcquisitionDevice device, Function(bool, Exception) callback) { + _currentRepository.connect(device, callback); + } + + void disconnect() { + _currentRepository.disconnect(); + } + + Future>> startDataStream() { + return _currentRepository.startDataStream(); + } + + void stopDataStream() { + _currentRepository.stopDataStream(); + } + + Stream> watch() { + return _acquisitionDeviceController.stream; + } +} From 60cdf14a46b2fb1e6293715bfd1b0961809bd43b Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Mon, 19 Oct 2020 15:44:49 -0400 Subject: [PATCH 50/56] Fixed negative handling in cyton transformer and cleaned up a bit --- .../src/application/eeg_data/data_cubit.dart | 1 + .../eeg_data_repository.dart | 4 -- .../cytonTransformer.dart | 36 +++++----- .../ganglionTransformer.dart | 67 +++++++++---------- mobile/pubspec.lock | 10 +-- 5 files changed, 60 insertions(+), 58 deletions(-) diff --git a/mobile/lib/src/application/eeg_data/data_cubit.dart b/mobile/lib/src/application/eeg_data/data_cubit.dart index 3f69dbce..d8b21fd4 100644 --- a/mobile/lib/src/application/eeg_data/data_cubit.dart +++ b/mobile/lib/src/application/eeg_data/data_cubit.dart @@ -14,6 +14,7 @@ class DataCubit extends Cubit { Future startStreaming() async { emit(DataStateRecording()); + _eegDataRepository.initialize(); _eegDataRepository .createRecordingFromStream(await _deviceRepository.startDataStream()); } diff --git a/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart index 68c10cce..784d5f46 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart @@ -28,10 +28,6 @@ class EEGDataRepository implements IEEGDataRepository { StreamingSharedPreferences _preferences; - EEGDataRepository() { - initialize(); - } - void initialize() async { if (_preferences == null) { _preferences = await StreamingSharedPreferences.instance; diff --git a/mobile/lib/src/infrastructure/eeg_data_transformers/cytonTransformer.dart b/mobile/lib/src/infrastructure/eeg_data_transformers/cytonTransformer.dart index f90742bd..4ae758d5 100644 --- a/mobile/lib/src/infrastructure/eeg_data_transformers/cytonTransformer.dart +++ b/mobile/lib/src/infrastructure/eeg_data_transformers/cytonTransformer.dart @@ -4,13 +4,13 @@ import 'package:polydodo/src/infrastructure/constants.dart'; import 'baseOpenBCITransformer.dart'; class CytonTransformer extends BaseOpenBCITransformer { - List packet; + List packet = List(); CytonTransformer.broadcast({bool synchronous: false, cancelOnError}) : super.broadcast(synchronous: synchronous, cancelOnError: cancelOnError); void reset() { - packet = new List(); + packet.clear(); } void onData(S data) { @@ -34,7 +34,9 @@ class CytonTransformer extends BaseOpenBCITransformer { List data = parsePacket(); - data = convertToMicrovolts(data); + packet.clear(); + + data = processData(data, true); controller.add(data); } @@ -54,26 +56,30 @@ class CytonTransformer extends BaseOpenBCITransformer { data[7] = (packet[20] << 16) | (packet[21] << 8) | packet[22]; data[8] = (packet[23] << 16) | (packet[24] << 8) | packet[25]; - packet.clear(); return data; } - List getListForCSV() { - List data = List(CYTON_NUMBER_COLUMNS); + List getListForCSV() => List.generate(CYTON_NUMBER_COLUMNS, (index) => 0); + + List processData(List data, bool hasNegativeCompression) { + List result = List.from(data); + + for (int i = 1; i < CYTON_NUMBER_CHANNELS + 1; ++i) { + if (hasNegativeCompression) result[i] = handleNegative(result[i]); - for (int i = 1; i < CYTON_EXTRA_COLUMNS + 1; ++i) { - data[CYTON_NUMBER_COLUMNS - i] = 0; + result[i] = convertToMicrovolts(result[i]); } - return data; + return result; } - List convertToMicrovolts(List data) { - for (int i = 1; i < CYTON_NUMBER_CHANNELS + 1; ++i) { - // Convert to microvolts using the scale factor - data[i] = data[i].toDouble() * (4500000 / (24 * (pow(2, 23) - 1))); - } + int handleNegative(int i) { + return ((i & 0x00800000) > 0) + ? (i | 0xFFFFFFFFFF000000) + : (i & 0x0000000000FFFFFF); + } - return data; + double convertToMicrovolts(int i) { + return i.toDouble() * (4500000 / (24 * (pow(2, 23) - 1))); } } diff --git a/mobile/lib/src/infrastructure/eeg_data_transformers/ganglionTransformer.dart b/mobile/lib/src/infrastructure/eeg_data_transformers/ganglionTransformer.dart index 8f9ada9f..8785956d 100644 --- a/mobile/lib/src/infrastructure/eeg_data_transformers/ganglionTransformer.dart +++ b/mobile/lib/src/infrastructure/eeg_data_transformers/ganglionTransformer.dart @@ -2,8 +2,8 @@ import 'package:polydodo/src/infrastructure/constants.dart'; import 'baseOpenBCITransformer.dart'; class GanglionTransformer extends BaseOpenBCITransformer { - List _lastSampleData; - int _sampleCounter; + List _lastSampleData = [0, 0, 0, 0, 0]; + int _sampleCounter = 0; GanglionTransformer.broadcast({bool synchronous: false, cancelOnError}) : super.broadcast(synchronous: synchronous, cancelOnError: cancelOnError); @@ -22,12 +22,14 @@ class GanglionTransformer extends BaseOpenBCITransformer { if (packetID == 0) { List data = parseRaw(event); - data = convertToMicrovolts(data, false); + data = processData(data, + nbSamples: 1, hasNegativeCompression: false, isDelta: false); controller.add(data.sublist(0, 15)); } else if (packetID >= 101 && packetID <= 200) { List data = parse19Bit(event); - data = convertToMicrovolts(data, true); + data = processData(data, + nbSamples: 2, hasNegativeCompression: true, isDelta: true); controller.add(data.sublist(0, 15)); controller.add(data.sublist(15, 30)); @@ -47,12 +49,6 @@ class GanglionTransformer extends BaseOpenBCITransformer { } List parse19Bit(event) { - // Test event, comment scale factor - // event = [ 101, 0, 0, 0, 0, 8, 0, 5, 0, 0, 72, 0, 9, 240, 1, 176, 0, 48, 0, 8]; // Positive Test - // Expected [[0, 2, 10, 4], [262148, 507910, 393222, 8]] - // event = [ 101, 255, 255, 191, 255, 239, 255, 252, 255, 255, 88, 0, 11, 62, 56, 224, 0, 63, 240, 1 ]; // Negative Test - // Expected [[-3, -5, -7, -11], [-262139, -198429, -262137, -4095]] - List data = getListForCSV(); data[0] = _sampleCounter; @@ -75,40 +71,43 @@ class GanglionTransformer extends BaseOpenBCITransformer { return data; } - List getListForCSV() { - List data = List(GANGLION_NUMBER_COLUMNS * 2); + List getListForCSV() => + List.generate(GANGLION_NUMBER_COLUMNS * 2, (index) => 0); - for (int i = 1; i < GANGLION_EXTRA_COLUMNS + 1; ++i) { - data[GANGLION_NUMBER_COLUMNS - i] = 0; - data[GANGLION_NUMBER_COLUMNS - i + 15] = 0; - } - return data; - } + List processData(List data, + {int nbSamples, bool hasNegativeCompression, bool isDelta}) { + List result = List.from(data); - List convertToMicrovolts(List data, bool isDelta) { for (int i = 1; i < GANGLION_NUMBER_CHANNELS + 1; ++i) { - for (int j = 0; j < 2; ++j) { - if (j == 1 && !isDelta) break; - + for (int j = 0; j < nbSamples; ++j) { int offset = 15 * j; - String binary = data[i + offset].toRadixString(2); - // Handle negatives - if (isDelta && binary[binary.length - 1] == '1') { - data[i + offset] = (~data[i + offset] & 524287 | 1) * -1; - } + if (hasNegativeCompression) + result[i + offset] = handleNegative(result[i + offset]); - // Convert to microvolts using the scale factor - data[i + offset] = - data[i + offset].toDouble() * (1200000 / (8388607.0 * 1.5 * 51.0)); + result[i + offset] = convertToMicrovolts(result[i + offset]); - // Convert delta - if (isDelta) data[i + offset] = _lastSampleData[i] - data[i + offset]; + if (isDelta) + result[i + offset] = convertDeltaToData(i, result[i + offset]); - _lastSampleData[i] = data[i + offset]; + _lastSampleData[i] = result[i + offset]; } } - return data; + return result; + } + + int handleNegative(int i) { + String binary = i.toRadixString(2); + + return binary[binary.length - 1] == '1' ? (~i & 524287 | 1) * -1 : i; + } + + double convertToMicrovolts(int i) { + return i.toDouble() * (1200000 / (8388607.0 * 1.5 * 51.0)); + } + + double convertDeltaToData(int lastSampleIndex, double i) { + return _lastSampleData[lastSampleIndex] - i; } } diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 731ffd89..98a50879 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -28,7 +28,7 @@ packages: name: battery url: "https://pub.dartlang.org" source: hosted - version: "1.0.6" + version: "1.0.7" battery_platform_interface: dependency: transitive description: @@ -49,7 +49,7 @@ packages: name: bloc url: "https://pub.dartlang.org" source: hosted - version: "6.0.3" + version: "6.1.0" boolean_selector: dependency: transitive description: @@ -166,7 +166,7 @@ packages: name: flutter_reactive_ble url: "https://pub.dartlang.org" source: hosted - version: "2.5.2" + version: "2.5.3" flutter_test: dependency: "direct dev" description: flutter @@ -246,7 +246,7 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.6.18" + version: "1.6.21" path_provider_linux: dependency: transitive description: @@ -330,7 +330,7 @@ packages: name: share url: "https://pub.dartlang.org" source: hosted - version: "0.6.5+2" + version: "0.6.5+3" shared_preferences: dependency: transitive description: From c252b57854c1a7ab648b61e85c1b321bfb75cc02 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Mon, 19 Oct 2020 16:17:07 -0400 Subject: [PATCH 51/56] Added unit testing --- .../cytonTransformer.dart | 22 +-- mobile/test/constants.dart | 158 ++++++++++++++++++ mobile/test/cytonTransformer_test.dart | 26 +++ mobile/test/ganglionTransformer_test.dart | 34 ++++ 4 files changed, 229 insertions(+), 11 deletions(-) create mode 100644 mobile/test/constants.dart create mode 100644 mobile/test/cytonTransformer_test.dart create mode 100644 mobile/test/ganglionTransformer_test.dart diff --git a/mobile/lib/src/infrastructure/eeg_data_transformers/cytonTransformer.dart b/mobile/lib/src/infrastructure/eeg_data_transformers/cytonTransformer.dart index 4ae758d5..e42fc0fb 100644 --- a/mobile/lib/src/infrastructure/eeg_data_transformers/cytonTransformer.dart +++ b/mobile/lib/src/infrastructure/eeg_data_transformers/cytonTransformer.dart @@ -32,7 +32,7 @@ class CytonTransformer extends BaseOpenBCITransformer { continue; } - List data = parsePacket(); + List data = parsePacket(packet); packet.clear(); @@ -43,18 +43,18 @@ class CytonTransformer extends BaseOpenBCITransformer { } } - List parsePacket() { + List parsePacket(List fullPacket) { List data = getListForCSV(); - data[0] = packet[1]; - data[1] = (packet[2] << 16) | (packet[3] << 8) | packet[4]; - data[2] = (packet[5] << 16) | (packet[6] << 8) | packet[7]; - data[3] = (packet[8] << 16) | (packet[9] << 8) | packet[10]; - data[4] = (packet[11] << 16) | (packet[12] << 8) | packet[13]; - data[5] = (packet[14] << 16) | (packet[15] << 8) | packet[16]; - data[6] = (packet[17] << 16) | (packet[18] << 8) | packet[19]; - data[7] = (packet[20] << 16) | (packet[21] << 8) | packet[22]; - data[8] = (packet[23] << 16) | (packet[24] << 8) | packet[25]; + data[0] = fullPacket[1]; + data[1] = (fullPacket[2] << 16) | (fullPacket[3] << 8) | fullPacket[4]; + data[2] = (fullPacket[5] << 16) | (fullPacket[6] << 8) | fullPacket[7]; + data[3] = (fullPacket[8] << 16) | (fullPacket[9] << 8) | fullPacket[10]; + data[4] = (fullPacket[11] << 16) | (fullPacket[12] << 8) | fullPacket[13]; + data[5] = (fullPacket[14] << 16) | (fullPacket[15] << 8) | fullPacket[16]; + data[6] = (fullPacket[17] << 16) | (fullPacket[18] << 8) | fullPacket[19]; + data[7] = (fullPacket[20] << 16) | (fullPacket[21] << 8) | fullPacket[22]; + data[8] = (fullPacket[23] << 16) | (fullPacket[24] << 8) | fullPacket[25]; return data; } diff --git a/mobile/test/constants.dart b/mobile/test/constants.dart new file mode 100644 index 00000000..85e57ec8 --- /dev/null +++ b/mobile/test/constants.dart @@ -0,0 +1,158 @@ +// Ganglion + +const ganglion_compressed_packet = [ + 101, + 0, + 0, + 0, + 0, + 8, + 0, + 5, + 0, + 0, + 72, + 0, + 9, + 240, + 1, + 176, + 0, + 48, + 0, + 8 +]; + +const ganglion_expected_compressed_1 = [0, 2, 10, 4]; +const ganglion_expected_compressed_2 = [262148, 507910, 393222, 8]; + +const ganglion_negative_packet = [ + 101, + 255, + 255, + 191, + 255, + 239, + 255, + 252, + 255, + 255, + 88, + 0, + 11, + 62, + 56, + 224, + 0, + 63, + 240, + 1 +]; + +const ganglion_expected_negative_1 = [-3, -5, -7, -11]; +const ganglion_expected_negative_2 = [-262139, -198429, -262137, -4095]; + +const raw_packet = [ + 0, + 4, + 0, + 4, + 7, + 192, + 6, + 6, + 0, + 6, + 0, + 0, + 8, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 +]; + +const expected_raw = [262148, 507910, 393222, 8]; + +// Cyton + +const cyton_packet = [ + 160, + 243, + 250, + 250, + 2, + 250, + 248, + 164, + 251, + 6, + 30, + 250, + 211, + 205, + 156, + 60, + 249, + 156, + 190, + 128, + 154, + 202, + 100, + 176, + 224, + 132, + 0, + 0, + 0, + 0, + 0, + 0, + 192 +]; + +const cyton_expected = [16448002, 16447652, 16451102, 16438221]; + +const cyton_negative_packet = [ + 160, + 243, + 0x7F, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x80, + 0x00, + 0x01, + 0x5D, + 0xCB, + 0xED, + 0xA2, + 0x34, + 0x13, + 156, + 190, + 128, + 154, + 202, + 100, + 176, + 0, + 0, + 0, + 0, + 0, + 0, + 192 +]; + +const cyton_expected_negative = [8388607, -1, -8388607, 6147053]; diff --git a/mobile/test/cytonTransformer_test.dart b/mobile/test/cytonTransformer_test.dart new file mode 100644 index 00000000..9f76cf58 --- /dev/null +++ b/mobile/test/cytonTransformer_test.dart @@ -0,0 +1,26 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:polydodo/src/infrastructure/eeg_data_transformers/cytonTransformer.dart'; + +import 'constants.dart'; + +void main() { + test('Parse Packet', () { + final transformer = CytonTransformer.broadcast(); + + var result = transformer.parsePacket(cyton_packet); + + expect(result.sublist(1, 5), cyton_expected); + }); + + test('Process Data', () { + final transformer = CytonTransformer.broadcast(); + + var result = transformer.parsePacket(cyton_negative_packet); + result = transformer.processData(result, true); + + var expected = + cyton_expected_negative.map((e) => transformer.convertToMicrovolts(e)); + + expect(result.sublist(1, 5), expected); + }); +} diff --git a/mobile/test/ganglionTransformer_test.dart b/mobile/test/ganglionTransformer_test.dart new file mode 100644 index 00000000..fc26740e --- /dev/null +++ b/mobile/test/ganglionTransformer_test.dart @@ -0,0 +1,34 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:polydodo/src/infrastructure/eeg_data_transformers/ganglionTransformer.dart'; +import 'constants.dart'; + +void main() { + final transformer = GanglionTransformer.broadcast(); + test('Parse 19 bit packet', () { + var result = transformer.parse19Bit(ganglion_compressed_packet); + + expect(result.sublist(1, 5), ganglion_expected_compressed_1); + expect(result.sublist(16, 20), ganglion_expected_compressed_2); + }); + + test('Parse 19 bit packet - Negative', () { + var expected_1 = ganglion_expected_negative_1 + .map((e) => transformer.convertToMicrovolts(e)); + + var expected_2 = ganglion_expected_negative_2 + .map((e) => transformer.convertToMicrovolts(e)); + + var result = transformer.parse19Bit(ganglion_negative_packet); + + result = transformer.processData(result, + nbSamples: 2, hasNegativeCompression: true, isDelta: false); + + expect(result.sublist(1, 5), expected_1); + expect(result.sublist(16, 20), expected_2); + }); + + test('Parse raw', () { + var result = transformer.parseRaw(raw_packet); + expect(result.sublist(1, 5), expected_raw); + }); +} From 254e6478d4d69b7b3a39c3740fadea004146b5ed Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Mon, 19 Oct 2020 18:50:21 -0400 Subject: [PATCH 52/56] Awaiting stream in serial --- .../connection_repositories/serial_repository.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart index 0d032aa8..8715e355 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart @@ -56,7 +56,7 @@ class SerialRepository implements IAcquisitionDeviceRepository { } Future>> startDataStream() async { - _serialPort.write(Uint8List.fromList(START_STREAM_CHAR.codeUnits)); + await _serialPort.write(Uint8List.fromList(START_STREAM_CHAR.codeUnits)); return _serialPort.inputStream; } From 6625807608c8179500130418d7690ade073549fa Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Sun, 25 Oct 2020 15:51:50 -0400 Subject: [PATCH 53/56] Added status check for Cyton --- .../serial_repository.dart | 31 ++++++++++++++++++- mobile/lib/src/infrastructure/constants.dart | 3 ++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart index 8715e355..bf8f8e7d 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart @@ -12,6 +12,7 @@ class SerialRepository implements IAcquisitionDeviceRepository { UsbPort _serialPort; List _acquisitionDevicePersistency = []; List _serialDevices = []; + StreamSubscription _inputStreamSubscription; final streamController = StreamController>(); void initializeRepository() { @@ -46,11 +47,39 @@ class SerialRepository implements IAcquisitionDeviceRepository { _serialPort.setPortParameters( 115200, UsbPort.DATABITS_8, UsbPort.STOPBITS_1, UsbPort.PARITY_NONE); - callback(true, null); + checkCytonConnection(callback); + } + + Future checkCytonConnection(Function(bool, Exception) callback) async { + String status = ""; + + _inputStreamSubscription = _serialPort.inputStream.listen((event) { + status += String.fromCharCodes(event); + + if (isFullMessage(status)) { + _inputStreamSubscription.cancel(); + + if (status == CYTON_SYSTEM_UP) + callback(true, null); + else { + disconnect(); + callback(false, Exception(status)); + } + } + }); + + await _serialPort.write(Uint8List.fromList(CYTON_GET_STATUS)); + } + + bool isFullMessage(String message) { + return message.length > CYTON_MESSAGE_FOOTER.length && + message.substring(message.length - CYTON_MESSAGE_FOOTER.length) == + CYTON_MESSAGE_FOOTER; } Future disconnect() async { await _serialPort?.close(); + _inputStreamSubscription = null; _selectedDevice = null; _serialPort = null; } diff --git a/mobile/lib/src/infrastructure/constants.dart b/mobile/lib/src/infrastructure/constants.dart index dd30eddf..73d90293 100644 --- a/mobile/lib/src/infrastructure/constants.dart +++ b/mobile/lib/src/infrastructure/constants.dart @@ -29,6 +29,9 @@ const OPEN_BCI_GANGLION_HEADER = [ ] ]; +const CYTON_MESSAGE_FOOTER = "\$\$\$"; +const CYTON_GET_STATUS = [0xF0, 0x07]; +const CYTON_SYSTEM_UP = "Success: System is Up\$\$\$"; const CYTON_NUMBER_CHANNELS = 8; const CYTON_PACKET_SIZE = 33; const CYTON_NUMBER_COLUMNS = 24; From ff5022cdcce2aec87a1be906c8a51fa46b297405 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Mon, 26 Oct 2020 17:38:56 -0400 Subject: [PATCH 54/56] Made exception parameter optional --- .../src/application/device/device_selector_cubit.dart | 2 +- .../i_acquisition_device_repository.dart | 2 +- .../connection_repositories/bluetooth_repository.dart | 4 ++-- .../connection_repositories/serial_repository.dart | 9 +++++---- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/mobile/lib/src/application/device/device_selector_cubit.dart b/mobile/lib/src/application/device/device_selector_cubit.dart index 8db63920..7479a403 100644 --- a/mobile/lib/src/application/device/device_selector_cubit.dart +++ b/mobile/lib/src/application/device/device_selector_cubit.dart @@ -36,7 +36,7 @@ class DeviceSelectorCubit extends Cubit { _deviceRepository.connect(device, connectionCallback); } - void connectionCallback(bool connected, Exception e) { + void connectionCallback(bool connected, [Exception e]) { if (e != null) { emit(DeviceConnectionFailure(e)); resetSearch(); diff --git a/mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart b/mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart index 4c6148a7..f0b569e8 100644 --- a/mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart +++ b/mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart @@ -3,7 +3,7 @@ import 'acquisition_device.dart'; abstract class IAcquisitionDeviceRepository { void initializeRepository(); - void connect(AcquisitionDevice device, Function(bool, Exception) callback); + void connect(AcquisitionDevice device, Function(bool, [Exception]) callback); void disconnect(); Future>> startDataStream(); diff --git a/mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart index b9a2500e..ac7b1b0b 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart @@ -49,7 +49,7 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { } void connect( - AcquisitionDevice device, Function(bool, Exception) callback) async { + AcquisitionDevice device, Function(bool, [Exception]) callback) async { _selectedDevice = device; _acquisitionDevicePersistency.clear(); _bluetoothScanSubscription.pause(); @@ -61,7 +61,7 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { .listen((event) { if (event.connectionState == DeviceConnectionState.connected) { setupCharacteristics(); - callback(true, null); + callback(true); } else if (event.connectionState == DeviceConnectionState.disconnected) { disconnect(); callback(false, Exception("Failed to connect to device")); diff --git a/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart index bf8f8e7d..2ceb876d 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart @@ -47,10 +47,11 @@ class SerialRepository implements IAcquisitionDeviceRepository { _serialPort.setPortParameters( 115200, UsbPort.DATABITS_8, UsbPort.STOPBITS_1, UsbPort.PARITY_NONE); - checkCytonConnection(callback); + _checkCytonConnection(callback); } - Future checkCytonConnection(Function(bool, Exception) callback) async { + Future _checkCytonConnection( + Function(bool, [Exception]) callback) async { String status = ""; _inputStreamSubscription = _serialPort.inputStream.listen((event) { @@ -60,7 +61,7 @@ class SerialRepository implements IAcquisitionDeviceRepository { _inputStreamSubscription.cancel(); if (status == CYTON_SYSTEM_UP) - callback(true, null); + callback(true); else { disconnect(); callback(false, Exception(status)); @@ -79,7 +80,7 @@ class SerialRepository implements IAcquisitionDeviceRepository { Future disconnect() async { await _serialPort?.close(); - _inputStreamSubscription = null; + _inputStreamSubscription?.cancel(); _selectedDevice = null; _serialPort = null; } From 9b8c10f5b57446b13c0f0e997b50c67b236e22ac Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Mon, 26 Oct 2020 17:40:45 -0400 Subject: [PATCH 55/56] Using implicit variable type instead of string --- .../connection_repositories/serial_repository.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart index 2ceb876d..31a488af 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart @@ -52,7 +52,7 @@ class SerialRepository implements IAcquisitionDeviceRepository { Future _checkCytonConnection( Function(bool, [Exception]) callback) async { - String status = ""; + var status = ""; _inputStreamSubscription = _serialPort.inputStream.listen((event) { status += String.fromCharCodes(event); From 0f8669cc2d1198e19d50b6fc90403311bf770b82 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Tue, 27 Oct 2020 12:06:26 -0400 Subject: [PATCH 56/56] Added overrides for interface implemented methods --- .../connection_repositories/bluetooth_repository.dart | 5 +++++ .../connection_repositories/serial_repository.dart | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart index ac7b1b0b..0ee14865 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/bluetooth_repository.dart @@ -21,6 +21,7 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { List _acquisitionDevicePersistency = []; final streamController = StreamController>(); + @override void initializeRepository() { if (_bluetoothScanSubscription == null) { flutterReactiveBle = FlutterReactiveBle(); @@ -48,6 +49,7 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { streamController.add(_acquisitionDevicePersistency); } + @override void connect( AcquisitionDevice device, Function(bool, [Exception]) callback) async { _selectedDevice = device; @@ -69,6 +71,7 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { }); } + @override void disconnect() async { _selectedDevice = null; _connectedDeviceStream?.cancel(); @@ -86,6 +89,7 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { deviceId: _selectedDevice.id.toString()); } + @override Future>> startDataStream() async { await flutterReactiveBle.requestConnectionPriority( deviceId: _selectedDevice.id.toString(), @@ -97,6 +101,7 @@ class BluetoothRepository implements IAcquisitionDeviceRepository { return flutterReactiveBle.subscribeToCharacteristic(_receiveCharacteristic); } + @override Future stopDataStream() async { await flutterReactiveBle.requestConnectionPriority( deviceId: _selectedDevice.id.toString(), diff --git a/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart index 31a488af..091831d7 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/serial_repository.dart @@ -15,6 +15,7 @@ class SerialRepository implements IAcquisitionDeviceRepository { StreamSubscription _inputStreamSubscription; final streamController = StreamController>(); + @override void initializeRepository() { _acquisitionDevicePersistency.clear(); _serialDevices.clear(); @@ -33,6 +34,7 @@ class SerialRepository implements IAcquisitionDeviceRepository { streamController.add(_acquisitionDevicePersistency); } + @override Future connect( AcquisitionDevice device, Function(bool, Exception) callback) async { _selectedDevice = @@ -78,6 +80,7 @@ class SerialRepository implements IAcquisitionDeviceRepository { CYTON_MESSAGE_FOOTER; } + @override Future disconnect() async { await _serialPort?.close(); _inputStreamSubscription?.cancel(); @@ -85,15 +88,18 @@ class SerialRepository implements IAcquisitionDeviceRepository { _serialPort = null; } + @override Future>> startDataStream() async { await _serialPort.write(Uint8List.fromList(START_STREAM_CHAR.codeUnits)); return _serialPort.inputStream; } + @override Future stopDataStream() async { await _serialPort.write(Uint8List.fromList(STOP_STREAM_CHAR.codeUnits)); } + @override Stream> watch() => streamController.stream; }