#!/usr/bin/env python3 import json import signal import time import jack import yaml import paho.mqtt.client as mqtt CONFIG_FILE = "config.yaml" class JackRouter: def __init__(self): with open(CONFIG_FILE, "r") as f: self.cfg = yaml.safe_load(f) self.client = jack.Client("AudioRouter") self.mode = "mute_all" self.base_topic = self.cfg["mqtt"]["base_topic"].rstrip("/") def connect(self, src, dst): try: if not self.client.get_all_connections(dst).count(src): self.client.connect(src, dst) print(f"Connected: {src} -> {dst}") except jack.JackError as e: print(f"Connect failed: {src} -> {dst}: {e}") def disconnect(self, src, dst): try: self.client.disconnect(src, dst) print(f"Disconnected: {src} -> {dst}") except jack.JackError: pass def clear_routes(self): all_routes = self.cfg["routes"] for route in all_routes.values(): source = self.cfg["ports"][route["source"]] output = self.cfg["ports"][route["output"]] pairs = [ (source["left"], output["left"]), (source["right"], output["right"]), ] for src, dst in pairs: self.disconnect(src, dst) def routes_for_mode(self, mode): preset = self.cfg["presets"].get(mode) if preset is None: return None resolved_routes = [] for route_name, enabled in preset.items(): if not enabled: continue route = self.cfg["routes"][route_name] source_name = route["source"] output_name = route["output"] source = self.cfg["ports"][source_name] output = self.cfg["ports"][output_name] resolved_routes.append((source["left"], output["left"])) resolved_routes.append((source["right"], output["right"])) return resolved_routes def set_mode(self, mode): routes = self.routes_for_mode(mode) if routes is None: print(f"Unknown mode: {mode}") return False print(f"Setting mode: {mode}") self.clear_routes() for src, dst in routes: self.connect(src, dst) self.mode = mode return True def state(self): return { "mode": self.mode, "running": True, "timestamp": int(time.time()), } def main(): router = JackRouter() mqtt_cfg = router.cfg["mqtt"] client = mqtt.Client() def publish_state(): client.publish(f"{router.base_topic}/state", json.dumps(router.state()), retain=True) client.publish(f"{router.base_topic}/mode/state", router.mode, retain=True) def on_connect(client, userdata, flags, rc): print(f"MQTT connected: {rc}") client.subscribe(f"{router.base_topic}/mode/set") router.set_mode("aux_only") publish_state() def on_message(client, userdata, msg): mode = msg.payload.decode().strip() print(f"MQTT received: {msg.topic} = {mode}") if router.set_mode(mode): publish_state() def shutdown(*args): print("Stopping router...") router.set_mode("mute_all") publish_state() raise SystemExit signal.signal(signal.SIGINT, shutdown) signal.signal(signal.SIGTERM, shutdown) client.on_connect = on_connect client.on_message = on_message with router.client: client.connect(mqtt_cfg["host"], int(mqtt_cfg["port"]), 60) client.loop_forever() if __name__ == "__main__": main()