Skip to content

Commit a91a7b4

Browse files
feat: added csv playback for gyroscope instrument. (#2894)
Co-authored-by: Marc Nause <[email protected]>
1 parent 6dd6022 commit a91a7b4

File tree

3 files changed

+173
-12
lines changed

3 files changed

+173
-12
lines changed

lib/providers/gyroscope_state_provider.dart

Lines changed: 133 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ class GyroscopeProvider extends ChangeNotifier {
2626
bool _isRecording = false;
2727
List<List<dynamic>> _recordedData = [];
2828

29+
bool _isPlayingBack = false;
30+
List<List<dynamic>>? _playbackData;
31+
int _playbackIndex = 0;
32+
Timer? _playbackTimer;
33+
bool _isPlaybackPaused = false;
34+
2935
double get xValue => _gyroscopeEvent.x;
3036
double get yValue => _gyroscopeEvent.y;
3137
double get zValue => _gyroscopeEvent.z;
@@ -39,6 +45,10 @@ class GyroscopeProvider extends ChangeNotifier {
3945

4046
bool get isListening => _gyroscopeSubscription != null;
4147
bool get isRecording => _isRecording;
48+
bool get isPlayingBack => _isPlayingBack;
49+
bool get isPlaybackPaused => _isPlaybackPaused;
50+
51+
Function? onPlaybackEnd;
4252

4353
void initializeSensors() {
4454
if (_gyroscopeSubscription != null) return;
@@ -61,6 +71,116 @@ class GyroscopeProvider extends ChangeNotifier {
6171
_gyroscopeSubscription = null;
6272
}
6373

74+
void startPlayback(List<List<dynamic>> data) {
75+
if (data.length <= 1) {
76+
logger.w("Playback skipped: insufficient data (length <= 1)");
77+
return;
78+
}
79+
80+
_isPlayingBack = true;
81+
_isPlaybackPaused = false;
82+
_playbackData = data;
83+
_playbackIndex = 1;
84+
disposeSensors();
85+
_xData.clear();
86+
_yData.clear();
87+
_zData.clear();
88+
xData.clear();
89+
yData.clear();
90+
zData.clear();
91+
_startPlaybackTimer();
92+
notifyListeners();
93+
}
94+
95+
void _startPlaybackTimer() {
96+
if (_playbackIndex >= _playbackData!.length) {
97+
stopPlayback();
98+
return;
99+
}
100+
101+
final currentRow = _playbackData![_playbackIndex];
102+
if (currentRow.length > 4) {
103+
final x = double.tryParse(currentRow[2].toString()) ?? 0.0;
104+
final y = double.tryParse(currentRow[3].toString()) ?? 0.0;
105+
final z = double.tryParse(currentRow[4].toString()) ?? 0.0;
106+
107+
_gyroscopeEvent = GyroscopeEvent(x, y, z, DateTime.now());
108+
_updateData();
109+
_playbackIndex++;
110+
notifyListeners();
111+
} else {
112+
logger.e(
113+
'Skipping playback row at index $_playbackIndex due to insufficient columns (found ${currentRow.length}, expected at least 5');
114+
_playbackIndex++;
115+
notifyListeners();
116+
}
117+
118+
Duration interval = const Duration(seconds: 1);
119+
120+
if (_playbackIndex < _playbackData!.length && _playbackIndex > 1) {
121+
try {
122+
final currentTimestamp =
123+
int.tryParse(_playbackData![_playbackIndex - 1][0].toString());
124+
final nextTimestamp =
125+
int.tryParse(_playbackData![_playbackIndex][0].toString());
126+
127+
if (currentTimestamp != null && nextTimestamp != null) {
128+
final timeDiff = nextTimestamp - currentTimestamp;
129+
interval = Duration(milliseconds: timeDiff);
130+
if (interval.inMilliseconds < 100) {
131+
interval = const Duration(milliseconds: 100);
132+
} else if (interval.inMilliseconds > 10000) {
133+
interval = const Duration(seconds: 10);
134+
}
135+
}
136+
} catch (e) {
137+
interval = const Duration(seconds: 1);
138+
}
139+
}
140+
141+
_playbackTimer = Timer(interval, () {
142+
if (_isPlayingBack && !_isPlaybackPaused) {
143+
_startPlaybackTimer();
144+
}
145+
});
146+
}
147+
148+
Future<void> stopPlayback() async {
149+
_isPlayingBack = false;
150+
_isPlaybackPaused = false;
151+
_playbackTimer?.cancel();
152+
_playbackData = null;
153+
_playbackIndex = 0;
154+
155+
_xData.clear();
156+
_yData.clear();
157+
_zData.clear();
158+
xData.clear();
159+
yData.clear();
160+
zData.clear();
161+
162+
_gyroscopeEvent = GyroscopeEvent(0, 0, 0, DateTime.now());
163+
164+
notifyListeners();
165+
onPlaybackEnd?.call();
166+
}
167+
168+
void pausePlayback() {
169+
if (_isPlayingBack) {
170+
_isPlaybackPaused = true;
171+
_playbackTimer?.cancel();
172+
notifyListeners();
173+
}
174+
}
175+
176+
void resumePlayback() {
177+
if (_isPlayingBack && _isPlaybackPaused) {
178+
_isPlaybackPaused = false;
179+
_startPlaybackTimer();
180+
notifyListeners();
181+
}
182+
}
183+
64184
void _updateData() {
65185
final x = _gyroscopeEvent.x;
66186
final y = _gyroscopeEvent.y;
@@ -88,12 +208,18 @@ class GyroscopeProvider extends ChangeNotifier {
88208
if (_yData.length > _maxLength) _yData.removeAt(0);
89209
if (_zData.length > _maxLength) _zData.removeAt(0);
90210

91-
_xMin = _xData.reduce(min);
92-
_xMax = _xData.reduce(max);
93-
_yMin = _yData.reduce(min);
94-
_yMax = _yData.reduce(max);
95-
_zMin = _zData.reduce(min);
96-
_zMax = _zData.reduce(max);
211+
if (_xData.isNotEmpty) {
212+
_xMin = _xData.reduce(min);
213+
_xMax = _xData.reduce(max);
214+
}
215+
if (_yData.isNotEmpty) {
216+
_yMin = _yData.reduce(min);
217+
_yMax = _yData.reduce(max);
218+
}
219+
if (_zData.isNotEmpty) {
220+
_zMin = _zData.reduce(min);
221+
_zMax = _zData.reduce(max);
222+
}
97223

98224
xData.clear();
99225
yData.clear();
@@ -196,6 +322,7 @@ class GyroscopeProvider extends ChangeNotifier {
196322

197323
@override
198324
void dispose() {
325+
_playbackTimer?.cancel();
199326
disposeSensors();
200327
super.dispose();
201328
}

lib/view/gyroscope_screen.dart

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import '../constants.dart';
1414
import 'gyroscope_config_screen.dart';
1515

1616
class GyroscopeScreen extends StatefulWidget {
17-
const GyroscopeScreen({super.key});
17+
final List<List<dynamic>>? playbackData;
18+
const GyroscopeScreen({super.key, this.playbackData});
1819

1920
@override
2021
State<StatefulWidget> createState() => _GyroscopeScreenState();
@@ -31,16 +32,24 @@ class _GyroscopeScreenState extends State<GyroscopeScreen> {
3132
void initState() {
3233
super.initState();
3334
_provider = GyroscopeProvider();
35+
_provider.onPlaybackEnd = () {
36+
if (mounted && Navigator.canPop(context)) {
37+
Navigator.pop(context);
38+
}
39+
};
3440
WidgetsBinding.instance.addPostFrameCallback((_) {
3541
if (mounted) {
36-
_provider.initializeSensors();
42+
if (widget.playbackData != null) {
43+
_provider.startPlayback(widget.playbackData!);
44+
} else {
45+
_provider.initializeSensors();
46+
}
3747
}
3848
});
3949
}
4050

4151
@override
4252
void dispose() {
43-
_provider.disposeSensors();
4453
_provider.dispose();
4554
super.dispose();
4655
}
@@ -218,11 +227,26 @@ class _GyroscopeScreenState extends State<GyroscopeScreen> {
218227
Consumer<GyroscopeProvider>(
219228
builder: (context, provider, child) {
220229
return CommonScaffold(
221-
title: appLocalizations.gyroscopeTitle,
230+
title: provider.isPlayingBack
231+
? '${appLocalizations.gyroscopeTitle} - ${appLocalizations.playback}'
232+
: appLocalizations.gyroscopeTitle,
222233
onGuidePressed: _showInstrumentGuide,
223-
onOptionsPressed: _showOptionsMenu,
224-
onRecordPressed: _toggleRecording,
234+
onOptionsPressed:
235+
provider.isPlayingBack ? null : _showOptionsMenu,
236+
onRecordPressed: provider.isPlayingBack ? null : _toggleRecording,
225237
isRecording: provider.isRecording,
238+
isPlayingBack: provider.isPlayingBack,
239+
isPlaybackPaused: provider.isPlaybackPaused,
240+
onPlaybackPauseResume: provider.isPlayingBack
241+
? (provider.isPlaybackPaused
242+
? _provider.resumePlayback
243+
: _provider.pausePlayback)
244+
: null,
245+
onPlaybackStop: provider.isPlayingBack
246+
? () async {
247+
await _provider.stopPlayback();
248+
}
249+
: null,
226250
body: SafeArea(
227251
child: Column(
228252
children: [

lib/view/logged_data_screen.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
33
import 'package:intl/intl.dart';
44
import 'package:pslab/others/csv_service.dart';
55
import 'package:pslab/theme/colors.dart';
6+
import 'package:pslab/view/gyroscope_screen.dart';
67
import 'package:pslab/view/logged_data_chart_screen.dart';
78
import 'package:pslab/view/luxmeter_screen.dart';
89
import 'package:pslab/view/soundmeter_screen.dart';
@@ -177,6 +178,14 @@ class _LoggedDataScreenState extends State<LoggedDataScreen> {
177178
),
178179
);
179180
break;
181+
case 'gyroscope':
182+
Navigator.push(
183+
context,
184+
MaterialPageRoute(
185+
builder: (context) => GyroscopeScreen(playbackData: data),
186+
),
187+
);
188+
break;
180189
case 'luxmeter':
181190
Navigator.push(
182191
context,
@@ -314,6 +323,7 @@ class _LoggedDataScreenState extends State<LoggedDataScreen> {
314323
mainAxisSize: MainAxisSize.min,
315324
children: [
316325
if (widget.instrumentName == "soundmeter" ||
326+
widget.instrumentName == "gyroscope" ||
317327
widget.instrumentName == "luxmeter")
318328
IconButton(
319329
icon:

0 commit comments

Comments
 (0)