Uso del Gamepad#

Disposición del Gamepad#

Diseño de imagen del gamepad de Logitech F310
Diseño de imagen del gamepad de Xbox 360.
Diseño de imagen del gamepad PS4/Etpark.

Nota

L1 es bumper izquierdo, L2 es gatillo izquierdo, R1/R2` son los equivalentes de la derecha.

Alias de los botones#

Dado que tanto los mandos estilo PS4 como los estilo Xbox son legales según FTC, existen aliases en el SDK de FTC entre la nomenclatura de botones estilo PS4 y estilo Xbox.

PS4

Xbox

círculo

b

cross

a

triángulo

y

cuadrado

x

compartir

volver

opciones

iniciar

ps

guía

Entradas booleanas/ Boolean Inputs#

Los OpModes de TeleOp se escriben generalmente en estilo iterativo, con un bucle que contiene código que se llama una y otra vez. Bajo este paradigma, un manejo simple de la entrada del usuario podría tener el siguiente aspecto

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

En esta situación, esto probablemente hace lo que el controlador quiere: mientras se mantiene pulsado el botón, la potencia del motor se establece en 1, y de lo contrario la potencia se establece en 0. Dado que escribir la misma potencia a un motor varias veces no tiene ningún efecto sobre el comportamiento del motor, esto funciona perfectamente bien. Sin embargo, surgen problemas cuando se quiere hacer algo una vez cuando se pulsa un botón. Por ejemplo, es tentador escribir algo como esto para obtener una pulsación de a o b para ajustar un servo.

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

Sin embargo, esto se comportará de forma impredecible, ya que cada vez que se pulse el botón, el método setPosition será llamado varias veces, ya que la frecuencia del bucle cambia al igual que la duración de la pulsación del botón. Hay algunas técnicas para evitar esto, sin embargo, todas requieren comparar el estado del gamepad con el estado del gamepad en el bucle anterior; por lo tanto, es necesario almacenarlo.

Almacenamiento del Estado del Gamepad#

Aunque el estado anterior de cada entrada del gamepad podría almacenarse individualmente en una variable, por ejemplo boolean previousA, esto se vuelve molesto muy rápidamente. Por suerte, el SDK de FTC proporciona una forma de copiar los estados del gamepad, con gamepad.copy(gamepadToCopy).

Nota

Además de almacenar el estado del gamepad para la iteración anterior del bucle, también se almacena el estado del gamepad para la iteración actual del bucle. Esto es necesario porque si el estado de un botón se leyera siempre de gamepad1/gamepad2, podría cambiar entre la lectura del valor y el almacenamiento del valor anterior. Esto se debe a que gamepad1/gamepad2 se actualizan concurrentemente para LinearOpMode, y por tanto pueden cambiar durante una iteración del bucle.

En un programa TeleOp basado en LinearOpMode, almacenar tanto el estado actual como el anterior del gamepad podría tener este aspecto:

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
    }
 }

Detector de Flanco Ascendente#

¿Por qué se llama detector de flanco ascendente?

Un flanco de señal es una transición en una señal digital. En este caso, la señal digital es la entrada del gamepad, que es baja cuando no se mantiene pulsada y alta cuando se mantiene pulsada. El flanco ascendente es la transición de bajo a alto, y el flanco descendente es la transición de alto a bajo.

Diagrama de un flanco de subida/bajada de una onda cuadrada

La técnica más utilizada es un detector de flanco ascendente. Permite que el código se ejecute sólo una vez cuando se pulsa inicialmente el botón, en lugar de cada bucle mientras se mantiene pulsado. Funciona comprobando que el botón está pulsado en ese momento, pero no lo estaba en el bucle anterior. Por ejemplo, dentro de un bucle TeleOp:

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

Esto incrementará la posición del servo en 0.1 exactamente una vez por cada pulsación de a.

Detector de Flanco Descendente#

Una técnica muy similar es un detector de flanco descendente. Permite que el código se ejecute sólo una vez cuando se suelta el botón, en lugar de cada bucle mientras se mantiene pulsado. Funciona comprobando que el botón no está pulsado en ese momento, pero que sí lo estuvo en el bucle anterior. Por ejemplo, dentro de un bucle TeleOp:

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

Esto disminuirá la posición del servo en 0.1 exactamente una vez por cada liberación de b.

Nota

Un botón puede ejecutar código diferente en el flanco ascendente y descendente. Esto es útil principalmente para interacciones bastante complejas, por lo que no se demuestra aquí.

Toggles#

Un caso de uso común para los detectores de flanco ascendente es el control de toggles. Los toggles se pueden utilizar para que el robot cambie de un estado a otro; por ejemplo, para encender y apagar una toma de corriente. Esto se puede hacer para cualquier número de estados, pero más comúnmente se hace entre dos. Para hacer un toggle entre dos estados, se utiliza un detector de flanco ascendente para establecer un booleano a su opuesto y luego ese booleano se utiliza para controlar una acción.

Ejemplo#

Dentro del código de inicialización:

boolean intakeToggle = false;

Dentro del bucle TeleOp correspondiente:

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

Esto encenderá el intake cuando se pulse a, y lo dejará encendido hasta que se vuelva a pulsar.

Nota

Cuanto menos tenga que pensar el conductor sobre el estado del robot, menos podrá meter la pata. Dado que los toggles implican que un botón hace cosas diferentes cada vez que se pulsa, hay que considerar soluciones alternativas. Esto es especialmente cierto para los toggles con más de dos estados.

Comentarios sobre el gamepad#

La retroalimentación del gamepad (es decir, el rumble y el control LED) puede ser una forma útil de que los robots comuniquen su estado a un conductor durante un partido. El grado en que los gamepads legales admiten esta funcionalidad varía:

  • Rumble: ninguno

  • Control LED: ninguno

  • Rumble: grande (whomp whomp) y pequeño (bzzz)

  • Control LED: ninguno

  • Rumble: grande (whomp whomp) y pequeño (bzzz)

  • Control LED: control de la barra de luces RGB (color sólido o patrón)

  • Rumble: contiene los motores rumble izquierdo y derecho, pero ambos parecen tener poco peso (bzzz)

  • Control LED: control de LED RGB (color sólido o patrón). LED es bastante pequeño y tenue y puede no ser una buena opción.

Truco

La retroalimentación del gamepad puede usarse para alertar a los conductores de: inicio de final de partida, intake cargado, alineación automática completada, etc.

Rumble#

El SDK ofrece una API sencilla y otra más compleja para controlar el ruido de fondo en función del caso de uso deseado.

Nota

  • La potencia de vibración «rumble» se especifica como un valor de coma flotante en el intervalo [0,0, 1,0].

  • La duración de la vibración «rumble» se especifica en milisegundos como un número entero. La constante Gamepad.RUMBLE_DURATION_CONTINUOUS se puede utilizar para indicar que el estruendo debe continuar hasta que se ordene otra acción de estruendo.

Nota

Todas las acciones de rumble se completan de forma asíncrona; es decir, las llamadas a la función regresarán inmediatamente. Cualquier llamada a una API de rumble desplazará inmediatamente cualquier acción de rumble que se esté ejecutando en ese momento. Es decir, si ordenas a un gamepad que ronronee durante 750 ms e inmediatamente después ordenas un ronroneo de 250 ms, el gamepad ronroneará durante 250 ms desde el momento en que se emitió la segunda orden.

API simple#

La forma más sencilla de ordenar rumble: rumble del motor 1 al 100% de potencia durante un tiempo determinado:

gamepad1.rumble(int durationMs);

Si se desea controlar tanto los motores como la intensidad de la vibración:º

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

Hacer que un gamepad retumbe durante un cierto número de «blips» (la noción de lo que es un «blip» está predefinida por el SDK):

gamepad1.rumbleBlips(int numBlips);

Funciones auxiliares:#

La función public boolean isRumbling() proporciona una suposición educada sobre si hay una acción de rumble en curso en este gamepad. El Robot Controller no sabe con certeza si una acción de rumble está en curso o no, porque una vez que el comando se envía a la estación de control, la estación de control se encarga de ejecutar los efectos del gamepad y el Robot Controller no interviene.

La función public void stopRumble() se puede utilizar para detener cualquier acción de rumble en curso para un gamepad (quizás más útil en conjunción con un rumble de duración continua).

API avanzada#

Para crear un comportamiento de rumble más avanzado, se puede crear un RumbleEffect, que se compone de «Pasos» que especifican la potencia y la duración de cada motor de rumble debe funcionar. Cuando se ordena a un gamepad que ejecute un RumbleEffect, ejecutará cada uno de los «Pasos» en serie.

Para crear un RumbleEffect, se debe utilizar la clase RumbleEffect.Builder. El constructor proporciona la función addStep(double rumble1, double rumble2, int durationMs) que se utiliza para añadir un paso a la secuencia, y la función build() para crear un RumbleEffect a partir de la secuencia de pasos.

Utilizar una instancia anónima de la clase constructora es la forma más limpia de construir un RumbleEffect, por ejemplo:

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

Una vez creado un RumbleEffect, puede enviarse a un gamepad llamando a:

gamepad1.runRumbleEffect(effect);

Control LED#

Nota

  • La intensidad del componente LED RGB (es decir, rojo, verde, azul) se especifica como un valor de punto flotante en el rango [0,0, 1,0].

  • La duración del LED se especifica en milisegundos como un número entero. La constante Gamepad.LED_DURATION_CONTINUOUS puede utilizarse para indicar que el LED debe permanecer en el color especificado hasta que se emita otro comando.

Nota

Todas las acciones LED se completan asíncronamente; es decir, las llamadas a las funciones retornarán inmediatamente. Cualquier llamada a una API LED desplazará inmediatamente cualquier acción LED que se esté ejecutando en ese momento. Es decir, si usted ordena al LED verde por 750ms y luego inmediatamente ordena púrpura por 250ms, el LED se encenderá púrpura por 250ms desde el momento en que el segundo comando fue emitido.

Para establecer el color del LED para una duración fija:

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

Para crear un comportamiento más avanzado del LED, se puede crear un LedEffect, que se compone de «Pasos» que especifican un color y la duración para mantenerlo. Cuando se ordena a un gamepad que ejecute un LedEffect, ejecutará cada uno de los «Pasos» en serie.

Para crear un LedEffect, se debe utilizar la clase LedEffect.Builder. El constructor proporciona la función addStep(double r, double g, double b, int durationMs) que se utiliza para añadir un paso a la secuencia, y la función build() para crear un LedEffect a partir de la secuencia de pasos.

Utilizar una instancia anónima de la clase constructora es la forma más limpia de construir un LedEffect, por ejemplo:

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

Una vez creado un LedEffect, puede enviarse a un gamepad llamando a:

gamepad1.runLedEffect(rgbEffect);