added marantz watcher script

This commit is contained in:
Mathieu Broillet 2025-08-28 13:03:05 +02:00
parent 1dabffeec9
commit b42d75dca8
Signed by: mathieub
GPG Key ID: 4428608CDA3A98D3

140
scripts/marantz-watcher.py Normal file
View File

@ -0,0 +1,140 @@
#!/usr/bin/env python3
# THIS IS AI-GENERATED, USE AT YOUR OWN RISK
# Simple script to turn on, set volume and source when Plexamp starts playing using Marantz API. Tested on a Marantz M-CR510.
# To be run on same machine that outputs audio (where Plexamp Headless is installed)
import glob
import time
import xml.etree.ElementTree as ET
import requests
# ================= CONFIG =================
RECEIVER_IP = "10.X.X.X"
INPUT_PLAYING = "SIAUXD"
INPUT_FALLBACK = "SISERVER" # optional fallback input when music stops
FALLBACK_BEHAVIOR = "nothing" # "standby" or "input" or "nothing"
CHECK_INTERVAL = 5 # seconds between ALSA checks
POWER_ON_TIMEOUT = 5 # seconds for power-on request
SOURCE_SWITCH_DELAY = 10 # seconds to wait after power-on before switching input
# Denon volume range
VOL_MIN_DB = -79.5
VOL_MAX_DB = 18.0
# ==========================================
def parse_status(file_path):
"""Parse ALSA PCM state."""
try:
with open(file_path, "r") as f:
content = f.read()
if "RUNNING" in content:
return "playing"
elif "PREPARED" in content:
return "paused"
elif "closed" in content:
return "off"
except Exception:
return None
return None
def get_audio_state():
"""Return overall audio state based on ALSA status."""
states = []
for f in glob.glob("/proc/asound/card*/pcm*/sub*/status"):
state = parse_status(f)
if state:
states.append(state)
if "playing" in states:
return "playing"
if "paused" in states:
return "paused"
return "off"
def is_receiver_on():
"""Check if Marantz is powered on via /formMainZone_MainZoneXml.xml."""
try:
r = requests.get(f"http://{RECEIVER_IP}/goform/formMainZone_MainZoneXml.xml", timeout=5)
r.raise_for_status()
root = ET.fromstring(r.text)
power_node = root.find(".//Power/value")
if power_node is not None:
power_state = power_node.text.strip().upper()
print(f"Receiver power state: {power_state}")
return power_state != "STANDBY"
except Exception as e:
print("⚠️ Error checking receiver power:", e)
return False
def set_volume(decimal_volume: float):
"""
Set Denon volume.
decimal_volume: 0.0 = min, 1.0 = max
"""
decimal_volume = max(0.0, min(1.0, decimal_volume))
denon_db = decimal_volume * (VOL_MAX_DB - VOL_MIN_DB) + VOL_MIN_DB
try:
print(f"http://{RECEIVER_IP}/goform/formiPhoneAppVolume.xml?1+{denon_db:.0f}")
requests.get(f"http://{RECEIVER_IP}/goform/formiPhoneAppVolume.xml?1+{denon_db:.0f}", timeout=5)
print(f"➡️ Volume set to {denon_db:.2f} dB ({decimal_volume * 100:.0f}%)")
except requests.exceptions.RequestException as e:
print("⚠️ Error setting volume:", e)
def marantz_power_on():
if not is_receiver_on():
try:
requests.get(f"http://{RECEIVER_IP}/goform/formiPhoneAppPower.xml?1+PowerOn", timeout=POWER_ON_TIMEOUT)
print("➡️ Power on request sent")
time.sleep(SOURCE_SWITCH_DELAY)
except requests.exceptions.RequestException as e:
print("⚠️ Error powering on:", e)
# Always switch input after ensuring receiver is on
try:
requests.get(f"http://{RECEIVER_IP}/goform/formiPhoneAppInputSelect.xml?{INPUT_PLAYING}", timeout=5)
print(f"➡️ Input switched to {INPUT_PLAYING}")
except requests.exceptions.RequestException as e:
print("⚠️ Error switching input:", e)
def marantz_power_off():
try:
requests.get(f"http://{RECEIVER_IP}/goform/formiPhoneAppPower.xml?1+PowerStandby", timeout=5)
print("➡️ Power standby request sent")
except requests.exceptions.RequestException as e:
print("⚠️ Error sending power off:", e)
def marantz_fallback_input():
try:
requests.get(f"http://{RECEIVER_IP}/goform/formiPhoneAppInputSelect.xml?{INPUT_FALLBACK}", timeout=5)
print(f"➡️ Input switched to fallback {INPUT_FALLBACK}")
except requests.exceptions.RequestException as e:
print("⚠️ Error switching to fallback input:", e)
def main():
last_state = None
while True:
state = get_audio_state()
if state != last_state:
print(f"🔄 State changed: {last_state} -> {state}")
if state == "playing":
marantz_power_on()
set_volume(0.35)
elif state == "off":
set_volume(0.1)
if FALLBACK_BEHAVIOR == "standby":
marantz_power_off()
elif FALLBACK_BEHAVIOR == "input":
marantz_fallback_input()
# paused -> do nothing
last_state = state
time.sleep(CHECK_INTERVAL)
if __name__ == "__main__":
main()