diff --git a/source/docs/software/commandbased/commands-v2/binding-commands-to-triggers.rst b/source/docs/software/commandbased/commands-v2/binding-commands-to-triggers.rst index 34a61dd840..b30b1f959b 100644 --- a/source/docs/software/commandbased/commands-v2/binding-commands-to-triggers.rst +++ b/source/docs/software/commandbased/commands-v2/binding-commands-to-triggers.rst @@ -4,7 +4,7 @@ Apart from autonomous commands, which are scheduled at the start of the autonomo As mentioned earlier, command-based is a :term:`declarative programming` paradigm. Accordingly, binding buttons to commands is done declaratively; the association of a button and a command is "declared" once, during robot initialization. The library then does all the hard work of checking the button state and scheduling (or canceling) the command as needed, behind-the-scenes. Users only need to worry about designing their desired UI setup - not about implementing it! -Command binding is done through the ``Trigger`` class ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/button/Trigger.html), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_trigger.html)). +Command binding is done through the ``Trigger`` class ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/button/Trigger.html), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_trigger.html), [Python](https://robotpy.readthedocs.io/projects/commands-v2/en/stable/commands2.button/Trigger.html)). ## Getting a Trigger Instance @@ -14,7 +14,7 @@ To bind commands to conditions, we need a ``Trigger`` object. There are three wa .. todo:: Update for CommandGamepad -The command-based HID classes contain factory methods returning a ``Trigger`` for a given button. ``CommandGenericHID`` has an index-based ``button(int)`` factory ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/button/CommandGenericHID.html#button(int)), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_generic_h_i_d.html#a5d8367128251961432905706b8060181)), and its subclasses ``CommandGamepad`` ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/button/CommandGamepad.html), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_gamepad.html)), and ``CommandJoystick`` ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/button/CommandJoystick.html), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_joystick.html)) have named factory methods for each button. +The command-based HID classes contain factory methods returning a ``Trigger`` for a given button. ``CommandGenericHID`` has an index-based ``button(int)`` factory ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/button/CommandGenericHID.html#button(int)), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_generic_h_i_d.html#a5d8367128251961432905706b8060181), [Python](https://robotpy.readthedocs.io/projects/commands-v2/en/stable/commands2.button/CommandGenericHID.html)), and its subclasses ``CommandGamepad`` ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/button/CommandGamepad.html), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_gamepad.html), [Python](https://robotpy.readthedocs.io/projects/commands-v2/en/stable/commands2.button/CommandGamepad.html)), and ``CommandJoystick`` ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/button/CommandJoystick.html), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_joystick.html), [Python](https://robotpy.readthedocs.io/projects/commands-v2/en/stable/commands2.button/CommandJoystick.html)) have named factory methods for each button. .. tab-set-code:: @@ -28,9 +28,14 @@ The command-based HID classes contain factory methods returning a ``Trigger`` fo wpi::cmd::Trigger xButton = exampleCommandController.X() // Creates a new Trigger object for the `X` button on exampleCommandController ``` + ```python + example_command_controller = commands2.button.CommandXboxController(1) # Creates a CommandXboxController on port 1. + x_button = example_command_controller.x() # Creates a new Trigger object for the `X` button on example_command_controller + ``` + ### JoystickButton -Alternatively, the :ref:`regular HID classes ` can be used and passed to create an instance of ``JoystickButton`` [Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/button/JoystickButton.html), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_joystick_button.html)), a constructor-only subclass of ``Trigger``: +Alternatively, the :ref:`regular HID classes ` can be used and passed to create an instance of ``JoystickButton`` ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/button/JoystickButton.html), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_joystick_button.html), [Python](https://robotpy.readthedocs.io/projects/commands-v2/en/stable/commands2.button/JoystickButton.html)), a constructor-only subclass of ``Trigger``: .. tab-set-code:: @@ -44,6 +49,11 @@ Alternatively, the :ref:`regular HID classes ` using the `debounce` method: @@ -186,3 +231,10 @@ To avoid rapid repeated activation, triggers (especially those originating from exampleButton.Debounce(100_ms, Debouncer::DebounceType::Both).OnTrue(ExampleCommand().ToPtr()); ``` + ```python + # debounces example_button with a 0.1s debounce time, rising edges only + example_button.debounce(0.1).onTrue(ExampleCommand()) + # debounces example_button with a 0.1s debounce time, both rising and falling edges + example_button.debounce(0.1, wpimath.filter.Debouncer.DebounceType.kBoth).onTrue(ExampleCommand()) + ``` + diff --git a/source/docs/software/commandbased/commands-v2/command-scheduler.rst b/source/docs/software/commandbased/commands-v2/command-scheduler.rst index 428e478456..85312c7eba 100644 --- a/source/docs/software/commandbased/commands-v2/command-scheduler.rst +++ b/source/docs/software/commandbased/commands-v2/command-scheduler.rst @@ -1,6 +1,6 @@ # The Command Scheduler -The ``CommandScheduler`` ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/CommandScheduler.html), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_scheduler.html)) is the class responsible for actually running commands. Each iteration (ordinarily once per 20ms), the scheduler polls all registered buttons, schedules commands for execution accordingly, runs the command bodies of all scheduled commands, and ends those commands that have finished or are interrupted. +The ``CommandScheduler`` ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/CommandScheduler.html), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_scheduler.html), [Python](https://robotpy.readthedocs.io/projects/commands-v2/en/stable/commands2/CommandScheduler.html)) is the class responsible for actually running commands. Each iteration (ordinarily once per 20ms), the scheduler polls all registered buttons, schedules commands for execution accordingly, runs the command bodies of all scheduled commands, and ends those commands that have finished or are interrupted. The ``CommandScheduler`` also runs the ``periodic()`` method of each registered ``Subsystem``. @@ -14,7 +14,7 @@ However, there is one exception: users *must* call ``CommandScheduler.getInstanc ## The ``schedule()`` Method -To schedule a command, users call the ``schedule()`` method ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/CommandScheduler.html#schedule(org.wpilib.command2.Command...)), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_scheduler.html#a772858c58eac2875eac7898002f78003)). This method takes a command, and attempts to add it to list of currently-running commands, pending whether it is already running or whether its requirements are available. If it is added, its ``initialize()`` method is called. +To schedule a command, users call the ``schedule()`` method ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/CommandScheduler.html#schedule(org.wpilib.command2.Command...)), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_scheduler.html#a772858c58eac2875eac7898002f78003), [Python](https://robotpy.readthedocs.io/projects/commands-v2/en/stable/commands2/CommandScheduler.html#commands2.CommandScheduler.schedule)). This method takes a command, and attempts to add it to list of currently-running commands, pending whether it is already running or whether its requirements are available. If it is added, its ``initialize()`` method is called. This method walks through the following steps: @@ -53,7 +53,7 @@ This method walks through the following steps: .. note:: The ``initialize()`` method of each ``Command`` is called when the command is scheduled, which is not necessarily when the scheduler runs (unless that command is bound to a button). -What does a single iteration of the scheduler's ``run()`` method ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/CommandScheduler.html#run()), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_scheduler.html#a772858c58eac2875eac7898002f78003)) actually do? The following section walks through the logic of a scheduler iteration. For the full implementation, see the source code ([Java](https://github.com/wpilibsuite/allwpilib/blob/v2024.3.2/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/CommandScheduler.java#L252-L331), [C++](https://github.com/wpilibsuite/allwpilib/blob/v2024.3.2/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandScheduler.cpp#L173-L253)). +What does a single iteration of the scheduler's ``run()`` method ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/CommandScheduler.html#run()), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_scheduler.html#a772858c58eac2875eac7898002f78003), :external:py:meth:`Python `) actually do? The following section walks through the logic of a scheduler iteration. For the full implementation, see the source code ([Java](https://github.com/wpilibsuite/allwpilib/blob/v2024.3.2/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/CommandScheduler.java#L252-L331), [C++](https://github.com/wpilibsuite/allwpilib/blob/v2024.3.2/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandScheduler.cpp#L173-L253)). ### Step 1: Run Subsystem Periodic Methods @@ -161,15 +161,15 @@ The scheduler may be re-enabled by calling ``CommandScheduler.getInstance().enab Occasionally, it is desirable to have the scheduler execute a custom action whenever a certain command event (initialization, execution, or ending) occurs. This can be done with the following methods: -- ``onCommandInitialize`` ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/CommandScheduler.html#onCommandInitialize(java.util.function.Consumer)), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_scheduler.html#a772858c58eac2875eac7898002f78003)) runs a specified action whenever a command is initialized. +- ``onCommandInitialize`` ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/CommandScheduler.html#onCommandInitialize(java.util.function.Consumer)), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_scheduler.html#a772858c58eac2875eac7898002f78003), [Python](https://robotpy.readthedocs.io/projects/commands-v2/en/stable/commands2/CommandScheduler.html#commands2.CommandScheduler.onCommandInitialize)) runs a specified action whenever a command is initialized. -- ``onCommandExecute`` ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/CommandScheduler.html#onCommandExecute(java.util.function.Consumer)), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_scheduler.html#a772858c58eac2875eac7898002f78003)) runs a specified action whenever a command is executed. +- ``onCommandExecute`` ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/CommandScheduler.html#onCommandExecute(java.util.function.Consumer)), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_scheduler.html#a772858c58eac2875eac7898002f78003), [Python](https://robotpy.readthedocs.io/projects/commands-v2/en/stable/commands2/CommandScheduler.html#commands2.CommandScheduler.onCommandExecute)) runs a specified action whenever a command is executed. -- ``onCommandFinish`` ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/CommandScheduler.html#onCommandFinish(java.util.function.Consumer)), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_scheduler.html#a772858c58eac2875eac7898002f78003)) runs a specified action whenever a command finishes normally (i.e. the ``isFinished()`` method returned true). +- ``onCommandFinish`` ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/CommandScheduler.html#onCommandFinish(java.util.function.Consumer)), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_scheduler.html#a772858c58eac2875eac7898002f78003), [Python](https://robotpy.readthedocs.io/projects/commands-v2/en/stable/commands2/CommandScheduler.html#commands2.CommandScheduler.onCommandFinish)) runs a specified action whenever a command finishes normally (i.e. the ``isFinished()`` method returned true). -- ``onCommandInterrupt`` ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/CommandScheduler.html#onCommandInterrupt(java.util.function.Consumer)), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_scheduler.html#affaa5c53db71e448a49eeb5067cd6b6f)) runs a specified action whenever a command is interrupted (i.e. by being explicitly canceled or by another command that shares one of its requirements). +- ``onCommandInterrupt`` ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/command2/CommandScheduler.html#onCommandInterrupt(java.util.function.Consumer)), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1cmd_1_1_command_scheduler.html#affaa5c53db71e448a49eeb5067cd6b6f), [Python](https://robotpy.readthedocs.io/projects/commands-v2/en/stable/commands2/CommandScheduler.html#commands2.CommandScheduler.onCommandInterrupt)) runs a specified action whenever a command is interrupted (i.e. by being explicitly canceled or by another command that shares one of its requirements). -A typical use-case for these methods is adding markers in an event log whenever a command scheduling event takes place, as demonstrated in the following code from the HatchbotInlined example project ([Java](https://github.com/wpilibsuite/allwpilib/tree/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/examples/hatchbotinlined), [C++](https://github.com/wpilibsuite/allwpilib/tree/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/examples/HatchbotInlined)): +A typical use-case for these methods is adding markers in an event log whenever a command scheduling event takes place, as demonstrated in the following code from the HatchbotInlined example project ([Java](https://github.com/wpilibsuite/allwpilib/tree/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/examples/hatchbotinlined), [C++](https://github.com/wpilibsuite/allwpilib/tree/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/examples/HatchbotInlined), [Python](https://github.com/robotpy/mostrobotpy/tree/main/examples/robot/HatchbotInlined)): .. tab-set:: @@ -188,3 +188,11 @@ A typical use-case for these methods is adding markers in an event log whenever :language: c++ :lines: 23-47 :lineno-match: + + .. tab-item:: Python + :sync: tabcode-python + + .. remoteliteralinclude:: https://raw.githubusercontent.com/robotpy/mostrobotpy/2027.0.0a6/examples/robot/HatchbotInlined/robotcontainer.py + :language: python + :lines: 60-70 + :lineno-match: diff --git a/source/docs/software/commandbased/commands-v2/index.rst b/source/docs/software/commandbased/commands-v2/index.rst index 75d45741cd..12d72ea0b5 100644 --- a/source/docs/software/commandbased/commands-v2/index.rst +++ b/source/docs/software/commandbased/commands-v2/index.rst @@ -34,7 +34,7 @@ Commands v2 is recommended for: ## Passing Functions As Parameters -In order to provide a concise inline syntax, the command-based library often accepts functions as parameters of constructors, factories, and decorators. Fortunately, both Java and C++ offer users the ability to :ref:`pass functions as objects `: +In order to provide a concise inline syntax, the command-based library often accepts functions as parameters of constructors, factories, and decorators. Fortunately, Java, C++, and Python all offer users the ability to :ref:`pass functions as objects `: ### Method References (Java) @@ -49,3 +49,7 @@ While method references work well for passing a function that has already been w .. warning:: Due to complications in C++ semantics, capturing ``this`` in a C++ lambda can cause a null pointer exception if done from a component command of a command composition. Whenever possible, C++ users should capture relevant command members explicitly and by value. For more details, see [here](https://github.com/wpilibsuite/allwpilib/issues/3109). C++ lacks a close equivalent to Java method references - pointers to member functions are generally not directly usable as parameters due to the presence of the implicit ``this`` parameter. However, C++ does offer lambda expressions - in addition, the lambda expressions offered by C++ are in many ways more powerful than those in Java. For specifics on how to write C++ lambda expressions, see :ref:`docs/software/basic-programming/functions-as-data:Lambda Expressions in C++`. + +### Functions and Lambdas (Python) + +Python natively treats functions and methods as first-class objects. In Python, methods can be passed directly by reference (such as ``self.intake.activate``) or defined inline using ``lambda`` expressions (such as ``lambda: self.drive(0.5, 0.0)``). For more details, see :ref:`docs/software/basic-programming/functions-as-data:Treating Functions as Data`. diff --git a/source/docs/software/commandbased/commands-v2/organizing-command-based.rst b/source/docs/software/commandbased/commands-v2/organizing-command-based.rst index 5dc79c5a9e..c75eb6a3bb 100644 --- a/source/docs/software/commandbased/commands-v2/organizing-command-based.rst +++ b/source/docs/software/commandbased/commands-v2/organizing-command-based.rst @@ -37,6 +37,10 @@ The easiest and most expressive way to do this is with a ``StartEndCommand``: wpi::cmd::CommandPtr runIntake = wpi::cmd::cmd::StartEnd([&intake] { intake.Set(1.0); }, [&intake] { intake.Set(0.0); }, {&intake}); ``` + ```python + run_intake = commands2.cmd.startEnd(lambda: intake.set(1.0), lambda: intake.set(0.0), intake) + ``` + This is sufficient for commands that are only used once. However, for a command like this that might get used in many different autonomous routines and button bindings, inline commands everywhere means a lot of repetitive code: .. tab-set-code:: @@ -64,6 +68,17 @@ This is sufficient for commands that are only used once. However, for a command ); ``` + ```python + # robotcontainer.py + intake_button.whileTrue(commands2.cmd.startEnd(lambda: intake.set(1.0), lambda: intake.set(0.0), intake)) + intake_and_shoot = commands2.cmd.startEnd(lambda: intake.set(1.0), lambda: intake.set(0.0), intake).alongWith(RunShooter(shooter)) + autonomous_command = commands2.cmd.sequence( + commands2.cmd.startEnd(lambda: intake.set(1.0), lambda: intake.set(0.0), intake).withTimeout(5.0), + commands2.cmd.waitSeconds(3.0), + commands2.cmd.startEnd(lambda: intake.set(1.0), lambda: intake.set(0.0), intake).withTimeout(5.0), + ) + ``` + Creating one ``StartEndCommand`` instance and putting it in a variable won't work here, since once an instance of a command is added to a command group it is effectively "owned" by that command group and cannot be used in any other context. #### Instance Command Factory Methods @@ -94,6 +109,14 @@ For example, a command like the intake-running command is conceptually related t } ``` + ```python + class Intake(commands2.Subsystem): + # ... + def runIntakeCommand(self) -> commands2.Command: + # implicitly requires `self` + return self.startEnd(lambda: self.set(1.0), lambda: self.set(0.0)) + ``` + Notice how since we are in the ``Intake`` class, we no longer refer to ``intake``; instead, we use the ``this`` keyword to refer to the current instance. Since we are inside the ``Intake`` class, technically we can access ``private`` variables and methods directly from within the ``runIntakeCommand`` method, thus not needing intermediary methods. (For example, the ``runIntakeCommand`` method can directly interface with the motor controller objects instead of calling ``set()``.) On the other hand, these intermediary methods can reduce code duplication and increase encapsulation. Like many other choices outlined in this document, this tradeoff is a matter of personal preference on a case-by-case basis. @@ -122,6 +145,16 @@ Using this new factory method in command groups and button bindings is highly ex ); ``` + ```python + intake_button.whileTrue(intake.runIntakeCommand()) + intake_and_shoot = intake.runIntakeCommand().alongWith(RunShooter(shooter)) + autonomous_command = commands2.cmd.sequence( + intake.runIntakeCommand().withTimeout(5.0), + commands2.cmd.waitSeconds(3.0), + intake.runIntakeCommand().withTimeout(5.0), + ) + ``` + Adding a parameter to the ``runIntakeCommand`` method to provide the exact percentage to run the intake is easy and allows for even more flexibility. .. tab-set-code:: @@ -139,6 +172,11 @@ Adding a parameter to the ``runIntakeCommand`` method to provide the exact perce } ``` + ```python + def runIntakeCommand(self, percent: float) -> commands2.Command: + return commands2.StartEndCommand(lambda: self.set(percent), lambda: self.set(0.0), self) + ``` + For instance, this code creates a command group that runs the intake forwards for two seconds, waits for two seconds, and then runs the intake backwards for five seconds. .. tab-set-code:: @@ -155,6 +193,15 @@ For instance, this code creates a command group that runs the intake forwards fo .AndThen(intake.RunIntakeCommand(-1.0).WithTimeout(5.0_s)); ``` + ```python + intake_run_sequence = ( + intake.runIntakeCommand(1.0) + .withTimeout(2.0) + .andThen(commands2.cmd.waitSeconds(2.0)) + .andThen(intake.runIntakeCommand(-1.0).withTimeout(5.0)) + ) + ``` + This approach is recommended for commands that are conceptually related to only a single subsystem, and is very concise. However, it doesn't fare well with commands related to more than one subsystem: passing in other subsystem objects is unintuitive and can cause race conditions and circular dependencies, and thus should be avoided. Therefore, this approach is best suited for single-subsystem commands, and should be used only for those cases. #### Static Command Factories @@ -186,6 +233,22 @@ Instance factory methods work great for single-subsystem commands. However, com // TODO ``` + ```python + class AutoRoutines: + @staticmethod + def driveAndIntake(drivetrain, intake) -> commands2.Command: + return commands2.cmd.sequence( + commands2.cmd.parallel( + drivetrain.driveCommand(0.5, 0.5), + intake.runIntakeCommand(1.0), + ).withTimeout(5.0), + commands2.cmd.parallel( + drivetrain.stopCommand(), + intake.stopCommand(), + ), + ) + ``` + .. todo:: implement C++ version of the above code #### Non-Static Command Factories @@ -315,6 +378,20 @@ Returning to our simple intake command from earlier, we could do this by creatin // TODO ``` + ```python + class RunIntakeCommand(commands2.Command): + def __init__(self, intake: Intake) -> None: + super().__init__() + self.intake = intake + self.addRequirements(intake) + + def initialize(self) -> None: + self.intake.set(1.0) + + def end(self, interrupted: bool) -> None: + self.intake.set(0.0) + ``` + .. todo:: implement C++ version of the above code This, however, is just as cumbersome as the original repetitive code, if not more verbose. The only two lines that really matter in this entire file are the two calls to ``intake.set()``, yet there are over 20 lines of boilerplate code! Not to mention, doing this for a lot of robot actions quickly clutters up a robot project with dozens of small files. Nevertheless, this might feel more "natural," particularly for programmers who prefer to stick closely to an object-oriented model. @@ -344,6 +421,16 @@ If we wish to write composite commands as their own classes, we may write a cons // TODO ``` + ```python + class IntakeThenOuttake(commands2.SequentialCommandGroup): + def __init__(self, intake: Intake) -> None: + super().__init__( + intake.runIntakeCommand(1.0).withTimeout(2.0), + commands2.WaitCommand(2.0), + intake.runIntakeCommand(-1.0).withTimeout(5.0), + ) + ``` + .. todo:: implement C++ version of the above code This is relatively short and minimizes boilerplate. It is also comfortable to use in a purely object-oriented paradigm and may be more acceptable to novice programmers. However, it has some downsides. For one, it is not immediately clear exactly what type of command group this is from the constructor definition: it is better to define this in a more inline and expressive way, particularly when nested command groups start showing up. Additionally, it requires a new file for every single command group, even when the groups are conceptually related. diff --git a/source/docs/software/commandbased/commands-v2/pid-subsystems-commands.rst b/source/docs/software/commandbased/commands-v2/pid-subsystems-commands.rst index 6963fc42e5..15f9493a61 100644 --- a/source/docs/software/commandbased/commands-v2/pid-subsystems-commands.rst +++ b/source/docs/software/commandbased/commands-v2/pid-subsystems-commands.rst @@ -5,7 +5,7 @@ PID Control in Command-based .. note:: For a description of the WPILib PID control features used by these command-based wrappers, see :ref:`docs/software/advanced-controls/controllers/pidcontroller:PID Control in WPILib`. -One of the most common control algorithms used in FRC\ |reg| and FTC\ |reg| is the :term:`PID` controller. WPILib offers its own :ref:`PIDController ` class to help teams implement this functionality on their robots. The following example is from the RapidReactCommandBot example project ([Java](https://github.com/wpilibsuite/allwpilib/tree/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/examples/rapidreactcommandbot), [C++](https://github.com/wpilibsuite/allwpilib/tree/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/examples/RapidReactCommandBot)) and shows how PIDControllers can be used within the command-based framework: +One of the most common control algorithms used in FRC\ |reg| and FTC\ |reg| is the :term:`PID` controller. WPILib offers its own :ref:`PIDController ` class ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/edu/wpi/first/math/controller/PIDController.html), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classfrc_1_1_p_i_d_controller.html), [Python](https://robotpy.readthedocs.io/projects/robotpy/en/stable/wpimath.controller/PIDController.html)) to help teams implement this functionality on their robots. The following example is from the RapidReactCommandBot example project ([Java](https://github.com/wpilibsuite/allwpilib/tree/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/examples/rapidreactcommandbot), [C++](https://github.com/wpilibsuite/allwpilib/tree/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/examples/RapidReactCommandBot), [Python](https://github.com/robotpy/mostrobotpy/tree/main/examples/robot/RapidReactCommandBot)) and shows how PIDControllers can be used within the command-based framework: .. tab-set:: @@ -17,7 +17,7 @@ One of the most common control algorithms used in FRC\ |reg| and FTC\ |reg| is t :lines: 5- :lineno-match: - .. tab-item:: C++ + .. tab-item:: C++ (Header) :sync: C++ (Header) .. remoteliteralinclude:: https://raw.githubusercontent.com/wpilibsuite/allwpilib/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/examples/RapidReactCommandBot/include/subsystems/Shooter.hpp @@ -33,4 +33,12 @@ One of the most common control algorithms used in FRC\ |reg| and FTC\ |reg| is t :lines: 5- :lineno-match: + .. tab-item:: Python + :sync: Python + + .. remoteliteralinclude:: https://raw.githubusercontent.com/robotpy/mostrobotpy/2027.0.0a6/examples/robot/RapidReactCommandBot/subsystems/shooter.py + :language: python + :lines: 5- + :lineno-match: + A ``PIDController`` is declared inside the ``Shooter`` subsystem. It is used by ``ShootCommand`` alongside a feedforward to spin the shooter flywheel to the specified velocity. Once the ``PIDController`` reaches the specified velocity, the ``ShootCommand`` runs the feeder. diff --git a/source/docs/software/commandbased/commands-v2/profile-subsystems-commands.rst b/source/docs/software/commandbased/commands-v2/profile-subsystems-commands.rst index b9841bb1a7..43bd00c890 100644 --- a/source/docs/software/commandbased/commands-v2/profile-subsystems-commands.rst +++ b/source/docs/software/commandbased/commands-v2/profile-subsystems-commands.rst @@ -5,11 +5,11 @@ Motion Profiling in Command-based .. note:: The ``TrapezoidProfile`` class, used on its own, is most useful when composed with external controllers, such as a "smart" motor controller with a built-in PID functionality. For combining trapezoidal motion profiling with WPILib's ``PIDController``, see :doc:`profilepid-subsystems-commands`. -When controlling a mechanism, is often desirable to move it smoothly between two positions, rather than to abruptly change its setpoint. This is called "motion-profiling," and is supported in WPILib through the ``TrapezoidProfile`` class ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/math/trajectory/TrapezoidProfile.html), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1math_1_1_trapezoid_profile.html)). +When controlling a mechanism, is often desirable to move it smoothly between two positions, rather than to abruptly change its setpoint. This is called "motion-profiling," and is supported in WPILib through the ``TrapezoidProfile`` class ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/org/wpilib/math/trajectory/TrapezoidProfile.html), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classwpi_1_1math_1_1_trapezoid_profile.html), [Python](https://robotpy.readthedocs.io/projects/robotpy/en/stable/wpimath.trajectory/TrapezoidProfile.html)). .. note:: In C++, the ``TrapezoidProfile`` class is templated on the unit type used for distance measurements, which may be angular or linear. The passed-in values *must* have units consistent with the distance units, or a compile-time error will be thrown. For more information on C++ units, see :ref:`docs/software/basic-programming/cpp-units:The C++ Units Library`. -The following examples are taken from the DriveDistanceOffboard example project ([Java](https://github.com/wpilibsuite/allwpilib/tree/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/examples/drivedistanceoffboard), [C++](https://github.com/wpilibsuite/allwpilib/tree/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/examples/DriveDistanceOffboard)): +The following examples are taken from the DriveDistanceOffboard example project ([Java](https://github.com/wpilibsuite/allwpilib/tree/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/examples/drivedistanceoffboard), [C++](https://github.com/wpilibsuite/allwpilib/tree/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/examples/DriveDistanceOffboard), [Python](https://github.com/robotpy/mostrobotpy/tree/main/examples/robot/DriveDistanceOffboard)): .. tab-set:: @@ -37,6 +37,14 @@ The following examples are taken from the DriveDistanceOffboard example project :lines: 5- :lineno-match: + .. tab-item:: Python + :sync: Python + + .. remoteliteralinclude:: https://raw.githubusercontent.com/robotpy/mostrobotpy/2027.0.0a6/examples/robot/DriveDistanceOffboard/subsystems/drivesubsystem.py + :language: python + :lines: 5- + :lineno-match: + There are two commands in this example. They function very similarly, with the main difference being that one resets encoders, and the other doesn't, which allows encoder data to be preserved. The subsystem contains a ``TrapezoidProfile`` with a ``Timer``. The timer is used along with a ``kDt`` constant of 0.02 seconds to calculate the current and next states from the ``TrapezoidProfile``. The current state is fed to the "smart" motor controller for PID control, while the current and next state are used to calculate feedforward outputs. Both commands end when ``isFinished(0)`` returns true, which means that the profile has reached the goal state. diff --git a/source/docs/software/commandbased/commands-v2/profilepid-subsystems-commands.rst b/source/docs/software/commandbased/commands-v2/profilepid-subsystems-commands.rst index eee9b718cb..53e12de79c 100644 --- a/source/docs/software/commandbased/commands-v2/profilepid-subsystems-commands.rst +++ b/source/docs/software/commandbased/commands-v2/profilepid-subsystems-commands.rst @@ -4,7 +4,7 @@ .. note:: For a description of the WPILib PID control features used by these command-based wrappers, see :ref:`docs/software/advanced-controls/controllers/pidcontroller:PID Control in WPILib`. -A common FRC\ |reg| controls solution is to pair a trapezoidal motion profile for setpoint generation with a PID controller for setpoint tracking. To facilitate this, WPILib includes its own :ref:`ProfiledPIDController ` class. The following example is from the RapidReactCommandBot example project (`Java `__, `C++ `__) and shows how ``ProfiledPIDController`` can be used within the command-based framework to turn a drivetrain to a specified angle: +A common FRC\ |reg| controls solution is to pair a trapezoidal motion profile for setpoint generation with a PID controller for setpoint tracking. To facilitate this, WPILib includes its own :ref:`ProfiledPIDController ` class ([Java](https://github.wpilib.org/allwpilib/docs/beta/java/edu/wpi/first/math/controller/ProfiledPIDController.html), [C++](https://github.wpilib.org/allwpilib/docs/beta/cpp/classfrc_1_1_profiled_p_i_d_controller.html), [Python](https://robotpy.readthedocs.io/projects/robotpy/en/stable/wpimath.controller/ProfiledPIDController.html)). The following example is from the RapidReactCommandBot example project ([Java](https://github.com/wpilibsuite/allwpilib/tree/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/examples/rapidreactcommandbot), [C++](https://github.com/wpilibsuite/allwpilib/tree/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/examples/RapidReactCommandBot), [Python](https://github.com/robotpy/mostrobotpy/tree/main/examples/robot/RapidReactCommandBot)) and shows how ``ProfiledPIDController`` can be used within the command-based framework to turn a drivetrain to a specified angle: .. tab-set:: @@ -32,4 +32,12 @@ A common FRC\ |reg| controls solution is to pair a trapezoidal motion profile fo :lines: 5- :lineno-match: + .. tab-item:: Python + :sync: Python + + .. remoteliteralinclude:: https://raw.githubusercontent.com/robotpy/mostrobotpy/2027.0.0a6/examples/robot/RapidReactCommandBot/subsystems/drive.py + :language: python + :lines: 5- + :lineno-match: + ``turnToAngleCommand`` uses a ProfiledPIDController to smoothly turn the drivetrain. The ``startRun`` command factory is used to reset the ``ProfiledPIDController`` when the command is scheduled to avoid unwanted behavior, and to calculate PID and feedforward outputs to pass into the ``arcadeDrive`` method in order to drive the robot. The command is decorated using the ``until`` decorator to end the command when the ProfiledPIDController is finished with the profile. To ensure the drivetrain stops when the command ends, the ``finallyDo`` decorator is used to stop the drivetrain by setting the speed to zero. diff --git a/source/docs/software/commandbased/commands-v2/structuring-command-based-project.rst b/source/docs/software/commandbased/commands-v2/structuring-command-based-project.rst index c1a8c7a565..a1fb07bf48 100644 --- a/source/docs/software/commandbased/commands-v2/structuring-command-based-project.rst +++ b/source/docs/software/commandbased/commands-v2/structuring-command-based-project.rst @@ -2,7 +2,7 @@ While users are free to use the command-based libraries however they like (and advanced users are encouraged to do so), new users may want some guidance on how to structure a basic command-based robot project. -A standard template for a command-based robot project is included in the WPILib examples repository ([Java](https://github.com/wpilibsuite/allwpilib/tree/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/templates/commandv2), [C++](https://github.com/wpilibsuite/allwpilib/tree/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/templates/commandv2)). This section will walk users through the structure of this template. +A standard template for a command-based robot project is included in the WPILib examples repository ([Java](https://github.com/wpilibsuite/allwpilib/tree/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/templates/commandv2), [C++](https://github.com/wpilibsuite/allwpilib/tree/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/templates/commandv2), [Python](https://github.com/robotpy/mostrobotpy/tree/main/examples/robot/HatchbotInlined)). This section will walk users through the structure of this template. The root package/directory generally will contain four classes: @@ -12,7 +12,7 @@ The root directory will also contain two sub-packages/sub-directories: ``Subsyst ## Robot -As ``Robot`` ([Java](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/templates/commandv2/Robot.java), [C++ (Header)](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/templates/commandv2/include/Robot.hpp), [C++ (Source)](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/templates/commandv2/cpp/Robot.cpp)) is responsible for the program’s control flow, and command-based is an declarative paradigm designed to minimize the amount of attention the user has to pay to explicit program control flow, the ``Robot`` class of a command-based project should be mostly empty. However, there are a few important things that must be included +As ``Robot`` ([Java](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/templates/commandv2/Robot.java), [C++ (Header)](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/templates/commandv2/include/Robot.hpp), [C++ (Source)](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/templates/commandv2/cpp/Robot.cpp), [Python](https://github.com/robotpy/mostrobotpy/blob/main/examples/robot/HatchbotInlined/robot.py)) is responsible for the program’s control flow, and command-based is an declarative paradigm designed to minimize the amount of attention the user has to pay to explicit program control flow, the ``Robot`` class of a command-based project should be mostly empty. However, there are a few important things that must be included .. tab-set:: @@ -24,6 +24,14 @@ As ``Robot`` ([Java](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alp :lines: 21-29 :lineno-match: + .. tab-item:: Python + :sync: Python + + .. remoteliteralinclude:: https://raw.githubusercontent.com/robotpy/mostrobotpy/2027.0.0a6/examples/robot/HatchbotInlined/robot.py + :language: python + :lines: 18-24 + :lineno-match: + In Java, an instance of ``RobotContainer`` should be constructed during the ``Robot`` constructor - this is important, as most of the declarative robot setup will be called from the ``RobotContainer`` constructor. In C++, this is not needed as RobotContainer is a value member and will be constructed during the construction of ``Robot``. @@ -46,6 +54,14 @@ In C++, this is not needed as RobotContainer is a value member and will be const :lines: 11-21 :lineno-match: + .. tab-item:: Python + :sync: Python + + .. remoteliteralinclude:: https://raw.githubusercontent.com/robotpy/mostrobotpy/2027.0.0a6/examples/robot/HatchbotInlined/robot.py + :language: python + :lines: 25-33 + :lineno-match: + The inclusion of the ``CommandScheduler.getInstance().run()`` call in the ``robotPeriodic()`` method is essential; without this call, the scheduler will not execute any scheduled commands. Since ``TimedRobot`` runs with a default main loop frequency of 50Hz, this is the frequency with which periodic command and subsystem methods will be called. It is not recommended for new users to call this method from anywhere else in their code. .. tab-set:: @@ -66,6 +82,14 @@ The inclusion of the ``CommandScheduler.getInstance().run()`` call in the ``robo :lines: 32-43 :lineno-match: + .. tab-item:: Python + :sync: Python + + .. remoteliteralinclude:: https://raw.githubusercontent.com/robotpy/mostrobotpy/2027.0.0a6/examples/robot/HatchbotInlined/robot.py + :language: python + :lines: 38-48 + :lineno-match: + The ``autonomousInit()`` method schedules an autonomous command returned by the ``RobotContainer`` instance. The logic for selecting which autonomous command to run can be handled inside of ``RobotContainer``. .. tab-set:: @@ -86,13 +110,21 @@ The ``autonomousInit()`` method schedules an autonomous command returned by the :lines: 47-55 :lineno-match: + .. tab-item:: Python + :sync: Python + + .. remoteliteralinclude:: https://raw.githubusercontent.com/robotpy/mostrobotpy/2027.0.0a6/examples/robot/HatchbotInlined/robot.py + :language: python + :lines: 50-58 + :lineno-match: + The ``teleopInit()`` method cancels any still-running autonomous commands. This is generally good practice. Advanced users are free to add additional code to the various init and periodic methods as they see fit; however, it should be noted that including large amounts of imperative robot code in ``Robot.java`` is contrary to the declarative design philosophy of the command-based paradigm, and can result in confusingly-structured/disorganized code. ## RobotContainer -This class ([Java](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/templates/commandv2/RobotContainer.java), [C++ (Header)](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/templates/commandv2/include/RobotContainer.hpp), [C++ (Source)](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/templates/commandv2/cpp/RobotContainer.cpp)) is where most of the setup for your command-based robot will take place. In this class, you will define your robot’s subsystems and commands, bind those commands to triggering events (such as buttons), and specify which command you will run in your autonomous routine. There are a few aspects of this class new users may want explanations for: +This class ([Java](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/templates/commandv2/RobotContainer.java), [C++ (Header)](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/templates/commandv2/include/RobotContainer.hpp), [C++ (Source)](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/templates/commandv2/cpp/RobotContainer.cpp), [Python](https://github.com/robotpy/mostrobotpy/blob/main/examples/robot/HatchbotInlined/robotcontainer.py)) is where most of the setup for your command-based robot will take place. In this class, you will define your robot’s subsystems and commands, bind those commands to triggering events (such as buttons), and specify which command you will run in your autonomous routine. There are a few aspects of this class new users may want explanations for: .. tab-set:: @@ -176,14 +208,14 @@ Finally, the ``getAutonomousCommand()`` method provides a convenient way for use ## Constants -The ``Constants`` class ([Java](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/templates/commandv2/Constants.java), [C++ (Header)](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/templates/commandv2/include/Constants.hpp)) (in C++ this is not a class, but simply a header file in which several namespaces are defined) is where globally-accessible robot constants (such as speeds, unit conversion factors, PID gains, and sensor/motor ports) can be stored. It is recommended that users separate these constants into individual inner classes corresponding to subsystems or robot modes, to keep variable names shorter. +The ``Constants`` class ([Java](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/templates/commandv2/Constants.java), [C++ (Header)](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/templates/commandv2/include/Constants.hpp), [Python](https://github.com/robotpy/mostrobotpy/blob/main/examples/robot/HatchbotInlined/constants.py)) (in C++ this is not a class, but simply a header file in which several namespaces are defined; in Python this is a module containing inner classes or constants) is where globally-accessible robot constants (such as speeds, unit conversion factors, PID gains, and sensor/motor ports) can be stored. It is recommended that users separate these constants into individual inner classes corresponding to subsystems or robot modes, to keep variable names shorter. In Java, all constants should be declared ``public static final`` so that they are globally accessible and cannot be changed. In C++, all constants should be ``constexpr``. For more illustrative examples of what a ``constants`` class should look like in practice, see those of the various command-based example projects: -* Hatchbot ([Java](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/examples/hatchbottraditional/Constants.java), [C++](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/examples/HatchbotTraditional/include/Constants.hpp)) -* RapidReactCommandBot ([Java](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/examples/rapidreactcommandbot/Constants.java), [C++](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/examples/RapidReactCommandBot/include/Constants.hpp)) +* Hatchbot ([Java](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/examples/hatchbottraditional/Constants.java), [C++](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/examples/HatchbotTraditional/include/Constants.hpp), [Python](https://github.com/robotpy/mostrobotpy/blob/main/examples/robot/HatchbotInlined/constants.py)) +* RapidReactCommandBot ([Java](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibjExamples/src/main/java/org/wpilib/examples/rapidreactcommandbot/Constants.java), [C++](https://github.com/wpilibsuite/allwpilib/blob/v2027.0.0-alpha-6/wpilibcExamples/src/main/cpp/examples/RapidReactCommandBot/include/Constants.hpp), [Python](https://github.com/robotpy/mostrobotpy/blob/main/examples/robot/RapidReactCommandBot/constants.py)) In Java, it is recommended that the constants be used from other classes by statically importing the necessary inner class. An ``import static`` statement imports the static namespace of a class into the class in which you are working, so that any ``static`` constants can be referenced directly as if they had been defined in that class. In C++, the same effect can be attained with ``using namespace``: @@ -197,6 +229,11 @@ In Java, it is recommended that the constants be used from other classes by stat using namespace OIConstants; ``` + ```python + import constants + # Access via constants.OIConstants.kDriverControllerPort + ``` + ## Subsystems User-defined subsystems should go in this package/directory.