allwpilib icon indicating copy to clipboard operation
allwpilib copied to clipboard

[design-docs] Add opmodes design doc

Open PeterJohnson opened this issue 8 months ago • 17 comments

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

TODO:

  • [x] Add migration sections for FTC and FRC summarizing how this is different than what is used in 2025-6
  • [ ] Resolve open trades
  • [ ] Add more detail to C++ and Python sections, particularly Python
  • [x] Split command-based to separate design doc as its structure is substantially different
  • [ ] Improve examples (especially command-based, as this will be probably the one most commonly used)
  • [ ] Develop implementation to see how this really looks across examples and templates and work through any implementation issues that might affect the user facing API

PeterJohnson avatar Mar 15 '25 06:03 PeterJohnson

Here are some thoughts from my initial read of things.

  1. How do default commands work (or how would they be replaced)? The command-based example shows binding a joystick control command with whileTrue, which would produce a different behavior (it is not automatically rescheduled when another command ends). Bringing back whileTrueContinuous isn't quite right either since it would require all other commands on the subsystem to be uninterruptible. Seems like default commands were a fitting solution, but I'm not sure how they would be translated to this structure.

  2. My understanding is that the CommandScheduler periodic call would be triggered automatically when running a command-based mode (it would previously have been part of robotPeriodic). It seems like there are many uses cases where allowing the command scheduler to not run (under any circumstance) would be quite problematic: subsystem periodic methods are frequently used for telemetry, verifying the state of mechanisms for safety, or enforcing other overrides. For AdvantageKit as one such use case, it is critically important that subsystem periodic methods are always called for telemetry and replay. I think there is also a role for the global robotPeriodic for non-subsystem mode-agnostic telemetry (as well as other global tasks like updating LEDs), though I don't know how to make that fit with the requirement to support linear modes (or periodic modes of different frequencies). There's also a risk of users trying to run logic in robotPeriodic that does not belong there, which would need to be addressed.

I think there has been some discussion of this already, but a partial solution I could see to (1) and (2) is having a separate robot base class for command-based that requires only command-based modes and calls the command scheduler itself across all modes (and maybe provides a global robotPeriodic callback for other telemetry, possibly integrated more deeply with the command scheduler). Arguably there's still an application for robotPeriodic telemetry even without command-based, but maybe that's just impractical to support (and should have some other proposed alternative for global telemetry and periodic operations).

  1. Is the plan that the RobotBase class managing these modes is single-threaded or multi-threaded? (Seems like maybe the latter if it needs to deal with running and killing linear modes). My concern here is designing things in a way that it can be made AdvantageKit-compatible in a fairly straightforward way (i.e. ideally without completely reimplementing RobotBase). For example, it would be easy for us to simply require AdvantageKit users to not use linear modes as long as RobotBase is otherwise deterministic (single-threaded?) when running periodic and command modes. This could potentially also be addressed with a separate command-based robot base (which would be closer to the current TimedRobot), but we don't necessarily want to tie all AdvantageKit users to command-based either.

Clearly I'm approaching this (in part) from the perspective of supporting AdvantageKit in some form going forward. While I don't think we're planning any official AdvantageKit integration in 2027 (maybe 2028 or beyond), we have also been successful on the roboRIO with tweaking WPILIb to minimize the number of "intrusive" modifications required on the part of AdvantageKit. I'm operating on the assumption that we would like to continue a similar approach in 2027 to minimize the support burden for FRC teams that continue to use AdvantageKit.

jwbonner avatar Mar 15 '25 15:03 jwbonner

Is the plan that the RobotBase class managing these modes is single-threaded or multi-threaded?

Definitely still single threaded, I don't expect that to change. Linear modes would still run in the main robot thread. It would need to be an external thread monitoring, that user code won't have access to (In fact, its possible its a completely separate process and not just a thread). And if the loop doesn't exit, the whole process would be torn down, not just the thread.

ThadHouse avatar Mar 15 '25 16:03 ThadHouse

How do default commands work (or how would they be replaced)?

Default commands can still exist as they do today. They will just only run when the command scheduler runs, which only happens in command-based modes. We could change the example code to use a default command and it will still work as it does today.

is having a separate robot base class for command-based that requires only command-based modes and calls the command scheduler itself across all modes

