Un futbolín Córdoba restaurado
Barreras de infrarrojos en los carriles de recogida y un ESP32 con ESPHome que lleva la partida. El marcador es un dashboard en la tablet, con locutor y música, se controla por voz con Assist y los goles se celebran con WLED. Va a batería, escondido en el mueble, y la portería no se ha tocado.

01
Se juega con 11 bolas y gana quien llega a 6. Con 5-5, la última bola se tira en el centro: es «la buena». El ESP32 conoce estas reglas y publica la fase de la partida en sensor.futbolin_fase. Todo lo demás (marcador, sonido y luces) reacciona a esa fase.
«Nueva partida» por voz. Marcador a cero y el locutor presenta el clásico.
normalSuma solo. Ola de luz del color del equipo y una frase del locutor.
event.futbolin_golAl llegar a 5, las tiras respiran en el color de quien la tiene.
bola_partido_a5-5: duelo de olas, latido dorado y cartel en el marcador.
la_buenaFiesta de luces y pantalla del ganador. Los goles que sobran ya no cuentan.
fin_a02
Es un futbolín Córdoba antiguo, negro y restaurado. Eso fijó las reglas del montaje:
El ábaco de cuentas rojas y azules sigue en su sitio, por si acaso.




03
Lo intuitivo sería mirar en la portería, pero es mejor mirar después. Cuando la bola entra, cae por una ranura a un carril de madera que la lleva rodando hasta el cajetín. Ahí ya no va a cien por hora: rueda despacio y siempre por el mismo camino.
En cada carril hay una barrera de infrarrojos, con un emisor a un lado y un receptor enfrente. Cuando la bola pasa, corta el haz unas decenas de milisegundos, y eso es un gol.
Dos cuñas de madera pegadas con cola dentro del carril. No se ha agujereado ni la portería ni el carril.
Las cuñas se despegan y los sensores van con conectores. El futbolín vuelve a estar como antes.
Cada sensor está a cubierto detrás de su cuña. La bola nunca lo toca y la rampa la guía sin frenarla.
El haz cruza el carril de pared a pared, así que la bola lo corta pase por donde pase.
binary_sensor.futbolin_atasco_a).sensor.futbolin_ultimo_corte con su duración y qué se hizo con él (gol, ruido o rebote). El registro de HA guarda así el diario de la partida.
04
La decisión clave: la partida la lleva el ESP32, no Home Assistant. Cuenta, calcula la fase y la guarda en flash (restore_value) para no perderla en un reinicio o una actualización OTA.
Desde ahí avisa por dos caminos. A HA, por la API nativa de ESPHome. A WLED, directamente por la API JSON ({"ps": 3}), sin pasar por HA, para que el efecto salga al instante.
Si cae la WiFi o Home Assistant, el ESP32 sigue contando, y al volver la tablet enseña el marcador correcto. HA no lleva ninguna lógica de la partida: solo muestra las entidades y pulsa los botones.
05
El dispositivo ESPHome se llama futbolin, así que todas las entidades quedan como <dominio>.futbolin_*. Los equipos son A y B: en casa, A es el Madrid y B el Barça.
| Entidad | Qué es |
|---|---|
sensor.futbolin_goles_a / _b | Goles de cada equipo, de 0 a 6 |
sensor.futbolin_fase | reposo, normal, bola_partido_a/_b, la_buena, fin_a/_b |
event.futbolin_gol | gol_a o gol_b, solo con los goles que ven las barreras. Es el disparador natural de las automatizaciones |
sensor.futbolin_bateria | % de batería, calculado con la tensión más alta de los últimos 10 min para que los picos de las tiras no lo hagan saltar |
binary_sensor.futbolin_autotest | Resultado del autotest de las barreras |
| Entidad | Qué hace |
|---|---|
button.futbolin_nueva_partida | 0-0 y a jugar |
button.futbolin_gol_a_mas / _menos | Corrige el marcador. La fase se recalcula: un −1 desde 6-5 devuelve la bola de partido |
button.futbolin_anular_partida | Vuelve a reposo |
switch.futbolin_efectos_luz | Activa o desactiva los efectos de las tiras |
number.futbolin_luces_bateria_minima | % de batería por debajo del cual se cortan las luces |
switch.futbolin_modo_enchufado | Para jugar con una fuente de 12 V en lugar de baterías: oculta la batería y silencia sus avisos |
06
El marcador es un dashboard de HA hecho con una sola button-card con HTML propio: dígitos de matriz de puntos, la columna de las 11 bolas con el orden de los goles, la animación de «¡GOL!», el cartel de «la buena» y la pantalla del ganador. Mientras está abierto, HA quita el salvapantallas de la tablet.
Cada gol lo canta un locutor que sigue el partido: el primer gol, el empate, la racha, la remontada, la bola de partido, el gol que salva el partido… Son 348 locuciones generadas de antemano con una voz TTS de OpenAI. HA calcula el momento del partido como atributo de un sensor y el dashboard elige una frase al azar, sin repetir la anterior.

