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.
@@ -0,0 +1,1088 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FTC_SDK_KNOWLEDGE = void 0;
4
+ exports.FTC_SDK_KNOWLEDGE = {
5
+ overview: `
6
+ ## FTC (FIRST Tech Challenge) Overview
7
+
8
+ FIRST Tech Challenge (FTC) is a robotics competition where teams design, build, program, and operate
9
+ robots built with Android-based control systems. Robots are programmed using Java (primarily) or
10
+ Kotlin via Android Studio.
11
+
12
+ ### SDK Details
13
+ - **Current SDK Version**: 11.1.0
14
+ - **Platform**: Android-based (REV Control Hub / REV Expansion Hub)
15
+ - **IDE**: Android Studio (recommended), OnBot Java, or Blocks
16
+ - **Language**: Java (primary), Kotlin (supported)
17
+
18
+ ### Project Structure
19
+ Team-written code lives in:
20
+ \`\`\`
21
+ TeamCode/src/main/java/org/firstinspires/ftc/teamcode/
22
+ \`\`\`
23
+
24
+ Package declaration for all team files:
25
+ \`\`\`java
26
+ package org.firstinspires.ftc.teamcode;
27
+ \`\`\`
28
+
29
+ Or with sub-packages:
30
+ \`\`\`java
31
+ package org.firstinspires.ftc.teamcode.opmodes;
32
+ package org.firstinspires.ftc.teamcode.subsystems;
33
+ package org.firstinspires.ftc.teamcode.util;
34
+ \`\`\`
35
+
36
+ ### Key SDK Modules
37
+ - **FtcRobotController**: The main Android app module (DO NOT modify unless advanced)
38
+ - **TeamCode**: Where ALL team code goes — OpModes, subsystems, utilities
39
+ - **RobotCore**: Low-level hardware abstraction (provided by SDK)
40
+ - **Hardware**: Hardware device interfaces (provided by SDK)
41
+
42
+ ### Control System Hardware
43
+ - **REV Control Hub**: Main robot controller (has built-in Android computer)
44
+ - **REV Expansion Hub**: Additional I/O (connected via RS-485 to Control Hub)
45
+ - **Driver Station**: Android phone or REV Driver Hub running the DS app
46
+ - **Gamepads**: Logitech F310 or Xbox-compatible (plugged into Driver Station)
47
+ `,
48
+ opmodePatterns: `
49
+ ## OpMode Patterns and Lifecycle
50
+
51
+ There are two base classes for writing OpModes in FTC:
52
+
53
+ ### 1. Iterative OpMode (extends OpMode)
54
+
55
+ The iterative OpMode uses a state-machine-friendly callback lifecycle:
56
+
57
+ \`\`\`
58
+ init() → init_loop() → start() → loop() → stop()
59
+ \`\`\`
60
+
61
+ **Lifecycle Methods:**
62
+ - \`init()\` — Called ONCE when INIT is pressed. Initialize hardware, set starting positions.
63
+ - \`init_loop()\` — Called REPEATEDLY after init() until START is pressed. Use for sensor calibration, waiting feedback.
64
+ - \`start()\` — Called ONCE when START is pressed (or auto-start timer fires). Reset timers, start motion.
65
+ - \`loop()\` — Called REPEATEDLY after start(). Main robot logic goes here. Runs until STOP.
66
+ - \`stop()\` — Called ONCE when STOP is pressed. Clean up resources, stop motors.
67
+
68
+ **When to use:** Best for finite-state-machine (FSM) based designs, TeleOp with subsystem states,
69
+ and when you need non-blocking architecture. Ideal for complex autonomous routines with many parallel actions.
70
+
71
+ **Complete Example:**
72
+ \`\`\`java
73
+ package org.firstinspires.ftc.teamcode;
74
+
75
+ import com.qualcomm.robotcore.eventloop.opmode.OpMode;
76
+ import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
77
+ import com.qualcomm.robotcore.hardware.DcMotor;
78
+ import com.qualcomm.robotcore.hardware.Servo;
79
+
80
+ @TeleOp(name = "Iterative TeleOp Example", group = "Examples")
81
+ public class IterativeTeleOpExample extends OpMode {
82
+
83
+ private DcMotor frontLeft;
84
+ private DcMotor frontRight;
85
+ private DcMotor backLeft;
86
+ private DcMotor backRight;
87
+ private Servo claw;
88
+
89
+ private boolean clawOpen = false;
90
+ private boolean previousA = false;
91
+
92
+ @Override
93
+ public void init() {
94
+ frontLeft = hardwareMap.get(DcMotor.class, "frontLeft");
95
+ frontRight = hardwareMap.get(DcMotor.class, "frontRight");
96
+ backLeft = hardwareMap.get(DcMotor.class, "backLeft");
97
+ backRight = hardwareMap.get(DcMotor.class, "backRight");
98
+ claw = hardwareMap.get(Servo.class, "claw");
99
+
100
+ frontLeft.setDirection(DcMotor.Direction.REVERSE);
101
+ backLeft.setDirection(DcMotor.Direction.REVERSE);
102
+
103
+ frontLeft.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
104
+ frontRight.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
105
+ backLeft.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
106
+ backRight.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
107
+
108
+ claw.setPosition(0.0); // closed
109
+
110
+ telemetry.addData("Status", "Initialized");
111
+ telemetry.update();
112
+ }
113
+
114
+ @Override
115
+ public void init_loop() {
116
+ // Could do sensor calibration here
117
+ telemetry.addData("Status", "Waiting for start...");
118
+ telemetry.update();
119
+ }
120
+
121
+ @Override
122
+ public void start() {
123
+ resetRuntime();
124
+ telemetry.addData("Status", "Running");
125
+ telemetry.update();
126
+ }
127
+
128
+ @Override
129
+ public void loop() {
130
+ // Mecanum drive
131
+ double drive = -gamepad1.left_stick_y; // Negate Y: pushing stick forward = negative value
132
+ double strafe = gamepad1.left_stick_x;
133
+ double rotate = gamepad1.right_stick_x;
134
+
135
+ double frontLeftPower = drive + strafe + rotate;
136
+ double frontRightPower = drive - strafe - rotate;
137
+ double backLeftPower = drive - strafe + rotate;
138
+ double backRightPower = drive + strafe - rotate;
139
+
140
+ // Normalize
141
+ double maxPower = Math.max(1.0, Math.max(
142
+ Math.max(Math.abs(frontLeftPower), Math.abs(frontRightPower)),
143
+ Math.max(Math.abs(backLeftPower), Math.abs(backRightPower))
144
+ ));
145
+ frontLeft.setPower(frontLeftPower / maxPower);
146
+ frontRight.setPower(frontRightPower / maxPower);
147
+ backLeft.setPower(backLeftPower / maxPower);
148
+ backRight.setPower(backRightPower / maxPower);
149
+
150
+ // Toggle claw on A button (rising edge detection)
151
+ if (gamepad1.a && !previousA) {
152
+ clawOpen = !clawOpen;
153
+ claw.setPosition(clawOpen ? 0.5 : 0.0);
154
+ }
155
+ previousA = gamepad1.a;
156
+
157
+ telemetry.addData("Drive", "FL=%.2f FR=%.2f BL=%.2f BR=%.2f",
158
+ frontLeftPower, frontRightPower, backLeftPower, backRightPower);
159
+ telemetry.addData("Claw", clawOpen ? "Open" : "Closed");
160
+ telemetry.addData("Runtime", "%.1f seconds", getRuntime());
161
+ telemetry.update();
162
+ }
163
+
164
+ @Override
165
+ public void stop() {
166
+ frontLeft.setPower(0);
167
+ frontRight.setPower(0);
168
+ backLeft.setPower(0);
169
+ backRight.setPower(0);
170
+ }
171
+ }
172
+ \`\`\`
173
+
174
+ ### 2. LinearOpMode (extends LinearOpMode)
175
+
176
+ The linear OpMode runs as a single sequential method:
177
+
178
+ \`\`\`
179
+ runOpMode() {
180
+ // initialize
181
+ waitForStart();
182
+ // sequential logic
183
+ }
184
+ \`\`\`
185
+
186
+ **Key Methods:**
187
+ - \`runOpMode()\` — The single entry point. ALL code goes here.
188
+ - \`waitForStart()\` — Blocks until the START button is pressed.
189
+ - \`opModeIsActive()\` — Returns true while OpMode is running (not stopped). Use in while loops.
190
+ - \`opModeInInit()\` — Returns true while in INIT phase (before START). Use for init loops.
191
+ - \`idle()\` — Yields thread to allow other operations to run. Use in tight loops.
192
+ - \`sleep(long milliseconds)\` — Pauses execution for the specified time (only safe in LinearOpMode).
193
+
194
+ **When to use:** Best for sequential autonomous routines, simple TeleOp, beginners, and when
195
+ step-by-step execution flow is clearer. Ideal for linear autonomous paths.
196
+
197
+ **Complete Example:**
198
+ \`\`\`java
199
+ package org.firstinspires.ftc.teamcode;
200
+
201
+ import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
202
+ import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
203
+ import com.qualcomm.robotcore.hardware.DcMotor;
204
+ import com.qualcomm.robotcore.hardware.Servo;
205
+ import com.qualcomm.robotcore.util.ElapsedTime;
206
+
207
+ @Autonomous(name = "Linear Auto Example", group = "Examples")
208
+ public class LinearAutoExample extends LinearOpMode {
209
+
210
+ private DcMotor frontLeft;
211
+ private DcMotor frontRight;
212
+ private DcMotor backLeft;
213
+ private DcMotor backRight;
214
+ private Servo claw;
215
+
216
+ private ElapsedTime runtime = new ElapsedTime();
217
+
218
+ @Override
219
+ public void runOpMode() {
220
+ // INIT PHASE
221
+ frontLeft = hardwareMap.get(DcMotor.class, "frontLeft");
222
+ frontRight = hardwareMap.get(DcMotor.class, "frontRight");
223
+ backLeft = hardwareMap.get(DcMotor.class, "backLeft");
224
+ backRight = hardwareMap.get(DcMotor.class, "backRight");
225
+ claw = hardwareMap.get(Servo.class, "claw");
226
+
227
+ frontLeft.setDirection(DcMotor.Direction.REVERSE);
228
+ backLeft.setDirection(DcMotor.Direction.REVERSE);
229
+
230
+ claw.setPosition(0.5); // grip preloaded sample
231
+
232
+ telemetry.addData("Status", "Initialized");
233
+ telemetry.update();
234
+
235
+ // Init loop — runs until START pressed
236
+ while (opModeInInit()) {
237
+ telemetry.addData("Status", "Waiting for start...");
238
+ telemetry.update();
239
+ }
240
+
241
+ // PLAY PHASE — waitForStart() has been replaced by opModeInInit() above
242
+ runtime.reset();
243
+
244
+ // Drive forward
245
+ setDrivePower(0.5, 0.5, 0.5, 0.5);
246
+ sleep(1000);
247
+
248
+ // Stop
249
+ setDrivePower(0, 0, 0, 0);
250
+
251
+ // Strafe right
252
+ setDrivePower(0.5, -0.5, -0.5, 0.5);
253
+ sleep(500);
254
+
255
+ // Stop and release
256
+ setDrivePower(0, 0, 0, 0);
257
+ claw.setPosition(0.0); // open claw
258
+ sleep(500);
259
+
260
+ telemetry.addData("Status", "Auto Complete in %.1f sec", runtime.seconds());
261
+ telemetry.update();
262
+ }
263
+
264
+ private void setDrivePower(double fl, double fr, double bl, double br) {
265
+ frontLeft.setPower(fl);
266
+ frontRight.setPower(fr);
267
+ backLeft.setPower(bl);
268
+ backRight.setPower(br);
269
+ }
270
+ }
271
+ \`\`\`
272
+
273
+ ### OpMode Annotations
274
+
275
+ \`\`\`java
276
+ // Autonomous OpMode — appears in Autonomous dropdown on Driver Station
277
+ @Autonomous(name = "Blue Left Auto", group = "Competition")
278
+ public class BlueLeftAuto extends LinearOpMode { ... }
279
+
280
+ // TeleOp OpMode — appears in TeleOp dropdown on Driver Station
281
+ @TeleOp(name = "Main TeleOp", group = "Competition")
282
+ public class MainTeleOp extends OpMode { ... }
283
+
284
+ // Disable an OpMode — hides it from the Driver Station without deleting code
285
+ @TeleOp(name = "Old TeleOp", group = "Deprecated")
286
+ @Disabled
287
+ public class OldTeleOp extends OpMode { ... }
288
+ \`\`\`
289
+
290
+ **Annotation Parameters:**
291
+ - \`name\` — Display name on Driver Station (required, should be descriptive)
292
+ - \`group\` — Groups OpModes together in the dropdown (optional but recommended)
293
+ - \`@Disabled\` — Hides the OpMode from the Driver Station list
294
+
295
+ ### Choosing Between OpMode Types
296
+ | Criteria | Iterative (OpMode) | Linear (LinearOpMode) |
297
+ |---|---|---|
298
+ | Control flow | Callback-based (FSM) | Sequential (top-to-bottom) |
299
+ | sleep() safe? | NO — blocks the loop | YES — natural flow |
300
+ | TeleOp | Preferred | Works fine |
301
+ | Complex auto | FSM pattern needed | Simpler sequential code |
302
+ | Parallel actions | Natural with states | Requires threads or FSM overlay |
303
+ | Pedro Pathing | Works great with FSM | Works with built-in follower loop |
304
+ `,
305
+ hardwareMap: `
306
+ ## hardwareMap Usage
307
+
308
+ The \`hardwareMap\` object is available in all OpModes. It maps configured device names (from the
309
+ Robot Configuration on the Driver Station) to Java hardware objects.
310
+
311
+ ### Basic Pattern
312
+ \`\`\`java
313
+ DeviceType variable = hardwareMap.get(DeviceType.class, "configName");
314
+ \`\`\`
315
+
316
+ The \`"configName"\` MUST match the name in the robot's active configuration on the Driver Station exactly.
317
+
318
+ ### All Hardware Types
319
+
320
+ #### Motors
321
+ \`\`\`java
322
+ // Basic DC Motor
323
+ DcMotor motor = hardwareMap.get(DcMotor.class, "motor");
324
+
325
+ // Extended DC Motor (adds velocity control, current monitoring, PID access)
326
+ DcMotorEx motorEx = hardwareMap.get(DcMotorEx.class, "motor");
327
+
328
+ // You can also cast:
329
+ DcMotorEx motorEx = (DcMotorEx) hardwareMap.get(DcMotor.class, "motor");
330
+ \`\`\`
331
+
332
+ **Motor Configuration:**
333
+ \`\`\`java
334
+ motor.setDirection(DcMotor.Direction.FORWARD); // or REVERSE
335
+ motor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE); // or FLOAT
336
+ motor.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER); // raw power
337
+ motor.setMode(DcMotor.RunMode.RUN_USING_ENCODER); // velocity-regulated
338
+ motor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER); // reset encoder count
339
+ motor.setMode(DcMotor.RunMode.RUN_TO_POSITION); // PID to target
340
+
341
+ // RUN_TO_POSITION pattern (order matters!):
342
+ motor.setTargetPosition(1000); // SET TARGET FIRST
343
+ motor.setMode(DcMotor.RunMode.RUN_TO_POSITION); // THEN set mode
344
+ motor.setPower(0.5); // THEN set power
345
+ while (motor.isBusy()) { /* wait */ } // Wait for completion
346
+ motor.setPower(0);
347
+ \`\`\`
348
+
349
+ #### Servos
350
+ \`\`\`java
351
+ // Standard Servo (position 0.0 to 1.0)
352
+ Servo servo = hardwareMap.get(Servo.class, "servo");
353
+ servo.setPosition(0.5); // move to center
354
+
355
+ // Extended Servo (adds PWM range control)
356
+ ServoImplEx servoEx = hardwareMap.get(ServoImplEx.class, "servo");
357
+ servoEx.setPwmRange(new PwmControl.PwmRange(500, 2500)); // custom PWM range
358
+ servoEx.setPwmDisable(); // disable PWM signal (servo goes limp)
359
+ servoEx.setPwmEnable(); // re-enable PWM signal
360
+
361
+ // Casting pattern:
362
+ ServoImplEx servoEx = (ServoImplEx) hardwareMap.get(Servo.class, "servo");
363
+
364
+ // Continuous Rotation Servo (power -1.0 to 1.0, no position control)
365
+ CRServo crServo = hardwareMap.get(CRServo.class, "crServo");
366
+ crServo.setPower(0.5); // spin at half speed
367
+ crServo.setPower(0); // stop
368
+ \`\`\`
369
+
370
+ #### IMU (Inertial Measurement Unit)
371
+ \`\`\`java
372
+ IMU imu = hardwareMap.get(IMU.class, "imu");
373
+
374
+ // Configure orientation based on how the Control Hub is mounted
375
+ imu.initialize(new IMU.Parameters(new RevHubOrientationOnRobot(
376
+ RevHubOrientationOnRobot.LogoFacingDirection.UP,
377
+ RevHubOrientationOnRobot.UsbFacingDirection.FORWARD
378
+ )));
379
+
380
+ // Reset yaw
381
+ imu.resetYaw();
382
+
383
+ // Read angles (in degrees)
384
+ YawPitchRollAngles angles = imu.getRobotYawPitchRollAngles();
385
+ double heading = angles.getYaw(AngleUnit.DEGREES);
386
+ double pitch = angles.getPitch(AngleUnit.DEGREES);
387
+ double roll = angles.getRoll(AngleUnit.DEGREES);
388
+
389
+ // Read angular velocity
390
+ AngularVelocity angVel = imu.getRobotAngularVelocity(AngleUnit.DEGREES);
391
+ double yawRate = angVel.zRotationRate;
392
+ \`\`\`
393
+
394
+ #### Distance Sensor
395
+ \`\`\`java
396
+ DistanceSensor distanceSensor = hardwareMap.get(DistanceSensor.class, "distance");
397
+ double distCM = distanceSensor.getDistance(DistanceUnit.CM);
398
+ double distIN = distanceSensor.getDistance(DistanceUnit.INCH);
399
+
400
+ // REV Color/Distance sensor also implements DistanceSensor:
401
+ // Configure as "REV Color Sensor V3" in config, access as DistanceSensor
402
+ \`\`\`
403
+
404
+ #### Color Sensor
405
+ \`\`\`java
406
+ NormalizedColorSensor colorSensor = hardwareMap.get(NormalizedColorSensor.class, "color");
407
+ colorSensor.setGain(2.0f); // optional gain adjustment
408
+
409
+ NormalizedRGBA colors = colorSensor.getNormalizedColors();
410
+ float red = colors.red;
411
+ float green = colors.green;
412
+ float blue = colors.blue;
413
+ float alpha = colors.alpha;
414
+
415
+ // REV Color Sensor V3 can also be used as a DistanceSensor (dual interface):
416
+ DistanceSensor distFromColor = (DistanceSensor) hardwareMap.get(NormalizedColorSensor.class, "color");
417
+ // OR
418
+ DistanceSensor distFromColor = hardwareMap.get(DistanceSensor.class, "color");
419
+ \`\`\`
420
+
421
+ #### Touch Sensor
422
+ \`\`\`java
423
+ TouchSensor touchSensor = hardwareMap.get(TouchSensor.class, "touch");
424
+ boolean isPressed = touchSensor.isPressed();
425
+ \`\`\`
426
+
427
+ #### Digital Channel
428
+ \`\`\`java
429
+ DigitalChannel digitalInput = hardwareMap.get(DigitalChannel.class, "digitalInput");
430
+ digitalInput.setMode(DigitalChannel.Mode.INPUT);
431
+ boolean state = digitalInput.getState(); // true = HIGH, false = LOW
432
+
433
+ // Magnetic limit switch example:
434
+ DigitalChannel limitSwitch = hardwareMap.get(DigitalChannel.class, "limitSwitch");
435
+ limitSwitch.setMode(DigitalChannel.Mode.INPUT);
436
+ boolean isTriggered = !limitSwitch.getState(); // Often active-low
437
+ \`\`\`
438
+
439
+ #### Analog Input
440
+ \`\`\`java
441
+ AnalogInput analogSensor = hardwareMap.get(AnalogInput.class, "analog");
442
+ double voltage = analogSensor.getVoltage(); // 0 to 3.3V
443
+ double maxVoltage = analogSensor.getMaxVoltage(); // typically 3.3V
444
+
445
+ // Potentiometer example:
446
+ double position = analogSensor.getVoltage() / analogSensor.getMaxVoltage(); // 0.0 to 1.0
447
+ \`\`\`
448
+
449
+ #### Webcam
450
+ \`\`\`java
451
+ WebcamName webcam = hardwareMap.get(WebcamName.class, "Webcam 1");
452
+ // Used with VisionPortal for AprilTag detection, TFOD, or custom processors
453
+ \`\`\`
454
+
455
+ #### GoBilda Pinpoint Odometry Computer
456
+ \`\`\`java
457
+ GoBildaPinpointDriver pinpoint = hardwareMap.get(GoBildaPinpointDriver.class, "pinpoint");
458
+
459
+ // Configure encoder resolution (ticks per mm)
460
+ pinpoint.setEncoderResolution(GoBildaPinpointDriver.GoBildaOdometryPods.goBILDA_4_BAR_POD);
461
+ // OR for custom pods:
462
+ // pinpoint.setEncoderResolution(13.26291192); // ticks per mm
463
+
464
+ // Set encoder directions
465
+ pinpoint.setEncoderDirections(
466
+ GoBildaPinpointDriver.EncoderDirection.FORWARD,
467
+ GoBildaPinpointDriver.EncoderDirection.FORWARD
468
+ );
469
+
470
+ // Set offsets from robot center (in mm)
471
+ pinpoint.setOffsets(-84.0, -168.0); // x offset, y offset
472
+
473
+ pinpoint.resetPosAndIMU(); // call in init
474
+
475
+ // In loop:
476
+ pinpoint.update(); // MUST call every loop
477
+ Pose2D pose = pinpoint.getPosition();
478
+ double x = pose.getX(DistanceUnit.MM);
479
+ double y = pose.getY(DistanceUnit.MM);
480
+ double heading = pose.getHeading(AngleUnit.RADIANS);
481
+ \`\`\`
482
+
483
+ #### SparkFun Optical Tracking Odometry Sensor (OTOS)
484
+ \`\`\`java
485
+ SparkFunOTOS otos = hardwareMap.get(SparkFunOTOS.class, "otos");
486
+
487
+ otos.setLinearUnit(DistanceUnit.INCH);
488
+ otos.setAngularUnit(AngleUnit.DEGREES);
489
+
490
+ // Offset from robot center
491
+ SparkFunOTOS.Pose2D offset = new SparkFunOTOS.Pose2D(0, 0, 0);
492
+ otos.setOffset(offset);
493
+
494
+ otos.setLinearScalar(1.0);
495
+ otos.setAngularScalar(1.0);
496
+
497
+ otos.calibrateImu();
498
+ otos.resetTracking();
499
+
500
+ // In loop:
501
+ SparkFunOTOS.Pose2D pos = otos.getPosition();
502
+ double x = pos.x;
503
+ double y = pos.y;
504
+ double h = pos.h;
505
+ \`\`\`
506
+
507
+ #### Limelight 3A
508
+ \`\`\`java
509
+ Limelight3A limelight = hardwareMap.get(Limelight3A.class, "limelight");
510
+ limelight.setPollRateHz(100);
511
+ limelight.start();
512
+ limelight.pipelineSwitch(0); // switch to pipeline 0
513
+
514
+ // In loop:
515
+ LLResult result = limelight.getLatestResult();
516
+ if (result != null && result.isValid()) {
517
+ double tx = result.getTx(); // horizontal offset
518
+ double ty = result.getTy(); // vertical offset
519
+ double ta = result.getTa(); // target area
520
+
521
+ // AprilTag results
522
+ List<LLResultTypes.FiducialResult> fiducials = result.getFiducialResults();
523
+ for (LLResultTypes.FiducialResult fr : fiducials) {
524
+ int id = fr.getFiducialId();
525
+ // fr.getRobotPoseFieldSpace() etc.
526
+ }
527
+ }
528
+ \`\`\`
529
+
530
+ ### Bulk Read Optimization (LynxModule)
531
+ \`\`\`java
532
+ // Get all Lynx modules (Control Hub + Expansion Hubs)
533
+ List<LynxModule> allHubs = hardwareMap.getAll(LynxModule.class);
534
+
535
+ // Set bulk caching mode to MANUAL for best performance
536
+ for (LynxModule hub : allHubs) {
537
+ hub.setBulkCachingMode(LynxModule.BulkCachingMode.MANUAL);
538
+ }
539
+
540
+ // In your loop, clear cache ONCE at the start:
541
+ for (LynxModule hub : allHubs) {
542
+ hub.clearBulkCache();
543
+ }
544
+ // All subsequent hardware reads in this loop iteration use cached values
545
+ // This dramatically reduces I2C/USB traffic and speeds up loop times
546
+ \`\`\`
547
+
548
+ **Bulk Caching Modes:**
549
+ - \`OFF\` — Default. Every read is a separate USB transaction (slow).
550
+ - \`AUTO\` — Caches reads, auto-clears when a new read of same type is requested. Good for simple OpModes.
551
+ - \`MANUAL\` — You must call \`clearBulkCache()\` each loop. Best performance but you manage the cache.
552
+
553
+ ### Common Casting Patterns
554
+ \`\`\`java
555
+ // DcMotor → DcMotorEx (adds velocity PID, current reading)
556
+ DcMotorEx motorEx = (DcMotorEx) hardwareMap.get(DcMotor.class, "motor");
557
+
558
+ // Servo → ServoImplEx (adds PWM range control, PWM disable)
559
+ ServoImplEx servoEx = (ServoImplEx) hardwareMap.get(Servo.class, "servo");
560
+
561
+ // ColorSensor → DistanceSensor (REV Color Sensor V3 dual interface)
562
+ DistanceSensor dist = (DistanceSensor) hardwareMap.get(NormalizedColorSensor.class, "color");
563
+
564
+ // These all work because the underlying hardware implementations implement multiple interfaces
565
+ \`\`\`
566
+ `,
567
+ gamepadApi: `
568
+ ## Gamepad API
569
+
570
+ FTC provides two gamepads: \`gamepad1\` (primary driver) and \`gamepad2\` (secondary/operator).
571
+ Both are available as fields in any OpMode.
572
+
573
+ ### Joysticks (Analog Sticks)
574
+ All stick values range from **-1.0 to 1.0**.
575
+
576
+ \`\`\`java
577
+ gamepad1.left_stick_x // Left stick horizontal: -1.0 (left) to 1.0 (right)
578
+ gamepad1.left_stick_y // Left stick vertical: -1.0 (UP) to 1.0 (DOWN) ← INVERTED!
579
+ gamepad1.right_stick_x // Right stick horizontal: -1.0 (left) to 1.0 (right)
580
+ gamepad1.right_stick_y // Right stick vertical: -1.0 (UP) to 1.0 (DOWN) ← INVERTED!
581
+ \`\`\`
582
+
583
+ **CRITICAL: Y-axis is inverted!** Pushing the stick FORWARD (up) gives a NEGATIVE value.
584
+ This means for driving forward, you almost always negate the Y value:
585
+ \`\`\`java
586
+ double drive = -gamepad1.left_stick_y; // Now forward = positive
587
+ \`\`\`
588
+
589
+ ### Buttons (Digital — boolean)
590
+ \`\`\`java
591
+ gamepad1.a // A button (Xbox) / Cross (PS)
592
+ gamepad1.b // B button (Xbox) / Circle (PS)
593
+ gamepad1.x // X button (Xbox) / Square (PS)
594
+ gamepad1.y // Y button (Xbox) / Triangle (PS)
595
+
596
+ gamepad1.dpad_up // D-Pad up
597
+ gamepad1.dpad_down // D-Pad down
598
+ gamepad1.dpad_left // D-Pad left
599
+ gamepad1.dpad_right // D-Pad right
600
+
601
+ gamepad1.left_bumper // Left bumper (LB)
602
+ gamepad1.right_bumper // Right bumper (RB)
603
+
604
+ gamepad1.left_stick_button // Left stick click (L3)
605
+ gamepad1.right_stick_button // Right stick click (R3)
606
+
607
+ gamepad1.back // Back/Select button
608
+ gamepad1.guide // Guide/Home button (may not work on all controllers)
609
+ gamepad1.start // Start button
610
+ \`\`\`
611
+
612
+ ### Triggers (Analog — float)
613
+ Trigger values range from **0.0 (released) to 1.0 (fully pressed)**.
614
+
615
+ \`\`\`java
616
+ gamepad1.left_trigger // Left trigger: 0.0 to 1.0
617
+ gamepad1.right_trigger // Right trigger: 0.0 to 1.0
618
+ \`\`\`
619
+
620
+ ### Gamepad1 vs Gamepad2
621
+ - \`gamepad1\` — Connected to the FIRST gamepad slot on the Driver Station. Typically the **driver** (drivetrain control).
622
+ - \`gamepad2\` — Connected to the SECOND slot. Typically the **operator** (mechanisms: arm, claw, intake, etc.).
623
+ - Both gamepads have identical APIs.
624
+ - During competition, gamepads are assigned at the Driver Station.
625
+
626
+ ### Common Drive Patterns
627
+
628
+ **Mecanum Drive (most common FTC drivetrain):**
629
+ \`\`\`java
630
+ double drive = -gamepad1.left_stick_y; // Forward/backward (negated!)
631
+ double strafe = gamepad1.left_stick_x; // Left/right strafe
632
+ double rotate = gamepad1.right_stick_x; // Rotation
633
+
634
+ double fl = drive + strafe + rotate;
635
+ double fr = drive - strafe - rotate;
636
+ double bl = drive - strafe + rotate;
637
+ double br = drive + strafe - rotate;
638
+
639
+ // Normalize so no motor exceeds 1.0
640
+ double max = Math.max(1.0, Math.max(Math.max(Math.abs(fl), Math.abs(fr)),
641
+ Math.max(Math.abs(bl), Math.abs(br))));
642
+ frontLeft.setPower(fl / max);
643
+ frontRight.setPower(fr / max);
644
+ backLeft.setPower(bl / max);
645
+ backRight.setPower(br / max);
646
+ \`\`\`
647
+
648
+ **Tank Drive:**
649
+ \`\`\`java
650
+ double leftPower = -gamepad1.left_stick_y;
651
+ double rightPower = -gamepad1.right_stick_y;
652
+ leftMotor.setPower(leftPower);
653
+ rightMotor.setPower(rightPower);
654
+ \`\`\`
655
+
656
+ **Arcade Drive:**
657
+ \`\`\`java
658
+ double drive = -gamepad1.left_stick_y;
659
+ double rotate = gamepad1.right_stick_x;
660
+ leftMotor.setPower(drive + rotate);
661
+ rightMotor.setPower(drive - rotate);
662
+ \`\`\`
663
+
664
+ ### Button Edge Detection (Toggle Pattern)
665
+ Gamepads report CURRENT state each loop. For toggles, detect the rising edge:
666
+
667
+ \`\`\`java
668
+ // Fields
669
+ private boolean previousA = false;
670
+ private boolean toggleState = false;
671
+
672
+ // In loop():
673
+ boolean currentA = gamepad1.a;
674
+ if (currentA && !previousA) {
675
+ // Rising edge — button was just pressed
676
+ toggleState = !toggleState;
677
+ }
678
+ previousA = currentA;
679
+ \`\`\`
680
+
681
+ ### Gamepad Deadzone
682
+ Sticks may not return exactly 0 when released. Apply a deadzone:
683
+
684
+ \`\`\`java
685
+ private double applyDeadzone(double value, double deadzone) {
686
+ return Math.abs(value) < deadzone ? 0.0 : value;
687
+ }
688
+
689
+ // Usage:
690
+ double drive = applyDeadzone(-gamepad1.left_stick_y, 0.05);
691
+ \`\`\`
692
+
693
+ ### Trigger as Button
694
+ \`\`\`java
695
+ boolean leftTriggerPressed = gamepad1.left_trigger > 0.5;
696
+ \`\`\`
697
+ `,
698
+ bestPractices: `
699
+ ## Best Practices for FTC Coding
700
+
701
+ ### Package Structure
702
+ Organize your TeamCode directory with sub-packages:
703
+
704
+ \`\`\`
705
+ org.firstinspires.ftc.teamcode/
706
+ ├── opmodes/ # All OpModes (TeleOp, Autonomous)
707
+ │ ├── auto/ # Autonomous OpModes
708
+ │ └── teleop/ # TeleOp OpModes
709
+ ├── subsystems/ # Hardware subsystems (Drivetrain, Arm, Intake, etc.)
710
+ ├── util/ # Utility classes (PID controllers, math helpers, etc.)
711
+ └── pathing/ # Path following code, trajectories, waypoints
712
+ \`\`\`
713
+
714
+ ### Naming Conventions
715
+ - **UPPER_SNAKE_CASE** — For \`@Config\` (FTC Dashboard) static fields:
716
+ \`\`\`java
717
+ @Config
718
+ public class ArmConstants {
719
+ public static double ARM_KP = 0.01;
720
+ public static double ARM_KI = 0.0;
721
+ public static double ARM_KD = 0.001;
722
+ public static int ARM_TARGET_HIGH = 2500;
723
+ }
724
+ \`\`\`
725
+ - **camelCase** — For methods and local/instance variables:
726
+ \`\`\`java
727
+ public void setArmPosition(int targetTicks) { ... }
728
+ private double currentPower = 0.0;
729
+ \`\`\`
730
+ - **PascalCase** — For class names:
731
+ \`\`\`java
732
+ public class MecanumDrivetrain { ... }
733
+ public class BlueLeftAuto extends LinearOpMode { ... }
734
+ \`\`\`
735
+ - **Config names** — Use camelCase in robot configuration:
736
+ \`\`\`
737
+ frontLeft, frontRight, backLeft, backRight, armMotor, clawServo
738
+ \`\`\`
739
+
740
+ ### Common Mistakes and How to Avoid Them
741
+
742
+ #### 1. Forgetting follower.update() (Pedro Pathing)
743
+ \`\`\`java
744
+ // WRONG — robot won't move, path won't execute
745
+ follower.followPath(path);
746
+ // ... nothing happens
747
+
748
+ // CORRECT — must call update() every loop iteration
749
+ @Override
750
+ public void loop() {
751
+ follower.update(); // CRITICAL: call every loop!
752
+ // rest of your code
753
+ }
754
+ \`\`\`
755
+
756
+ #### 2. Caching @Config Values at Init Time
757
+ \`\`\`java
758
+ // WRONG — value is captured at init, Dashboard changes won't take effect
759
+ private double kP = ArmConstants.ARM_KP;
760
+
761
+ public void loop() {
762
+ double output = kP * error; // Always uses initial value
763
+ }
764
+
765
+ // CORRECT — read the static field each loop for live tuning
766
+ public void loop() {
767
+ double output = ArmConstants.ARM_KP * error; // Picks up Dashboard changes
768
+ }
769
+ \`\`\`
770
+
771
+ #### 3. Using Thread.sleep() in Iterative OpMode
772
+ \`\`\`java
773
+ // WRONG — blocks the entire loop, stops ALL updates including telemetry
774
+ @Override
775
+ public void loop() {
776
+ motor.setPower(1.0);
777
+ Thread.sleep(1000); // NEVER DO THIS in OpMode.loop()!
778
+ motor.setPower(0.0);
779
+ }
780
+
781
+ // CORRECT — use a timer / state machine
782
+ private ElapsedTime timer = new ElapsedTime();
783
+ private enum State { DRIVING, STOPPED }
784
+ private State state = State.DRIVING;
785
+
786
+ @Override
787
+ public void start() {
788
+ timer.reset();
789
+ state = State.DRIVING;
790
+ motor.setPower(1.0);
791
+ }
792
+
793
+ @Override
794
+ public void loop() {
795
+ switch (state) {
796
+ case DRIVING:
797
+ if (timer.seconds() > 1.0) {
798
+ motor.setPower(0.0);
799
+ state = State.STOPPED;
800
+ }
801
+ break;
802
+ case STOPPED:
803
+ break;
804
+ }
805
+ }
806
+ \`\`\`
807
+
808
+ #### 4. RUN_TO_POSITION Without setTargetPosition First
809
+ \`\`\`java
810
+ // WRONG — may run to position 0 or cause errors
811
+ motor.setMode(DcMotor.RunMode.RUN_TO_POSITION);
812
+ motor.setTargetPosition(1000);
813
+ motor.setPower(0.5);
814
+
815
+ // CORRECT — set target BEFORE setting mode
816
+ motor.setTargetPosition(1000); // 1. Set target
817
+ motor.setMode(DcMotor.RunMode.RUN_TO_POSITION); // 2. Set mode
818
+ motor.setPower(0.5); // 3. Set power
819
+ \`\`\`
820
+
821
+ #### 5. Not Negating Gamepad Y Stick
822
+ \`\`\`java
823
+ // WRONG — forward on stick = robot goes backward
824
+ double drive = gamepad1.left_stick_y;
825
+
826
+ // CORRECT — negate Y so forward stick = positive value = forward motion
827
+ double drive = -gamepad1.left_stick_y;
828
+ \`\`\`
829
+
830
+ #### 6. Not Checking opModeIsActive() in LinearOpMode Loops
831
+ \`\`\`java
832
+ // WRONG — loop continues even after STOP is pressed
833
+ while (motor.isBusy()) {
834
+ // keeps running forever if you hit STOP
835
+ }
836
+
837
+ // CORRECT — always check opModeIsActive()
838
+ while (opModeIsActive() && motor.isBusy()) {
839
+ idle();
840
+ }
841
+ \`\`\`
842
+
843
+ #### 7. Forgetting Direction Reversal on One Side
844
+ \`\`\`java
845
+ // WRONG — one side of drivetrain runs backward
846
+ frontLeft.setPower(1.0);
847
+ frontRight.setPower(1.0); // Robot turns instead of going straight
848
+
849
+ // CORRECT — reverse one side (typically left for most configurations)
850
+ frontLeft.setDirection(DcMotor.Direction.REVERSE);
851
+ backLeft.setDirection(DcMotor.Direction.REVERSE);
852
+ \`\`\`
853
+
854
+ ### Competition-Specific Rules
855
+
856
+ #### RS09 — No FTC Dashboard in Competition
857
+ - FTC Dashboard (the web-based tuning interface) is NOT allowed during competition matches.
858
+ - You MUST NOT have Dashboard actively serving during competition.
859
+ - However, you CAN keep the Dashboard library in your code — just don't access the web UI during matches.
860
+ - Dashboard is only for practice/tuning sessions.
861
+
862
+ #### Disable Wi-Fi Direct on Expansion Hub
863
+ - If using an Expansion Hub connected to a Control Hub, Wi-Fi Direct on the Expansion Hub MUST be disabled.
864
+ - The Expansion Hub connects via RS-485, not Wi-Fi.
865
+ - Having Wi-Fi Direct enabled on the Expansion Hub can cause interference and connectivity issues.
866
+ - Disable it via the REV Hardware Client or the Robot Controller settings.
867
+
868
+ ### General Best Practices
869
+ - **Use Bulk Reads**: Always enable LynxModule bulk caching (MANUAL mode) for faster loop times.
870
+ - **Keep loop() fast**: Target <20ms per loop. No blocking calls, no heavy computation.
871
+ - **Use subsystem classes**: Encapsulate hardware in subsystem classes, not raw in OpModes.
872
+ - **ElapsedTime for timing**: Use \`ElapsedTime\` instead of \`System.currentTimeMillis()\`.
873
+ - **Telemetry for debugging**: Use \`telemetry.addData()\` liberally during development.
874
+ - **Zero power on stop**: Always set motors to 0 power in \`stop()\` or end of \`runOpMode()\`.
875
+ - **Brake mode for arms/lifts**: Use \`BRAKE\` zero power behavior for mechanisms that fight gravity.
876
+ - **FLOAT mode for drivetrain**: Sometimes preferred for driver feel during TeleOp.
877
+ - **Version control**: Use Git. Commit working code before making changes.
878
+ - **Comment your code**: Future you (and your teammates) will thank you.
879
+ `,
880
+ devEnvironment: `
881
+ ## Development Environment Setup
882
+
883
+ Android Studio is the traditional IDE for FTC, but it is NOT required. The FTC SDK includes a
884
+ Gradle wrapper (\`gradlew\`/\`gradlew.bat\`) that handles the entire build process. Any editor or IDE
885
+ that supports Java and Gradle can be used — VS Code, IntelliJ IDEA, or even a plain text editor
886
+ with command-line builds.
887
+
888
+ ### Prerequisites (All IDEs)
889
+
890
+ #### 1. JDK 17 (Required)
891
+ The FTC SDK build requires JDK 17. Install it from one of these sources:
892
+ - **Adoptium (recommended)**: https://adoptium.net/ — select JDK 17 LTS
893
+ - **Oracle JDK 17**: https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html
894
+ - **macOS (Homebrew)**: \`brew install openjdk@17\`
895
+ - **Linux (apt)**: \`sudo apt install openjdk-17-jdk\`
896
+ - **Windows (winget)**: \`winget install EclipseAdoptium.Temurin.17.JDK\`
897
+
898
+ Verify installation:
899
+ \`\`\`bash
900
+ java -version
901
+ # Should show: openjdk version "17.x.x" or similar
902
+ \`\`\`
903
+
904
+ **IMPORTANT**: Do NOT rely on the JDK bundled with Android Studio Ladybug. Ladybug ships with
905
+ JDK 21 which is incompatible with the FTC SDK's Gradle configuration. Always install JDK 17
906
+ separately and point your IDE/build to it.
907
+
908
+ #### 2. Android SDK Command-Line Tools
909
+ You need the Android SDK for compiling Android APKs. There are two ways to get it:
910
+
911
+ **Option A: Install via Android Studio (easiest)**
912
+ - Install Android Studio, which bundles the full SDK
913
+ - SDK is typically installed at:
914
+ - **Windows**: \`C:\\Users\\<username>\\AppData\\Local\\Android\\Sdk\`
915
+ - **macOS**: \`~/Library/Android/sdk\`
916
+ - **Linux**: \`~/Android/Sdk\`
917
+
918
+ **Option B: Command-line only (no Android Studio)**
919
+ 1. Download "Command line tools only" from https://developer.android.com/studio#command-line-tools-only
920
+ 2. Unzip to a directory (e.g., \`~/android-sdk/cmdline-tools/latest/\`)
921
+ 3. Install required SDK components:
922
+ \`\`\`bash
923
+ sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0"
924
+ \`\`\`
925
+
926
+ #### 3. ANDROID_HOME Environment Variable
927
+ Set this to point to your Android SDK location:
928
+
929
+ **macOS/Linux** — add to \`~/.bashrc\`, \`~/.zshrc\`, or \`~/.profile\`:
930
+ \`\`\`bash
931
+ export ANDROID_HOME=~/Library/Android/sdk # macOS (Android Studio default)
932
+ # export ANDROID_HOME=~/Android/Sdk # Linux (Android Studio default)
933
+ # export ANDROID_HOME=~/android-sdk # manual install
934
+ export PATH="$ANDROID_HOME/platform-tools:$PATH" # adds adb to PATH
935
+ \`\`\`
936
+
937
+ **Windows** — System Environment Variables:
938
+ \`\`\`
939
+ ANDROID_HOME = C:\\Users\\<username>\\AppData\\Local\\Android\\Sdk
940
+ PATH += %ANDROID_HOME%\\platform-tools
941
+ \`\`\`
942
+
943
+ Verify:
944
+ \`\`\`bash
945
+ adb version
946
+ # Should show: Android Debug Bridge version x.x.x
947
+ \`\`\`
948
+
949
+ #### 4. ADB (Android Debug Bridge)
950
+ ADB is included in the Android SDK's \`platform-tools/\` directory. Once \`ANDROID_HOME\` is set
951
+ and \`platform-tools\` is in your PATH, \`adb\` should work from any terminal.
952
+
953
+ ---
954
+
955
+ ### VS Code Setup (Recommended Alternative to Android Studio)
956
+
957
+ VS Code is a fully viable IDE for FTC development. The Gradle wrapper handles all compilation
958
+ and deployment — VS Code just needs Java language support for code intelligence.
959
+
960
+ #### Required Extensions
961
+ 1. **Extension Pack for Java** (\`vscjava.vscode-java-pack\`)
962
+ - Includes: Language Support for Java, Debugger for Java, Maven for Java, Test Runner
963
+ - Provides: autocomplete, go-to-definition, error highlighting, refactoring
964
+ 2. **Gradle for Java** (\`vscjava.vscode-gradle\`)
965
+ - Provides: Gradle task runner sidebar, dependency management, sync
966
+
967
+ Install from the VS Code Extensions panel or via command line:
968
+ \`\`\`bash
969
+ code --install-extension vscjava.vscode-java-pack
970
+ code --install-extension vscjava.vscode-gradle
971
+ \`\`\`
972
+
973
+ #### VS Code Settings for FTC
974
+ Add these to your workspace \`.vscode/settings.json\`:
975
+ \`\`\`json
976
+ {
977
+ "java.configuration.runtimes": [
978
+ {
979
+ "name": "JavaSE-17",
980
+ "path": "/path/to/jdk-17",
981
+ "default": true
982
+ }
983
+ ],
984
+ "java.jdt.ls.java.home": "/path/to/jdk-17",
985
+ "java.import.gradle.java.home": "/path/to/jdk-17",
986
+ "java.compile.nullAnalysis.mode": "disabled"
987
+ }
988
+ \`\`\`
989
+
990
+ **Find your JDK 17 path:**
991
+ \`\`\`bash
992
+ # macOS (Homebrew)
993
+ /usr/libexec/java_home -v 17
994
+
995
+ # macOS (Adoptium)
996
+ /Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home
997
+
998
+ # Linux (apt)
999
+ /usr/lib/jvm/java-17-openjdk-amd64
1000
+
1001
+ # Windows (typical)
1002
+ C:\\Program Files\\Eclipse Adoptium\\jdk-17.x.x-hotspot
1003
+ \`\`\`
1004
+
1005
+ #### Opening the FTC Project in VS Code
1006
+ 1. Clone the FTC Robot Controller: \`git clone https://github.com/FIRST-Tech-Challenge/FtcRobotController.git\`
1007
+ 2. Open the folder in VS Code: \`code FtcRobotController\`
1008
+ 3. VS Code will detect the Gradle project automatically
1009
+ 4. When prompted "Java projects found", click **Yes** to import
1010
+ 5. Wait for Gradle sync to complete (first time takes 1-3 minutes)
1011
+ 6. The Java extension will provide full autocomplete, error checking, and navigation
1012
+
1013
+ #### VS Code Build & Deploy
1014
+ Use the integrated terminal:
1015
+ \`\`\`bash
1016
+ # Build the APK
1017
+ ./gradlew assembleDebug
1018
+
1019
+ # Deploy to connected device (USB or wireless ADB)
1020
+ ./gradlew installDebug
1021
+
1022
+ # Clean and rebuild
1023
+ ./gradlew clean assembleDebug
1024
+ \`\`\`
1025
+
1026
+ Or use the Gradle sidebar panel to run tasks visually.
1027
+
1028
+ ---
1029
+
1030
+ ### Android Studio Setup
1031
+
1032
+ Android Studio is the "official" IDE but is NOT required. If you choose to use it:
1033
+
1034
+ #### Installation
1035
+ 1. Download from https://developer.android.com/studio
1036
+ 2. Install — the installer includes the Android SDK and ADB
1037
+ 3. Open Android Studio, go through the setup wizard
1038
+ 4. Open the FTC project: **File → Open** → select the FtcRobotController folder
1039
+
1040
+ #### CRITICAL: Android Studio Ladybug JDK Issue
1041
+ Android Studio Ladybug (2024.2+) bundles JDK 21, which is **incompatible** with the FTC SDK's
1042
+ Gradle configuration. You MUST configure Android Studio to use JDK 17:
1043
+
1044
+ 1. Install JDK 17 separately (see Prerequisites above)
1045
+ 2. In Android Studio: **File → Settings → Build, Execution, Deployment → Build Tools → Gradle**
1046
+ 3. Set **Gradle JDK** to your JDK 17 installation
1047
+ 4. Click **Apply** and re-sync
1048
+
1049
+ #### Do NOT Upgrade Gradle
1050
+ When Android Studio prompts you to "upgrade the Gradle plugin" or "update the Gradle wrapper",
1051
+ **click NO / dismiss the notification**. The FTC SDK uses specific Gradle and AGP versions that
1052
+ are tested together. Upgrading can break the build.
1053
+
1054
+ ---
1055
+
1056
+ ### IntelliJ IDEA Setup
1057
+
1058
+ IntelliJ IDEA Community Edition (free) also works for FTC:
1059
+
1060
+ 1. Download from https://www.jetbrains.com/idea/download/ — select **Community** (free)
1061
+ 2. Open the FtcRobotController folder as a Gradle project
1062
+ 3. IntelliJ will auto-detect Gradle and import the project
1063
+ 4. Set the Gradle JDK to JDK 17: **File → Settings → Build → Gradle → Gradle JDK**
1064
+ 5. Build/deploy from the terminal: \`./gradlew assembleDebug\` / \`./gradlew installDebug\`
1065
+
1066
+ IntelliJ provides similar Java intelligence to Android Studio (they share the same base platform)
1067
+ but without Android-specific UI designers (which FTC doesn't use anyway).
1068
+
1069
+ ---
1070
+
1071
+ ### Summary: IDE Comparison for FTC
1072
+
1073
+ | Feature | VS Code | Android Studio | IntelliJ IDEA |
1074
+ |---|---|---|---|
1075
+ | Java autocomplete | Yes (via extension) | Yes (built-in) | Yes (built-in) |
1076
+ | Gradle build | Yes (terminal + extension) | Yes (built-in) | Yes (built-in) |
1077
+ | ADB deploy | Yes (terminal) | Yes (Run button) | Yes (terminal) |
1078
+ | Lightweight | Very lightweight | Heavy (2-4 GB RAM) | Moderate |
1079
+ | Setup complexity | Easy | Easy but JDK issue | Easy |
1080
+ | Android-specific features | No (not needed for FTC) | Yes (not needed) | No |
1081
+ | Cost | Free | Free | Free (Community) |
1082
+ | Recommended for | Teams wanting speed + simplicity | Teams wanting traditional setup | Teams familiar with JetBrains |
1083
+
1084
+ **Bottom line:** All three work. VS Code + Gradle wrapper is the lightest-weight option and avoids
1085
+ the Android Studio Ladybug JDK compatibility issue entirely. Android Studio is familiar to most
1086
+ FTC teams but requires the JDK 17 workaround. Use whatever your team is most comfortable with.
1087
+ `
1088
+ };