Skip to content
39 changes: 38 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,43 @@
"sharingMessage" : "Sharing PSLab Data",
"delete" : "Delete",
"deleteHint": "Are you sure you want to delete this file?",
"deleteFile" : "Delete File",
"deleteAllData" : "Delete All Data",
"deleteCautionMessage" : "Are you sure you want to delete all logged data for this instrument?",
"deleteAll" : "Delete All",
"noLoggedData" : "No logged data found.",
"importLog" : "Import Log",
"failedToSave" : "Failed to save file. No data was recorded.",
"fileSaved" : "File saved",
"save" : "Save",
"enterFileName" : "Enter filename (leave empty for auto-generated name)",
"fileName" : "Filename",
"saveRecording" : "Save Recording",
"recordingStarted" : "Recording started",
"noValidData" : "No valid data to display.",
"csvPickingError" : "Error picking or reading CSV file",
"csvReadingError" : "Error reading CSV from file",
"sharingError" : "Error sharing file",
"csvGettingError" : "Error getting saved files",
"unsupportedPlatform" : "Unsupported platform",
"noDataRecorded" : "No data recorded to save for",
"csvFileSaved" : "CSV file saved at",
"csvSavingError" : "Error saving CSV file",
"csvDeletingError" : "Error deleting file",
"fileDeleted" : "File deleted",
"soundmeterConfig" : "Soundmeter Configurations",
"barometerConfig" : "Barometer Configurations",
"baroUpdatePeriodHint" : "Please provide time interval at which data will be updated (100 ms to 2000 ms)",
"barometerHighLimitHint" : "Please provide the maximum limit of lux value to be recorded (0 atm to 1.10 atm)",
"gyroscopeConfigurations" : "Gyroscope Configurations",
"gyroscopeHighLimitHint" : "Please provide the maximum limit of lux value to be recorded (0 rad/s to 1000 rad/s)",
"accelerometerConfigurations" : "Accelerometer Configurations",
"accelerometerUpdatePeriodHint" : "Please provide time interval at which data will be updated",
"accelerometerHighLimitHint" : "Please provide the maximum limit of lux value to be recorded",
"soundmeterSnackBarMessage" : "Unable to access sound sensor",
"dangerous" : "Dangerous",
"roboticArmIntro": "• A robotic arm is a programmable mechanical device that mimics the movement of a human arm.\n• It uses servo motors to control its motion, and these motors are operated using PWM signals.\n• The PSLab provides four PWM square wave generators (SQ1, SQ2, SQ3, SQ4), allowing control of up to four servo motors and enabling a robotic arm with up to four degrees of freedom.",
"roboticArmConnection": "• In the above figure, SQ1 is connected to the signal pin of the first servo motor. The servo's GND pin is connected to both the PSLab’s GND and the external power supply GND, while the VCC pin is connected to the external power supply VCC.\n• Similarly, connect the remaining servos to SQ2, SQ3, and SQ4 along with their respective GND and power supply connections.\n• Once connected, each servo can be controlled using either circular sliders for manual control or a timeline-based sequence for automated movement.",
"documentationLink" : "https://docs.pslab.io/",
"documentationError" : "Could not open the documentation link",
"deleteFile": "Delete File",
Expand Down Expand Up @@ -391,4 +428,4 @@
"time" : "Time",
"notAvailable" : "N/A",
"estimated" : "Estimated"
}
}
36 changes: 24 additions & 12 deletions lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2086,18 +2086,6 @@ abstract class AppLocalizations {
/// **'Are you sure you want to delete this file?'**
String get deleteHint;

/// No description provided for @documentationLink.
///
/// In en, this message translates to:
/// **'https://docs.pslab.io/'**
String get documentationLink;

/// No description provided for @documentationError.
///
/// In en, this message translates to:
/// **'Could not open the documentation link'**
String get documentationError;

/// No description provided for @deleteFile.
///
/// In en, this message translates to:
Expand Down Expand Up @@ -2296,6 +2284,18 @@ abstract class AppLocalizations {
/// **'Please provide the maximum limit of lux value to be recorded'**
String get accelerometerHighLimitHint;

/// No description provided for @soundmeterSnackBarMessage.
///
/// In en, this message translates to:
/// **'Unable to access sound sensor'**
String get soundmeterSnackBarMessage;

/// No description provided for @dangerous.
///
/// In en, this message translates to:
/// **'Dangerous'**
String get dangerous;

/// No description provided for @roboticArmIntro.
///
/// In en, this message translates to:
Expand All @@ -2308,6 +2308,18 @@ abstract class AppLocalizations {
/// **'• In the above figure, SQ1 is connected to the signal pin of the first servo motor. The servo\'s GND pin is connected to both the PSLab’s GND and the external power supply GND, while the VCC pin is connected to the external power supply VCC.\n• Similarly, connect the remaining servos to SQ2, SQ3, and SQ4 along with their respective GND and power supply connections.\n• Once connected, each servo can be controlled using either circular sliders for manual control or a timeline-based sequence for automated movement.'**
String get roboticArmConnection;

/// No description provided for @documentationLink.
///
/// In en, this message translates to:
/// **'https://docs.pslab.io/'**
String get documentationLink;

/// No description provided for @documentationError.
///
/// In en, this message translates to:
/// **'Could not open the documentation link'**
String get documentationError;

/// No description provided for @autoscan.
///
/// In en, this message translates to:
Expand Down
18 changes: 12 additions & 6 deletions lib/l10n/app_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1067,12 +1067,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get deleteHint => 'Are you sure you want to delete this file?';

@override
String get documentationLink => 'https://docs.pslab.io/';

@override
String get documentationError => 'Could not open the documentation link';

@override
String get deleteFile => 'Delete File';

Expand Down Expand Up @@ -1179,6 +1173,12 @@ class AppLocalizationsEn extends AppLocalizations {
String get accelerometerHighLimitHint =>
'Please provide the maximum limit of lux value to be recorded';

@override
String get soundmeterSnackBarMessage => 'Unable to access sound sensor';

@override
String get dangerous => 'Dangerous';

@override
String get roboticArmIntro =>
'• A robotic arm is a programmable mechanical device that mimics the movement of a human arm.\n• It uses servo motors to control its motion, and these motors are operated using PWM signals.\n• The PSLab provides four PWM square wave generators (SQ1, SQ2, SQ3, SQ4), allowing control of up to four servo motors and enabling a robotic arm with up to four degrees of freedom.';
Expand All @@ -1187,6 +1187,12 @@ class AppLocalizationsEn extends AppLocalizations {
String get roboticArmConnection =>
'• In the above figure, SQ1 is connected to the signal pin of the first servo motor. The servo\'s GND pin is connected to both the PSLab’s GND and the external power supply GND, while the VCC pin is connected to the external power supply VCC.\n• Similarly, connect the remaining servos to SQ2, SQ3, and SQ4 along with their respective GND and power supply connections.\n• Once connected, each servo can be controlled using either circular sliders for manual control or a timeline-based sequence for automated movement.';

@override
String get documentationLink => 'https://docs.pslab.io/';

@override
String get documentationError => 'Could not open the documentation link';

@override
String get autoscan => 'Autoscan';

Expand Down
11 changes: 6 additions & 5 deletions lib/providers/luxmeter_state_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ class LuxMeterStateProvider extends ChangeNotifier {
bool _sensorAvailable = false;
bool _isRecording = false;
List<List<dynamic>> _recordedData = [];
double _recordingStartTime = 0.0;
bool get isRecording => _isRecording;

LuxMeterConfigProvider? _configProvider;
Expand Down Expand Up @@ -112,13 +111,14 @@ class LuxMeterStateProvider extends ChangeNotifier {
final time = _currentTime;
if (lux != null) {
if (_isRecording) {
final relativeTime = time - _recordingStartTime;
final now = DateTime.now();
final dateFormat = DateFormat('yyyy-MM-dd HH:mm:ss.SSS');
_recordedData.add([
now.millisecondsSinceEpoch.toString(),
dateFormat.format(now),
relativeTime.toStringAsFixed(2),
lux.toStringAsFixed(2),
0,
0
]);
}

Expand Down Expand Up @@ -146,8 +146,9 @@ class LuxMeterStateProvider extends ChangeNotifier {

void startRecording() {
_isRecording = true;
_recordingStartTime = _currentTime;
_recordedData = [];
_recordedData = [
['Timestamp', 'DateTime', 'Readings', 'Latitude', 'Longitude']
];
notifyListeners();
}

Expand Down
45 changes: 42 additions & 3 deletions lib/providers/soundmeter_state_provider.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:math';
import 'package:fl_chart/fl_chart.dart';
import 'package:intl/intl.dart';
import 'package:pslab/l10n/app_localizations.dart';
import 'package:pslab/others/logger_service.dart';
import 'package:flutter/foundation.dart';
Expand All @@ -18,13 +19,20 @@ class SoundMeterStateProvider extends ChangeNotifier {
AudioJack? _audioJack;
double _startTime = 0;
double _currentTime = 0;
final int _maxLength = 50;
final int _chartMaxLength = 50;
double _dbMin = 0;
double _dbMax = 0;
double _dbSum = 0;
int _dataCount = 0;
bool _isRecording = false;
List<List<dynamic>> _recordedData = [];
bool get isRecording => _isRecording;

Function(String)? onSensorError;

void initializeSensors({Function(String)? onError}) async {
onSensorError = onError;

void initializeSensors() async {
try {
_audioJack = AudioJack();
await _audioJack!.initialize();
Expand All @@ -50,9 +58,15 @@ class SoundMeterStateProvider extends ChangeNotifier {
});
} catch (e) {
logger.e("${appLocalizations.soundMeterInitialError} $e");
_handleSensorError(e);
}
}

void _handleSensorError(dynamic error) {
onSensorError?.call(appLocalizations.soundmeterSnackBarMessage);
logger.e("${appLocalizations.soundMeterInitialError} $error");
}

double _calculateDecibels(List<double> audioData) {
if (audioData.isEmpty) return 0.0;

Expand Down Expand Up @@ -87,11 +101,22 @@ class SoundMeterStateProvider extends ChangeNotifier {
void _updateData() {
final db = _currentDb;
final time = _currentTime;
if (_isRecording) {
final now = DateTime.now();
final dateFormat = DateFormat('yyyy-MM-dd HH:mm:ss.SSS');
_recordedData.add([
now.millisecondsSinceEpoch.toString(),
dateFormat.format(now),
db.toStringAsFixed(2),
0,
0
]);
}
_dbData.add(db);
_timeData.add(time);
_dbSum += db;
_dataCount++;
if (_dbData.length > _maxLength) {
if (_dbData.length > _chartMaxLength) {
final removedValue = _dbData.removeAt(0);
_timeData.removeAt(0);
_dbSum -= removedValue;
Expand All @@ -108,6 +133,20 @@ class SoundMeterStateProvider extends ChangeNotifier {
notifyListeners();
}

void startRecording() {
_isRecording = true;
_recordedData = [
['Timestamp', 'DateTime', 'Readings', 'Latitude', 'Longitude']
];
notifyListeners();
}

List<List<dynamic>> stopRecording() {
_isRecording = false;
notifyListeners();
return _recordedData;
}

double getCurrentDb() => _currentDb;
double getMinDb() => _dbMin;
double getMaxDb() => _dbMax;
Expand Down
63 changes: 47 additions & 16 deletions lib/view/logged_data_chart_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class LoggedDataChartScreen extends StatefulWidget {
required this.fileName,
this.xAxisLabel = 'Time (s)',
this.yAxisLabel = 'Value',
this.xDataColumnIndex = 1,
this.xDataColumnIndex = 0,
this.yDataColumnIndex = 2,
});

Expand Down Expand Up @@ -105,18 +105,7 @@ class _LoggedDataChartScreenState extends State<LoggedDataChartScreen> {
sideTitles: SideTitles(
showTitles: true,
reservedSize: reservedSizeBottom,
getTitlesWidget: (value, meta) {
return SideTitleWidget(
meta: meta,
child: Text(
value.toStringAsFixed(1),
style: TextStyle(
color: blackTextColor,
fontSize: chartFontSize,
),
),
);
},
getTitlesWidget: _sideTitleWidgets,
interval: timeInterval,
),
),
Expand Down Expand Up @@ -195,6 +184,7 @@ class _LoggedDataChartScreenState extends State<LoggedDataChartScreen> {
double maxY = 0;
double maxX = 0;
double minX = 0;
double? startTime;

for (int i = 1; i < widget.data.length; i++) {
final row = widget.data[i];
Expand All @@ -204,10 +194,16 @@ class _LoggedDataChartScreenState extends State<LoggedDataChartScreen> {
final yValue = _parseDouble(row[widget.yDataColumnIndex]);

if (xValue != null && yValue != null) {
spots.add(FlSpot(xValue, yValue));
if (startTime == null) {
startTime = xValue;
minX = 0;
}

final relativeTime = ((xValue - startTime) / 1000.0);

spots.add(FlSpot(relativeTime, yValue));
if (yValue > maxY) maxY = yValue;
if (xValue > maxX) maxX = xValue;
if (spots.length == 1 || xValue < minX) minX = xValue;
if (relativeTime > maxX) maxX = relativeTime;
}
}
}
Expand Down Expand Up @@ -252,4 +248,39 @@ class _LoggedDataChartScreenState extends State<LoggedDataChartScreen> {
),
);
}

Widget _sideTitleWidgets(double value, TitleMeta meta) {
final screenWidth = MediaQuery.of(context).size.width;
final fontSize = screenWidth < 400
? 7.0
: screenWidth < 600
? 8.0
: 9.0;
final style = TextStyle(
color: blackTextColor,
fontSize: fontSize,
);

String timeText;
if (value < 60) {
timeText = '${value.toInt()}s';
} else if (value < 3600) {
int minutes = (value / 60).floor();
int seconds = (value % 60).toInt();
timeText = '${minutes}m${seconds}s';
} else {
int hours = (value / 3600).floor();
int minutes = ((value % 3600) / 60).floor();
timeText = '${hours}h${minutes}m';
}

return SideTitleWidget(
meta: meta,
child: Text(
maxLines: 1,
timeText,
style: style,
),
);
}
}
4 changes: 2 additions & 2 deletions lib/view/logged_data_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,14 @@ class _LoggedDataScreenState extends State<LoggedDataScreen> {
return {
'xAxisLabel': appLocalizations.timeAxisLabel,
'yAxisLabel': appLocalizations.lx,
'xDataColumnIndex': 1,
'xDataColumnIndex': 0,
'yDataColumnIndex': 2,
};
case 'soundmeter':
return {
'xAxisLabel': appLocalizations.timeAxisLabel,
'yAxisLabel': appLocalizations.db,
'xDataColumnIndex': 1,
'xDataColumnIndex': 0,
'yDataColumnIndex': 2,
};
case 'barometer':
Expand Down
Loading
Loading