Fonctions pratiques

Alors que les méthodologies décrites précédemment fonctionneront bien pour écrire du code de robot basé sur des commandes, les librairies basées sur des commandes contiennent plusieurs fonctionnalités pratiques pour les utilisateurs plus avancés qui peuvent réduire considérablement la verbosité/complexité du code basé sur les commandes. Il est fortement recommandé aux utilisateurs de se familiariser avec ces fonctionnalités pour maximiser la valeur qu’ils retirent des librairies orientées commandes.

Définitions des commandes en ligne

Alors que les utilisateurs peuvent créer des commandes en écrivant explicitement des classes de commandes (soit en sous-classant CommandBase ou en implémentant Command), pour de nombreuses commandes (telles que celles qui appellent simplement une seule méthode de sous-système), cela implique beaucoup de code inutile. Pour aider à atténuer cela, la plupart des commandes pré-écrites incluses dans la librairie basée sur les commandes peuvent être inline - c’est-à-dire que le corps de la commande peut être défini dans une seule ligne de code lors de la construction de la commande.

Passer des sous-programmes en tant que paramètres

Afin d’insérer une définition de commande, les utilisateurs ont besoin d’un moyen de spécifier le code que les commandes exécuteront en tant que paramètres de constructeur. Heureusement, Java et C ++ offrent aux utilisateurs la possibilité de transmettre des sous-programmes en tant que paramètres.

Références de méthode (Java)

En Java, une référence à un sous-programme qui peut être passé en tant que paramètre est appelée une référence de méthode. La syntaxe générale d’une référence de méthode est object::method. Notez qu’aucun paramètre de méthode n’est inclus, puisque la méthode elle-même est le paramètre. La méthode n’est pas appelée - elle est transmise à un autre bout de code (dans ce cas, une commande) afin que ce code puisse l’appeler en cas de besoin. Pour plus d’informations sur les références de méthodes, voir la documentation officielle d’Oracle.

Expressions Lambda (Java)

Alors que les références de méthode fonctionnent bien pour passer un sous-programme qui a déjà été écrit, il est souvent peu pratique/inutile d’écrire un sous-programme uniquement dans le but de l’envoyer comme référence de méthode, si ce sous-programme ne sera jamais utilisé ailleurs. Pour éviter cela, Java prend également en charge une fonctionnalité appelée « expressions lambda ». Une expression lambda est une définition de méthode « inline » - elle permet à un sous-programme d’être défini à l’intérieur d’une liste de paramètres. Pour plus de détails sur l’écriture d’expressions lambda Java, consultez ce tutoriel.

Expressions Lambda (C ++)

Avertissement

En raison de complications dans la sémantique C ++, la capture de `` this “” dans un lambda C ++ peut provoquer une exception de pointeur nul si elle est effectuée à partir d’une commande de composant d’un groupe de commandes. Dans la mesure du possible, les utilisateurs C ++ doivent capturer les membres de commande pertinents de manière explicite et par valeur. Pour plus de détails, voir ici.

C++ n’a pas d’équivalent proche des références de méthode Java - les pointeurs vers les fonctions membres ne sont généralement pas directement utilisables en tant que paramètres en raison de la présence du paramètre implicite this. Cependant, C++ propose des expressions lambda - en outre, les expressions lambda proposées par C++ sont à bien des égards plus puissantes que celles de Java. Pour plus d’informations sur l’écriture d’expressions lambda C++, consultez cppreference.

Exemple de commande « inline »

Alors, à quoi ressemble une définition de commande « inline » dans la pratique?

The InstantCommand class provides an example of a type of command that benefits greatly from inlining. Consider the following from the HatchBotInlined example project (Java, C++):

 99
100
101
102
103
104
    new JoystickButton(m_driverController, Button.kB.value)
        .whenPressed(new InstantCommand(m_hatchSubsystem::releaseHatch, m_hatchSubsystem));
    // While holding the shoulder button, drive at half speed
    new JoystickButton(m_driverController, Button.kBumperRight.value)
        .whenPressed(() -> m_robotDrive.setMaxOutput(0.5))
        .whenReleased(() -> m_robotDrive.setMaxOutput(1));

Au lieu d’écrire inutilement des commandes séparées GrabHatch et ReleaseHatch qui n’appellent qu’une seule méthode avant de se terminer, les deux peuvent être accomplies avec une simple définition « inline » en passant la méthode du sous-système appropriée.

Types de commandes incluses

