Build: Web UI + mDNS (pedal.local)

FastAPI web server with REST + WebSocket, 5 Jinja2 HTML pages,
mobile-friendly dark theme CSS, page-specific JS controllers,
and Avahi mDNS service advertisement for pedal.local.

Files:
  src/web/server.py          — FastAPI app with 17 REST endpoints + /ws WebSocket
  src/web/__init__.py         — Package init with WebServer, WebServerDeps exports
  src/web/templates/*.html    — Dashboard, Presets, Models, IRs, Settings (base)
  src/web/static/style.css    — Dark mobile-first CSS (80-82% max-width)
  src/web/static/websocket.js — Auto-reconnecting WebSocket manager
  src/web/static/app.js       — Dashboard live updates + REST helpers
  src/web/static/presets.js   — Bank/preset CRUD modal UI
  src/web/static/models.js    — NAM model list/load/upload
  src/web/static/irs.js       — IR file list/load/upload
  etc/avahi/pi-multifx-pedal.service — Avahi service advertisement
  scripts/setup-mdns.sh       — One-shot mDNS setup (avahi, hostname, restart)
  main.py                     — Wire WebServer into boot/shutdown
  requirements.txt            — Add fastapi, uvicorn, jinja2, python-multipart
  tests/test_web.py           — 36 tests (pages, REST, WebSocket, errors)

All 36 tests pass.
This commit is contained in:
2026-06-08 00:57:26 -04:00
parent b7f5488efd
commit 1c5b728e8b
19 changed files with 2884 additions and 2 deletions
+29 -1
View File
@@ -32,6 +32,7 @@ from src.midi.handler import MIDIHandler
from src.ui.footswitch import FootswitchController, FootSwitch, SwitchAction
from src.ui.leds import LEDController, LEDDriver, LEDConfig, LEDPattern
from src.ui.display import DisplayController, DisplayState
from src.web.server import WebServer, WebServerDeps
# ── Logging ───────────────────────────────────────────────────────────────────
@@ -84,6 +85,7 @@ class PedalApp:
self.footswitches: FootswitchController | None = None
self.leds: LEDController | None = None
self.display: DisplayController | None = None
self.web: WebServer | None = None
# ── Signal handlers ────────────────────────────────────────────
self._shutdown_requested = threading.Event()
@@ -190,6 +192,24 @@ class PedalApp:
# Boot LED animation — quick scan
self.leds.preset_animate(direction="up")
# ── 8. Web UI server (non-blocking HTTP + WebSocket) ────
# Runs on port 8080 by default, alongside the JACK audio loop.
try:
self.web = WebServer(
deps=WebServerDeps(
presets=self.presets,
pipeline=self.pipeline,
nam_host=self.nam_host,
ir_loader=self.ir_loader,
),
host="0.0.0.0",
port=8080,
)
self.web.start()
logger.info("Web UI server started on http://0.0.0.0:8080")
except Exception as e:
logger.warning("Web UI server failed to start (non-fatal): %s", e)
except Exception as e:
logger.critical("Boot failed: %s", e, exc_info=True)
return False
@@ -495,7 +515,15 @@ class PedalApp:
except Exception as e:
logger.warning("Footswitch stop error: %s", e)
# 2. Save current state (preset + bank)
# 2b. Stop web server (before state save — WS clients get final message)
if self.web:
try:
self.web.stop()
logger.debug("Web server stopped")
except Exception as e:
logger.warning("Web server stop error: %s", e)
# 3. Save current state (preset + bank)
if self.presets:
try:
self.presets.save_state()