Yeah it’s mentioned in the trades section that potentially command-based should be a robot-wide thing, and you wouldn’t be able to mix command and non-command modes in a command-based robot. That restriction might make command-based feel more cohesive, but would make it basically a completely separate project level thing you have to use, rather that being able to use it as a transitional thing in a project (which maybe isn’t realistic anyway due to subsystems etc, to your point).

PeterJohnson avatar Mar 15 '25 17:03 PeterJohnson

We could change the example code to use a default command and it will still work as it does today.

In this case, does there need to be a way to create mode-specific default commands in addition to the current implementation? It seems like the example code was trying to demonstrate how a command like joystick drive could be bound to a specific mode. Global default commands are definitely still useful and should stick around, but it looked like the intention based on the example was to have a similar feature available in a mode-specific context.

potentially command-based should be a robot-wide thing

This path makes sense to me overall. How can/should global periodic callbacks fit into this structure, given the utility of robotPeriodic for global telemetry? Command-based is built around a global periodic function already, and I guess the question is how much of that to expose to the user code (and in what way). That could take the form of a periodic function in the robot base class, an addPeriodic function in the robot base (which can't guarantee running in sync with the command scheduler), or something different (recommend that users make a command that ignores disable and is scheduled on init?).

The other question is whether something like this is in scope for the non-command variant of robot base. I think there's still an application for a periodic telemetry function regardless of mode, but that may just be impractical alongside e.g. linear modes.

jwbonner avatar Mar 15 '25 19:03 jwbonner

I split the command-based design into a separate design document. Based on the initial feedback, I've changed it to be a separate robot base class (so it's not possible to intermix command-based and non-command-based modes). I merged the CommandModes class into this new base class (so it's now less verbose to set up). I also added support/sugar to set per-mode subsystem default commands. I haven't added sugaring for joystick buttons yet, that's an open trade.

PeterJohnson avatar Mar 18 '25 03:03 PeterJohnson

I don't see a way to set the default mode for a specific driverstation mode? Would that be something that could be added ie RobotBase.setDefaultTeleoperatedMode etc

spacey-sooty avatar Mar 18 '25 04:03 spacey-sooty

At this point, I'm not sure we need a default. The way this is planned to be implemented at the DS level, its part of the direct control packets, so theres never a case one wouldn't be set. And the DS would save its last value.

ThadHouse avatar Mar 18 '25 04:03 ThadHouse

It would be nice to be able to enforce the default irrespective of what the driverstation had last selected.

spacey-sooty avatar Mar 18 '25 04:03 spacey-sooty

I would love to see a top level design that allows for a smooth progression from linear/periodic opmodes into command based opmodes without requiring those concepts to exist in separate projects. Right now the FRC students I've worked with are command based all the time because we force them to be from the beginning, but FTC students have a progression from "all of our code is in one file" to a codebase with 4+ autos and at least one teleop that are sharing code but without the benefits of a command based architecture. I would love to be able to walk students through upgrading a codebase in place from linear/periodic opmodes to having everything be command based instead of having to transition code across over to a new project or break existing opmodes.

I think the requirement of a Robot singleton is a great starting point since hardware configuration code is already conceptually different from opmode code and this immediately gets teams over the hurdle of sharing hardware configuration between teleop and auto. From there I would love to be able to introduce InstantCommand as a way to share robot functionality between teleop and auto, especially for markers on paths¹. At this stage, students could construct and invoke commands in their teleop code using if/else button bindings inside of linear/periodic opmodes rather than interacting with the scheduler, but they could be writing Command classes that could continue to work as their code increases in complexity. Concepts like Subsystem requirements and the full Command lifecycle could come up incrementally while working on more complex autos, and students could make their own decision about when they're ready to hand teleop over to the command scheduler.

I understand that FRC teams are used to having CommandScheduler run at all times after robot initialization, but in simpler codebases I would love to have an abstract CommandOpMode that lives alongside linear/periodic opmodes and allows for bindings to be set up which live only during a specific robot mode and hides the complexity of the scheduler.

@Teleoperated
public class Teleop extends CommandOpMode {
  public Teleop(Robot robot) {
    var driverController = new CommandXboxController(1);

    // Control the drive with split-stick arcade controls
    robot.drive.setDefaultCommand(
        robot.drive.arcadeDriveCommand(
            () -> -driverController.getLeftY(), () -> -driverController.getRightX()));

    // Deploy the intake with the X button
    driverController.x()).onTrue(robot.intake.intakeCommand());
    // Retract the intake with the Y button
    driverController.y()).onTrue(robot.intake.retractCommand());
  }
}

