Skip to content

Commit

Permalink
Validate various commands with minor adjustments
Browse files Browse the repository at this point in the history
- Update docs
- Change member variables to be prefixed with _
  • Loading branch information
virtuald committed Jan 20, 2024
1 parent 1135dab commit 53c0e92
Show file tree
Hide file tree
Showing 13 changed files with 165 additions and 53 deletions.
1 change: 1 addition & 0 deletions commands2/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# notrack
class IllegalCommandUse(Exception):
"""
This exception is raised when a command is used in a way that it shouldn't be.
Expand Down
7 changes: 5 additions & 2 deletions commands2/functionalcommand.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# validated: 2024-01-19 DS 6e58db398d63 FunctionalCommand.java
from __future__ import annotations

from typing import Any, Callable
Expand All @@ -11,7 +12,8 @@ class FunctionalCommand(Command):
A command that allows the user to pass in functions for each of the basic command methods through
the constructor. Useful for inline definitions of complex commands - note, however, that if a
command is beyond a certain complexity it is usually better practice to write a proper class for
it than to inline it."""
it than to inline it.
"""

def __init__(
self,
Expand All @@ -28,7 +30,8 @@ def __init__(
:param onExecute: the function to run on command execution
:param onEnd: the function to run on command end
:param isFinished: the function that determines whether the command has finished
:param requirements: the subsystems required by this command"""
:param requirements: the subsystems required by this command
"""
super().__init__()

assert callable(onInit)
Expand Down
13 changes: 8 additions & 5 deletions commands2/instantcommand.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# validated: 2024-01-19 DS 5cf961edb973 InstantCommand.java
from __future__ import annotations

from typing import Callable, Optional
Expand All @@ -9,17 +10,19 @@
class InstantCommand(FunctionalCommand):
"""
A Command that runs instantly; it will initialize, execute once, and end on the same iteration of
the scheduler. Users can either pass in a Runnable and a set of requirements, or else subclass
this command if desired."""
the scheduler. Users can either pass in a Callable and a set of requirements, or else subclass
this command if desired.
"""

def __init__(
self, toRun: Optional[Callable[[], None]] = None, *requirements: Subsystem
):
"""
Creates a new InstantCommand that runs the given Runnable with the given requirements.
Creates a new InstantCommand that runs the given Callable with the given requirements.
:param toRun: the Runnable to run
:param requirements: the subsystems required by this command"""
:param toRun: the Callable to run
:param requirements: the subsystems required by this command
"""
super().__init__(
toRun or (lambda: None),
lambda: None,
Expand Down
30 changes: 17 additions & 13 deletions commands2/notifiercommand.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,44 @@
# validated: 2024-01-19 DS 6e58db398d63 NotifierCommand.java
from __future__ import annotations

from typing import Any, Callable

from wpilib import Notifier
from wpimath import units

from .command import Command
from .subsystem import Subsystem


class NotifierCommand(Command):
"""
A command that starts a notifier to run the given runnable periodically in a separate thread. Has
no end condition as-is; either subclass it or use Command#withTimeout(double) or {@link
Command#until(java.util.function.BooleanSupplier)} to give it one.
A command that starts a notifier to run the given Callable periodically in a separate thread. Has
no end condition as-is; either subclass it or use :func:`commands2.Command.withTimeout` or :func:`commands2.Command.until` to give it one.
WARNING: Do not use this class unless you are confident in your ability to make the executed
code thread-safe. If you do not know what "thread-safe" means, that is a good sign that you
should not use this class."""
.. warning:: Do not use this class unless you are confident in your ability
to make the executed code thread-safe. If you do not know what
"thread-safe" means, that is a good sign that you should not use
this class.
"""

