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ée commande (Java, C++):

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import edu.wpi.first.wpilibj2.command.SubsystemBase;

public class ExampleSubsystem extends SubsystemBase {
  /**
   * Creates a new ExampleSubsystem.
   */
  public ExampleSubsystem() {

  }

  @Override
  public void periodic() {
    // This method will be called once per scheduler run
  }
}

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++):

 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems;

import edu.wpi.first.wpilibj.DoubleSolenoid;
import edu.wpi.first.wpilibj2.command.SubsystemBase;

import edu.wpi.first.wpilibj.examples.hatchbottraditional.Constants.HatchConstants;

import static edu.wpi.first.wpilibj.DoubleSolenoid.Value.kForward;
import static edu.wpi.first.wpilibj.DoubleSolenoid.Value.kReverse;

/**
 * A hatch mechanism actuated by a single {@link DoubleSolenoid}.
 */
public class HatchSubsystem extends SubsystemBase {
  private final DoubleSolenoid m_hatchSolenoid =
      new DoubleSolenoid(HatchConstants.kHatchSolenoidModule, HatchConstants.kHatchSolenoidPorts[0],
                         HatchConstants.kHatchSolenoidPorts[1]);

  /**
   * Grabs the hatch.
   */
  public void grabHatch() {
    m_hatchSolenoid.set(kForward);
  }

  /**
   * Releases the hatch.
   */
  public void releaseHatch() {
    m_hatchSolenoid.set(kReverse);
  }
}

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);