How to sequence a PID motor return-to-zero after shooting using State Machines in FTC Java?

3 days ago 6
ARTICLE AD BOX

I’m working on an FTC TeleOp where a motorized spindexer (with encoder + PID) advances to shoot game pieces. After shooting, I want the spindexer to automatically return to encoder position 0.

I understand that I probably need to use state flags, but I’m not sure where or how to structure them in my control loop.

What I’m trying to do

Press right stick → spindexer advances to shoot (PID-controlled)

After the shot finishes & shot=true → spindexer should rotate back to encoder position 0 (targert=0)

While this happens, I don’t want:

the PID loop to be overridden immediately

the return-to-zero command to interrupt the shooting motion

Current issue

I already have a shot boolean flag, which I use to switch the behavior of the right stick / B button.
However, if I reuse this flag to also trigger the return-to-zero logic, the motor doesn’t wait for the shooting motion to finish — it immediately overrides the current target.

I tried doing something like:

if (!sorter.isBusy()) { target = 0; arm.setPosition(0.81); }

I learned that isBusy() only works with RUN_TO_POSITION mode so it can't work.
Here's the full relevent code:

@TeleOp public class sorter extends LinearOpMode { static final int LOAD = 155; static final int STEP_TICKS = 191; static final int BACK_STEPS = 7; static final int TOLERANCE = 3; static double MAX_POWER = 0.3; boolean shot = false; PIDController pid = new PIDController(0.004 , 0.0005, 0.0); int target = 0; DcMotor sorter; Servo arm; @Override public void runOpMode() { sorter = hardwareMap.get(DcMotor.class, "sorter"); arm = hardwareMap.get(Servo.class, "arm"); sorter.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER); sorter.setMode(DcMotor.RunMode.RUN_USING_ENCODER); waitForStart(); while (opModeIsActive()) { if (gamepad1.bWasPressed()) { if (!shot) { shot = true; MAX_POWER = 0.3; Thread t = new Thread(() -> { try { target -= LOAD - 20; pid.reset(getRuntime()); arm.setPosition(0.81); Thread.sleep(450); target += LOAD - 10; pid.reset(getRuntime()); } catch (InterruptedException ignored) {} }); t.start(); } else { MAX_POWER = 0.85; shot = false; target += BACK_STEPS * STEP_TICKS; pid.reset(getRuntime()); } } int pos = sorter.getCurrentPosition(); int error = target - pos; double power = pid.update(target, pos, getRuntime()); power = Math.max(-MAX_POWER, Math.min(MAX_POWER, power)); if (Math.abs(error) <= TOLERANCE) { power = 0; pid.reset(getRuntime()); } sorter.setPower(power); } } } }
Read Entire Article