def __init__(
self, toRun: Callable[[], Any], period: float, *requirements: Subsystem
self, toRun: Callable[[], Any], period: units.seconds, *requirements: Subsystem
):
"""
Creates a new NotifierCommand.
:param toRun: the runnable for the notifier to run
:param toRun: the Callable for the notifier to run
:param period: the period at which the notifier should run, in seconds
:param requirements: the subsystems required by this command"""
:param requirements: the subsystems required by this command
"""
super().__init__()

self.notifier = Notifier(toRun)
self.period = period
self._notifier = Notifier(toRun)
self._period = period
self.addRequirements(*requirements)

def initialize(self):
self.notifier.startPeriodic(self.period)
self._notifier.startPeriodic(self._period)

def end(self, interrupted: bool):
self.notifier.stop()
self._notifier.stop()
12 changes: 10 additions & 2 deletions commands2/parallelcommandgroup.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# validated: 2024-01-19 DS aaea85ff1656 ParallelCommandGroup.java
from __future__ import annotations

from typing import Dict
Expand All @@ -14,22 +15,29 @@ class ParallelCommandGroup(Command):
The rules for command compositions apply: command instances that are passed to it cannot be
added to any other composition or scheduled individually, and the composition requires all
subsystems its components require."""
subsystems its components require.
"""

def __init__(self, *commands: Command):
"""
Creates a new ParallelCommandGroup. The given commands will be executed simultaneously. The
command composition will finish when the last command finishes. If the composition is
interrupted, only the commands that are still running will be interrupted.
:param commands: the commands to include in this composition."""
:param commands: the commands to include in this composition.
"""
super().__init__()
self._commands: Dict[Command, bool] = {}
self._runsWhenDisabled = True
self._interruptBehavior = InterruptionBehavior.kCancelIncoming
self.addCommands(*commands)

