Opmodes
What an opmode does from INIT to STOP, and where robot code goes in it.
An opmode
Opmodes are what the drivers pick on the Driver Station. With BearPlatform they extend CoroutineOpMode, and carry
FIRST’s @TeleOp or @Autonomous annotation as usual:
@TeleOp(name = "Driving")
class Driving : CoroutineOpMode() {
private val robot by robot(::MyRobot)
override suspend fun CoroutineScope.onInit() {
everyTick { show("Ready when you are") }
}
override suspend fun CoroutineScope.onStart() {
controller1.a.onPress { robot.claw.grab() }
everyTick { robot.drive.driveWith(controller1.leftStick, controller1.rightStick.x) }
}
override fun onStop() {
// after everything has stopped, rarely needed
}
}
val robot by robot(::MyRobot)declares the opmode’s robot. It is created at INIT, not with the opmode, so a robot that can’t be created or set up, like one whose motor is missing from the robot configuration, fails INIT with the reason on the Driver Station. Use it fromonInitandonStarton. For an app serving several robots,robot(TeamRobots)creates whichever robot this one is (see Robots and parts).onInitruns from INIT until START: for menus, picking a starting position, or showing that the robot is ready. START stops it, along with every handler and every-loop block it set up.onStartruns from START until the opmode ends. An autonomous puts its steps here; a TeleOp sets up its buttons and its every-loop driving.onStopruns at the very end, after robot code and the robot’s parts have stopped.
Both onInit and onStart are coroutines: they can wait, and what they set up runs alongside them. See
Robot code over time.
INIT
Pressing INIT on the Driver Station:
- creates the robot and its parts;
- loads the robot’s settings (see Settings). A value that can’t be read,
like
drive.maxPower = fast, fails INIT and lists every such value, rather than failing in the middle of a match; - initializes the parts: their
init()sets up the hardware; - starts
onInit, and loops until START.
Until START, the Driver Station also shows what needs attention: settings nobody uses (often typos, with a “did you
mean” hint), warnings from the robot’s parts, and a battery below battery.low (12 V).
Every loop
From INIT to STOP, the opmode loops, and every loop:
- reads the gamepads, digital sensors and the newest readings of slow sensors, so everything in the loop sees the same state;
- updates the robot’s parts, parents before their parts, in the order they were created;
- runs robot code that is due: button handlers, every-loop blocks, actions;
- sends what was shown to the Driver Station and the robot’s graphs page.
A loop that takes less than loop.minTime (5 ms) sleeps for the rest: looping faster than sensors change only takes
time from the hubs. The first lines on the Driver Station tell how long loops take, like
Loop: 2.4 ms average, 6.1 ms longest, and how the battery is doing. Performance
explains what makes loops slow.
STOP
When the opmode ends, by STOP on the Driver Station or the end of autonomous, robot code is stopped and runs its
cleanup (finally blocks), then the robot’s parts stop: motors and servos the platform provides stop by themselves.
Then onStop runs.
The robot’s pose carries over to the next opmode: TeleOp starts where autonomous ended (see Localization).
Showing what happens
Opmodes show lines on the Driver Station, record values for the live graphs, write to the log, and offer commands that start actions from a laptop:
everyTick {
show("Pose") { robot.localizer.pose } // computed only when lines are sent
graph("shooter rpm", robot.shooter.rpm)
}
command("forward 60 cm") { robot.follower.driveTo(Pose(60.cm, 0.cm, 0.degrees)) }
Telemetry has all of it.
Opmodes for both alliances
Opmodes that differ only by alliance are one abstract class with a subclass for each:
abstract class Driving(private val alliance: Alliance) : CoroutineOpMode() {
private val robot by robot(::MyRobot)
override suspend fun CoroutineScope.onStart() {
val driversFace = Field2026.driversFace(alliance)
everyTick {
robot.drive.driveWith(controller1.leftStick, controller1.rightStick.x, robot.localizer.pose.heading, driversFace)
}
}
}
@TeleOp(name = "Red driving")
class RedDriving : Driving(Alliance.RED)
@TeleOp(name = "Blue driving")
class BlueDriving : Driving(Alliance.BLUE)
The season’s field (here Field2026) knows where each alliance’s drivers stand; see
Units and geometry.