ftc-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +224 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +15 -0
- package/build/knowledge/dashboard.d.ts +8 -0
- package/build/knowledge/dashboard.js +843 -0
- package/build/knowledge/examples.d.ts +1 -0
- package/build/knowledge/examples.js +1800 -0
- package/build/knowledge/ftc-sdk.d.ts +8 -0
- package/build/knowledge/ftc-sdk.js +1088 -0
- package/build/knowledge/ftclib.d.ts +10 -0
- package/build/knowledge/ftclib.js +1403 -0
- package/build/knowledge/gradle.d.ts +8 -0
- package/build/knowledge/gradle.js +956 -0
- package/build/knowledge/hardware.d.ts +22 -0
- package/build/knowledge/hardware.js +1949 -0
- package/build/knowledge/panels.d.ts +10 -0
- package/build/knowledge/panels.js +572 -0
- package/build/knowledge/pedro.d.ts +8 -0
- package/build/knowledge/pedro.js +1548 -0
- package/build/knowledge/roadrunner.d.ts +3 -0
- package/build/knowledge/roadrunner.js +481 -0
- package/build/knowledge/vision.d.ts +12 -0
- package/build/knowledge/vision.js +1869 -0
- package/build/prompts/registry.d.ts +2 -0
- package/build/prompts/registry.js +634 -0
- package/build/resources/registry.d.ts +2 -0
- package/build/resources/registry.js +520 -0
- package/build/server.d.ts +2 -0
- package/build/server.js +17 -0
- package/build/tools/registry.d.ts +2 -0
- package/build/tools/registry.js +660 -0
- package/package.json +34 -0
|
@@ -0,0 +1,1800 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EXAMPLES = void 0;
|
|
4
|
+
exports.EXAMPLES = {
|
|
5
|
+
"pedro-auto": `package org.firstinspires.ftc.teamcode;
|
|
6
|
+
|
|
7
|
+
import com.acmerobotics.dashboard.FtcDashboard;
|
|
8
|
+
import com.acmerobotics.dashboard.config.Config;
|
|
9
|
+
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
|
10
|
+
import com.acmerobotics.dashboard.canvas.Canvas;
|
|
11
|
+
import com.acmerobotics.dashboard.telemetry.TelemetryPacket;
|
|
12
|
+
import com.pedropathing.follower.Follower;
|
|
13
|
+
import com.pedropathing.localization.Pose;
|
|
14
|
+
import com.pedropathing.pathgen.BezierCurve;
|
|
15
|
+
import com.pedropathing.pathgen.BezierLine;
|
|
16
|
+
import com.pedropathing.pathgen.PathChain;
|
|
17
|
+
import com.pedropathing.pathgen.Point;
|
|
18
|
+
import com.pedropathing.util.Timer;
|
|
19
|
+
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
|
20
|
+
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
|
21
|
+
|
|
22
|
+
@Config
|
|
23
|
+
@Autonomous(name = "Pedro Auto - 3 Sample", group = "Auto")
|
|
24
|
+
public class PedroAutoExample extends OpMode {
|
|
25
|
+
|
|
26
|
+
// --- Dashboard-tunable poses ---
|
|
27
|
+
public static double START_X = 9.0;
|
|
28
|
+
public static double START_Y = 60.0;
|
|
29
|
+
public static double START_HEADING = Math.toRadians(0);
|
|
30
|
+
|
|
31
|
+
public static double SCORE_X = 38.0;
|
|
32
|
+
public static double SCORE_Y = 68.0;
|
|
33
|
+
public static double SCORE_HEADING = Math.toRadians(0);
|
|
34
|
+
|
|
35
|
+
public static double SAMPLE1_X = 37.0;
|
|
36
|
+
public static double SAMPLE1_Y = 30.0;
|
|
37
|
+
public static double SAMPLE1_HEADING = Math.toRadians(0);
|
|
38
|
+
|
|
39
|
+
public static double SAMPLE2_X = 37.0;
|
|
40
|
+
public static double SAMPLE2_Y = 20.0;
|
|
41
|
+
public static double SAMPLE2_HEADING = Math.toRadians(0);
|
|
42
|
+
|
|
43
|
+
public static double SAMPLE3_X = 44.0;
|
|
44
|
+
public static double SAMPLE3_Y = 20.0;
|
|
45
|
+
public static double SAMPLE3_HEADING = Math.toRadians(30);
|
|
46
|
+
|
|
47
|
+
public static double PARK_X = 10.0;
|
|
48
|
+
public static double PARK_Y = 10.0;
|
|
49
|
+
public static double PARK_HEADING = Math.toRadians(0);
|
|
50
|
+
|
|
51
|
+
// Control point for curved paths
|
|
52
|
+
public static double CURVE_CONTROL_X = 20.0;
|
|
53
|
+
public static double CURVE_CONTROL_Y = 40.0;
|
|
54
|
+
|
|
55
|
+
private Follower follower;
|
|
56
|
+
private Timer pathTimer;
|
|
57
|
+
private int pathState;
|
|
58
|
+
|
|
59
|
+
// Path chains
|
|
60
|
+
private PathChain scorePreload, grabSample1, scoreSample1;
|
|
61
|
+
private PathChain grabSample2, scoreSample2;
|
|
62
|
+
private PathChain grabSample3, scoreSample3;
|
|
63
|
+
private PathChain park;
|
|
64
|
+
|
|
65
|
+
private void buildPaths() {
|
|
66
|
+
Pose startPose = new Pose(START_X, START_Y, START_HEADING);
|
|
67
|
+
Pose scorePose = new Pose(SCORE_X, SCORE_Y, SCORE_HEADING);
|
|
68
|
+
Pose sample1Pose = new Pose(SAMPLE1_X, SAMPLE1_Y, SAMPLE1_HEADING);
|
|
69
|
+
Pose sample2Pose = new Pose(SAMPLE2_X, SAMPLE2_Y, SAMPLE2_HEADING);
|
|
70
|
+
Pose sample3Pose = new Pose(SAMPLE3_X, SAMPLE3_Y, SAMPLE3_HEADING);
|
|
71
|
+
Pose parkPose = new Pose(PARK_X, PARK_Y, PARK_HEADING);
|
|
72
|
+
|
|
73
|
+
follower.setStartingPose(startPose);
|
|
74
|
+
|
|
75
|
+
scorePreload = follower.pathBuilder()
|
|
76
|
+
.addPath(new BezierLine(
|
|
77
|
+
new Point(startPose),
|
|
78
|
+
new Point(scorePose)))
|
|
79
|
+
.setLinearHeadingInterpolation(startPose.getHeading(), scorePose.getHeading())
|
|
80
|
+
.build();
|
|
81
|
+
|
|
82
|
+
grabSample1 = follower.pathBuilder()
|
|
83
|
+
.addPath(new BezierCurve(
|
|
84
|
+
new Point(scorePose),
|
|
85
|
+
new Point(CURVE_CONTROL_X, CURVE_CONTROL_Y, Point.CARTESIAN),
|
|
86
|
+
new Point(sample1Pose)))
|
|
87
|
+
.setLinearHeadingInterpolation(scorePose.getHeading(), sample1Pose.getHeading())
|
|
88
|
+
.build();
|
|
89
|
+
|
|
90
|
+
scoreSample1 = follower.pathBuilder()
|
|
91
|
+
.addPath(new BezierLine(
|
|
92
|
+
new Point(sample1Pose),
|
|
93
|
+
new Point(scorePose)))
|
|
94
|
+
.setLinearHeadingInterpolation(sample1Pose.getHeading(), scorePose.getHeading())
|
|
95
|
+
.build();
|
|
96
|
+
|
|
97
|
+
grabSample2 = follower.pathBuilder()
|
|
98
|
+
.addPath(new BezierLine(
|
|
99
|
+
new Point(scorePose),
|
|
100
|
+
new Point(sample2Pose)))
|
|
101
|
+
.setLinearHeadingInterpolation(scorePose.getHeading(), sample2Pose.getHeading())
|
|
102
|
+
.build();
|
|
103
|
+
|
|
104
|
+
scoreSample2 = follower.pathBuilder()
|
|
105
|
+
.addPath(new BezierLine(
|
|
106
|
+
new Point(sample2Pose),
|
|
107
|
+
new Point(scorePose)))
|
|
108
|
+
.setLinearHeadingInterpolation(sample2Pose.getHeading(), scorePose.getHeading())
|
|
109
|
+
.build();
|
|
110
|
+
|
|
111
|
+
grabSample3 = follower.pathBuilder()
|
|
112
|
+
.addPath(new BezierCurve(
|
|
113
|
+
new Point(scorePose),
|
|
114
|
+
new Point(CURVE_CONTROL_X, CURVE_CONTROL_Y, Point.CARTESIAN),
|
|
115
|
+
new Point(sample3Pose)))
|
|
116
|
+
.setLinearHeadingInterpolation(scorePose.getHeading(), sample3Pose.getHeading())
|
|
117
|
+
.build();
|
|
118
|
+
|
|
119
|
+
scoreSample3 = follower.pathBuilder()
|
|
120
|
+
.addPath(new BezierLine(
|
|
121
|
+
new Point(sample3Pose),
|
|
122
|
+
new Point(scorePose)))
|
|
123
|
+
.setLinearHeadingInterpolation(sample3Pose.getHeading(), scorePose.getHeading())
|
|
124
|
+
.build();
|
|
125
|
+
|
|
126
|
+
park = follower.pathBuilder()
|
|
127
|
+
.addPath(new BezierCurve(
|
|
128
|
+
new Point(scorePose),
|
|
129
|
+
new Point(CURVE_CONTROL_X, CURVE_CONTROL_Y, Point.CARTESIAN),
|
|
130
|
+
new Point(parkPose)))
|
|
131
|
+
.setLinearHeadingInterpolation(scorePose.getHeading(), parkPose.getHeading())
|
|
132
|
+
.build();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private void setPathState(int state) {
|
|
136
|
+
pathState = state;
|
|
137
|
+
pathTimer.resetTimer();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private void autonomousPathUpdate() {
|
|
141
|
+
switch (pathState) {
|
|
142
|
+
case 0: // Drive to score preloaded sample
|
|
143
|
+
if (!follower.isBusy()) {
|
|
144
|
+
follower.followPath(scorePreload, true);
|
|
145
|
+
setPathState(1);
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
|
|
149
|
+
case 1: // Wait to arrive at scoring position, then "score"
|
|
150
|
+
if (!follower.isBusy()) {
|
|
151
|
+
// Score the preloaded sample (actuate mechanism here)
|
|
152
|
+
setPathState(2);
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
|
|
156
|
+
case 2: // Drive to grab sample 1
|
|
157
|
+
if (pathTimer.getElapsedTimeSeconds() > 0.5) {
|
|
158
|
+
follower.followPath(grabSample1, true);
|
|
159
|
+
setPathState(3);
|
|
160
|
+
}
|
|
161
|
+
break;
|
|
162
|
+
|
|
163
|
+
case 3: // Wait to arrive at sample 1, then grab
|
|
164
|
+
if (!follower.isBusy()) {
|
|
165
|
+
// Grab sample 1 (actuate mechanism here)
|
|
166
|
+
setPathState(4);
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
|
|
170
|
+
case 4: // Drive to score sample 1
|
|
171
|
+
if (pathTimer.getElapsedTimeSeconds() > 0.5) {
|
|
172
|
+
follower.followPath(scoreSample1, true);
|
|
173
|
+
setPathState(5);
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
|
|
177
|
+
case 5: // Wait to arrive, then score sample 1
|
|
178
|
+
if (!follower.isBusy()) {
|
|
179
|
+
// Score sample 1
|
|
180
|
+
setPathState(6);
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
|
|
184
|
+
case 6: // Drive to grab sample 2
|
|
185
|
+
if (pathTimer.getElapsedTimeSeconds() > 0.5) {
|
|
186
|
+
follower.followPath(grabSample2, true);
|
|
187
|
+
setPathState(7);
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
|
|
191
|
+
case 7: // Wait to arrive at sample 2, then grab
|
|
192
|
+
if (!follower.isBusy()) {
|
|
193
|
+
// Grab sample 2
|
|
194
|
+
setPathState(8);
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
|
|
198
|
+
case 8: // Drive to score sample 2
|
|
199
|
+
if (pathTimer.getElapsedTimeSeconds() > 0.5) {
|
|
200
|
+
follower.followPath(scoreSample2, true);
|
|
201
|
+
setPathState(9);
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
|
|
205
|
+
case 9: // Wait to arrive, then score sample 2
|
|
206
|
+
if (!follower.isBusy()) {
|
|
207
|
+
// Score sample 2
|
|
208
|
+
setPathState(10);
|
|
209
|
+
}
|
|
210
|
+
break;
|
|
211
|
+
|
|
212
|
+
case 10: // Drive to grab sample 3
|
|
213
|
+
if (pathTimer.getElapsedTimeSeconds() > 0.5) {
|
|
214
|
+
follower.followPath(grabSample3, true);
|
|
215
|
+
setPathState(11);
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
|
|
219
|
+
case 11: // Wait to arrive at sample 3, then grab
|
|
220
|
+
if (!follower.isBusy()) {
|
|
221
|
+
// Grab sample 3
|
|
222
|
+
setPathState(12);
|
|
223
|
+
}
|
|
224
|
+
break;
|
|
225
|
+
|
|
226
|
+
case 12: // Drive to score sample 3
|
|
227
|
+
if (pathTimer.getElapsedTimeSeconds() > 0.5) {
|
|
228
|
+
follower.followPath(scoreSample3, true);
|
|
229
|
+
setPathState(13);
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
|
|
233
|
+
case 13: // Wait to arrive, then score sample 3
|
|
234
|
+
if (!follower.isBusy()) {
|
|
235
|
+
// Score sample 3
|
|
236
|
+
setPathState(14);
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
239
|
+
|
|
240
|
+
case 14: // Park
|
|
241
|
+
if (pathTimer.getElapsedTimeSeconds() > 0.5) {
|
|
242
|
+
follower.followPath(park, true);
|
|
243
|
+
setPathState(15);
|
|
244
|
+
}
|
|
245
|
+
break;
|
|
246
|
+
|
|
247
|
+
case 15: // Done
|
|
248
|
+
// Autonomous complete
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
@Override
|
|
254
|
+
public void init() {
|
|
255
|
+
pathTimer = new Timer();
|
|
256
|
+
pathState = 0;
|
|
257
|
+
|
|
258
|
+
follower = new Follower(hardwareMap);
|
|
259
|
+
|
|
260
|
+
telemetry = new MultipleTelemetry(telemetry, FtcDashboard.getInstance().getTelemetry());
|
|
261
|
+
|
|
262
|
+
buildPaths();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
@Override
|
|
266
|
+
public void start() {
|
|
267
|
+
pathTimer.resetTimer();
|
|
268
|
+
setPathState(0);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@Override
|
|
272
|
+
public void loop() {
|
|
273
|
+
follower.update();
|
|
274
|
+
autonomousPathUpdate();
|
|
275
|
+
|
|
276
|
+
// Draw robot on FTC Dashboard field overlay
|
|
277
|
+
Pose currentPose = follower.getPose();
|
|
278
|
+
TelemetryPacket packet = new TelemetryPacket();
|
|
279
|
+
Canvas fieldOverlay = packet.fieldOverlay();
|
|
280
|
+
|
|
281
|
+
fieldOverlay.setStroke("#3F51B5");
|
|
282
|
+
fieldOverlay.setStrokeWidth(1);
|
|
283
|
+
fieldOverlay.strokeCircle(currentPose.getX(), currentPose.getY(), 9);
|
|
284
|
+
double arrowX = currentPose.getX() + 12 * Math.cos(currentPose.getHeading());
|
|
285
|
+
double arrowY = currentPose.getY() + 12 * Math.sin(currentPose.getHeading());
|
|
286
|
+
fieldOverlay.strokeLine(currentPose.getX(), currentPose.getY(), arrowX, arrowY);
|
|
287
|
+
|
|
288
|
+
FtcDashboard.getInstance().sendTelemetryPacket(packet);
|
|
289
|
+
|
|
290
|
+
telemetry.addData("Path State", pathState);
|
|
291
|
+
telemetry.addData("X", currentPose.getX());
|
|
292
|
+
telemetry.addData("Y", currentPose.getY());
|
|
293
|
+
telemetry.addData("Heading (deg)", Math.toDegrees(currentPose.getHeading()));
|
|
294
|
+
telemetry.addData("Busy", follower.isBusy());
|
|
295
|
+
telemetry.update();
|
|
296
|
+
}
|
|
297
|
+
}`,
|
|
298
|
+
"pedro-teleop": `package org.firstinspires.ftc.teamcode;
|
|
299
|
+
|
|
300
|
+
import com.acmerobotics.dashboard.FtcDashboard;
|
|
301
|
+
import com.acmerobotics.dashboard.config.Config;
|
|
302
|
+
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
|
303
|
+
import com.pedropathing.follower.Follower;
|
|
304
|
+
import com.pedropathing.localization.Pose;
|
|
305
|
+
import com.qualcomm.hardware.lynx.LynxModule;
|
|
306
|
+
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
|
307
|
+
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
|
308
|
+
import com.qualcomm.robotcore.util.ElapsedTime;
|
|
309
|
+
|
|
310
|
+
import java.util.List;
|
|
311
|
+
|
|
312
|
+
@Config
|
|
313
|
+
@TeleOp(name = "Pedro TeleOp", group = "TeleOp")
|
|
314
|
+
public class PedroTeleOpExample extends OpMode {
|
|
315
|
+
|
|
316
|
+
public static double NORMAL_SPEED = 1.0;
|
|
317
|
+
public static double SLOW_SPEED = 0.4;
|
|
318
|
+
|
|
319
|
+
private Follower follower;
|
|
320
|
+
private double speedMultiplier = NORMAL_SPEED;
|
|
321
|
+
private boolean slowModeActive = false;
|
|
322
|
+
private boolean previousAState = false;
|
|
323
|
+
|
|
324
|
+
private List<LynxModule> allHubs;
|
|
325
|
+
private ElapsedTime loopTimer;
|
|
326
|
+
|
|
327
|
+
@Override
|
|
328
|
+
public void init() {
|
|
329
|
+
// Set up manual bulk reads for performance
|
|
330
|
+
allHubs = hardwareMap.getAll(LynxModule.class);
|
|
331
|
+
for (LynxModule hub : allHubs) {
|
|
332
|
+
hub.setBulkCachingMode(LynxModule.BulkCachingMode.MANUAL);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
follower = new Follower(hardwareMap);
|
|
336
|
+
follower.setStartingPose(new Pose(0, 0, 0));
|
|
337
|
+
|
|
338
|
+
telemetry = new MultipleTelemetry(telemetry, FtcDashboard.getInstance().getTelemetry());
|
|
339
|
+
|
|
340
|
+
loopTimer = new ElapsedTime();
|
|
341
|
+
|
|
342
|
+
telemetry.addData("Status", "Initialized");
|
|
343
|
+
telemetry.update();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
@Override
|
|
347
|
+
public void start() {
|
|
348
|
+
follower.startTeleopDrive();
|
|
349
|
+
loopTimer.reset();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
@Override
|
|
353
|
+
public void loop() {
|
|
354
|
+
// Clear bulk cache at the start of each loop
|
|
355
|
+
for (LynxModule hub : allHubs) {
|
|
356
|
+
hub.clearBulkCache();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Toggle slow mode with gamepad1.a (rising edge detection)
|
|
360
|
+
boolean currentAState = gamepad1.a;
|
|
361
|
+
if (currentAState && !previousAState) {
|
|
362
|
+
slowModeActive = !slowModeActive;
|
|
363
|
+
}
|
|
364
|
+
previousAState = currentAState;
|
|
365
|
+
|
|
366
|
+
// Read speed multiplier from dashboard-tunable statics at point of use
|
|
367
|
+
speedMultiplier = slowModeActive ? SLOW_SPEED : NORMAL_SPEED;
|
|
368
|
+
|
|
369
|
+
// Drive using Pedro's built-in TeleOp drive
|
|
370
|
+
follower.setTeleOpMovementVectors(
|
|
371
|
+
-gamepad1.left_stick_y * speedMultiplier,
|
|
372
|
+
-gamepad1.left_stick_x * speedMultiplier,
|
|
373
|
+
-gamepad1.right_stick_x * speedMultiplier,
|
|
374
|
+
true
|
|
375
|
+
);
|
|
376
|
+
follower.update();
|
|
377
|
+
|
|
378
|
+
// Telemetry
|
|
379
|
+
Pose currentPose = follower.getPose();
|
|
380
|
+
telemetry.addData("--- Drive Info ---", "");
|
|
381
|
+
telemetry.addData("Slow Mode", slowModeActive ? "ON" : "OFF");
|
|
382
|
+
telemetry.addData("Speed Multiplier", "%.2f", speedMultiplier);
|
|
383
|
+
telemetry.addData("--- Pose ---", "");
|
|
384
|
+
telemetry.addData("X", "%.2f", currentPose.getX());
|
|
385
|
+
telemetry.addData("Y", "%.2f", currentPose.getY());
|
|
386
|
+
telemetry.addData("Heading (deg)", "%.2f", Math.toDegrees(currentPose.getHeading()));
|
|
387
|
+
telemetry.addData("--- Performance ---", "");
|
|
388
|
+
telemetry.addData("Loop Time (ms)", "%.1f", loopTimer.milliseconds());
|
|
389
|
+
telemetry.update();
|
|
390
|
+
|
|
391
|
+
loopTimer.reset();
|
|
392
|
+
}
|
|
393
|
+
}`,
|
|
394
|
+
"pedro-constants": `package org.firstinspires.ftc.teamcode;
|
|
395
|
+
|
|
396
|
+
import com.pedropathing.follower.Follower;
|
|
397
|
+
import com.pedropathing.follower.FollowerConstants;
|
|
398
|
+
import com.pedropathing.localization.constants.PinpointConstants;
|
|
399
|
+
import com.pedropathing.pathgen.MathFunctions;
|
|
400
|
+
import com.pedropathing.pathgen.PathConstraints;
|
|
401
|
+
import com.qualcomm.hardware.lynx.LynxModule;
|
|
402
|
+
import com.qualcomm.robotcore.hardware.DcMotorSimple;
|
|
403
|
+
import com.qualcomm.robotcore.hardware.HardwareMap;
|
|
404
|
+
|
|
405
|
+
import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit;
|
|
406
|
+
|
|
407
|
+
public class Constants {
|
|
408
|
+
|
|
409
|
+
static {
|
|
410
|
+
// --- Follower PID and Drive Constants ---
|
|
411
|
+
FollowerConstants.mass = 13.0; // robot mass in kg
|
|
412
|
+
|
|
413
|
+
FollowerConstants.translationalPIDFCoefficients.setCoefficients(0.15, 0, 0.01, 0);
|
|
414
|
+
FollowerConstants.headingPIDFCoefficients.setCoefficients(2.0, 0, 0.1, 0);
|
|
415
|
+
FollowerConstants.drivePIDFCoefficients.setCoefficients(0.01, 0, 0.0002, 0.6);
|
|
416
|
+
|
|
417
|
+
FollowerConstants.zeroPowerAcceleration = -30;
|
|
418
|
+
FollowerConstants.centripetalScaling = 0.0005;
|
|
419
|
+
|
|
420
|
+
FollowerConstants.useSecondaryTranslationalPID = false;
|
|
421
|
+
FollowerConstants.useSecondaryHeadingPID = false;
|
|
422
|
+
FollowerConstants.useSecondaryDrivePID = false;
|
|
423
|
+
|
|
424
|
+
// --- Mecanum Motor Configuration ---
|
|
425
|
+
FollowerConstants.leftFrontMotorName = "leftFront";
|
|
426
|
+
FollowerConstants.leftRearMotorName = "leftRear";
|
|
427
|
+
FollowerConstants.rightFrontMotorName = "rightFront";
|
|
428
|
+
FollowerConstants.rightRearMotorName = "rightRear";
|
|
429
|
+
|
|
430
|
+
FollowerConstants.leftFrontMotorDirection = DcMotorSimple.Direction.REVERSE;
|
|
431
|
+
FollowerConstants.leftRearMotorDirection = DcMotorSimple.Direction.REVERSE;
|
|
432
|
+
FollowerConstants.rightFrontMotorDirection = DcMotorSimple.Direction.FORWARD;
|
|
433
|
+
FollowerConstants.rightRearMotorDirection = DcMotorSimple.Direction.FORWARD;
|
|
434
|
+
|
|
435
|
+
// --- Pinpoint Localizer Configuration ---
|
|
436
|
+
PinpointConstants.forwardY = 5.5; // forward encoder Y offset in inches
|
|
437
|
+
PinpointConstants.strafeX = -3.0; // strafe encoder X offset in inches
|
|
438
|
+
PinpointConstants.distanceUnit = DistanceUnit.INCH;
|
|
439
|
+
PinpointConstants.hardwareMapName = "pinpoint";
|
|
440
|
+
PinpointConstants.useYawScalar = false;
|
|
441
|
+
PinpointConstants.yawScalar = 1.0;
|
|
442
|
+
PinpointConstants.useCustomEncoderResolution = false;
|
|
443
|
+
PinpointConstants.encoderResolution = PinpointConstants.GoBildaOdometryPods.goBILDA_4_BAR_POD;
|
|
444
|
+
PinpointConstants.forwardEncoderDirection = PinpointConstants.EncoderDirection.FORWARD;
|
|
445
|
+
PinpointConstants.strafeEncoderDirection = PinpointConstants.EncoderDirection.FORWARD;
|
|
446
|
+
|
|
447
|
+
// --- Path Constraints ---
|
|
448
|
+
PathConstraints.maxVelocity = 50;
|
|
449
|
+
PathConstraints.maxAcceleration = 50;
|
|
450
|
+
PathConstraints.maxAngularVelocity = Math.toRadians(300);
|
|
451
|
+
PathConstraints.maxAngularAcceleration = Math.toRadians(300);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Creates and returns a configured Follower instance.
|
|
456
|
+
* Call this from your OpMode's init() method.
|
|
457
|
+
*
|
|
458
|
+
* @param hardwareMap the hardware map from the OpMode
|
|
459
|
+
* @return a configured Follower
|
|
460
|
+
*/
|
|
461
|
+
public static Follower createFollower(HardwareMap hardwareMap) {
|
|
462
|
+
return new Follower(hardwareMap);
|
|
463
|
+
}
|
|
464
|
+
}`,
|
|
465
|
+
"dashboard-config": `package org.firstinspires.ftc.teamcode;
|
|
466
|
+
|
|
467
|
+
import com.acmerobotics.dashboard.FtcDashboard;
|
|
468
|
+
import com.acmerobotics.dashboard.config.Config;
|
|
469
|
+
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
|
470
|
+
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
|
471
|
+
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Demonstrates @Config usage with FTC Dashboard.
|
|
475
|
+
*
|
|
476
|
+
* CRITICAL: Always read static fields at the POINT OF USE so that
|
|
477
|
+
* dashboard edits take effect immediately. Never cache them in
|
|
478
|
+
* local variables during init().
|
|
479
|
+
*/
|
|
480
|
+
@Config
|
|
481
|
+
@TeleOp(name = "Dashboard Config Demo", group = "Examples")
|
|
482
|
+
public class DashboardConfigExample extends OpMode {
|
|
483
|
+
|
|
484
|
+
// --- Primitive types: editable from the dashboard ---
|
|
485
|
+
public static double POWER = 0.5;
|
|
486
|
+
public static int TARGET_POSITION = 1000;
|
|
487
|
+
public static boolean USE_PID = true;
|
|
488
|
+
public static String ALLIANCE_COLOR = "RED";
|
|
489
|
+
|
|
490
|
+
// --- Enum: shows a dropdown in the dashboard ---
|
|
491
|
+
public enum DriveMode {
|
|
492
|
+
FIELD_CENTRIC,
|
|
493
|
+
ROBOT_CENTRIC,
|
|
494
|
+
TANK
|
|
495
|
+
}
|
|
496
|
+
public static DriveMode DRIVE_MODE = DriveMode.FIELD_CENTRIC;
|
|
497
|
+
|
|
498
|
+
// --- Nested object: each field editable in the dashboard ---
|
|
499
|
+
public static class PIDCoefficients {
|
|
500
|
+
public double kP = 0.05;
|
|
501
|
+
public double kI = 0.0;
|
|
502
|
+
public double kD = 0.01;
|
|
503
|
+
public double kF = 0.0;
|
|
504
|
+
}
|
|
505
|
+
public static PIDCoefficients pidCoeffs = new PIDCoefficients();
|
|
506
|
+
|
|
507
|
+
@Override
|
|
508
|
+
public void init() {
|
|
509
|
+
telemetry = new MultipleTelemetry(telemetry, FtcDashboard.getInstance().getTelemetry());
|
|
510
|
+
telemetry.addData("Status", "Initialized - edit values on dashboard!");
|
|
511
|
+
telemetry.update();
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
@Override
|
|
515
|
+
public void loop() {
|
|
516
|
+
// =====================================================
|
|
517
|
+
// CORRECT: Read statics at the point of use.
|
|
518
|
+
// Dashboard changes are reflected IMMEDIATELY each loop.
|
|
519
|
+
// =====================================================
|
|
520
|
+
double currentPower = POWER; // fresh every loop
|
|
521
|
+
int currentTarget = TARGET_POSITION; // fresh every loop
|
|
522
|
+
boolean usePid = USE_PID; // fresh every loop
|
|
523
|
+
|
|
524
|
+
// PID coefficients are also read fresh because pidCoeffs
|
|
525
|
+
// is a static reference and its fields are public.
|
|
526
|
+
double p = pidCoeffs.kP;
|
|
527
|
+
double i = pidCoeffs.kI;
|
|
528
|
+
double d = pidCoeffs.kD;
|
|
529
|
+
double f = pidCoeffs.kF;
|
|
530
|
+
|
|
531
|
+
// Use the values ...
|
|
532
|
+
telemetry.addData("--- Current Values ---", "");
|
|
533
|
+
telemetry.addData("Power", "%.2f", currentPower);
|
|
534
|
+
telemetry.addData("Target Position", currentTarget);
|
|
535
|
+
telemetry.addData("Use PID", usePid);
|
|
536
|
+
telemetry.addData("Alliance", ALLIANCE_COLOR);
|
|
537
|
+
telemetry.addData("Drive Mode", DRIVE_MODE);
|
|
538
|
+
telemetry.addData("--- PID ---", "");
|
|
539
|
+
telemetry.addData("kP", "%.4f", p);
|
|
540
|
+
telemetry.addData("kI", "%.4f", i);
|
|
541
|
+
telemetry.addData("kD", "%.4f", d);
|
|
542
|
+
telemetry.addData("kF", "%.4f", f);
|
|
543
|
+
|
|
544
|
+
// =====================================================
|
|
545
|
+
// WRONG: Do NOT cache statics in instance fields in init()!
|
|
546
|
+
//
|
|
547
|
+
// private double cachedPower;
|
|
548
|
+
//
|
|
549
|
+
// public void init() {
|
|
550
|
+
// cachedPower = POWER; // BUG: copies the value once!
|
|
551
|
+
// }
|
|
552
|
+
//
|
|
553
|
+
// public void loop() {
|
|
554
|
+
// // cachedPower never changes even if you edit POWER
|
|
555
|
+
// // on the dashboard, because Java copied the double
|
|
556
|
+
// // value, not a reference to the static field.
|
|
557
|
+
// motor.setPower(cachedPower); // stale!
|
|
558
|
+
// }
|
|
559
|
+
//
|
|
560
|
+
// This is a "copy semantics" bug. Primitives are copied
|
|
561
|
+
// by value, so the local variable is disconnected from
|
|
562
|
+
// the static field after assignment.
|
|
563
|
+
// =====================================================
|
|
564
|
+
|
|
565
|
+
telemetry.update();
|
|
566
|
+
}
|
|
567
|
+
}`,
|
|
568
|
+
"bulk-reads": `package org.firstinspires.ftc.teamcode;
|
|
569
|
+
|
|
570
|
+
import com.acmerobotics.dashboard.FtcDashboard;
|
|
571
|
+
import com.acmerobotics.dashboard.config.Config;
|
|
572
|
+
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
|
573
|
+
import com.qualcomm.hardware.lynx.LynxModule;
|
|
574
|
+
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
|
575
|
+
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
|
576
|
+
import com.qualcomm.robotcore.hardware.DcMotor;
|
|
577
|
+
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
|
578
|
+
import com.qualcomm.robotcore.hardware.Servo;
|
|
579
|
+
import com.qualcomm.robotcore.util.ElapsedTime;
|
|
580
|
+
|
|
581
|
+
import java.util.List;
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Demonstrates optimised hardware reads using MANUAL bulk caching.
|
|
585
|
+
*
|
|
586
|
+
* Without bulk caching every call to getPosition(), getCurrent(), etc.
|
|
587
|
+
* triggers a separate USB transaction (~3 ms each). With MANUAL mode
|
|
588
|
+
* a single clearBulkCache() call at the top of the loop fetches ALL
|
|
589
|
+
* sensor data in one transaction, and subsequent reads come from cache.
|
|
590
|
+
*/
|
|
591
|
+
@Config
|
|
592
|
+
@TeleOp(name = "Bulk Reads Demo", group = "Examples")
|
|
593
|
+
public class BulkReadsExample extends OpMode {
|
|
594
|
+
|
|
595
|
+
public static double SERVO_POS_A = 0.0;
|
|
596
|
+
public static double SERVO_POS_B = 1.0;
|
|
597
|
+
|
|
598
|
+
private List<LynxModule> allHubs;
|
|
599
|
+
|
|
600
|
+
private DcMotorEx frontLeft;
|
|
601
|
+
private DcMotorEx frontRight;
|
|
602
|
+
private DcMotorEx backLeft;
|
|
603
|
+
private DcMotorEx backRight;
|
|
604
|
+
|
|
605
|
+
private Servo clawServo;
|
|
606
|
+
|
|
607
|
+
private ElapsedTime loopTimer;
|
|
608
|
+
private double maxLoopTime = 0;
|
|
609
|
+
|
|
610
|
+
@Override
|
|
611
|
+
public void init() {
|
|
612
|
+
// --- Enable MANUAL bulk caching on every hub ---
|
|
613
|
+
allHubs = hardwareMap.getAll(LynxModule.class);
|
|
614
|
+
for (LynxModule hub : allHubs) {
|
|
615
|
+
hub.setBulkCachingMode(LynxModule.BulkCachingMode.MANUAL);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// --- Motors ---
|
|
619
|
+
frontLeft = hardwareMap.get(DcMotorEx.class, "frontLeft");
|
|
620
|
+
frontRight = hardwareMap.get(DcMotorEx.class, "frontRight");
|
|
621
|
+
backLeft = hardwareMap.get(DcMotorEx.class, "backLeft");
|
|
622
|
+
backRight = hardwareMap.get(DcMotorEx.class, "backRight");
|
|
623
|
+
|
|
624
|
+
frontLeft.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
|
|
625
|
+
frontRight.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
|
|
626
|
+
backLeft.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
|
|
627
|
+
backRight.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
|
|
628
|
+
|
|
629
|
+
// --- Servos ---
|
|
630
|
+
clawServo = hardwareMap.get(Servo.class, "claw");
|
|
631
|
+
|
|
632
|
+
// --- Telemetry ---
|
|
633
|
+
telemetry = new MultipleTelemetry(telemetry, FtcDashboard.getInstance().getTelemetry());
|
|
634
|
+
loopTimer = new ElapsedTime();
|
|
635
|
+
|
|
636
|
+
telemetry.addData("Status", "Initialized with MANUAL bulk reads");
|
|
637
|
+
telemetry.update();
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
@Override
|
|
641
|
+
public void loop() {
|
|
642
|
+
// *** Clear bulk cache ONCE at the top of each loop ***
|
|
643
|
+
for (LynxModule hub : allHubs) {
|
|
644
|
+
hub.clearBulkCache();
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
loopTimer.reset();
|
|
648
|
+
|
|
649
|
+
// --- Drive: simple tank drive for demo ---
|
|
650
|
+
double drive = -gamepad1.left_stick_y;
|
|
651
|
+
double turn = gamepad1.right_stick_x;
|
|
652
|
+
|
|
653
|
+
frontLeft.setPower(drive + turn);
|
|
654
|
+
backLeft.setPower(drive + turn);
|
|
655
|
+
frontRight.setPower(drive - turn);
|
|
656
|
+
backRight.setPower(drive - turn);
|
|
657
|
+
|
|
658
|
+
// --- Servo: toggle with bumpers ---
|
|
659
|
+
if (gamepad1.left_bumper) {
|
|
660
|
+
clawServo.setPosition(SERVO_POS_A);
|
|
661
|
+
} else if (gamepad1.right_bumper) {
|
|
662
|
+
clawServo.setPosition(SERVO_POS_B);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// --- Read encoder positions (all from cache — fast!) ---
|
|
666
|
+
int flPos = frontLeft.getCurrentPosition();
|
|
667
|
+
int frPos = frontRight.getCurrentPosition();
|
|
668
|
+
int blPos = backLeft.getCurrentPosition();
|
|
669
|
+
int brPos = backRight.getCurrentPosition();
|
|
670
|
+
|
|
671
|
+
double elapsed = loopTimer.milliseconds();
|
|
672
|
+
if (elapsed > maxLoopTime) {
|
|
673
|
+
maxLoopTime = elapsed;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// --- Telemetry ---
|
|
677
|
+
telemetry.addData("--- Encoders ---", "");
|
|
678
|
+
telemetry.addData("FL", flPos);
|
|
679
|
+
telemetry.addData("FR", frPos);
|
|
680
|
+
telemetry.addData("BL", blPos);
|
|
681
|
+
telemetry.addData("BR", brPos);
|
|
682
|
+
telemetry.addData("--- Servo ---", "");
|
|
683
|
+
telemetry.addData("Claw Position", "%.2f", clawServo.getPosition());
|
|
684
|
+
telemetry.addData("--- Performance ---", "");
|
|
685
|
+
telemetry.addData("Loop Time (ms)", "%.2f", elapsed);
|
|
686
|
+
telemetry.addData("Max Loop Time (ms)", "%.2f", maxLoopTime);
|
|
687
|
+
telemetry.update();
|
|
688
|
+
}
|
|
689
|
+
}`,
|
|
690
|
+
"subsystem": `package org.firstinspires.ftc.teamcode;
|
|
691
|
+
|
|
692
|
+
import com.acmerobotics.dashboard.config.Config;
|
|
693
|
+
import com.qualcomm.robotcore.hardware.DcMotor;
|
|
694
|
+
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
|
695
|
+
import com.qualcomm.robotcore.hardware.HardwareMap;
|
|
696
|
+
import com.qualcomm.robotcore.hardware.Servo;
|
|
697
|
+
|
|
698
|
+
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Example hardware subsystem for a linear-slide lift with a
|
|
702
|
+
* claw servo. Designed to be instantiated from an OpMode.
|
|
703
|
+
*
|
|
704
|
+
* Usage:
|
|
705
|
+
* LiftSubsystem lift = new LiftSubsystem(hardwareMap);
|
|
706
|
+
* lift.goToPosition(LiftSubsystem.HIGH_POSITION);
|
|
707
|
+
* lift.setClaw(LiftSubsystem.CLAW_OPEN);
|
|
708
|
+
* lift.update();
|
|
709
|
+
*/
|
|
710
|
+
@Config
|
|
711
|
+
public class LiftSubsystem {
|
|
712
|
+
|
|
713
|
+
// --- Dashboard-tunable positions ---
|
|
714
|
+
// Always read these statics at the POINT OF USE so dashboard
|
|
715
|
+
// edits are reflected immediately.
|
|
716
|
+
public static int HIGH_POSITION = 2800;
|
|
717
|
+
public static int MID_POSITION = 1400;
|
|
718
|
+
public static int LOW_POSITION = 400;
|
|
719
|
+
public static int HOME_POSITION = 0;
|
|
720
|
+
public static int POSITION_TOLERANCE = 15;
|
|
721
|
+
|
|
722
|
+
public static double LIFT_POWER = 0.8;
|
|
723
|
+
public static double HOLD_POWER = 0.15;
|
|
724
|
+
|
|
725
|
+
public static double CLAW_OPEN = 0.6;
|
|
726
|
+
public static double CLAW_CLOSED = 0.15;
|
|
727
|
+
|
|
728
|
+
public static double kP = 0.005;
|
|
729
|
+
public static double kF = 0.1;
|
|
730
|
+
|
|
731
|
+
private final DcMotorEx liftMotor;
|
|
732
|
+
private final Servo clawServo;
|
|
733
|
+
|
|
734
|
+
private int targetPosition = 0;
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Initializes the subsystem from the hardware map.
|
|
738
|
+
*
|
|
739
|
+
* @param hardwareMap the OpMode's hardware map
|
|
740
|
+
*/
|
|
741
|
+
public LiftSubsystem(HardwareMap hardwareMap) {
|
|
742
|
+
liftMotor = hardwareMap.get(DcMotorEx.class, "liftMotor");
|
|
743
|
+
liftMotor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
|
|
744
|
+
liftMotor.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
|
|
745
|
+
liftMotor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
|
|
746
|
+
|
|
747
|
+
clawServo = hardwareMap.get(Servo.class, "clawServo");
|
|
748
|
+
clawServo.setPosition(CLAW_CLOSED);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Sets the target encoder position for the lift.
|
|
753
|
+
* Use the public static constants (HIGH_POSITION, etc.) which
|
|
754
|
+
* are editable from the dashboard.
|
|
755
|
+
*/
|
|
756
|
+
public void goToPosition(int position) {
|
|
757
|
+
targetPosition = position;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Software PID update — call this every loop iteration.
|
|
762
|
+
* Reads kP and kF from statics at point of use so dashboard
|
|
763
|
+
* changes take effect immediately.
|
|
764
|
+
*/
|
|
765
|
+
public void update() {
|
|
766
|
+
int currentPosition = liftMotor.getCurrentPosition();
|
|
767
|
+
int error = targetPosition - currentPosition;
|
|
768
|
+
|
|
769
|
+
// Read tunable coefficients fresh each loop
|
|
770
|
+
double power = error * kP + Math.signum(error) * kF;
|
|
771
|
+
power = Math.max(-LIFT_POWER, Math.min(LIFT_POWER, power));
|
|
772
|
+
|
|
773
|
+
// Apply a small holding power when near target to resist gravity
|
|
774
|
+
if (Math.abs(error) < POSITION_TOLERANCE && targetPosition > 50) {
|
|
775
|
+
power = HOLD_POWER;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
liftMotor.setPower(power);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/** Opens or closes the claw. */
|
|
782
|
+
public void setClaw(double position) {
|
|
783
|
+
clawServo.setPosition(position);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/** @return true if the lift is within tolerance of its target. */
|
|
787
|
+
public boolean isAtTarget() {
|
|
788
|
+
return Math.abs(liftMotor.getCurrentPosition() - targetPosition) < POSITION_TOLERANCE;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/** @return the current encoder position of the lift motor. */
|
|
792
|
+
public int getCurrentPosition() {
|
|
793
|
+
return liftMotor.getCurrentPosition();
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/** @return the active target position. */
|
|
797
|
+
public int getTargetPosition() {
|
|
798
|
+
return targetPosition;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/** Sends subsystem telemetry to the driver station / dashboard. */
|
|
802
|
+
public void outputTelemetry(Telemetry telemetry) {
|
|
803
|
+
int currentPos = liftMotor.getCurrentPosition();
|
|
804
|
+
telemetry.addData("--- Lift ---", "");
|
|
805
|
+
telemetry.addData("Target", targetPosition);
|
|
806
|
+
telemetry.addData("Current", currentPos);
|
|
807
|
+
telemetry.addData("Error", targetPosition - currentPos);
|
|
808
|
+
telemetry.addData("At Target", isAtTarget());
|
|
809
|
+
telemetry.addData("Motor Power", "%.2f", liftMotor.getPower());
|
|
810
|
+
telemetry.addData("Claw Pos", "%.2f", clawServo.getPosition());
|
|
811
|
+
}
|
|
812
|
+
}`,
|
|
813
|
+
"pid-tuning": `package org.firstinspires.ftc.teamcode;
|
|
814
|
+
|
|
815
|
+
import com.acmerobotics.dashboard.FtcDashboard;
|
|
816
|
+
import com.acmerobotics.dashboard.config.Config;
|
|
817
|
+
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
|
818
|
+
import com.qualcomm.hardware.lynx.LynxModule;
|
|
819
|
+
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
|
820
|
+
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
|
821
|
+
import com.qualcomm.robotcore.hardware.DcMotor;
|
|
822
|
+
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
|
823
|
+
import com.qualcomm.robotcore.util.ElapsedTime;
|
|
824
|
+
|
|
825
|
+
import java.util.List;
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* PID tuning OpMode for a single motor with encoder feedback.
|
|
829
|
+
*
|
|
830
|
+
* Open the FTC Dashboard graph panel and add:
|
|
831
|
+
* - target
|
|
832
|
+
* - position
|
|
833
|
+
* - error
|
|
834
|
+
* - output
|
|
835
|
+
*
|
|
836
|
+
* Then adjust kP, kI, kD, kF live to tune the controller.
|
|
837
|
+
*/
|
|
838
|
+
@Config
|
|
839
|
+
@TeleOp(name = "PID Tuner", group = "Tuning")
|
|
840
|
+
public class PIDTuningExample extends OpMode {
|
|
841
|
+
|
|
842
|
+
// --- Dashboard-tunable PID coefficients ---
|
|
843
|
+
public static double kP = 0.01;
|
|
844
|
+
public static double kI = 0.0;
|
|
845
|
+
public static double kD = 0.0;
|
|
846
|
+
public static double kF = 0.0;
|
|
847
|
+
|
|
848
|
+
// --- Dashboard-tunable target ---
|
|
849
|
+
public static int TARGET = 1000;
|
|
850
|
+
public static double MAX_OUTPUT = 0.8;
|
|
851
|
+
|
|
852
|
+
private DcMotorEx motor;
|
|
853
|
+
|
|
854
|
+
private double integralSum = 0;
|
|
855
|
+
private double lastError = 0;
|
|
856
|
+
private ElapsedTime timer;
|
|
857
|
+
|
|
858
|
+
private List<LynxModule> allHubs;
|
|
859
|
+
|
|
860
|
+
@Override
|
|
861
|
+
public void init() {
|
|
862
|
+
// Bulk reads for performance
|
|
863
|
+
allHubs = hardwareMap.getAll(LynxModule.class);
|
|
864
|
+
for (LynxModule hub : allHubs) {
|
|
865
|
+
hub.setBulkCachingMode(LynxModule.BulkCachingMode.MANUAL);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
motor = hardwareMap.get(DcMotorEx.class, "liftMotor");
|
|
869
|
+
motor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
|
|
870
|
+
motor.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
|
|
871
|
+
motor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
|
|
872
|
+
|
|
873
|
+
timer = new ElapsedTime();
|
|
874
|
+
|
|
875
|
+
telemetry = new MultipleTelemetry(telemetry, FtcDashboard.getInstance().getTelemetry());
|
|
876
|
+
telemetry.addData("Status", "PID Tuner ready — adjust kP/kI/kD/kF on dashboard");
|
|
877
|
+
telemetry.update();
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
@Override
|
|
881
|
+
public void start() {
|
|
882
|
+
timer.reset();
|
|
883
|
+
integralSum = 0;
|
|
884
|
+
lastError = 0;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
@Override
|
|
888
|
+
public void loop() {
|
|
889
|
+
// Clear bulk cache
|
|
890
|
+
for (LynxModule hub : allHubs) {
|
|
891
|
+
hub.clearBulkCache();
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// Read coefficients from dashboard statics at point of use
|
|
895
|
+
double p = kP;
|
|
896
|
+
double i = kI;
|
|
897
|
+
double d = kD;
|
|
898
|
+
double f = kF;
|
|
899
|
+
int target = TARGET;
|
|
900
|
+
|
|
901
|
+
int currentPosition = motor.getCurrentPosition();
|
|
902
|
+
double error = target - currentPosition;
|
|
903
|
+
|
|
904
|
+
double dt = timer.seconds();
|
|
905
|
+
timer.reset();
|
|
906
|
+
|
|
907
|
+
// Proportional
|
|
908
|
+
double pTerm = p * error;
|
|
909
|
+
|
|
910
|
+
// Integral (with windup guard)
|
|
911
|
+
integralSum += error * dt;
|
|
912
|
+
integralSum = Math.max(-1000, Math.min(1000, integralSum));
|
|
913
|
+
double iTerm = i * integralSum;
|
|
914
|
+
|
|
915
|
+
// Derivative
|
|
916
|
+
double derivative = (dt > 0) ? (error - lastError) / dt : 0;
|
|
917
|
+
double dTerm = d * derivative;
|
|
918
|
+
lastError = error;
|
|
919
|
+
|
|
920
|
+
// Feedforward
|
|
921
|
+
double fTerm = f * Math.signum(error);
|
|
922
|
+
|
|
923
|
+
// Total output, clamped
|
|
924
|
+
double output = pTerm + iTerm + dTerm + fTerm;
|
|
925
|
+
output = Math.max(-MAX_OUTPUT, Math.min(MAX_OUTPUT, output));
|
|
926
|
+
|
|
927
|
+
motor.setPower(output);
|
|
928
|
+
|
|
929
|
+
// Telemetry — these keys are graphable in the dashboard
|
|
930
|
+
telemetry.addData("target", target);
|
|
931
|
+
telemetry.addData("position", currentPosition);
|
|
932
|
+
telemetry.addData("error", error);
|
|
933
|
+
telemetry.addData("output", output);
|
|
934
|
+
telemetry.addData("--- PID Terms ---", "");
|
|
935
|
+
telemetry.addData("P term", "%.4f", pTerm);
|
|
936
|
+
telemetry.addData("I term", "%.4f", iTerm);
|
|
937
|
+
telemetry.addData("D term", "%.4f", dTerm);
|
|
938
|
+
telemetry.addData("F term", "%.4f", fTerm);
|
|
939
|
+
telemetry.addData("--- Coefficients ---", "");
|
|
940
|
+
telemetry.addData("kP", p);
|
|
941
|
+
telemetry.addData("kI", i);
|
|
942
|
+
telemetry.addData("kD", d);
|
|
943
|
+
telemetry.addData("kF", f);
|
|
944
|
+
telemetry.update();
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
@Override
|
|
948
|
+
public void stop() {
|
|
949
|
+
motor.setPower(0);
|
|
950
|
+
}
|
|
951
|
+
}`,
|
|
952
|
+
"vision-pipeline": `package org.firstinspires.ftc.teamcode;
|
|
953
|
+
|
|
954
|
+
import com.acmerobotics.dashboard.FtcDashboard;
|
|
955
|
+
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
|
956
|
+
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
|
957
|
+
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
|
958
|
+
|
|
959
|
+
import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName;
|
|
960
|
+
import org.firstinspires.ftc.vision.VisionPortal;
|
|
961
|
+
import org.firstinspires.ftc.vision.apriltag.AprilTagDetection;
|
|
962
|
+
import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor;
|
|
963
|
+
|
|
964
|
+
import java.util.List;
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* Demonstrates VisionPortal with an AprilTag processor.
|
|
968
|
+
* Streams the camera feed to the FTC Dashboard for remote viewing.
|
|
969
|
+
*/
|
|
970
|
+
@TeleOp(name = "Vision AprilTag Demo", group = "Vision")
|
|
971
|
+
public class VisionPipelineExample extends OpMode {
|
|
972
|
+
|
|
973
|
+
private AprilTagProcessor aprilTagProcessor;
|
|
974
|
+
private VisionPortal visionPortal;
|
|
975
|
+
|
|
976
|
+
@Override
|
|
977
|
+
public void init() {
|
|
978
|
+
telemetry = new MultipleTelemetry(telemetry, FtcDashboard.getInstance().getTelemetry());
|
|
979
|
+
|
|
980
|
+
// --- AprilTag Processor ---
|
|
981
|
+
aprilTagProcessor = new AprilTagProcessor.Builder()
|
|
982
|
+
.setDrawAxes(true)
|
|
983
|
+
.setDrawCubeProjection(true)
|
|
984
|
+
.setDrawTagOutline(true)
|
|
985
|
+
.build();
|
|
986
|
+
|
|
987
|
+
// --- Vision Portal ---
|
|
988
|
+
visionPortal = new VisionPortal.Builder()
|
|
989
|
+
.setCamera(hardwareMap.get(WebcamName.class, "Webcam 1"))
|
|
990
|
+
.addProcessor(aprilTagProcessor)
|
|
991
|
+
.enableLiveView(true)
|
|
992
|
+
.setStreamFormat(VisionPortal.StreamFormat.MJPEG)
|
|
993
|
+
.build();
|
|
994
|
+
|
|
995
|
+
// Stream camera to FTC Dashboard
|
|
996
|
+
FtcDashboard.getInstance().startCameraStream(visionPortal, 0);
|
|
997
|
+
|
|
998
|
+
telemetry.addData("Status", "VisionPortal initialized");
|
|
999
|
+
telemetry.addData("Camera State", visionPortal.getCameraState());
|
|
1000
|
+
telemetry.update();
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
@Override
|
|
1004
|
+
public void init_loop() {
|
|
1005
|
+
telemetry.addData("Camera State", visionPortal.getCameraState());
|
|
1006
|
+
|
|
1007
|
+
List<AprilTagDetection> detections = aprilTagProcessor.getDetections();
|
|
1008
|
+
telemetry.addData("# Detections", detections.size());
|
|
1009
|
+
for (AprilTagDetection detection : detections) {
|
|
1010
|
+
telemetry.addLine(String.format("\\nTag ID %d (%s)", detection.id,
|
|
1011
|
+
detection.metadata != null ? detection.metadata.name : "Unknown"));
|
|
1012
|
+
}
|
|
1013
|
+
telemetry.update();
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
@Override
|
|
1017
|
+
public void loop() {
|
|
1018
|
+
List<AprilTagDetection> detections = aprilTagProcessor.getDetections();
|
|
1019
|
+
|
|
1020
|
+
telemetry.addData("# AprilTags Detected", detections.size());
|
|
1021
|
+
|
|
1022
|
+
for (AprilTagDetection detection : detections) {
|
|
1023
|
+
if (detection.metadata != null) {
|
|
1024
|
+
telemetry.addLine(String.format("\\n==== Tag ID %d \\\"%s\\\" ====",
|
|
1025
|
+
detection.id, detection.metadata.name));
|
|
1026
|
+
} else {
|
|
1027
|
+
telemetry.addLine(String.format("\\n==== Tag ID %d (Unknown) ====",
|
|
1028
|
+
detection.id));
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
telemetry.addLine(String.format("XYZ: %.1f, %.1f, %.1f (inches)",
|
|
1032
|
+
detection.ftcPose.x,
|
|
1033
|
+
detection.ftcPose.y,
|
|
1034
|
+
detection.ftcPose.z));
|
|
1035
|
+
telemetry.addLine(String.format("PRY: %.1f, %.1f, %.1f (degrees)",
|
|
1036
|
+
detection.ftcPose.pitch,
|
|
1037
|
+
detection.ftcPose.roll,
|
|
1038
|
+
detection.ftcPose.yaw));
|
|
1039
|
+
telemetry.addLine(String.format("Range: %.1f in", detection.ftcPose.range));
|
|
1040
|
+
telemetry.addLine(String.format("Bearing: %.1f deg", detection.ftcPose.bearing));
|
|
1041
|
+
telemetry.addLine(String.format("Elevation: %.1f deg", detection.ftcPose.elevation));
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
if (detections.isEmpty()) {
|
|
1045
|
+
telemetry.addData("Info", "No AprilTags detected");
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
telemetry.update();
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
@Override
|
|
1052
|
+
public void stop() {
|
|
1053
|
+
if (visionPortal != null) {
|
|
1054
|
+
visionPortal.close();
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}`,
|
|
1058
|
+
"custom-pid-drive": `package org.firstinspires.ftc.teamcode;
|
|
1059
|
+
|
|
1060
|
+
import com.acmerobotics.dashboard.FtcDashboard;
|
|
1061
|
+
import com.acmerobotics.dashboard.config.Config;
|
|
1062
|
+
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
|
1063
|
+
import com.qualcomm.hardware.rev.RevHubOrientationOnRobot;
|
|
1064
|
+
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
|
1065
|
+
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
|
1066
|
+
import com.qualcomm.robotcore.hardware.DcMotor;
|
|
1067
|
+
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
|
1068
|
+
import com.qualcomm.robotcore.hardware.IMU;
|
|
1069
|
+
import com.qualcomm.robotcore.util.ElapsedTime;
|
|
1070
|
+
import com.qualcomm.robotcore.util.Range;
|
|
1071
|
+
|
|
1072
|
+
import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit;
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* Encoder-based autonomous driving with PID heading correction.
|
|
1076
|
+
* No pathing library required — uses the built-in motor encoders
|
|
1077
|
+
* and IMU for straight-line driving.
|
|
1078
|
+
*/
|
|
1079
|
+
@Config
|
|
1080
|
+
@Autonomous(name = "Custom PID Drive", group = "Auto")
|
|
1081
|
+
public class CustomPIDDriveExample extends LinearOpMode {
|
|
1082
|
+
|
|
1083
|
+
// --- Dashboard-tunable PID ---
|
|
1084
|
+
public static double HEADING_KP = 0.02;
|
|
1085
|
+
public static double HEADING_KI = 0.0;
|
|
1086
|
+
public static double HEADING_KD = 0.005;
|
|
1087
|
+
|
|
1088
|
+
// --- Dashboard-tunable drive parameters ---
|
|
1089
|
+
public static double DRIVE_SPEED = 0.4;
|
|
1090
|
+
public static double TURN_SPEED = 0.3;
|
|
1091
|
+
public static double COUNTS_PER_INCH = 537.7 / (4.0 * Math.PI); // goBILDA 312 RPM, 4" wheels
|
|
1092
|
+
public static double DRIVE_TIMEOUT = 5.0;
|
|
1093
|
+
|
|
1094
|
+
private DcMotorEx frontLeft, frontRight, backLeft, backRight;
|
|
1095
|
+
private IMU imu;
|
|
1096
|
+
|
|
1097
|
+
@Override
|
|
1098
|
+
public void runOpMode() {
|
|
1099
|
+
telemetry = new MultipleTelemetry(telemetry, FtcDashboard.getInstance().getTelemetry());
|
|
1100
|
+
|
|
1101
|
+
// --- Motor setup ---
|
|
1102
|
+
frontLeft = hardwareMap.get(DcMotorEx.class, "frontLeft");
|
|
1103
|
+
frontRight = hardwareMap.get(DcMotorEx.class, "frontRight");
|
|
1104
|
+
backLeft = hardwareMap.get(DcMotorEx.class, "backLeft");
|
|
1105
|
+
backRight = hardwareMap.get(DcMotorEx.class, "backRight");
|
|
1106
|
+
|
|
1107
|
+
frontLeft.setDirection(DcMotor.Direction.REVERSE);
|
|
1108
|
+
backLeft.setDirection(DcMotor.Direction.REVERSE);
|
|
1109
|
+
frontRight.setDirection(DcMotor.Direction.FORWARD);
|
|
1110
|
+
backRight.setDirection(DcMotor.Direction.FORWARD);
|
|
1111
|
+
|
|
1112
|
+
setMotorMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
|
|
1113
|
+
setMotorMode(DcMotor.RunMode.RUN_USING_ENCODER);
|
|
1114
|
+
|
|
1115
|
+
// --- IMU setup ---
|
|
1116
|
+
imu = hardwareMap.get(IMU.class, "imu");
|
|
1117
|
+
IMU.Parameters imuParams = new IMU.Parameters(
|
|
1118
|
+
new RevHubOrientationOnRobot(
|
|
1119
|
+
RevHubOrientationOnRobot.LogoFacingDirection.UP,
|
|
1120
|
+
RevHubOrientationOnRobot.UsbFacingDirection.FORWARD));
|
|
1121
|
+
imu.initialize(imuParams);
|
|
1122
|
+
imu.resetYaw();
|
|
1123
|
+
|
|
1124
|
+
telemetry.addData("Status", "Initialized");
|
|
1125
|
+
telemetry.update();
|
|
1126
|
+
|
|
1127
|
+
waitForStart();
|
|
1128
|
+
|
|
1129
|
+
// --- Autonomous routine ---
|
|
1130
|
+
encoderDrive(DRIVE_SPEED, 24, 24, DRIVE_TIMEOUT); // Forward 24 in
|
|
1131
|
+
encoderDrive(TURN_SPEED, 12, -12, DRIVE_TIMEOUT); // Turn right
|
|
1132
|
+
encoderDrive(DRIVE_SPEED, 18, 18, DRIVE_TIMEOUT); // Forward 18 in
|
|
1133
|
+
encoderDrive(DRIVE_SPEED, -12, -12, DRIVE_TIMEOUT); // Reverse 12 in
|
|
1134
|
+
|
|
1135
|
+
telemetry.addData("Path", "Complete");
|
|
1136
|
+
telemetry.update();
|
|
1137
|
+
sleep(1000);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
/**
|
|
1141
|
+
* Drive a specified distance using encoder counts with PID
|
|
1142
|
+
* heading correction from the IMU.
|
|
1143
|
+
*
|
|
1144
|
+
* @param speed max motor power (0..1)
|
|
1145
|
+
* @param leftInches distance for left side (negative = reverse)
|
|
1146
|
+
* @param rightInches distance for right side (negative = reverse)
|
|
1147
|
+
* @param timeout max seconds before aborting
|
|
1148
|
+
*/
|
|
1149
|
+
public void encoderDrive(double speed, double leftInches, double rightInches, double timeout) {
|
|
1150
|
+
if (!opModeIsActive()) return;
|
|
1151
|
+
|
|
1152
|
+
// Read the dashboard-tunable constants fresh
|
|
1153
|
+
double countsPerInch = COUNTS_PER_INCH;
|
|
1154
|
+
|
|
1155
|
+
int leftTarget = frontLeft.getCurrentPosition() + (int)(leftInches * countsPerInch);
|
|
1156
|
+
int rightTarget = frontRight.getCurrentPosition() + (int)(rightInches * countsPerInch);
|
|
1157
|
+
|
|
1158
|
+
frontLeft.setTargetPosition(leftTarget);
|
|
1159
|
+
backLeft.setTargetPosition(leftTarget);
|
|
1160
|
+
frontRight.setTargetPosition(rightTarget);
|
|
1161
|
+
backRight.setTargetPosition(rightTarget);
|
|
1162
|
+
|
|
1163
|
+
setMotorMode(DcMotor.RunMode.RUN_TO_POSITION);
|
|
1164
|
+
|
|
1165
|
+
double targetHeading = imu.getRobotYawPitchRollAngles().getYaw(AngleUnit.DEGREES);
|
|
1166
|
+
double headingIntegral = 0;
|
|
1167
|
+
double lastHeadingError = 0;
|
|
1168
|
+
|
|
1169
|
+
ElapsedTime runtime = new ElapsedTime();
|
|
1170
|
+
ElapsedTime pidTimer = new ElapsedTime();
|
|
1171
|
+
|
|
1172
|
+
// Set initial power
|
|
1173
|
+
setAllMotorPower(Math.abs(speed));
|
|
1174
|
+
|
|
1175
|
+
while (opModeIsActive()
|
|
1176
|
+
&& runtime.seconds() < timeout
|
|
1177
|
+
&& (frontLeft.isBusy() || frontRight.isBusy())) {
|
|
1178
|
+
|
|
1179
|
+
double dt = pidTimer.seconds();
|
|
1180
|
+
pidTimer.reset();
|
|
1181
|
+
|
|
1182
|
+
// PID heading correction — read coefficients fresh each iteration
|
|
1183
|
+
double currentHeading = imu.getRobotYawPitchRollAngles().getYaw(AngleUnit.DEGREES);
|
|
1184
|
+
double headingError = targetHeading - currentHeading;
|
|
1185
|
+
|
|
1186
|
+
// Normalize to -180..180
|
|
1187
|
+
while (headingError > 180) headingError -= 360;
|
|
1188
|
+
while (headingError < -180) headingError += 360;
|
|
1189
|
+
|
|
1190
|
+
headingIntegral += headingError * dt;
|
|
1191
|
+
headingIntegral = Range.clip(headingIntegral, -50, 50);
|
|
1192
|
+
|
|
1193
|
+
double headingDerivative = (dt > 0) ? (headingError - lastHeadingError) / dt : 0;
|
|
1194
|
+
lastHeadingError = headingError;
|
|
1195
|
+
|
|
1196
|
+
double correction = (HEADING_KP * headingError)
|
|
1197
|
+
+ (HEADING_KI * headingIntegral)
|
|
1198
|
+
+ (HEADING_KD * headingDerivative);
|
|
1199
|
+
|
|
1200
|
+
double leftPower = Range.clip(speed + correction, -1.0, 1.0);
|
|
1201
|
+
double rightPower = Range.clip(speed - correction, -1.0, 1.0);
|
|
1202
|
+
|
|
1203
|
+
frontLeft.setPower(Math.abs(leftPower));
|
|
1204
|
+
backLeft.setPower(Math.abs(leftPower));
|
|
1205
|
+
frontRight.setPower(Math.abs(rightPower));
|
|
1206
|
+
backRight.setPower(Math.abs(rightPower));
|
|
1207
|
+
|
|
1208
|
+
telemetry.addData("Target L/R", "%7d / %7d", leftTarget, rightTarget);
|
|
1209
|
+
telemetry.addData("Current L/R", "%7d / %7d",
|
|
1210
|
+
frontLeft.getCurrentPosition(), frontRight.getCurrentPosition());
|
|
1211
|
+
telemetry.addData("Heading Target", "%.1f", targetHeading);
|
|
1212
|
+
telemetry.addData("Heading Current", "%.1f", currentHeading);
|
|
1213
|
+
telemetry.addData("Heading Error", "%.2f", headingError);
|
|
1214
|
+
telemetry.addData("Correction", "%.3f", correction);
|
|
1215
|
+
telemetry.addData("Power L/R", "%.2f / %.2f", leftPower, rightPower);
|
|
1216
|
+
telemetry.update();
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
setAllMotorPower(0);
|
|
1220
|
+
setMotorMode(DcMotor.RunMode.RUN_USING_ENCODER);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
private void setMotorMode(DcMotor.RunMode mode) {
|
|
1224
|
+
frontLeft.setMode(mode);
|
|
1225
|
+
frontRight.setMode(mode);
|
|
1226
|
+
backLeft.setMode(mode);
|
|
1227
|
+
backRight.setMode(mode);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
private void setAllMotorPower(double power) {
|
|
1231
|
+
frontLeft.setPower(power);
|
|
1232
|
+
frontRight.setPower(power);
|
|
1233
|
+
backLeft.setPower(power);
|
|
1234
|
+
backRight.setPower(power);
|
|
1235
|
+
}
|
|
1236
|
+
}`,
|
|
1237
|
+
"field-centric-drive": `package org.firstinspires.ftc.teamcode;
|
|
1238
|
+
|
|
1239
|
+
import com.acmerobotics.dashboard.FtcDashboard;
|
|
1240
|
+
import com.acmerobotics.dashboard.config.Config;
|
|
1241
|
+
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
|
1242
|
+
import com.qualcomm.hardware.lynx.LynxModule;
|
|
1243
|
+
import com.qualcomm.hardware.rev.RevHubOrientationOnRobot;
|
|
1244
|
+
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
|
1245
|
+
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
|
1246
|
+
import com.qualcomm.robotcore.hardware.DcMotor;
|
|
1247
|
+
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
|
1248
|
+
import com.qualcomm.robotcore.hardware.IMU;
|
|
1249
|
+
import com.qualcomm.robotcore.util.ElapsedTime;
|
|
1250
|
+
|
|
1251
|
+
import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit;
|
|
1252
|
+
|
|
1253
|
+
import java.util.List;
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* Field-centric mecanum TeleOp with IMU heading reference.
|
|
1257
|
+
*
|
|
1258
|
+
* The left stick controls translation relative to the FIELD,
|
|
1259
|
+
* not the robot. The right stick controls rotation. Press
|
|
1260
|
+
* gamepad1.back to reset the heading reference so "forward"
|
|
1261
|
+
* always means toward the opposing alliance wall.
|
|
1262
|
+
*/
|
|
1263
|
+
@Config
|
|
1264
|
+
@TeleOp(name = "Field-Centric Mecanum", group = "TeleOp")
|
|
1265
|
+
public class FieldCentricDriveExample extends OpMode {
|
|
1266
|
+
|
|
1267
|
+
public static double NORMAL_SPEED = 1.0;
|
|
1268
|
+
public static double SLOW_SPEED = 0.35;
|
|
1269
|
+
|
|
1270
|
+
private DcMotorEx frontLeft, frontRight, backLeft, backRight;
|
|
1271
|
+
private IMU imu;
|
|
1272
|
+
|
|
1273
|
+
private List<LynxModule> allHubs;
|
|
1274
|
+
private ElapsedTime loopTimer;
|
|
1275
|
+
|
|
1276
|
+
private boolean slowModeActive = false;
|
|
1277
|
+
private boolean previousAState = false;
|
|
1278
|
+
|
|
1279
|
+
@Override
|
|
1280
|
+
public void init() {
|
|
1281
|
+
// --- Manual bulk reads ---
|
|
1282
|
+
allHubs = hardwareMap.getAll(LynxModule.class);
|
|
1283
|
+
for (LynxModule hub : allHubs) {
|
|
1284
|
+
hub.setBulkCachingMode(LynxModule.BulkCachingMode.MANUAL);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// --- Motors ---
|
|
1288
|
+
frontLeft = hardwareMap.get(DcMotorEx.class, "frontLeft");
|
|
1289
|
+
frontRight = hardwareMap.get(DcMotorEx.class, "frontRight");
|
|
1290
|
+
backLeft = hardwareMap.get(DcMotorEx.class, "backLeft");
|
|
1291
|
+
backRight = hardwareMap.get(DcMotorEx.class, "backRight");
|
|
1292
|
+
|
|
1293
|
+
frontLeft.setDirection(DcMotor.Direction.REVERSE);
|
|
1294
|
+
backLeft.setDirection(DcMotor.Direction.REVERSE);
|
|
1295
|
+
frontRight.setDirection(DcMotor.Direction.FORWARD);
|
|
1296
|
+
backRight.setDirection(DcMotor.Direction.FORWARD);
|
|
1297
|
+
|
|
1298
|
+
frontLeft.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
|
|
1299
|
+
frontRight.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
|
|
1300
|
+
backLeft.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
|
|
1301
|
+
backRight.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
|
|
1302
|
+
|
|
1303
|
+
// --- IMU ---
|
|
1304
|
+
imu = hardwareMap.get(IMU.class, "imu");
|
|
1305
|
+
IMU.Parameters imuParams = new IMU.Parameters(
|
|
1306
|
+
new RevHubOrientationOnRobot(
|
|
1307
|
+
RevHubOrientationOnRobot.LogoFacingDirection.UP,
|
|
1308
|
+
RevHubOrientationOnRobot.UsbFacingDirection.FORWARD));
|
|
1309
|
+
imu.initialize(imuParams);
|
|
1310
|
+
imu.resetYaw();
|
|
1311
|
+
|
|
1312
|
+
// --- Telemetry ---
|
|
1313
|
+
telemetry = new MultipleTelemetry(telemetry, FtcDashboard.getInstance().getTelemetry());
|
|
1314
|
+
loopTimer = new ElapsedTime();
|
|
1315
|
+
|
|
1316
|
+
telemetry.addData("Status", "Initialized — press Back to reset heading");
|
|
1317
|
+
telemetry.update();
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
@Override
|
|
1321
|
+
public void loop() {
|
|
1322
|
+
// Clear bulk cache once per loop
|
|
1323
|
+
for (LynxModule hub : allHubs) {
|
|
1324
|
+
hub.clearBulkCache();
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
loopTimer.reset();
|
|
1328
|
+
|
|
1329
|
+
// --- Reset heading reference ---
|
|
1330
|
+
if (gamepad1.back) {
|
|
1331
|
+
imu.resetYaw();
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
// --- Slow mode toggle (rising edge on A) ---
|
|
1335
|
+
boolean currentAState = gamepad1.a;
|
|
1336
|
+
if (currentAState && !previousAState) {
|
|
1337
|
+
slowModeActive = !slowModeActive;
|
|
1338
|
+
}
|
|
1339
|
+
previousAState = currentAState;
|
|
1340
|
+
|
|
1341
|
+
// Read speed from dashboard-tunable statics at point of use
|
|
1342
|
+
double speedMultiplier = slowModeActive ? SLOW_SPEED : NORMAL_SPEED;
|
|
1343
|
+
|
|
1344
|
+
// --- Read gamepad inputs ---
|
|
1345
|
+
double y = -gamepad1.left_stick_y; // forward is positive
|
|
1346
|
+
double x = gamepad1.left_stick_x; // right is positive
|
|
1347
|
+
double rx = gamepad1.right_stick_x; // clockwise is positive
|
|
1348
|
+
|
|
1349
|
+
// --- Get heading from IMU ---
|
|
1350
|
+
double heading = imu.getRobotYawPitchRollAngles().getYaw(AngleUnit.RADIANS);
|
|
1351
|
+
|
|
1352
|
+
// --- Field-centric rotation matrix ---
|
|
1353
|
+
double rotX = x * Math.cos(-heading) - y * Math.sin(-heading);
|
|
1354
|
+
double rotY = x * Math.sin(-heading) + y * Math.cos(-heading);
|
|
1355
|
+
|
|
1356
|
+
// Counteract imperfect strafing (optional 1.1 multiplier)
|
|
1357
|
+
rotX *= 1.1;
|
|
1358
|
+
|
|
1359
|
+
// --- Mecanum kinematics ---
|
|
1360
|
+
double denominator = Math.max(Math.abs(rotY) + Math.abs(rotX) + Math.abs(rx), 1.0);
|
|
1361
|
+
double flPower = (rotY + rotX + rx) / denominator;
|
|
1362
|
+
double blPower = (rotY - rotX + rx) / denominator;
|
|
1363
|
+
double frPower = (rotY - rotX - rx) / denominator;
|
|
1364
|
+
double brPower = (rotY + rotX - rx) / denominator;
|
|
1365
|
+
|
|
1366
|
+
// --- Apply speed multiplier and set power ---
|
|
1367
|
+
frontLeft.setPower(flPower * speedMultiplier);
|
|
1368
|
+
backLeft.setPower(blPower * speedMultiplier);
|
|
1369
|
+
frontRight.setPower(frPower * speedMultiplier);
|
|
1370
|
+
backRight.setPower(brPower * speedMultiplier);
|
|
1371
|
+
|
|
1372
|
+
// --- Telemetry ---
|
|
1373
|
+
double loopMs = loopTimer.milliseconds();
|
|
1374
|
+
telemetry.addData("--- Drive ---", "");
|
|
1375
|
+
telemetry.addData("Mode", "Field-Centric");
|
|
1376
|
+
telemetry.addData("Slow Mode", slowModeActive ? "ON" : "OFF");
|
|
1377
|
+
telemetry.addData("Speed", "%.2f", speedMultiplier);
|
|
1378
|
+
telemetry.addData("--- Heading ---", "");
|
|
1379
|
+
telemetry.addData("Heading (deg)", "%.1f", Math.toDegrees(heading));
|
|
1380
|
+
telemetry.addData("--- Motor Powers ---", "");
|
|
1381
|
+
telemetry.addData("FL / FR", "%.2f / %.2f", flPower * speedMultiplier, frPower * speedMultiplier);
|
|
1382
|
+
telemetry.addData("BL / BR", "%.2f / %.2f", blPower * speedMultiplier, brPower * speedMultiplier);
|
|
1383
|
+
telemetry.addData("--- Performance ---", "");
|
|
1384
|
+
telemetry.addData("Loop Time (ms)", "%.2f", loopMs);
|
|
1385
|
+
telemetry.update();
|
|
1386
|
+
}
|
|
1387
|
+
}`,
|
|
1388
|
+
"command-teleop": `package org.firstinspires.ftc.teamcode.opmode.teleop;
|
|
1389
|
+
|
|
1390
|
+
import com.acmerobotics.dashboard.FtcDashboard;
|
|
1391
|
+
import com.acmerobotics.dashboard.config.Config;
|
|
1392
|
+
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
|
1393
|
+
import com.seattlesolvers.solverslib.command.CommandOpMode;
|
|
1394
|
+
import com.seattlesolvers.solverslib.command.CommandScheduler;
|
|
1395
|
+
import com.seattlesolvers.solverslib.command.InstantCommand;
|
|
1396
|
+
import com.seattlesolvers.solverslib.command.RunCommand;
|
|
1397
|
+
import com.seattlesolvers.solverslib.command.SequentialCommandGroup;
|
|
1398
|
+
import com.seattlesolvers.solverslib.command.WaitCommand;
|
|
1399
|
+
import com.seattlesolvers.solverslib.gamepad.GamepadEx;
|
|
1400
|
+
import com.seattlesolvers.solverslib.gamepad.GamepadKeys;
|
|
1401
|
+
import com.qualcomm.hardware.lynx.LynxModule;
|
|
1402
|
+
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
|
1403
|
+
|
|
1404
|
+
import org.firstinspires.ftc.teamcode.commands.custom.DefaultDriveCommand;
|
|
1405
|
+
import org.firstinspires.ftc.teamcode.commands.custom.LiftToPositionCommand;
|
|
1406
|
+
import org.firstinspires.ftc.teamcode.commands.group.ScoreHighBasketCommand;
|
|
1407
|
+
import org.firstinspires.ftc.teamcode.subsystems.ClawSubsystem;
|
|
1408
|
+
import org.firstinspires.ftc.teamcode.subsystems.DriveSubsystem;
|
|
1409
|
+
import org.firstinspires.ftc.teamcode.subsystems.LiftSubsystem;
|
|
1410
|
+
|
|
1411
|
+
/**
|
|
1412
|
+
* Command-based TeleOp using SolversLib.
|
|
1413
|
+
*
|
|
1414
|
+
* Controls:
|
|
1415
|
+
* Driver (gamepad1):
|
|
1416
|
+
* Left stick = translate (field-centric or robot-centric)
|
|
1417
|
+
* Right stick X = rotate
|
|
1418
|
+
* A = toggle slow mode
|
|
1419
|
+
*
|
|
1420
|
+
* Operator (gamepad2):
|
|
1421
|
+
* A = toggle claw open/close
|
|
1422
|
+
* Y = score high basket macro (lift → open → lower)
|
|
1423
|
+
* DPAD_UP = lift to high basket position
|
|
1424
|
+
* DPAD_DN = lift to home position
|
|
1425
|
+
* DPAD_LT = lift to low basket position
|
|
1426
|
+
* LB = manual lift down (while held)
|
|
1427
|
+
* RB = manual lift up (while held)
|
|
1428
|
+
*/
|
|
1429
|
+
@Config
|
|
1430
|
+
@TeleOp(name = "Command TeleOp", group = "Competition")
|
|
1431
|
+
public class CommandTeleOpExample extends CommandOpMode {
|
|
1432
|
+
|
|
1433
|
+
public static double SLOW_MULTIPLIER = 0.35;
|
|
1434
|
+
public static double NORMAL_MULTIPLIER = 1.0;
|
|
1435
|
+
|
|
1436
|
+
private boolean slowMode = false;
|
|
1437
|
+
|
|
1438
|
+
@Override
|
|
1439
|
+
public void initialize() {
|
|
1440
|
+
// --- Bulk reads via scheduler ---
|
|
1441
|
+
CommandScheduler.getInstance().setBulkCacheMode(LynxModule.BulkCachingMode.MANUAL);
|
|
1442
|
+
|
|
1443
|
+
// --- Telemetry to Dashboard ---
|
|
1444
|
+
telemetry = new MultipleTelemetry(telemetry, FtcDashboard.getInstance().getTelemetry());
|
|
1445
|
+
|
|
1446
|
+
// --- Gamepads ---
|
|
1447
|
+
GamepadEx driverGp = new GamepadEx(gamepad1);
|
|
1448
|
+
GamepadEx operatorGp = new GamepadEx(gamepad2);
|
|
1449
|
+
|
|
1450
|
+
// --- Subsystems ---
|
|
1451
|
+
DriveSubsystem drive = new DriveSubsystem(hardwareMap);
|
|
1452
|
+
LiftSubsystem lift = new LiftSubsystem(hardwareMap);
|
|
1453
|
+
ClawSubsystem claw = new ClawSubsystem(hardwareMap);
|
|
1454
|
+
|
|
1455
|
+
// --- Default Drive Command ---
|
|
1456
|
+
drive.setDefaultCommand(new DefaultDriveCommand(
|
|
1457
|
+
drive,
|
|
1458
|
+
() -> -driverGp.getLeftY() * (slowMode ? SLOW_MULTIPLIER : NORMAL_MULTIPLIER),
|
|
1459
|
+
() -> driverGp.getLeftX() * (slowMode ? SLOW_MULTIPLIER : NORMAL_MULTIPLIER),
|
|
1460
|
+
() -> driverGp.getRightX() * (slowMode ? SLOW_MULTIPLIER : NORMAL_MULTIPLIER)
|
|
1461
|
+
));
|
|
1462
|
+
|
|
1463
|
+
// --- Driver Bindings ---
|
|
1464
|
+
driverGp.getGamepadButton(GamepadKeys.Button.A)
|
|
1465
|
+
.whenPressed(new InstantCommand(() -> slowMode = !slowMode));
|
|
1466
|
+
|
|
1467
|
+
// --- Operator Bindings ---
|
|
1468
|
+
|
|
1469
|
+
// Claw toggle
|
|
1470
|
+
operatorGp.getGamepadButton(GamepadKeys.Button.A)
|
|
1471
|
+
.whenPressed(new InstantCommand(claw::toggle, claw));
|
|
1472
|
+
|
|
1473
|
+
// Score macro: lift high → pause → open claw → pause → lower
|
|
1474
|
+
operatorGp.getGamepadButton(GamepadKeys.Button.Y)
|
|
1475
|
+
.whenPressed(new ScoreHighBasketCommand(lift, claw));
|
|
1476
|
+
|
|
1477
|
+
// Lift presets
|
|
1478
|
+
operatorGp.getGamepadButton(GamepadKeys.Button.DPAD_UP)
|
|
1479
|
+
.whenPressed(new LiftToPositionCommand(lift, LiftSubsystem.HIGH_BASKET));
|
|
1480
|
+
|
|
1481
|
+
operatorGp.getGamepadButton(GamepadKeys.Button.DPAD_DOWN)
|
|
1482
|
+
.whenPressed(new LiftToPositionCommand(lift, LiftSubsystem.HOME));
|
|
1483
|
+
|
|
1484
|
+
operatorGp.getGamepadButton(GamepadKeys.Button.DPAD_LEFT)
|
|
1485
|
+
.whenPressed(new LiftToPositionCommand(lift, LiftSubsystem.LOW_BASKET));
|
|
1486
|
+
|
|
1487
|
+
// Manual lift control (while held)
|
|
1488
|
+
operatorGp.getGamepadButton(GamepadKeys.Button.RIGHT_BUMPER)
|
|
1489
|
+
.whileHeld(new RunCommand(() -> lift.setManualPower(0.6), lift))
|
|
1490
|
+
.whenReleased(new InstantCommand(lift::exitManual, lift));
|
|
1491
|
+
|
|
1492
|
+
operatorGp.getGamepadButton(GamepadKeys.Button.LEFT_BUMPER)
|
|
1493
|
+
.whileHeld(new RunCommand(() -> lift.setManualPower(-0.4), lift))
|
|
1494
|
+
.whenReleased(new InstantCommand(lift::exitManual, lift));
|
|
1495
|
+
|
|
1496
|
+
telemetry.addData("Status", "Command TeleOp Initialized");
|
|
1497
|
+
telemetry.update();
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
@Override
|
|
1501
|
+
public void run() {
|
|
1502
|
+
super.run(); // MUST call super to tick the scheduler
|
|
1503
|
+
|
|
1504
|
+
telemetry.addData("Slow Mode", slowMode ? "ON" : "OFF");
|
|
1505
|
+
telemetry.update();
|
|
1506
|
+
}
|
|
1507
|
+
}`,
|
|
1508
|
+
"command-auto": `package org.firstinspires.ftc.teamcode.opmode.auton;
|
|
1509
|
+
|
|
1510
|
+
import com.acmerobotics.dashboard.FtcDashboard;
|
|
1511
|
+
import com.acmerobotics.dashboard.config.Config;
|
|
1512
|
+
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
|
1513
|
+
import com.pedropathing.follower.Follower;
|
|
1514
|
+
import com.pedropathing.localization.Pose;
|
|
1515
|
+
import com.pedropathing.pathgen.BezierLine;
|
|
1516
|
+
import com.pedropathing.pathgen.PathChain;
|
|
1517
|
+
import com.pedropathing.pathgen.Point;
|
|
1518
|
+
import com.seattlesolvers.solverslib.command.CommandOpMode;
|
|
1519
|
+
import com.seattlesolvers.solverslib.command.CommandScheduler;
|
|
1520
|
+
import com.seattlesolvers.solverslib.command.InstantCommand;
|
|
1521
|
+
import com.seattlesolvers.solverslib.command.SequentialCommandGroup;
|
|
1522
|
+
import com.seattlesolvers.solverslib.command.WaitCommand;
|
|
1523
|
+
import com.seattlesolvers.solverslib.command.WaitUntilCommand;
|
|
1524
|
+
import com.qualcomm.hardware.lynx.LynxModule;
|
|
1525
|
+
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
|
1526
|
+
|
|
1527
|
+
import org.firstinspires.ftc.teamcode.commands.custom.FollowPathCommand;
|
|
1528
|
+
import org.firstinspires.ftc.teamcode.commands.custom.LiftToPositionCommand;
|
|
1529
|
+
import org.firstinspires.ftc.teamcode.subsystems.ClawSubsystem;
|
|
1530
|
+
import org.firstinspires.ftc.teamcode.subsystems.LiftSubsystem;
|
|
1531
|
+
|
|
1532
|
+
/**
|
|
1533
|
+
* Command-based autonomous using SolversLib + Pedro Pathing.
|
|
1534
|
+
*
|
|
1535
|
+
* This replaces the traditional FSM/switch-case pattern with a
|
|
1536
|
+
* declarative SequentialCommandGroup. Each step is a command,
|
|
1537
|
+
* and parallel actions use .alongWith().
|
|
1538
|
+
*/
|
|
1539
|
+
@Config
|
|
1540
|
+
@Autonomous(name = "Command Auto - Left", group = "Auto")
|
|
1541
|
+
public class CommandAutoExample extends CommandOpMode {
|
|
1542
|
+
|
|
1543
|
+
// Dashboard-tunable poses
|
|
1544
|
+
public static double START_X = 9.0, START_Y = 60.0, START_HEADING = 0;
|
|
1545
|
+
public static double SCORE_X = 38.0, SCORE_Y = 68.0;
|
|
1546
|
+
public static double SAMPLE1_X = 37.0, SAMPLE1_Y = 30.0;
|
|
1547
|
+
public static double PARK_X = 10.0, PARK_Y = 10.0;
|
|
1548
|
+
|
|
1549
|
+
private Follower follower;
|
|
1550
|
+
private LiftSubsystem lift;
|
|
1551
|
+
private ClawSubsystem claw;
|
|
1552
|
+
|
|
1553
|
+
@Override
|
|
1554
|
+
public void initialize() {
|
|
1555
|
+
// Bulk reads
|
|
1556
|
+
CommandScheduler.getInstance().setBulkCacheMode(LynxModule.BulkCachingMode.MANUAL);
|
|
1557
|
+
|
|
1558
|
+
// Telemetry
|
|
1559
|
+
telemetry = new MultipleTelemetry(telemetry, FtcDashboard.getInstance().getTelemetry());
|
|
1560
|
+
|
|
1561
|
+
// Subsystems
|
|
1562
|
+
follower = new Follower(hardwareMap);
|
|
1563
|
+
lift = new LiftSubsystem(hardwareMap);
|
|
1564
|
+
claw = new ClawSubsystem(hardwareMap);
|
|
1565
|
+
|
|
1566
|
+
// Starting pose
|
|
1567
|
+
Pose startPose = new Pose(START_X, START_Y, Math.toRadians(START_HEADING));
|
|
1568
|
+
follower.setStartingPose(startPose);
|
|
1569
|
+
|
|
1570
|
+
// Build paths
|
|
1571
|
+
PathChain scorePreload = follower.pathBuilder()
|
|
1572
|
+
.addPath(new BezierLine(
|
|
1573
|
+
new Point(START_X, START_Y, Point.CARTESIAN),
|
|
1574
|
+
new Point(SCORE_X, SCORE_Y, Point.CARTESIAN)))
|
|
1575
|
+
.setLinearHeadingInterpolation(Math.toRadians(START_HEADING), 0)
|
|
1576
|
+
.build();
|
|
1577
|
+
|
|
1578
|
+
PathChain grabSample1 = follower.pathBuilder()
|
|
1579
|
+
.addPath(new BezierLine(
|
|
1580
|
+
new Point(SCORE_X, SCORE_Y, Point.CARTESIAN),
|
|
1581
|
+
new Point(SAMPLE1_X, SAMPLE1_Y, Point.CARTESIAN)))
|
|
1582
|
+
.setLinearHeadingInterpolation(0, 0)
|
|
1583
|
+
.build();
|
|
1584
|
+
|
|
1585
|
+
PathChain scoreSample1 = follower.pathBuilder()
|
|
1586
|
+
.addPath(new BezierLine(
|
|
1587
|
+
new Point(SAMPLE1_X, SAMPLE1_Y, Point.CARTESIAN),
|
|
1588
|
+
new Point(SCORE_X, SCORE_Y, Point.CARTESIAN)))
|
|
1589
|
+
.setLinearHeadingInterpolation(0, 0)
|
|
1590
|
+
.build();
|
|
1591
|
+
|
|
1592
|
+
PathChain park = follower.pathBuilder()
|
|
1593
|
+
.addPath(new BezierLine(
|
|
1594
|
+
new Point(SCORE_X, SCORE_Y, Point.CARTESIAN),
|
|
1595
|
+
new Point(PARK_X, PARK_Y, Point.CARTESIAN)))
|
|
1596
|
+
.setLinearHeadingInterpolation(0, 0)
|
|
1597
|
+
.build();
|
|
1598
|
+
|
|
1599
|
+
// Schedule the full autonomous as one command group
|
|
1600
|
+
schedule(new SequentialCommandGroup(
|
|
1601
|
+
// === Score preloaded sample ===
|
|
1602
|
+
// Drive to scoring position WHILE raising the lift
|
|
1603
|
+
new FollowPathCommand(follower, scorePreload, true)
|
|
1604
|
+
.alongWith(new LiftToPositionCommand(lift, LiftSubsystem.HIGH_BASKET)),
|
|
1605
|
+
// Wait for lift to finish reaching target
|
|
1606
|
+
new WaitUntilCommand(lift::atTarget),
|
|
1607
|
+
// Open claw to release
|
|
1608
|
+
new InstantCommand(claw::open, claw),
|
|
1609
|
+
new WaitCommand(300),
|
|
1610
|
+
|
|
1611
|
+
// === Grab sample 1 ===
|
|
1612
|
+
// Drive to sample WHILE lowering lift to intake height
|
|
1613
|
+
new FollowPathCommand(follower, grabSample1, true)
|
|
1614
|
+
.alongWith(new LiftToPositionCommand(lift, LiftSubsystem.INTAKE)),
|
|
1615
|
+
// Close claw to grab
|
|
1616
|
+
new InstantCommand(claw::close, claw),
|
|
1617
|
+
new WaitCommand(200),
|
|
1618
|
+
|
|
1619
|
+
// === Score sample 1 ===
|
|
1620
|
+
new FollowPathCommand(follower, scoreSample1, true)
|
|
1621
|
+
.alongWith(new LiftToPositionCommand(lift, LiftSubsystem.HIGH_BASKET)),
|
|
1622
|
+
new WaitUntilCommand(lift::atTarget),
|
|
1623
|
+
new InstantCommand(claw::open, claw),
|
|
1624
|
+
new WaitCommand(300),
|
|
1625
|
+
|
|
1626
|
+
// === Park ===
|
|
1627
|
+
new FollowPathCommand(follower, park, false)
|
|
1628
|
+
.alongWith(new LiftToPositionCommand(lift, LiftSubsystem.HOME))
|
|
1629
|
+
));
|
|
1630
|
+
|
|
1631
|
+
telemetry.addData("Status", "Command Auto Initialized");
|
|
1632
|
+
telemetry.update();
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
@Override
|
|
1636
|
+
public void run() {
|
|
1637
|
+
super.run();
|
|
1638
|
+
|
|
1639
|
+
// Telemetry
|
|
1640
|
+
Pose pose = follower.getPose();
|
|
1641
|
+
telemetry.addData("X", "%.1f", pose.getX());
|
|
1642
|
+
telemetry.addData("Y", "%.1f", pose.getY());
|
|
1643
|
+
telemetry.addData("Heading", "%.1f", Math.toDegrees(pose.getHeading()));
|
|
1644
|
+
telemetry.addData("Lift Pos", lift.getPosition());
|
|
1645
|
+
telemetry.addData("Lift Target", lift.getTarget());
|
|
1646
|
+
telemetry.update();
|
|
1647
|
+
}
|
|
1648
|
+
}`,
|
|
1649
|
+
"command-subsystem": `package org.firstinspires.ftc.teamcode.subsystems;
|
|
1650
|
+
|
|
1651
|
+
import com.acmerobotics.dashboard.config.Config;
|
|
1652
|
+
import com.seattlesolvers.solverslib.command.SubsystemBase;
|
|
1653
|
+
import com.qualcomm.robotcore.hardware.DcMotor;
|
|
1654
|
+
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
|
1655
|
+
import com.qualcomm.robotcore.hardware.HardwareMap;
|
|
1656
|
+
import com.qualcomm.robotcore.util.ElapsedTime;
|
|
1657
|
+
|
|
1658
|
+
/**
|
|
1659
|
+
* Command-based lift subsystem using SolversLib's SubsystemBase.
|
|
1660
|
+
*
|
|
1661
|
+
* PID control runs continuously in periodic() — commands just set the target.
|
|
1662
|
+
* All tunable constants are @Config public static for FTC Dashboard.
|
|
1663
|
+
*
|
|
1664
|
+
* Usage:
|
|
1665
|
+
* LiftSubsystem lift = new LiftSubsystem(hardwareMap);
|
|
1666
|
+
* // Register is called in constructor
|
|
1667
|
+
*
|
|
1668
|
+
* // In a command:
|
|
1669
|
+
* lift.setTarget(LiftSubsystem.HIGH_BASKET);
|
|
1670
|
+
* // periodic() handles the PID automatically each scheduler tick
|
|
1671
|
+
*/
|
|
1672
|
+
@Config
|
|
1673
|
+
public class LiftSubsystem extends SubsystemBase {
|
|
1674
|
+
|
|
1675
|
+
// --- Dashboard-tunable PID coefficients ---
|
|
1676
|
+
public static double kP = 0.005;
|
|
1677
|
+
public static double kI = 0.0;
|
|
1678
|
+
public static double kD = 0.001;
|
|
1679
|
+
public static double kF = 0.12; // gravity feedforward for linear slide
|
|
1680
|
+
|
|
1681
|
+
// --- Dashboard-tunable position presets ---
|
|
1682
|
+
public static int HOME = 0;
|
|
1683
|
+
public static int INTAKE = 50;
|
|
1684
|
+
public static int LOW_BASKET = 1200;
|
|
1685
|
+
public static int HIGH_BASKET = 2600;
|
|
1686
|
+
public static int HIGH_CHAMBER = 1800;
|
|
1687
|
+
public static int TOLERANCE = 15;
|
|
1688
|
+
|
|
1689
|
+
public static double MAX_POWER = 1.0;
|
|
1690
|
+
|
|
1691
|
+
// --- Hardware ---
|
|
1692
|
+
private final DcMotorEx motor;
|
|
1693
|
+
|
|
1694
|
+
// --- State ---
|
|
1695
|
+
private int targetPosition = 0;
|
|
1696
|
+
private boolean manualMode = false;
|
|
1697
|
+
private double manualPower = 0;
|
|
1698
|
+
|
|
1699
|
+
// --- PID state ---
|
|
1700
|
+
private double integralSum = 0;
|
|
1701
|
+
private double lastError = 0;
|
|
1702
|
+
private final ElapsedTime timer = new ElapsedTime();
|
|
1703
|
+
|
|
1704
|
+
public LiftSubsystem(HardwareMap hardwareMap) {
|
|
1705
|
+
motor = hardwareMap.get(DcMotorEx.class, "liftMotor");
|
|
1706
|
+
motor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
|
|
1707
|
+
motor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
|
|
1708
|
+
motor.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
|
|
1709
|
+
|
|
1710
|
+
// Register with the CommandScheduler automatically
|
|
1711
|
+
register();
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
/**
|
|
1715
|
+
* Called EVERY scheduler loop — runs PID regardless of which command is active.
|
|
1716
|
+
* This ensures the mechanism is always actively controlled.
|
|
1717
|
+
*/
|
|
1718
|
+
@Override
|
|
1719
|
+
public void periodic() {
|
|
1720
|
+
if (manualMode) {
|
|
1721
|
+
motor.setPower(manualPower);
|
|
1722
|
+
// Track position so we can hold when manual mode exits
|
|
1723
|
+
targetPosition = motor.getCurrentPosition();
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
// --- Software PID ---
|
|
1728
|
+
// Read @Config coefficients fresh each loop for live Dashboard tuning
|
|
1729
|
+
int current = motor.getCurrentPosition();
|
|
1730
|
+
double error = targetPosition - current;
|
|
1731
|
+
double dt = timer.seconds();
|
|
1732
|
+
timer.reset();
|
|
1733
|
+
|
|
1734
|
+
// Proportional
|
|
1735
|
+
double pTerm = kP * error;
|
|
1736
|
+
|
|
1737
|
+
// Integral (with anti-windup clamping)
|
|
1738
|
+
integralSum += error * dt;
|
|
1739
|
+
integralSum = Math.max(-500, Math.min(500, integralSum));
|
|
1740
|
+
double iTerm = kI * integralSum;
|
|
1741
|
+
|
|
1742
|
+
// Derivative
|
|
1743
|
+
double dTerm = (dt > 0) ? kD * (error - lastError) / dt : 0;
|
|
1744
|
+
lastError = error;
|
|
1745
|
+
|
|
1746
|
+
// Feedforward (constant gravity compensation for linear slides)
|
|
1747
|
+
double fTerm = (targetPosition > 50) ? kF : 0;
|
|
1748
|
+
|
|
1749
|
+
// Total output, clamped
|
|
1750
|
+
double output = pTerm + iTerm + dTerm + fTerm;
|
|
1751
|
+
output = Math.max(-MAX_POWER, Math.min(MAX_POWER, output));
|
|
1752
|
+
motor.setPower(output);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// --- Target setters (called by commands) ---
|
|
1756
|
+
|
|
1757
|
+
public void setTarget(int position) {
|
|
1758
|
+
manualMode = false;
|
|
1759
|
+
targetPosition = position;
|
|
1760
|
+
integralSum = 0; // reset integral on new target to prevent windup
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
public void goHome() { setTarget(HOME); }
|
|
1764
|
+
public void goIntake() { setTarget(INTAKE); }
|
|
1765
|
+
public void goLowBasket() { setTarget(LOW_BASKET); }
|
|
1766
|
+
public void goHighBasket() { setTarget(HIGH_BASKET); }
|
|
1767
|
+
|
|
1768
|
+
// --- Manual control (for joystick override) ---
|
|
1769
|
+
|
|
1770
|
+
public void setManualPower(double power) {
|
|
1771
|
+
manualMode = true;
|
|
1772
|
+
manualPower = power;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
public void exitManual() {
|
|
1776
|
+
manualMode = false;
|
|
1777
|
+
// Hold current position when switching back to PID
|
|
1778
|
+
targetPosition = motor.getCurrentPosition();
|
|
1779
|
+
integralSum = 0;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
// --- Queries ---
|
|
1783
|
+
|
|
1784
|
+
public boolean atTarget() {
|
|
1785
|
+
return !manualMode && Math.abs(motor.getCurrentPosition() - targetPosition) < TOLERANCE;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
public int getPosition() {
|
|
1789
|
+
return motor.getCurrentPosition();
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
public int getTarget() {
|
|
1793
|
+
return targetPosition;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
public boolean isManual() {
|
|
1797
|
+
return manualMode;
|
|
1798
|
+
}
|
|
1799
|
+
}`
|
|
1800
|
+
};
|