Estructuración de un proyecto de robot basado en comandos

Si bien los usuarios son libres de usar las bibliotecas basadas en comandos como lo deseen (y se recomienda a los usuarios avanzados que lo hagan), los nuevos usuarios pueden necesitar orientación sobre cómo estructurar un proyecto de robot básico basado en comandos.

En el repositorio de ejemplos de WPILib se incluye una plantilla estándar para un proyecto de robot basado en comandos (Java, C++). Esta sección guiará a los usuarios a través de la estructura de esta plantilla.

El directorio / paquete raíz generalmente contendrá cuatro clases:

Main, que es la aplicación principal del robot (solo Java). Los nuevos usuarios * no deben * tocar esta clase. Robot, que es responsable del flujo de control principal del código del robot. RobotContainer, que contiene los subsistemas y comandos del robot, y es donde se realiza la mayor parte de la configuración declarativa del robot (por ejemplo, enlaces de botones). Constants, que contiene constantes accesibles globalmente para su uso en todo el robot.

El directorio raíz también contendrá dos subpaquetes / subdirectorios: Subsystems contiene todas las clases de subsistemas definidas por el usuario.``Commands`` contiene todas las clases de comandos definidas por el usuario.

Robot

Como Robot (Java, C++ (Header), C++ (Source)) es responsable del flujo de control del programa, y basado en comandos es un paradigma declarativo diseñado para minimizar La cantidad de atención que el usuario debe prestar al flujo de control explícito del programa, la clase Robot de un proyecto basado en comandos debe estar prácticamente vacía. Sin embargo, hay algunas cosas importantes que deben incluirse

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, se debe construir una instancia de RobotContainer durante el método robotInit() ; esto es importante, ya que la mayor parte de la configuración declarativa del robot se llamará desde el constructor RobotContainer.

En C++, esto no es necesario ya que RobotContainer es un miembro de valor y se construirá durante la construcción 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}

La inclusión de la llamada CommandScheduler.getInstance().run() en el método robotPeriodic() es esencial; sin esta llamada, el programador no ejecutará ningún comando programado. Dado que TimedRobot se ejecuta con una frecuencia de bucle principal predeterminada de 50 Hz, esta es la frecuencia con la que se llamarán los comandos periódicos y los métodos de subsistema. No se recomienda que los nuevos usuarios llamen a este método desde cualquier otro lugar de su código.

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}

El método autonomousInit() programa un comando autónomo devuelto por la instancia de RobotContainer. La lógica para seleccionar qué comando autónomo ejecutar se puede manejar dentro 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}

El método teleopInit() cancela cualquier comando autónomo que aún se esté ejecutando. En general, esta es una buena práctica.

Los usuarios avanzados tienen la libertad de agregar código adicional a los diversos métodos de inicio y periódicos como mejor les parezca; sin embargo, debe tenerse en cuenta que incluir grandes cantidades de código de robot imperativo en Robot.java es contrario a la filosofía de diseño declarativo del paradigma basado en comandos y puede resultar en un código desorganizado o estructurado de manera confusa.

RobotContainer

Esta clase (Java <https://github.com/wpilibsuite/allwpilib/blob/main/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/templates/commandbased/RobotContainer.java> __, C ++ (Encabezado) <https://github.com/wpilibsuite/allwpilib/blob/main/wpilibcExamples/src/main/cpp/templates/commandbased/include/RobotContainer.h> __, C ++ (Fuente) <https://github.com/wpilibsuite/allwpilib/blob/main/wpilibcExamples/src/main/cpp/templates/commandbased/cpp/RobotContainer.cpp> __) es donde se llevará a cabo la mayor parte de la configuración de su robot basado en comandos. En esta clase, definirá los subsistemas y comandos de su robot, vinculará esos comandos a eventos de activación (como botones) y especificará qué comando ejecutará en su rutina autónoma. Hay algunos aspectos de esta clase para los que los nuevos usuarios pueden querer explicaciones:

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

Tenga en cuenta que los subsistemas se declaran como campos privados en RobotContainer. Esto está en marcado contraste con la encarnación anterior del marco basado en comandos, pero está mucho más alineado con las mejores prácticas orientadas a objetos acordadas. Si los subsistemas se declaran como variables globales, permite al usuario acceder a ellos desde cualquier parte del código. Si bien esto puede facilitar ciertas cosas (por ejemplo, no habría necesidad de pasar subsistemas a comandos para que esos comandos accedan a ellos), hace que el flujo de control del programa sea mucho más difícil de rastrear, ya que no es inmediato. obvio qué partes del código pueden cambiar o cambiarse por qué otras partes del código. Esto también evita la capacidad del sistema de administración de recursos para hacer su trabajo, ya que la facilidad de acceso facilita que los usuarios realicen accidentalmente llamadas conflictivas a métodos del subsistema fuera de los comandos administrados por recursos.

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

Dado que los subsistemas se declaran como miembros privados, deben pasarse explícitamente a los comandos (un patrón llamado «inyección de dependencia») para que esos comandos llamen a sus métodos. Esto se hace aquí con ExampleCommand, que se pasa un puntero a 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}

Finalmente, el método getAutonomousCommand() proporciona una forma conveniente para que los usuarios envíen su comando autónomo seleccionado a la clase principal de Robot (que necesita acceso a él para programarlo cuando se inicie autónomo).

Constants

La clase Constants (Java, C++ (Header)) (en C++ no es una clase, sino simplemente un archivo de cabecera en el que se definen varios espacios de nombres) es donde pueden almacenarse las constantes del robot de acceso global (como las velocidades, los factores de conversión de unidades, las ganancias PID y los puertos de sensores/motores). Se recomienda que los usuarios separen estas constantes en clases internas individuales correspondientes a los subsistemas o modos del robot, para mantener los nombres de las variables más cortos.

En Java, todas las constantes deben declararse como public static final para que sean accesibles globalmente y no se puedan cambiar. En C++, todas las constantes deben ser constexpr.

Para obtener más ejemplos ilustrativos de cómo debería verse una clase de constants en la práctica, consulte los de varios proyectos de ejemplo basados en comandos:

En Java, se recomienda que las constantes se utilicen de otras clases importando estáticamente la clase interna necesaria. Una declaración de import static importa el espacio de nombres estático de una clase en la clase en la que está trabajando, de modo que cualquier constante static pueda ser referenciada directamente como si se hubiera definido en esa clase. En C++, el mismo efecto se puede lograr con using namespace:

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

Subsistemas

Los subsistemas definidos por el usuario deben ir en este paquete / directorio.

Comandos

Los comandos definidos por el usuario deben ir en este paquete / directorio.