Initial commit

This commit is contained in:
2026-05-15 12:54:22 +02:00
commit 875640d8f3
5 changed files with 335 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
# (Optional) lokale Notizen / IDE
.DS_Store
.idea/
.vscode/
*.log
+7
View File
@@ -0,0 +1,7 @@
# Changelog
## 0.2.0
- Erste GitHub/Gitea-ready Version (Single-File ioBroker Script)
- Keep-Alive Refresh + Auto-Remove bei Stop/Pause
- Debounce für Sonos Events
- README + MIT License
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+82
View File
@@ -0,0 +1,82 @@
# AWTRIX 3 Sonos NowPlaying (ioBroker Script)
Zeigt den aktuell abgespielten Sonos-Titel als **AWTRIX 3 Custom App** via **MQTT** an:
> 🎵 Künstler — Titel (Album)
Das Script nutzt einen **Keep-Alive Refresh**, damit die App in der Rotation bleibt, solange Sonos spielt. Sobald Playback stoppt/pausiert oder kein Titel mehr vorhanden ist, wird die App automatisch entfernt.
## Features
- ✅ Anzeige: `🎵 Künstler — Titel (Album)`
- ✅ Keep-Alive Refresh (damit die App nicht „rausfällt“)
- ✅ Entfernt die App automatisch bei Stop/Pause/kein Titel
- ✅ Debounce gegen Event-Spam vom Sonos-Adapter
- ✅ Alles in **einer Datei** (ioBroker-friendly)
## Voraussetzungen
- ioBroker **JavaScript-Adapter**
- ioBroker **Sonos-Adapter**
- ioBroker **MQTT-Adapter** (als Client/Publisher nutzbar über `sendMessage2Client`)
- AWTRIX 3 mit aktivierter MQTT-Anbindung (Prefix bekannt, i.d.R. `awtrix`)
## Installation (Quick Start)
1. Datei `nowplaying.js` öffnen und den Block **USER CONFIG** anpassen:
- `AWTRIX_PREFIX` (meist `awtrix`)
- `DP.*` (deine Sonos-Datenpunkte)
2. In ioBroker → **JavaScript** → neues Script anlegen → Inhalt von `nowplaying.js` einfügen
3. Script starten
4. Sonos abspielen → nach spätestens wenigen Sekunden sollte es auf der AWTRIX erscheinen
## Sonos-Datenpunkte finden
In ioBroker unter **Objekte**:
`sonos.0``root``<dein Gerät>`
- `current_title`
- `current_artist`
- `current_album`
- `state_simple`
Kopiere die Objekt-IDs in `CFG.DP`.
## AWTRIX MQTT Topics (was das Script sendet)
- Custom App:
- `<prefix>/custom/<APP_NAME>`
- Beispiel: `awtrix/custom/NowPlaying`
- Optionaler Switch:
- `<prefix>/switch`
- Wird nur genutzt, wenn `FORCE_SWITCH=true`
## Konfiguration (wichtigste Optionen)
Im `CFG` Block:
- `LIFETIME_SEC`: Wie lange ein Eintrag ohne Refresh überlebt
- `KEEPALIVE_SEC`: Alle wieviel Sekunden refreshed wird
- `FORCE_SWITCH`: Wenn `true`, schaltet AWTRIX bei jedem Refresh aktiv auf die App (meist **false** lassen)
- `ICON_MUSIC`, `COLOR_RGB`, `TEXT_CASE`: Darstellung
- `DEBUG`: Zusätzliche Logs
## Troubleshooting
### Es wird nichts angezeigt
- Stimmt `AWTRIX_PREFIX`?
- Ist MQTT auf der AWTRIX aktiv?
- Funktioniert dein MQTT Adapter (und kann publishen)?
- Stimmen die Sonos-Datenpunkte?
### App verschwindet nach kurzer Zeit
- `KEEPALIVE_SEC` ggf. kleiner setzen (z.B. 510)
- `LIFETIME_SEC` größer setzen (z.B. 6001200)
- Prüfen ob `state_simple` wirklich `true` während Playback ist
### Die Uhr springt ständig auf die App
- `FORCE_SWITCH` auf `false` setzen
## Lizenz
MIT siehe `LICENSE`.
+220
View File
@@ -0,0 +1,220 @@
/******************************************************
* 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);