/****************************************************** * AWTRIX NowPlaying – Sonos → AWTRIX Custom App * Version 0.0.2 * Autor: Mike * Zweck: Zeigt "🎵 Künstler — Titel (Album)" auf der AWTRIX * Trigger: Änderungen an Sonos-Datenpunkten (Titel/Artist/Album/State) ******************************************************/ /*********** Einstellungen ***********/ const MQTT_INSTANCE = "mqtt.0"; const AWTRIX_PREFIX = "awtrix"; // Dein Prefix const APP_NAME = "NowPlaying"; const LIFETIME_SEC = 600; // Eintrag läuft ab, wenn kein Refresh kommt const KEEPALIVE_SEC = 10; // alle x Sekunden Refresh solange Titel vorhanden const FORCE_SWITCH = false; // bei jedem Refresh auf App schalten (falls viele Apps) /* Anzeige */ const ICON_MUSIC = 29944; const COLOR_RGB = [255, 255, 255]; const TEXT_CASE = 2; /* Sonos-Datenpunkte (ggf. anpassen) */ const DP = { title: "sonos.0.root.192_168_178_75.current_title", artist: "sonos.0.root.192_168_178_75.current_artist", album: "sonos.0.root.192_168_178_75.current_album", stateSimple: "sonos.0.root.192_168_178_75.state_simple" // true = spielt, false = kein Playback }; /*************************************/ /*********** Helpers ***********/ function readVal(id) { const st = getState(id); return st ? (st.val ?? "") : ""; } function readBool(id) { const st = getState(id); return st ? !!st.val : false; } function isPlaying() { // state_simple ist bereits Boolean: true = Wiedergabe return readBool(DP.stateSimple); } function buildText(artist, title, album) { let base = ""; if (artist && title) base = `${artist} — ${title}`; else if (title) base = title; else if (artist) base = artist; if (base && album) base += ` (${album})`; if (!base) base = "—"; return `🎵 ${base}`; } function sendMQTT(topic, payloadObj) { sendTo(MQTT_INSTANCE, "sendMessage2Client", { topic, message: JSON.stringify(payloadObj), retain: false, qos: 0 }); } function publishCustom(text) { const payload = { name: APP_NAME, text, icon: ICON_MUSIC, color: COLOR_RGB, textCase: TEXT_CASE, lifetime: LIFETIME_SEC }; sendMQTT(`${AWTRIX_PREFIX}/custom/${APP_NAME}`, payload); if (FORCE_SWITCH) sendMQTT(`${AWTRIX_PREFIX}/switch`, { name: APP_NAME }); } function removeApp() { sendMQTT(`${AWTRIX_PREFIX}/custom/${APP_NAME}`, { name: APP_NAME, lifetime: 1 }); log("🛑 NowPlaying entfernt (kein Playback)"); } /*********** Kernlogik ***********/ let currentSig = ""; // artist|title|album let keepAliveTmr = null; // setInterval-Handle let debounceTmr = null; function stopKeepAlive() { if (keepAliveTmr) { clearInterval(keepAliveTmr); keepAliveTmr = null; } } function startKeepAlive(text) { stopKeepAlive(); // Erster Push sofort (sichtbar machen) publishCustom(text); // … und dann regelmäßig solange Titel vorhanden UND Playback aktiv keepAliveTmr = setInterval(() => { const t = String(readVal(DP.title)).trim(); const playing = isPlaying(); // Wenn kein Titel oder nicht mehr playing → sofort stoppen und App entfernen if (!t || !playing) { stopKeepAlive(); currentSig = ""; removeApp(); return; } publishCustom(text); }, KEEPALIVE_SEC * 1000); } function updateAwtrix() { const title = String(readVal(DP.title)).trim(); const artist = String(readVal(DP.artist)).trim(); const album = String(readVal(DP.album)).trim(); const playing = isPlaying(); // Kein Lied ODER Player nicht im Play-Status → alles aus if ((!title && !artist && !album) || !playing) { stopKeepAlive(); if (currentSig) removeApp(); currentSig = ""; return; } const text = buildText(artist, title, album); const sig = `${artist}|${title}|${album}`; // Neuer/anderer Track → Keep-Alive neu starten if (sig !== currentSig) { currentSig = sig; log(`🎧 NowPlaying → ${text}`); startKeepAlive(text); } } /*********** Trigger & Initiallauf ***********/ function scheduleUpdate() { if (debounceTmr) clearTimeout(debounceTmr); debounceTmr = setTimeout(updateAwtrix, 200); } // Triggert jetzt auch auf state_simple on({ id: [DP.title, DP.artist, DP.album, DP.stateSimple], change: "ne" }, scheduleUpdate); // Beim Start einmal versuchen setTimeout(updateAwtrix, 1500);