Web frontend
This commit is contained in:
@@ -17,9 +17,10 @@ Assistant integration.
|
||||
| Path | What it is |
|
||||
|---|---|
|
||||
| `python-backend/` | Python host application — the main runtime. Has its own `README.md` with the architecture; start there for backend work. |
|
||||
| `web/` | React + TypeScript front-end (Vite). Browse the whole library and play any of it from a browser. Built output is served by `python-backend` itself. |
|
||||
| `esp-firmware/` | ESP32 firmware (C++, Arduino framework via PlatformIO). Reads the RFID reader and buttons, drives the LED strips, talks to `python-backend` over serial. |
|
||||
| `hardware/` | 3D-print models for the figurines and enclosure (FreeCAD/Blender/OBJ/STL), a Fritzing electronics sketch, datasheets, and `pinout.md` (RFID reader + button-board wiring). |
|
||||
| `claude-design/` | An unrelated exploratory web-UI mockup ("Dolphin Beats"). Not integrated with anything — no build system ties it in. Don't assume it reflects current product direction. |
|
||||
| `claude-design/` | The interaction and visual spec the web front-end was built from ("Dolphin Beats"), as a standalone HTML mockup with hardcoded data. `web/` is the real implementation; the mockup is kept as the reference for the keyboard model and the styling. It also contains a second page, "Mein Zimmer" (room lights), which is **not** implemented. |
|
||||
|
||||
## How the pieces talk to each other
|
||||
|
||||
@@ -34,6 +35,12 @@ Assistant integration.
|
||||
exact bytes of every effect payload — so a firmware change that breaks the host now
|
||||
breaks a test instead of just the LEDs.
|
||||
|
||||
- **`python-backend` ↔ `web/`**: JSON over HTTP for the library and for commands, plus a
|
||||
push-only websocket at `/api/ws` for state. Commands emit exactly the same *intents*
|
||||
the physical buttons emit, so the web UI has no privileged path — and no way to get
|
||||
out of step with a figure someone puts on the reader. See
|
||||
`python-backend/README.md` for the endpoint list.
|
||||
|
||||
- **`python-backend` ↔ Home Assistant**: MQTT only. The backend publishes three
|
||||
discoverable lights, a player sensor, a volume number, transport buttons, device
|
||||
triggers for every button and touch area, and a tag scanner for the RFID reader. It
|
||||
@@ -47,7 +54,17 @@ Assistant integration.
|
||||
python -m musicmouse --config /media/musicmouse/config.yml
|
||||
```
|
||||
|
||||
Or, with no hardware and no audio:
|
||||
On a machine with no mouse attached — real audio and a real web UI, no serial port:
|
||||
|
||||
```sh
|
||||
python -m musicmouse --config ./config.yml --no-hardware
|
||||
```
|
||||
|
||||
`general.serial_port: simulate` does the same thing from the config, and
|
||||
`general.alsa_device: simulate` swaps in a silent player. Both warn at startup, and
|
||||
both keys are required - omitting one is an error rather than an implicit simulation.
|
||||
|
||||
Or with no hardware *and* no audio:
|
||||
|
||||
```sh
|
||||
python -m musicmouse --config ./config.yml --simulate
|
||||
@@ -57,19 +74,38 @@ The simulator runs the whole app against a fake serial link and a fake player, e
|
||||
interactively or from a scenario file. `python-backend/musicmouse.service` is the
|
||||
systemd unit for the device.
|
||||
|
||||
For front-end work, run the backend (either way above) and then:
|
||||
|
||||
```sh
|
||||
cd web && npm install && npm run dev # http://localhost:5173, /api proxied to :8080
|
||||
```
|
||||
|
||||
`npm run build` writes `web/dist`, which `general.web.static_dir` points at in
|
||||
production so one process serves both the UI and the API.
|
||||
|
||||
## Config
|
||||
|
||||
`python-backend/config.yml.example` documents the schema. In short:
|
||||
|
||||
- `general.{figure_folder, serial_port, baudrate, reconnect_interval, alsa_device,
|
||||
- `general.{library.*, serial_port, baudrate, reconnect_interval, alsa_device,
|
||||
min_volume, max_volume, initial_volume, volume_increment, button_leds_brightness,
|
||||
audio_extensions, mqtt.*}`
|
||||
audio_extensions, mqtt.*, web.*}`
|
||||
- `figures.<name>.{id, colors}` — `id` is a 5-byte hex RFID tag, unique per figure;
|
||||
`colors` is exactly four (`primary, secondary, bg, accent`), each `"#rrggbb"` or
|
||||
`"wNN"`.
|
||||
|
||||
Each figure plays `<figure_folder>/<figure name>`, alphabetically by filename. Config is
|
||||
validated with pydantic: unknown keys are errors, and every problem is reported at once.
|
||||
`general.library.root` is the one path to the music. The shelves under it are fixed
|
||||
names, not settings, because each has quirks the scanner knows about:
|
||||
|
||||
```
|
||||
<root>/Figuren/<figure name>/ one folder per figurine
|
||||
<root>/Musik/<Artist> - <Album>/ albums, grouped by artist
|
||||
<root>/Hörbücher/<Artist> - <Album>/ audiobooks, grouped by character
|
||||
<root>/Kinderpodcasts/<Show>/ shows, newest episode first
|
||||
```
|
||||
|
||||
Config is validated with pydantic: unknown keys are errors, and every problem is
|
||||
reported at once.
|
||||
|
||||
## Notes for agents
|
||||
|
||||
@@ -80,3 +116,10 @@ validated with pydantic: unknown keys are errors, and every problem is reported
|
||||
- `python-backend/notebooks/` is university course material on chord recognition, not
|
||||
part of the app.
|
||||
- The firmware has no automated tests beyond a PlatformIO `native` env for LED effects.
|
||||
- The front-end has `vitest` over the pure modules (`web/src/lib/`) and `tsc --noEmit`;
|
||||
there is no component-level test harness. `npm run test` and `npx tsc --noEmit` are
|
||||
the two checks.
|
||||
- The library index is cached under `general.library.cache`. It is keyed by file mtime
|
||||
and size, so **a change to how the scanner derives a title, artist or series is
|
||||
invisible until the cache is invalidated** — bump `_INDEX_VERSION` in
|
||||
`musicmouse/library/cache.py` when you touch that logic.
|
||||
|
||||
Reference in New Issue
Block a user