Skip to content

← Tornar a la llista

La UI web local: FastAPI dins l'app

Un comandament físic seria òptim. Construir-lo, no. Vaig posar la UI al navegador i la vaig fer servir des de qualsevol dispositiu de la xarxa.

3 min de lectura

Un comandament físic amb potenciòmetres seria òptim. Construir-lo, no. Vaig posar la UI al navegador i la vaig fer accessible des de qualsevol dispositiu de la xarxa local. Una decisió pragmàtica, no una declaració.

Esquema de la UI web local: capçalera amb estat de connexió, canvas de preview, sliders de tuning (FPS, gamma, kelvin, brightness) i franja de mètriques.

El context

Lumware corre al portàtil o a una Raspberry. Però vull poder ajustar la brillantor des del mòbil mentre toco la tira físicament a tres metres de la pantalla. Una app nativa per cada plataforma és absurda per a un projecte personal. Un servidor web local resol tot d’un cop:

  • Cap instal·lació al dispositiu de control — només un navegador.
  • Mateixa URL des de mòbil, tauleta o portàtil.
  • Zero permisos d’app store, zero builds creuades.

El cost és afegir un servidor al procés del host. Però el host ja és un procés persistent — un servidor més no canvia el model.

Preview = strip. Amb --transport virtual el host arrenca sense Arduino: el frame final acaba a un ring buffer en memòria i la UI rep exactament els mateixos bytes que rebria la tira. La primera vegada que arrenca, l’app sembra els patrons calib-*.png al directori de recursos per poder prémer Play immediatament.

La decisió

FastAPI per HTTP + WebSocket. React + Vite + Tailwind per al frontend, construït a estàtic i servit per la mateixa FastAPI. Tot empaquetat amb PyInstaller en un sol binari.

Dos canals WebSocket

/ws/framesbytes binaris crus. Cada missatge és un frame: N × 3 bytes RGB, sense JSON, sense capçaleres, sense codec. Exactament el mateix byte sequence que el codec envia al transport, però abans de l’encoding del frame binari (sense SOF, sense CRC). Per què: així el preview a la UI és per disseny el mateix que la tira veu. No hi pot haver “preview no coincideix amb tira” perquè és literalment la mateixa font.

/ws/eventsJSON dels events del bus. La UI rep PLAYBACK_METRICS, TRANSPORT_*, SETTINGS_CHANGED, etc. Dibuixa gràfics, actualitza l’estat de connexió, mostra logs. Si vols afegir una nova vista, subscriu-te a un event que ja existeix.

REST per al que no canvia 30 cops per segon

  • GET /api/health — viu?
  • GET /api/ports — autodetecció de ports sèrie.
  • POST /api/connection/connect{port, baud}.
  • POST /api/playback/{play,pause,stop}.
  • GET /api/resources — llistat d’imatges/vídeos.
  • PATCH /api/settings — patch parcial de Settings.

Cada handler agafa les seves dependències via Depends() de FastAPI. Els tests usen TestClient i construeixen les deps manualment — el factory create_app(...) és el seam.

LAN-only per disseny. No hi ha auth. CORS és wide-open. Això sembla una vulnerabilitat fins que recordes l’escenari real: un sol usuari, xarxa privada, dispositiu físic connectat al host per USB. Si algú té accés a la teva LAN i et vol fotre, té problemes més grossos que una API que canvia el gamma de la teva tira. Afegir login complica el codi, complica la UX (re-autenticar al mòbil cada cop?), i no aporta seguretat real al model d’amenaça. El servidor escolta a 0.0.0.0:8080 però el README és explícit: no exposeu a internet.

El frontend és React per pragmatisme

No per estètica. Vite + React + Tailwind és el que sé muntar més ràpid amb tests (vitest), tipat (tsconfig.app) i build determinístic. La story 07-07 empaqueta el dist/ de Vite dins del bundle PyInstaller — així el binari final inclou frontend i backend en un sol arxiu. Quan no hi ha build encara, FastAPI serveix un placeholder HTML amb la versió i una nota “compileu el frontend amb npm run build”.

El que ve

Tot el sistema és damunt la taula. Falta el moment de la veritat: els números. Quants FPS aconseguim realment, quina latència té el cable, on és el coll d’ampolla. Últim post de la sèrie: framerate i bottlenecks.