La librairie orientée commande comprend une variété de commandes déjà programmées et prêtes pour les cas d’utilisation couramment rencontrés. Bon nombre de ces commandes sont destinées à être utilisées « clés-en-main » à travers l’inlining, mais elles peuvent également avoir des classes dérivées (sous-classes). Une liste des commandes déjà programmées fournies peut être trouvée ci-dessous, ainsi que de brefs exemples d’utilisation de chacune d’elles - pour une documentation plus rigoureuse, voir les documents API (Java, C++).

ConditionalCommand

La classe ConditionalCommand (Java, C++) exécute l’une des deux commandes lorsqu’elles sont exécutées, selon une condition vrai-ou-faux spécifiée par l’utilisateur :

// Runs either commandOnTrue or commandOnFalse depending on the value of m_limitSwitch.get()
new ConditionalCommand(commandOnTrue, commandOnFalse, m_limitSwitch::get)

SelectCommand

Note

Alors que la version Java de SelectCommand utilise simplement un Object comme clé, la version C++ est basée sur le type de clé.

Note

Une version alternative de SelectCommand prend simplement une méthode qui fournit la commande à exécuter - cela peut être très succinct, mais rend impossible à déduire les exigences de la commande, et laisse ainsi l’utilisateur responsable de l’ajout manuel des exigences à SelectCommand.

