Este proyecto usa Node-RED Dashboard para mostrar la carrera en una pantalla grande y controlar el torneo desde el movil. Los dos flows trabajan contra el mismo broker MQTT local (localhost:1883) y se apoyan en los topics publicados por bridge/bridge.js y tournament-engine.js.
- Node-RED instalado.
- Paquete
node-red-dashboardinstalado. Los flows estan hechos paranode-red-dashboard3.6.6. - Broker MQTT local en
localhost:1883. bridge/bridge.jspublicando la telemetria de la pista.tournament-engine.jspublicando el estado del torneo.
En Node-RED:
- Abre el menu principal.
- Entra en
Import. - Pega el JSON del flow de pantalla grande o del control movil.
- Pulsa
Import. - Comprueba que el broker MQTT apunta a
localhost:1883. - Pulsa
Deploy.
El dashboard se abre normalmente en:
http://localhost:1880/ui
Si accedes desde otro dispositivo de la red, usa la IP del ordenador donde corre Node-RED:
http://IP_DEL_ORDENADOR:1880/ui
Flow: OpenLedRace - Full Screen No Header
Es la vista pensada para un monitor, TV o proyector. Oculta la barra superior del dashboard para ocupar toda la pantalla.
| Nodo | Topic | Funcion |
|---|---|---|
Estado |
openledrace/tournament/state |
Recibe el estado completo del torneo. |
Players |
openledrace/player/# |
Recibe telemetria de los dos jugadores. |
Store Data: guarda en memoria de flow las vueltas y velocidad/distancia recibidas por MQTT.Merge UI Data: junta el ultimo estado del torneo con la telemetria mas reciente.Pantalla Full Screen Sin Header: renderiza toda la interfaz con unui_template.
Cuando msg.payload.mode === 'race', la pantalla muestra:
- Nombre del jugador del carril rojo (
player1). - Nombre del jugador del carril verde (
player2). - Vueltas de cada jugador.
- Velocidad mostrada como
km/h. - Barra de potencia para cada carril.
- Bloque central
VS. - Ronda actual del torneo, por ejemplo
Final,SemifinalesoCuartos de Final.
La pantalla toma los nombres desde:
msg.payload.currentMatch.match.player1
msg.payload.currentMatch.match.player2
Si no hay torneo cargado, usa valores por defecto:
LOCAL
VISITANTE
Cuando msg.payload.mode === 'bracket', la pantalla cambia a vista de torneo:
- Muestra todas las rondas del bracket.
- Oculta los partidos marcados como
isBye. - Resalta el ganador de cada enfrentamiento.
- Si el torneo ha terminado, muestra el campeon final.
El modo se cambia desde el control movil publicando:
{"action":"set_mode","mode":"bracket"}o:
{"action":"set_mode","mode":"race"}Flow: OpenLedRace Tournament
Es una vista compacta para usar desde el telefono durante el torneo.
| Nodo | Topic | Funcion |
|---|---|---|
Estado torneo |
openledrace/tournament/state |
Recibe el bracket y el partido actual. |
P1 km |
openledrace/player/1/km |
Recibe distancia del jugador 1. |
P1 laps |
openledrace/player/1/laps |
Recibe vueltas del jugador 1. |
P2 km |
openledrace/player/2/km |
Recibe distancia del jugador 2. |
P2 laps |
openledrace/player/2/laps |
Recibe vueltas del jugador 2. |
El nodo Enviar comando publica en:
openledrace/tournament/command
Marcar ganador del jugador 1:
{
"action": "set_winner",
"matchId": "r1-m1",
"winner": "Nombre jugador 1"
}Marcar ganador del jugador 2:
{
"action": "set_winner",
"matchId": "r1-m1",
"winner": "Nombre jugador 2"
}Mostrar bracket en la pantalla grande:
{"action":"set_mode","mode":"bracket"}Mostrar carrera en la pantalla grande:
{"action":"set_mode","mode":"race"}Reiniciar torneo:
{"action":"reset_tournament"}El flujo completo es:
- El Arduino ejecuta la carrera y emite eventos JSON por Serial.
bridge/bridge.jslee el Serial y publica telemetria en MQTT.tournament-engine.jspublica el estado del torneo y escucha comandos.- Node-RED lee los topics MQTT y actualiza la pantalla grande.
- El movil envia comandos MQTT para cambiar modo o marcar ganadores.
- El motor de torneo avanza el bracket y vuelve a publicar el estado actualizado.
| Topic | Emisor | Consumidor | Uso |
|---|---|---|---|
openledrace/player/1/km |
Bridge | Node-RED | Distancia P1. |
openledrace/player/1/laps |
Bridge | Node-RED | Vueltas P1. |
openledrace/player/1/kmh |
Bridge | Node-RED pantalla grande | Velocidad P1 por coincidencia de topic. |
openledrace/player/2/km |
Bridge | Node-RED | Distancia P2. |
openledrace/player/2/laps |
Bridge | Node-RED | Vueltas P2. |
openledrace/player/2/kmh |
Bridge | Node-RED pantalla grande | Velocidad P2 por coincidencia de topic. |
openledrace/tournament/state |
Tournament engine | Node-RED | Estado completo del torneo. |
openledrace/tournament/command |
Node-RED movil | Tournament engine | Comandos de torneo. |
Nota: en el flow de pantalla grande se usa openledrace/player/# y despues se comprueba includes('1/km') o includes('2/km'). Eso tambien coincide con openledrace/player/1/kmh y openledrace/player/2/kmh, por eso la variable interna p1km/p2km acaba funcionando como velocidad en la interfaz. Si se quiere dejar mas claro, conviene renombrar esas variables a p1kmh y p2kmh.
En el firmware el derrape esta implementado como un sistema de crash y recuperacion. No depende de Node-RED: ocurre dentro de main/main.ino mientras se ejecuta la carrera.
#define CRASH_SPEED_THRESHOLD 70.0f
#define CRASH_PROB_MAX 0.005f
#define RECOVERY_DURATION 2000ULCada pulsacion aumenta la velocidad interna:
speed += ACEL;En cada ciclo tambien hay freno/friccion:
speed -= speed * kf;La velocidad interna se convierte a km/h con:
float k = (spd / SPEED_MAX) * 100.0f;
return constrain(k, 0.0f, 100.0f) * 8.2;Si la velocidad calculada esta por debajo o igual a 70 km/h, no hay derrape.
if (kmh <= CRASH_SPEED_THRESHOLD) return false;Si supera 70 km/h, aparece una probabilidad de accidente. Cuanto mas se pasa del umbral, mas probable es derrapar:
float excess = (kmh - CRASH_SPEED_THRESHOLD) / (100.0f - CRASH_SPEED_THRESHOLD);
float prob = excess * CRASH_PROB_MAX;Despues el firmware lanza una tirada aleatoria:
return ((float)random(0, 10000) / 10000.0f) < prob;Cuando un jugador derrapa:
- Su velocidad pasa a
0. - Entra en estado de recuperacion.
- Su LED parpadea en blanco.
- Durante la recuperacion no puede seguir acelerando.
- Pasados
RECOVERY_DURATIONmilisegundos, vuelve a la carrera.
La recuperacion dura por defecto:
2000 ms = 2 segundos
El evento tambien sale por Serial como:
{"evt":"crash","player":1}o:
{"evt":"crash","player":2}El bridge publica ese evento en:
openledrace/race/event
- P1 normalmente es rojo.
- P2 normalmente es verde.
- Si un jugador derrapa, su posicion parpadea en blanco durante la recuperacion.
La pantalla grande muestra la velocidad y la barra de potencia. Cuando la velocidad sube, el jugador se acerca a la zona de riesgo de derrape.
El template ya marca una clase danger-active cuando el valor mostrado supera 70, aunque ahora mismo no hay una regla CSS especifica para esa clase. Se puede usar para añadir un aviso visual, por ejemplo borde rojo, parpadeo o texto de peligro.
Si los derrapes ocurren demasiado pronto o demasiado tarde, toca estos valores en main/main.ino:
- Sube
CRASH_SPEED_THRESHOLDpara que sea mas dificil derrapar. - Baja
CRASH_SPEED_THRESHOLDpara que sea mas facil derrapar. - Sube
CRASH_PROB_MAXpara aumentar la probabilidad. - Baja
CRASH_PROB_MAXpara hacer la carrera mas estable. - Sube
RECOVERY_DURATIONpara penalizar mas el derrape. - Baja
RECOVERY_DURATIONpara que el jugador vuelva antes a la carrera.