Files
allwpilib/design-docs/opmodes.md
Zach Harel a8c7f3e3c6 [wpilib] Change opmodes to purely periodic (#8652)
1. Make the OpMode interface itself periodic; this means the only
differences between `OpMode` and `PeriodicOpMode` are the latter's
methods to add sideloaded periodic callbacks
2. Make OpModeRobot process callbacks in a similar fashion to TimedRobot
and
3. Add some lifecycle functions (discussed below)
4. Pull the callback priority queue from TimedRobot to a new class
called `PeriodicPriorityQueue` so that `TimedRobot` and `OpModeRobot`
have less duplication
5. Fix a typo in the DriverStationJNI class that causes a memory leak
when certain driver station sim calls
6. Port the C++ OpModeRobot tests to Java 

`OpModeRobot` now possesses some `IterativeRobotBase`-stye lifecycle
functions; these functions
1. `robotPeriodic` 
2. `simulationInit` and `simulationPeriodic` 
3. `disabledInit`, `disabledPeriodic`, and `disabledExit`
(note that `simulationInit` and `disabledInit` may be renamed to match
wpilibsuite#8719)

`OpModeRobot` also now processes `OpMode` changes (by the Driver
Station) in its `loopFunc` method, similar to
`IterativeRobotBase.loopFunc` processing game mode changes; `loopFunc`
is, similarly to `TimedRobot`, provided as a default `Callback`

---------

Signed-off-by: Zach Harel <zach@zharel.me>
Co-authored-by: Joseph Eng <91924258+KangarooKoala@users.noreply.github.com>
2026-04-10 13:40:17 -07:00

35 KiB
Raw Blame History

Summary

This document describes a standardized approach for operators to select different code to run for different robot modes of operation and for programmers to easily write code that creates these selection options.

A note on terminology: the word "opmode" is used in this document to describe this functionality.

Motivation

Operator selection of different code implementing unique top-level robot behaviorwithout recompilation of the robot programis a very common need across most FTC and FRC teams, so its desirable to have a standardized approach for cleanly structuring robot code to support this, along with integrated support for selection at the Driver Station.

Primary use cases for operator-selectable code include:

  • Multiple autonomous routines (e.g. following different paths, performing different actions)

  • Different teleoperated behavior (e.g. tank vs arcade drive, different button mappings, operating restrictions for robot demonstrations/guest drivers)

  • Testing (e.g. testing of the whole robot or a single subsystem, sensor, or motor)

Notably these use cases span both matches and off-field testing.

Secondarily, some teams may want to use different code structures for different use cases.

Background

Both FTC and FRC historically have had at least some level of support for operator selection of code, but the approaches used and resulting functionality and behaviors are quite different. To provide context and background driving the design decisions in this document, a summary of these behaviors (as of 2025) is provided in this section.

FRC

Core concepts

  • The robot has 3 fixed system-level modes of operation: autonomous, teleoperated, and test
  • The robot is in either enabled (actuators enabled) or disabled (actuators disabled) state
  • In competition matches, the Field Management System (FMS) automatically transitions between autonomous, disabled, and teleoperated modes
  • Robot code is structured around a top-level Robot class that instantiates the hardware objects as member variables (often organized into subsystem objects stored as members) and functions that run in each robot mode; the library robot base class handles calling these functions as directed by the DS
  • Selection of different user code routines within these system-level modes (e.g. multiple autonomous routines) is usually performed using a separate dashboard application
  • In robot code, the selection list is generated by user code instantiating a utility class called SendableChooser, adding options to it with function calls during initialization, and calling a function to get the selected option at the appropriate time

Driver Station

When not connected to the Field Management System (FMS), the FRC driver station application provides the operator a fixed selection of 4 different modes of robot operation (teleoperated, autonomous, test, and practice) and buttons to enable and disable the robot. In three of these modes (teleoperated, autonomous, and test), selecting a mode and enabling the robot immediately starts code execution for that mode for an unlimited amount of time (until the user disables the robot). The "practice" mode provides for off-field testing of match behavior by mimicking match behavior (automatically transitioning disabled->auto->disabled->teleop->disabled with pre-configured durations for each step).

Driver Station GUI

All actuators are disabled at all times the robot is disabled. The robot starts in disabled state (prior to receiving driver station packets) and the only way to enable the robot is via the driver station. Any loss of communication results in the robot transitioning back to disabled state. Similarly, the driver station starts in disabled state and automatically transitions to disabled state when communication is lost.

In autonomous mode, joystick inputs from the Driver Station are not passed to the robot; values are latched at the last state before entering autonomous mode. Joystick inputs are sent to the robot in all other modes, including disabled.

When a match starts, the FMS commands the driver station to enable the robot in autonomous mode for the autonomous period, followed by a short time disabled (typically 1-3 seconds), followed by enabling the robot in teleoperated mode for the teleoperated period, followed by disable.

Separate from the driver station application, WPILib-provided as well as custom dashboard applications are commonly utilized by teams to provide a drop-down list for selection of autonomous routines. Operators select the desired autonomous routine via the dashboard prior to the start of a match (or off-field testing of autonomous). Some teams use other methods to select different autonomous routines (e.g. physical jumpers on the robot, using joystick inputs while the robot is disabled), but the dashboard method is the standard approach.

Robot Code

The standard WPILib team code structure derives a single team-written Robot class from an "periodic" robot base class. This Robot class is constructed and run by the Java main() function. After construction, the base class implementation runs a periodic loop (typically running on a 20 ms period, although that can be changed by the user) that reads the enabled state and teleop/auto/test mode provided by the DS and calls overrideable functions for each mode (disabled, teleop, auto, and test), as well as a robotPeriodic overrideable function that is always called regardless of mode. Three overrideable functions are provided for each mode: an init function (called when the mode is transitioned into from another mode), a periodic function (called on a fixed period), and an exit function (called when the mode is transitioned out of into another mode). Teams override these functions in their Robot derived class to implement their robot code. Because of this code structure, teams generally create their hardware configuration (can be flat or organized into subsystems) and other objects as member variables within the Robot class (constructed at robot start) and share them across all modes of operation.

WPILib provides a class called SendableChooser for creating the drop-down lists shown on the dashboard. This class is a generic/template class that provides a map of string key (shown on the drop-down) and object value (read by robot code). This is typically displayed by dashboards as a simple list with no categorization. This feature is most often used by teams for autonomous routine selection, but is not limited to that use case. Examples and templates show teams how to instantiate this class, add it to the dashboard, and use it for operator selection of different autonomous routines (by reading the chosen value from the object and executing different code). Examples of how to use SendableChooser for operator selection in other modes (e.g. teleop or test) is not provided, and it's generally uncommon for teams to use it in that waymost team code has just a single teleop routine, and the test mode is rarely used (manual testing code is instead usually integrated as part of the teleop routine).

Historically, WPILib offered a "simple" (later renamed to "sample") robot base class that had single overrideable functions for teleop, auto, and test, with no outer periodic loop (the user was responsible for writing the loop). This was deprecated and removed circa 2016 as it was common to see teams writing autonomous loops or sleeps without proper exit condition checking (e.g. wait for the robot to drive X feet), resulting in the robot code never exiting autonomous and thus not transitioning to teleoperated in matches. After removal of this template, this relatively common issue has entirely disappeared.

FTC

Core concepts

  • Robot programs are structured around the concept of "opmodes"; there are two types of opmodes: teleoperated and autonomous
  • In competition matches, operators manually operate the DS to start the autonomous and teleoperated periods (there is no FMS)
  • Selection of opmodes is fully integrated into the DS; an opmode must be selected before Init is pressed
  • The DS has a three-step manual process for running robot code: select the opmode, press Init (robot enabled, opmode constructed, opmode init() called), press Play (opmode run() or equivalent called)
  • In robot code, opmodes are represented by users creating individual annotated classes; a library opmode manager handles generation of the opmode selection list (by scanning for annotated classes) and instantiating and calling class functions as directed by the DS
  • Hardware objects are created by the library based on an XML file; opmodes read this map to get access to the objects

Driver Station

Opmodes are presented in the DS as a selectable list containing both auto and teleop opmodes. Opmode selection on the DS is just UI candy; the robot knows nothing about what the operator has done until Init is pressed, at which point the opmode is communicated to the robot and the robot is enabled. If an auto op mode is selected, an additional button appears that allows the operator to pre-select a teleop op mode. This eases the match transition between auto and teleop, but it's still necessary for the operator to manually press Init and Play buttons to start the teleop mode.

FTC DS OpMode Selection Screen

The robot actuators are enabled as soon as the Init button is pressed. Pressing the Play button starts OpMode execution. Pressing the Stop button (shown after Init is pressed) disables the robot.

Auto modes by default stop/disable after 30 seconds, but this can be turned off for non-competition use.

Robot Code

FTC robot programs are structured around the concept of "opmodes." Opmodes are user classes that are registered as either teleoperated or autonomous opmodes using Java annotations. Several different opmode base class options are available, including a "linear" opmode that provides a single function (and the user is responsible for the loop) and an "iterative" opmode that calls a single function periodically.

The library contains an opmode manager that is responsible for registering and switching between opmodes based on operator input. The switching is generally done cooperatively, but if an opmode fails to return within a reasonable amount of time, several increasing steps are taken to try to get it to exit; if ultimately the opmode does not exit, the entire robot executable is terminated (and automatically restarted).

Opmode classes are constructed when the user taps the Init button in the DS. This works because hardware initialization processes never take more than a few seconds, and there's always a human lag between tapping Init and tapping Start.

Hardware configuration is stored in an XML file that is consumed on APK startup, or when a different configuration is selected by the operator. Consuming the XML file instantiates all objects (sensors, motors, servos, etc...) and stores them in a dictionary. This dictionary then persists until either the robot is power cycled, or the operator selects a different configuration. User code pulls items out of the dictionary, typically in an initialization portion of an opmode, and then use throughout the opmode. Notably, when the opmode finishes, or is stopped, the dictionary persists.

As opmodes are separate classes and there is no top-level robot class in the standard template, sharing state between opmodes is often a challenge for teams.

The Java annotation-based registration approach allows for specifying a string name for the opmode (displayed on the DS); this is automatically generated from the class name if none is provided. The annotation also allows specifying a string group name; this is used to provide display grouping of the opmodes at the DS. Two annotations are used to separately register autonomous and teleop opmodes to filter the displayed lists on the DS for each mode. Additionally, opmodes may be disabled through the use of an additional @Disabled annotation; these are hidden from the DS displayed list.

Requirements and Desirable Features

For competition, matches usually consist of an autonomous period followed by a teleoperated period. FTC and FRC currently have different amounts of operator interaction. In FRC, the time period between auto and teleop is very short (can be <1 second some years), and so it's a requirement for FRC that both the auto and teleop opmodes be able to be pre-selected by the operator prior to the start of the match, and for the DS to handle automatically transitioning between these opmodes. In FTC, there is currently no FMS, and so a longer delay between auto and teleop is common to allow teams to pick up controllers, but pre-selecting a teleop opmode enables a more efficient transition to teleop, so having this feature benefits FTC use cases as well.

It is not a requirement to support different programming languages for different operator-selectable opmodes (e.g. auto in Python, teleop in Java). Supporting this would require completely terminating the robot executable and starting a new robot executable (and re-initializing all the hardware), which can take a substantial and variable amount of time to complete. This is very undesirable for FRC due to the short transition from auto to teleop; although it's less problematic for FTC, it makes sharing state between opmodes essentially impossible, and sharing hardware configurations is also difficult.

Similarly, it is not a requirement to support running the same robot project on entirely different physical robots. That situation is better handled through entirely separate robot projects that define the unique hardware configuration.

A clear enable/disable in the Driver Station that disables all robot actuators when disabled is a requirement because it is a safety-critical feature for FRC due to the size and power of FRC robots.

The enabled "Init" step in the 2025 FTC SDK/DS is not a requirement due to anticipated rule changes. However, performing initialization of opmodes while the robot is still disabled is a requirement for both FTC and FRC, as it's important for user code to be able to do expensive opmode-specific operations (e.g. computing autonomous paths) prior to actually starting the match.

Providing the top-level teleop, autonomous, and test selection available in the 2025 FRC driver station is desirable and has few downsides for either program. In combination with code specifying the applicable opmodes (also desirable), this allows for filtering down of selectable opmodes. The "practice" mode feature of the 2025 FRC driver station which automatically transitions between modes based on time is a very useful tool for teams to simulate match transitions at home and should be retained, but should be renamed to reduce confusion, particularly as this mode is likely to be used for FTC matches without a FMS, "match" mode could be a good name for this feature.

Design

Overview / Key Features

  • DS provides a prominent enable/disable state control and a teleop/auto/test/match selector. For teleop/auto/test, enabling the robot immediately starts execution of the selected opmode; in match mode, a match sequence is followed (auto mode followed by a disabled delay, followed by teleop mode).

  • DS provides drop-down selector(s) for the user-defined opmodes; these are filtered based on the top-level mode selector (e.g. if auto is selected, a single drop-down selector of just the auto opmodes is provided). For match mode or when FMS-attached, two drop downs are provided (one for auto opmode selection and one for teleop opmode selection). The drop-down selector provides grouped categories as specified by the robot program.

  • Robot programs are structured to have a top-level Robot class (e.g. motors/sensors/subsystems); this is also where robot-wide initialization is performed (in the class constructor or static initializer block).

  • The Robot class has access to comprehensive lifecycle methods that are called at different stages of robot operation, providing behavior that occurs across all opmodes.

  • OpModes are usually registered via annotation of classes. These annotations may specify a name (or default to the class name), group name (for grouping of routines in the selection list), description, and optional display colors. Annotations are used to specify whether the class is a teleop, auto, or test opmode.

  • OpModes may also be registered via explicit function calls. As C++ does not support annotations, function call registration is the only available method in that language. If manually registered, publishOpModes() must be called to publish the list of opmodes to the DS.

  • For maximum flexibility, all code in the robot project has access to the enable/disable state, the overall robot teleop/auto/test mode, and the selected opmodes for teleop, auto, and test (even when the robot is disabled).

  • OpModes may be periodic or linear (or custom). The Robot base class handles switching between opmodes and opmode object instance creation. OpMode objects are constructed when the drop-down selection is made in the DS and run when the robot is enabled.

How opmodes work with the command-based framework is described in a separate design document.

Driver Station

For reference, the FRC driver station is shown below. This illustrates the enable/disable control (2) as well as the teleop/auto/practice/test selector (1). For this design, "practice" is renamed to "match" for clarity.

Driver Station GUI

One or two drop-down selectors are added to this main screen. One selector is displayed in teleop, auto, and test modes; two selectors (one for auto and one for teleop) is displayed in match mode (either manually selected by clicking "match," or when the DS is FMS-attached).

The drop-down lists have the opmodes (as defined by user code) organized into groups, similar to the below. The groups and opmodes are sorted.

Combo box with groups

Only the opmodes appropriate to the selected mode are shown in the corresponding list. For example, if the "Test" option is selected, a single drop-down list with only test opmodes listed will be shown.

Robot Code User-Facing API

Java is used for illustrative purposes. Python and C++ should follow a similar structure, except that C++ doesn't support annotations.

OpModeRobot

The OpModeRobot class is the base class for the user's Robot class. It extends RobotBase directly and implements the private library machinery for robot startup and robot execution (including creating and transitioning between opmodes in accordance with the opmode lifecycle, as described in the following section).

public abstract class OpModeRobot extends RobotBase {
  // OpMode registration methods
  public void addOpModeFactory(Supplier<OpMode> factory, RobotMode mode,
      String name, String group, String description,
      Color textColor, Color backgroundColor) {...}

  // add a particular opmode class (Java only)
  public void addOpMode(Class<? extends OpMode> cls, RobotMode mode,
      String name, String group, String description,
      Color textColor, Color backgroundColor) {...}
  public void addAnnotatedOpMode(Class<? extends OpMode> cls) {...}

  // add all annotated opmodes in a package and nested packages
  public void addAnnotatedOpModeClasses(Package pkg) {...}

  public void removeOpMode(RobotMode mode, String name) {...}
  public void publishOpModes() {...}
  public void clearOpModes() {...}

  // Robot lifecycle methods
  public void driverStationConnected() {
    // called once when the DS first connects
  }

  public void robotPeriodic() {
    // called periodically every loop, regardless of enabled state or opmode
  }

  public void simulationInit() {
    // called once during robot initialization in simulation
  }

  public void simulationPeriodic() {
    // called periodically in simulation
  }

  public void disabledInit() {
    // called once when the robot becomes disabled
  }

  public void disabledPeriodic() {
    // called periodically while the robot is disabled
  }

  public void disabledExit() {
    // called once when the robot exits disabled state
  }

  public void nonePeriodic() {
    // called periodically when no opmode is selected
  }
}

OpModeRobot (in Java) automatically scans the user's package and subpackages for @Autonomous, @Teleop, and @TestOpMode classes in its constructor and publishes that list to the DS. When opmodes are added or removed manually after construction, publishOpModes() must be called to push changes to the DS.

OpMode Classes

The OpMode interface serves as the base interface for all opmodes. Most users will extend PeriodicOpMode, but users may also implement OpMode directly for custom behavior.

The lifecycle of an opmode is:

  • When operator selects opmode on DS, a new opmode object is constructed
  • While selected and disabled, disabledPeriodic() is called
  • On disabled → enabled transition, start() is called once
  • While enabled, periodic() is called at OpModeRobot#getPeriod(), and additional callbacks from getCallbacks() are run at their own configured rates
  • If robot disables or a different opmode is selected while enabled, end() is called then close() is called (Java), or the object is destroyed (C++/Python); the object is not reused
  • If a different opmode is selected while disabled, only close() is called (Java), or the object is destroyed (C++); the object is not reused

Following close() being called (Java)/the opmode being destroyed (C++), a new opmode object is constructed based on the DS teleop/auto/test/match selector and selected opmode. In teleop/auto/test, the drop-down selection will be the same as before the previous enable, so the same opmode class is constructed again. In match (or when FMS-connected), only the selected auto opmode object is initially constructed; once auto completes, the selected teleop opmode object is constructed. Thus only zero or one opmode objects will ever be "alive" at any given time.

For consistency in operation, the library will ensure that disabledPeriodic() is always called at least once before start() is called.

User implementations of opmode classes may have either a no-parameter constructor or a constructor that accepts (a subclass of) the user's Robot class type. If available, the library will call the latter and pass the user's Robot object to it when constructing the class.

public interface OpMode extends AutoCloseable {
  // called periodically while selected and robot is disabled
  default void disabledPeriodic() {}

  // called once when this opmode transitions to enabled
  default void start() {}

  // called periodically while enabled at OpModeRobot#getPeriod()
  default void periodic() {}

  // called asynchronously when robot disables or switches opmodes while enabled
  default void end() {}

  // called when this opmode is no longer selected; object is not reused
  @Override
  default void close() {}

  // optional additional callbacks with custom timing
  default Set<PeriodicPriorityQueue.Callback> getCallbacks() {
    return Set.of();
  }
}
public abstract class PeriodicOpMode implements OpMode {
  protected PeriodicOpMode() {...}

  @Override
  public Set<PeriodicPriorityQueue.Callback> getCallbacks() {...}

  // additional periodic callbacks with custom rates/offsets
  public final void addPeriodic(Runnable callback, double period) {...}
  public final void addPeriodic(Runnable callback, double period, double offset) {...}
}

Annotations

All annotations are class-level. All elements are optional and may be omitted. If name is omitted, the class name is used. If group is omitted, the opmode is listed as ungrouped. The description is blank if it is omitted. Color strings are parsed by Color.fromString().

@Autonomous(String name, String group, String description,
            String textColor, String backgroundColor)
@Teleop(String name, String group, String description,
         String textColor, String backgroundColor)
@TestOpMode(String name, String group, String description,
            String textColor, String backgroundColor)

Example use cases:

// name defaults to class name; ungrouped
@Autonomous
public class MyAuto extends PeriodicOpMode {...}

// custom name and colors
@Teleop(name="Arcade", textColor="#FFFFFF", backgroundColor="#003366")
public class MyTeleop extends PeriodicOpMode {...}

@TestOpMode(name="Arm Test", group="mechanisms", description="tests arm")
public class MyTest extends PeriodicOpMode {...}

DriverStation class

Note: the DriverStation class contains many other functions; only the opmode-relevant functions are shown here.

public final class DriverStation {
  // these return the enabled state;
  // the robot is always disabled when the DS is not connected
  public static boolean isEnabled() {...}
  public static boolean isDisabled() {...}

  // these return the overall robot mode;
  // they return false when the robot is disabled
  public static boolean isAutonomous() {...}
  public static boolean isTeleoperated() {...}
  public static boolean isTest() {...}

  // returns the currently selected opmode for the currently selected robot mode;
  // this works even when the robot is disabled
  public static String getOpMode() {...}
  // returns the opmode ID instead of the the string name
  public static long getOpModeId() {...}

  // returns true if the opmode is the provided ID/name
  public static boolean isOpMode(long id) {...}
  public static boolean isOpMode(String name) {...}

  // add/remove opmodes
  public static long addOpMode(RobotMode mode, String name, String group, String description, Color textColor, Color backgroundColor) {...}
  public static long removeOpMode(RobotMode mode, String name) {...}
  // publish the list of opmodes to the DS
  public static void publishOpModes() {...}
  // clear the list of opmodes
  public static void clearOpModes() {...}
}

Java Robot Code Examples

Non-command-based robots will typically use classes to define opmodes.

The following example code for non-command-based Java demonstrates the following:

  • Robot class
  • A periodic autonomous opmode
  • A periodic teleop opmode
  • A periodic test opmode

Robot:

public class Robot extends OpModeRobot {
  public final DifferentialDrive drive = new DifferentialDrive(...);

  public Robot() {}
}

Autonomous opmode:

@Autonomous(name="Drive straight", group="Drive")
public class AutoDriveStraight extends PeriodicOpMode {
  private final Robot robot;
  private final Timer timer = new Timer();

  public AutoDriveStraight(Robot robot) {
    this.robot = robot;
  }

  @Override
  public void start() {
    timer.start();
  }

  @Override
  public void periodic() {
    // drive forward for 2 seconds at half speed, then stop
    if (!timer.hasElapsed(2.0)) {
      robot.drive.arcadeDrive(0.5, 0);
    } else {
      robot.drive.arcadeDrive(0, 0);
    }
  }
}

Teleop opmode:

@Teleop
public class Teleop extends PeriodicOpMode {
  private final Joystick joy = new Joystick(1);
  private final Robot robot;

  public Teleop(Robot robot) {
    this.robot = robot;
  }

  @Override
  public void periodic() {
    robot.drive.arcadeDrive(joy.getY(), joy.getX());
  }
}

Test opmode:

@TestOpMode(name="Blink dashboard indicator")
public class TestDashboardIndicator extends PeriodicOpMode {
  private final Timer timer = new Timer();
  private boolean m_logged;

  @Override
  public void start() {
    timer.start();
    m_logged = false;
  }

  @Override
  public void periodic() {
    if (!m_logged) {
      Telemetry.log("indicator", true);
      m_logged = true;
    }
    if (m_logged && timer.hasElapsed(0.5)) {
      Telemetry.log("indicator", false);
    }
  }
}

C++ Robot Code

C++ does not support annotations, so explicit registration via OpModeRobot function calls is required.

Python Robot Code

Should be able to be annotation (decorator) based, similar to Java.

HAL

At the HAL level, Control Word bits indicate enabled state and teleop/auto/test selection (identical to current WPILib). The Control Word needs to be lengthened to 64-bit to indicate the selected opmode, and functions to maintain the list of available opmodes need to be added. An "opmode ID" is the combination of the opmode hash and the robot mode (e.g. teleop/auto/test).

To communicate the selected opmode ID, HAL_ControlWord needs to become a 64-bit type; changing it from a bitfield to a type with helper functions also will improve the ease of use:

  • HAL_ControlWord HAL_MakeControlWord(int64_t opModeHash, HAL_RobotMode robotMode, HAL_Bool enabled, HAL_Bool eStop, HAL_Bool fmsAttached, HAL_Bool dsAttached) - builds a control word from its parts
  • int64_t HAL_ControlWord_GetOpModeHash(HAL_ControlWord word) - gets the opmode hash from the control word
  • int64_t HAL_ControlWord_GetOpModeId(HAL_ControlWord word) - gets the opmode ID from the control word (combination of hash and robot mode); 0 is returned if the hash is 0
  • void HAL_ControlWord_SetOpModeId(HAL_ControlWord* word, int64_t id) - sets the opmode ID portion of a control word
  • HAL_RobotMode HAL_ControlWord_GetRobotMode(HAL_ControlWord word) - gets the robot mode from the control word
  • HAL_Bool HAL_ControlWord_IsEnabled(HAL_ControlWord word) - returns true if the control word indicates the robot is enabled
  • HAL_Bool HAL_ControlWord_IsEStopped(HAL_ControlWord word) - returns true if the control word indicates the robot is estopped
  • HAL_Bool HAL_ControlWord_IsFMSAttached(HAL_ControlWord word) - returns true if the control word indicates the robot is attached to the FMS
  • HAL_Bool HAL_ControlWord_IsDSAttached(HAL_ControlWord word) - returns true if the control word indicates the robot is attached to the DS
  • int64_t HAL_MakeOpModeId(HAL_RobotMode mode, int64_t hash) - makes an opmode ID from a robot mode and an opmode hash
  • HAL_RobotMode HAL_OpMode_GetRobotMode(int64_t id) - gets the robot mode portion of an opmode ID
  • int64_t HAL_OpMode_GetHash(int64_t id) - gets the opmode hash portion of an opmode ID

Adding the following function enables maintaining the available opmode options list:

  • void HAL_SetOpModeOptions(HAL_OpModeOption* array, int count) sets the list of available opmode options

HAL_OpModeOption is a structure describing each option:

  • long id - unique ID identifying the opmode (robot mode, name) pair. This encodes the robot mode in the upper bits, indicating which robot mode the opmode should be visible for (auto/teleop/test)
  • string name
  • string group
  • string description
  • int textColor - optional, used to set the text color for the option in the DS GUI
  • int backgroundColor - optional, used to set the text color for the option in the DS GUI

The user program observe functions also need to be updated:

  • Replace HAL_ObserveUserProgramDisabled(), HAL_ObserveUserProgramAutonomous(), HAL_ObserveUserProgramTeleop(), and HAL_ObserveUserProgramTest() with HAL_ObserveUserProgram(HAL_ControlWord word)

DS Protocol

The list of opmodes (and details thereof) is communicated from the Robot to the DS via the tagged TCP link.

As in the current FRC protocol, the enabled state and teleop/auto/test selection are sent as part of the UDP control word from the DS to the Robot. Additionally, the selected opmode is sent as part of every UDP packet. This is done via a 56-bit hash of each opmode's name. Hash collisions are extremely low probability given the low quantity of opmodes, but this can be checked on the robot side when generating the opmode list, and spaces appended as necessary to the provided names to uniquify the hashes. The selected opmode is sent even when the robot is disabled.

Migration from 2025 FRC (WPILib)

Key differences from 2025 FRC:

  • The per-opmode overrideable functions in Robot are replaced with separate annotated per-opmode classes (for non-command-based)
  • SendableChooser is no longer required for selecting autonomous routines; instead this functionality is integrated into opmodes, as users can create/register multiple autonomous opmodes classes, and it's been extended to support multiple teleoperated and multiple test opmodes
  • Selection of autonomous opmodes is integrated into the DS instead of being performed by the dashboard

Migration from 2025 FTC (FTC SDK)

Key differences from 2025 FTC:

  • There is no hardware map. Hardware objects are instead directly instantiated inside a top-level Robot class. This Robot class is provided as a parameter to the opmode constructor.
  • Init is integrated into the opmode constructor. There is no equivalent to the the enabled init step; the opmode constructor is run as soon as the opmode is selected on the DS, while the robot is still disabled. Match periods will start as soon as the robot is enabled.
  • There is no @Disabled annotation for opmodes; the opmode annotation can simply be commented out to achieve the same result.
  • Opmodes are all periodic. While it may be possible to create linear-like behavior, there is no equivalent of the FTC SDK's LinearOpMode class.

Drawbacks

Alternatives

Use SendableChooser for more opmodes (teleop and test as well as autonomous); downsides of this:

  • Doesn't provide a first-class opmode lifecycle or opmode-specific callback scheduling model
  • Harder to use than annotations (generic class)
  • No grouping, no filtering by opmode (although these could be added)

Trades

  • Overall naming: mode? routine? opmode?

  • Naming of opmode functions? start-periodic-end, vs init-periodic-exit (2025 FRC PeriodicRobot), vs init-execute-end (2025 FRC Command), vs init-start-loop-stop (2025 FTC OpMode; note init behaves like the constructor here)

  • For matches, should we construct teleop at the same time as auto? If we do that, we probably need a disabledStart() or 2025 FTC opmode style init() that's run on teleop after auto completes, and don't run disabledPeriodic for both simultaneously.

Unresolved Questions

  • FRC SendableChooser has a "default" option set by robot code. Do we want something similar here or should it be 100% DS driven? It's kind of nice to be able to set a default (e.g. via a setDefaultAutonomousOpMode(String) function in Robot), but also might be a little fragile since it's name based. If it's done via annotation, what happens if multiple annotations are marked as default?

  • Should it be possible to have multiple top-level Robot classes (e.g. for different robot configurations), and have that be selectable at the DS as well? This is a bit ugly to support even if the Robot object is passed into the opmode constructor, because different Robot class types will only work with certain opmodes.

  • Python--will decorators be able to work similarly to Java annotations for opmode registration?