The SelectCommand class (Java, C++) is a generalization of the ConditionalCommand class that runs one of a selection of commands based on the value of a user-specified selector. The following example code is taken from the SelectCommand example project (Java, C++):

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class RobotContainer {
  // The enum used as keys for selecting the command to run.
  private enum CommandSelector {
    ONE,
    TWO,
    THREE
  }

  // An example selector method for the selectcommand.  Returns the selector that will select
  // which command to run.  Can base this choice on logical conditions evaluated at runtime.
  private CommandSelector select() {
    return CommandSelector.ONE;
  }

  // An example selectcommand.  Will select from the three commands based on the value returned
  // by the selector method at runtime.  Note that selectcommand works on Object(), so the
  // selector does not have to be an enum; it could be any desired type (string, integer,
  // boolean, double...)
  private final Command m_exampleSelectCommand =
      new SelectCommand(
          // Maps selector values to commands
          Map.ofEntries(
              Map.entry(CommandSelector.ONE, new PrintCommand("Command one was selected!")),
              Map.entry(CommandSelector.TWO, new PrintCommand("Command two was selected!")),
              Map.entry(CommandSelector.THREE, new PrintCommand("Command three was selected!"))),
          this::select);

InstantCommand

La classe InstantCommand (Java, C++) exécute une seule action lors de l’initialisation, puis se termine immédiatement :

// Actuates the hatch subsystem to grab the hatch
new InstantCommand(m_hatchSubsystem::grabHatch, m_hatchSubsystem)

RunCommand

La classe RunCommand (Java, C++) exécute de manière répétitive une méthode spécifiée dans son bloc execute(). Il n’a pas de conditions de fin par défaut; les utilisateurs peuvent soit la dériver, soit la décorer pour l’ajouter.

// A split-stick arcade command, with forward/backward controlled by the left
// hand, and turning controlled by the right.
new RunCommand(() -> m_robotDrive.arcadeDrive(
    -driverController.getY(GenericHID.Hand.kLeft),
    driverController.getX(GenericHID.Hand.kRight)),
    m_robotDrive)

StartEndCommand

La classe StartEndCommand (Java, C++) exécute une action au démarrage, et une seconde action à la fin. Il n’a pas de conditions de fin par défaut; les utilisateurs peuvent soit la dériver, soit décorer une commande incorporée (inlined) pour l’ajouter.

new StartEndCommand(
    // Start a flywheel spinning at 50% power
    () -> m_shooter.shooterSpeed(0.5),
    // Stop the flywheel at the end of the command
    () -> m_shooter.shooterSpeed(0.0),
    // Requires the shooter subsystem
    m_shooter
)

FunctionalCommand

La classe FunctionalCommand (Java, C++)permet de passer les quatre méthodes Command comme références de méthode ou lambdas :

new FunctionalCommand(
    // Reset encoders on command start
    m_robotDrive::resetEncoders,
    // Start driving forward at the start of the command
    () -> m_robotDrive.arcadeDrive(kAutoDriveSpeed, 0),
    // Stop driving at the end of the command
    interrupted -> m_robotDrive.arcadeDrive(0, 0),
    // End the command when the robot's driven distance exceeds the desired value
    () -> m_robotDrive.getAverageEncoderDistance() >= kAutoDriveDistanceInches,
    // Require the drive subsystem
    m_robotDrive
)

PrintCommand

La classe PrintCommand (Java, C++) affiche à l’écran une chaîne de caractères donnée.

new PrintCommand("This message will be printed!")

ScheduleCommand

La classe ScheduleCommand (Java, C++) planifie une commande spécifiée et se termine instantanément :

// Schedules commandToSchedule when run
new ScheduleCommand(commandToSchedule)

Ceci est souvent utile pour « sortir » du groupe de commandes: par défaut, les commandes des groupes de commandes sont exécutées via le groupe de commandes, et ne sont jamais elles-mêmes vues par le planificateur. En conséquence, leurs exigences s’ajoutent aux exigences du groupe. Bien que ce soit généralement bien, il n’est parfois pas souhaitable que tout le groupe de commandes obtienne les exigences d’une seule commande - une bonne solution est de «sortir» du groupe de commandes et de planifier cette commande séparément.

ProxyScheduleCommand

La classe ProxyScheduleCommand (Java, C++) planifie une commande spécifiée et ne se termine pas tant que cette commande n’est pas terminée :

// Schedules commandToSchedule when run, does not end until commandToSchedule is no longer scheduled
new ProxyScheduleCommand(commandToSchedule)

Ceci est souvent utile pour « sortir » des groupes de commandes: par défaut, les commandes des groupes de commandes sont exécutées via le groupe de commandes, et ne sont jamais elles-mêmes vues par le planificateur. En conséquence, leurs exigences s’ajoutent aux exigences du groupe. Bien que ce soit généralement bien, il n’est parfois pas souhaitable que tout le groupe de commandes obtienne les exigences d’une seule commande - une bonne solution est de «sortir» du groupe de commandes et de planifier la commande séparément.

WaitCommand

La classe WaitCommand (Java, C++) ne fait rien, et se termine après qu’une période de temps spécifiée se soit s’écoulée après sa planification initiale:

// Ends 5 seconds after being scheduled
new WaitCommand(5)

Ceci est souvent utile en tant que composant d’un groupe de commandes.

WaitCommand peut également être dérivée (sous-classée) pour créer une commande plus compliquée qui s’exécute pendant un certain temps. Si WaitCommand est utilisée dans cette méthode, l’utilisateur doit s’assurer que les méthodes WaitCommand Initialize, End et IsFinished sont toujours appelées pour le timer de WaitCommand fonctionne.

WaitUntilCommand

Avertissement

La minuterie de match utilisé par WaitUntilCommand ne fournit pas un temps de match officiel! Bien qu’elle soit assez précise, l’utilisation de cette minuterie ne peut pas garantir la légalité des actions de votre robot.

La classe WaitUntilCommand (Java, C++) ne fait rien, et se termine une fois qu’une condition spécifiée devient vraie, ou jusqu’à ce qu’un temps de jeu spécifié s’écoule.

// Ends after the 60-second mark of the current match
new WaitUntilCommand(60)

// Ends after m_limitSwitch.get() returns true
new WaitUntilCommand(m_limitSwitch::get)

PerpetualCommand

La classe PerpetualCommand (Java, C++) exécute une commande donnée avec sa condition de fin supprimée, de sorte qu’elle s’exécute pour toujours (sauf si elle est interrompue de l’extérieur):

// Will run commandToRunForever perpetually, even if its isFinished() method returns true
new PerpetualCommand(commandToRunForever)

Méthodes « Command Decorator »

L’interface Command contient un certain nombre de méthodes « décoratrices » ou « decorator » par défaut qui peuvent être utilisées pour ajouter des fonctionnalités supplémentaires aux commandes existantes. Une méthode « décoratrice » est une méthode qui prend un objet (dans ce cas, une commande) et renvoie un objet du même type (c’est-à-dire une commande) avec quelques fonctionnalités supplémentaires qui lui été ajoutées au passage. Une liste des méthodes décoratrices fournies avec de brefs exemples est incluse ci-dessous - pour une documentation rigoureuse, voir les documents API (Java, C++).

withTimeout

La méthode décoratrice withTimeout() (Java, C++) ajoute un temps d’attente ou timeout à une commande. La commande décorée sera interrompue si ce délai expire :

// Will time out 5 seconds after being scheduled, and be interrupted
button.whenPressed(command.withTimeout(5));

withInterrupt

La méthode décoratrice withInterrupt() (Java, C++) ajoute une condition sous laquelle la commande sera interrompue :

// Will be interrupted if m_limitSwitch.get() returns true
button.whenPressed(command.withInterrupt(m_limitSwitch::get));

andThen

La méthode décoratrice andThen() (Java, C++) ajoute une méthode à exécuter après la fin de l’exécution de la commande :

// Will print "hello" after ending
button.whenPressed(command.andThen(() -> System.out.println("hello")));

beforeStarting

La méthode décoratrice beforeStarting() (Java, C++) ajoute une méthode à exécuter avant le début de l’exécution de la commande :

// Will print "hello" before starting
button.whenPressed(command.beforeStarting(() -> System.out.println("hello")));

alongWith (Java seulement)

Note

Ce décorateur n’est pas pris en charge en C ++ en raison de contraintes techniques - les utilisateurs doivent simplement construire un groupe de commandes parallèle de la manière habituelle.

La méthode décoratrice <https://first.wpi.edu/wpilib/allwpilib/docs/release/java/edu/wpi/first/wpilibj2/command/Command.html#alongWith(edu.wpi.first.wpilibj2.command.Command…)> alongWith() retourne un parallel command group. Toutes les commandes s’exécuteront simultanément et l’exécution de chaque commande se terminera indépendamment des autres.

// Will be a parallel command group that ends after three seconds with all three commands running their full duration.
button.whenPressed(oneSecCommand.alongWith(twoSecCommand, threeSecCommand));

raceWith (Java seulement)

Note

Ce décorateur n’est pas pris en charge en C ++ en raison de contraintes techniques - les utilisateurs doivent simplement construire un groupe de type « race » (ou course) parallèle de la manière habituelle.

La méthode décoratrice raceWith() retourne un groupe de commandes parallèles en compétition , dont l’exécution se termine aussitôt que l’exécution de la première commande prend s’achève. En ce moment-là, l’exécution de toutes les autres commandes est interrompue. Et ce peu importe la commande appellante.

// Will be a parallel race group that ends after one second with the two and three second commands getting interrupted.
button.whenPressed(twoSecCommand.raceWith(oneSecCommand, threeSecCommand));

deadlineWith (Java seulement)

Note

Ce décorateur n’est pas pris en charge en C++ en raison de contraintes techniques - les utilisateurs doivent simplement créer un groupe de type « deadline » (échéance) parallèle de la manière habituelle.

La méthode décoratrice deadlineWith() retourne un groupe deadline (échéance) de commandes en parallèle avec la commande appellante jouant le rôle de deadline. Quand l’exécution de cette deadline s’achève, l’exécution de toutes les autres commandes du groupe encore en cours est interrompue.

// Will be a parallel deadline group that ends after two seconds (the deadline) with the three second command getting interrupted (one second command already finished).
button.whenPressed(twoSecCommand.deadlineWith(oneSecCommand, threeSecCommand));

withName (Pour Java uniquement)

Note

Ce décorateur n’est pas supporté en C++ en raison de contraintes techniques - à la place, les utilisateurs devraient définir le nom de la commande à l’intérieur de leur classe de commande.

La méthode décoratrice withName() ajoute un nom à une commande. Ce nom apparaîtra au dashboard si la commande est envoyée à travers l’interface sendable.

// This command will be called "My Command".
var command = new PrintCommand("Hello robot!").withName("My Command");

perpetually

La méthode décoratrice perpetually() (Java, C++) supprime la condition de fin d’exécution d’une commande, de sorte que cette commande s’exécute pour toujours.

// Will run forever unless externally interrupted, regardless of command.isFinished()
button.whenPressed(command.perpetually());

Décorateurs de composition

N’oubliez pas que les décorateurs, comme tous les groupes de commandes, peuvent être composés! Cela permet des expressions en ligne très puissantes et concises:

// Will run fooCommand, and then a race between barCommand and bazCommand
button.whenPressed(fooCommand.andThen(barCommand.raceWith(bazCommand)));

Méthodes « Static Factory » pour les groupes de commandes (Java seulement)

Note

Ces méthodes de type « Factory » ne sont pas incluses dans la librairie de commandes C++, car la réduction de la verbosité serait minimale - les commandes C++ devraient être allouées dans la pile (stack), supprimant ainsi le besoin du mot clé new.

SI les usagers ne souhaitent pas utiliser les décorateurs andThen, alongWith, raceWith, et deadlineWith pour déclarer des groupes de commandes, mais souhaitent toujours réduire la verbosité par rapport à l’appel des constructeurs, la classe CommandGroupBase contient quatre méthodes statiques déjà programmées pour déclarer les groupes de commandes : sequence(), parallel(), race(), et deadline(). Lorsqu’ils sont utilisés à partir d’une sous-classe de groupe de commandes ou en combinaison avec l’importation import static, ceux-ci deviennent extrêmement concis et aident grandement à la composition de commandes:

public class ExampleSequence extends SequentialCommandGroup {

  // Will run a FooCommand, and then a race between a BarCommand and a BazCommand
  public ExampleSequence() {
    addCommands(
        new FooCommand(),
        race(
            new BarCommand(),
            new BazCommand()
        )
    );
  }

}