Pin each MusicMouse host to a deploy branch, and freeze musicmouse
musicmouse_version pointed both hosts at rearchitect-backend, a branch that only ever existed on a laptop - so the previous commit's rework could never actually have run. Point each host at a deploy/<host> branch on the remote instead. Those branches are the deployment record: what is installed on that box right now, moved with a force-push when you mean to deploy. musicmouse (Kinderzimmer) is the reason this matters. It is in daily use and still runs the pre-rearchitecture backend out of /opt/musicmouse_venv and espmusicmouse/host_driver/main.py. That commit has no python-backend/ pyproject.toml, no python-backend/musicmouse.service and no web/ at all, so this role cannot install it - and a run would have force-checked-out incompatible code over a working device first and failed afterwards. Set mediapi_install_kidsmusic: false there and leave it alone; deploy/musicmouse records the commit that is installed, for rollback. Add a stat + fail right after the checkout so the same mistake stops with an explanation instead of failing three tasks deeper. Also in the role: - Install the typing game's curriculum next to config.yml. A config with a tippen: section refuses to start without it - curriculum_file is validated as must-exist - so musicdolphin's new config needs it present. - Stop clobbering config.yml. The app writes to it at runtime: parent mode patches the volume keys and the remote-control page rewrites the remote: block. A plain copy: undid that on every run. Install-once by default, with -e pi_musicmouse_force_config=true to push a change deliberately. - Fill out config-musicdolphin.yml against the current schema: library root and cache, an absolute static_dir (../web/dist would resolve to /media/web/dist from /media/musicmouse and silently serve nothing), tippen, and the volume range for a screen-driven instance. ha: is left commented out until the Wohnzimmer-oben entity ids are picked - the ones in the dev config are Arbeitszimmer. No mqtt: (it would publish a device of dead entities on a simulate-only host) and no lirc:/remote: (this host runs irserver, a serial daemon, not the lircd this speaks TCP to). The curriculum is a verbatim copy of app content, so exclude it from ansible-lint rather than reformat it away from its source. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,8 +2,9 @@
|
||||
|
||||
Deploys the MusicMouse backend (`python-backend/`) and its built web frontend.
|
||||
|
||||
- Checks out `musicmouse_version` (a tag or branch - set per host in `host_vars`, no
|
||||
default) from `musicmouse_repo` to `/opt/musicmouse`.
|
||||
- Checks out `musicmouse_version` from `pi_musicmouse_repo` to `/opt/musicmouse`, then
|
||||
**fails early** if that checkout has no `python-backend/pyproject.toml` - see
|
||||
"Which version" below.
|
||||
- Installs a pinned `uv` release and uses it to create `/opt/musicmouse/.venv` with
|
||||
Python `pi_musicmouse_python_version` (the backend needs 3.12+ for PEP 695 syntax;
|
||||
Raspbian Bookworm's apt python3 is only 3.11) and `pip install` the backend into it.
|
||||
@@ -11,10 +12,55 @@ Deploys the MusicMouse backend (`python-backend/`) and its built web frontend.
|
||||
the Pi - no Node/npm is installed on the Pi) and syncs `web/dist` over.
|
||||
- Installs `/media/musicmouse/config.yml` from `files/config-<inventory_hostname>.yml`
|
||||
(one file per host - the schema differs between a host with a real mouse attached and
|
||||
a display-only one; see those files' own comments) and the systemd unit copied
|
||||
straight from the checked-out repo's `python-backend/musicmouse.service`, so it can
|
||||
never drift from what's actually in the app repo.
|
||||
a display-only one; see those files' own comments), the typing game's curriculum
|
||||
beside it, and the systemd unit copied straight from the checked-out repo's
|
||||
`python-backend/musicmouse.service`, so it can never drift from what's actually in the
|
||||
app repo.
|
||||
- Sets up a Samba share of `/media/musicmouse` for copying music onto the device.
|
||||
|
||||
Manual steps after a first deploy: set the Samba password with `smbpasswd`, and copy
|
||||
music into `/media/musicmouse` (or via the Samba share).
|
||||
## Which version
|
||||
|
||||
`musicmouse_version` is set per host in `host_vars`, with no default, and points at a
|
||||
`deploy/<host>` branch on the remote. Those branches are the deployment record: what is
|
||||
installed on that box right now. Moving one is a force-push, and the next run follows it
|
||||
(the `git:` task uses `force: true`):
|
||||
|
||||
git push -f origin rearchitect-backend:deploy/musicdolphin
|
||||
just mediapis musicdolphin
|
||||
|
||||
This role only understands the post-rearchitecture layout - `python-backend/` as an
|
||||
installable package plus a `web/` frontend. On older commits the host driver lives at
|
||||
`espmusicmouse/host_driver/main.py`, there is no `pyproject.toml`, the unit file is
|
||||
under `esp-firmware/`, there is no frontend at all, and the config is a different format
|
||||
that this role's config files would fail to load. There is deliberately no legacy path:
|
||||
the role stops at the checkout with an explanation instead.
|
||||
|
||||
`musicmouse` (Kinderzimmer) is on such an older commit and still working, so it has
|
||||
`mediapi_install_kidsmusic: false` and this role skips it entirely; `deploy/musicmouse`
|
||||
exists purely as the rollback marker for what is installed there. Migrating it means
|
||||
moving `/media/musicmouse/<figure>/` into `Figuren/` and switching to the new config
|
||||
schema - `files/config-musicmouse.yml` is already written for that.
|
||||
|
||||
## config.yml is install-once
|
||||
|
||||
The app writes to its own config at runtime: parent mode (`?parentMode=1`) patches the
|
||||
volume keys, and the remote-control page rewrites the whole `remote:` block. A plain
|
||||
`copy:` would throw those away on every ansible run, so the config task is
|
||||
`force: "{{ pi_musicmouse_force_config }}"`, default `false`. Push a changed config when
|
||||
you actually mean to:
|
||||
|
||||
just run mediapis.yml musicdolphin -e pi_musicmouse_force_config=true
|
||||
|
||||
The curriculum file is not written by the app, so it is installed unconditionally. A
|
||||
config with a `tippen:` section will not start without it - `curriculum_file` is
|
||||
validated as must-exist - so a host whose config has no `tippen:` section should set
|
||||
`pi_musicmouse_curriculum_file: ""`.
|
||||
|
||||
## Manual steps after a first deploy
|
||||
|
||||
- Set the Samba password with `smbpasswd`.
|
||||
- Get the music onto the device. The app repo's `scripts/sync-library.sh` is the way to
|
||||
do it: as well as the library it carries over the cover and track-analysis cache,
|
||||
which is the part that costs minutes of DSP per track to recompute, and it knows which
|
||||
parts of that cache survive the move and which must not be copied. The Samba share
|
||||
works too, and then the device does all that work itself.
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
---
|
||||
pi_musicmouse_repo: "ssh://git@git.bauer.tech:2222/martin/musicmouse.git"
|
||||
# No default on purpose: which tag/branch to run is a per-host decision, set in host_vars.
|
||||
# musicmouse_version: "rearchitect-backend"
|
||||
# No default on purpose: which branch to run is a per-host decision, set in host_vars.
|
||||
# The deploy/<host> branches on the remote are the intended values - move one with
|
||||
# `git push -f origin <branch>:deploy/musicdolphin` and re-run this role.
|
||||
# musicmouse_version: "deploy/musicdolphin"
|
||||
|
||||
pi_musicmouse_python_version: "3.13"
|
||||
pi_musicmouse_uv_version: "0.12.10"
|
||||
@@ -13,3 +15,16 @@ pi_musicmouse_build_cache: "/tmp/musicmouse-build-cache"
|
||||
|
||||
# Which files/config-*.yml to install as this host's /media/musicmouse/config.yml.
|
||||
pi_musicmouse_config_file: "config-{{ inventory_hostname }}.yml"
|
||||
|
||||
# The app owns config.yml at runtime: parent mode writes the volume keys back to it, and
|
||||
# the remote-control page rewrites its `remote:` block. So the config is installed once
|
||||
# and then left alone, or every ansible run would silently undo those edits. Push a
|
||||
# changed config deliberately:
|
||||
#
|
||||
# just run mediapis.yml musicdolphin -e pi_musicmouse_force_config=true
|
||||
pi_musicmouse_force_config: false
|
||||
|
||||
# The typing game's lesson plan, installed next to config.yml. A config with a `tippen:`
|
||||
# section will not start without it - curriculum_file is validated as must-exist. Set to
|
||||
# "" for a host whose config has no tippen section.
|
||||
pi_musicmouse_curriculum_file: "tippen-curriculum.yml"
|
||||
|
||||
@@ -1,25 +1,89 @@
|
||||
---
|
||||
# musicdolphin: display-only instance. No physical mouse attached (for
|
||||
# now), so serial and audio are both "simulate" - the web front-end (shown locally in
|
||||
# the pi_kiosk role's Firefox kiosk window, and reachable on the LAN for a tablet) is a
|
||||
# complete way to drive the player on its own. `figures` still needs at least one entry
|
||||
# (the schema requires it) even though nothing will ever scan a tag here.
|
||||
# musicdolphin: display-only instance. No physical mouse attached, so serial and audio
|
||||
# are both "simulate" - the web front-end (shown locally in the pi_kiosk Firefox window,
|
||||
# and reachable on the LAN for a tablet) is a complete way to drive the player on its
|
||||
# own. `figures` still needs at least one entry (the schema requires it) even though
|
||||
# nothing will ever scan a tag here.
|
||||
#
|
||||
# This Pi does have a HifiBerry amp (mediapi_has_hifiberry_amp, alsa_card_name
|
||||
# sndrpihifiberry), so the `softvol_effects` slot from pi_alsasetup exists on it.
|
||||
# Swapping alsa_device to that is the one-line change that gives this instance real
|
||||
# sound - the amp is currently shared by squeezelite and shairport only.
|
||||
#
|
||||
# Unknown keys are rejected rather than ignored, so a typo fails at startup with the
|
||||
# path to the offending line. Every key is documented in the app repo's
|
||||
# python-backend/config.yml.example.
|
||||
general:
|
||||
library:
|
||||
# The four shelves underneath are fixed names, not settings: Figuren/, Musik/,
|
||||
# Hoerbuecher/ (with real umlauts) and Kinderpodcasts/.
|
||||
root: /media/musicmouse
|
||||
# Relative, so it resolves against *this file's* directory -
|
||||
# /media/musicmouse/.musicmouse-cache. Seeded by the app repo's
|
||||
# scripts/sync-library.sh; safe to delete, at the cost of recomputing analysis.
|
||||
cache: .musicmouse-cache
|
||||
# analysis_workers: 2
|
||||
# ^ pointless until this role installs the backend's `analysis` extra
|
||||
# (librosa + numpy, a heavy build on a Pi). Without it build_analyzer() falls
|
||||
# back to NullAnalyzer and the reactive background stays static.
|
||||
|
||||
# Both required rather than optional on purpose: running blind or silent has to be
|
||||
# asked for, so a config that lost a line fails loudly instead of booting into
|
||||
# something that looks like it is working. Startup says so every boot.
|
||||
serial_port: "simulate"
|
||||
alsa_device: "simulate"
|
||||
|
||||
web:
|
||||
host: "0.0.0.0"
|
||||
port: 8080
|
||||
# Absolute on purpose. The repo's example uses ../web/dist because its config sits
|
||||
# in python-backend/; from /media/musicmouse that would resolve to /media/web/dist
|
||||
# and silently serve nothing.
|
||||
static_dir: /opt/musicmouse/web/dist
|
||||
|
||||
# The typing game. Both paths are relative, so both land in /media/musicmouse/.
|
||||
# curriculum_file must exist at startup and is installed by this role;
|
||||
# progress_file is created and written by the app itself.
|
||||
tippen:
|
||||
curriculum_file: tippen-curriculum.yml
|
||||
progress_file: tippen-progress.json
|
||||
|
||||
# Home Assistant "Mein Zimmer" page. Left out until the Wohnzimmer-oben entity ids
|
||||
# are picked - the dev config's entities are Arbeitszimmer, the wrong room for this
|
||||
# Pi. An empty ha: block is invalid; the section needs at least one device or scene,
|
||||
# so uncomment it only once it is filled in:
|
||||
#
|
||||
# ha:
|
||||
# url: "https://ha.bauer.tech"
|
||||
# token: "<long-lived access token>"
|
||||
# devices:
|
||||
# - entity_id: cover.wohnzimmer_oben_rollo
|
||||
# name: "Rollo"
|
||||
# - entity_id: light.wohnzimmer_oben_...
|
||||
# name: "Licht"
|
||||
# scenes:
|
||||
# - entity_id: scene.wohnzimmer_oben_...
|
||||
# name: "Lesen"
|
||||
|
||||
# No mqtt: section. The Home Assistant integration exposes the *mouse's* lights,
|
||||
# buttons, touch areas and tag scanner, none of which exist on a simulate-only
|
||||
# instance - it would publish a device of dead entities.
|
||||
#
|
||||
# No lirc: section either, and consequently no top-level `remote:` mapping. This host
|
||||
# uses mediapi_ir_control: irserver, a custom serial daemon on /dev/ttyUSB0; the
|
||||
# backend's LircConfig speaks lircd's TCP protocol (pi_lirc, port 2222) and cannot
|
||||
# talk to it.
|
||||
|
||||
# Backlight of the prev/next buttons while a figure is playing, 0..1. Inert here
|
||||
# (no mouse), kept so the file reads the same as the other hosts'.
|
||||
button_leds_brightness: 0.5
|
||||
volume_increment: 5
|
||||
|
||||
# Volume, 0..100. The full range: this one is driven from a screen, not from a knob a
|
||||
# child can spin, so there is nothing to protect against.
|
||||
min_volume: 0
|
||||
max_volume: 100
|
||||
initial_volume: 40
|
||||
volume_increment: 5
|
||||
|
||||
figures:
|
||||
# Placeholder - serial_port is "simulate" so no reader ever reports a tag id, but the
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
---
|
||||
# NOT IN USE. The Kinderzimmer device is frozen on the pre-rearchitecture backend
|
||||
# (host_vars/musicmouse.yml sets mediapi_install_kidsmusic: false, and deploy/musicmouse
|
||||
# records the commit that is installed), so this role never installs this file today.
|
||||
# It is kept because it is already written against the new schema and is what the
|
||||
# migration will use - which also means moving /media/musicmouse/<figure>/ into
|
||||
# Figuren/, since the new scanner expects the figures under that shelf.
|
||||
general:
|
||||
library:
|
||||
root: /media/musicmouse
|
||||
|
||||
527
roles/pi_musicmouse/files/tippen-curriculum.yml
Normal file
527
roles/pi_musicmouse/files/tippen-curriculum.yml
Normal file
@@ -0,0 +1,527 @@
|
||||
# The typing game's lesson plan, referenced from config.yml's general.tippen.curriculum_file.
|
||||
#
|
||||
# Every lesson: title, subtitle (read aloud). Then either:
|
||||
# - keys: the letters-kind keys this round has on the table. At most two of them may
|
||||
# be new; the rest are keys already taught, written out to say "these are in play
|
||||
# too". The loader tracks which keys were already active and spotlights only what is
|
||||
# new, so `keys: [f, j, a, d, k]` right after `[f, j, a, d]` is a K lesson with the
|
||||
# other four as review ("isolated" - heavy weight on K). A round that introduces
|
||||
# nothing new spotlights its whole list instead ("mixed" - lighter weight, blended
|
||||
# with everything learned so far), so two lessons with the same `keys` back to back
|
||||
# is exactly how you write "isolated, then mixed" - no separate flag needed.
|
||||
# - drill: true - a pure review round, no new content, whatever is active so far.
|
||||
# - kind + words - a fragments/words/sentences consolidation round (dive mode by
|
||||
# default; `mode:` overrides it - see the backend's `ELIGIBLE_MODES` for which
|
||||
# modes fit which kind).
|
||||
#
|
||||
# Letters rounds always play in bubbles mode - the only arcade game that fits a single
|
||||
# key at a time.
|
||||
#
|
||||
# Letter order follows German letter frequency, adapted to a home-row-first, mirrored
|
||||
# pace for a six-year-old.
|
||||
#
|
||||
# Optional reward key, on any lesson:
|
||||
#
|
||||
# unlocks: <path>
|
||||
#
|
||||
# A path into the music library, written relative to config.yml's
|
||||
# general.library.root - "Musik/Klaviersammlung/Lied.mp3", not a path from "/" or "~".
|
||||
# (An absolute path, or one starting with "~", still resolves, so an old file keeps
|
||||
# working - but relative is the form to write, since it survives moving the library.)
|
||||
# Passing this lesson (two stars, or five attempts
|
||||
# regardless of score) unlocks that track and everything before it in the same album,
|
||||
# inclusive - so pointing at an album's last track unlocks the whole album, and
|
||||
# pointing at track 5 of an audiobook unlocks chapters 1 through 5. The web front-end
|
||||
# shows a still-locked track as a question mark, with a note on which lesson unlocks it.
|
||||
#
|
||||
# For a podcast (one album per episode under Kinderpodcasts), the same key unlocks that
|
||||
# episode and every earlier one of the same show, oldest first - so pointing at the
|
||||
# third episode ever published unlocks the first three, regardless of upload order.
|
||||
#
|
||||
# A path that matches nothing in the library is logged as a warning at startup, not a
|
||||
# fatal error - a moved or mistyped path only loses that one reward, never the app.
|
||||
#
|
||||
# Unlike config.yml, this file is never written back to by the app - only progress is.
|
||||
|
||||
worlds:
|
||||
- number: 1
|
||||
title: "Die Grundstellung"
|
||||
emoji: "🏝️"
|
||||
reward: clownfish
|
||||
lessons:
|
||||
- title: "F und J"
|
||||
subtitle: "Die Zeigefinger - die Tasten mit den Punkten"
|
||||
keys: [f, j]
|
||||
- title: "Mit A"
|
||||
subtitle: "Das A kommt dazu"
|
||||
keys: [f, j, a]
|
||||
- title: "Ja"
|
||||
subtitle: "Jaja ;)"
|
||||
kind: fragments
|
||||
words: [ja, fa, aja, afa, faja]
|
||||
- title: "Wörter"
|
||||
subtitle: "Aja"
|
||||
kind: fragments
|
||||
mode: feed
|
||||
words: [ja, fa, jafa, aja, ajaja]
|
||||
unlocks: "Musik/Klaviersammlung/Coffin Dance - Astronomía 2K.mp3"
|
||||
- title: "D"
|
||||
subtitle: "Das D kommt dazu"
|
||||
keys: [f, j, a, d]
|
||||
- title: "Wörter"
|
||||
subtitle: "ja da"
|
||||
kind: fragments
|
||||
words: [da ja]
|
||||
- title: "Wörter"
|
||||
subtitle: "ja da"
|
||||
kind: fragments
|
||||
mode: feed
|
||||
words: [da ja ada adda fada aja jaja]
|
||||
- title: "Wörter"
|
||||
subtitle: "ja da"
|
||||
kind: fragments
|
||||
words: [da ja ada adda fada aja jaja ajaja afa fa dafa jafa jada jajaja]
|
||||
- title: "K"
|
||||
subtitle: "Der rechte Mittelfinger"
|
||||
keys: [f, j, a, d, k]
|
||||
- title: "Wörter"
|
||||
subtitle: "kaka"
|
||||
kind: fragments
|
||||
words: [ka, ak, kak, akka, kaka, daka, kada, jaka, kaja, fakka, kakka]
|
||||
- title: "Wörter"
|
||||
subtitle: "kada jaka"
|
||||
kind: fragments
|
||||
mode: feed
|
||||
words: [ja, da, ada, adda, fada, aja, jaja, afa, dafa, jafa, jada,
|
||||
ka, ak, kak, akka, kaka, daka, kada, jaka, kaja, fakka,
|
||||
ka, ak, kak, akka, kaka, daka, kada, jaka, kaja, fakka]
|
||||
- title: "S"
|
||||
subtitle: "Der linke Ringfinger"
|
||||
keys: [f, j, a, d, k, s]
|
||||
- title: "Wörter"
|
||||
subtitle: "sassa"
|
||||
kind: fragments
|
||||
words: [sa, as, sas, assa, sass, das, dasa, sada, kasa, saka, fasa, jassa]
|
||||
- title: "Wörter"
|
||||
subtitle: "das sassa"
|
||||
kind: fragments
|
||||
mode: feed
|
||||
words: [ja, da, ada, adda, fada, aja, jaja, afa, dafa, jafa, jada,
|
||||
ka, kak, akka, kaka, daka, kada, jaka, kaja,
|
||||
sa, as, sas, assa, sass, das, dasa, sada, kasa, saka, fasa, jassa,
|
||||
sa, as, sas, assa, sass, das, dasa, sada, kasa, saka, fasa, jassa]
|
||||
# Unlocks track 2 of Klaviersammlung, Coffin Dance already being track 1.
|
||||
unlocks: "Musik/Klaviersammlung/Shakira - Dai Dai.mp3"
|
||||
- title: "L"
|
||||
subtitle: "Der rechte Ringfinger"
|
||||
keys: [f, j, a, d, k, s, l]
|
||||
- title: "Wörter"
|
||||
subtitle: "lalla"
|
||||
kind: fragments
|
||||
words: [la, al, als, all, lala, alla, salla, lasa, lass, kalla, falla, jalla]
|
||||
- title: "Wörter"
|
||||
subtitle: "lass das"
|
||||
kind: fragments
|
||||
mode: feed
|
||||
words: [ja, da, ada, adda, fada, aja, jaja, afa, dafa, jafa, jada,
|
||||
ka, kak, akka, kaka, daka, kada, jaka, kaja,
|
||||
sa, as, sas, assa, sass, das, dasa, saka, jassa,
|
||||
la, al, als, all, lala, alla, salla, lasa, lass, kalla, falla, jalla,
|
||||
la, al, als, all, lala, alla, salla, lasa, lass, kalla, falla, jalla]
|
||||
# Unlocks track 3 of Klaviersammlung.
|
||||
unlocks: "Musik/Klaviersammlung/Shakira - Waka Waka (This Time for Africa) (feat. Freshlyground).mp3"
|
||||
- title: "Ö"
|
||||
subtitle: "Der rechte kleine Finger"
|
||||
keys: [f, j, a, d, k, s, l, ö]
|
||||
- title: "Wörter"
|
||||
subtitle: "öl"
|
||||
kind: fragments
|
||||
words: [öl, ölö, öla, ölda, ösa, öll, sölö, köla, dölö, jölö, fölö, lösa]
|
||||
- title: "Wörter"
|
||||
subtitle: "öl kalk salsa"
|
||||
kind: fragments
|
||||
words: [ja, da, ada, adda, fada, aja, jaja, afa, dafa, jafa, jada,
|
||||
ka, kak, akka, kaka, daka, kada, jaka, kaja,
|
||||
sa, as, sas, assa, sass, das, dasa, saka, jassa,
|
||||
la, al, als, all, lala, alla, salla, lasa, lass, kalla,
|
||||
öl, ölö, öla, ölda, ösa, öll, sölö, köla, dölö, jölö, fölö, lösa,
|
||||
öl, ölö, öla, ölda, ösa, öll, sölö, köla, dölö, jölö, fölö, lösa]
|
||||
- title: "Übung: die Grundstellung"
|
||||
subtitle: "Alle acht Finger"
|
||||
drill: true
|
||||
- title: "Erste kleine Wörter"
|
||||
subtitle: "Echte Wörter mit acht Tasten"
|
||||
kind: fragments
|
||||
words: [da, ja, das, dass, als, all, fall, falls, lass, saal, kalk, salsa, jass, öl]
|
||||
- title: "Die Leertaste"
|
||||
subtitle: "Der Daumen kommt dazu"
|
||||
keys: [" "]
|
||||
- title: "Übung: Grundstellung mit Leertaste"
|
||||
subtitle: "Jetzt mit dem Daumen"
|
||||
drill: true
|
||||
- title: "Wörter mit Leertaste"
|
||||
subtitle: "Kleine Wörter, kleine Sätze"
|
||||
kind: fragments
|
||||
mode: feed
|
||||
words: ["lass das", "das da", "da ja", "fall da", "kalk da", "saal da", "ja lass das", "als da", "öl da"]
|
||||
# Unlocks track 4 of Klaviersammlung - the reward for finishing die Grundstellung.
|
||||
unlocks: "Musik/Klaviersammlung/Tones and I - Dance Monkey.mp3"
|
||||
# to here
|
||||
|
||||
- number: 2
|
||||
title: "Nach oben"
|
||||
emoji: "🌊"
|
||||
reward: octopus
|
||||
lessons:
|
||||
- title: "Das E"
|
||||
subtitle: "Mittelfinger links nach oben"
|
||||
keys: [e]
|
||||
- title: "E üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [e]
|
||||
- title: "Das I"
|
||||
subtitle: "Mittelfinger rechts nach oben"
|
||||
keys: [i]
|
||||
- title: "I üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [i]
|
||||
- title: "Übung: E und I"
|
||||
subtitle: "Beide Mittelfinger nach oben"
|
||||
drill: true
|
||||
- title: "Das R"
|
||||
subtitle: "Zeigefinger links nach oben"
|
||||
keys: [r]
|
||||
- title: "R üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [r]
|
||||
- title: "Das U"
|
||||
subtitle: "Zeigefinger rechts nach oben"
|
||||
keys: [u]
|
||||
- title: "U üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [u]
|
||||
- title: "Übung: R und U"
|
||||
subtitle: "Beide Zeigefinger nach oben"
|
||||
drill: true
|
||||
- title: "Kleine Wörter: E I R U"
|
||||
subtitle: "Erste echte Wörter mit der oberen Reihe"
|
||||
kind: fragments
|
||||
words: [die, sie, elf, eis, esel, see, keks, fiel, lied, rad, reis, eier, rufe, feuer, sauer, lauf]
|
||||
- title: "Das T"
|
||||
subtitle: "Zeigefinger links weit nach oben"
|
||||
keys: [t]
|
||||
- title: "T üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [t]
|
||||
- title: "Das Z"
|
||||
subtitle: "Zeigefinger rechts weit nach oben"
|
||||
keys: [z]
|
||||
- title: "Z üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [z]
|
||||
- title: "Übung: T und Z"
|
||||
subtitle: "Weit nach oben greifen"
|
||||
drill: true
|
||||
- title: "Das O"
|
||||
subtitle: "Ringfinger rechts nach oben"
|
||||
keys: [o]
|
||||
- title: "O üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [o]
|
||||
- title: "Das W"
|
||||
subtitle: "Ringfinger links nach oben"
|
||||
keys: [w]
|
||||
- title: "W üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [w]
|
||||
- title: "Übung: O und W"
|
||||
subtitle: "Beide Ringfinger nach oben"
|
||||
drill: true
|
||||
- title: "Kleine Wörter: T Z O W"
|
||||
subtitle: "Noch mehr echte Wörter"
|
||||
kind: fragments
|
||||
mode: feed
|
||||
words: [tier, tafel, kette, leiter, zeit, salz, zelt, katze, rot, tor, los, foto, wo, wald, zwei, wolke]
|
||||
- title: "Das P"
|
||||
subtitle: "Kleiner Finger rechts nach oben"
|
||||
keys: [p]
|
||||
- title: "P üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [p]
|
||||
- title: "Das Q"
|
||||
subtitle: "Kleiner Finger links nach oben"
|
||||
keys: [q]
|
||||
- title: "Q üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [q]
|
||||
- title: "Das Ü"
|
||||
subtitle: "Kleiner Finger rechts, ganz außen"
|
||||
keys: [ü]
|
||||
- title: "Ü üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [ü]
|
||||
- title: "Übung: P Q Ü"
|
||||
subtitle: "Die kleinen Finger nach oben"
|
||||
drill: true
|
||||
- title: "Übung: die obere Reihe"
|
||||
subtitle: "Die ganze Reihe zusammen"
|
||||
kind: words
|
||||
mode: feed
|
||||
words: [wolke, zeit, pause, prüfe, qualle, torte, reiter, würfel]
|
||||
- title: "Übung: Welt 2 komplett"
|
||||
subtitle: "Alles aus der oberen Reihe"
|
||||
drill: true
|
||||
# Unlocks the whole audiobook (its last chapter, inclusive of every chapter
|
||||
# before it).
|
||||
unlocks: "Hörbücher/Grüffelo/Der Grüffelo/08 - Der Grüffelo - Outro.mp3"
|
||||
|
||||
- number: 3
|
||||
title: "Nach unten"
|
||||
emoji: "🪸"
|
||||
reward: seahorse
|
||||
lessons:
|
||||
- title: "Das N"
|
||||
subtitle: "Zeigefinger rechts nach unten"
|
||||
keys: [n]
|
||||
- title: "N üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [n]
|
||||
- title: "Das M"
|
||||
subtitle: "Zeigefinger rechts, neben dem N"
|
||||
keys: [m]
|
||||
- title: "M üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [m]
|
||||
- title: "Übung: N und M"
|
||||
subtitle: "Die neuen Tasten festigen"
|
||||
drill: true
|
||||
- title: "Das G"
|
||||
subtitle: "Zeigefinger links, in der Mitte"
|
||||
keys: [g]
|
||||
- title: "G üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [g]
|
||||
- title: "Das H"
|
||||
subtitle: "Zeigefinger rechts, in der Mitte"
|
||||
keys: [h]
|
||||
- title: "H üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [h]
|
||||
- title: "Übung: G und H"
|
||||
subtitle: "Die Mitte der Grundreihe"
|
||||
drill: true
|
||||
- title: "Kleine Wörter: N M G H"
|
||||
subtitle: "Erste echte Wörter nach unten"
|
||||
kind: fragments
|
||||
words: [nase, nein, kind, wind, sonne, mama, mond, meer, maus, gut, gans, regen, hase, haus, hund, hupe]
|
||||
- title: "Das C"
|
||||
subtitle: "Mittelfinger links nach unten"
|
||||
keys: [c]
|
||||
- title: "C üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [c]
|
||||
- title: "Das V"
|
||||
subtitle: "Zeigefinger links nach unten"
|
||||
keys: [v]
|
||||
- title: "V üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [v]
|
||||
- title: "Übung: C und V"
|
||||
subtitle: "Nach unten greifen"
|
||||
drill: true
|
||||
- title: "Das B"
|
||||
subtitle: "Zeigefinger links, neben dem V"
|
||||
keys: [b]
|
||||
- title: "B üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [b]
|
||||
- title: "Das Y"
|
||||
subtitle: "Kleiner Finger links nach unten"
|
||||
keys: [y]
|
||||
- title: "Y üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [y]
|
||||
- title: "Übung: B und Y"
|
||||
subtitle: "Ganz unten links"
|
||||
drill: true
|
||||
- title: "Kleine Wörter: C V B Y"
|
||||
subtitle: "Noch mehr echte Wörter"
|
||||
kind: fragments
|
||||
words: [koch, milch, schule, chaos, vier, vase, voll, vater, baum, boot, bunt, brot, yoga, baby, typ, pony]
|
||||
- title: "Das X"
|
||||
subtitle: "Ringfinger links nach unten"
|
||||
keys: [x]
|
||||
- title: "X üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [x]
|
||||
- title: "Das Ä"
|
||||
subtitle: "Kleiner Finger rechts, ganz außen"
|
||||
keys: [ä]
|
||||
- title: "Ä üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [ä]
|
||||
- title: "Übung: X und Ä"
|
||||
subtitle: "Die letzten beiden Tasten"
|
||||
drill: true
|
||||
- title: "Übung: alle Buchstaben"
|
||||
subtitle: "Das ganze Alphabet"
|
||||
kind: words
|
||||
mode: race
|
||||
words: [delfin, wasser, xylofon, bäume, vogel, qualle, muschel, tauchen]
|
||||
- title: "Übung: Welt 3 komplett"
|
||||
subtitle: "Alles aus der unteren Reihe"
|
||||
drill: true
|
||||
# Unlocks the first five episodes of GEOlino Spezial, oldest first.
|
||||
unlocks: "Kinderpodcasts/GEOlino Spezial/20200323 - Das Immunsystem.mp3"
|
||||
|
||||
- number: 4
|
||||
title: "Große Buchstaben"
|
||||
emoji: "👑"
|
||||
reward: turtle
|
||||
lessons:
|
||||
- title: "Umschalttaste rechts, Teil 1"
|
||||
subtitle: "Große Buchstaben der linken Hand"
|
||||
keys: ["⇧"]
|
||||
kind: words
|
||||
words: [Delfin, Wal, Fisch, Baum]
|
||||
- title: "Umschalttaste rechts, Teil 2"
|
||||
subtitle: "Noch mehr große Buchstaben"
|
||||
kind: words
|
||||
words: [Garten, Ente, Vogel, Riff]
|
||||
- title: "Umschalttaste links, Teil 1"
|
||||
subtitle: "Große Buchstaben der rechten Hand"
|
||||
kind: words
|
||||
words: [Haus, Kind, Mond, Nase]
|
||||
- title: "Umschalttaste links, Teil 2"
|
||||
subtitle: "Noch mehr große Buchstaben"
|
||||
kind: words
|
||||
words: [Lampe, Onkel, Uhr, Puppe]
|
||||
- title: "Übung: Namen, Teil 1"
|
||||
subtitle: "Namen fangen groß an"
|
||||
kind: words
|
||||
words: [Anna, Lena, Paul, Mia, Emil, Jonas, Tom, Lisa]
|
||||
- title: "Übung: Namen, Teil 2"
|
||||
subtitle: "Noch mehr Namen"
|
||||
kind: words
|
||||
mode: feed
|
||||
words: [Ben, Nora, Finn, Ida, Max, Ella, Oskar, Greta]
|
||||
- title: "Übung: große und kleine"
|
||||
subtitle: "Beides gemischt"
|
||||
kind: words
|
||||
words: ["Das Meer", "Ein Delfin", "Die Sonne", "Mein Boot", "Der Wal", "Eine Muschel", "Ein Fisch", "Das Riff", "Mein Ball", "Die Welle", "Ein Stern", "Der Hai"]
|
||||
- title: "Übung: Welt 4 komplett"
|
||||
subtitle: "Groß und klein zusammen"
|
||||
kind: words
|
||||
drill: true
|
||||
words: [Delfin, Haus, Anna, Ben, "Das Meer", "Der Wal", Mond, Riff]
|
||||
|
||||
- number: 5
|
||||
title: "Ganze Sätze"
|
||||
emoji: "📖"
|
||||
reward: pearlmussel
|
||||
lessons:
|
||||
- title: "Der Punkt"
|
||||
subtitle: "Ringfinger rechts nach unten"
|
||||
keys: ["."]
|
||||
- title: "Punkt üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: ["."]
|
||||
- title: "Erste Sätze mit Punkt"
|
||||
subtitle: "Ein Satz, ein Punkt"
|
||||
kind: sentences
|
||||
words:
|
||||
- "Das Meer ist tief."
|
||||
- "Der Hund bellt."
|
||||
- "Ich mag Kekse."
|
||||
- "Die Sonne scheint."
|
||||
- "Der Wal ist riesig."
|
||||
- "Wir gehen baden."
|
||||
- "Mama liest ein Buch."
|
||||
- "Der Fisch schwimmt."
|
||||
- "Heute ist es warm."
|
||||
- "Ich habe einen Ball."
|
||||
- "Die Welle ist hoch."
|
||||
- "Papa kocht Suppe."
|
||||
- title: "Das Komma"
|
||||
subtitle: "Mittelfinger rechts nach unten"
|
||||
keys: [","]
|
||||
- title: "Komma üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: [","]
|
||||
- title: "Sätze mit Komma"
|
||||
subtitle: "Zwei Gedanken, ein Satz"
|
||||
kind: sentences
|
||||
mode: race
|
||||
words:
|
||||
- "Ich mag Wale, Delfine und Fische."
|
||||
- "Erst lesen, dann tippen."
|
||||
- "Es ist warm, also baden wir."
|
||||
- "Rot, gelb und blau sind Farben."
|
||||
- "Wenn es regnet, bleiben wir drinnen."
|
||||
- "Der Delfin springt, taucht und spielt."
|
||||
- "Morgen, sagt Papa, fahren wir los."
|
||||
- "Eins, zwei, drei, vier."
|
||||
- "Oma, Opa und ich gehen schwimmen."
|
||||
- "Die Sonne scheint, das Meer glitzert."
|
||||
- "Muscheln, Steine und Sand liegen am Strand."
|
||||
- title: "Der Bindestrich"
|
||||
subtitle: "Kleiner Finger rechts, ganz außen"
|
||||
keys: ["-"]
|
||||
- title: "Bindestrich üben"
|
||||
subtitle: "Die neue Taste festigen"
|
||||
keys: ["-"]
|
||||
- title: "Sätze mit Bindestrich"
|
||||
subtitle: "Zwei Wörter, ein Strich"
|
||||
kind: sentences
|
||||
words:
|
||||
- "Wir spielen mit dem Wasser-Ball."
|
||||
- "Das ist ein Delfin-Baby."
|
||||
- "Meine Ur-Oma kommt heute."
|
||||
- "Wir bauen eine Sand-Burg."
|
||||
- "Der Fisch-Schwarm ist riesig."
|
||||
- "Ich trage mein T-Shirt."
|
||||
- "Das Schwimm-Bad ist offen."
|
||||
- "Die Bade-Hose ist nass."
|
||||
- "Wir essen ein Eis-Hörnchen."
|
||||
- "Das Segel-Boot ist blau."
|
||||
- "Mein Lieblings-Tier ist der Delfin."
|
||||
- title: "Fragezeichen und Ausrufezeichen"
|
||||
subtitle: "Mit der Umschalttaste"
|
||||
keys: ["ß", "1"]
|
||||
- title: "Fragezeichen und Ausrufezeichen üben"
|
||||
subtitle: "Die neuen Tasten festigen"
|
||||
keys: ["ß", "1"]
|
||||
- title: "Fragen und Rufe"
|
||||
subtitle: "Wie klingt ein Satz?"
|
||||
kind: sentences
|
||||
words:
|
||||
- "Wo ist der Delfin?"
|
||||
- "Das war toll!"
|
||||
- "Wie geht es dir?"
|
||||
- "Pass auf!"
|
||||
- "Kommst du mit?"
|
||||
- "Der Wal ist so groß!"
|
||||
- "Hast du Hunger?"
|
||||
- "Hurra, Ferien!"
|
||||
- "Was schwimmt da?"
|
||||
- "Schau mal, ein Hai!"
|
||||
- "Wie tief ist das Meer?"
|
||||
- "Wir haben es geschafft!"
|
||||
- title: "Übung: ganze Sätze"
|
||||
subtitle: "Alles zusammen"
|
||||
kind: sentences
|
||||
drill: true
|
||||
mode: race
|
||||
words:
|
||||
- "Der Delfin schwimmt sehr schnell."
|
||||
- "Wo ist mein Boot?"
|
||||
- "Ich tippe jetzt mit zehn Fingern!"
|
||||
- "Das Meer ist blau, tief und kalt."
|
||||
- "Kannst du das auch?"
|
||||
- "Wir bauen eine Sand-Burg am Strand."
|
||||
- "Die Möwe fliegt über das Wasser."
|
||||
- "Oma, Opa und ich gehen schwimmen."
|
||||
- "Das ist ja super!"
|
||||
- "Wie heißt der große Wal?"
|
||||
- "Im Riff wohnen bunte Fische."
|
||||
- "Der Krake hat acht Arme."
|
||||
@@ -42,6 +42,23 @@
|
||||
force: true
|
||||
notify: Reinstall musicmouse backend
|
||||
|
||||
- name: Check the checkout has the layout this role knows
|
||||
ansible.builtin.stat:
|
||||
path: /opt/musicmouse/python-backend/pyproject.toml
|
||||
register: pi_musicmouse_pyproject
|
||||
|
||||
- name: Fail on a pre-rearchitecture checkout
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
{{ musicmouse_version }} has no python-backend/pyproject.toml, so it predates the
|
||||
rearchitecture and this role cannot install it - the venv, the systemd unit and
|
||||
the web/ build all live somewhere else on those commits. Point
|
||||
musicmouse_version at a branch that has it (deploy/musicdolphin), or set
|
||||
mediapi_install_kidsmusic to false for this host and leave whatever is installed
|
||||
alone. Stopping here rather than failing three tasks deeper with the real reason
|
||||
buried.
|
||||
when: not pi_musicmouse_pyproject.stat.exists
|
||||
|
||||
- name: Create virtualenv with Python {{ pi_musicmouse_python_version }}
|
||||
ansible.builtin.command:
|
||||
cmd: "/usr/local/bin/uv venv --python {{ pi_musicmouse_python_version }} /opt/musicmouse/.venv"
|
||||
@@ -58,6 +75,17 @@
|
||||
src: "{{ pi_musicmouse_config_file }}"
|
||||
dest: /media/musicmouse/config.yml
|
||||
mode: "0644"
|
||||
# Install-once: the app writes this file itself (parent mode's volume keys, the
|
||||
# remote-control mapping). See pi_musicmouse_force_config in defaults.
|
||||
force: "{{ pi_musicmouse_force_config }}"
|
||||
notify: Restart musicmouse
|
||||
|
||||
- name: Install tippen curriculum
|
||||
ansible.builtin.copy:
|
||||
src: "{{ pi_musicmouse_curriculum_file }}"
|
||||
dest: /media/musicmouse/tippen-curriculum.yml
|
||||
mode: "0644"
|
||||
when: pi_musicmouse_curriculum_file | length > 0
|
||||
notify: Restart musicmouse
|
||||
|
||||
- name: Install systemd service file (from the checked-out repo, not a copy kept here)
|
||||
@@ -108,5 +136,6 @@
|
||||
daemon_reload: "yes"
|
||||
# manual steps:
|
||||
# - set samba passwords with smbpasswd
|
||||
# - copy music into /media/musicmouse (or via samba share)
|
||||
# - manual patching of hassclient necessary :( loop has to be passed in, to not use running_event_loop
|
||||
# - copy music into /media/musicmouse: scripts/sync-library.sh in the app repo, which
|
||||
# also carries the cover and analysis cache over (or use the samba share, and let
|
||||
# the device spend the DSP time itself)
|
||||
|
||||
Reference in New Issue
Block a user