Utilisation de la manette de jeu

Disposition de la manette de jeu

Disposition des boutons de la manette Logitech F310.
Présentation de l'image de la manette de jeu Xbox 360.
Présentation de l'image de la manette de jeu PS4/Etpark.

Note

L1 est left_bumper, L2 est left_trigger, R1/R2 sont les équivalents de droite.

Alias des boutons

Étant donné que les manettes de type PS4 et Xbox sont toutes deux autorisées par FTC®, il existe des « alias » dans le SDK de FTC <https://github.com/OpenFTC/Extracted-RC/blob/c960dd7de34d49a66c00a345636175392f936b9e/RobotCore/src/main/java/com/qualcomm/robotcore/hardware/Gamepad.java#L892>`_ entre le style PS4 et le style Xbox en ce qui concerne le nom des boutons.

PS4

Xbox

cercle

b

croix

a

triangle

y

carré

x

partage

retour

options

début

ps

guide

Entrées Booléennes

Les OpModes TeleOp sont généralement écrits dans un style itératif, avec une boucle qui contient du code appelé à plusieurs reprises. Selon ce paradigme, un traitement simple de l’entrée de l’utilisateur pourrait ressembler à ce qui suit

if (gamepad1.a) {
    motor.setPower(1);
}
else {
    motor.setPower(0);
}

Dans cette situation, le pilote fait ce qu’il veut : tant que le bouton est maintenu, la puissance du moteur est réglée sur 1, et sinon la puissance est réglée sur 0. Comme le fait d’écrire plusieurs fois la même puissance à un moteur n’a aucun effet sur le comportement du moteur, cela fonctionne parfaitement bien. Cependant, des problèmes se posent lorsque l’on veut faire quelque chose une seule fois lorsqu’on appuie sur un bouton. Par exemple, il est tentant d’écrire quelque chose comme ceci pour obtenir un appui sur a ou b pour ajuster un servo.

if (gamepad1.a) {
    servo.setPosition(servo.getPosition()+0.1);
}
else if (gamepad1.b) {
    servo.setPosition(servo.getPosition()-0.1);
}

Cependant, le comportement sera imprévisible, car à chaque fois que le bouton est pressé, la méthode setPosition sera appelée plusieurs fois, car la fréquence de la boucle change, tout comme la durée de la pression sur le bouton. Il existe quelques techniques pour éviter cela, mais elles nécessitent toutes de comparer l’état de la manette de jeu à l’état de la manette de jeu dans la boucle précédente ; il est donc nécessaire de le stocker.

Stockage de l’état de la manette de jeu

Bien que l’état précédent de chaque entrée du gamepad puisse être stocké individuellement dans une variable, par exemple boolean previousA, cela devient très vite ennuyeux. Heureusement, le SDK de FTC fournit un moyen de copier les états du gamepad, avec gamepad.copy(gamepadToCopy).

Note

En plus de stocker l’état de la manette de jeu pour l’itération précédente de la boucle, l’état de la manette de jeu pour l’itération actuelle de la boucle est également stocké. Ceci est nécessaire car si l’état d’un bouton était toujours lu depuis gamepad1/gamepad2, il pourrait changer entre la lecture de la valeur et le stockage de la valeur précédente. C’est parce que gamepad1`/`gamepad2 se mettent à jour simultanément pour LinearOpMode, et peuvent donc changer pendant une itération de la boucle.

Dans un programme TeleOp basé sur le LinearOpMode, le stockage de l’état actuel et de l’état précédent de la manette de jeu pourrait ressembler à ce qui suit :

public void runOpMode() {
    // By setting these values to new Gamepad(), they will default to all
    // boolean values as false and all float values as 0
    Gamepad currentGamepad1 = new Gamepad();
    Gamepad currentGamepad2 = new Gamepad();

    Gamepad previousGamepad1 = new Gamepad();
    Gamepad previousGamepad2 = new Gamepad();

    // other initialization code goes here

    while (opModeIsActive()) {
        // Store the gamepad values from the previous loop iteration in
        // previousGamepad1/2 to be used in this loop iteration.
        // This is equivalent to doing this at the end of the previous
        // loop iteration, as it will run in the same order except for
        // the first/last iteration of the loop.
        previousGamepad1.copy(currentGamepad1);
        previousGamepad2.copy(currentGamepad2);

        // Store the gamepad values from this loop iteration in
        // currentGamepad1/2 to be used for the entirety of this loop iteration.
        // This prevents the gamepad values from changing between being
        // used and stored in previousGamepad1/2.
        currentGamepad1.copy(gamepad1);
        currentGamepad2.copy(gamepad2);

        // Main teleop loop goes here
    }
 }

Détecteur de front montant

Pourquoi l’appelle-t-on détecteur de front montant ?

Un front de signal est une transition dans un signal numérique. Dans ce cas, le signal numérique est l’entrée de la manette de jeu, qui est basse lorsqu’elle n’est pas maintenue et haute lorsqu’elle est maintenue. Le front montant est la transition de bas en haut, et le front descendant est la transition de haut en bas.

