SmartPong Dynamics · 2025—2026
IoT · Echtzeit · Indoor-TrackingIoT · Real time · Indoor tracking

SmartPong
Dynamics

Ein Pong-Spiel, das man mit dem eigenen Körper steuert. Ultra-Wideband-Sensoren messen die Position der Spieler im Raum — gefiltert, per UDP übertragen und mit 60 Bildern pro Sekunde auf die Leinwand gerendert.

A Pong game you control with your body. Ultra-Wideband sensors measure where the players are in the room — filtered, shipped over UDP and rendered to the projector at 60 frames per second.

KontextContext
Modulprojekt „Internet of Things“
Hochschule Worms
"Internet of Things" module
Hochschule Worms
ZeitraumTimeline
Okt. 2025 – Jan. 2026Oct 2025 – Jan 2026
UmfangSize
~3.300 Zeilen · 5 Komponenten~3,300 lines · 5 components
Stack
C++ · Python · Node.js · React · Docker
Spielansicht mit blauem und pinkem Schläger, Ball mit Schweif und der Sensoranzeige unten links
Die Spielansicht. Unten links steht die Live-Distanz des UWB-Sensors — der einzige Ort im Bild, an dem die Hardware sichtbar wird: P1 SENSOR: 105cm The play view. Bottom left is the live UWB sensor distance — the one place in the frame where the hardware becomes visible: P1 SENSOR: 105cm
Der SignalwegThe signal path

Von der Funkmessung bis zum PixelFrom radio measurement to pixel

Fünf Stationen, vier Laufzeitumgebungen, drei Protokolle. Jede Station hat genau eine Aufgabe — und gibt weiter, was die nächste braucht.

Five stations, four runtimes, three protocols. Each station does exactly one job — and hands on what the next one needs.

01 MessenMeasure UWB-Modul misst per Time-of-Flight die Distanz zum Anker.UWB module ranges to the anchor by time of flight. Qorvo DWM3001CDK
02 Lesen & filternParse & filter ESP32 liest UART, verwirft kaputte Zeilen, glättet je Anker.ESP32 reads UART, drops garbled lines, smooths per anchor. UART 115200 · EMA α=0.9
03 SendenTransmit JSON-Paket, fire and forget — ohne Handshake, ohne Wiederholung.JSON packet, fire and forget — no handshake, no retry. UDP :5005
04 ZustandState Flask hält den Spielzustand; ein eigener Thread lauscht auf dem Sensorstrom.Flask owns game state; a dedicated thread listens to the sensor stream. Python · Flask
05 ZeichnenRender Canvas-Engine bildet Distanz auf Bildschirmkoordinaten ab.Canvas engine maps distance onto screen coordinates. React 19 · 60 FPS
Pong IoT System Architektur Responder 1 Anker ESP32 Initiator Tag ESP32 Responder 2 Anker ESP32 Sensor UDP Gateway Spiel paddeln 1 paddeln 2 UDP / WebSocket Server/ Cloud Raspberry Pi Server/Cloud WebSocket Actuator Screen 1 Weste Spieler 1 LED Screen 2 Weste Spieler 2 LED Actuator HS W Hochschule Worms 507 IoT – Internet of Things

Systemarchitektur: zwei Anker, ein Initiator, ein Raspberry Pi als Access Point und Docker-Host. System architecture: two anchors, one initiator, a Raspberry Pi as access point and Docker host.

Die AufgabeThe brief

Der Raum ist das SpielfeldThe room is the playing field

Digitale Spiele belohnen Stillsitzen. Die Idee war, das umzudrehen: Wer sich nach links bewegt, bewegt seinen Schläger nach links — ohne Kamera, ohne Marker an der Decke, ohne Tracking-Anzug.

