Structurer un projet de robot orienté commande

Alors que les utilisateurs sont libres d’utiliser les bibliothèques orientées commande comme ils le veulent (et les utilisateurs avancés sont encouragés à le faire), les nouveaux utilisateurs pourraient avoir besoin de conseils sur la manière de structurer un projet de robot orienté commande de base.

Un modèle standard pour un projet de robot orienté commande est inclus dans le dépôt d’exemples WPILib (Java, C++). Cette section guidera les utilisateurs à travers la structure de ce modèle.

Le package/répertoire racine contient généralement quatre classes :

Main, qui est l’application principale du robot (Java seulement). Les nouveaux utilisateurs ne devraient pas toucher à cette classe. Robot, qui est responsable du flux de contrôle principal du code robot. RobotContainer, qui contient des sous-systèmes et des commandes du robot, et c’est là que l’essentielle de la configuration déclarative du robot (par exemple la liaison des boutons avec les commandes) est effectuée. Constants, qui renferme des constantes accessibles globalement, donc à utiliser dans l’ensemble du programme du robot.

Le répertoire racine contiendra aussi deux sous-packages/sous-répertoires : Subsystems contiendra toutes les classes sous-systèmes définies par l’utilisateur. Commands contiendra tout les classes commandes définies par l’utilisateur.

Robot

Alors que Robot (Java, C++ (En-tête), C++ (Source)) est responsable du flux de contrôle du programme et la programmation orientée commande est un paradigme déclaratif conçu pour minimiser l’attention que l’utilisateur doit porter au flux de contrôle du programme explicite, la classe Robot d’un projet orienté commande devrait être presque vide. Cependant, il y a quelques éléments importants qui doivent être inclus.

22  /**
23   * This function is run when the robot is first started up and should be used for any
24   * initialization code.
25   */
26  @Override
27  public void robotInit() {
28    // Instantiate our RobotContainer.  This will perform all our button bindings, and put our
29    // autonomous chooser on the dashboard.
30    m_robotContainer = new RobotContainer();
31  }

En Java, une instance de RobotContainer devrait être construite dans la méthode robotInit() - cela est important, car l’essentiel de la configuration déclarative du robot sera appelé depuis le constructeur RobotContainer.

En C++, ce n’est pas nécessaire, car RobotContainer est un membre valeur et va être construit durant la construction de Robot.

33  /**
34   * This function is called every 20 ms, no matter the mode. Use this for items like diagnostics
35   * that you want ran during disabled, autonomous, teleoperated and test.
36   *
37   * <p>This runs after the mode specific periodic functions, but before LiveWindow and
38   * SmartDashboard integrated updating.
39   */
40  @Override
41  public void robotPeriodic() {
42    // Runs the Scheduler.  This is responsible for polling buttons, adding newly-scheduled
43    // commands, running already-scheduled commands, removing finished or interrupted commands,
44    // and running subsystem periodic() methods.  This must be called from the robot's periodic
45    // block in order for anything in the Command-based framework to work.
46    CommandScheduler.getInstance().run();
47  }
11/**
12 * This function is called every 20 ms, no matter the mode. Use
13 * this for items like diagnostics that you want to run during disabled,
14 * autonomous, teleoperated and test.
15 *
16 * <p> This runs after the mode specific periodic functions, but before
17 * LiveWindow and SmartDashboard integrated updating.
18 */
19void Robot::RobotPeriodic() {
20  frc2::CommandScheduler::GetInstance().Run();
21}

L’inclusion de l’appel CommandScheduler.getInstance().run() dans la méthode robotPeriodic() est essentielle; sans cela, le planificateur n’exécutera pas les commandes programmées. Puisque TimedRobot s’exécute à la fréquence de la boucle principale (50Hz par défaut), c’est la fréquence à laquelle les commandes périodiques et les méthodes de sous-systèmes s’exécuteront. Il n’est pas recommandé pour les utilisateurs novices d’appeler cette méthode ailleurs dans leur code.

