Skip to content
Work in progress. This library and its docs are new and still changing, and some parts are not yet tested on a real robot. Something wrong or unclear?Tell us (a GitHub issue).

TeleOp module

Package: com.crossbotics.ftchelper.teleop

Learn it hands-on: 20 Easy TeleOp. Ready to copy: the starter kit’s MainTeleOp.

A whole TeleOp (the driver-controlled part of the match) where you only write what is special about your robot. Driving, the match timer, safety at START and STOP, and the tuning menu come ready-made. The robot in five files’s Controls.java is built on it, so with five files start there. Use EasyTeleOp directly with the starter kit or your own classes.

Class Use it to
EasyTeleOp a whole TeleOp OpMode: give it your robot settings and mechanisms, add the operator’s buttons
flowchart LR
    I["INIT<br/>drive, IMU, 'forward' from auto,<br/>your mechanisms + buttons,<br/>warnings, alliance"] --> S["START<br/>match timer starts"]
    S --> L["every loop:<br/>buttons -> driving -> everyLoop() -><br/>mechanisms update() -> telemetry"]
    L --> L
    L --> E["STOP (or an error)<br/>wheels and every mechanism stopped"]

@TeleOp(name = "Main TeleOp")
public class MainTeleOp extends EasyTeleOp {
private Robot robot;
// the robot's settings, shared with every autonomous
@Override protected RobotConfig robotConfig() { return new RobotSettings(); }
// the mechanisms: updated every loop, shown, stopped at the end
@Override protected Subsystems createSubsystems() {
robot = new Robot(hardwareMap);
return robot.all;
}
// the operator's buttons (gamepad 2)
@Override protected void bindButtons(ControllerBindings driver, ControllerBindings operator) {
operator.bindPress(Pad.G2, Btn.RB, () -> robot.claw.toggle("OPEN", "CLOSED"));
}
// anything else, every loop (here: the lift by hand)
@Override protected void everyLoop() {
robot.lift.manual(StickShaping.shape(-gamepad2.left_stick_y, 0.1, 1));
}
}
Driving (gamepad 1) left stick drive, right stick X turn, left trigger slow. Mecanum and tank, picked by your DriveConfig. See DriverControls.
Field-centric starts robot-centric (stick up = the way the robot faces). Y turns field-centric (stick up = away from the driver) on/off (two buzzes = on, one = off). Mecanum with an IMU only.
“Forward” carries on from where autonomous ended (DriverHeading), even after INIT, STOP, INIT again. Press and let go of BACK alone (one buzz) = “the robot now faces away from me”. With backTap(), a tap of BACK does your job instead, and holding BACK 1 s sets “forward”. Either way, “forward” is only reset while field-centric is on; in robot-centric driving BACK does not reset it, and the Driver Station says why for 3 s: BACK: "forward" is only reset in field-centric driving (turn it on first).
Alliance the one autonomous chose, from MatchMemory. If none came (no autonomous ran, or the robot app restarted), INIT says “Choose the alliance”: gamepad 1 X = BLUE, B = RED (also to change it). INIT also shows autonomous’s report.
Match timer 2:00, both gamepads buzz at 1:00 and 0:20. The time is on the screen.
Tuning menu GUIDE opens it (PRACTICE profile only). Gamepad 1 runs the menu; the wheels hold still. With a button map (buttonHelp(), as ControlMap has): a tap of GUIDE shows the map during the match (the robot still drives) and holding GUIDE 1 s opens the tuning menu.
Telemetry your mechanisms’ lines, drive mode, time; START + BACK together (either order) = debug lines on/off.
Warnings PRACTICE profile, tuned values that differ from the code, slow button actions or update()s.
Safety buttons held through START are ignored; the wheels and every mechanism stop at the end, even after an error.
Method Required? What you return / do
robotConfig() yes* new RobotSettings(): the RobotConfig shared with autonomous
createSubsystems() no your mechanisms. Default: none
bindButtons(driver, operator) no your buttons. Gamepad 2 on operator; extra gamepad 1 buttons on driver (they pause while the tuning menu is open). Taken on gamepad 1: Y (field-centric, when the drive has it), BACK, START (with BACK: debug lines), GUIDE.
everyLoop() no your code for every loop (sticks for mechanisms, simple rules). Must not wait.
addTelemetry(t) no extra screen lines
backTap() no a job for a tap of BACK (a Controls.java TeleOp: pause everything); then holding BACK 1 s resets “forward”
buttonHelp() no the button map’s lines: then a tap of GUIDE shows them, and holding GUIDE 1 s opens the tuning menu
addInitTelemetry(t) no extra screen lines during INIT
onStart() no runs once right after START, before the first loop (the first moment anything may move)

* Starter kit: return new RobotSettings();. Tutorials (09-20): return RobotSettings.config(); (the tutorial RobotSettings keeps its settings in static methods, so config() wraps them). Or, without a RobotConfig: override driveConfig() and createImu().

Note: the bindings name the gamepad (Pad.G1, Pad.G2) themselves. A Pad.G1 button put on operator works, but it does not pause while the tuning menu is open: put gamepad 1 buttons on driver.

Method Default
startFieldCentric() false true = start in field-centric
slowSpeed() 0.3 speed at a full squeeze of the slow trigger
fieldCentricButton() Btn.Y the gamepad 1 button for field-centric on/off; null = none
buzzAtSecondsLeft() {60, 20} when both gamepads buzz
drive(gamepad) the standard controls replace it to drive your own way, e.g. two-stick tank style: driving().tank(-gamepad.left_stick_y, -gamepad.right_stick_y, 1)
Method Gives
alliance() RED or BLUE
robotTelemetry() the screen helper (RobotTelemetry)
driving() the DriverControls (field-centric, slow)
heading() the DriverHeading, or null without an IMU
matchTimer() the MatchTimer
isTuningMenuOpen() true while the menu has gamepad 1: do not read gamepad 1’s sticks then

runOpMode() is final: your class cannot replace it, so the safety steps always run. Need a completely different loop? Write a plain LinearOpMode (tutorials 04 and 12) with the same parts.