Files

221 lines
6.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/******************************************************
* AWTRIX NowPlaying Sonos → AWTRIX Custom App (ioBroker)
* Version: 0.2.0
* Autor: Mike (Repo-ready Version)
*
* Zweck:
* - Zeigt "🎵 Künstler — Titel (Album)" auf der AWTRIX 3 (via MQTT Custom App)
* - Keep-Alive Refresh hält die App in der Rotation, solange Sonos spielt & Titel vorhanden ist
* - Entfernt die App sofort, wenn Playback stoppt/pausiert oder kein Titel vorhanden ist
*
* Voraussetzungen:
* - ioBroker JavaScript-Adapter
* - ioBroker MQTT-Adapter (als Client/Publisher) -> sendTo(..., "sendMessage2Client", ...)
* - ioBroker Sonos-Adapter
* - AWTRIX 3 mit MQTT aktiviert
******************************************************/
/******************** USER CONFIG (ANPASSEN) ********************/
const CFG = {
/* ioBroker MQTT Adapter-Instanz */
MQTT_INSTANCE: "mqtt.0",
/* AWTRIX MQTT Prefix (Standard: "awtrix") */
AWTRIX_PREFIX: "awtrix",
/* Name der Custom App auf der AWTRIX */
APP_NAME: "NowPlaying",
/* Wie lange darf der AWTRIX-Eintrag ohne Refresh leben (Sekunden) */
LIFETIME_SEC: 600,
/* Alle x Sekunden ein Refresh, solange Titel vorhanden UND Sonos spielt */
KEEPALIVE_SEC: 10,
/* Wenn true: bei jedem Refresh wird aktiv auf die App gewechselt (kann nerven) */
FORCE_SWITCH: false,
/* Anzeige */
ICON_MUSIC: 29944,
COLOR_RGB: [255, 255, 255],
TEXT_CASE: 2, // 0=none, 1=upper, 2=lower (AWTRIX Setting, je nach Firmware)
/* Sonos-Datenpunkte (ANPASSEN!)
* Tipp: In ioBroker Objekte -> sonos.0 -> root -> <dein Gerät> -> current_title / current_artist / current_album / state_simple
*/
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
},
/* Textformat */
PREFIX_EMOJI: "🎵",
SEP_ARTIST_TITLE: " — ",
SHOW_ALBUM_IN_PARENS: true,
/* Debounce für schnelle Sonos-Updates (ms) */
DEBOUNCE_MS: 200,
/* Optional: Debug-Logs */
DEBUG: false
};
/******************** /USER CONFIG ******************************/
/******************** Helpers ******************************/
function dbg(msg) {
if (CFG.DEBUG) log(`🐞 ${msg}`);
}
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 im Sonos-Adapter i.d.R. Boolean: true = Wiedergabe
return readBool(CFG.DP.stateSimple);
}
function buildText(artist, title, album) {
let base = "";
const a = (artist || "").trim();
const t = (title || "").trim();
const al = (album || "").trim();
if (a && t) base = `${a}${CFG.SEP_ARTIST_TITLE}${t}`;
else if (t) base = t;
else if (a) base = a;
if (base && CFG.SHOW_ALBUM_IN_PARENS && al) base += ` (${al})`;
if (!base) base = "—";
return `${CFG.PREFIX_EMOJI} ${base}`;
}
function sendMQTT(topic, payloadObj) {
sendTo(CFG.MQTT_INSTANCE, "sendMessage2Client", {
topic,
message: JSON.stringify(payloadObj),
retain: false,
qos: 0
});
}
function publishCustom(text) {
const payload = {
name: CFG.APP_NAME,
text,
icon: CFG.ICON_MUSIC,
color: CFG.COLOR_RGB,
textCase: CFG.TEXT_CASE,
lifetime: CFG.LIFETIME_SEC
};
sendMQTT(`${CFG.AWTRIX_PREFIX}/custom/${CFG.APP_NAME}`, payload);
dbg(`publishCustom -> ${text}`);
if (CFG.FORCE_SWITCH) {
sendMQTT(`${CFG.AWTRIX_PREFIX}/switch`, { name: CFG.APP_NAME });
dbg("FORCE_SWITCH -> switch");
}
}
function removeApp() {
// "lifetime: 1" sorgt dafür, dass die App praktisch sofort aus der Rotation fällt
sendMQTT(`${CFG.AWTRIX_PREFIX}/custom/${CFG.APP_NAME}`, {
name: CFG.APP_NAME,
lifetime: 1
});
dbg("removeApp");
log("🛑 NowPlaying entfernt (kein Playback)");
}
/******************** Kernlogik ******************************/
let currentSig = ""; // artist|title|album
let keepAliveTmr = null; // setInterval-Handle
let debounceTmr = null; // setTimeout-Handle
function stopKeepAlive() {
if (keepAliveTmr) {
clearInterval(keepAliveTmr);
keepAliveTmr = null;
dbg("stopKeepAlive");
}
}
function startKeepAlive(text) {
stopKeepAlive();
// 1) Sofort pushen (sichtbar machen)
publishCustom(text);
// 2) Dann regelmäßig refreshen solange Titel vorhanden & playing
keepAliveTmr = setInterval(() => {
const title = String(readVal(CFG.DP.title)).trim();
const playing = isPlaying();
if (!title || !playing) {
dbg(`keepAlive stop: title="${title}" playing=${playing}`);
stopKeepAlive();
currentSig = "";
removeApp();
return;
}
// Text bleibt bewusst gleich, solange der Track gleich ist.
publishCustom(text);
}, CFG.KEEPALIVE_SEC * 1000);
dbg(`startKeepAlive every ${CFG.KEEPALIVE_SEC}s`);
}
function updateAwtrix() {
const title = String(readVal(CFG.DP.title)).trim();
const artist = String(readVal(CFG.DP.artist)).trim();
const album = String(readVal(CFG.DP.album)).trim();
const playing = isPlaying();
dbg(`updateAwtrix: playing=${playing} title="${title}" artist="${artist}" album="${album}"`);
// Kein Lied ODER Player nicht playing -> 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 -> KeepAlive neu starten
if (sig !== currentSig) {
currentSig = sig;
log(`🎧 NowPlaying → ${text}`);
startKeepAlive(text);
}
}
/******************** Trigger & Initiallauf ******************************/
function scheduleUpdate() {
if (debounceTmr) clearTimeout(debounceTmr);
debounceTmr = setTimeout(updateAwtrix, CFG.DEBOUNCE_MS);
}
// Triggert auf Titel/Artist/Album/State
on(
{ id: [CFG.DP.title, CFG.DP.artist, CFG.DP.album, CFG.DP.stateSimple], change: "ne" },
scheduleUpdate
);
// Beim Start einmal versuchen (Adapter brauchen manchmal kurz)
setTimeout(updateAwtrix, 1500);