def addCommands(self, *commands: Command):
"""
Adds the given commands to the group.
:param commands: Commands to add to the group
"""
commands = flatten_args_commands(commands)
if True in self._commands.values():
raise IllegalCommandUse(
Expand Down
65 changes: 53 additions & 12 deletions commands2/paralleldeadlinegroup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# validated: 2024-01-19 DS e07de37e64f2 ParallelDeadlineGroup.java
from __future__ import annotations

from typing import Dict

from wpiutil import SendableBuilder

from .command import Command, InterruptionBehavior
from .commandscheduler import CommandScheduler
from .exceptions import IllegalCommandUse
Expand All @@ -10,35 +13,66 @@

class ParallelDeadlineGroup(Command):
"""
A command composition that runs one of a selection of commands, either using a selector and a key
to command mapping, or a supplier that returns the command directly at runtime.
A command composition that runs a set of commands in parallel, ending only when a specific
command (the "deadline") ends, interrupting all other commands that are still running at that
point.
The rules for command compositions apply: command instances that are passed to it cannot be
added to any other composition or scheduled individually, and the composition requires all
subsystems its components require."""
subsystems its components require.
"""

def __init__(self, deadline: Command, *commands: Command):
"""
Creates a new SelectCommand.
Creates a new ParallelDeadlineGroup. The given commands (including the
deadline) will be executed simultaneously. The composition will finish when
the deadline finishes, interrupting all other still-running commands. If
the composition is interrupted, only the commands still running will be
interrupted.
:param deadline: the command that determines when the composition ends
:param commands: the commands to be executed
:param commands: the map of commands to choose from
:param selector: the selector to determine which command to run"""
:raises IllegalCommandUse: if the deadline command is also in the otherCommands argument
"""
super().__init__()
self._commands: Dict[Command, bool] = {}
self._runsWhenDisabled = True
self._finished = True
self._deadline = deadline
self._interruptBehavior = InterruptionBehavior.kCancelIncoming
self.addCommands(*commands)
if deadline not in self._commands:
self.addCommands(deadline)
self.setDeadline(deadline)

def setDeadline(self, deadline: Command):
if deadline not in self._commands:
self.addCommands(deadline)
"""
Sets the deadline to the given command. The deadline is added to the group if it is not already
contained.
:param deadline: the command that determines when the group ends
:raises IllegalCommandUse: if the deadline command is already in the composition
"""

# use getattr here because deadline not set in constructor
isAlreadyDeadline = deadline == getattr(self, "_deadline", None)
if isAlreadyDeadline:
return

if deadline in self._commands:
raise IllegalCommandUse(
f"The deadline command cannot also be in the other commands!"
)
self.addCommands(deadline)
self._deadline = deadline

def addCommands(self, *commands: Command):
"""
Adds the given commands to the group.
:param commands: Commands to add to the group.
:raises IllegalCommandUse: if the deadline command is already in the composition
"""
commands = flatten_args_commands(commands)
if not self._finished:
raise IllegalCommandUse(
Expand All @@ -50,7 +84,7 @@ def addCommands(self, *commands: Command):
for command in commands:
if not command.getRequirements().isdisjoint(self.requirements):
raise IllegalCommandUse(
"Multiple comands in a parallel composition cannot require the same subsystems."
"Multiple commands in a parallel composition cannot require the same subsystems."
)

self._commands[command] = False
Expand Down Expand Up @@ -94,3 +128,10 @@ def runsWhenDisabled(self) -> bool:

def getInterruptionBehavior(self) -> InterruptionBehavior:
return self._interruptBehavior

def initSendable(self, builder: SendableBuilder):
super().initSendable(builder)

builder.addStringProperty(
"deadline", lambda: self._deadline.getName(), lambda _: None
)
14 changes: 11 additions & 3 deletions commands2/parallelracegroup.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# validated: 2024-01-19 DS aaea85ff1656 ParallelRaceGroup.java
from __future__ import annotations

from typing import Set
Expand All @@ -15,23 +16,30 @@ class ParallelRaceGroup(Command):
The rules for command compositions apply: command instances that are passed to it cannot be
added to any other composition or scheduled individually, and the composition requires all
subsystems its components require."""
subsystems its components require.
"""

def __init__(self, *commands: Command):
"""
Creates a new ParallelCommandRace. The given commands will be executed simultaneously, and will
"race to the finish" - the first command to finish ends the entire command, with all other
commands being interrupted.
:param commands: the commands to include in this composition."""
:param commands: the commands to include in this composition.
"""
super().__init__()
self._commands: Set[Command] = set()
self._runsWhenDisabled = True
self._interruptBehavior = InterruptionBehavior.kCancelIncoming
self._finished = True
self._interruptBehavior = InterruptionBehavior.kCancelIncoming
self.addCommands(*commands)

def addCommands(self, *commands: Command):
"""
Adds the given commands to the group.
:param commands: Commands to add to the group.
"""
commands = flatten_args_commands(commands)
if not self._finished:
raise IllegalCommandUse(
Expand Down
5 changes: 3 additions & 2 deletions commands2/pidcommand.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# validated: 2024-01-19 DS f29a7d2e501b PIDCommand.java
# Copyright (c) FIRST and other WPILib contributors.
# Open Source Software; you can modify and/or share it under the terms of
# the WPILib BSD license file in the root directory of this project.
Expand All @@ -13,7 +14,7 @@

class PIDCommand(Command):
"""
A command that controls an output with a PIDController. Runs forever by default - to add
A command that controls an output with a :class:`wpimath.controller.PIDController`. Runs forever by default - to add
exit conditions and/or other behavior, subclass this class. The controller calculation and output
are performed synchronously in the command's execute() method.
"""
Expand All @@ -27,7 +28,7 @@ def __init__(
*requirements: Subsystem,
):
"""
Creates a new PIDCommand, which controls the given output with a PIDController.
Creates a new PIDCommand, which controls the given output with a :class:`wpimath.controller.PIDController`.
:param controller: the controller that controls the output.
:param measurementSource: the measurement of the process variable
Expand Down
3 changes: 2 additions & 1 deletion commands2/pidsubsystem.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# validated: 2024-01-19 DS f29a7d2e501b PIDSubsystem.java
# Copyright (c) FIRST and other WPILib contributors.
# Open Source Software; you can modify and/or share it under the terms of
# the WPILib BSD license file in the root directory of this project.
Expand All @@ -10,7 +11,7 @@

class PIDSubsystem(Subsystem):
"""
A subsystem that uses a {@link PIDController} to control an output. The
A subsystem that uses a :class:`wpimath.controller.PIDController` to control an output. The
controller is run synchronously from the subsystem's periodic() method.
"""

Expand Down
7 changes: 5 additions & 2 deletions commands2/printcommand.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
# validated: 2024-01-19 DS 8ac45f20bb47 PrintCommand.java
from __future__ import annotations

from .instantcommand import InstantCommand


class PrintCommand(InstantCommand):
"""
A command that prints a string when initialized."""
A command that prints a string when initialized.
"""

def __init__(self, message: str):
"""
Creates a new a PrintCommand.
:param message: the message to print"""
:param message: the message to print
"""
super().__init__(lambda: print(message))

def runsWhenDisabled(self) -> bool:
Expand Down
Loading

0 comments on commit 53c0e92

Please sign in to comment.