/****************************************************** * AWTRIX NowPlaying – Sonos → AWTRIX Custom App * Version 0.0.1 * Autor: Mike * Zweck: Zeigt "🎵 Künstler — Titel (Album)" auf der AWTRIX * Trigger: Änderungen an Sonos-Datenpunkten (Titel/Artist/Album) ******************************************************/ /*********** 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" }; /*************************************/ /*********** Helpers ***********/ function readVal(id) { const st = getState(id); return st ? (st.val ?? "") : ""; } 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) { // ioBroker als Broker → sendMessage2Client 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 (keine Titelinfos)"); } /*********** 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 ist keepAliveTmr = setInterval(() => { // Wenn Titel leer geworden ist, sofort stoppen und App entfernen const t = String(readVal(DP.title)).trim(); if (!t) { 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(); // Kein Lied → alles aus if (!title && !artist && !album) { 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); } else { // Gleicher Track: nichts weiter – Keep-Alive tickt von selbst } } /*********** Trigger & Initiallauf ***********/ function scheduleUpdate() { if (debounceTmr) clearTimeout(debounceTmr); debounceTmr = setTimeout(updateAwtrix, 200); } on({ id: [DP.title, DP.artist, DP.album], change: "ne" }, scheduleUpdate); // Beim Start einmal versuchen setTimeout(updateAwtrix, 1500);