···11# Audio routing matrix: one Home Assistant switch (MQTT discovery) per
22# source and speaker. While a switch is on, the source's PipeWire node is
33# linked to the speaker's node. Sources are the fixed nodes from the config
44-# plus every connected Bluetooth A2DP source (bluez_input.*). A new source
55-# starts on the config's `rooms` of it, else on all speakers. Each speaker
44+# plus every connected Bluetooth A2DP source (bluez_input.*; phones left on
55+# the "off" profile are switched to A2DP). A new source starts on the
66+# config's `rooms` of it, else on all speakers. Each speaker
67# also gets a volume (MQTT number), applied to its node with wpctl. Usage:
78# audio-matrix CONFIG.json [--dry-run], --dry-run prints the pw-link and
89# wpctl calls without running them.
···22232324events = queue.Queue()
24252525-# PipeWire objects by id: nodes, ports and links
2626+# PipeWire objects by id: devices, nodes, ports and links
2627graph = {}
2728interesting = {
2929+ "PipeWire:Interface:Device",
2830 "PipeWire:Interface:Node",
2931 "PipeWire:Interface:Port",
3032 "PipeWire:Interface:Link",
···9294 )
9395 src["nodes"].append(i)
9496 return found
9797+9898+9999+# bluez devices whose profile was switched on, so a phone that turns it off
100100+# again is left alone
101101+profiled = set()
102102+103103+104104+# WirePlumber leaves a newly paired phone on the "off" profile when its A2DP
105105+# transport is not up yet at connect time, so it gets no bluez_input node.
106106+# Switch such devices to the A2DP (audio-gateway) profile once.
107107+def bluetooth_profiles():
108108+ for i, info in objects("Device"):
109109+ if info["props"].get("device.api") != "bluez5" or i in profiled:
110110+ continue
111111+ params = info.get("params", {})
112112+ current = (params.get("Profile") or [{}])[0].get("name")
113113+ gateway = [
114114+ p for p in params.get("EnumProfile", [])
115115+ if p.get("name") == "audio-gateway" and p.get("available") != "no"
116116+ ]
117117+ if current == "off" and gateway:
118118+ run("wpctl", "set-profile", i, gateway[0]["index"])
119119+ profiled.add(i)
951209612197122# whether both nodes have their ports to link yet
···239264240265241266def sync():
267267+ bluetooth_profiles()
242268 found = sources()
243269 for key in [k for k in published if k not in found]:
244270 unpublish(key)