This would simplify some of the proposed binding code, with the drawback that it adds additional complexity to managing the state of the robot while it isn't enabled. It seems to me that where teams need bindings that persist between teleop/auto/disabled modes that justifies shifting that complexity up into the Robot definition and being more verbose if necessary. If there's a need for a separate CommandRobot that's incompatible with linear/periodic opmodes in order to match the existing command based capabilities of FRC software I think that's fine, but I would want it to be an incremental increase in complexity available to a codebase which already has Subsystem and Command classes defined and working with the simpler Robot interface.

¹ I'm really excited for controller hosted path planning being available to teams by default. Upgrading an FTC codebase to Road Runner is a barrier that prevents many teams in my area from having competitive autos, and I would love to have similar path building & following tools available to students without having to mess with external dependencies. However, a lot of the accessibility that the PathPlanner provides to FRC relies on NamedCommands and I would hate for teams to lock themselves off from making autos with a GUI based tool because their teleop opmode is linear/periodic.

jeversmann avatar Apr 24 '25 03:04 jeversmann

Pedagogically, linear and periodic have a different robot-wide structure and expectations than command-based. Command-based is designed to have nearly everything set up at robot construction (connecting commands to triggers and defining command subsystem dependencies), and also has a very specific definition of what a "subsystem" needs to be in order for command dependencies to logically work. There's also recommended design patterns unique to command-based (such as using factory functions to create commands in subsystems) that would likely be confusing with periodic/linear modes mixed in. I would be concerned that a team starting from a periodic design approach and trying to gradually transition to command based within the same robot project would really struggle to understand why their subsystems they used when developing their periodic design don't map to a command-based approach, or things will break in surprising ways in linear or periodic code because the scheduler isn't calling subsystem periodic behind the scenes in those cases, and in general they will end up with more difficult to understand and less maintainable code with poor design patterns by going down this road.

Implementation wise, having a CommandOpMode along the lines of what you propose to allow mixing of the two is where I started, but changed it due to Jonah's comment (https://github.com/wpilibsuite/allwpilib/pull/7863#issuecomment-2726749673). I think the disabled case is probably the most challenging to think about re: running the scheduler. We could call it when a command-based mode is selected (by running it from CommandOpMode.disabledPeriodic), but if a team wants it always to be called (even before a command is selected), there's not a good way to do that--they can't put it in robotPeriodic or disabledPeriodic, because then it would run twice. I suppose we could add a robot-wide noOpModeSelectedPeriodic type of call to handle that specific case, but that feels like it adds even more complexity to an already complex system.

One other point is that the approach where command-based constructs everything up front benefits teams because various types of construction errors can be handled by throwing exceptions, and that's okay, because it's very obvious that your code didn't start. Deferring that work to the opmode selection means we will want to avoid that (because we don't want to crash code e.g. when the auto mode is selected or at the beginning of teleop), which will make errors more difficult for teams to discover and diagnose.

PeterJohnson avatar Apr 24 '25 15:04 PeterJohnson

Having a single Robot is likely to be very problematic for FTC.

In FTC, it's common to have multiple robots with reasonably substantial differences through the course of the season. For example, might have your RobotIn3DaysRobot, your LeagueMeetRobot, and your ShinyNewRebuildRobot. In the current control system, you would have e.g. 3dayTeleop and 3dayAutonomous OpModes with Robot robot = new RobotIn3DaysRobot() in them, with RobotIn3DaysRobot providing all the Motor and Servo and Sensor and such objects; and so on for your LeagueMeetRobot and your ShinyNewRebuildRobot.

With only a single Robot allowed, would we have to have an entirely separate codebase for each robot? We can't just delete the old Robot class and associated OpModes the moment we start on the new one, as even though e.g. LeagueMeetRobot is unlikely to significantly change while working on ShinyNewRebuildRobot, the LeagueMeetRobot will almost certainly still see minor changes for the sake of the drivers, until ShinyNewRebuildRobot is complete enough for the drivers to start driving that robot instead.

And that's not even to count the various test Robots used with test OpModes for testing various things, e.g. throwing a new odometry sensor on a spare strafer chassis to test it and make sure it's working as expected.

Honestly, having Robot on top of the hierarchy instead of the OpModes just feels like the wrong order.

EDIT to add what I posted on Discord: (EDIT 2: updated since Robot instantiation can take a long time according to feedback on Discord)

This is the current proposal from the slide deck:

