148 lines
4.8 KiB
Python
148 lines
4.8 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()
|
|
# One room on Spotify and one not, so both kinds of card show up.
|
|
self._spotify = {key: i == 0 for i, key in enumerate(cfg.ROOM_KEYS)}
|
|
|
|
# -- 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], "spotify": self._spotify[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
|
|
|
|
# -- the AVR: app.py calls these directly, same as the real Controller
|
|
def avr_inputs(self):
|
|
return self.avr.inputs()
|
|
|
|
def avr_current_input(self):
|
|
return self.avr.current_input()
|
|
|
|
def avr_select_input(self, code):
|
|
return self.avr.select_input(code)
|