diff --git a/lib/connection/ble/bluetooth_client.dart b/lib/connection/ble/bluetooth_client.dart deleted file mode 100644 index 10796fb..0000000 --- a/lib/connection/ble/bluetooth_client.dart +++ /dev/null @@ -1,19 +0,0 @@ - -import 'package:flutter_blue/flutter_blue.dart'; - -/// Wrapper for the bluetooth class. -class BluetoothWrapper { - const BluetoothWrapper(); - - Future scanGlove(String uuid) async { - List devices = await FlutterBlue.instance.connectedDevices; - for (BluetoothDevice device in devices) { - if (device.id.toString() == uuid) { - return device; - } - } - return null; - } - - -} diff --git a/lib/datacollection/file_content.dart b/lib/datacollection/file_content.dart deleted file mode 100644 index 06874da..0000000 --- a/lib/datacollection/file_content.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:lsa_gloves/datacollection/storage.dart'; -import 'package:path/path.dart'; - -class FileContentPage extends StatelessWidget { - static const routeName = '/fileContent'; - - @override - Widget build(BuildContext context) { - final file = ModalRoute.of(context)!.settings.arguments as DeviceMeasurementsFile; - - return Scaffold( - appBar: AppBar( - title: Text(basename(file.path)), - ), - body: Center( - child: FileContentWidget(mFile: file) - )); - } -} -/// This is the stateful widget that the main application instantiates. -class FileContentWidget extends StatefulWidget { - final DeviceMeasurementsFile mFile; - const FileContentWidget({Key? key, required this.mFile}) : super(key: key); - - @override - State createState() => _FileContentWidget(mFile); -} - -/// This is the private State class that goes with MyStatefulWidget. -class _FileContentWidget extends State { - final DeviceMeasurementsFile mFile; - _FileContentWidget(this.mFile); - - @override - Widget build(BuildContext context) { - return DefaultTextStyle( - style: Theme.of(context).textTheme.bodyText1!, - textAlign: TextAlign.center, - child: FutureBuilder( - future: mFile.readJsonContent().then((value) => value.toJson().toString()), // a previously-obtained Future or null - builder: (BuildContext context, AsyncSnapshot snapshot) { - List children; - if (snapshot.hasData) { - children = [ - Padding( - padding: const EdgeInsets.only(top: 16), - child: Text('${snapshot.data!}', - style: Theme.of(context).textTheme.bodyText1!), - ) - ]; - } else if (snapshot.hasError) { - children = [ - const Icon( - Icons.error_outline, - color: Colors.red, - size: 60, - ), - Padding( - padding: const EdgeInsets.only(top: 16), - child: Text('Error: ${snapshot.error}', - style: Theme.of(context).textTheme.headline2!), - ) - ]; - } else { - children = [ - SizedBox( - child: CircularProgressIndicator(), - width: 60, - height: 60, - ), - Padding( - padding: EdgeInsets.only(top: 11), - child: Text('Awaiting result...', - style: Theme.of(context).textTheme.headline2!), - ) - ]; - } - return Center( - child: new SingleChildScrollView( - scrollDirection: Axis.vertical,//.horizontal - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: children, - )), - ); - }, - ), - ); - } -} - diff --git a/lib/datacollection/storage.dart b/lib/datacollection/storage.dart index b8de144..ee3e4e2 100644 --- a/lib/datacollection/storage.dart +++ b/lib/datacollection/storage.dart @@ -1,20 +1,22 @@ import 'dart:async'; import 'dart:io'; import 'dart:convert'; +import 'package:lsa_gloves/model/finger.dart'; import 'package:lsa_gloves/model/glove_measurement.dart'; import 'package:lsa_gloves/edgeimpulse/api_client.dart'; import 'package:path_provider/path_provider.dart'; import 'dart:developer' as developer; -class GloveEventsStorage { - static const String TAG = "GloveEventsStorage"; - static final GloveEventsStorage _singleton = GloveEventsStorage._internal(); +/// Class to manage the stored files with sensors measurements from the gloves. +class FileManager { + static const String TAG = "FileManager"; + static final FileManager _singleton = FileManager._internal(); - factory GloveEventsStorage() { + factory FileManager() { return _singleton; } - GloveEventsStorage._internal(); + FileManager._internal(); Future get _localPath async { final directory = await getExternalStorageDirectory(); @@ -55,11 +57,12 @@ class GloveEventsStorage { } } +/// Class to handle the storage of the received measurements in a json file. class DeviceMeasurementsFile { static const TAG = "DeviceMeasurementsFile"; final File file; final DateTime lastModificationDate; - SensorMeasurements? fileContent; + BufferedSensorMeasurements? fileContent; String get path => file.path; @@ -71,11 +74,11 @@ class DeviceMeasurementsFile { static Future create( String deviceName, String deviceId, String word) async { var creationDate = DateTime.now(); - SensorMeasurements json = new SensorMeasurements( + BufferedSensorMeasurements json = new BufferedSensorMeasurements( deviceName, deviceId, word, >[], []); String datetimeStr = format(creationDate); var filename = "${deviceName.substring(0, 1)}_${word}_$datetimeStr"; - var file = await new GloveEventsStorage().createFile(filename); + var file = await new FileManager().createFile(filename); return DeviceMeasurementsFile._(file, creationDate, json); } @@ -92,7 +95,6 @@ class DeviceMeasurementsFile { Future save() async { try { - //TODO proteger concunrrencia, mutex?? String json = jsonEncode(this.fileContent); developer.log("saving $json"); await this.file.writeAsString(json); @@ -114,7 +116,6 @@ class DeviceMeasurementsFile { Future _readAllAsString() async { try { - //TODO proteger concunrrencia, mutex?? final contents = await file.readAsString(); return contents; } catch (e) { @@ -123,14 +124,14 @@ class DeviceMeasurementsFile { } } - Future readJsonContent() async { + Future readJsonContent() async { String fileContent = await _readAllAsString(); - return SensorMeasurements.fromJson(json.decode(fileContent)); + return BufferedSensorMeasurements.fromJson(json.decode(fileContent)); } Future upload() async { try { - SensorMeasurements measurementsJson = await readJsonContent(); + BufferedSensorMeasurements measurementsJson = await readJsonContent(); return EdgeImpulseApiClient.uploadFile( measurementsJson, lastModificationDate); }catch(e, stacktrace) { @@ -150,14 +151,15 @@ class DeviceMeasurementsFile { } } -class SensorMeasurements { +/// Class to buffer the incoming sensor measurements in an internal list. +class BufferedSensorMeasurements { final String deviceName; final String deviceId; final String word; final List timestamps; final List> values; - SensorMeasurements(this.deviceName, this.deviceId, this.word,this.values, + BufferedSensorMeasurements(this.deviceName, this.deviceId, this.word,this.values, this.timestamps); bool add(GloveMeasurement gloveMeasurement) { @@ -188,7 +190,7 @@ class SensorMeasurements { return measurementList; } - factory SensorMeasurements.fromJson(dynamic json) { + factory BufferedSensorMeasurements.fromJson(dynamic json) { List> _values = >[]; List _timestamps = []; @@ -203,7 +205,7 @@ class SensorMeasurements { var jsonLists = json['timestamps'] as List; _timestamps = jsonLists.map((v) => v as int).toList(); } - return SensorMeasurements( + return BufferedSensorMeasurements( json['device_name'] as String, json['device_id'] as String, json['word'] as String, diff --git a/lib/edgeimpulse/api_client.dart b/lib/edgeimpulse/api_client.dart index bf669d1..10cf81e 100644 --- a/lib/edgeimpulse/api_client.dart +++ b/lib/edgeimpulse/api_client.dart @@ -11,7 +11,7 @@ class EdgeImpulseApiClient { static const int OK_STATUS_CODE = 200; static Future uploadFile( - SensorMeasurements sensorMeasurements, DateTime datetime) async { + BufferedSensorMeasurements sensorMeasurements, DateTime datetime) async { var fileName = sensorMeasurements.word + '-' + datetime.toString(); var protected = Protected( diff --git a/lib/main.dart b/lib/main.dart index 80b2d46..1e40267 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:lsa_gloves/datacollection/file_content.dart'; -import 'package:lsa_gloves/pages/ble_connection_error_page.dart'; import 'package:lsa_gloves/pages/ble_devices_connection_page.dart'; import 'package:lsa_gloves/pages/file_content_chart_page.dart'; import 'package:lsa_gloves/pages/home_page.dart'; @@ -24,10 +22,8 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( routes: { - FileContentPage.routeName: (context) => FileContentPage(), FileContentChartPage.routeName: (context) => FileContentChartPage(), BleGloveConnectionPage.routeName: (context) => BleGloveConnectionPage(), - BleConnectionErrorPage.routeName: (context) => BleConnectionErrorPage(), }, title: 'Lengua de señas Argentina', theme: ThemeData( diff --git a/lib/model/acceleration.dart b/lib/model/acceleration.dart new file mode 100644 index 0000000..59ff4e4 --- /dev/null +++ b/lib/model/acceleration.dart @@ -0,0 +1,29 @@ + +import 'package:lsa_gloves/model/vector3.dart'; + +/// Class to encapsulate the acceleration values received from the glove. +class Acceleration with Vector3 { + final double x; + final double y; + final double z; + + Acceleration(this.x, this.y, this.z); + + Acceleration.fromJson(Map json) + : x = json['x'], y = json['y'], z = json['z']; + + Map toJson() => { + 'x': x, + 'y': y, + 'z': z, + }; + + @override + double getX() => x; + + @override + double getY() => y; + + @override + double getZ() => z; +} \ No newline at end of file diff --git a/lib/model/finger.dart b/lib/model/finger.dart new file mode 100644 index 0000000..768a70e --- /dev/null +++ b/lib/model/finger.dart @@ -0,0 +1,59 @@ +import 'sensor_value.dart'; +import 'vector3.dart'; +import 'acceleration.dart'; +import 'gyro.dart'; + +/// Enumeration for each of the hand's fingers. +enum FingerValue { + Pinky, + Ring, + Middle, + Index, + Thumb +} + +extension FingerValueTranslation on FingerValue { + String spanishName() { + switch (this) { + case FingerValue.Pinky: + return "Meñique"; + case FingerValue.Ring: + return "Anular"; + case FingerValue.Middle: + return "Medio"; + case FingerValue.Index: + return "Índice"; + case FingerValue.Thumb: + return "Pulgar"; + } + } +} + +/// Class to handle the sensor measurements from a finger's mpu6050. +class Finger { + final Acceleration acc; + final Gyro gyro; + + Finger(this.acc, this.gyro); + + Finger.fromJson(Map json) + : acc = Acceleration.fromJson(json['acc'] as Map), + gyro = Gyro.fromJson(json['gyro'] as Map); + Map toJson() => { + 'acc': acc.toJson(), + 'gyro': gyro.toJson(), + }; + + Finger.fromList(List m): + acc = Acceleration(m[0],m[1], m[2]), + gyro = Gyro(m[3],m[4], m[5]); + + Vector3 getSensorValues(SensorValue sensorName) { + switch (sensorName) { + case SensorValue.Acceleration: + return acc; + case SensorValue.Gyroscope: + return gyro; + } + } +} \ No newline at end of file diff --git a/lib/model/glove_measurement.dart b/lib/model/glove_measurement.dart index 9f2bc27..525269a 100644 --- a/lib/model/glove_measurement.dart +++ b/lib/model/glove_measurement.dart @@ -1,29 +1,6 @@ +import 'finger.dart'; -enum FingerValue { - Pinky, - Ring, - Middle, - Index, - Thumb -} - -extension FingerValueTranslation on FingerValue { - String spanishName() { - switch (this) { - case FingerValue.Pinky: - return "Meñique"; - case FingerValue.Ring: - return "Anular"; - case FingerValue.Middle: - return "Medio"; - case FingerValue.Index: - return "Índice"; - case FingerValue.Thumb: - return "Pulgar"; - } - } -} - +/// Class to represent a glove measurement. class GloveMeasurement { static const int measurementsNumber = 6; static const String pinkyLetter = "P"; @@ -99,129 +76,4 @@ class GloveMeasurement { } } -enum SensorValue { - Acceleration, - Gyroscope -} - -extension SensorValueExtension on SensorValue { - String spanishName() { - switch (this) { - case SensorValue.Acceleration: - return "Acelerómetro"; - case SensorValue.Gyroscope: - return "Giroscopio"; - } - } - - String getXLabel() { - switch (this) { - case SensorValue.Acceleration: - return "x (m/s²)"; - case SensorValue.Gyroscope: - return "x (º/s)"; - } - } - String getYLabel() { - switch (this) { - case SensorValue.Acceleration: - return "y (m/s²)"; - case SensorValue.Gyroscope: - return "y (º/s)"; - } - } - String getZLabel() { - switch (this) { - case SensorValue.Acceleration: - return "z (m/s²)"; - case SensorValue.Gyroscope: - return "z (º/s)"; - } - } -} - -class Finger { - final Acceleration acc; - final Gyro gyro; - - Finger(this.acc, this.gyro); - - Finger.fromJson(Map json) - : acc = Acceleration.fromJson(json['acc'] as Map), - gyro = Gyro.fromJson(json['gyro'] as Map); - Map toJson() => { - 'acc': acc.toJson(), - 'gyro': gyro.toJson(), - }; - - Finger.fromList(List m): - acc = Acceleration(m[0],m[1], m[2]), - gyro = Gyro(m[3],m[4], m[5]); - - Vector3 getSensorValues(SensorValue sensorName) { - switch (sensorName) { - case SensorValue.Acceleration: - return acc; - case SensorValue.Gyroscope: - return gyro; - } - } -} - -class Acceleration with Vector3 { - final double x; - final double y; - final double z; - - Acceleration(this.x, this.y, this.z); - - Acceleration.fromJson(Map json) - : x = json['x'], y = json['y'], z = json['z']; - - Map toJson() => { - 'x': x, - 'y': y, - 'z': z, - }; - - @override - double getX() => x; - - @override - double getY() => y; - - @override - double getZ() => z; -} - -class Gyro with Vector3 { - final double x; - final double y; - final double z; - Gyro (this.x, this.y, this.z); - - Gyro .fromJson(Map json) - : x = json['x'], y = json['y'], z = json['z']; - - Map toJson() => { - 'x': x, - 'y': y, - 'z': z, - }; - - @override - double getX() => x; - - @override - double getY() => y; - - @override - double getZ() => z; -} - -abstract class Vector3 { - double getX(); - double getY(); - double getZ(); -} diff --git a/lib/model/gyro.dart b/lib/model/gyro.dart new file mode 100644 index 0000000..b4d81c4 --- /dev/null +++ b/lib/model/gyro.dart @@ -0,0 +1,28 @@ + +import 'package:lsa_gloves/model/vector3.dart'; + +/// Class to encapsulate the gyroscope values received from the glove. +class Gyro with Vector3 { + final double x; + final double y; + final double z; + Gyro (this.x, this.y, this.z); + + Gyro .fromJson(Map json) + : x = json['x'], y = json['y'], z = json['z']; + + Map toJson() => { + 'x': x, + 'y': y, + 'z': z, + }; + + @override + double getX() => x; + + @override + double getY() => y; + + @override + double getZ() => z; +} \ No newline at end of file diff --git a/lib/model/sensor_value.dart b/lib/model/sensor_value.dart new file mode 100644 index 0000000..fc8f40b --- /dev/null +++ b/lib/model/sensor_value.dart @@ -0,0 +1,40 @@ +enum SensorValue { + Acceleration, + Gyroscope +} + +extension SensorValueExtension on SensorValue { + String spanishName() { + switch (this) { + case SensorValue.Acceleration: + return "Acelerómetro"; + case SensorValue.Gyroscope: + return "Giroscopio"; + } + } + + String getXLabel() { + switch (this) { + case SensorValue.Acceleration: + return "x (m/s²)"; + case SensorValue.Gyroscope: + return "x (º/s)"; + } + } + String getYLabel() { + switch (this) { + case SensorValue.Acceleration: + return "y (m/s²)"; + case SensorValue.Gyroscope: + return "y (º/s)"; + } + } + String getZLabel() { + switch (this) { + case SensorValue.Acceleration: + return "z (m/s²)"; + case SensorValue.Gyroscope: + return "z (º/s)"; + } + } +} \ No newline at end of file diff --git a/lib/model/vector3.dart b/lib/model/vector3.dart new file mode 100644 index 0000000..7287c75 --- /dev/null +++ b/lib/model/vector3.dart @@ -0,0 +1,6 @@ +/// Helper class to contain the 3 axis values of the mpu6050 sensors. +abstract class Vector3 { + double getX(); + double getY(); + double getZ(); +} \ No newline at end of file diff --git a/lib/pages/about_page.dart b/lib/pages/about_page.dart index f32fbc5..3e891dc 100644 --- a/lib/pages/about_page.dart +++ b/lib/pages/about_page.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:lsa_gloves/navigation/navigation_drawer.dart'; +/// Page containing a brief description of the application and the project per +/// se, as well as contact information. class AboutPage extends StatefulWidget { const AboutPage({Key? key}) : super(key: key); diff --git a/lib/pages/ble_connection_error_page.dart b/lib/pages/ble_connection_error_page.dart deleted file mode 100644 index 24e93ac..0000000 --- a/lib/pages/ble_connection_error_page.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; - -import 'ble_devices_connection_page.dart'; - -class BleConnectionErrorPage extends StatelessWidget { - static const routeName = '/bleConnectionError'; - @override - Widget build(BuildContext context) { - Size size = MediaQuery.of(context).size; - return Scaffold( - appBar: AppBar( - title: Text('Error!'), - ), - body: Container( - height: double.maxFinite, - child: new Stack( - //alignment:new Alignment(x, y) - children: [ - new Positioned( - child: Center( - child: Wrap( - direction: Axis.vertical, - alignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, - spacing: size.height * 0.03, - children: [ - Icon(Icons.error, - size: 100, color: Theme.of(context).errorColor), - Text("Se perdió la conexión bluetooth!", - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .subtitle1! - .fontSize, - color: Theme.of(context).errorColor)), - ]), - ), - ), - new Positioned( - child: new Align( - alignment: FractionalOffset.bottomCenter, - child: Container( - height: 50.0, - margin: EdgeInsets.all(10), - child: ElevatedButton( - onPressed: () => Navigator.pushNamedAndRemoveUntil( - context, BleGloveConnectionPage.routeName, (_) => false), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Volver a conectar', - style: TextStyle( - color: Theme.of(context).secondaryHeaderColor, - ), - ), - Icon( - Icons.arrow_right, - size: Theme.of(context).buttonTheme.height , - color: Theme.of(context).secondaryHeaderColor, - ) - ]), - style: ElevatedButton.styleFrom( - shape: StadiumBorder(), - minimumSize: Size(double.infinity, 30)), - ))), - ) - ], - ), - )); - } -} diff --git a/lib/pages/ble_data_collection_page.dart b/lib/pages/ble_data_collection_page.dart index 11f0fe5..5ccea25 100644 --- a/lib/pages/ble_data_collection_page.dart +++ b/lib/pages/ble_data_collection_page.dart @@ -1,18 +1,16 @@ import 'dart:async'; import 'package:animated_text_kit/animated_text_kit.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:lsa_gloves/connection/ble/bluetooth_backend.dart'; import 'package:lsa_gloves/datacollection/measurements_collector.dart'; import 'package:lsa_gloves/datacollection/measurements_listener.dart'; import 'package:lsa_gloves/model/glove_measurement.dart'; -import 'package:lsa_gloves/pages/ble_connection_error_page.dart'; import 'package:provider/provider.dart'; import 'package:simple_timer/simple_timer.dart'; import 'dart:developer' as developer; +/// Page to run data collections. class BleDataCollectionPage extends StatefulWidget { const BleDataCollectionPage({Key? key}) : super(key: key); @@ -159,17 +157,10 @@ class _BleDataCollectionState extends State void _startRecording(BluetoothBackend bluetoothBackend) { developer.log('startRecording'); - if (bluetoothBackend.connectedDevices.isEmpty) { - developer.log('Cant start recording! No devices connected.'); - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => BleConnectionErrorPage(), - maintainState: false)); - } else { bluetoothBackend.sendStartDataCollectionCommand(); _measurementsCollector.startCollecting( this.selectedGesture, bluetoothBackend.dataCollectionCharacteristics); _isRecording = true; - } } void _stopRecording(BluetoothBackend bluetoothBackend) async { diff --git a/lib/pages/ble_devices_connection_page.dart b/lib/pages/ble_devices_connection_page.dart index 9411343..210116b 100644 --- a/lib/pages/ble_devices_connection_page.dart +++ b/lib/pages/ble_devices_connection_page.dart @@ -1,15 +1,13 @@ -import 'dart:async'; - -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_blue/flutter_blue.dart'; import 'package:lsa_gloves/connection/ble/bluetooth_backend.dart'; import 'package:lsa_gloves/connection/ble/bluetooth_specification.dart'; import 'package:lsa_gloves/navigation/navigation_drawer.dart'; import 'package:provider/provider.dart'; -import '../connection/ble/bluetooth_device.dart'; +import 'bluetooth_device_page.dart'; import 'dart:developer' as developer; +/// Page to let the user connect the app to one or more devices. class BleGloveConnectionPage extends StatelessWidget { static const routeName = '/bleGloveConnectionPage'; @@ -23,13 +21,13 @@ class BleGloveConnectionPage extends StatelessWidget { if (state == BluetoothState.on) { return FindDevicesScreen(); } - return BluetoothOffScreen(state: state); + return BluetoothDisabledWarningPage(state: state); }); } } -class BluetoothOffScreen extends StatelessWidget { - const BluetoothOffScreen({Key? key, this.state}) : super(key: key); +class BluetoothDisabledWarningPage extends StatelessWidget { + const BluetoothDisabledWarningPage({Key? key, this.state}) : super(key: key); final BluetoothState? state; @@ -220,7 +218,7 @@ class _ConnectionGloveCard extends State { void navigateToDeviceSettings(bool isConnected) { Navigator.of(context).push(MaterialPageRoute( builder: (context) => - DeviceScreen(device: device, isEnabled: isConnected))); + BluetoothDevicePage(device: device, isEnabled: isConnected))); } void toggleConnection(BuildContext context) { diff --git a/lib/connection/ble/bluetooth_device.dart b/lib/pages/bluetooth_device_page.dart similarity index 82% rename from lib/connection/ble/bluetooth_device.dart rename to lib/pages/bluetooth_device_page.dart index 5ca9fef..5641748 100644 --- a/lib/connection/ble/bluetooth_device.dart +++ b/lib/pages/bluetooth_device_page.dart @@ -1,4 +1,3 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_blue/flutter_blue.dart'; import 'package:lsa_gloves/connection/ble/bluetooth_backend.dart'; @@ -6,19 +5,29 @@ import 'package:lsa_gloves/connection/ble/bluetooth_specification.dart'; import 'package:provider/provider.dart'; -class DeviceScreen extends StatefulWidget { - const DeviceScreen({Key? key, required this.device, required this.isEnabled}) +/// Page to set the configuration of a glove. +/// +/// The user can either: +/// - connect the app to the device +/// - update the mtu to 512 bytes +/// - calibrate the glove. +/// +/// This page is accessible when clicking the configuration icon of a device +/// in the BluetoothDevicesConnectionPage. +class BluetoothDevicePage extends StatefulWidget { + const BluetoothDevicePage( + {Key? key, required this.device, required this.isEnabled}) : super(key: key); final BluetoothDevice device; final bool isEnabled; @override - _DeviceScreenState createState() => - _DeviceScreenState(this.device, this.isEnabled); + _BluetoothDevicePageState createState() => + _BluetoothDevicePageState(this.device, this.isEnabled); } -class _DeviceScreenState extends State { - _DeviceScreenState(this.device, this._isEnabled); +class _BluetoothDevicePageState extends State { + _BluetoothDevicePageState(this.device, this._isEnabled); BluetoothDevice device; bool _isEnabled; @@ -83,13 +92,14 @@ class _DeviceScreenState extends State { onPressed: (_isEnabled && snapshot.data == BluetoothDeviceState.connected) ? () { - Provider.of(context, listen: false) + Provider.of(context, + listen: false) .sendCalibrationCommand(device); ScaffoldMessenger.of(context) .showSnackBar(SnackBar( content: Row( children: [ - Icon(Icons.lightbulb, color: Colors.blue), + Icon(Icons.lightbulb, color: Colors.blue), SizedBox(width: 20), Expanded( child: Text( diff --git a/lib/pages/data_visualization_page.dart b/lib/pages/data_visualization_page.dart index fe11b9c..96254a4 100644 --- a/lib/pages/data_visualization_page.dart +++ b/lib/pages/data_visualization_page.dart @@ -1,17 +1,23 @@ -import 'dart:async'; -import 'dart:collection'; - import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:lsa_gloves/connection/ble/bluetooth_backend.dart'; import 'package:lsa_gloves/datacollection/measurements_collector.dart'; import 'package:lsa_gloves/datacollection/measurements_listener.dart'; +import 'package:lsa_gloves/model/finger.dart'; import 'package:lsa_gloves/model/glove_measurement.dart'; +import 'package:lsa_gloves/model/sensor_value.dart'; +import 'package:lsa_gloves/model/vector3.dart'; import 'package:lsa_gloves/navigation/navigation_drawer.dart'; import 'package:provider/provider.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; -import 'dart:developer' as developer; +/// Page to plot real time sensors data. +/// +/// The user can select the sensor from which finger to be plotted. +/// +/// The received data is not recorded, the purpose of this page is to simply +/// provide the user with a debugging tool to assess if the received data +/// from the sensors makes sense. class DataVisualizationPage extends StatefulWidget { const DataVisualizationPage({Key? key}) : super(key: key); @@ -68,13 +74,11 @@ class _DataVisualizationPageState extends State { )) .toList()), Expanded( - child:MeasurementsChart( - key: Key("$_finger-$_sensor"), - measurementsCollector: _measurementsCollector, - finger: _finger, - sensor: _sensor) - ) - + child: MeasurementsChart( + key: Key("$_finger-$_sensor"), + measurementsCollector: _measurementsCollector, + finger: _finger, + sensor: _sensor)) ], )), floatingActionButton: FloatingActionButton( @@ -160,44 +164,42 @@ class _MeasurementsChartState extends State @override Widget build(BuildContext context) { return SfCartesianChart( - series: [ - LineSeries( - name: sensor.getXLabel(), - dataSource: _measurementsX, - xValueMapper: (SeriesEntry measurement, _) => measurement.x, - yValueMapper: (SeriesEntry measurement, _) => measurement.y, - onRendererCreated: (ChartSeriesController controller) { - _chartSeriesControllerX = controller; - }), - LineSeries( - name: sensor.getYLabel(), - dataSource: _measurementsY, - xValueMapper: (SeriesEntry measurement, _) => measurement.x, - yValueMapper: (SeriesEntry measurement, _) => measurement.y, - onRendererCreated: (ChartSeriesController controller) { - _chartSeriesControllerY = controller; - }), - LineSeries( - name: sensor.getZLabel(), - dataSource: _measurementsZ, - xValueMapper: (SeriesEntry measurement, _) => measurement.x, - yValueMapper: (SeriesEntry measurement, _) => measurement.y, - onRendererCreated: (ChartSeriesController controller) { - _chartSeriesControllerZ = controller; - }), - ], - legend: Legend(isVisible: true, position: LegendPosition.bottom), - primaryXAxis: NumericAxis( - numberFormat: NumberFormat("##,###s") - ) - ); + series: [ + LineSeries( + name: sensor.getXLabel(), + dataSource: _measurementsX, + xValueMapper: (SeriesEntry measurement, _) => measurement.x, + yValueMapper: (SeriesEntry measurement, _) => measurement.y, + onRendererCreated: (ChartSeriesController controller) { + _chartSeriesControllerX = controller; + }), + LineSeries( + name: sensor.getYLabel(), + dataSource: _measurementsY, + xValueMapper: (SeriesEntry measurement, _) => measurement.x, + yValueMapper: (SeriesEntry measurement, _) => measurement.y, + onRendererCreated: (ChartSeriesController controller) { + _chartSeriesControllerY = controller; + }), + LineSeries( + name: sensor.getZLabel(), + dataSource: _measurementsZ, + xValueMapper: (SeriesEntry measurement, _) => measurement.x, + yValueMapper: (SeriesEntry measurement, _) => measurement.y, + onRendererCreated: (ChartSeriesController controller) { + _chartSeriesControllerZ = controller; + }), + ], + legend: Legend(isVisible: true, position: LegendPosition.bottom), + primaryXAxis: NumericAxis(numberFormat: NumberFormat("##,###s"))); } @override void onMeasurement(GloveMeasurement measurement) { measurementsBuffer.add(measurement); lastTimestampMs += measurement.timestampMillis; - Vector3 sensorValues = measurement.getFinger(finger).getSensorValues(sensor); + Vector3 sensorValues = + measurement.getFinger(finger).getSensorValues(sensor); _measurementsX.add(SeriesEntry(lastTimestampMs, sensorValues.getX())); _measurementsY.add(SeriesEntry(lastTimestampMs, sensorValues.getY())); _measurementsZ.add(SeriesEntry(lastTimestampMs, sensorValues.getZ())); diff --git a/lib/pages/file_content_chart_page.dart b/lib/pages/file_content_chart_page.dart index 15d3aa5..9875c51 100644 --- a/lib/pages/file_content_chart_page.dart +++ b/lib/pages/file_content_chart_page.dart @@ -2,12 +2,20 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:lsa_gloves/connection/ble/bluetooth_backend.dart'; import 'package:lsa_gloves/datacollection/storage.dart'; -import 'package:lsa_gloves/model/glove_measurement.dart'; +import 'package:lsa_gloves/model/acceleration.dart'; +import 'package:lsa_gloves/model/finger.dart'; +import 'package:lsa_gloves/model/gyro.dart'; +import 'package:lsa_gloves/model/sensor_value.dart'; +import 'package:lsa_gloves/model/vector3.dart'; import 'package:lsa_gloves/navigation/navigation_drawer.dart'; import 'package:provider/provider.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; -import 'dart:developer' as developer; +/// Page to plot the stored sensor values from a data collection. +/// +/// Two charts are displayed, one for the accelerometer and the other for the +/// gyroscope sensor. The user can select the finger associated with the sensor +/// they want to plot. class FileContentChartPage extends StatefulWidget { static const routeName = '/fileContentChart'; @@ -22,8 +30,8 @@ class _FileContentChartPageState extends State { @override Widget build(BuildContext context) { - SensorMeasurements sensorMeasurements = - ModalRoute.of(context)!.settings.arguments as SensorMeasurements; + BufferedSensorMeasurements sensorMeasurements = + ModalRoute.of(context)!.settings.arguments as BufferedSensorMeasurements; return Consumer( builder: (context, backend, _) => SafeArea( @@ -79,7 +87,7 @@ class _FileContentChartPageState extends State { } class MeasurementsChart extends StatefulWidget { - final SensorMeasurements measurements; + final BufferedSensorMeasurements measurements; final FingerValue finger; final SensorValue sensor; final String title; @@ -100,7 +108,7 @@ class MeasurementsChart extends StatefulWidget { } class _MeasurementsChartState extends State { - SensorMeasurements _measurements; + BufferedSensorMeasurements _measurements; FingerValue finger; String title; bool legend; diff --git a/lib/pages/file_manager_page.dart b/lib/pages/file_manager_page.dart index c15a043..e1834f7 100644 --- a/lib/pages/file_manager_page.dart +++ b/lib/pages/file_manager_page.dart @@ -1,7 +1,5 @@ import 'dart:async'; -import 'package:flutter/cupertino.dart'; -import 'package:lsa_gloves/datacollection/file_content.dart'; import 'package:lsa_gloves/navigation/navigation_drawer.dart'; import 'package:path/path.dart'; import 'package:flutter/material.dart'; @@ -9,6 +7,11 @@ import 'package:lsa_gloves/datacollection/storage.dart'; import 'file_content_chart_page.dart'; +/// Page to manage the stored files from data collections that haven't been yet +/// uploaded. +/// +/// This page displays the list of files, with its name, letting the user the +/// possibility to visualize the data and to upload or to delete the file. class FileManagerPage extends StatefulWidget { @override State createState() { @@ -29,50 +32,48 @@ class _FileManagerPage extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ StreamBuilder>( - stream: GloveEventsStorage().getListOfFiles().asStream(), + stream: FileManager().getListOfFiles().asStream(), initialData: [], builder: (c, snapshot) => Column( children: snapshot.data! .map( - (f) => Card( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - leading: IconButton( - icon: Icon(Icons.folder_open_sharp), + (deviceMeasurementsFile) => Card( + key: Key(deviceMeasurementsFile.path), + child: ListTile( + leading: IconButton( + icon: Icon(Icons.folder_open_sharp), + onPressed: () async { + BufferedSensorMeasurements measurements = + await deviceMeasurementsFile.readJsonContent(); + Navigator.pushNamed( + context, + FileContentChartPage.routeName, + arguments: measurements, + ); + }, + ), + title: Text(basename(deviceMeasurementsFile.path)), + subtitle: Text(deviceMeasurementsFile.lastModified), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.delete_outline_outlined, + color: Theme.of(context) + .iconTheme + .color + ?.withOpacity(0.5)), onPressed: () async { - SensorMeasurements measurements = await f.readJsonContent(); - Navigator.pushNamed( - context, - FileContentChartPage.routeName, - arguments: measurements, - ); + await deviceMeasurementsFile.deleteFile(); + //refresh + setState(() {}); }, ), - title: Text(basename(f.path)), - subtitle: Text(f.lastModified), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: Icon(Icons.delete_outline_outlined, - color: Theme.of(context) - .iconTheme - .color - ?.withOpacity(0.5)), - onPressed: () async { - await f.deleteFile(); - //refresh - setState(() {}); - }, - ), - UploadButton(onButtonPressed: () => - uploadFileCallBack(f)) - ], - ), - ), - ], + UploadButton( + onButtonPressed: () => + uploadFileCallBack(deviceMeasurementsFile)) + ], + ), ), ), ) @@ -98,9 +99,7 @@ class _FileManagerPage extends State { class UploadButton extends StatefulWidget { final Function onButtonPressed; - const UploadButton( - {Key? key, - required this.onButtonPressed}) + const UploadButton({Key? key, required this.onButtonPressed}) : super(key: key); @override @@ -115,21 +114,20 @@ class _UploadButton extends State { UploadResult _status; final Function onButtonPressed; - _UploadButton(this.onButtonPressed): _status = UploadResult.ready; + _UploadButton(this.onButtonPressed) : _status = UploadResult.ready; @override void initState() { super.initState(); Timer.periodic(Duration(seconds: 5), (Timer t) { - if(_status == UploadResult.failed){ - setState(() { - _status = UploadResult.ready; - }); - }}); + if (_status == UploadResult.failed) { + setState(() { + _status = UploadResult.ready; + }); + } + }); } - - @override Widget build(BuildContext context) { switch (_status) { @@ -137,10 +135,8 @@ class _UploadButton extends State { return new CircularProgressIndicator(); case UploadResult.failed: return IconButton( - icon: Icon(Icons.close, - color: Theme.of(context).errorColor), - onPressed: null - ); + icon: Icon(Icons.close, color: Theme.of(context).errorColor), + onPressed: null); default: return IconButton( icon: Icon( @@ -152,14 +148,12 @@ class _UploadButton extends State { _status = UploadResult.uploading; }); var result = await onButtonPressed.call(); - if(!result){ + if (!result) { setState(() { _status = UploadResult.failed; }); } }); } - - } } diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 5802786..295e85b 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -1,4 +1,3 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import '../navigation/navigation_drawer.dart'; @@ -9,6 +8,8 @@ import 'data_visualization_page.dart'; import 'file_manager_page.dart'; import 'interpretation_page.dart'; +/// Home page with navigation buttons to let the user access the multiple +/// pages of the application. class HomePage extends StatefulWidget { const HomePage({Key? key}) : super(key: key); diff --git a/lib/pages/interpretation_page.dart b/lib/pages/interpretation_page.dart index a61593b..0dd04f7 100644 --- a/lib/pages/interpretation_page.dart +++ b/lib/pages/interpretation_page.dart @@ -1,8 +1,5 @@ -import 'dart:collection'; - import 'package:assets_audio_player/assets_audio_player.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_blue/flutter_blue.dart'; import 'package:lsa_gloves/connection/ble/bluetooth_backend.dart'; import 'package:lsa_gloves/navigation/navigation_drawer.dart'; @@ -11,6 +8,7 @@ import 'dart:developer' as developer; import 'package:provider/provider.dart'; +/// Page to display the interpretations from the glove. class InterpretationPage extends StatefulWidget { const InterpretationPage({Key? key}) : super(key: key); diff --git a/lib/widgets/Dialog.dart b/lib/widgets/Dialog.dart deleted file mode 100644 index b75e246..0000000 --- a/lib/widgets/Dialog.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; - -class Dialogs { - - static Future showLoadingDialog( - BuildContext context, GlobalKey key, String message) async { - return showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return new WillPopScope( - onWillPop: () async => false, - child: SimpleDialog( - key: key, - children: [ - Center( - child: Column(children: [ - CircularProgressIndicator(), - SizedBox(height: 10,), - Text(message) - ]), - ) - ])); - }); - } -} \ No newline at end of file