@Autonomous(name=“My Auto”, group=“Drive”)
public class MyAuto extends PeriodicOpMode {
    private final Robot robot;
    private final Timer timer = new Timer();
    public MyAuto(Robot robot) {
        this.robot = robot;
    }
    @Override
    public void start() {
        timer.start();
    }
    @Override
    public void periodic() {
        if (!timer.hasElapsed(2.0)) {
            robot.drivetrain.Drive(...);
        }
    }
}

This is what I'm proposing (UPDATED):

RobotsManager.java:

@RobotsManager
public class RobotsManager {
    private final Robot oldRobot;
    private final Robot newRobot;
    public RobotsManager() {
        this.oldRobot = new OldRobot();
        this.newRobot = new NewRobot();
    }
}

OldBotAuto.java:

@Autonomous(name=“My Old Robot Auto”, group=“OldBot”)
public class OldBotAuto extends PeriodicOpMode {
    private final Robot robot;
    private final Timer timer = new Timer();
    public OldBotAuto() {
        this.robot = RobotsManager.OldRobot();
    }
    @Override
    public void start() {
        timer.start();
    }
    @Override
    public void periodic() {
        if (!timer.hasElapsed(2.0)) {
            robot.drivetrain.Drive(...);
        }
    }
}

NewBotAuto.java:

@Autonomous(name=“My New Robot Auto”, group=“NewBot”)
public class NewBotAuto extends PeriodicOpMode {
    private final Robot robot;
    private final Timer timer = new Timer();
    public NewBotAuto() {
        this.robot = RobotsManager.NewRobot();
    }
    @Override
    public void start() {
        timer.start();
    }
    @Override
    public void periodic() {
        if (!timer.hasElapsed(2.0)) {
            robot.drivetrain.Drive(...);
        }
    }
}

EDIT 3:

FAQ:

Q: Why not just have separate projects/Git branches/etc for separate Robots?

A: If I'm using a test chassis to test localization code I wrote in my main project, I need my localization code. I feel like teaching kids to constantly copy-paste code between multiple projects is A Bad Thing™ and is just waiting for someone to spend a week debugging an issue that turns out to be one of the projects not getting the latest code in a particular file copy-pasted in.

Q: Well why not use Git branches then?

A: After the branch point, you would still need to either git cheery-pick or copy-paste changes in common code from and branch to another, and therefore is still likely a bad situation just waiting to happen.

Q: Would there ever be a situation where a different Robot needs to be initialized between reboots/re-deploys?

A: Intentionally? Maybe not. Unintentionally? Absolutely.

  1. User accidentally selects OpModeA that uses e.g. RobotA
  2. Robot gives error or does wrong thing or whatever
  3. User selects OpModeB that uses RobotB

This is pretty common, and wpilib should strive to handle this as gracefully as possible.

EDIT 4: Potential similar solution that might be better for all parties?

@Peter I’m okay with supporting constructing different robot classes at startup (eg either from commenting out, or based on some other input), and having opmodes filtered by what’s compatible with the constructed robot object. What I’m not okay with is having it be constructed by the opmode selection or otherwise user-selectable after robot code startup.

@QwertyChouskie In the current FTC DS app, in the Configure Robot screen, you Activate a configuration. Doing something like this in wpilib then only showing the opmodes that use that Robot might work pretty well. Feel free to add that to the GitHub thread, as it seems like a pretty solid solution. (arguably, better in some ways than the current FTC setup, where you can init an opmode just to get an estop about missing hardware devices, to realize the wrong hardware config is selected.)

qwertychouskie avatar Apr 25 '25 02:04 qwertychouskie

@RobotsManager
public class RobotsManager {
    private final Robot oldRobot;
    private final Robot newRobot;
    public RobotsManager() {
        this.oldRobot = new OldRobot();
        this.newRobot = new NewRobot();
    }
}

Wouldn't this defeat the whole purpose of different hardware setups? When RobotsManager gets initialized, both Robot classes get instantiated here, which can mess things up. (Most likely via a crash)