56  /** This autonomous runs the autonomous command selected by your {@link RobotContainer} class. */
57  @Override
58  public void autonomousInit() {
59    m_autonomousCommand = m_robotContainer.getAutonomousCommand();
60
61    // schedule the autonomous command (example)
62    if (m_autonomousCommand != null) {
63      m_autonomousCommand.schedule();
64    }
65  }
33/**
34 * This autonomous runs the autonomous command selected by your {@link
35 * RobotContainer} class.
36 */
37void Robot::AutonomousInit() {
38  m_autonomousCommand = m_container.GetAutonomousCommand();
39
40  if (m_autonomousCommand) {
41    m_autonomousCommand->Schedule();
42  }
43}

La méthode autonomousInit() planifie une commande autonome retournée par l’instance de RobotContainer. La logique pour sélectionner quelle commande autonome sera exécutée peut être gérée à l’intérieur de RobotContainer.

71  @Override
72  public void teleopInit() {
73    // This makes sure that the autonomous stops running when
74    // teleop starts running. If you want the autonomous to
75    // continue until interrupted by another command, remove
76    // this line or comment it out.
77    if (m_autonomousCommand != null) {
78      m_autonomousCommand.cancel();
79    }
80  }
46void Robot::TeleopInit() {
47  // This makes sure that the autonomous stops running when
48  // teleop starts running. If you want the autonomous to
49  // continue until interrupted by another command, remove
50  // this line or comment it out.
51  if (m_autonomousCommand) {
52    m_autonomousCommand->Cancel();
53  }
54}

La méthode teleopInit() annulera toutes les commandes du mode autonome qui sont en train d’être exécutées. C’est généralement une bonne pratique.

Les utilisateurs avancés peuvent ajouter du code additionnel aux méthodes init et periodic comme ils l’entendent; cependant, il faut noter que le fait d’inclure de grandes quantités de code robot impératif dans Robot.java va à l’encontre de la philosophie de conception déclarative du paradigme orienté commande, et peut entraîner un code structuré de manière confuse/désorganisée.

RobotContainer

Cette classe (Java, C++ (En-tête), C++ (Source)) est où la plupart de la configuration d’un robot orienté commande sera effectuée. Dans cette classe, on définit les sous-systèmes et commandes du robot, lie ces commandes aux événements déclencheurs (tels que les boutons), et spécifie quelle commande sera exécutée dans la routine autonome. Il y a quelques aspects de cette classe pour lesquels de nouveaux utilisateurs pourraient avoir besoin d’explications:

23  private final ExampleSubsystem m_exampleSubsystem = new ExampleSubsystem();
32  ExampleSubsystem m_subsystem;

Notez que les sous-systèmes sont déclarés comme des champs privés dans RobotContainer. Ceci est en contraste notable avec l’incarnation précédente du cadre orienté commande, mais est beaucoup plus aligné sur les meilleures pratiques orientées objet convenues. Si les sous-systèmes sont déclarés comme des variables globales, cela permet à l’utilisateur d’y accéder n’importe où dans le code. Alors que cela peut faciliter certaines choses (par exemple, il n’y aurait pas besoin de passer les sous-systèmes aux commandes pour que ces commandes puissent y avoir accès), cela rend aussi le flux de contrôle du programme beaucoup plus difficile à suivre, car on ne peut pas voir immédiatement quelles parties du code pourraient changer ou pourraient être changées par d’autres parties du code. Cela peut également empêcher le système de gestion de ressources de faire son travail, car la facilité d’accès rend facile pour un utilisateur de faire un appel à une méthode de sous-systèmes hors des commandes gérées par les ressources.

61    return Autos.exampleAuto(m_exampleSubsystem);
34  return autos::ExampleAuto(&m_subsystem);

Puisque les sous-systèmes sont déclarés comme des membres privés, ils doivent être passés explicitement aux commandes (un modèle nommé « dependency injection ») pour que les commandes puissent appeler leurs méthodes. Cela est fait ici avec ExampleCommand, qui se fait passer un pointeur vers un ExampleSubsystem.