Digital games reward sitting still. The idea was to invert that: move left and your paddle moves left — no camera, no ceiling markers, no motion-capture suit.

  • Positionsbestimmung im Innenraum, zentimetergenau und ohne Sichtlinie zu einer Kamera.Indoor positioning, accurate to centimetres, with no line of sight to a camera.
  • Latenz unter der Wahrnehmungsschwelle. Ein Schläger, der spürbar nachhängt, macht das Spiel unspielbar.Latency below the perception threshold. A paddle that visibly lags makes the game unplayable.
  • Autarker Betrieb. Aufbaubar in jedem Raum — ohne vorhandenes WLAN, ohne Internet, ohne Entwickler-Laptop.Self-contained operation. Deployable in any room — no existing Wi-Fi, no internet, no developer laptop.
EntscheidungenDecisions

Drei Entscheidungen, die das System tragenThree decisions the system rests on

UDP für Sensorik, HTTP für ZustandUDP for sensing, HTTP for state

Bei 10–20 Messungen pro Sekunde kostet der TCP-Handshake mehr, als er bringt. Geht ein Positionspaket verloren, ist der nächste Wert ohnehin aktueller als der nachgelieferte alte — und genau das Nachliefern ist das Problem: TCP blockiert dabei alles Folgende. Für Punktestand und Spielende, wo Zuverlässigkeit zählt, läuft die Kommunikation bewusst über HTTP und WebSocket.

At 10–20 readings per second the TCP handshake costs more than it returns. If a position packet is lost, the next reading is fresher than the retransmitted one — and retransmission is exactly the problem: TCP blocks everything behind it. Score and game-over, where reliability matters, deliberately travel over HTTP and WebSocket.

Sensorik = verlustbehaftet und schnell.
Zustand = zuverlässig und langsam.
Sensing = lossy and fast.
State = reliable and slow.

Ein Filter, der bewusst kaum filtertA filter that deliberately barely filters

UWB-Rohwerte rauschen. Naiv würde man stark glätten — und bekäme einen Schläger, der sich anfühlt wie in Sirup. Der Exponential-Moving-Average läuft deshalb mit hohem Alpha: Der aktuelle Messwert fließt zu 90 % ein, Richtungswechsel kommen fast verzögerungsfrei an, einzelne Ausreißer werden trotzdem gedämpft.

Raw UWB readings are noisy. The naive fix is heavy smoothing — which gives you a paddle that moves like it is in syrup. So the exponential moving average runs with a high alpha: the current reading contributes 90 %, direction changes land almost immediately, and isolated outliers are still damped.

gefiltert = 0.9 · Messwert + 0.1 · Verlauf

Die restliche Glättung passiert dort, wo sie nichts kostet: im Frontend über einen 4-Werte-Mittelwert, eine Interpolation von 30 % pro Frame und eine Totzone von zwei Pixeln. Drei Stufen mit verschiedenen Aufgaben statt einer aggressiven.

The remaining smoothing happens where it costs nothing: in the frontend, via a four-sample average, 30 % interpolation per frame and a two-pixel dead zone. Three stages with different jobs instead of one aggressive one.

Die Weste weiß nichts vom SpielThe vest knows nothing about the game

Die Wearable-Firmware enthält keine Spiellogik. Sie meldet sich beim Start selbst am Server an, startet einen eigenen HTTP-Server und wartet auf Befehle: Bildschirm wechseln, Punktestand setzen, LEDs ändern. Das macht das System debuggbar — jeder Gerätezustand lässt sich mit einem einzigen curl-Aufruf reproduzieren, ohne Firmware zu flashen.

The wearable firmware contains no game logic. On boot it registers itself with the server, starts its own HTTP server and waits for commands: change screen, set score, change LEDs. That makes the system debuggable — any device state can be reproduced with a single curl call, no firmware flashing involved.

WiderstandResistance

Drei Probleme, die das Projekt geprägt habenThree problems that shaped the project

FunkRadio

Die Hardware konnte nicht, was die Architektur brauchteThe hardware could not do what the architecture assumed

