Bring the docs in line with the code
- python-backend/README: track analysis is implemented (2e0e6ad); the cache layout, the module table (lirc, screen, timers, podcasts, tippen) and the route table (track analysis, HA proxy, typing game) were missing pieces. - docs/REPO_OVERVIEW: Mein Zimmer and the clock are implemented; Home Assistant is also reached over HTTP through the room page's proxy, not MQTT only; the IR remote, scripts/, the optional config sections and figure kind were not mentioned. - web/README: the key table was the mockup's (Ctrl transport keys, TAB over 'all'); ?pi=1 is now a preset with per-browser overrides, not a read-once flag; the room page and the typing game exist; eslint is part of the checks. - Docstrings that described earlier designs: HaConfig (written as browser-direct in the same commit that built the proxy,a8ed350), services/__init__, events, playback, media, colors, tippen progress and AppHeader, lowPower.ts, and the 'Hoerbuecher' spelling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -94,7 +94,10 @@ the prompt becomes a regression test by pasting it into a `.txt` file.
|
||||
| `musicmouse/reactions/` | The policy. `@on(SomeEvent)` functions that get the app and act. |
|
||||
| `musicmouse/library/` | The music collection: scanning, tags, cover art and its colours, the cache, and the seams for future track analysis. |
|
||||
| `musicmouse/services/mqtt/` | Home Assistant entities: three lights, a player sensor, a volume number, transport buttons, device triggers, a tag scanner. |
|
||||
| `musicmouse/services/web/` | The browser front-end's API: the library, a state websocket, command endpoints, and parent-mode settings. |
|
||||
| `musicmouse/services/web/` | The browser front-end's API: the library, a state websocket, command endpoints, parent-mode settings, the typing game and the Home Assistant proxy. |
|
||||
| `musicmouse/services/lirc/` | The IR remote, as a lircd TCP client. |
|
||||
| `musicmouse/services/` (`screen.py`, `timers.py`, `podcasts.py`) | The monitor's on/off and idle auto-off; the parents' timer and school-day calendar; the podcast feed poller. |
|
||||
| `musicmouse/tippen/` | The typing game's curriculum, progress and track unlocks. |
|
||||
| `musicmouse/simulator/` | Fake transport and player, the driver vocabulary, the REPL and the script runner. |
|
||||
| `musicmouse/config.py` | Pydantic schema, validation, and human-readable error formatting. |
|
||||
|
||||
@@ -186,8 +189,9 @@ different amounts to produce:
|
||||
|
||||
```
|
||||
index.json cheap: tags and structure. Rebuilt freely.
|
||||
covers/<album_id>.jpg medium: art extracted from an ID3 APIC frame
|
||||
analysis/<track_key>.json expensive: reserved for offline audio analysis
|
||||
covers/<album_id>.jpg medium: the album's art, downscaled to 384 px
|
||||
covers/<album_id>.thumb.jpg medium: the same at 256 px, for cards and rows
|
||||
analysis/<track_key>.json expensive: track analysis (plus .beats.json, .curve.json)
|
||||
```
|
||||
|
||||
Deleting the whole directory is safe; deleting it throws away analysis that is minutes
|
||||
@@ -199,13 +203,16 @@ change to how the scanner derives a title, artist or series is invisible until t
|
||||
is invalidated: bump `_INDEX_VERSION` in `musicmouse/library/cache.py` when you touch
|
||||
that logic.
|
||||
|
||||
**Track analysis is not implemented.** `musicmouse/library/analysis.py` fixes the shape
|
||||
of the results - scalars (`tempo`, `energy`, `valence`, `brightness`) travel inline with
|
||||
the index, and a beat grid lives in its own file and is fetched per track - so an
|
||||
analyzer can be added later without touching the scanner, the API or the frontend. It
|
||||
will go behind an optional dependency group, and because results are content-keyed
|
||||
files, they can equally well be computed on a workstation and the `analysis/` folder
|
||||
copied to the device.
|
||||
**Track analysis** drives the web front-end's ambient background. It is optional: install
|
||||
the `analysis` extra (`pip install -e '.[analysis]'`, librosa and numpy) and
|
||||
`musicmouse/library/librosa_analyzer.py` works through the music shelf in the
|
||||
background, in a pool of niced worker processes (`musicmouse/library/workers.py`), and
|
||||
pauses while anything is playing. Without the extra, `build_analyzer()` returns a no-op
|
||||
and the background stays static. Scalars (`tempo`, `energy`, `valence`, `brightness`,
|
||||
`pulse`) travel inline with the index; the beat grid and per-second curves live in their
|
||||
own files and are fetched only for the playing track. Results are content-keyed, so they
|
||||
can be computed on a workstation and the `analysis/` folder copied to the device
|
||||
(`scripts/sync-library.sh` does that).
|
||||
|
||||
## The web front-end
|
||||
|
||||
@@ -226,6 +233,9 @@ copied to the device.
|
||||
| `POST /api/screen/toggle` `/screen/activity` | the kiosk's Del key; its key presses and taps, holding off the idle screen-off |
|
||||
| `GET` `PUT /api/settings` | parent mode |
|
||||
| `POST` `DELETE /api/timer` | parent mode's countdown: `{minutes}` or `{until: "HH:MM"}`; stop or silence it |
|
||||
| `GET /api/tracks/{album_id}/{index}/analysis` | beat grid and mood curves of one track, or 404 |
|
||||
| `GET /api/ha`, `GET /api/ha/states/{entity}`, `POST /api/ha/services/{domain}/{service}` | the room page's Home Assistant proxy; 404 without an `ha` section |
|
||||
| `GET /api/tippen/curriculum` `/progress`, `PUT /api/tippen/settings`, `POST /api/tippen/runs` | the typing game; 404 without a `tippen` section |
|
||||
|
||||
Two decisions worth knowing:
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ class ClockConfig(_Strict):
|
||||
class LibraryConfig(_Strict):
|
||||
"""Where the music lives.
|
||||
|
||||
One path. The shelves underneath it - ``Figuren``, ``Musik``, ``Hoerbuecher``,
|
||||
One path. The shelves underneath it - ``Figuren``, ``Musik``, ``Hörbücher``,
|
||||
``Kinderpodcasts`` - are fixed names, not settings; see
|
||||
:mod:`musicmouse.library.sections`.
|
||||
"""
|
||||
@@ -259,10 +259,10 @@ class HaDeviceConfig(_Strict):
|
||||
class HaConfig(_Strict):
|
||||
"""Home Assistant integration for the room-control page ("Mein Zimmer").
|
||||
|
||||
The backend never calls Home Assistant itself - it only hands the browser the
|
||||
server URL, the token, and these two ordered lists. Control happens directly from
|
||||
the browser to Home Assistant's own REST API, so this token grants full HA control
|
||||
to anything on the LAN that can reach musicmouse. See config.yml.example.
|
||||
The browser only gets the two ordered lists. Every state read and service call goes
|
||||
through the backend's ``/api/ha/...`` proxy, which attaches the token - so the token
|
||||
never leaves the backend. The proxy itself is unauthenticated, though: anything on
|
||||
the LAN that can reach musicmouse can use it. See config.yml.example.
|
||||
"""
|
||||
|
||||
url: str
|
||||
|
||||
@@ -7,8 +7,8 @@ Three flavours, distinguished by base class:
|
||||
* :class:`StateEvent` - something changed.
|
||||
|
||||
The intent layer is what lets several front-ends drive the same behaviour: a button
|
||||
press, an MQTT command and a future web request all emit ``NextTrackRequested`` and a
|
||||
single reaction acts on it.
|
||||
press, an MQTT command, a web request and the IR remote all emit ``NextTrackRequested``
|
||||
and a single reaction acts on it.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
The frontend paints an album card with these and the LED strips run an effect in them,
|
||||
so a web-started album looks the same on the shelf as it does on the screen. Extraction
|
||||
happens once per album ever - the result is cached next to the cover.
|
||||
happens when an album is scanned; the result is stored in the cached index.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""Playlist model.
|
||||
|
||||
Deliberately a real type rather than a bare ``list[str]``: it is what the browse API
|
||||
serves, and it keeps track metadata in one place. Building one from a folder is
|
||||
:mod:`musicmouse.library`'s job.
|
||||
What the player plays: a name and the files, in order. The browse API serves
|
||||
:class:`~musicmouse.library.models.Album` instead, which carries the tag metadata;
|
||||
``Album.to_playlist()`` builds one of these from it.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""What the mouse plays, and when.
|
||||
|
||||
Physical inputs are turned into *intents*, and the intents are what actually drive the
|
||||
player. That indirection is the point: an MQTT command or a future web request emits
|
||||
the same intent and lands in the same handler, so there is one place per behaviour.
|
||||
player. That indirection is the point: an MQTT command, a web request or an IR remote key
|
||||
emits the same intent and lands in the same handler, so there is one place per behaviour.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"""Front-ends: things that mirror state outwards and turn requests into intents.
|
||||
|
||||
``MqttService`` is the reference implementation. A web service would be another file
|
||||
here plus one line in the app - devices and reactions would not change.
|
||||
MQTT (Home Assistant), web (the browser), lirc (the IR remote) and the screen, timer and
|
||||
podcast services each live here; adding another is a file here plus a line in
|
||||
``__main__._build_services`` - devices and reactions do not change.
|
||||
"""
|
||||
|
||||
from musicmouse.services.base import Service
|
||||
|
||||
@@ -5,8 +5,8 @@ Same convention as everywhere else this codebase persists something outside
|
||||
missing or corrupt file is just a fresh start, never a crash: nothing here is precious
|
||||
enough to raise over.
|
||||
|
||||
``record_run`` is a near-verbatim port of tippen's own (client-side, until now)
|
||||
``recordRun`` in ``tippen/src/lib/progress.ts`` - grading a keystroke-by-keystroke run
|
||||
``record_run`` is a port of the ``recordRun`` the standalone typing app had client-side
|
||||
before it was merged into the web front-end - grading a keystroke-by-keystroke run
|
||||
into stars/points/an animal stays entirely client-side (see ``TippenRunIn`` in
|
||||
``musicmouse.services.web.schemas``); this only owns what happens to progress once a
|
||||
graded result arrives.
|
||||
|
||||
Reference in New Issue
Block a user