SmartestHome/hosts/touch-panel/live-build/config
Amir Alexander Abdelbaki 864b5d1719 Remote microphone: the Loggia panel becomes an input on the desktop
The headphones have no microphone, so the follow-me switch needs somewhere to
switch TO. The Loggia has no mic you can plug into the desktop, but it does
have a machine with one — the touch panel already in that room.

remote-mic.sh makes that panel's microphone appear on the desktop as an
ordinary source:

  panel: pw-record ──ssh──> desktop: pw-cat into a null sink
                                          └─ module-remap-source ─> micfollow_<room>

SSH RATHER THAN AN AUDIO PROTOCOL. RTP and PipeWire's pulse-tunnel are both
lower latency, and both want a new listening service on the panel, an ACL, and
config on two machines that has to agree. The panel already runs sshd as its
documented admin path and already trusts this desktop's key, so the transport
arrives with authentication and encryption already solved and nothing new
listening on the network. For a smoke-break voice call, 40ms of buffering is
not the constraint — a hot microphone in an empty room is. If the latency ever
matters, the null-sink half stays and only the transport changes.

And audio exists ONLY while the stream runs: no daemon, one SSH session,
`stop` closes it. That is a property of the transport rather than a promise in
a config file, which is the reason to prefer it.

A null sink plus module-remap-source, not the sink's monitor: a monitor is not
a real source, applications treat it as "record what the desktop is playing",
and this component's own audio layer refuses to select one on purpose.

The panel says so, in the room. hosts/touch-panel/ gains mic-in-use and a red
MIC LIVE badge on the dock, lit whenever ANYTHING is capturing that panel's
microphone — this feature or not. A room mic somebody elsewhere can open has to
be visible to the person standing in front of it; Home Assistant knowing is not
the same as them knowing. It reads PipeWire's actual capture streams rather
than trusting who asked, ignores monitor streams, and without jq it falls back
to over-reporting, which is the right direction to be wrong in for a warning
light. pipewire-bin and pulseaudio-utils join that image for it.

Fixed while building it: the agent looked for a source BEFORE running the start
hook, so a remote microphone — which does not exist as a PipeWire node until
its stream is up — could never be selected. It fell back to the desk every
time, correct by the letter of the code and useless in practice. Hooks now run
first, the source is waited for (source_timeout_seconds, default 6), and a
failed start has its stop hook run before falling back rather than leaving a
half-started stream behind.

Configuration is a `remote` block on the source instead of a source pattern;
the generator writes both hooks and the transport's parameters, because "stop
the stream" is a safety property and not something to rely on somebody having
typed correctly. Tests pin exactly that: every remote source produces both
hooks, a local one is left alone, and mic-in-use's monitor exclusion is checked
against fixtures. 24 tests now.

The one command that decides whether any of this works, and it needs the real
machines: ssh <user>@<host> pw-record --help

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B7yp4NcmX4zqja1MKRjeWJ
2026-08-31 13:55:31 +02:00
..
hooks/normal Stream Dock lighting controls, and Discord on the Loggia panel 2026-08-31 13:04:17 +02:00
package-lists Remote microphone: the Loggia panel becomes an input on the desktop 2026-08-31 13:55:31 +02:00
preseed.cfg Add Phase 16 touch panel and Phase 17 kitchen/fridge display endpoints 2026-07-30 11:33:08 +02:00