Geplant war Trilateration: zwei mobile Tags messen gegen drei feste Anker, daraus wird eine XY-Position. Die Qorvo-Module gaben das mit zwei gleichzeitigen Initiatoren nicht her — die Funkzugriffe kollidierten, der Paketverlust machte die Daten unbrauchbar.

The plan was trilateration: two mobile tags ranging against three fixed anchors to compute an XY position. The Qorvo modules could not sustain that with two simultaneous initiators — radio access collided and packet loss made the data unusable.

Statt weiter gegen die Hardware zu arbeiten, wurde die Frage umgedreht: Braucht das Spiel überhaupt zwei Dimensionen? Der Schläger bewegt sich auf einer Achse. Die Ranging-Rollen wurden neu verteilt, sequenziell gemessen, die Kollisionsdomäne verkleinert. Danach war die Datenrate stabil.

Rather than keep fighting the hardware, the question got inverted: does the game even need two dimensions? The paddle moves on one axis. Ranging roles were redistributed, measurement made sequential, the collision domain shrunk. After that the data rate held.

Der Trade-off ist dokumentiert, nicht versteckt: Ein TDMA-Konzept mit festem Sendeversatz liegt für die nächste Iteration vor — implementiert wurde es aus Zeitgründen nicht.The trade-off is documented, not hidden: a TDMA scheme with fixed transmission offsets is written up for the next iteration — it was not implemented within the timeline.

NetzwerkNetwork

Docker verschluckt UDP — lautlosDocker swallows UDP — silently

Der Server läuft im Container, die ESP32-Module schicken UDP an eine feste IP. Dockers Bridge-Netzwerk sorgt dafür, dass diese Pakete nie ankommen. Bei TCP fällt das sofort als Verbindungsfehler auf. Bei UDP schweigt alles: Der Mikrocontroller meldet „gesendet“, der Server sieht nichts.

The server runs in a container; the ESP32 modules send UDP to a fixed IP. Docker's bridge network means those packets never arrive. With TCP you get a connection error immediately. With UDP everything goes quiet: the microcontroller reports "sent", the server sees nothing.

ports:
  - "5001:5001"      # HTTP / REST
  - "5005:5005/udp"  # separat freigebenpublish separately

Deployment

Ein System, das ohne Infrastruktur startetA system that boots without infrastructure

Ein Aufbau, der nur im Labor-WLAN läuft, ist keine Lösung, sondern eine Demo. Der Raspberry Pi spannt deshalb sein eigenes WLAN auf und ist unter einer festen Gateway-Adresse erreichbar — damit haben alle ESP32-Clients ein konstantes Ziel, unabhängig von der Umgebung.

A setup that only works on the lab Wi-Fi is not a solution, it is a demo. So the Raspberry Pi hosts its own network and answers on a fixed gateway address — giving every ESP32 client a constant target regardless of the environment.

Ein Autostart-Skript wartet in der richtigen Reihenfolge auf X-Session, Docker-Daemon und HTTP-Erreichbarkeit, startet dann die Container und zuletzt Chromium im Kiosk-Modus. Strom an, kurz warten, das Spiel steht — kein Login, keine Tastatur, kein Terminal.

An autostart script waits, in order, for the X session, the Docker daemon and HTTP reachability, then brings up the containers and finally Chromium in kiosk mode. Power on, wait a moment, the game is up — no login, no keyboard, no terminal.

HardwareHardware

Vom Gehäuse her gedachtDesigned from the enclosure out

Getragene Weste mit rundem Modul, auf dem Display läuft der Startbildschirm
Die fertige Weste. Auf dem Display läuft dieselbe Firmware wie im übrigen System — was auf der Leinwand als PONG SERVER erscheint, erscheint gleichzeitig auf der Brust. The finished vest. The display runs the same firmware as the rest of the system — what appears on the projection as PONG SERVER appears at the same moment on the chest.

Ein Wearable ist keine Platine mit Kabelbaum. Die Elektronik musste in etwas hinein, das man sich anzieht, das Stöße aushält und das man laden kann, ohne es zu zerlegen.