If a team needs different hardware setups (IE, slab bot testbed) then we can add the ability to select which Robot class to load on code initialization and have the user select in code via some constants or identifying feature. (See Peter's comment) (public static boolean TEST_BOT = true;, RobotController.getSerialNumber().equals("123ABC");, etc) However, unloading a Robot then loading in a new one in runtime is a complete non-starter due to long initialization time and memory/physical safety challenges of making sure everything in the old Robot got unloaded and cleaned up properly. This can also already be done in current FRC via the IO layer system, just that IO layers do it at a lower, more granular level than replacing the whole Robot class.

If the robot hardware architecture is different enough that code can't just be commented out or swapped via IO layers/a different Robot class, then IMO, it would be less hassle to maintain a separate branch/robot project.

I also don't see the use for having multiple, highly different robot architectures available simultaneously. If build team's working on a rebuild, it's safe to assume the old bot's not gonna get touched, and it's gonna be relegated to driver practice. If any code changes arise during those driver practice in this scenario of multiple bots, and that code change needs to be carried over to the new bot, copy paste or cherry pick isn't hard.

  1. User accidentally selects OpModeA that uses e.g. RobotA
  2. Robot gives error or does wrong thing or whatever
  3. User selects OpModeB that uses RobotB

This can be entirely avoided if the OpModes that don't use RobotB don't get shown at all, ex by never instantiating RobotA.

TheComputer314 avatar Apr 25 '25 03:04 TheComputer314

@RobotsManager
public class RobotsManager {
    private final Robot oldRobot;
    private final Robot newRobot;
    public RobotsManager() {
        this.oldRobot = new OldRobot();
        this.newRobot = new NewRobot();
    }
}

Wouldn't this defeat the whole purpose of different hardware setups? When RobotsManager gets initialized, both Robot classes get instantiated here, which can mess things up. (Most likely via a crash)

If a team needs different hardware setups (IE, slab bot testbed) then we can add the ability to select which Robot class to load on code initialization and have the user select in code via some constants or identifying feature. (See Peter's comment) (public static boolean TEST_BOT = true;, RobotController.getSerialNumber().equals("123ABC");, etc) However, unloading a Robot then loading in a new one in runtime is a complete non-starter due to long initialization time and memory/physical safety challenges of making sure everything in the old Robot got unloaded and cleaned up properly. This can also already be done in current FRC via the IO layer system, just that IO layers do it at a lower, more granular level than replacing the whole Robot class.

If the robot hardware architecture is different enough that code can't just be commented out or swapped via IO layers/a different Robot class, then IMO, it would be less hassle to maintain a separate branch/robot project.

I also don't see the use for having multiple, highly different robot architectures available simultaneously. If build team's working on a rebuild, it's safe to assume the old bot's not gonna get touched, and it's gonna be relegated to driver practice. If any code changes arise during those driver practice in this scenario of multiple bots, and that code change needs to be carried over to the new bot, copy paste or cherry pick isn't hard.

  1. User accidentally selects OpModeA that uses e.g. RobotA
  2. Robot gives error or does wrong thing or whatever
  3. User selects OpModeB that uses RobotB

This can be entirely avoided if the OpModes that don't use RobotB don't get shown at all, ex by never instantiating RobotA.

Yeah the RobotsManager idea has some kinks. Just changing the Robot class being created in main like guinawheek posted works fine.

/**
 * Do NOT add any static variables to this class, or any initialization at all. Unless you know what
 * you are doing, do not modify this file except to change the parameter class to the startRobot
 * call.
 */
public final class Main {
  private Main() {}

  /**
   * Main initialization function. Do not perform any initialization here.
   *
   * <p>If you change your main robot class, change the parameter type.
   */
  public static void main(String... args) {
    RobotBase.startRobot(Robot::new); // <-- change Robot::new with DifferentRobot::new at compile time
  }
}

This should probably be well documented though, as it's behavior that many FTC teams will be looking for.

qwertychouskie avatar Apr 25 '25 06:04 qwertychouskie

I generally find the idea of a robot class an antipattern in combination with an opmode, when an opmode should describe the linking and binding of subsystems and commands. At what point is the opmode class fairly pointless, serving only to provide a name and a categorisation? (seems like command opmodes as currently modelled don't actually exist as classes, and solely do this). Generally in ftc the model is: opmode -> creates / pulls in subsystems -> links commands to gamepads / does other human input linking with robot action. i have a hard time not seeing robot class and opmode as interchangeable here, just with a different perspective.

Froze-N-Milk avatar Apr 25 '25 07:04 Froze-N-Milk

I think the closest analogy to what current FRC/the design doc's Robot class does is current FTC's hardware map, where it's hardware specific and configures the hardware for op modes/commands to use. However, the design doc's Robot class is larger in scope than the hardware map, encompassing the subsystem code as well, since subsystem code is pretty tightly linked with physical hardware.

If hardware does change enough to break a Robot class or subsystem code, there are a number of ways that don't involve unloading and reloading hardware accessors in runtime, such as commenting out code, conditional logic in the constructors, swapping out the Robot class, etc.

TheComputer314 avatar Apr 25 '25 14:04 TheComputer314

An idea:

What if OpModes weren't created in the Robot class and were instead in the entry-point (similar to how ASP.NET works, but of course it doesn't have OpModes)? But the Robot would still contain all the hardware.

So main would look something like this:

RobotBuilder builder = new RobotBuilder();
builder.addRobot(new MyRobot());
builder.addAutonomous("Simple Auto").running.whileTrue(Autos.simpleAuto(builder.robot));
CommandOpMode opMode = builder.addTeleoperated("TeleOp");
// and all the rest
// then:
builder.build().run();

Advantages to this approach:

  • It is much more clear exactly what addAutonomous and addTeleoperated are doing. With having them be static methods in CommandOpModes, it's not very clear that they have a side effect (registering the OpMode).

Disadvantages to this approach:

  • If it's all in main then having multiple robots would suck, although you could always just make the entire thing a static method in a different class and call it from main.

Also, a possible idea for the verboseness of gamepads in specific OpModes:

opMode.bindWhileRunning(
  driverControlled.x(), onTrue(intake.intakeCommand()),
  driverControlled.y(), onTrue(intake.retractCommand())
);

Additionally, possibly a way to do command OpModes as classes by extending a CommandOpModeHelper base class and then registering using CommandOpModes.addAutonomous(MyCommandOpMode()) (or builder.addAutonomous(...).

In CommandOpModeHelper, you would pass the name of the OpMode, probably to the super constructor. There would also be an easy way to write start and periodic code in the OpMode, that is turned into a command. Also, there could be methods that help with bindings for that OpMode to reduce the verboseness, something like:

// in the constructor:
trigger(driverControlled.x()).onTrue(...);

Edit: just reread the comments and saw that this last idea has already been proposed, although mine is (slightly) different.

BeepBot99 avatar Apr 26 '25 02:04 BeepBot99

Following much discussion in Discord over the course of a few days, a new idea has emerged: Command contexts. Initial iterations of the idea can be found in the earlier edits of this comment, but the main description has been moved to PeterJohnson#18.

Here's some of the key points of v1.3:

# Motivation

Command opmodes will significantly increase the number of command bindings that need to be restricted to particular conditions.  In addition, it should be a conscious decision to make bindings that are active regardless of opmode- Unknowingly doing so can be a major safety risk.

# Design

Core concepts:
- A `BooleanSupplier` represents a condition.
- A `Context` describes when a set of `Trigger`s should be active.
- A `Trigger` represents a condition to use for command bindings.
- A `Trigger`’s *context condition* is the condition of the `Context` associated with the `Trigger`.
- A `Trigger`’s *base condition* is the original condition used for the `Trigger`, without the context condition applied.

Relevant library classes:
- `BooleanSupplier` (described above) is provided by the Java Standard Environment.
- `Context` is described above.  Although it contains a boolean condition, it should only be used for making `Trigger`s with that context condition, so it does _not_ implement `BooleanSupplier`.
- `Trigger` is described above.  It is made by a `Context` from a `BooleanSupplier` and implements `BooleanSupplier`.
- `Conditions` provides static utility methods for performing logical operations on `BooleanSupplier`s.
- `CommandOpModes` is part of [command opmodes](opmodes-commandbased.md), and provides some common `Context` instances.
- `CommandOpMode` is part of [command opmodes](opmodes-commandbased.md) and inherits from `Context`.
- `CommandGenericHID` is the base class of all command controller classes (described below).
- Command controller classes (such as `CommandXboxController`) are used to create `Trigger`s associated with a controller's inputs.

Robot code outline:
- Controllers are created with particular `Context`s to restrict the controller’s `Trigger` bindings to particular opmodes, optionally imposing additional conditions on the `Context`s.
- Subsystems provide `BooleanSupplier`s to indicate state. (e.g., arm at angle)
- Static methods in `Conditions` perform any necessary logic operations on the `BooleanSupplier`s.
- `Context`s are used to create `Trigger`s from `BooleanSupplier`s to make bindings for any automatic actions.

For details, read the PR diff.

KangarooKoala avatar Apr 30 '25 04:04 KangarooKoala