Skip to content

Commit 63c3d3d

Browse files
feat: Ported robotic arm (#2729)
Co-authored-by: Marc Nause <marc.nause@audioattack.de>
1 parent 3a64431 commit 63c3d3d

9 files changed

Lines changed: 715 additions & 0 deletions

File tree

lib/communication/science_lab.dart

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,4 +413,44 @@ class ScienceLab {
413413
logger.e(e);
414414
}
415415
}
416+
417+
Future<void> servo4(
418+
double angle1, double angle2, double angle3, double angle4) async {
419+
const int period = 10000;
420+
const int base = 750;
421+
const int range = 1900;
422+
const int params = (1 << 5) | 2;
423+
424+
try {
425+
mPacketHandler.sendByte(mCommandsProto.wavegen);
426+
427+
mPacketHandler.sendByte(mCommandsProto.sqr4);
428+
429+
mPacketHandler.sendInt(period);
430+
431+
int pulse1 = base + (angle1 * range ~/ 180);
432+
mPacketHandler.sendInt(pulse1);
433+
434+
mPacketHandler.sendInt(0);
435+
436+
int pulse2 = base + (angle2 * range ~/ 180);
437+
mPacketHandler.sendInt(pulse2);
438+
439+
mPacketHandler.sendInt(0);
440+
441+
int pulse3 = base + (angle3 * range ~/ 180);
442+
mPacketHandler.sendInt(pulse3);
443+
444+
mPacketHandler.sendInt(0);
445+
446+
int pulse4 = base + (angle4 * range ~/ 180);
447+
mPacketHandler.sendInt(pulse4);
448+
449+
mPacketHandler.sendByte(params);
450+
451+
await mPacketHandler.getAcknowledgement();
452+
} catch (e) {
453+
logger.e("Error in servo4(): $e");
454+
}
455+
}
416456
}

lib/constants.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,28 @@ String maxValue = 'Max: ';
208208
String gyroscopeTitle = "Gyroscope";
209209
String gyroscopeAxisLabel = 'rad/s';
210210
String noData = 'No data available';
211+
String degreeSymbol = '°';
212+
String enterAngleRange = 'Enter angle (0 - 360)';
213+
String errorCannotBeEmpty = 'Cannot be empty';
214+
String servoValidNumberRange = 'Please enter a valid number between 0 and 360';
215+
String ok = 'Ok';
216+
String roboticArm = 'Robotic Arm';
217+
String play = 'Play';
218+
String pause = 'Pause';
219+
String stop = 'Stop';
220+
String controls = 'Controls';
221+
String saveData = 'Save Data';
222+
String showGuide = 'Show Guide';
223+
String showLoggedDataKey = 'show_logged_data';
224+
String showLoggedData = 'Show Logged Data';
225+
String setAngle = 'Set angle for Servo';
226+
String angleDialog = 'AngleDialog';
227+
List<String> servoLabels = [
228+
'Servo 1',
229+
'Servo 2',
230+
'Servo 3',
231+
'Servo 4',
232+
];
211233
String xyPlot = 'XY Plot';
212234
String enablePlot = 'Enable XY Plot';
213235
String trigger = 'Trigger';