A wearable is not a board with a wiring harness. The electronics had to go inside something you put on, that survives being knocked about, and that can be charged without taking it apart.

Beide Gehäuse wurden vollständig in CAD konstruiert, bevor der erste Druck lief — parametrisch bemaßt, mit Schraubdomen, Kabelkanälen und einem umlaufenden Schraubkranz für die Frontplatte. Das Display sitzt diagonal, weil es so den verfügbaren Durchmesser am besten ausnutzt: eine Entscheidung, die in der Skizze fällt und nicht beim Zusammenbau.

Both enclosures were fully modelled in CAD before the first print ran — parametrically dimensioned, with screw bosses, cable channels and a perimeter screw ring for the face plate. The display sits diagonally because that makes the best use of the available diameter: a decision taken in the sketch, not during assembly.

DurchmesserDiameter
180 mm
LochkreisBolt circle
140 mm
AusschnittCutout
60 × 98 mm
WandstärkeWall
2,5 mm
Bemaßte CAD-Konstruktionszeichnung des runden Moduls
Konstruktion. Jede Bohrung ist bemaßt statt frei platziert — Frontplatte und Gehäuse sind aus derselben Skizze abgeleitet.Design. Every hole is dimensioned rather than freely placed — face plate and housing derive from the same sketch.
CAD-Ansicht der Frontplatte mit Displayausschnitt und LED-Bohrungen
Frontplatte mit Displayausschnitt, LED-Bohrungen und Aussparung für den Taster.Face plate with display cutout, LED holes and a recess for the button.

Energieversorgung als eigenes ProblemPower as its own problem

Der ESP32 arbeitet mit 3,3 V Logik. Display-Backlight und WS2812-LED-Streifen brauchen 5 V. Beides aus einer einzelnen Li-Ion-Zelle zu versorgen, hieß: Step-Up-Wandler einplanen, Lademodul integrieren, damit der Akku im verbauten Zustand geladen werden kann, und alles so verlöten, dass auf engem Raum nichts kurzschließt.

The ESP32 runs on 3.3 V logic. The display backlight and the WS2812 LED strip need 5 V. Supplying both from a single Li-ion cell meant designing in a step-up converter, integrating a charging module so the battery can be charged in place, and soldering it all so that nothing shorts in a tight space.

Geöffnetes 3D-gedrucktes Gehäuse mit UWB-Modul, ESP32 und 18650-Zelle
UWB-Einheit, geöffnet — Qorvo DWM3001CDK, ESP32 und 18650-Zelle in den dafür konstruierten Aussparungen.UWB unit, opened — Qorvo DWM3001CDK, ESP32 and an 18650 cell in cutouts designed for them.
Geöffnetes rundes Modul der Weste mit Step-Up-Wandler
Rundmodul der Weste, geöffnet — zusätzlich mit Step-Up-Wandler für die 5-V-Peripherie.The vest circular module, opened — with a step-up converter for the 5 V peripherals.

Die WesteThe vest

Für das Tragen am Körper wurde eine Weste mit eigenem Schnittmuster gefertigt: robuster Stoff, eingenähte Tasche für das Modul, Bewegungsfreiheit trotz starrem Gehäuse. Gehäusehöhe und Sitz der Tasche mussten zusammen entworfen werden — ein tieferes Gehäuse hätte bei jeder Drehung gestört.

For wearing on the body, a vest was made from its own pattern: durable fabric, a sewn-in pocket for the module, freedom of movement despite a rigid enclosure. Housing depth and pocket placement had to be designed together — a deeper enclosure would have got in the way on every turn.

Weste von schräg vorn getragen
Das Modul sitzt in einer eingenähten Tasche; der Schraubkranz hält Frontplatte und Gehäuse zusammen.The module sits in a sewn-in pocket; the screw ring holds face plate and housing together.
Weste im Profil getragen
Im Profil: flach genug, um Bewegungsfreiheit zu lassen.In profile: flat enough to leave freedom of movement.
FrontendFrontend

