Add IR remote control (LIRC) with a number-key content mapping
Adds a TCP client for lircd's classic protocol: play/pause/next/prev/ volume/mute map to the same intents every other front-end already emits, and number keys 0-9 play an assigned album/audiobook from the start or a podcast show's newest episode, resolved fresh on every press. The mapping is configured in config.yml and editable from the frontend: a small "Taste zuweisen" button on the play screen (or the A+digit keyboard shortcut) opens a 10-key picker to assign whatever is currently playing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -33,18 +33,24 @@ __all__ = [
|
||||
"SIMULATE",
|
||||
"Config",
|
||||
"ConfigError",
|
||||
"Digit",
|
||||
"FigureColors",
|
||||
"FigureConfig",
|
||||
"GeneralConfig",
|
||||
"HaConfig",
|
||||
"HaDeviceConfig",
|
||||
"LibraryConfig",
|
||||
"LircConfig",
|
||||
"MqttConfig",
|
||||
"RemoteSlotConfig",
|
||||
"WebConfig",
|
||||
"format_validation_error",
|
||||
"load_config",
|
||||
]
|
||||
|
||||
#: Number keys on the IR remote, as lircd's ``BTN_0``..``BTN_9`` map to them.
|
||||
type Digit = Literal["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
|
||||
|
||||
DEFAULT_AUDIO_EXTENSIONS = (".mp3", ".ogg", ".oga", ".opus", ".flac", ".wav", ".m4a", ".aac")
|
||||
|
||||
#: Stand-in value for ``serial_port`` and ``alsa_device``. Running without the mouse or
|
||||
@@ -133,6 +139,22 @@ class MqttConfig(_Strict):
|
||||
reconnect_interval: float = Field(default=10.0, gt=0)
|
||||
|
||||
|
||||
class LircConfig(_Strict):
|
||||
"""TCP client for lircd's classic network protocol - see ``ansible/roles/pi_lirc``.
|
||||
|
||||
Omit the whole section to run without an IR remote.
|
||||
"""
|
||||
|
||||
host: str
|
||||
#: This deployment's lircd listens on 2222 (see the ansible role); lircd's own
|
||||
#: default is 8765, so this is worth overriding rather than assuming.
|
||||
port: int = Field(default=2222, ge=1, le=65535)
|
||||
#: Only button events from this remote are acted on - other remotes registered
|
||||
#: with the same lircd (an LED remote, say) are ignored.
|
||||
remote_name: str = "Hauppauge"
|
||||
reconnect_interval: float = Field(default=5.0, gt=0)
|
||||
|
||||
|
||||
class LibraryConfig(_Strict):
|
||||
"""Where the music lives.
|
||||
|
||||
@@ -221,6 +243,7 @@ class GeneralConfig(_Strict):
|
||||
mqtt: MqttConfig | None = None
|
||||
web: WebConfig | None = None
|
||||
ha: HaConfig | None = None
|
||||
lirc: LircConfig | None = None
|
||||
|
||||
min_volume: int = Field(default=0, ge=0, le=200)
|
||||
max_volume: int = Field(default=100, ge=0, le=200)
|
||||
@@ -262,9 +285,26 @@ class FigureConfig(_Strict):
|
||||
kind: Literal["music", "book"] = "music"
|
||||
|
||||
|
||||
class RemoteSlotConfig(_Strict):
|
||||
"""What a number key on the IR remote plays.
|
||||
|
||||
``"album"``: ``target`` is an ``Album.id``, always started from track 0 - a music
|
||||
album or an audiobook. ``"series"``: ``target`` is a podcast show name (an
|
||||
``Album.series``); resolved to that show's newest episode fresh on every press,
|
||||
since a podcast show is not itself one playable thing in this library - each
|
||||
episode is its own album.
|
||||
"""
|
||||
|
||||
target_kind: Literal["album", "series"]
|
||||
target: str
|
||||
|
||||
|
||||
class Config(_Strict):
|
||||
general: GeneralConfig
|
||||
figures: dict[str, FigureConfig] = Field(min_length=1)
|
||||
#: Number key (0-9) -> what it plays. Empty by default: a fresh install has no
|
||||
#: assignments, and that is not an error.
|
||||
remote: dict[Digit, RemoteSlotConfig] = Field(default_factory=dict)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _check_unique_tag_ids(self) -> Self:
|
||||
|
||||
Reference in New Issue
Block a user