Les sous-systèmes

Les sous-systèmes sont l’unité de base de l’organisation d’un robot dans le paradigme orienté commande. Un sous-système est une abstraction pour un ensemble de composants matériels du robot qui fonctionnent ensemble comme une unité. Les sous-systèmes encapsulent ces composants : ils les « cachent » du reste du code du robot (par exemple, les commandes) et limitent l’accès à ceux-ci, sauf par l’intermédiaire des méthodes publiques du sous-système. Restreindre ainsi l’accès permet d’avoir un seul endroit pratique où écrire certains bouts de code (comme la mise à l’échelle des valeurs de sortie des moteurs ou la vérification des interrupteurs de fin de course) qui pourraient autrement être dupliqués à plusieurs endroits si les éléments internes du sous-système étaient accessibles. Cela permet aussi d’isoler du reste du programme les modifications qui peuvent être apportées aux détails spécifiques du fonctionnement du sous-système (« l’implémentation »). Cela facilite grandement les modifications substantielles si (ou plutôt quand) les contraintes de conception changent.

Les sous-systèmes sont la base du système de gestion des ressources du CommandScheduler. Les commandes peuvent déclarer les ressources dont elles ont besoin en spécifiant les sous-systèmes avec lesquels elles interagissent. Le planificateur ne démarrera jamais en même temps plus d’une commande qui nécessite le même sous-système. Tenter de planifier une commande qui nécessite un sous-système qui est déjà utilisé entraînera sont l’interruption de la commande en cours d’exécution (si la commande a été planifiée comme étant interruptible), ou la commande nouvellement planifiée sera ignorée.

Les sous-systèmes peuvent être associés à des “commandes par défaut“ qui seront automatiquement planifiées lorsqu’aucune autre commande n’utilise actuellement le sous-système. Ceci est utile pour les actions continues de “fond” telles que le contrôle de l’entraînement du robot, ou le maintien d’un bras tenu à un point de consigne. Des fonctionnalités similaires peuvent être obtenues dans la méthode periodic() du sous-système, qui est exécutée une fois par itération du planificateur; les équipes devraient essayer d’être cohérentes dans leur code de base sur les fonctionnalités obtenues grâce à l’une ou l’autre de ces méthodes. Il existe également une méthode simulationPeriodic() qui est semblable à periodic(), sauf qu’elle n’est pas exécutée pendant la Simulation et peut être utilisée pour mettre à jour l’état du robot. Les sous-systèmes sont représentés dans la librairie orientée commande par l’interface Subsystem (Java, C++).

Créer un sous-système

La méthode recommandée pour créer un sous-système pour la plupart des utilisateurs est de dériver la classe abstraite SubsystemBase (Java, C++), comme on le voit dans le modèle orienté commande (Java, C++):

 7import edu.wpi.first.wpilibj2.command.SubsystemBase;
 8
 9public class ExampleSubsystem extends SubsystemBase {
10  /** Creates a new ExampleSubsystem. */
11  public ExampleSubsystem() {}
12
13  @Override
14  public void periodic() {
15    // This method will be called once per scheduler run
16  }
17
18  @Override
19  public void simulationPeriodic() {
20    // This method will be called once per scheduler run during simulation
21  }
22}

Cette classe contient quelques fonctionnalités pratiques en complément à l’interface Subsystem de base : elle appelle automatiquement la méthode register() dans son constructeur pour enregistrer le sous-système auprès du CommandScheduler (cela est nécessaire pour que la méthode periodic() soit exécutée par le CommandScheduler), et elle implémente aussi l’interface Sendable afin que le sous-système puisse être envoyé au tableau de bord (SmartDashboard et/ou Shuffleboard) et ainsi afficher les informations pertinentes sur son état.

Les utilisateurs avancés qui veulent plus de flexibilité peuvent simplement créer une classe qui implémente l’interface Subsystem.

Exemple de sous-système simple

À quoi pourrait ressembler un sous-système fonctionnel en pratique? Vous trouverez ci-dessous un mécanisme d’écoutille simple à actionnement pneumatique provenant du projet d’exemple HatchBot (Java, C++):

 5package edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems;
 6
 7import static edu.wpi.first.wpilibj.DoubleSolenoid.Value.kForward;
 8import static edu.wpi.first.wpilibj.DoubleSolenoid.Value.kReverse;
 9
10import edu.wpi.first.wpilibj.DoubleSolenoid;
11import edu.wpi.first.wpilibj.PneumaticsModuleType;
12import edu.wpi.first.wpilibj.examples.hatchbottraditional.Constants.HatchConstants;
13import edu.wpi.first.wpilibj2.command.SubsystemBase;
14
15/** A hatch mechanism actuated by a single {@link DoubleSolenoid}. */
16public class HatchSubsystem extends SubsystemBase {
17  private final DoubleSolenoid m_hatchSolenoid =
18      new DoubleSolenoid(
19          PneumaticsModuleType.CTREPCM,
20          HatchConstants.kHatchSolenoidPorts[0],
21          HatchConstants.kHatchSolenoidPorts[1]);
22
23  /** Grabs the hatch. */
24  public void grabHatch() {
25    m_hatchSolenoid.set(kForward);
26  }
27
28  /** Releases the hatch. */
29  public void releaseHatch() {
30    m_hatchSolenoid.set(kReverse);
31  }
32}

Remarquez que le sous-système cache au reste du code la présence du DoubleSolenoid (il est déclaré private), et expose plutôt publiquement deux actions descriptives de plus haut niveau: grabHatch() et releaseHatch(). C’est extrêmement important que les « détails d’implémentation » comme le solénoïde double soient « cachés » de cette manière : cela garantit que le code hors du sous-système ne mettra jamais le solénoïde dans un état inattendu. Cela permet également à l’utilisateur de changer l’implémentation (par exemple, un moteur pourrait être utilisé au lieu d’un système pneumatique) sans qu’aucun code à l’extérieur du sous-système ne soit obligé d’être modifié avec lui.

Configurer les commandes par défaut

Note

Dans la bibliothèque orientée commande C++, le CommandScheduler possède les objets des commandes par défaut - par conséquent, l’objet passé à la méthode SetDefaultCommand() sera soit déplacé s’il s’agit d’une rvalue, ou bien copié s’il s’agit d’une lvalue (explication sur les rvalues et les lvalues). Les exemples ci-dessous garantissent que la sémantique de déplacement est utilisée par conversion en rvalue avec std::move().

Les « commandes par défaut » sont des commandes exécutées automatiquement lorsqu’un sous-système n’est pas utilisé par une autre commande.

Configurer la commande par défaut d’un sous-système est très facile : il faut simplement appeler la méthode CommandScheduler.getInstance().setDefaultCommand(Subsystem, Command) ou, encore plus simple, appeler la méthode setDefaultCommand(Command) de l’interface Subsytem :

CommandScheduler.getInstance().setDefaultCommand(exampleSubsystem, exampleCommand);
exampleSubsystem.setDefaultCommand(exampleCommand);

Note

Une commande affectée comme commande par défaut pour un sous-système doit requérir ce sous-système.