Eine Spiel-Engine ohne FrameworkA game engine with no framework

Das Spiel ist keine UI-Library-Anwendung, sondern eine kleine, selbst gebaute Engine auf einem HTML5-Canvas: Renderloop über requestAnimationFrame, Zustandsautomat über fünf Bildschirme, eigene Button-Widgets, vereinheitlichte Eingabe für Tastatur, Maus und Touch.

The game is not a UI-library application but a small hand-built engine on an HTML5 canvas: a requestAnimationFrame render loop, a state machine across five screens, custom button widgets, unified input for keyboard, mouse and touch.

React verwaltet nur Lebenszyklus und Canvas-Referenz — der Renderloop läuft bewusst außerhalb des React-Zustands, damit kein Re-Render die 60 Bilder pro Sekunde stört. Die Netzwerkschicht arbeitet mit Timeouts und gibt bei Ausfall null zurück statt zu werfen: Fällt das Backend weg, bleibt das Spiel über die Tastatur bedienbar.

React only manages lifecycle and the canvas ref — the render loop deliberately lives outside React state so no re-render disturbs the 60 FPS budget. The network layer uses timeouts and returns null on failure instead of throwing: if the backend disappears, the game stays playable from the keyboard.

Menü zur Auswahl des Spielmodus mit zwei Schaltflächen
Der Modus „Spieler gegen Spieler“ erscheint erst, wenn sich beide Westen am Server registriert haben."Player vs player" only appears once both vests have registered with the server.
Schneller Ballwechsel mit sichtbarem Ballschweif
Die Ballgeschwindigkeit steigt pro Treffer um 5 %; der Schweif entsteht aus einem Ringpuffer der letzten zehn Positionen.Ball speed rises 5 % per hit; the trail comes from a ring buffer of the last ten positions.
Countdown vor dem Spielstart
Countdown — Zeit, die Weste anzulegen und sich im Raum zu positionieren.Countdown — time to put the vest on and get into position.
Ergebniskarte am Spielende
Beim Verlassen setzt der Client die Konfiguration aller Screens serverseitig zurück.On exit the client resets every screen's configuration server-side.

Aufnahmen aus der laufenden Anwendung in 1920 × 1080; Backend und Sensorstrom waren dabei simuliert, da die Hardware nicht angeschlossen war. Captured from the running application at 1920 × 1080; backend and sensor stream were simulated, as the hardware was not attached.

Wearable

Jede Weste sieht ihren eigenen SpielstandEach vest sees its own score

Auf der Weste sitzt ein ESP32-S3 mit 4,3-Zoll-Touchdisplay. Die Oberfläche läuft auf LVGL 8; beim Bildschirmwechsel wird der alte Objektbaum explizit freigegeben, und jeder UI-Zugriff aus dem HTTP-Handler ist per Mutex abgesichert — die Grafikbibliothek ist nicht thread-sicher.

The vest carries an ESP32-S3 with a 4.3-inch touch display. The interface runs on LVGL 8; on every screen change the old object tree is explicitly released, and each UI access from the HTTP handler is mutex-guarded — the graphics library is not thread-safe.

Der Server dreht den Punktestand für den zweiten Bildschirm um, damit auf beiden Displays die eigene Punktzahl vorne steht. Gesicht und LED-Farbe leitet er aus dem Punktverhältnis ab: grünes Pulsieren bei Führung, rotes Blinken bei Rückstand.

The server flips the score for the second screen so each display shows its own points first. Face and LED colour follow from the score ratio: a green pulse when ahead, a red blink when behind.

Modusauswahl auf dem Wearable-Display
ModusauswahlMode select
Bereitschaftsknopf auf dem Wearable-Display
BereitschaftReady check
Grünes Gesicht bei Führung
Führung — LEDs pulsierenLeading — LEDs pulse
Rotes Gesicht bei Rückstand
Rückstand — LEDs blinkenBehind — LEDs blink
Technik im ÜberblickStack at a glance

