Files
heos/demo.py
T
franzz 69476134f7
Deploy HEOS panel / deploy (push) Successful in 25s
Add icons
2026-09-16 18:06:14 +02:00

159 lines
5.6 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:
# Codes already in HEOS's own "inputs/<mid>" shape (see GET
# /api/avr/inputs against the real AVR), not the demo's own invention --
# the panel's input icon is keyed on this exact format.
INPUTS = [
{"code": "inputs/mediaplayer", "name": "Apple TV"},
{"code": "inputs/game", "name": "PlayStation"},
{"code": "inputs/tvaudio", "name": "TV Box"},
{"code": "inputs/bluray", "name": "Blu-ray"},
{"code": "inputs/tuner", "name": "Radio"},
{"code": "inputs/phono", "name": "Turntable"},
]
def __init__(self):
self.connected = True
self._code = "inputs/mediaplayer"
def inputs(self, refresh=False):
return list(self.INPUTS)
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()
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)}
# No cover, since the demo has no network to fetch one from.
self._track = {"song": "Harvest Moon", "artist": "Neil Young", "image": None}
# Its play button only shows for a stream one of your accounts plays.
self._account = next(iter(cfg.SPOTIFY_ACCOUNTS), None)
# -- 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],
"spotify_account": self._account if self._spotify[key] else None,
"now_playing": self._track if self._spotify[key] and self._play[key] != "stop" else None,
"error": None}
for key in self.cfg.ROOM_KEYS
],
"avr": {
"connected": True, "inputs": self.avr.inputs(), "input": self.avr.current_input(),
# Standing in for a Zidoo plugged into this input, so the
# AVR card's now-playing block has something to show here too.
"now_playing": {"song": "Big Buck Bunny", "artist": None, "image": None}
if self.avr.current_input()["code"] == self.cfg.ZIDOO_INPUT_CODE else None,
},
"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": s["code"]} 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)