From 69ea5451d95d6bb3ceb4a9ae3048cdd357a2022d Mon Sep 17 00:00:00 2001 From: Matthew Bardoe Date: Sat, 1 Jun 2024 20:24:27 -0400 Subject: [PATCH] Python edits to Command documentation (#2648) --------- Co-authored-by: sciencewhiz --- .../commandbased/command-compositions.rst | 106 +++++++++++--- .../docs/software/commandbased/commands.rst | 132 +++++++++++++++--- .../docs/software/commandbased/subsystems.rst | 95 ++++++++++++- .../commandbased/what-is-command-based.rst | 15 +- 4 files changed, 306 insertions(+), 42 deletions(-) diff --git a/source/docs/software/commandbased/command-compositions.rst b/source/docs/software/commandbased/command-compositions.rst index 53363cef86..67f9c4c94e 100644 --- a/source/docs/software/commandbased/command-compositions.rst +++ b/source/docs/software/commandbased/command-compositions.rst @@ -19,6 +19,11 @@ Most importantly, however, command compositions are themselves commands - they e // Will run fooCommand, and then a race between barCommand and bazCommand button.OnTrue(std::move(fooCommand).AndThen(std::move(barCommand).RaceWith(std::move(bazCommand)))); + .. code-block:: python + + # Will run fooCommand, and then a race between barCommand and bazCommand + button.onTrue(fooCommand.andThen(barCommand.raceWith(bazCommand))) + As a rule, command compositions require all subsystems their components require, may run when disabled if all their component set ``runsWhenDisabled`` as ``true``, and are ``kCancelIncoming`` if all their components are ``kCancelIncoming`` as well. Command instances that have been passed to a command composition cannot be independently scheduled or passed to a second command composition. Attempting to do so will throw an exception and crash the user program. This is because composition members are run through their encapsulating command composition, and errors could occur if those same command instances were independently scheduled at the same time as the composition - the command would be being run from multiple places at once, and thus could end up with inconsistent internal state, causing unexpected and hard-to-diagnose behavior. The C++ command-based library uses ``CommandPtr``, a class with move-only semantics, so this type of mistake is easier to avoid. @@ -33,7 +38,7 @@ The command-based library includes various composition types. All of them can be Repeating ^^^^^^^^^ -The ``repeatedly()`` decorator (`Java `__, `C++ `__), backed by the ``RepeatCommand`` class (`Java `__, `C++ `__) restarts the command each time it ends, so that it runs until interrupted. +The ``repeatedly()`` decorator (`Java `__, `C++ `__, :external:py:meth:`Python `), backed by the ``RepeatCommand`` class (`Java `__, `C++ `__, :external:py:class:`Python `) restarts the command each time it ends, so that it runs until interrupted. .. tab-set-code:: @@ -47,12 +52,17 @@ The ``repeatedly()`` decorator (`Java `__, `C++ `__), backed by the ``SequentialCommandGroup`` class (`Java `__, `C++ `__), runs a list of commands in sequence: the first command will be executed, then the second, then the third, and so on until the list finishes. The sequential group finishes after the last command in the sequence finishes. It is therefore usually important to ensure that each command in the sequence does actually finish (if a given command does not finish, the next command will never start!). +The ``Sequence`` factory (`Java `__, `C++ `__, :external:py:func:`Python `), backed by the ``SequentialCommandGroup`` class (`Java `__, `C++ `__, :external:py:class:`Python `), runs a list of commands in sequence: the first command will be executed, then the second, then the third, and so on until the list finishes. The sequential group finishes after the last command in the sequence finishes. It is therefore usually important to ensure that each command in the sequence does actually finish (if a given command does not finish, the next command will never start!). -The ``andThen()`` (`Java `__, `C++ `__) and ``beforeStarting()`` (`Java `__, `C++ `__) decorators can be used to construct a sequence composition with infix syntax. +The ``andThen()`` (`Java `__, `C++ `__, :external:py:meth:`Python `) and ``beforeStarting()`` (`Java `__, `C++ `__, :external:py:meth:`Python `) decorators can be used to construct a sequence composition with infix syntax. .. tab-set-code:: @@ -64,19 +74,24 @@ The ``andThen()`` (`Java `__, `C++ `__) creates a `Repeating`_ `Sequence`_ that runs until interrupted, restarting from the first command each time the last command finishes. +As it's a fairly common combination, the ``RepeatingSequence`` factory (`Java `__, `C++ `__, :external:py:func:`Python `) creates a `Repeating`_ `Sequence`_ that runs until interrupted, restarting from the first command each time the last command finishes. Parallel ^^^^^^^^ There are three types of parallel compositions, differing based on when the composition finishes: -- The ``Parallel`` factory (`Java `__, `C++ `__), backed by the ``ParallelCommandGroup`` class (`Java `__, `C++ `__), constructs a parallel composition that finishes when all members finish. The ``alongWith`` decorator (`Java `__, `C++ `__) does the same in infix notation. -- The ``Race`` factory (`Java `__, `C++ `__), backed by the ``ParallelRaceGroup`` class (`Java `__, `C++ `__), constructs a parallel composition that finishes as soon as any member finishes; all other members are interrupted at that point. The ``raceWith`` decorator (`Java `__, `C++ `__) does the same in infix notation. -- The ``Deadline`` factory (`Java `__, `C++ `__), ``ParallelDeadlineGroup`` (`Java `__, `C++ `__) finishes when a specific command (the "deadline") ends; all other members still running at that point are interrupted. The ``deadlineWith`` decorator (`Java `__, `C++ `__) does the same in infix notation; the comand the decorator was called on is the deadline. +- The ``Parallel`` factory (`Java `__, `C++ `__, :external:py:func:`Python `), backed by the ``ParallelCommandGroup`` class (`Java `__, `C++ `__, :external:py:class:`Python `), constructs a parallel composition that finishes when all members finish. The ``alongWith`` decorator (`Java `__, `C++ `__, :external:py:meth:`Python `) does the same in infix notation. +- The ``Race`` factory (`Java `__, `C++ `__, :external:py:func:`Python `), backed by the ``ParallelRaceGroup`` class (`Java `__, `C++ `__, :external:py:class:`Python `), constructs a parallel composition that finishes as soon as any member finishes; all other members are interrupted at that point. The ``raceWith`` decorator (`Java `__, `C++ `__, :external:py:meth:`Python `) does the same in infix notation. +- The ``Deadline`` factory (`Java `__, `C++ `__, :external:py:func:`Python `), ``ParallelDeadlineGroup`` (`Java `__, `C++ `__, :external:py:class:`Python `) finishes when a specific command (the "deadline") ends; all other members still running at that point are interrupted. The ``deadlineWith`` decorator (`Java `__, `C++ `__, :external:py:meth:`Python `) does the same in infix notation; the comand the decorator was called on is the deadline. .. tab-set-code:: @@ -102,10 +117,21 @@ There are three types of parallel compositions, differing based on when the comp // Will be a parallel deadline composition that ends after two seconds (the deadline) with the three second command getting interrupted (one second command already finished). button.OnTrue(frc2::cmd::Deadline(std::move(twoSecCommand), std::move(oneSecCommand), std::move(threeSecCommand))); + .. code-block:: python + + # Will be a parallel command composition that ends after three seconds with all three commands running their full duration. + button.onTrue(commands2.cmd.parallel(twoSecCommand, oneSecCommand, threeSecCommand)) + + # Will be a parallel race composition that ends after one second with the two and three second commands getting interrupted. + button.onTrue(commands2.cmd.race(twoSecCommand, oneSecCommand, threeSecCommand)) + + # Will be a parallel deadline composition that ends after two seconds (the deadline) with the three second command getting interrupted (one second command already finished). + button.onTrue(commands2.cmd.deadline(twoSecCommand, oneSecCommand, threeSecCommand)) + Adding Command End Conditions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The ``until()`` (`Java `__, `C++ `__) decorator composes the command with an additional end condition. Note that the command the decorator was called on will see this end condition as an interruption. +The ``until()`` (`Java `__, `C++ `__, :external:py:meth:`Python `) decorator composes the command with an additional end condition. Note that the command the decorator was called on will see this end condition as an interruption. .. tab-set-code:: @@ -119,7 +145,12 @@ The ``until()`` (`Java `__, `C++ `__) is a specialization of ``until`` that uses a timeout as the additional end condition. + .. code-block:: python + + # Will be interrupted if limitSwitch.get() returns true + button.onTrue(commands2.cmd.until(limitSwitch.get)) + +The ``withTimeout()`` decorator (`Java `__, `C++ `__, :external:py:meth:`Python `) is a specialization of ``until`` that uses a timeout as the additional end condition. .. tab-set-code:: @@ -133,19 +164,24 @@ The ``withTimeout()`` decorator (`Java `__, `C++ `__) decorator composes the command with an a lambda that will be called after the command's ``end()`` method, with the same boolean parameter indicating whether the command finished or was interrupted. +The ``finallyDo()`` (`Java `__, `C++ `__, :external:py:meth:`Python `) decorator composes the command with an a lambda that will be called after the command's ``end()`` method, with the same boolean parameter indicating whether the command finished or was interrupted. -The ``handleInterrupt()`` (`Java `__, `C++ `__) decorator composes the command with an a lambda that will be called only when the command is interrupted. +The ``handleInterrupt()`` (`Java `__, `C++ `__, :external:py:meth:`Python `) decorator composes the command with an a lambda that will be called only when the command is interrupted. Selecting Compositions ^^^^^^^^^^^^^^^^^^^^^^ Sometimes it's desired to run a command out of a few options based on sensor feedback or other data known only at runtime. This can be useful for determining an auto routine, or running a different command based on whether a game piece is present or not, and so on. -The ``Select`` factory (`Java `__, `C++ `__), backed by the ``SelectCommand`` class (`Java `__, `C++ `__), executes one command from a map, based on a selector function called when scheduled. +The ``Select`` factory (`Java `__, `C++ `__, :external:py:func:`Python `), backed by the ``SelectCommand`` class (`Java `__, `C++ `__, :external:py:class:`Python `), executes one command from a map, based on a selector function called when scheduled. .. tab-set:: @@ -167,7 +203,7 @@ The ``Select`` factory (`Java `__, `C++ `__), backed by the ``ConditionalCommand`` class (`Java `__, `C++ `__), is a specialization accepting two commands and a boolean selector function. +The ``Either`` factory (`Java `__, `C++ `__, :external:py:func:`Python `), backed by the ``ConditionalCommand`` class (`Java `__, `C++ `__, :external:py:class:`Python `), is a specialization accepting two commands and a boolean selector function. .. tab-set-code:: @@ -181,7 +217,12 @@ The ``Either`` factory (`Java `__, `C++ `__) composes a command with a condition that will prevent it from running. + .. code-block:: python + + # Runs either commandOnTrue or commandOnFalse depending on the value of limitSwitch.get() + ConditionalCommand(commandOnTrue, commandOnFalse, limitSwitch.get) + +The ``unless()`` decorator (`Java `__, `C++ `__, :external:py:meth:`Python `) composes a command with a condition that will prevent it from running. .. tab-set-code:: @@ -195,17 +236,22 @@ The ``unless()`` decorator (`Java `__, `C++ `__) that calls a command-returning lambda at schedule-time and runs the returned command by proxy. + .. code-block:: python + + # Command will only run if the intake is deployed. If the intake gets deployed while the command is running, the command will not stop running + button.onTrue(command.unless(lambda: not intake.isDeployed())) + +``ProxyCommand`` described below also has a constructor overload (`Java `__, `C++ `__, :external:py:class:`Python `) that calls a command-returning lambda at schedule-time and runs the returned command by proxy. Scheduling Other Commands ^^^^^^^^^^^^^^^^^^^^^^^^^ By default, composition members are run through the command composition, and are never themselves seen by the scheduler. Accordingly, their requirements are added to the composition's requirements. While this is usually fine, sometimes it is undesirable for the entire command composition to gain the requirements of a single command. A good solution is to "fork off" from the command composition and schedule that command separately. However, this requires synchronization between the composition and the individually-scheduled command. -``ProxyCommand`` (`Java `__, `C++ `__), also creatable using the ``.asProxy()`` decorator (`Java `__, `C++ `__), schedules a command "by proxy": the command is scheduled when the proxy is scheduled, and the proxy finishes when the command finishes. In the case of "forking off" from a command composition, this allows the composition to track the command's progress without it being in the composition. +``ProxyCommand`` (`Java `__, `C++ `__, :external:py:class:`Python `), also creatable using the ``.asProxy()`` decorator (`Java `__, `C++ `__, :external:py:meth:`Python `), schedules a command "by proxy": the command is scheduled when the proxy is scheduled, and the proxy finishes when the command finishes. In the case of "forking off" from a command composition, this allows the composition to track the command's progress without it being in the composition. -Command compositions inherit the union of their compoments' requirements and requirements are immutable. Therefore, a ``SequentialCommandGroup`` (`Java `__, `C++ `__) that intakes a game piece, indexes it, aims a shooter, and shoots it would reserve all three subsystems (the intake, indexer, and shooter), precluding any of those subsystems from performing other operations in their "downtime". If this is not desired, the subsystems that should only be reserved for the composition while they are actively being used by it should have their commands proxied. +Command compositions inherit the union of their compoments' requirements and requirements are immutable. Therefore, a ``SequentialCommandGroup`` (`Java `__, `C++ `__, :external:py:class:`Python `) that intakes a game piece, indexes it, aims a shooter, and shoots it would reserve all three subsystems (the intake, indexer, and shooter), precluding any of those subsystems from performing other operations in their "downtime". If this is not desired, the subsystems that should only be reserved for the composition while they are actively being used by it should have their commands proxied. .. warning:: Do not use ``ProxyCommand`` unless you are sure of what you are doing and there is no other way to accomplish your need! Proxying is only intended for use as an escape hatch from command composition requirement unions. @@ -231,7 +277,16 @@ Command compositions inherit the union of their compoments' requirements and req shooter.AimAndShoot() ); -For cases that don't need to track the proxied command, ``ScheduleCommand`` (`Java `__, `C++ `__) schedules a specified command and ends instantly. + .. code-block:: python + + # composition requirements are indexer and shooter, intake still reserved during its command but not afterwards + commands2.cmd.sequence( + intake.intakeGamePiece().asProxy(), # we want to let the intake intake another game piece while we are processing this one + indexer.processGamePiece(), + shooter.aimAndShoot() + ) + +For cases that don't need to track the proxied command, ``ScheduleCommand`` (`Java `__, `C++ `__, :external:py:class:`Python `) schedules a specified command and ends instantly. .. tab-set-code:: @@ -247,6 +302,12 @@ For cases that don't need to track the proxied command, ``ScheduleCommand`` (`Ja frc2::ScheduleCommand(frc2::cmd::Wait(5.0_s)) .AndThen(frc2::cmd::Print("This will be printed immediately!")) + .. code-block:: python + + # ScheduleCommand ends immediately, so the sequence continues + ScheduleCommand(commands2.cmd.waitSeconds(5.0)) + .andThen(commands2.cmd.print("This will be printed immediately!")) + Subclassing Compositions ------------------------ @@ -281,4 +342,13 @@ Command compositions can also be written as a constructor-only subclass of the m :linenos: :lineno-start: 5 + .. tab-item:: Python + :sync: Python + + .. remoteliteralinclude:: https://raw.githubusercontent.com/robotpy/examples/main/HatchbotTraditional/commands/complexauto.py + :language: python + :lines: 7- + :linenos: + :lineno-start: 5 + The advantages and disadvantages of this subclassing approach in comparison to others are discussed in :ref:`docs/software/commandbased/organizing-command-based:Subclassing Command Groups`. diff --git a/source/docs/software/commandbased/commands.rst b/source/docs/software/commandbased/commands.rst index 51715ff601..45ed7a2d9d 100644 --- a/source/docs/software/commandbased/commands.rst +++ b/source/docs/software/commandbased/commands.rst @@ -1,7 +1,7 @@ Commands ======== -**Commands** represent actions the robot can take. Commands run when scheduled, until they are interrupted or their end condition is met. Commands are represented in the command-based library by the ``Command`` class (`Java `__, `C++ `__). +**Commands** represent actions the robot can take. Commands run when scheduled, until they are interrupted or their end condition is met. Commands are represented in the command-based library by the ``Command`` class (`Java `__, `C++ `__) or the ``Command`` class in ``commands2`` library (:external:py:class:`Python `). The Structure of a Command -------------------------- @@ -11,22 +11,22 @@ Commands specify what the command will do in each of its possible states. This i Initialization ^^^^^^^^^^^^^^ -The ``initialize()`` method (`Java `__, `C++ `__) marks the command start, and is called exactly once per time a command is scheduled. The ``initialize()`` method should be used to place the command in a known starting state for execution. Command objects may be reused and scheduled multiple times, so any state or resources needed for the command's functionality should be initialized or opened in ``initialize`` (which will be called at the start of each use) rather than the constructor (which is invoked only once on object allocation). It is also useful for performing tasks that only need to be performed once per time scheduled, such as setting motors to run at a constant speed or setting the state of a solenoid actuator. +The ``initialize()`` method (`Java `__, `C++ `__, :external:py:meth:`Python `) marks the command start, and is called exactly once per time a command is scheduled. The ``initialize()`` method should be used to place the command in a known starting state for execution. Command objects may be reused and scheduled multiple times, so any state or resources needed for the command's functionality should be initialized or opened in ``initialize`` (which will be called at the start of each use) rather than the constructor (which is invoked only once on object allocation). It is also useful for performing tasks that only need to be performed once per time scheduled, such as setting motors to run at a constant speed or setting the state of a solenoid actuator. Execution ^^^^^^^^^ -The ``execute()`` method (`Java `__, `C++ `__) is called repeatedly while the command is scheduled; this is when the scheduler’s ``run()`` method is called (this is generally done in the main robot periodic method, which runs every 20ms by default). The execute block should be used for any task that needs to be done continually while the command is scheduled, such as updating motor outputs to match joystick inputs, or using the output of a control loop. +The ``execute()`` method (`Java `__, `C++ `__, :external:py:meth:`Python `) is called repeatedly while the command is scheduled; this is when the scheduler’s ``run()`` method is called (this is generally done in the main robot periodic method, which runs every 20ms by default). The execute block should be used for any task that needs to be done continually while the command is scheduled, such as updating motor outputs to match joystick inputs, or using the output of a control loop. Ending ^^^^^^ -The ``end(bool interrupted)`` method (`Java `__, `C++ `__) is called once when the command ends, whether it finishes normally (i.e. ``isFinished()`` returned true) or it was interrupted (either by another command or by being explicitly canceled). The method argument specifies the manner in which the command ended; users can use this to differentiate the behavior of their command end accordingly. The end block should be used to "wrap up" command state in a neat way, such as setting motors back to zero or reverting a solenoid actuator to a "default" state. Any state or resources initialized in ``initialize()`` should be closed in ``end()``. +The ``end(bool interrupted)`` method (`Java `__, `C++ `__, :external:py:meth:`Python `) is called once when the command ends, whether it finishes normally (i.e. ``isFinished()`` returned true) or it was interrupted (either by another command or by being explicitly canceled). The method argument specifies the manner in which the command ended; users can use this to differentiate the behavior of their command end accordingly. The end block should be used to "wrap up" command state in a neat way, such as setting motors back to zero or reverting a solenoid actuator to a "default" state. Any state or resources initialized in ``initialize()`` should be closed in ``end()``. Specifying end conditions ^^^^^^^^^^^^^^^^^^^^^^^^^ -The ``isFinished()`` method (`Java `__, `C++ `__) is called repeatedly while the command is scheduled, whenever the scheduler’s ``run()`` method is called. As soon as it returns true, the command’s ``end()`` method is called and it ends. The ``isFinished()`` method is called after the ``execute()`` method, so the command will execute once on the same iteration that it ends. +The ``isFinished()`` method (`Java `__, `C++ `__, :external:py:meth:`Python `) is called repeatedly while the command is scheduled, whenever the scheduler’s ``run()`` method is called. As soon as it returns true, the command’s ``end()`` method is called and it ends. The ``isFinished()`` method is called after the ``execute()`` method, so the command will execute once on the same iteration that it ends. Command Properties ------------------ @@ -38,7 +38,7 @@ getRequirements Each command should declare any subsystems it controls as requirements. This backs the scheduler's resource management mechanism, ensuring that no more than one command requires a given subsystem at the same time. This prevents situations such as two different pieces of code attempting to set the same motor controller to different output values. -Declaring requirements is done by overriding the ``getRequirements()`` method in the relevant command class, by calling ``addRequirements()``, or by using the ``requirements`` vararg (Java) / ``Requirements`` struct (C++) parameter at the end of the parameter list of most command constructors and factories in the library: +Declaring requirements is done by overriding the ``getRequirements()`` method in the relevant command class, by calling ``addRequirements()``, or by using the ``requirements`` vararg (Java) / ``Requirements`` struct (C++) parameter / ``requirements`` argument (Python) at the end of the parameter list of most command constructors and factories in the library: .. tab-set-code:: @@ -50,16 +50,20 @@ Declaring requirements is done by overriding the ``getRequirements()`` method in frc2::cmd::Run([&intake] { intake.Activate(); }, {&intake}); + .. code-block:: python + + commands2.cmd.run(intake.activate, intake) + As a rule, command compositions require all subsystems their components require. runsWhenDisabled ^^^^^^^^^^^^^^^^ -The ``runsWhenDisabled()`` method (`Java `__, `C++ `__) returns a ``boolean``/``bool`` specifying whether the command may run when the robot is disabled. With the default of returning ``false``, the command will be canceled when the robot is disabled and attempts to schedule it will do nothing. Returning ``true`` will allow the command to run and be scheduled when the robot is disabled. +The ``runsWhenDisabled()`` method (`Java `__, `C++ `__, :external:py:meth:`Python `) returns a ``boolean``/``bool`` specifying whether the command may run when the robot is disabled. With the default of returning ``false``, the command will be canceled when the robot is disabled and attempts to schedule it will do nothing. Returning ``true`` will allow the command to run and be scheduled when the robot is disabled. .. important:: When the robot is disabled, :term:`PWM` outputs are disabled and CAN motor controllers may not apply voltage, regardless of ``runsWhenDisabled``! -This property can be set either by overriding the ``runsWhenDisabled()`` method in the relevant command class, or by using the ``ignoringDisable`` decorator (`Java `__, `C++ `__): +This property can be set either by overriding the ``runsWhenDisabled()`` method in the relevant command class, or by using the ``ignoringDisable`` decorator (`Java `__, `C++ `__, :external:py:meth:`Python `): .. tab-set-code:: @@ -71,16 +75,20 @@ This property can be set either by overriding the ``runsWhenDisabled()`` method frc2::CommandPtr mayRunDuringDisabled = frc2::cmd::Run([] { UpdateTelemetry(); }).IgnoringDisable(true); + .. code-block:: python + + may_run_during_disabled = commands2.cmd.run(lambda: update_telemetry()).ignoring_disable(True) + As a rule, command compositions may run when disabled if all their component commands set ``runsWhenDisabled`` as ``true``. getInterruptionBehavior ^^^^^^^^^^^^^^^^^^^^^^^ -The ``getInterruptionBehavior()`` method (`Java `__, `C++ `__) defines what happens if another command sharing a requirement is scheduled while this one is running. In the default behavior, ``kCancelSelf``, the current command will be canceled and the incoming command will be scheduled successfully. If ``kCancelIncoming`` is returned, the incoming command's scheduling will be aborted and this command will continue running. Note that ``getInterruptionBehavior`` only affects resolution of requirement conflicts: all commands can be canceled, regardless of ``getInterruptionBehavior``. +The ``getInterruptionBehavior()`` method (`Java `__, `C++ `__, :external:py:meth:`Python `) defines what happens if another command sharing a requirement is scheduled while this one is running. In the default behavior, ``kCancelSelf``, the current command will be canceled and the incoming command will be scheduled successfully. If ``kCancelIncoming`` is returned, the incoming command's scheduling will be aborted and this command will continue running. Note that ``getInterruptionBehavior`` only affects resolution of requirement conflicts: all commands can be canceled, regardless of ``getInterruptionBehavior``. .. note:: This was previously controlled by the ``interruptible`` parameter passed when scheduling a command, and is now a property of the command object. -This property can be set either by overriding the ``getInterruptionBehavior`` method in the relevant command class, or by using the `withInterruptBehavior()` decorator (`Java `__, `C++ `__): +This property can be set either by overriding the ``getInterruptionBehavior`` method in the relevant command class, or by using the `withInterruptBehavior()` decorator (`Java `__, `C++ `__, :external:py:meth:`Python `) .. tab-set-code:: @@ -92,19 +100,23 @@ This property can be set either by overriding the ``getInterruptionBehavior`` me frc2::CommandPtr noninterruptible = frc2::cmd::Run([&intake] { intake.Activate(); }, {&intake}).WithInterruptBehavior(Command::InterruptBehavior::kCancelIncoming); + .. code-block:: python + + non_interruptible = commands2.cmd.run(intake.activate, intake).with_interrupt_behavior(Command.InterruptBehavior.kCancelIncoming) + As a rule, command compositions are ``kCancelIncoming`` if all their components are ``kCancelIncoming`` as well. Included Command Types ---------------------- -The command-based library includes many pre-written command types. Through the use of :ref:`lambdas `, these commands can cover almost all use cases and teams should rarely need to write custom command classes. Many of these commands are provided via static factory functions in the ``Commands`` utility class (Java) or in the ``frc2::cmd`` namespace defined in the ``Commands.h`` header (C++). Classes inheriting from ``Subsystem`` also have instance methods that implicitly require ``this``. +The command-based library includes many pre-written command types. Through the use of :ref:`lambdas `, these commands can cover almost all use cases and teams should rarely need to write custom command classes. Many of these commands are provided via static factory functions in the ``Commands`` utility class (Java), in the ``frc2::cmd`` namespace defined in the ``Commands.h`` header (C++), or in the ``commands2.cmd`` namespace (Python). In Java and C++, classes inheriting from ``Subsystem`` also have instance methods that implicitly require ``this``. Running Actions ^^^^^^^^^^^^^^^ The most basic commands are actions the robot takes: setting voltage to a motor, changing a solenoid's direction, etc. For these commands, which typically consist of a method call or two, the command-based library offers several factories to be construct commands inline with one or more lambdas to be executed. -The ``runOnce`` factory, backed by the ``InstantCommand`` (`Java `__, `C++ `__) class, creates a command that calls a lambda once, and then finishes. +The ``runOnce`` factory, backed by the ``InstantCommand`` (`Java `__, `C++ `__, :external:py:class:`Python `) class, creates a command that calls a lambda once, and then finishes. .. tab-set:: @@ -135,7 +147,16 @@ The ``runOnce`` factory, backed by the ``InstantCommand`` (`Java `__, `C++ `__) class, creates a command that calls a lambda repeatedly, until interrupted. + .. tab-item:: Python + :sync: tabcode-python + + .. remoteliteralinclude:: https://raw.githubusercontent.com/robotpy/examples/main/HatchbotInlined/subsystems/hatchsubsystem.py + :language: python + :lines: 24-34 + :linenos: + :lineno-start: 24 + +The ``run`` factory, backed by the ``RunCommand`` (`Java `__, `C++ `__, :external:py:class:`Python `) class, creates a command that calls a lambda repeatedly, until interrupted. .. tab-set-code:: @@ -160,7 +181,18 @@ The ``run`` factory, backed by the ``RunCommand`` (`Java `__, `C++ `__) class, calls one lambda when scheduled, and then a second lambda when interrupted. + .. code-block:: python + + # A split-stick arcade command, with forward/backward controlled by the left + # hand, and turning controlled by the right. + commands2.cmd.run(lambda: robot_drive.arcade_drive( + -driver_controller.get_left_y(), + driver_controller.get_right_x()), + robot_drive) + + + +The ``startEnd`` factory, backed by the ``StartEndCommand`` (`Java `__, `C++ `__, :external:py:class:`Python `) class, calls one lambda when scheduled, and then a second lambda when interrupted. .. tab-set-code:: @@ -186,7 +218,17 @@ The ``startEnd`` factory, backed by the ``StartEndCommand`` (`Java `__, `C++ `__) accepts four lambdas that constitute the four command lifecycle methods: a ``Runnable``/``std::function`` for each of ``initialize()`` and ``execute()``, a ``BooleanConsumer``/``std::function`` for ``end()``, and a ``BooleanSupplier``/``std::function`` for ``isFinished()``. + .. code-block:: python + + commands2.cmd.start_end( + # Start a flywheel spinning at 50% power + lambda: shooter.shooter_speed(0.5), + # Stop the flywheel at the end of the command + lambda: shooter.shooter_speed(0.0), + # Requires the shooter subsystem + shooter) + +``FunctionalCommand`` (`Java `__, `C++ `__, :external:py:class:`Python `) accepts four lambdas that constitute the four command lifecycle methods: a ``Runnable``/``std::function/Callable`` for each of ``initialize()`` and ``execute()``, a ``BooleanConsumer``/``std::function/Callable[bool,[]]`` for ``end()``, and a ``BooleanSupplier``/``std::function/Callable[[],bool]`` for ``isFinished()``. .. tab-set-code:: @@ -220,14 +262,28 @@ The ``startEnd`` factory, backed by the ``StartEndCommand`` (`Java `__, `C++ `__) subclass of ``InstantCommand``. + .. code-block:: python + + commands2.cmd.functional_command( + # Reset encoders on command start + lambda: robot_drive.reset_encoders(), + # Start driving forward at the start of the command + lambda: robot_drive.arcade_drive(ac.kAutoDriveSpeed, 0), + # Stop driving at the end of the command + lambda interrupted: robot_drive.arcade_drive(0, 0), + # End the command when the robot's driven distance exceeds the desired value + lambda: robot_drive.get_average_encoder_distance() >= ac.kAutoDriveDistanceInches, + # Require the drive subsystem + robot_drive) + +To print a string and ending immediately, the library offers the ``Commands.print(String)``/``frc2::cmd::Print(std::string_view)``/``commands2.cmd.print(String)`` factory, backed by the ``PrintCommand`` (`Java `__, `C++ `__, :external:py:class:`Python `) subclass of ``InstantCommand``. Waiting ^^^^^^^ Waiting for a certain condition to happen or adding a delay can be useful to synchronize between different commands in a command composition or between other robot actions. -To wait and end after a specified period of time elapses, the library offers the ``Commands.waitSeconds(double)``/``frc2::cmd::Wait(units::second_t)`` factory, backed by the ``WaitCommand`` (`Java `__, `C++ `__) class. +To wait and end after a specified period of time elapses, the library offers the ``Commands.waitSeconds(double)``/``frc2::cmd::Wait(units::second_t)``/``commands2.cmd.wait(float)`` factory, backed by the ``WaitCommand`` (`Java `__, `C++ `__, :external:py:class:`Python `) class. .. tab-set-code:: @@ -241,7 +297,12 @@ To wait and end after a specified period of time elapses, the library offers the // Ends 5 seconds after being scheduled frc2::WaitCommand(5.0_s) -To wait until a certain condition becomes ``true``, the library offers the ``Commands.waitUntil(BooleanSupplier)``/``frc2::cmd::WaitUntil(std::function)`` factory, backed by the ``WaitUntilCommand`` class (`Java `__, `C++ `__). + .. code-block:: python + + # Ends 5 seconds after being scheduled + commands2.cmd.wait(5.0) + +To wait until a certain condition becomes ``true``, the library offers the ``Commands.waitUntil(BooleanSupplier)``/``frc2::cmd::WaitUntil(std::function)`` factory, backed by the ``WaitUntilCommand`` class (`Java `__, `C++ `__, :external:py:class:`Python `). .. tab-set-code:: @@ -255,6 +316,11 @@ To wait until a certain condition becomes ``true``, the library offers the ``Com // Ends after m_limitSwitch.Get() returns true frc2::WaitUntilCommand([&m_limitSwitch] { return m_limitSwitch.Get(); }) + .. code-block:: python + + # Ends after limit_switch.get() returns True + commands2.cmd.wait_until(limit_switch.get) + Control Algorithm Commands ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -329,6 +395,15 @@ What might a functional command look like in practice? As before, below is a sim :linenos: :lineno-start: 5 + .. tab-item:: Python + :sync: tabcode-python + + .. remoteliteralinclude:: https://raw.githubusercontent.com/robotpy/examples/main/HatchbotTraditional/commands/grabhatch.py + :language: python + :lines: 7- + :linenos: + :lineno-start: 7 + Notice that the hatch subsystem used by the command is passed into the command through the command’s constructor. This is a pattern called :term:`dependency injection`, and allows users to avoid declaring their subsystems as global variables. This is widely accepted as a best-practice - the reasoning behind this is discussed in a :doc:`later section `. Notice also that the above command calls the subsystem method once from initialize, and then immediately ends (as ``isFinished()`` simply returns true). This is typical for commands that toggle the states of subsystems, and as such it would be more succinct to write this command using the factories described above. @@ -364,6 +439,15 @@ What about a more complicated case? Below is a drive command, from the same exam :linenos: :lineno-start: 5 + .. tab-item:: Python + :sync: tabcode-python + + .. remoteliteralinclude:: https://raw.githubusercontent.com/robotpy/examples/main/HatchbotTraditional/commands/defaultdrive.py + :language: python + :lines: 7- + :linenos: + :lineno-start: 7 + And then usage: .. tab-set-code:: @@ -380,6 +464,12 @@ And then usage: :linenos: :lineno-start: 57 + .. remoteliteralinclude:: https://raw.githubusercontent.com/robotpy/examples/main/HatchbotTraditional/robotcontainer.py + :language: python + :lines: 65-72 + :linenos: + :lineno-start: 65 + Notice that this command does not override ``isFinished()``, and thus will never end; this is the norm for commands that are intended to be used as default commands. Once more, this command is rather simple and calls the subsystem method only from one place, and as such, could be more concisely written using factories: .. tab-set-code:: @@ -395,3 +485,9 @@ Notice that this command does not override ``isFinished()``, and thus will never :lines: 52-58 :linenos: :lineno-start: 52 + + .. remoteliteralinclude:: https://raw.githubusercontent.com/robotpy/examples/main/HatchbotInlined/robotcontainer.py + :language: python + :lines: 53-65 + :linenos: + :lineno-start: 53 diff --git a/source/docs/software/commandbased/subsystems.rst b/source/docs/software/commandbased/subsystems.rst index e706df5492..ceddb19351 100644 --- a/source/docs/software/commandbased/subsystems.rst +++ b/source/docs/software/commandbased/subsystems.rst @@ -5,12 +5,12 @@ Subsystems are the basic unit of robot organization in the command-based paradig Subsystems also serve as the backbone of the ``CommandScheduler``\ ’s resource management system. Commands may declare resource requirements by specifying which subsystems they interact with; the scheduler will never concurrently schedule more than one command that requires a given subsystem. An attempt to schedule a command that requires a subsystem that is already-in-use will either interrupt the currently-running command or be ignored, based on the running command's :ref:`Interruption Behavior `. -Subsystems can be associated with "default commands" that will be automatically scheduled when no other command is currently using the subsystem. This is useful for "background" actions such as controlling the robot drive, keeping an arm held at a setpoint, or stopping motors when the subsystem isn't used. Similar functionality can be achieved in the subsystem’s ``periodic()`` method, which is run once per run of the scheduler; teams should try to be consistent within their codebase about which functionality is achieved through either of these methods. Subsystems are represented in the command-based library by the ``Subsystem`` interface (`Java `__, `C++ `__). +Subsystems can be associated with "default commands" that will be automatically scheduled when no other command is currently using the subsystem. This is useful for "background" actions such as controlling the robot drive, keeping an arm held at a setpoint, or stopping motors when the subsystem isn't used. Similar functionality can be achieved in the subsystem’s ``periodic()`` method, which is run once per run of the scheduler; teams should try to be consistent within their codebase about which functionality is achieved through either of these methods. Subsystems are represented in the command-based library by the ``Subsystem`` interface (`Java `__, `C++ `__, :external:py:class:`Python `). Creating a Subsystem -------------------- -The recommended method to create a subsystem for most users is to subclass the abstract ``SubsystemBase`` class (`Java `__, `C++ `__), as seen in the command-based template (`Java `__, `C++ `__): +The recommended method to create a subsystem for most users is to subclass the abstract ``SubsystemBase`` class in (`Java `__, `C++ `__), as seen in the command-based template (`Java `__, `C++ `__). In Python, because Python does not have interfaces, the ``Subsystem`` class is a concrete class that can be subclassed directly (:external:py:class:`Python `). The following example demonstrates how to create a simple subsystem in each of the supported languages: .. tab-set:: @@ -32,6 +32,51 @@ The recommended method to create a subsystem for most users is to subclass the a :linenos: :lineno-start: 5 + .. tab-item:: Python + :sync: Python + + .. code-block:: python + + from commands2 import Command + from commands2 import Subsystem + + + class ExampleSubsystem(Subsystem): + + def __init__(self): + """Creates a new ExampleSubsystem.""" + super().__init__() + + + def exampleMethodCommand()->Command: + """ + Example command factory method. + + :return a command + """ + + return self.runOnce( + lambda: # one-time action goes here # + ) + + def exampleCondition(self)->bool: + """ + An example method querying a boolean state of the subsystem (for example, a digital sensor). + + :return value of some boolean subsystem state, such as a digital sensor. + """ + + #Query some boolean state, such as a digital sensor. + return False + + def periodic(self): + # This method will be called once per scheduler run + pass + + def simulationPeriodic(self): + # This method will be called once per scheduler run during simulation + pass + This class contains a few convenience features on top of the basic ``Subsystem`` interface: it automatically calls the ``register()`` method in its constructor to register the subsystem with the scheduler (this is necessary for the ``periodic()`` method to be called when the scheduler runs), and also implements the ``Sendable`` interface so that it can be sent to the dashboard to display/log relevant status information. Advanced users seeking more flexibility may simply create a class that implements the ``Subsystem`` interface. @@ -39,7 +84,7 @@ Advanced users seeking more flexibility may simply create a class that implement Simple Subsystem Example ------------------------ -What might a functional subsystem look like in practice? Below is a simple pneumatically-actuated hatch mechanism from the HatchBotTraditional example project (`Java `__, `C++ `__): +What might a functional subsystem look like in practice? Below is a simple pneumatically-actuated hatch mechanism from the HatchBotTraditional example project (`Java `__, `C++ `__, `Python `__): .. tab-set:: @@ -70,9 +115,18 @@ What might a functional subsystem look like in practice? Below is a simple pneum :linenos: :lineno-start: 5 + .. tab-item:: Python + :sync: Python + + .. remoteliteralinclude:: https://raw.githubusercontent.com/robotpy/examples/main/HatchbotTraditional/subsystems/hatchsubsystem.py + :language: python + :lines: 7- + :linenos: + :lineno-start: 7 + Notice that the subsystem hides the presence of the DoubleSolenoid from outside code (it is declared ``private``), and instead publicly exposes two higher-level, descriptive robot actions: ``grabHatch()`` and ``releaseHatch()``. It is extremely important that "implementation details" such as the double solenoid be "hidden" in this manner; this ensures that code outside the subsystem will never cause the solenoid to be in an unexpected state. It also allows the user to change the implementation (for instance, a motor could be used instead of a pneumatic) without any of the code outside of the subsystem having to change with it. -Alternatively, instead of writing ``void`` public methods that are called from commands, we can define the public methods as factories that return a command. Consider the following from the HatchBotInlined example project (`Java `__, `C++ `__): +Alternatively, instead of writing ``void`` public methods that are called from commands, we can define the public methods as factories that return a command. Consider the following from the HatchBotInlined example project (`Java `__, `C++ `__, `Python `__): .. tab-set:: @@ -103,7 +157,17 @@ Alternatively, instead of writing ``void`` public methods that are called from c :linenos: :lineno-start: 5 -Note the qualification of the ``RunOnce`` factory used here: this isn't the static factory in ``Commands``! Subsystems have similar instance factories that return commands requiring ``this`` subsystem. Here, the ``Subsystem.runOnce(Runnable)`` factory (`Java `__, `C++ `__) is used. + .. tab-item:: Python + :sync: Python + + .. remoteliteralinclude:: https://raw.githubusercontent.com/robotpy/examples/main/HatchbotInlined/subsystems/hatchsubsystem.py + :language: python + :lines: 7- + :linenos: + :lineno-start: 7 + + +Note the qualification of the ``RunOnce`` factory used here: this isn't the static factory in ``Commands``! Subsystems have similar instance factories that return commands requiring ``this`` (Java/C++) or ``self`` (Python) subsystem. Here, the ``Subsystem.runOnce(Runnable)`` factory (`Java `__, `C++ `__, :external:py:meth:`Python `) is used. For a comparison between these options, see :ref:`docs/software/commandbased/organizing-command-based:Instance Command Factory Methods`. @@ -141,6 +205,19 @@ Subsystems have a ``periodic`` method that is called once every scheduler iterat :linenos: :lineno-start: 30 + .. tab-item:: Python + :sync: Python + + .. code-block:: python + + def periodic(self): + #Update the odometry in the periodic block + self.odometry.update( + Rotation2d.fromDegrees(getHeading()), + self.leftEncoder.getDistance(), + self.rightEncoder.getDistance()) + self.fieldSim.setRobotPose(getPose()) + There is also a ``simulationPeriodic()`` method that is similar to ``periodic()`` except that it is only run during :doc:`Simulation ` and can be used to update the state of the robot. Default Commands @@ -162,6 +239,10 @@ Setting a default command for a subsystem is very easy; one simply calls ``Comma CommandScheduler.GetInstance().SetDefaultCommand(exampleSubsystem, std::move(exampleCommand)); + .. code-block:: python + + CommandScheduler.getInstance().setDefaultCommand(exampleSubsystem, exampleCommand) + .. tab-set-code:: .. code-block:: java @@ -172,4 +253,8 @@ Setting a default command for a subsystem is very easy; one simply calls ``Comma exampleSubsystem.SetDefaultCommand(std::move(exampleCommand)); + .. code-block:: python + + exampleSubsystem.setDefaultCommand(exampleCommand) + .. note:: A command that is assigned as the default command for a subsystem must require that subsystem. diff --git a/source/docs/software/commandbased/what-is-command-based.rst b/source/docs/software/commandbased/what-is-command-based.rst index aa4a2dd6ba..b3b163fa1c 100644 --- a/source/docs/software/commandbased/what-is-command-based.rst +++ b/source/docs/software/commandbased/what-is-command-based.rst @@ -17,6 +17,10 @@ The command-based paradigm is also an example of :term:`declarative programming` Trigger([&condition] { return condition.Get()).OnTrue(frc2::cmd::RunOnce([&piston] { piston.Set(frc::DoubleSolenoid::kForward))); + .. code-block:: python + + Trigger(condition.get).onTrue(Commands.runOnce(lambda: piston.set(DoubleSolenoid.Value.kForward))) + In contrast, without using command-based, the user would need to check the button state every iteration, and perform the appropriate action based on the state of the button. .. tab-set-code:: @@ -43,6 +47,15 @@ In contrast, without using command-based, the user would need to check the butto pressed = false; } + .. code-block:: python + + if condition.get(): + if not pressed: + piston.set(DoubleSolenoid.Value.kForward) + pressed = True + else: + pressed = False + Subsystems and Commands ----------------------- @@ -60,7 +73,7 @@ How Commands Are Run .. note:: For a more detailed explanation, see :doc:`command-scheduler`. -Commands are run by the ``CommandScheduler`` (`Java `__, `C++ `__) singleton, which polls triggers (such as buttons) for commands to schedule, preventing resource conflicts, and executing scheduled commands. The scheduler's ``run()`` method must be called; it is generally recommended to call it from the ``robotPeriodic()`` method of the ``Robot`` class, which is run at a default frequency of 50Hz (once every 20ms). +Commands are run by the ``CommandScheduler`` (`Java `__, `C++ `__, :external:py:class:`Python `) singleton, which polls triggers (such as buttons) for commands to schedule, preventing resource conflicts, and executing scheduled commands. The scheduler's ``run()`` method must be called; it is generally recommended to call it from the ``robotPeriodic()`` method of the ``Robot`` class, which is run at a default frequency of 50Hz (once every 20ms). Multiple commands can run concurrently, as long as they do not require the same resources on the robot. Resource management is handled on a per-subsystem basis: commands specify which subsystems they interact with, and the scheduler will ensure that no more more than one command requiring a given subsystem is scheduled at a time. This ensures that, for example, users will not end up with two different pieces of code attempting to set the same motor controller to different output values.