Diagramme d'un front montant/descendant d'une onde carrée

La technique la plus couramment utilisée est un détecteur de front montant. Elle permet d’exécuter le code une seule fois lorsque le bouton est initialement enfoncé, plutôt que de le faire à chaque boucle pendant qu’il est maintenu enfoncé. Il vérifie que le bouton est actuellement enfoncé, mais qu’il ne l’a pas été dans la boucle précédente. Par exemple, à l’intérieur d’une boucle TeleOp :

if (currentGamepad1.a && !previousGamepad1.a) {
    servo.setPosition(servo.getPosition() + 0.1);
}

Ceci augmentera la position du servo de 0.1 exactement une fois par pression sur a.

Détecteur de front descendant

Une technique très similaire est le détecteur de front descendant. Elle permet d’exécuter le code une seule fois lorsque le bouton est relâché, au lieu de le faire à chaque boucle lorsque le bouton est maintenu enfoncé. Il fonctionne en vérifiant que le bouton n’est actuellement pas enfoncé, mais qu’il l’a été dans la boucle précédente. Par exemple, à l’intérieur d’une boucle TeleOp :

if (!currentGamepad1.b && previousGamepad1.b) {
    servo.setPosition(servo.getPosition() - 0.1);
}

Ceci diminuera la position du servo de 0.1 exactement une fois par relâchement de b.

Note

Un bouton peut exécuter un code différent sur le front montant et le front descendant. Cette fonction est principalement utile pour les interactions relativement complexes et n’est donc pas démontrée ici.

Bascules

Un cas d’utilisation courant des détecteurs de fronts montants est la commande de bascules. Les bascules peuvent être utilisées pour que le robot dispose d’un bouton lui permettant de passer d’un état à l’autre, par exemple pour allumer et éteindre une prise. Cela peut se faire pour n’importe quel nombre d’états, mais le plus souvent entre deux états. Pour réaliser un basculement entre deux états, un détecteur de front montant est utilisé pour placer un booléen à son opposé, puis ce booléen est utilisé pour contrôler une action.

Exemple

Dans le code d’initialisation :

boolean intakeToggle = false;

À l’intérieur de la boucle TeleOp correspondante :

// Rising edge detector
if (currentGamepad1.a && !previousGamepad1.a) {
    // This will set intakeToggle to true if it was previously false
    // and intakeToggle to false if it was previously true,
    // providing a toggling behavior.
    intakeToggle = !intakeToggle;
}

// Using the toggle variable to control the robot.
if (intakeToggle) {
    intakeMotor.setPower(1);
}
else {
    intakeMotor.setPower(0);
}

Cela activera la prise d’air lorsque a est pressé, et la laissera allumée jusqu’à ce que l’on appuie à nouveau sur cette touche.

Note

Moins le pilote doit se préoccuper de l’état du robot, moins il risque de se tromper. Étant donné que les bascules signifient qu’un bouton fait des choses différentes à chaque fois qu’il est enfoncé, envisagez d’autres solutions. C’est particulièrement vrai pour les boutons à bascule ayant plus de deux états.

Retour d’information sur la manette de jeu

Le retour d’information de la manette de jeu (c’est-à-dire le grondement et le contrôle des LED) peut être un moyen utile pour les robots de communiquer leur état à un pilote pendant un match. Le degré de prise en charge de cette fonctionnalité par les manettes de jeu légales varie :

  • Tumulte : aucun

  • Contrôle des LED : aucun

  • Rumble : grand (whomp whomp) et petit (bzzz)

  • Contrôle des LED : aucun

  • Rumble : grand (whomp whomp) et petit (bzzz)

  • Contrôle des LED : contrôle de la barre lumineuse RVB (couleur unie ou motif)

  • Rumble : contient les deux moteurs de rumble gauche et droit, mais les deux semblent n’avoir qu’un faible poids (bzzz).

  • Contrôle des LED : contrôle des LED RVB (couleur unie ou motif). Les LED sont assez petites et peu lumineuses et peuvent ne pas être un bon choix.

Astuce

Le retour d’information de la manette de jeu peut être utilisé pour avertir les pilotes du début de la partie finale, du chargement du ramasseur, de la fin de l’alignement automatique, etc.

Vibration

Le SDK fournit une API simple ou plus complexe pour contrôler le bruit selon le cas d’utilisation souhaité.

Note

  • La puissance de vibration est spécifiée sous la forme d’une valeur à virgule flottante comprise entre [0,0 et 1,0].

  • La durée du rumble est spécifiée en millisecondes sous la forme d’un entier. La constante Gamepad.RUMBLE_DURATION_CONTINUOUS peut être utilisée pour indiquer que le grondement doit continuer jusqu’à ce qu’une autre action de grondement soit commandée.

Note

Toutes les actions de vibration sont réalisées de manière asynchrone, c’est-à-dire que les appels de fonction sont renvoyés immédiatement. Tout appel à une API de vibration remplacera immédiatement toute action de vibration en cours. En d’autres termes, si vous ordonnez à une manette de jeu de vibrer pendant 750 ms, puis ordonnez immédiatement un vibrage pendant 250 ms, la manette de jeu vibrera pendant 250 ms à partir du moment où la seconde commande a été émise.