Con música sonando en la tablet, el locutor tiene que bajarla y no pararla. Android solo baja la música si el sonido nuevo pide el foco de audio «temporal, se puede bajar», y el WebView solo lo pide así para medios de menos de 5 s. Con una locución más larga, la música se pausaba y no volvía.
La solución: mientras habla el locutor, suena en bucle un <audio> con 1 s de silencio, que pide el foco corto. La voz va aparte por Web Audio, que no pide foco. Al acabar, se vacía el src del silencio y se llama a load(): con pause() solo, el foco no se suelta y la música se queda baja.
07
El futbolín no tiene botones: todo se hace con Assist desde la tablet. Las frases son disparadores de conversación de una automatización que pulsan los botones de ESPHome. Se resuelven en local antes de llegar al LLM, así que no hace falta exponer el futbolín a Assist.
triggers: - trigger: conversation command: - "gol del Madrid" - "gol de los blancos" actions: - action: button.press target: entity_id: button.futbolin_gol_a_mas
| Frase | Acción |
|---|---|
| «Nueva partida», «echamos una» | Nueva partida y marcador en la tablet |
| «Gol del Barça» | +1 |
| «Ese no vale el del Madrid» | −1 |
| «¿Cómo vamos?» | Responde con el marcador y la fase |
| «Anula la partida» | Vuelta a reposo |
| «Pon la música» | Música en la tablet |
08
Hay una tira LED a cada lado del campo (WS2811 de 12 V, 28 píxeles cada una), movidas por un controlador Gledopto con WLED. Cada tira es un segmento. A batería gastan demasiado para ir encendidas toda la partida, así que solo hacen efectos de unos segundos y vuelven a apagarse solas.
Cada momento es un preset (o una playlist de presets) que el ESP32 lanza por la API JSON. Los presets salen de un script generador, así que se versionan con el resto del código.
| Preset | Cuándo | Qué hace |
|---|---|---|
gol_a / gol_b | Gol | Ola del color del equipo hacia la portería contraria y fuegos artificiales, 3 s |
bola_partido_* | Un equipo llega a 5 | Las tiras respiran en su color 8 s |
la_buena | 5-5 | Duelo de olas, cada una desde su portería, y latido dorado |
fin_a / fin_b | Gol que gana | Fiesta del color del ganador 10 s |
aviso | Atasco o autotest fallido | Rojo fijo hasta que se resuelve |
Con batería baja, el ESP32 corta las luces solo: si el % pasa 60 s por debajo de number.futbolin_luces_bateria_minima, las apaga, y no las vuelve a encender hasta que la batería sube 10 puntos por encima del mínimo. Ese margen evita que el corte rebote, porque los propios efectos hunden la tensión.
09
Toda la electrónica va en un panel de contrachapado junto a las baterías, en la parte fija del mueble: el ESP32, el buck que baja de 12 a 5 V, una regleta que hace de bus de 12 V y la interfaz de las barreras. Las tiras toman los 12 V directamente.
A cada carril llega un cable de 5 hilos que acaba en dos conectores JST-SM, uno para el receptor y otro para el emisor. Las resistencias de pull-up van soldadas en el extremo del cable, junto al ESP32, y un divisor de 100k/20k lleva los 12 V al GPIO 34 para calcular el % de batería.
Son dos baterías de plomo de 12 V y 7 Ah en paralelo, cada una con su fusible de 5 A, y un interruptor general por debajo del mueble. Según las cuentas, dan para unas 7 horas de juego (falta medirlo). Para jugar enchufado, se quitan las baterías y se conecta una fuente de 12 V en su lugar.




Todo está en GitHub, con código abierto: el firmware de ESPHome, el paquete de Home Assistant con el dashboard del marcador y las frases de Assist, los presets de WLED con su generador, el generador de las locuciones y los diagramas.