Una entrada digital permite detectar dos estados eléctricos y reaccionar cuando cambian. Un botón es el ejemplo más sencillo: puede estar pulsado o liberado.
En un programa sencillo de Arduino, leer un botón suele implicar consultarlo constantemente en el bucle principal. A esta técnica se la denomina sondeo o polling.
Funciona, sí, pero es ineficiente. Si tu procesador está ocupado haciendo cálculos complejos o esperando una respuesta WiFi, puede que se pierda la pulsación justo en el momento en que el usuario apretó el botón.
DeviceScript, al estar basado en TypeScript, trae consigo el modelo asíncrono y orientado a eventos. En lugar de preguntar todo el rato, le diremos al sistema: “Avísame cuando alguien pulse el botón”.
Hoy vamos a ver cómo usar el servicio Button, cómo suscribirnos a eventos y cómo olvidarnos (por fin) del molesto rebote o debounce.
El servicio Button
Al igual que con las luces (LightBulb), en DeviceScript no solemos leer el GPIO “en crudo” (gpio.read()), sino que iniciamos un driver de botón.
Esto nos abstrae de la electrónica. No nos importa si el botón es Active Low (conecta a GND) o Active High (conecta a VCC), ni si tiene resistencia de Pull-Up o Pull-Down. El driver se encarga de normalizar todo eso para que nosotros solo recibamos “Presionado” o “Soltado”.
Para usarlo, importamos startButton:
import { pins } from "@dsboard/esp32_c3_devkit_m"
import { startButton } from "@devicescript/servers"
// Configuramos un botón en el GPIO 9
const miBoton = startButton({
pin: pins.GPIO9,
activeHigh: false // false = El botón conecta a GND (Pull-Up interno)
})Por defecto, si no especificamos nada, DeviceScript suele asumir que usamos Pull-Up interno (el botón conecta el pin a GND), que es la configuración más común en electrónica maker.
Suscribirse a los eventos
Aquí viene el cambio de mentalidad. En vez de un if (boton.pressed) dentro de un setInterval, vamos a suscribirnos a un evento.
El servicio Button expone varios eventos clave:
down: Se dispara una vez cuando el botón se presiona.up: Se dispara una vez cuando el botón se suelta.hold: Se dispara si el botón se mantiene pulsado un tiempo.
La sintaxis es maravillosa:
// Ejecuta esta función CADA VEZ que se pulse el botón
miBoton.down.subscribe(async () => {
console.log("¡Botón presionado!")
// Aquí tu lógica: encender un LED, enviar un mensaje MQTT, etc.
})
// Ejecuta esta función cuando se suelte
miBoton.up.subscribe(async () => {
console.log("Botón liberado")
})Ventajas frente al sondeo
- Eficiencia Energética: Si no hay pulsaciones, el procesador puede “dormir” o dedicarse a otras tareas.
- No bloqueante: Tu código principal puede estar haciendo una petición HTTP o una animación en pantalla. La pulsación se procesará en cuanto ocurra (o se encolará en el ciclo de eventos).
- Limpieza: El código es mucho más legible. Defines la acción asociada al evento, y te olvidas.
Filtrar los rebotes
Cualquiera que haya conectado un pulsador físico a un Arduino sabe que los interruptores mecánicos son “sucios”. Al pulsar, los contactos metálicos rebotan microscópicamente, generando una ráfaga de encendidos y apagados muy rápidos antes de estabilizarse.
En Arduino, solíamos arreglar esto con delay(50) o librerías complejas.
En DeviceScript, el driver startButton ya implementa debouncing por software.
Cuando te suscribes a down, el sistema filtra esas señales espurias. Tú recibes un evento limpio. Si necesitas ajustar la sensibilidad, el driver acepta parámetros de configuración, pero los valores por defecto funcionan perfecto para pulsadores estándar.
Ejemplo: interruptor de luz
Vamos a juntar lo aprendido en la lección anterior (LEDs) con esta. Vamos a crear el típico sistema de “interruptor de pasillo”: pulsas el botón y la luz cambia de estado (Toggle).
import { pins } from "@dsboard/esp32_c3_devkit_m"
import { startButton, startLightBulb } from "@devicescript/servers"
// 1. Configuramos el Hardware (Servicios)
const btn = startButton({ pin: pins.GPIO9 }) // Botón en GPIO 9
const luz = startLightBulb({ pin: pins.GPIO2 }) // LED en GPIO 2
console.log("Sistema listo. Pulsa el botón.")
// 2. Lógica Reactiva
btn.down.subscribe(async () => {
// Leemos el estado actual de la luz (0 o 1)
const intensidadActual = await luz.intensity.read()
// Calculamos el nuevo estado (lo contrario)
const nuevaIntensidad = intensidadActual > 0 ? 0 : 1
// Aplicamos
await luz.intensity.write(nuevaIntensidad)
console.log(`Luz cambiada a: ${nuevaIntensidad}`)
})Copia el código, inicia el simulador y pulsa el botón virtual del dashboard. El LED virtual cambiará de estado con cada pulsación.
No hay un bucle principal explícito como setInterval o while (true). La suscripción permanece activa y el runtime invoca el manejador cuando recibe el evento.
Probar el botón en el simulador
Cuando usamos startButton, el dashboard muestra un control de botón.
- Un clic rápido simula una pulsación normal.
- Mantener pulsado el control simula una pulsación larga, útil para probar el evento
hold.
Es increíblemente útil para depurar lógica de “pulsación larga” sin tener que estar físicamente apretando un botón en la protoboard todo el rato.