Einfg on the kiosk keyboard, or Guide (KEY_EPG) on the IR remote, shows a big analog watch over whatever page is open, built from the Delphin Uhr mockup without its swimming dolphin. The overlay stays mounted and is only hidden, so it appears at once. Beside the watch, only while they run: - The bus: on school days, a silent countdown to `general.clock.bus_time` (07:25) that starts `bus_lead_minutes` (60) before. School days skip weekends and the school and public holidays of `general.clock.subdivision` (DE-BY), fetched daily from openholidaysapi.org and cached under the library cache. A badge on the clock says whether there is school today, and why not. - The dolphin timer, set in parent mode (5/10/15/30 minutes, or until a time). It lives in the backend, so a parent's phone can start it for the kiosk. When it runs out the kiosk opens the clock at 00:00 and a soft bell chimes on the device; it clears after a minute, or at the next key, tap or remote press. Parent mode is now a way in rather than a page: `?parentMode=1` adds a 🔧 button next to the ⌨ one, or an "Eltern" tab in the phone's dock, so the URL can stay bookmarked. The timer comes first there, the device settings below it. Unmapped IR buttons are logged at debug level, to find out what irserver calls a new one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
2.8 KiB
Python
80 lines
2.8 KiB
Python
"""One place that answers "what is the mouse doing right now?".
|
|
|
|
Assembled on demand rather than accumulated from events. The join between what is
|
|
playing and which figure is on the reader is the same one the MQTT player entity makes:
|
|
read ``mouse.active_figure``, do not keep a second copy of it that can drift.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date
|
|
|
|
from musicmouse.app import App
|
|
from musicmouse.services.web.schemas import (
|
|
BusOut,
|
|
ClockStateOut,
|
|
ConnectionOut,
|
|
PlayerStateOut,
|
|
TimerOut,
|
|
)
|
|
from musicmouse.services.web.settings import to_percent
|
|
|
|
__all__ = ["snapshot"]
|
|
|
|
|
|
def snapshot(app: App) -> PlayerStateOut:
|
|
player = app.player
|
|
playlist = player.playlist
|
|
album = app.album_for(playlist)
|
|
index = player.track_index
|
|
# The player only knows a file path, and a tagged file's name is rarely its title
|
|
# ("01 - So ein schoener Tag.mp3"). The library read the real one at scan time.
|
|
indexed = (
|
|
album.tracks[index] if album and 0 <= index < len(album.tracks) else None
|
|
)
|
|
track = player.current_track
|
|
|
|
return PlayerStateOut(
|
|
playing=player.is_playing,
|
|
album_id=album.id if album else None,
|
|
album_title=album.title if album else (playlist.name if playlist else None),
|
|
artist=album.artist if album else None,
|
|
kind=album.kind if album else None,
|
|
track_index=index,
|
|
track_title=indexed.title if indexed else (track.title if track else None),
|
|
track_count=len(playlist) if playlist else 0,
|
|
position=player.position,
|
|
# Likewise the duration: the player only has one once libVLC has opened the file.
|
|
duration=(indexed.duration if indexed else 0.0) or player.duration,
|
|
volume=to_percent(player.volume, app.config.general),
|
|
active_figure=app.mouse.active_figure,
|
|
connected=ConnectionOut(
|
|
firmware=app.mouse.connected,
|
|
mqtt=app.state.mqtt_connected,
|
|
lirc=app.state.lirc_connected,
|
|
),
|
|
clock=clock_state(app),
|
|
)
|
|
|
|
|
|
def clock_state(app: App, today: date | None = None) -> ClockStateOut:
|
|
state = app.state
|
|
config = app.config.general.clock
|
|
# Without a holiday calendar a weekday counts as a school day: a bus countdown on
|
|
# a holiday is a smaller surprise than none on the first day back.
|
|
today = today or date.today()
|
|
school = state.school_day if state.school_day is not None else today.weekday() < 5
|
|
timer = state.parent_timer
|
|
return ClockStateOut(
|
|
school_day=state.school_day,
|
|
school_day_reason=state.school_day_reason,
|
|
bus=BusOut(time=config.bus_time, lead_minutes=config.bus_lead_minutes)
|
|
if school
|
|
else None,
|
|
parent_timer=TimerOut(
|
|
started_at=timer.started_at, ends_at=timer.ends_at, ringing=timer.ringing
|
|
)
|
|
if timer
|
|
else None,
|
|
)
|