fix AVR input swap
Deploy HEOS panel / deploy (push) Successful in 25s

This commit is contained in:
2026-09-15 17:20:24 +02:00
parent dc7f09052d
commit 6a0f1fa5e8
13 changed files with 272 additions and 410 deletions
+37 -27
View File
@@ -25,7 +25,7 @@ Everything it does fits on one screen:
| --- | --- | --- |
| Living Room | 2× Denon Home 200 as an In-Room Group | a **group** (`gid`) |
| Lego Room | Denon Home 400 | a **player** (`pid`) |
| Home Cinema | Denon AVR-X3800H | a **player**, plus Telnet on port 23 |
| Home Cinema | Denon AVR-X3800H | a **player** |
Any other mix works — it is all in `config.py`.
@@ -67,15 +67,16 @@ ROOM_KEYS = ["home400", "living_room_group"] # the cards, in order
```
`HEOS_HOST` only needs to point at **one** device: HEOS is distributed, so any
unit can see and control the whole network. `AVR_HOST` must be the AVR itself.
unit can see and control the whole network — including the AVR's own inputs,
so there is nothing AVR-specific to configure beyond its entry in `TARGETS`.
`VOLUME_STEP` is the grid the volume buttons snap to, not simply how much
they add: at 5, a tap moves 23 to 25 and 25 to 30.
The input picker already leaves out sources switched off in the AVR's own
setup menu (it asks the AVR with `SSSOD ?`). `AVR_INPUT_CODES` narrows it
further to the sources you actually use, and sets their order; leave it empty
to list everything the AVR still has switched on.
`AVR_INPUT_CODES` narrows the input picker to the sources you actually use,
by their HEOS input id (`GET /api/avr/inputs` shows the exact strings, e.g.
`inputs/aux_in_1`), and sets their order; leave it empty to list everything
HEOS reports for it.
## Add it to the iOS home screen
@@ -87,8 +88,9 @@ if you ever put it behind a domain name, give it HTTPS.
## Run it as a service
`deploy/heos-panel.service` runs the panel out of its own virtualenv and
restarts it if it dies:
`deploy/heos-panel.service` runs the panel out of its own virtualenv, under
`gunicorn` rather than `python3 app.py`'s dev server, and restarts it if it
dies:
```bash
sudo cp deploy/heos-panel.service /etc/systemd/system/
@@ -98,6 +100,10 @@ sudo systemctl enable --now heos-panel
Edit `User=` and the paths in it if you keep the panel somewhere else.
`python3 app.py` (no gunicorn) is still the right way to run it by hand
while working on it — see `--demo` below — the dev-server warning it prints
is expected there and only matters for the service above.
## Deploying from Gitea
`.gitea/workflows/deploy.yml` checks out the push, runs the tests, rsyncs
@@ -235,20 +241,25 @@ a restart. If you would rather pin them down, list them in `config.py`:
Leaving a room deliberately does *not* rewrite the AVR's group, so the other
room's music does not restart.
## Two protocols, not one
**A newly joined room can stay silent on the AVR's input.** Joining alone
does not push audio to it — HEOS needs telling *again* which input is
playing before it streams that input to the new member, the same reselect
you'd otherwise do by hand in the HEOS app (Home → Sources → AV). `join()`
does this for you: it reads the AVR's current input back and replays it
through `browse/play_input` right after the group merge.
| | HEOS CLI (port 1255) | Denon Telnet (port 23) |
| --- | --- | --- |
| Speaks | JSON, `heos://player/...` | plain text, `SIGAME`, `SSFUN ?` |
| Used for | players, groups, volume | the AVR's **renamed** input list |
## One protocol, not two
HEOS only knows generic input ids like `inputs/hdmi_in_1`; the names you gave
your sources live in the AVR's own protocol, which is why both are here.
Everything goes over the HEOS CLI (port 1255) — players, groups, volume, and
the AVR's own inputs. `browse/browse` on the AVR's pid lists its inputs under
whatever names you gave them in its setup menu; HEOS reports those renamed
labels itself, so there used to be a second client here for the AVR's Denon
Telnet port just to fetch them, and it is not needed any more.
The Telnet connection is held open, so input changes made with the physical
remote show up in the panel too. Some Denon models only accept **one** Telnet
connection at a time — if another integration (Home Assistant, say) already
holds it, the AVR card will read `offline` while the HEOS half keeps working.
Selecting an input goes through `browse/play_input`, not a raw `SI<code>`
Telnet command, for the same reason joining a room re-sends it (see above):
that is what actually tells HEOS to *stream* the input to whichever players
are grouped with the AVR, not just which jack the AVR itself is listening to.
## HTTP API
@@ -261,10 +272,11 @@ Used by the interface:
| `POST /api/volume` | `{"target": "home400", "steps": 1}` — taps, snapped to `VOLUME_STEP`. Also takes `delta` (raw points) or `level` (absolute) |
| `POST /api/mute` | `{"target": "home400"}` |
| `POST /api/playback` | `{"target": "home400", "state": "pause"}`, or no `state` to toggle |
| `POST /api/skip` | `{"target": "home400", "direction": "next"}``previous` too |
| `POST /api/group` | `{"target": "home400", "joined": true}` |
| `POST /api/group/none` | every room back on its own |
| `GET /api/avr/inputs` | your renamed sources |
| `POST /api/avr/input` | `{"code": "GAME"}` |
| `GET /api/avr/inputs` | your renamed sources, over HEOS |
| `POST /api/avr/input` | `{"code": "inputs/aux_in_1"}` |
`POST /volume/up` and `/volume/down` take one snapped tap by default; pass
`?step=3` and they move that many raw points instead, as they always did.
@@ -272,13 +284,12 @@ Used by the interface:
The original bridge's endpoints still answer, so existing Shortcuts and
scripts keep working: `/targets`, `/volume`, `/volume/{set,up,down,mute}`,
`/playback/{play,pause,stop,next,previous}`, `/group/{create,remove}`,
`/inputs`, `/input/{set,relay}`, `/avr/{raw,input,inputs}`, `/raw/<command>`.
`/inputs`, `/input/{set,relay}`, `/avr/{input,inputs}`, `/raw/<command>`.
Two of them are worth keeping for troubleshooting:
Worth keeping for troubleshooting:
```
GET /raw/browse/browse?sid=1027 # any heos:// command, raw reply
GET /avr/raw?cmd=SSFUN ? # any Telnet command, every line back
```
## Working on it
@@ -293,11 +304,10 @@ python3 tools/make_icons.py # re-render the icons from static/lo
```
app.py Flask: the UI, the API, and the old bridge's routes
controller.py what a room is, what grouping means, volume
controller.py what a room is, what grouping means, volume, the AVR's inputs
heos.py HEOS CLI client (persistent socket, reconnects itself)
avr.py Denon Telnet client + the renamed input list
config.py your devices and preferences
demo.py fake speakers for --demo
templates/ static/ the interface
tests/ fake HEOS + AVR servers, and tests against them
tests/ a fake HEOS server, and tests against it
```