API simple

La façon la plus simple de commander le grondement est de faire gronder le moteur 1 à 100 % de sa puissance pendant une durée déterminée :

gamepad1.rumble(int durationMs);

Si l’on souhaite contrôler à la fois les deux moteurs de vibration et l’intensité des vibrations:

gamepad1.rumble(double rumble1, double rumble2, int durationMs);

Faire vibrer une manette de jeu pendant un certain nombre de « blips » (la notion de « blip » étant prédéfinie par le SDK) :

gamepad1.rumbleBlips(int numBlips);

Fonctions d’assistance :

La fonction public boolean isRumbling() permet de savoir si une action de vibration est en cours sur cette manette de jeu. Le contrôleur du robot ne sait pas avec certitude si une action de vibration est en cours ou non, car une fois que la commande est envoyée à la station de pilotage, celle-ci se charge d’exécuter les effets de la manette de jeu et le contrôleur du robot n’a plus rien à faire.

La fonction public void stopRumble() peut être utilisée pour arrêter toute action de vibration en cours pour une manette de jeu (peut-être plus utile en conjonction avec une vibration de durée continue).

API avancée

Pour créer un comportement de vibration plus avancé, un RumbleEffect peut être créé, qui est composé de « Steps » qui spécifient la puissance et la durée à laquelle chaque moteur de vibration doit fonctionner. Lorsqu’une manette de jeu est commandée pour exécuter un RumbleEffect, elle exécute chaque « Pas » en série.

Pour créer un RumbleEffect, la classe RumbleEffect.Builder doit être utilisée. Le constructeur fournit la fonction addStep(double rumble1, double rumble2, int durationMs) qui est utilisée pour ajouter un pas à la séquence, et la fonction build() pour créer un RumbleEffect à partir de la séquence de pas.

L’utilisation d’une instance anonyme de la classe builder est la façon la plus propre de construire un RumbleEffect, par exemple :

Gamepad.RumbleEffect effect = new Gamepad.RumbleEffect.Builder()
       .addStep(0.0, 1.0, 500)  //  Rumble right motor 100% for 500 mSec
       .addStep(0.0, 0.0, 300)  //  Pause for 300 mSec
       .addStep(1.0, 0.0, 250)  //  Rumble left motor 100% for 250 mSec
       .addStep(0.0, 0.0, 250)  //  Pause for 250 mSec
       .addStep(1.0, 0.0, 250)  //  Rumble left motor 100% for 250 mSec
       .build();

Une fois qu’un RumbleEffect a été créé, il peut être envoyé à une manette de jeu en appelant :

gamepad1.runRumbleEffect(effect);

Contrôle des LED

Note

  • L’intensité de la composante RVB des DEL (c’est-à-dire le rouge, le vert et le bleu) est spécifiée sous la forme d’une valeur à virgule flottante dans la plage [0,0, 1,0].

  • La durée de la DEL est spécifiée en millisecondes sous la forme d’un entier. La constante Gamepad.LED_DURATION_CONTINUOUS peut être utilisée pour indiquer que la DEL doit rester dans la couleur spécifiée jusqu’à ce qu’une autre commande soit émise.

Note

Toutes les actions des DEL sont effectuées de manière « asynchrone », c’est-à-dire que les appels de fonction sont renvoyés immédiatement. Tout appel à une API de DEL remplacera immédiatement toute action de DEL en cours. Ainsi, si vous commandez une DEL verte pendant 750 ms, puis immédiatement une DEL violette pendant 250 ms, la DEL s’allumera en violet pendant 250 ms à partir du moment où la seconde commande a été émise.

Pour définir la couleur de la LED pour une durée fixe :

gamepad1.setLedColor(double r, double g, double b, int durationMs);

Pour créer un comportement plus avancé des DELs, un LedEffect peut être créé, qui est composé de « Steps » qui spécifient une couleur et la durée pour laquelle elle doit être maintenue. Lorsqu’une manette de jeu est commandée pour exécuter un LedEffect, elle exécute chaque « Pas » en série.

Pour créer un LedEffect, la classe LedEffect.Builder doit être utilisée. Le constructeur fournit la fonction addStep(double r, double g, double b, int durationMs) qui est utilisée pour ajouter un pas à la séquence, et la fonction build() pour créer un LedEffect à partir de la séquence de pas.

L’utilisation d’une instance anonyme de la classe constructeur est la façon la plus propre de construire un LedEffect, par exemple :

Gamepad.LedEffect rgbEffect = new Gamepad.LedEffect.Builder()
       .addStep(1, 0, 0, 250) // Show red for 250ms
       .addStep(0, 1, 0, 250) // Show green for 250ms
       .addStep(0, 0, 1, 250) // Show blue for 250ms
       .addStep(1, 1, 1, 250) // Show white for 250ms
       .build();

Une fois qu’un LedEffect a été créé, il peut être envoyé à une manette de jeu en appelant :

gamepad1.runLedEffect(rgbEffect);