Skip to content

Commit 0aa77ec

Browse files
committed
First commit
1 parent 538cf33 commit 0aa77ec

6 files changed

Lines changed: 921 additions & 2 deletions

File tree

lib/l10n/app_en.arb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -668,5 +668,11 @@
668668
"saw" : "Saw",
669669
"comingSoon": "Coming Soon",
670670
"startRecording": "Start Recording",
671-
"stopRecording": "Stop Recording"
671+
"stopRecording": "Stop Recording",
672+
"ppm": "PPM",
673+
"airQuality": "Air Quality",
674+
"good": "Good",
675+
"moderate": "Moderate",
676+
"unhealthy": "Unhealthy",
677+
"hazardous": "Hazardous"
672678
}

lib/main.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:pslab/providers/settings_config_provider.dart';
99
import 'package:pslab/view/accelerometer_screen.dart';
1010
import 'package:pslab/view/barometer_screen.dart';
1111
import 'package:pslab/view/connect_device_screen.dart';
12+
import 'package:pslab/view/dust_sensor_screen.dart';
1213
import 'package:pslab/view/faq_screen.dart';
1314
import 'package:pslab/view/gyroscope_screen.dart';
1415
import 'package:pslab/view/instruments_screen.dart';
@@ -85,6 +86,7 @@ class MyApp extends StatelessWidget {
8586
'/waveGenerator': (context) => const WaveGeneratorScreen(),
8687
'/logicAnalyzer': (context) => const LogicAnalyzerScreen(),
8788
'/powerSource': (context) => const PowerSourceScreen(),
89+
'/dustsensor': (context) => const DustSensorScreen(),
8890
'/compass': (context) => const CompassScreen(),
8991
'/connectDevice': (context) => const ConnectDeviceScreen(),
9092
'/faq': (context) => FAQScreen(),
Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
import 'dart:async';
2+
import 'dart:math';
3+
4+
import 'package:flutter/material.dart';
5+
import 'package:fl_chart/fl_chart.dart';
6+
import 'package:geolocator/geolocator.dart';
7+
import 'package:intl/intl.dart';
8+
9+
import '../l10n/app_localizations.dart';
10+
import '../others/logger_service.dart';
11+
import 'locator.dart';
12+
13+
class DustSensorStateProvider extends ChangeNotifier {
14+
AppLocalizations appLocalizations = getIt.get<AppLocalizations>();
15+
double _currentDust = 0.0;
16+
Timer? _timeTimer;
17+
Timer? _dustTimer;
18+
final List<double> _dustData = [];
19+
final List<double> _timeData = [];
20+
final List<FlSpot> dustChartData = [];
21+
final List<FlSpot> ppmChartData = [];
22+
double _startTime = 0;
23+
double _currentTime = 0;
24+
final int _chartMaxLength = 50;
25+
double _dustMin = 0;
26+
double _dustMax = 0;
27+
double _dustSum = 0;
28+
int _dataCount = 0;
29+
bool _isRecording = false;
30+
List<List<dynamic>> _recordedData = [];
31+
bool _isPlayingBack = false;
32+
List<List<dynamic>>? _playbackData;
33+
int _playbackIndex = 0;
34+
Timer? _playbackTimer;
35+
bool _isPlaybackPaused = false;
36+
bool get isRecording => _isRecording;
37+
bool get isPlayingBack => _isPlayingBack;
38+
bool get isPlaybackPaused => _isPlaybackPaused;
39+
40+
Function(String)? onSensorError;
41+
Function? onPlaybackEnd;
42+
43+
Position? currentPosition;
44+
StreamSubscription? _locationStream;
45+
46+
Future<void> _startGeoLocationUpdates() async {
47+
bool serviceEnabled;
48+
LocationPermission permission;
49+
50+
serviceEnabled = await Geolocator.isLocationServiceEnabled();
51+
if (!serviceEnabled) {
52+
logger.w('Location services are disabled.');
53+
return;
54+
}
55+
56+
permission = await Geolocator.checkPermission();
57+
if (permission == LocationPermission.denied) {
58+
permission = await Geolocator.requestPermission();
59+
if (permission == LocationPermission.denied) {
60+
logger.w('Location permissions are denied');
61+
return;
62+
}
63+
}
64+
65+
if (permission == LocationPermission.deniedForever) {
66+
logger.w(
67+
'Location permissions are permanently denied, we cannot request permissions.');
68+
return;
69+
}
70+
71+
_locationStream = Geolocator.getPositionStream(
72+
locationSettings: const LocationSettings(
73+
accuracy: LocationAccuracy.high,
74+
),
75+
).listen((Position position) {
76+
currentPosition = position;
77+
});
78+
}
79+
80+
void initializeSensors({Function(String)? onError}) async {
81+
onSensorError = onError;
82+
83+
try {
84+
_startTime = DateTime.now().millisecondsSinceEpoch / 1000.0;
85+
86+
_timeTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
87+
_currentTime =
88+
(DateTime.now().millisecondsSinceEpoch / 1000.0) - _startTime;
89+
_updateData();
90+
notifyListeners();
91+
});
92+
93+
_dustTimer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
94+
// Placeholder for dust sensor reading logic
95+
notifyListeners();
96+
});
97+
} catch (e) {
98+
logger.e("Dust sensor initialization error: $e");
99+
_handleSensorError(e);
100+
}
101+
}
102+
103+
void _handleSensorError(dynamic error) {
104+
onSensorError?.call("Unable to access dust sensor");
105+
logger.e("Dust sensor error: $error");
106+
}
107+
108+
void disposeSensors() async {
109+
_timeTimer?.cancel();
110+
_dustTimer?.cancel();
111+
}
112+
113+
void startPlayback(List<List<dynamic>> data) {
114+
if (data.length <= 1) return;
115+
116+
_isPlayingBack = true;
117+
_isPlaybackPaused = false;
118+
_playbackData = data;
119+
_playbackIndex = 1;
120+
121+
_timeTimer?.cancel();
122+
_dustTimer?.cancel();
123+
124+
_dustData.clear();
125+
dustChartData.clear();
126+
_timeData.clear();
127+
_startTime = DateTime.now().millisecondsSinceEpoch / 1000.0;
128+
_currentTime = 0;
129+
_dustSum = 0;
130+
_dataCount = 0;
131+
132+
_startPlaybackTimer();
133+
notifyListeners();
134+
}
135+
136+
void _startPlaybackTimer() {
137+
if (_playbackIndex >= _playbackData!.length) {
138+
stopPlayback();
139+
return;
140+
}
141+
142+
final currentRow = _playbackData![_playbackIndex];
143+
if (currentRow.length > 2) {
144+
_currentDust = double.tryParse(currentRow[2].toString()) ?? 0.0;
145+
_currentTime = (_playbackIndex - 1).toDouble();
146+
_updateData();
147+
_playbackIndex++;
148+
notifyListeners();
149+
} else {
150+
logger.e(
151+
'Skipping playback row at index $_playbackIndex due to insufficient columns (found ${currentRow.length}, expected at least 3');
152+
_playbackIndex++;
153+
notifyListeners();
154+
}
155+
156+
Duration interval = const Duration(seconds: 1);
157+
158+
if (_playbackIndex < _playbackData!.length && _playbackIndex > 1) {
159+
try {
160+
final currentTimestamp =
161+
int.tryParse(_playbackData![_playbackIndex - 1][0].toString());
162+
final nextTimestamp =
163+
int.tryParse(_playbackData![_playbackIndex][0].toString());
164+
165+
if (currentTimestamp != null && nextTimestamp != null) {
166+
final timeDiff = nextTimestamp - currentTimestamp;
167+
interval = Duration(milliseconds: timeDiff);
168+
if (interval.inMilliseconds < 100) {
169+
interval = const Duration(milliseconds: 100);
170+
} else if (interval.inMilliseconds > 10000) {
171+
interval = const Duration(seconds: 10);
172+
}
173+
}
174+
} catch (e) {
175+
interval = const Duration(seconds: 1);
176+
}
177+
}
178+
179+
_playbackTimer = Timer(interval, () {
180+
if (_isPlayingBack && !_isPlaybackPaused) {
181+
_startPlaybackTimer();
182+
}
183+
});
184+
}
185+
186+
Future<void> stopPlayback() async {
187+
_isPlayingBack = false;
188+
_isPlaybackPaused = false;
189+
_playbackTimer?.cancel();
190+
_playbackData = null;
191+
_playbackIndex = 0;
192+
193+
_dustData.clear();
194+
dustChartData.clear();
195+
_timeData.clear();
196+
_dustSum = 0;
197+
_dataCount = 0;
198+
_currentDust = 0.0;
199+
_currentTime = 0;
200+
notifyListeners();
201+
onPlaybackEnd?.call();
202+
}
203+
204+
void pausePlayback() {
205+
if (_isPlayingBack) {
206+
_isPlaybackPaused = true;
207+
_playbackTimer?.cancel();
208+
notifyListeners();
209+
}
210+
}
211+
212+
void resumePlayback() {
213+
if (_isPlayingBack && _isPlaybackPaused) {
214+
_isPlaybackPaused = false;
215+
_startPlaybackTimer();
216+
notifyListeners();
217+
}
218+
}
219+
220+
@override
221+
void dispose() {
222+
if (_locationStream != null) {
223+
_locationStream!.cancel();
224+
}
225+
_playbackTimer?.cancel();
226+
disposeSensors();
227+
super.dispose();
228+
}
229+
230+
void _updateData() {
231+
final dust = _currentDust;
232+
final time = _currentTime;
233+
if (_isRecording) {
234+
final now = DateTime.now();
235+
final dateFormat = DateFormat('yyyy-MM-dd HH:mm:ss.SSS');
236+
_recordedData.add([
237+
now.millisecondsSinceEpoch.toString(),
238+
dateFormat.format(now),
239+
dust.toStringAsFixed(2),
240+
currentPosition?.latitude.toString() ?? 0,
241+
currentPosition?.longitude.toString() ?? 0
242+
]);
243+
}
244+
_dustData.add(dust);
245+
_timeData.add(time);
246+
_dustSum += dust;
247+
_dataCount++;
248+
if (_dustData.length > _chartMaxLength) {
249+
final removedValue = _dustData.removeAt(0);
250+
_timeData.removeAt(0);
251+
_dustSum -= removedValue;
252+
_dataCount--;
253+
}
254+
if (_dustData.isNotEmpty) {
255+
_dustMin = _dustData.reduce(min);
256+
_dustMax = _dustData.reduce(max);
257+
}
258+
dustChartData.clear();
259+
for (int i = 0; i < _dustData.length; i++) {
260+
dustChartData.add(FlSpot(_timeData[i], _dustData[i]));
261+
ppmChartData.add(FlSpot(_timeData[i], _dustData[i] * 0.1));
262+
}
263+
notifyListeners();
264+
}
265+
266+
Future<void> startRecording() async {
267+
await _startGeoLocationUpdates();
268+
_isRecording = true;
269+
_recordedData = [
270+
['Timestamp', 'DateTime', 'Readings', 'Latitude', 'Longitude']
271+
];
272+
notifyListeners();
273+
}
274+
275+
List<List<dynamic>> stopRecording() {
276+
if (_locationStream != null) {
277+
_locationStream!.cancel();
278+
}
279+
_isRecording = false;
280+
notifyListeners();
281+
return _recordedData;
282+
}
283+
284+
double getCurrentDust() => _currentDust;
285+
double getMinDust() => _dustMin;
286+
double getMaxDust() => _dustMax;
287+
double getAverageDust() => _dataCount > 0 ? _dustSum / _dataCount : 0.0;
288+
double getPPM() => _currentDust * 0.1;
289+
double getMaxPPM() => _dustMax * 0.1;
290+
String getAirQuality() {
291+
if (_currentDust < 300) return appLocalizations.good;
292+
if (_currentDust < 1000) return appLocalizations.moderate;
293+
if (_currentDust < 3000) return appLocalizations.unhealthy;
294+
return appLocalizations.hazardous;
295+
}
296+
297+
List<FlSpot> getDustChartData() => dustChartData;
298+
List<FlSpot> getPPMChartData() => ppmChartData;
299+
int getDataLength() => dustChartData.length;
300+
double getCurrentTime() => _currentTime;
301+
double getMaxTime() => _timeData.isNotEmpty ? _timeData.last : 0;
302+
double getMinTime() => _timeData.isNotEmpty ? _timeData.first : 0;
303+
double getTimeInterval() {
304+
if (_currentTime <= 10) return 2;
305+
if (_currentTime <= 30) return 5;
306+
return 10;
307+
}
308+
}

0 commit comments

Comments
 (0)