Files
heos/demo.py
T
franzz dc7f09052d
Deploy HEOS panel / deploy (push) Successful in 24s
play/pause
2026-09-15 01:03:25 +02:00

136 lines
4.3 KiB
Python

"""A pretend HEOS network, for `python3 app.py --demo`.
Lets you work on the interface on a laptop, with no speakers on the
network -- and lets the panel be tested without waking the house up.
"""
import time
from controller import stepped_level
class _FakeAvr:
INPUTS = [
{"code": "MPLAY", "name": "Apple TV"},
{"code": "GAME", "name": "PlayStation"},
{"code": "SAT/CBL", "name": "TV Box"},
{"code": "BD", "name": "Blu-ray"},
{"code": "TUNER", "name": "Radio"},
{"code": "PHONO", "name": "Turntable"},
]
def __init__(self, allowed_codes=()):
self.allowed_codes = list(allowed_codes or [])
self.connected = True
self._code = "MPLAY"
def inputs(self, refresh=False):
sources = list(self.INPUTS)
if self.allowed_codes:
order = {code: i for i, code in enumerate(self.allowed_codes)}
sources = sorted((s for s in sources if s["code"] in order), key=lambda s: order[s["code"]])
return sources
def name_for(self, code):
return next((s["name"] for s in self.INPUTS if s["code"] == code), code)
def current_input(self):
return {"code": self._code, "name": self.name_for(self._code)}
def select_input(self, code):
time.sleep(0.15) # the real AVR is not instant either
self._code = code
return self.current_input()
class DemoController:
def __init__(self, cfg):
self.cfg = cfg
self.avr = _FakeAvr(cfg.AVR_INPUT_CODES)
self.heos = None
self._volume = {key: 22 + 7 * i for i, key in enumerate(cfg.TARGETS)}
self._play = {key: "play" for key in cfg.TARGETS}
self._joined = set()
# -- what the UI uses ---------------------------------------------
def state(self):
return {
"host": {"key": self.cfg.HOST_KEY, "label": self.cfg.TARGETS[self.cfg.HOST_KEY]["label"]},
"rooms": [
{"key": key, "label": self.cfg.TARGETS[key]["label"], "available": True,
"grouped": key in self._joined, "volume": self._volume[key],
"play_state": self._play[key], "error": None}
for key in self.cfg.ROOM_KEYS
],
"avr": {"connected": True, "inputs": self.avr.inputs(), "input": self.avr.current_input()},
"heos_ok": True,
"errors": [],
"demo": True,
}
def volume(self, key):
return self._volume[key]
def set_volume(self, key, level):
self._volume[key] = max(0, min(100, int(level)))
return self._volume[key]
def nudge_volume(self, key, delta):
return self.set_volume(key, self._volume[key] + int(delta))
def step_volume(self, key, steps):
return self.set_volume(key, stepped_level(self._volume[key], int(steps), self.cfg.VOLUME_STEP))
def toggle_mute(self, key):
return None
def get_play_state(self, key):
return self._play[key]
def toggle_play(self, key, state=None):
if state is None:
state = "pause" if self._play[key] == "play" else "play"
self._play[key] = state
return state
def join(self, key):
self._joined.add(key)
return self.joined_keys()
def leave(self, key):
self._joined.discard(key)
return self.joined_keys()
def set_membership(self, joined):
self._joined = {k for k in self.cfg.ROOM_KEYS if k in joined}
return self.joined_keys()
def joined_keys(self):
return [k for k in self.cfg.ROOM_KEYS if k in self._joined]
# -- enough of the rest to keep the legacy routes answering --------
def scan(self):
return {
"players": [{"name": t["heos_name"], "pid": 1000 + i, "model": "Demo"}
for i, t in enumerate(self.cfg.TARGETS.values())],
"groups": [],
}
def group_targets(self, host_key, member_keys):
return self.set_membership(member_keys)
def ungroup(self, key):
return self.leave(key)
def play_state(self, key, state):
return None
def skip(self, key, direction):
return None
def heos_inputs(self, key):
return [{"name": s["name"], "input_id": f"inputs/{s['code'].lower()}"} for s in self.avr.inputs()]
def play_heos_input(self, key, input_id, source_key=None):
return None