From 0577bdafacf3fb638df3e3e241471a9946d90cb5 Mon Sep 17 00:00:00 2001 From: Yugesh-Kumar-S Date: Sun, 20 Jul 2025 12:39:23 +0530 Subject: [PATCH 1/5] soundmeter csv functionalities --- lib/l10n/app_en.arb | 4 +- lib/l10n/app_localizations.dart | 12 + lib/l10n/app_localizations_en.dart | 7 + lib/providers/soundmeter_state_provider.dart | 44 +++- lib/view/soundmeter_screen.dart | 226 +++++++++++++++---- 5 files changed, 243 insertions(+), 50 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 3f2278495..d5170fd2c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -341,5 +341,7 @@ "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" + "accelerometerHighLimitHint" : "Please provide the maximum limit of lux value to be recorded", + "soundmeterSnackBarMessage" : "Unable to access sound sensor", + "dangerous" : "Dangerous" } \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 1f83d13e8..e11173740 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2145,6 +2145,18 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'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; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 04d10402b..525e76f59 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1069,6 +1069,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get fileDeleted => 'File deleted'; + @override String get soundmeterConfig => 'Soundmeter Configurations'; @override @@ -1099,4 +1100,10 @@ class AppLocalizationsEn extends AppLocalizations { @override 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'; } diff --git a/lib/providers/soundmeter_state_provider.dart b/lib/providers/soundmeter_state_provider.dart index 21dcdf731..f01e7e6aa 100644 --- a/lib/providers/soundmeter_state_provider.dart +++ b/lib/providers/soundmeter_state_provider.dart @@ -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'; @@ -18,13 +19,21 @@ 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> _recordedData = []; + double _recordingStartTime = 0.0; + bool get isRecording => _isRecording; + + Function(String)? onSensorError; + + void initializeSensors({Function(String)? onError}) async { + onSensorError = onError; - void initializeSensors() async { try { _audioJack = AudioJack(); await _audioJack!.initialize(); @@ -50,9 +59,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 audioData) { if (audioData.isEmpty) return 0.0; @@ -87,11 +102,21 @@ class SoundMeterStateProvider extends ChangeNotifier { void _updateData() { final db = _currentDb; final time = _currentTime; + if (_isRecording) { + final relativeTime = time - _recordingStartTime; + final now = DateTime.now(); + final dateFormat = DateFormat('yyyy-MM-dd HH:mm:ss.SSS'); + _recordedData.add([ + dateFormat.format(now), + relativeTime.toStringAsFixed(2), + db.toStringAsFixed(2), + ]); + } _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; @@ -108,6 +133,19 @@ class SoundMeterStateProvider extends ChangeNotifier { notifyListeners(); } + void startRecording() { + _isRecording = true; + _recordingStartTime = _currentTime; + _recordedData = []; + notifyListeners(); + } + + List> stopRecording() { + _isRecording = false; + notifyListeners(); + return _recordedData; + } + double getCurrentDb() => _currentDb; double getMinDb() => _dbMin; double getMaxDb() => _dbMax; diff --git a/lib/view/soundmeter_screen.dart b/lib/view/soundmeter_screen.dart index 570df2c1e..7574ba53d 100644 --- a/lib/view/soundmeter_screen.dart +++ b/lib/view/soundmeter_screen.dart @@ -8,7 +8,10 @@ import 'package:pslab/view/widgets/common_scaffold_widget.dart'; import 'package:pslab/view/widgets/guide_widget.dart'; import 'package:pslab/view/widgets/soundmeter_card.dart'; import 'package:fl_chart/fl_chart.dart'; +import 'package:pslab/others/csv_service.dart'; +import 'package:pslab/view/logged_data_screen.dart'; import '../providers/soundmeter_config_provider.dart'; +import '../constants.dart'; import '../theme/colors.dart'; class SoundMeterScreen extends StatefulWidget { @@ -19,8 +22,11 @@ class SoundMeterScreen extends StatefulWidget { class _SoundMeterScreenState extends State { AppLocalizations appLocalizations = getIt.get(); + final CsvService _csvService = CsvService(); + late SoundMeterStateProvider _provider; bool _showGuide = false; static const imagePath = 'assets/images/bh1750_schematic.png'; + void _showInstrumentGuide() { setState(() { _showGuide = true; @@ -71,7 +77,7 @@ class _SoundMeterScreenState extends State { if (value != null) { switch (value) { case 'show_logged_data': - // TODO + _navigateToLoggedData(); break; case 'sound_meter_config': _navigateToConfig(); @@ -93,54 +99,182 @@ class _SoundMeterScreenState extends State { ); } + Future _navigateToLoggedData() async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => LoggedDataScreen( + instrumentName: 'soundmeter', + appBarName: 'Sound Meter', + instrumentIcon: instrumentIcons[15], + ), + ), + ); + } + + Future _toggleRecording() async { + if (_provider.isRecording) { + final data = _provider.stopRecording(); + await _showSaveFileDialog(data); + } else { + _provider.startRecording(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + '${appLocalizations.recordingStarted}...', + style: TextStyle(color: snackBarContentColor), + ), + backgroundColor: snackBarBackgroundColor, + ), + ); + } + } + + Future _showSaveFileDialog(List> data) async { + final TextEditingController filenameController = TextEditingController(); + final String defaultFilename = ''; + filenameController.text = defaultFilename; + + final String? fileName = await showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text(appLocalizations.saveRecording), + content: TextField( + controller: filenameController, + decoration: InputDecoration( + hintText: appLocalizations.enterFileName, + labelText: appLocalizations.fileName, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(appLocalizations.cancel), + ), + ElevatedButton( + onPressed: () { + Navigator.pop(context, filenameController.text); + }, + child: Text(appLocalizations.save), + ), + ], + ); + }, + ); + + if (fileName != null) { + _csvService.writeMetaData('soundmeter', data); + final file = await _csvService.saveCsvFile('soundmeter', fileName, data); + if (mounted) { + if (file != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + '${appLocalizations.fileSaved}: ${file.path.split('/').last}', + style: TextStyle(color: snackBarContentColor), + ), + backgroundColor: snackBarBackgroundColor, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + appLocalizations.failedToSave, + style: TextStyle(color: snackBarContentColor), + ), + backgroundColor: snackBarBackgroundColor, + ), + ); + } + } + } + } + @override - Widget build(BuildContext context) { - return MultiProvider( - providers: [ - ChangeNotifierProvider( - create: (_) => SoundMeterStateProvider()..initializeSensors(), + void initState() { + super.initState(); + _provider = SoundMeterStateProvider(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _provider.initializeSensors(onError: _showSensorErrorSnackbar); + } + }); + } + + @override + void dispose() { + _provider.dispose(); + super.dispose(); + } + + void _showSensorErrorSnackbar(String message) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + message, + style: TextStyle(color: snackBarContentColor), + ), + backgroundColor: snackBarBackgroundColor, + duration: const Duration(seconds: 4), + behavior: SnackBarBehavior.floating, ), - ], + ); + } + } + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider.value( + value: _provider, child: Stack( children: [ - CommonScaffold( - title: appLocalizations.soundMeterTitle, - onGuidePressed: _showInstrumentGuide, - onOptionsPressed: _showOptionsMenu, - body: SafeArea( - child: LayoutBuilder( - builder: (context, constraints) { - final isLargeScreen = constraints.maxWidth > 900; - if (isLargeScreen) { - return Row( - children: [ - const Expanded( - flex: 35, - child: SoundMeterCard(), - ), - Expanded( - flex: 65, - child: _buildChartSection(), - ), - ], - ); - } else { - return Column( - children: [ - const Expanded( - flex: 45, - child: SoundMeterCard(), - ), - Expanded( - flex: 55, - child: _buildChartSection(), - ), - ], - ); - } - }, - ), - ), + Consumer( + builder: (context, provider, child) { + return CommonScaffold( + title: appLocalizations.soundMeterTitle, + onGuidePressed: _showInstrumentGuide, + onOptionsPressed: _showOptionsMenu, + onRecordPressed: _toggleRecording, + isRecording: provider.isRecording, + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + final isLargeScreen = constraints.maxWidth > 900; + if (isLargeScreen) { + return Row( + children: [ + const Expanded( + flex: 35, + child: SoundMeterCard(), + ), + Expanded( + flex: 65, + child: _buildChartSection(), + ), + ], + ); + } else { + return Column( + children: [ + const Expanded( + flex: 45, + child: SoundMeterCard(), + ), + Expanded( + flex: 55, + child: _buildChartSection(), + ), + ], + ); + } + }, + ), + ), + ); + }, ), if (_showGuide) InstrumentOverviewDrawer( @@ -318,7 +452,7 @@ class _SoundMeterScreenState extends State { color: soundMeterSafeLimitColor, fontSize: 12, ), - labelResolver: (line) => '"Dangerous"', + labelResolver: (line) => appLocalizations.dangerous, ), ), ], From 7557f1e9bdf1cca5f9d51838327eccc4bd9f6291 Mon Sep 17 00:00:00 2001 From: Yugesh-Kumar-S Date: Tue, 22 Jul 2025 02:24:57 +0530 Subject: [PATCH 2/5] changes --- lib/providers/luxmeter_state_provider.dart | 11 ++-- lib/providers/soundmeter_state_provider.dart | 11 ++-- lib/view/logged_data_chart_screen.dart | 63 +++++++++++++++----- lib/view/logged_data_screen.dart | 4 +- 4 files changed, 61 insertions(+), 28 deletions(-) diff --git a/lib/providers/luxmeter_state_provider.dart b/lib/providers/luxmeter_state_provider.dart index c3bcff93d..5dc761c4f 100644 --- a/lib/providers/luxmeter_state_provider.dart +++ b/lib/providers/luxmeter_state_provider.dart @@ -28,7 +28,6 @@ class LuxMeterStateProvider extends ChangeNotifier { bool _sensorAvailable = false; bool _isRecording = false; List> _recordedData = []; - double _recordingStartTime = 0.0; bool get isRecording => _isRecording; LuxMeterConfigProvider? _configProvider; @@ -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 ]); } @@ -146,8 +146,9 @@ class LuxMeterStateProvider extends ChangeNotifier { void startRecording() { _isRecording = true; - _recordingStartTime = _currentTime; - _recordedData = []; + _recordedData = [ + ['Timestamp', 'DateTime', 'Readings', 'Latitude', 'Longitude'] + ]; notifyListeners(); } diff --git a/lib/providers/soundmeter_state_provider.dart b/lib/providers/soundmeter_state_provider.dart index f01e7e6aa..475ffe58d 100644 --- a/lib/providers/soundmeter_state_provider.dart +++ b/lib/providers/soundmeter_state_provider.dart @@ -26,7 +26,6 @@ class SoundMeterStateProvider extends ChangeNotifier { int _dataCount = 0; bool _isRecording = false; List> _recordedData = []; - double _recordingStartTime = 0.0; bool get isRecording => _isRecording; Function(String)? onSensorError; @@ -103,13 +102,14 @@ class SoundMeterStateProvider extends ChangeNotifier { final db = _currentDb; final time = _currentTime; 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), db.toStringAsFixed(2), + 0, + 0 ]); } _dbData.add(db); @@ -135,8 +135,9 @@ class SoundMeterStateProvider extends ChangeNotifier { void startRecording() { _isRecording = true; - _recordingStartTime = _currentTime; - _recordedData = []; + _recordedData = [ + ['Timestamp', 'DateTime', 'Readings', 'Latitude', 'Longitude'] + ]; notifyListeners(); } diff --git a/lib/view/logged_data_chart_screen.dart b/lib/view/logged_data_chart_screen.dart index 01fa0a56a..a84cb6252 100644 --- a/lib/view/logged_data_chart_screen.dart +++ b/lib/view/logged_data_chart_screen.dart @@ -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, }); @@ -105,18 +105,7 @@ class _LoggedDataChartScreenState extends State { 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, ), ), @@ -195,6 +184,7 @@ class _LoggedDataChartScreenState extends State { 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]; @@ -204,10 +194,16 @@ class _LoggedDataChartScreenState extends State { 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; } } } @@ -252,4 +248,39 @@ class _LoggedDataChartScreenState extends State { ), ); } + + 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, + ), + ); + } } diff --git a/lib/view/logged_data_screen.dart b/lib/view/logged_data_screen.dart index bbee6db8d..4a0c9579b 100644 --- a/lib/view/logged_data_screen.dart +++ b/lib/view/logged_data_screen.dart @@ -112,14 +112,14 @@ class _LoggedDataScreenState extends State { 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': From de2701cd86bf1b8835da2ffddeec056c48182ab8 Mon Sep 17 00:00:00 2001 From: Yugesh Kumar Date: Sat, 2 Aug 2025 23:33:41 +0530 Subject: [PATCH 3/5] Update app_en.arb --- lib/l10n/app_en.arb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 82cbab0f1..5066db77c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -348,7 +348,7 @@ "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" + "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." -} \ No newline at end of file +} From 1c0ca1a315231713165c2d1bd1bc371585609e43 Mon Sep 17 00:00:00 2001 From: Yugesh-Kumar-S Date: Tue, 5 Aug 2025 22:17:34 +0530 Subject: [PATCH 4/5] removed manage-external-storage permission --- android/app/src/main/AndroidManifest.xml | 1 - lib/others/csv_service.dart | 15 ++------------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e01bfbba7..f84359346 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -5,7 +5,6 @@ - getInstrumentDirectory(String instrumentName) async { if (Platform.isAndroid) { - await requestStoragePermission(); - final directory = - Directory('/storage/emulated/0/Android/media/PSLab/$instrumentName'); + final externalDir = await getExternalStorageDirectory(); + final directory = Directory('${externalDir?.path}/PSLab/$instrumentName'); if (!await directory.exists()) { await directory.create(recursive: true); } @@ -38,15 +36,6 @@ class CsvService { } } - Future requestStoragePermission() async { - if (Platform.isAndroid) { - final status = await Permission.manageExternalStorage.request(); - if (!status.isGranted) { - await openAppSettings(); - } - } - } - Future saveCsvFile( String instrumentName, String fileName, List> data) async { try { From 3318e6b2bf6835ec71b17acf5bc7287f55fd3884 Mon Sep 17 00:00:00 2001 From: Marc Nause Date: Sat, 16 Aug 2025 09:12:49 +0200 Subject: [PATCH 5/5] fix bug introduced in merge commit --- lib/l10n/app_en.arb | 2 +- lib/l10n/app_localizations.dart | 24 ++++++++++++------------ lib/l10n/app_localizations_en.dart | 13 +++++++------ 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index e8126119c..ab532dc6c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -368,7 +368,7 @@ "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." + "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", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 9284c2f86..b2ccb018a 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -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: @@ -2320,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: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 9661870f0..3d5b31d41 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -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'; @@ -1185,6 +1179,7 @@ class AppLocalizationsEn extends AppLocalizations { @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.'; @@ -1192,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';