- event bus systen - all components are independent - preparation for web frontend
97 lines
3.1 KiB
Python
97 lines
3.1 KiB
Python
"""The interactive simulator prompt.
|
|
|
|
Runs the whole app - bus, device, reactions, MQTT if configured - against fake
|
|
hardware, and lets you poke it by hand while watching the events go past.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import contextlib
|
|
|
|
from musicmouse.events import (
|
|
ActiveFigureChanged,
|
|
Event,
|
|
InputEvent,
|
|
LedEffectChanged,
|
|
PlaybackChanged,
|
|
StateEvent,
|
|
TrackChanged,
|
|
VolumeChanged,
|
|
)
|
|
from musicmouse.simulator.driver import ExpectationError, ScriptError
|
|
from musicmouse.simulator.harness import Simulation
|
|
|
|
__all__ = ["run_repl"]
|
|
|
|
PROMPT = "musicmouse> "
|
|
|
|
HELP = """\
|
|
place <figure> put a figure on the reader remove
|
|
press <left|right|rotary> [action] turn <+n|-n>
|
|
touch <left_ear|right_ear|left_foot|right_foot> release <same>
|
|
play pause next prev volume <0-100>
|
|
disconnect / reconnect simulate the USB cable
|
|
wait <1s|500ms|2> let time pass
|
|
status one-line summary leds
|
|
expect <key> <value> assert (playing, figure, playlist, track, title,
|
|
volume, brightness, ring, mouse, shelf)
|
|
help this text quit
|
|
"""
|
|
|
|
|
|
def _describe(event: Event) -> str | None:
|
|
"""A compact one-liner, or None for events too noisy to show."""
|
|
match event:
|
|
case LedEffectChanged(zone=zone, effect=effect):
|
|
return f" led {zone:>5}: {effect}"
|
|
case PlaybackChanged(playing=playing, figure=figure):
|
|
state = "playing" if playing else "paused"
|
|
return f" play {state}{f' [{figure}]' if figure else ''}"
|
|
case TrackChanged(index=index, track=track):
|
|
return f" play track {index}" + (f": {track.title}" if track else "")
|
|
case VolumeChanged(volume=volume):
|
|
return f" vol {volume}"
|
|
case ActiveFigureChanged(figure=figure):
|
|
return f" rfid {figure or '(removed)'}"
|
|
case InputEvent():
|
|
return f" in {event}"
|
|
case StateEvent():
|
|
return f" state {event}"
|
|
case _:
|
|
return None
|
|
|
|
|
|
async def run_repl(sim: Simulation) -> None:
|
|
print("MusicMouse simulator - no hardware, no audio. 'help' for commands.\n")
|
|
sim.bus.subscribe_all(_print_event)
|
|
print(sim.driver.status())
|
|
|
|
while True:
|
|
try:
|
|
line = await asyncio.to_thread(input, PROMPT)
|
|
except (EOFError, KeyboardInterrupt):
|
|
print()
|
|
return
|
|
|
|
command = line.strip().lower()
|
|
if command in {"quit", "exit", "q"}:
|
|
return
|
|
if command in {"help", "?"}:
|
|
print(HELP, end="")
|
|
continue
|
|
|
|
try:
|
|
if (output := await sim.driver.execute(line)) is not None:
|
|
print(output)
|
|
except (ScriptError, ExpectationError) as exc:
|
|
print(f"! {exc}")
|
|
except Exception as exc: # the prompt must survive anything
|
|
print(f"! {type(exc).__name__}: {exc}")
|
|
|
|
|
|
def _print_event(event: Event) -> None:
|
|
if (text := _describe(event)) is not None:
|
|
with contextlib.suppress(OSError):
|
|
print(text)
|