lib/main.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:pslab/view/gyroscope_screen.dart';
99
import 'package:pslab/view/instruments_screen.dart';
1010
import 'package:pslab/view/luxmeter_screen.dart';
1111
import 'package:pslab/view/oscilloscope_screen.dart';
12+
import 'package:pslab/view/robotic_arm_screen.dart';
1213
import 'package:pslab/view/settings_screen.dart';
1314
import 'package:pslab/view/about_us_screen.dart';
1415
import 'package:pslab/view/software_licenses_screen.dart';
@@ -54,6 +55,7 @@ class MyApp extends StatelessWidget {
5455
'/softwareLicenses': (context) => const SoftwareLicensesScreen(),
5556
'/accelerometer': (context) => const AccelerometerScreen(),
5657
'/gyroscope': (context) => const GyroscopeScreen(),
58+
'/roboticArm': (context) => const RoboticArmScreen(),
5759
'/luxmeter': (context) => const LuxMeterScreen(),
5860
'/soundmeter': (context) => const SoundMeterScreen(),
5961
},
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import 'dart:async';
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter/services.dart';
4+
import 'package:pslab/communication/science_lab.dart';
5+
import 'package:pslab/others/logger_service.dart';
6+
import '../others/science_lab_common.dart';
7+
8+
class RoboticArmStateProvider extends ChangeNotifier {
9+
final List<double> servoValues = [0, 0, 0, 0];
10+
final int totalTimelineItems = 60;
11+
final double scrollAmountPerTick = 120;
12+
final List<List<double>> timelineDegrees =
13+
List.generate(60, (_) => List.filled(4, 0));
14+
int timelinePosition = 0;
15+
bool isPlaying = false;
16+
17+
late ScienceLab scienceLab;
18+
Timer? _debounceTimer;
19+
Timer? _timelineTimer;
20+
final ScrollController timelineScrollController = ScrollController();
21+
22+
Future<void> initialize() async {
23+
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
24+
SystemChrome.setPreferredOrientations([
25+
DeviceOrientation.landscapeLeft,
26+
DeviceOrientation.landscapeRight,
27+
]);
28+
scienceLab =
29+
ScienceLabCommon(ScienceLabCommon.communicationHandler).getScienceLab();
30+
await scienceLab.connect();
31+
}
32+
33+
void disposeResources() {
34+
_timelineTimer?.cancel();
35+
_debounceTimer?.cancel();
36+
timelineScrollController.dispose();
37+
SystemChrome.setPreferredOrientations([
38+
DeviceOrientation.portraitUp,
39+
DeviceOrientation.portraitDown,
40+
]);
41+
}
42+
43+
void updateServoValue(int index, double value) {
44+
servoValues[index] = value;
45+
notifyListeners();
46+
_sendAllServoCommands();
47+
}
48+
49+
void updateTimelineDegree(int timeIndex, int servoIndex, double value) {
50+
timelineDegrees[timeIndex][servoIndex] = value;
51+
notifyListeners();
52+
}
53+
54+
void _sendAllServoCommands() {
55+
_debounceTimer?.cancel();
56+
_debounceTimer = Timer(const Duration(milliseconds: 200), () async {
57+
try {
58+
await scienceLab.servo4(
59+
servoValues[0],
60+
servoValues[1],
61+
servoValues[2],
62+
servoValues[3],
63+
);
64+
} catch (e) {
65+
logger.e(e);
66+
}
67+
});
68+
}
69+
70+
void togglePlayPause() {
71+
if (isPlaying) {
72+
_timelineTimer?.cancel();
73+
isPlaying = false;
74+
notifyListeners();
75+
} else {
76+
_timelineTimer =
77+
Timer.periodic(const Duration(seconds: 1), (timer) async {
78+
if (timelinePosition >= totalTimelineItems) {
79+
stopScrolling(resetPosition: false);
80+
return;
81+
}
82+
83+
timelineScrollController.animateTo(
84+
timelineScrollController.offset + scrollAmountPerTick,
85+
duration: const Duration(milliseconds: 50),
86+
curve: Curves.easeInOut,
87+
);
88+
89+
final angles = timelineDegrees[timelinePosition];
90+
91+
if (scienceLab.isConnected()) {
92+
try {
93+
await scienceLab.servo4(
94+
angles[0],
95+
angles[1],
96+
angles[2],
97+
angles[3],
98+
);
99+
} catch (e) {
100+
logger.e(e);
101+
}
102+
}
103+
104+
timelinePosition++;
105+
notifyListeners();
106+
});
107+
108+
isPlaying = true;
109+
notifyListeners();
110+
}
111+
}
112+
113+
void stopScrolling({bool resetPosition = true}) {
114+
_timelineTimer?.cancel();
115+
isPlaying = false;
116+
timelinePosition = 0;
117+
notifyListeners();
118+
119+
if (resetPosition) {
120+
timelineScrollController.animateTo(
121+
0,
122+
duration: const Duration(milliseconds: 50),
123+
curve: Curves.easeOut,
124+
);
125+
}
126+
}
127+
}

lib/view/instruments_screen.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,18 @@ class _InstrumentsScreenState extends State<InstrumentsScreen> {
6363
);
6464
}
6565
break;
66+
case 12:
67+
if (Navigator.canPop(context) &&
68+
ModalRoute.of(context)?.settings.name == '/roboticArm') {
69+
Navigator.popUntil(context, ModalRoute.withName('/roboticArm'));
70+
} else {
71+
Navigator.pushNamedAndRemoveUntil(
72+
context,
73+
'/roboticArm',
74+
(route) => route.isFirst,
75+
);
76+
}
77+
break;
6678
case 15:
6779
if (Navigator.canPop(context) &&
6880
ModalRoute.of(context)?.settings.name == '/soundmeter') {

0 commit comments

Comments
 (0)