Skip to content

feat: added guide for robotic arm #2801

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/images/robotic_arm_guide.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
72 changes: 37 additions & 35 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -311,40 +311,42 @@
"baroMeterBulletPoint2": "If you want to use the sensor BMP-180, connect the sensor to PSLab device as shown in the figure.",
"baroMeterBulletPoint3": "The above pin configuration has to be same except for the pin GND. GND is meant for Ground and any of the PSLab device GND pins can be used since they are common.",
"baroMeterBulletPoint4": "Select the sensor by going to the Configure tab from the bottom navigation bar and choose BMP-180 in the drop down menu under Select Sensor.",
"sharingMessage" : "Sharing PSLab Data",
"delete" : "Delete",
"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"
"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",
"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."
}
12 changes: 12 additions & 0 deletions lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2175,6 +2175,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 @roboticArmIntro.
///
/// In en, this message translates to:
/// **'• 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.'**
String get roboticArmIntro;

/// No description provided for @roboticArmConnection.
///
/// In en, this message translates to:
/// **'• 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;
}

class _AppLocalizationsDelegate
Expand Down
8 changes: 8 additions & 0 deletions lib/l10n/app_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1115,4 +1115,12 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get accelerometerHighLimitHint =>
'Please provide the maximum limit of lux value to be recorded';

@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.';

@override
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.';
}
158 changes: 98 additions & 60 deletions lib/view/robotic_arm_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:pslab/view/widgets/robotic_arm_dialog.dart';
import 'package:pslab/view/widgets/robotic_arm_summary.dart';
import 'package:pslab/view/widgets/robotic_arm_timeline.dart';
import '../providers/robotic_arm_state_provider.dart';
import 'package:pslab/view/widgets/guide_widget.dart';
import 'widgets/servo_card.dart';

class RoboticArmScreen extends StatefulWidget {
Expand All @@ -20,6 +21,8 @@ class RoboticArmScreen extends StatefulWidget {
class _RoboticArmScreenState extends State<RoboticArmScreen> {
late RoboticArmStateProvider provider;
late List<String> servoLabels;
bool _showGuide = false;
static const imagePath = 'assets/images/robotic_arm_guide.png';
AppLocalizations appLocalizations = getIt.get<AppLocalizations>();
@override
void initState() {
Expand All @@ -46,6 +49,26 @@ class _RoboticArmScreenState extends State<RoboticArmScreen> {
};
}

void _hideInstrumentGuide() {
setState(() {
_showGuide = false;
});
}

List<Widget> _getRoboticArmContent() {
return [
InstrumentIntroText(
text: appLocalizations.roboticArmIntro,
),
const InstrumentImage(
imagePath: imagePath,
),
InstrumentIntroText(
text: appLocalizations.roboticArmConnection,
),
];
}

void _showAngleInputDialog(BuildContext context, int index) {
final currentValue = provider.servoValues[index];

Expand Down Expand Up @@ -83,7 +106,7 @@ class _RoboticArmScreenState extends State<RoboticArmScreen> {
final screenWidth = MediaQuery.of(context).size.width;
final scrollAmount = (screenWidth / 6);
return CommonScaffold(
title: appLocalizations.roboticArm,
title: appLocalizations.roboticArmTitle,
actions: [
IconButton(
icon: Icon(
Expand Down Expand Up @@ -160,12 +183,16 @@ class _RoboticArmScreenState extends State<RoboticArmScreen> {
IconButton(
icon: const Icon(Icons.save, color: Colors.white),
tooltip: appLocalizations.saveData,
onPressed: () {}, //TODO
onPressed: () {}, // TODO
),
IconButton(
icon: const Icon(Icons.info, color: Colors.white),
tooltip: appLocalizations.showGuide,
onPressed: () {}, //TODO
onPressed: () {
setState(() {
_showGuide = !_showGuide;
});
},
),
PopupMenuButton<String>(
icon: const Icon(Icons.more_vert, color: Colors.white),
Expand All @@ -182,67 +209,78 @@ class _RoboticArmScreenState extends State<RoboticArmScreen> {
],
),
],
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: servoHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate(4, (index) {
return Expanded(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 1),
child: SizedBox(
height: servoHeight,
child: ServoCard(
value: provider.servoValues[index],
label: servoLabels[index],
servoId: index,
onChanged: (val) {
setState(() {
provider.updateServoValue(index, val);
});
},
onTap: () =>
_showAngleInputDialog(context, index),
cardHeight: servoHeight,
body: Stack(
children: [
SafeArea(
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: servoHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate(4, (index) {
return Expanded(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 1),
child: SizedBox(
height: servoHeight,
child: ServoCard(
value: provider.servoValues[index],
label: servoLabels[index],
servoId: index,
onChanged: (val) {
setState(() {
provider.updateServoValue(index, val);
});
},
onTap: () =>
_showAngleInputDialog(context, index),
cardHeight: servoHeight,
),
),
),
),
);
}),
),
),
const SizedBox(height: 2),
Expanded(
child: Scrollbar(
controller: provider.timelineScrollController,
thumbVisibility: true,
thickness: screenHeight * 0.006,
radius: const Radius.circular(4),
child: TimelineScrollView(
totalTimelineItems: provider.totalTimelineItems,
screenHeight: screenHeight,
timelinePosition: provider.timelinePosition,
timelineDegrees: provider.timelineDegrees,
scrollController:
provider.timelineScrollController,
onUpdate: (index, servo, value) {
setState(() {
provider.updateTimelineDegree(
index, servo, value);
});
},
),
);
}),
),
),
)
],
),
const SizedBox(height: 2),
Expanded(
child: Scrollbar(
controller: provider.timelineScrollController,
thumbVisibility: true,
thickness: screenHeight * 0.006,
radius: const Radius.circular(4),
child: TimelineScrollView(
totalTimelineItems: provider.totalTimelineItems,
screenHeight: screenHeight,
timelinePosition: provider.timelinePosition,
timelineDegrees: provider.timelineDegrees,
scrollController: provider.timelineScrollController,
onUpdate: (index, servo, value) {
setState(() {
provider.updateTimelineDegree(
index, servo, value);
});
},
),
),
)
],
),
),
),
if (_showGuide)
InstrumentOverviewDrawer(
instrumentName: appLocalizations.roboticArmTitle,
content: _getRoboticArmContent(),
onHide: _hideInstrumentGuide,
),
],
),
);
},
Expand Down
Loading