35  /**
36   * Use this method to define your trigger->command mappings. Triggers can be created via the
37   * {@link Trigger#Trigger(java.util.function.BooleanSupplier)} constructor with an arbitrary
38   * predicate, or via the named factories in {@link
39   * edu.wpi.first.wpilibj2.command.button.CommandGenericHID}'s subclasses for {@link
40   * CommandXboxController Xbox}/{@link edu.wpi.first.wpilibj2.command.button.CommandPS4Controller
41   * PS4} controllers or {@link edu.wpi.first.wpilibj2.command.button.CommandJoystick Flight
42   * joysticks}.
43   */
44  private void configureBindings() {
45    // Schedule `ExampleCommand` when `exampleCondition` changes to `true`
46    new Trigger(m_exampleSubsystem::exampleCondition)
47        .onTrue(new ExampleCommand(m_exampleSubsystem));
48
49    // Schedule `exampleMethodCommand` when the Xbox controller's B button is pressed,
50    // cancelling on release.
51    m_driverController.b().whileTrue(m_exampleSubsystem.exampleMethodCommand());
52  }
19void RobotContainer::ConfigureBindings() {
20  // Configure your trigger bindings here
21
22  // Schedule `ExampleCommand` when `exampleCondition` changes to `true`
23  frc2::Trigger([this] {
24    return m_subsystem.ExampleCondition();
25  }).OnTrue(ExampleCommand(&m_subsystem).ToPtr());
26
27  // Schedule `ExampleMethodCommand` when the Xbox controller's B button is
28  // pressed, cancelling on release.
29  m_driverController.B().WhileTrue(m_subsystem.ExampleMethodCommand());
30}

As mentioned before, the RobotContainer() constructor is where most of the declarative setup for the robot should take place, including button bindings, configuring autonomous selectors, etc. If the constructor gets too « busy, » users are encouraged to migrate code into separate subroutines (such as the configureBindings() method included by default) which are called from the constructor.

54  /**
55   * Use this to pass the autonomous command to the main {@link Robot} class.
56   *
57   * @return the command to run in autonomous
58   */
59  public Command getAutonomousCommand() {
60    // An example command will be run in autonomous
61    return Autos.exampleAuto(m_exampleSubsystem);
62  }
63}
32frc2::CommandPtr RobotContainer::GetAutonomousCommand() {
33  // An example command will be run in autonomous
34  return autos::ExampleAuto(&m_subsystem);
35}

Finalement, la méthode getAutonomousCommand() fournit un moyen pratique à l’utilisateur pour envoyer sa commande autonome sélectionnée à la classe Robot principale (qui a besoin d’y accèder pour la planifier au début de la période autonome).

Constants

La classe Constants (Java, C++ (En-tête)) (en C++, ce n’est pas une classe, mais simplement un fichier en-tête dans lequel plusieurs espaces de noms sont définis) est où les constantes du robot accessibles globalement (comme les vitesses, les facteurs de conversion d’unités, les gains PID, les ports de capteurs/moteurs, etc.) peuvent être stockées. Il est recommandé que les utilisateurs séparent ces constantes en classes internes individuelles qui correspondent aux sous-systèmes ou aux modes du robot pour garder le nom des variables le plus court possible.

En Java, toutes les constantes devraient être déclarées public static final pour qu’elles soient accessibles globalement et ne puissent pas être changées. En C++, toutes les constantes devraient être constexpr.

Pour des exemples plus illustratifs de ce à quoi devrait ressembler une classe Constants en pratique, voir les classes des projets exemples orientés commande :

En java, il est recommandé que les constantes soient utilisées à partir d’autres classes par l’importation statique de la classe intérieure nécessaire. Une déclaration import static importe l’espace de nom statique d’une classe dans la classe dans laquelle vous travaillez pour que les constantes static puissent être référencées directement comme si elles étaient définies dans cette classe. En C++, la même chose peut être faite avec using namespace:

import static edu.wpi.first.wpilibj.templates.commandbased.Constants.OIConstants.*;
using namespace OIConstants;

Subsystems

Les sous-systèmes définis par l’utilisateur devraient entrer dans ce package/répertoire.

Commands

Les commandes définies par l’utilisateur devraient entrer dans cet package/répertoire.