diff --git a/lib/datacollection/measurements_collector.dart b/lib/datacollection/measurements_collector.dart index a6b4f80..b6ad1be 100644 --- a/lib/datacollection/measurements_collector.dart +++ b/lib/datacollection/measurements_collector.dart @@ -91,7 +91,7 @@ class MeasurementsCollector { GloveMeasurement gloveMeasurement = GloveMeasurement.fromFingerMeasurementsList( parsedMeasurements.eventNumber, - parsedMeasurements.elapsedTime, + parsedMeasurements.timestampMillis, deviceId, parsedMeasurements.values); // developer.log('map to -> ${gloveMeasurement.toJson().toString()}'); @@ -138,8 +138,8 @@ class MeasurementsCollector { return null; } int eventNum = int.parse(fingerMeasurements.removeAt(0)); - double elapsedTime = double.parse(fingerMeasurements.removeAt(0)); - return _ParsedMeasurements(eventNum, elapsedTime, fingerMeasurements); + int timestampMillis = int.parse(fingerMeasurements.removeAt(0)); + return _ParsedMeasurements(eventNum, timestampMillis, fingerMeasurements); } void _notifyListeners(GloveMeasurement measurement) { @@ -151,10 +151,10 @@ class MeasurementsCollector { class _ParsedMeasurements { final int eventNumber; - final double elapsedTime; + final int timestampMillis; final List values; - _ParsedMeasurements(this.eventNumber, this.elapsedTime, this.values); + _ParsedMeasurements(this.eventNumber, this.timestampMillis, this.values); @override String toString() { diff --git a/lib/datacollection/storage.dart b/lib/datacollection/storage.dart index d0c023a..49a2ede 100644 --- a/lib/datacollection/storage.dart +++ b/lib/datacollection/storage.dart @@ -70,10 +70,8 @@ class DeviceMeasurementsFile { static Future create( String deviceName, String deviceId, String word) async { var creationDate = DateTime.now(); - var values = >[]; - double intervalSumInMillis = 0.0; SensorMeasurements json = new SensorMeasurements( - deviceName, deviceId, word, values, intervalSumInMillis); + deviceName, deviceId, word, >[], []); String datetimeStr = format(creationDate); var filename = "${deviceName.substring(0, 1)}_${word}_$datetimeStr"; var file = await new GloveEventsStorage().createFile(filename); @@ -155,11 +153,11 @@ class SensorMeasurements { final String deviceName; final String deviceId; final String word; + final List timestamps; final List> values; - double intervalSumInMillis; SensorMeasurements(this.deviceName, this.deviceId, this.word,this.values, - this.intervalSumInMillis); + this.timestamps); bool add(GloveMeasurement gloveMeasurement) { if (gloveMeasurement.deviceId != this.deviceId) { @@ -167,13 +165,13 @@ class SensorMeasurements { return false; } List measurementList = []; + measurementList.addAll(extractFingerMeasurement(gloveMeasurement.thumb)); measurementList.addAll(extractFingerMeasurement(gloveMeasurement.pinky)); measurementList.addAll(extractFingerMeasurement(gloveMeasurement.ring)); measurementList.addAll(extractFingerMeasurement(gloveMeasurement.middle)); measurementList.addAll(extractFingerMeasurement(gloveMeasurement.index)); - measurementList.addAll(extractFingerMeasurement(gloveMeasurement.thumb)); this.values.add(measurementList); - this.intervalSumInMillis = this.intervalSumInMillis + gloveMeasurement.elapsedTimeMs; + this.timestamps.add(gloveMeasurement.timestampMillis); return true; } @@ -193,6 +191,8 @@ class SensorMeasurements { factory SensorMeasurements.fromJson(dynamic json) { List> _values = >[]; + List _timestamps = []; + if (json['values'] != null) { var jsonLists = json['values'] as List; _values = jsonLists.map((jsonList) { @@ -200,12 +200,16 @@ class SensorMeasurements { return valuesList.map((v) => v as double).toList(); }).toList(); } + if (json['timestamps'] != null) { + var jsonLists = json['timestamps'] as List; + _timestamps = jsonLists.map((v) => v as int).toList(); + } return SensorMeasurements( json['device_name'] as String, json['device_id'] as String, json['word'] as String, _values, - json['interval_sum_in_millis'] as double, + _timestamps, ); } @@ -215,7 +219,7 @@ class SensorMeasurements { 'device_id': deviceId, 'word': word, 'values': values, - 'interval_sum_in_millis': intervalSumInMillis, + 'timestamps': timestamps }; } } diff --git a/lib/edgeimpulse/api_client.dart b/lib/edgeimpulse/api_client.dart index 0c8a042..0899d62 100644 --- a/lib/edgeimpulse/api_client.dart +++ b/lib/edgeimpulse/api_client.dart @@ -21,8 +21,12 @@ class EdgeImpulseApiClient { //the algorithm used to sign this file. Either HS256 (HMAC-SHA256) or none (required) iat: datetime.toUtc().millisecondsSinceEpoch ~/1000.0// date when the file was created in seconds since epoch ); - - double averageIntervalInMilliseconds = sensorMeasurements.intervalSumInMillis / double.parse("${sensorMeasurements.values.length}"); + double elapsedTimeSum = 0.0; + for(var i = sensorMeasurements.timestamps.length -1 ; i > 0 ; i--){ + var elapsedTime = sensorMeasurements.timestamps[i] - sensorMeasurements.timestamps[i-1]; + elapsedTimeSum += elapsedTime ; + } + double averageIntervalInMilliseconds = elapsedTimeSum / (sensorMeasurements.values.length * 1.0); developer.log("averageIntervalInMilliseconds: $averageIntervalInMilliseconds", name: TAG); var payload = Payload( deviceName: sensorMeasurements.deviceId, @@ -52,7 +56,7 @@ class EdgeImpulseApiClient { request.headers.set('x-api-key', secret.rightGloveApiKey); } request.headers.set('x-file-name', fileName); - request.headers.set('x-label', sensorMeasurements.word); + request.headers.set('x-label', sensorMeasurements.word); request.add(utf8.encode(json.encode(edgeImpulseBody))); HttpClientResponse response = await request.close(); String reply = await response.transform(utf8.decoder).join(); diff --git a/lib/main.dart b/lib/main.dart index ff80a01..80b2d46 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,7 @@ 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'; import 'package:provider/provider.dart'; @@ -24,6 +25,7 @@ class MyApp extends StatelessWidget { return MaterialApp( routes: { FileContentPage.routeName: (context) => FileContentPage(), + FileContentChartPage.routeName: (context) => FileContentChartPage(), BleGloveConnectionPage.routeName: (context) => BleGloveConnectionPage(), BleConnectionErrorPage.routeName: (context) => BleConnectionErrorPage(), }, diff --git a/lib/model/glove_measurement.dart b/lib/model/glove_measurement.dart index 944c44f..a822900 100644 --- a/lib/model/glove_measurement.dart +++ b/lib/model/glove_measurement.dart @@ -34,18 +34,18 @@ class GloveMeasurement { final String deviceId; final int eventNum; - final double elapsedTimeMs; + final int timestampMillis; final Finger thumb; final Finger index; final Finger middle; final Finger ring; final Finger pinky; - GloveMeasurement(this.deviceId, this.eventNum,this.elapsedTimeMs,this.pinky, this.ring, this.middle, this.index, this.thumb); + GloveMeasurement(this.deviceId, this.eventNum,this.timestampMillis,this.pinky, this.ring, this.middle, this.index, this.thumb); GloveMeasurement.fromJson(Map json) : deviceId = json['device_id'], eventNum = json['event_num'], - elapsedTimeMs = json['elapsed_time'], + timestampMillis = json['timestamp_millis'], pinky = Finger.fromJson(json['pinky'] as Map), ring = Finger.fromJson(json['ring'] as Map), middle = Finger.fromJson(json['middle'] as Map), @@ -54,7 +54,7 @@ class GloveMeasurement { Map toJson() => { 'device_id': deviceId, - 'elapsed_time': elapsedTimeMs, + 'timestampMillis': timestampMillis, 'event_num': eventNum, 'pinky' : pinky.toJson(), 'ring': ring.toJson(), @@ -64,7 +64,7 @@ class GloveMeasurement { }; - static fromFingerMeasurementsList(int eventNum, double elapsedTime, String deviceId, List fingerMeasurements) { + static fromFingerMeasurementsList(int eventNum, int timestampMillis, String deviceId, List fingerMeasurements) { Map measurementsMap = new Map(); for (final item in fingerMeasurements) { @@ -80,7 +80,7 @@ class GloveMeasurement { Finger? middle = measurementsMap[middleLetter]; Finger? index = measurementsMap[indexLetter]; Finger? thumb = measurementsMap[thumbLetter]; - return new GloveMeasurement(deviceId, eventNum, elapsedTime, pinky!, ring!, middle!, index!, thumb!); + return new GloveMeasurement(deviceId, eventNum, timestampMillis, pinky!, ring!, middle!, index!, thumb!); } Finger getFinger(FingerValue fingerName) { diff --git a/lib/pages/ble_data_collection_page.dart b/lib/pages/ble_data_collection_page.dart index 06c2036..8f15ecc 100644 --- a/lib/pages/ble_data_collection_page.dart +++ b/lib/pages/ble_data_collection_page.dart @@ -77,7 +77,7 @@ class _BleDataCollectionState extends State }); }), DataVisualizer( - key: Key("$_collections"), collector: _measurementsCollector), + key: Key("$_collections-$_isRecording"), collector: _measurementsCollector), Expanded( child: Align( alignment: FractionalOffset.bottomCenter, @@ -161,6 +161,7 @@ class _BleDataCollectionState extends State void _stopRecording(BluetoothBackend bluetoothBackend) async { developer.log('stopRecording'); bluetoothBackend.sendStopCommand(); + _isRecording = false; showDialog( context: context, @@ -313,10 +314,11 @@ class DataVisualizer extends StatefulWidget { class _DataVisualizerState extends State with MeasurementsListener { final MeasurementsCollector collector; + Map _stats; + + _DataVisualizerState(this.collector):_stats = Map(); - _DataVisualizerState(this.collector); - Map _stats = Map(); @override void initState() { @@ -350,23 +352,39 @@ class _DataVisualizerState extends State setState(() { if (!_stats.containsKey(measurement.deviceId)) { _stats[measurement.deviceId] = GloveStats(); - } else { - _stats[measurement.deviceId]?.update(measurement.elapsedTimeMs); } + _stats[measurement.deviceId]?.update(measurement.eventNum, measurement.timestampMillis); }); } } class GloveStats { - double accumulatedTimeMs = 0; - int eventNumber = 0; - - void update(double elapsedTimeMs) { + static const String TAG = 'GloveStats'; + int initialMs = -1; + int accumulatedTimeMs = 1; + int eventNumber = 1; + + void update(int gloveEventNum, int timestampMillis) { + if(this.initialMs < 0 ){ + this.initialMs = timestampMillis; + developer.log("set initialMs -> $initialMs", name: TAG); + } + if(gloveEventNum != this.eventNumber ){ + developer.log("se perdieron eventos ?", name: TAG); + developer.log("eventNumber: $eventNumber gloveEventNum: $gloveEventNum", name: TAG); + } eventNumber++; - accumulatedTimeMs = accumulatedTimeMs + elapsedTimeMs; + accumulatedTimeMs = timestampMillis; } double getFrequency() { - return 1000 * eventNumber / accumulatedTimeMs; + double elapsedTime = ((accumulatedTimeMs - initialMs) * 1.0); + developer.log("elapsedTime = $accumulatedTimeMs - $initialMs= $elapsedTime", name: TAG); + if(elapsedTime < 1){ + return 0.0; + } + double frequency = 1000.0 * eventNumber / elapsedTime; + developer.log("frequency= 1000 * $eventNumber /$elapsedTime = $frequency", name: TAG); + return frequency; } } diff --git a/lib/pages/data_visualization_page.dart b/lib/pages/data_visualization_page.dart index 330e547..fe11b9c 100644 --- a/lib/pages/data_visualization_page.dart +++ b/lib/pages/data_visualization_page.dart @@ -196,7 +196,7 @@ class _MeasurementsChartState extends State @override void onMeasurement(GloveMeasurement measurement) { measurementsBuffer.add(measurement); - lastTimestampMs += measurement.elapsedTimeMs; + lastTimestampMs += measurement.timestampMillis; Vector3 sensorValues = measurement.getFinger(finger).getSensorValues(sensor); _measurementsX.add(SeriesEntry(lastTimestampMs, sensorValues.getX())); _measurementsY.add(SeriesEntry(lastTimestampMs, sensorValues.getY())); diff --git a/lib/pages/file_content_chart_page.dart b/lib/pages/file_content_chart_page.dart new file mode 100644 index 0000000..22bd043 --- /dev/null +++ b/lib/pages/file_content_chart_page.dart @@ -0,0 +1,205 @@ +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/navigation/navigation_drawer.dart'; +import 'package:provider/provider.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'dart:developer' as developer; + +class FileContentChartPage extends StatefulWidget { + static const routeName = '/fileContentChart'; + + const FileContentChartPage({Key? key}) : super(key: key); + + @override + _FileContentChartPageState createState() => _FileContentChartPageState(); +} + +class _FileContentChartPageState extends State { + int _fingerChosen = 0; + + @override + Widget build(BuildContext context) { + SensorMeasurements sensorMeasurements = + ModalRoute.of(context)!.settings.arguments as SensorMeasurements; + + return Consumer( + builder: (context, backend, _) => SafeArea( + child: Scaffold( + appBar: AppBar( + title: Text('VisualizaciĆ³n'), + ), + drawer: NavDrawer(), + body: Padding( + padding: EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Wrap( + spacing: 6.0, + runSpacing: 6.0, + children: List.generate( + FingerValue.values.length, (int index) { + return ChoiceChip( + label: Text(FingerValue.values[index].spanishName()), + selected: _fingerChosen == index, + selectedColor: Colors.cyan, + onSelected: (bool selected) { + setState(() { + _fingerChosen = selected ? index : 0; + }); + }, + backgroundColor: Colors.blue, + labelStyle: TextStyle(color: Colors.white), + ); + }), + ), + Expanded( + child: MeasurementsChart( + measurements: sensorMeasurements, + finger: FingerValue.values[_fingerChosen], + key: ValueKey(_fingerChosen), + sensor: SensorValue.Acceleration, + title: "Aceleracion", + legend: false)), + Expanded( + child: MeasurementsChart( + measurements: sensorMeasurements, + finger: FingerValue.values[_fingerChosen], + key: ValueKey(_fingerChosen), + sensor: SensorValue.Gyroscope, + title: "Velocidad angular", + legend: false)), + Expanded( + child: MeasurementsChart( + measurements: sensorMeasurements, + finger: FingerValue.values[_fingerChosen], + key: ValueKey(_fingerChosen), + sensor: SensorValue.Inclination, + title: "Inclinacion", + legend: true)) + ], + )), + ))); + } +} + +class MeasurementsChart extends StatefulWidget { + final SensorMeasurements measurements; + final FingerValue finger; + final SensorValue sensor; + final String title; + final bool legend; + + const MeasurementsChart( + {Key? key, + required this.measurements, + required this.finger, + required this.sensor, + required this.title, + required this.legend}) + : super(key: key); + + @override + _MeasurementsChartState createState() => + _MeasurementsChartState(measurements, finger, sensor, title, legend); +} + +class _MeasurementsChartState extends State { + SensorMeasurements _measurements; + FingerValue finger; + String title; + bool legend; + SensorValue sensor; + late ZoomPanBehavior _zoomPanBehavior; + + List _measurementsX = []; + List _measurementsY = []; + List _measurementsZ = []; + + _MeasurementsChartState( + this._measurements, this.finger, this.sensor, this.title, this.legend); + + @override + void initState() { + super.initState(); + _zoomPanBehavior = ZoomPanBehavior( + enablePinching: true, enablePanning: true, zoomMode: ZoomMode.xy); + } + + @override + Widget build(BuildContext context) { + double initialTimestamp = 0.0; + for (var i = 0; i < this._measurements.values.length; i++) { + if (i == 0) { + initialTimestamp = this._measurements.timestamps[i] * 1.0; + } + var timestamp = (this._measurements.timestamps[i] - initialTimestamp) * 1.0; + var fingerValues = getFingerValues(this._measurements.values[i]); + var sensorValues = this.getSensorValues(fingerValues); + _measurementsX.add(SeriesEntry(timestamp, sensorValues.getX())); + _measurementsY.add(SeriesEntry(timestamp, sensorValues.getY())); + _measurementsZ.add(SeriesEntry(timestamp, sensorValues.getZ())); + } + return SfCartesianChart( + title: ChartTitle(text: this.title), + zoomPanBehavior: _zoomPanBehavior, + series: [ + LineSeries( + name: 'X', + dataSource: _measurementsX, + markerSettings: MarkerSettings(isVisible: true), + xValueMapper: (SeriesEntry measurement, _) => measurement.x, + yValueMapper: (SeriesEntry measurement, _) => measurement.y), + LineSeries( + name: 'Y', + dataSource: _measurementsY, + markerSettings: MarkerSettings(isVisible: true), + xValueMapper: (SeriesEntry measurement, _) => measurement.x, + yValueMapper: (SeriesEntry measurement, _) => measurement.y), + LineSeries( + name: 'Z', + dataSource: _measurementsZ, + markerSettings: MarkerSettings(isVisible: true), + xValueMapper: (SeriesEntry measurement, _) => measurement.x, + yValueMapper: (SeriesEntry measurement, _) => measurement.y), + ], + legend: Legend(isVisible: this.legend, position: LegendPosition.bottom), + primaryXAxis: NumericAxis(numberFormat: NumberFormat("##,###s"))); + } + + List getFingerValues(List m) { + switch (finger) { + case FingerValue.Thumb: + return m.sublist(0, 9); + case FingerValue.Index: + return m.sublist(9, 18); + case FingerValue.Middle: + return m.sublist(18, 27); + case FingerValue.Ring: + return m.sublist(27, 36); + case FingerValue.Pinky: + return m.sublist(36); + } + } + + Vector3 getSensorValues(List m) { + switch (this.sensor) { + case SensorValue.Acceleration: + return Acceleration(m[0], m[1], m[2]); + case SensorValue.Gyroscope: + return Gyro(m[3], m[4], m[5]); + case SensorValue.Inclination: + return Inclination(m[6], m[7], m[8]); + } + } +} + +class SeriesEntry { + double x; + double y; + + SeriesEntry(this.x, this.y); +} diff --git a/lib/pages/file_manager_page.dart b/lib/pages/file_manager_page.dart index a4a9344..c15a043 100644 --- a/lib/pages/file_manager_page.dart +++ b/lib/pages/file_manager_page.dart @@ -7,6 +7,8 @@ import 'package:path/path.dart'; import 'package:flutter/material.dart'; import 'package:lsa_gloves/datacollection/storage.dart'; +import 'file_content_chart_page.dart'; + class FileManagerPage extends StatefulWidget { @override State createState() { @@ -40,10 +42,11 @@ class _FileManagerPage extends State { leading: IconButton( icon: Icon(Icons.folder_open_sharp), onPressed: () async { + SensorMeasurements measurements = await f.readJsonContent(); Navigator.pushNamed( context, - FileContentPage.routeName, - arguments: f, + FileContentChartPage.routeName, + arguments: measurements, ); }, ),