Fünf Schichten, vier SprachenFive layers, four languages

SchichtLayer TechnologieTechnology AufgabeResponsibility
SensorikSensingQorvo DWM3001CDKDistanzmessung per Time-of-FlightTime-of-flight ranging
Edge / GatewayESP32 · C++UART-Parsing, Filterung, UDP-VersandUART parsing, filtering, UDP transmission
BackendPython · Flask · flask-sockSpielzustand, REST-API, UDP-ListenerGame state, REST API, UDP listener
Control PlaneNode.js · ExpressScreen-Registry, Push an die WearablesScreen registry, push to the wearables
WearableESP32-S3 · LVGL 8 · WS2812Lokale Oberfläche und LED-FeedbackLocal interface and LED feedback
FrontendReact 19 · HTML5 CanvasSpielfeld, 60 Bilder pro SekundePlaying field at 60 frames per second
DeploymentDocker · nginx · Raspberry Pi 5Access Point, Container, Kiosk-AutostartAccess point, containers, kiosk autostart
Stand der DingeWhere it stands

Was läuft — und was nichtWhat works — and what does not

Ehrlichkeit ist an dieser Stelle nützlicher als Marketing. Was das System kann:

Honesty is more useful here than marketing. What the system does:

  • Lauffähiges Gesamtsystem aus fünf verteilten Komponenten, das per Knopfdruck auf einem Raspberry Pi startet.A working end-to-end system of five distributed components that boots on a Raspberry Pi at the press of a button.
  • Zwei Spielmodi mit vollständigem Zustandsabgleich über drei Geräte hinweg.Two game modes with full state synchronisation across three devices.
  • Visuelles Feedback über LED-Streifen und lokale Displays, synchron zum Spielgeschehen.Visual feedback via LED strips and local displays, in sync with gameplay.

Und was es nicht kann:

And what it does not:

  • Keine echte Trilateration. Das System misst Distanzen und bildet sie auf eine Achse ab; eine XY-Position wird nicht berechnet.No true trilateration. The system measures distances and maps them onto one axis; no XY position is computed.
  • Der Positionsstrom wird gepollt, nicht gepusht. Ein Push über den vorhandenen WebSocket wäre der nächste Schritt.The position stream is polled, not pushed. Pushing over the existing WebSocket would be the next step.
  • Keine gemessenen Latenzwerte. Das System ist auf niedrige Latenz ausgelegt, aber es liegen keine Messreihen vor.No measured latency figures. The system is designed for low latency, but no measurements were taken.
  • Keine automatisierten Tests. Verifiziert wurde gegen die reale Hardware.No automated tests. Verification was done against the real hardware.
MitgenommenTakeaways

Das Datenblatt ist ein Versprechen, kein VertragA datasheet is a promise, not a contract

Die UWB-Module konnten auf dem Papier alles, was die Architektur brauchte. Die Konsequenz für jedes weitere Projekt: den kritischsten Hardware-Pfad in der ersten Woche als Wegwerf-Prototyp bauen, bevor eine Architektur darauf aufsetzt.

On paper the UWB modules did everything the architecture needed. The consequence for every project after this one: build the most critical hardware path as a throwaway prototype in week one, before any architecture rests on it.

Und: Fehlersuche über Systemgrenzen hinweg ist die eigentliche Arbeit. Wenn ein Wert nicht ankommt, kann er an sechs Stellen verloren gegangen sein — UART, Parser, Filter, UDP, Docker-Bridge, Polling. Der Aufwand, an jeder dieser Grenzen eine Log-Ausgabe zu haben, zahlt sich innerhalb eines Tages aus.

And: debugging across system boundaries is the actual work. When a value fails to arrive it can have been lost in six places — UART, parser, filter, UDP, the Docker bridge, polling. The effort of having a log line at every one of those boundaries pays for itself within a day.