commit 4206560574b3c3f4bab8c62149af8ce6726b877a Author: mike Date: Fri May 15 13:42:15 2026 +0200 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..17e0dd0 --- /dev/null +++ b/README.md @@ -0,0 +1,260 @@ +\# AWTRIX Müllkalender (ioBroker) + + + +Zeigt bevorstehende Müllabfuhrtermine auf einer AWTRIX 3 Matrix-Uhr über MQTT an. + + + +Das Skript nutzt den ioBroker Trashschedule-Adapter und veröffentlicht automatisch Hinweise für: + + + +\* Hausmüll + +\* Biomüll + +\* Gelbe Tonne + + + +Die Anzeige erfolgt als AWTRIX Custom App inklusive: + + + +\* passendem Icon + +\* individueller Farbe je Müllart + +\* automatischer Rotation + +\* Keep-Alive Refresh + + + +\--- + + + +\# Funktionen + + + +\* Anzeige der nächsten Müllabfuhr auf AWTRIX 3 + +\* Automatische Anzeige: + + + + \* ab Vortag 16:00 Uhr + + \* bis zum Abholtag 10:00 Uhr + +\* Unterschiedliche Farben je Müllart + +\* Eigene AWTRIX-Icons + +\* MQTT-basierte Kommunikation + +\* Keep-Alive Refresh gegen Ablauf der AWTRIX-App + +\* Fallback auf `/notify` möglich + + + +\--- + + + +\# Voraussetzungen + + + +\## ioBroker Adapter + + + +Benötigt werden: + + + +\* JavaScript-Adapter + +\* MQTT-Adapter + +\* Trashschedule-Adapter + + + +\--- + + + +\# Verwendete Datenpunkte + + + +```text + +trashschedule.0.type.hausmuell.nextDate + +trashschedule.0.type.biotonne.nextDate + +trashschedule.0.type.gelbetonne.nextDate + +``` + + + +\--- + + + +\# AWTRIX Voraussetzungen + + + +\* AWTRIX 3 + +\* MQTT-Anbindung aktiviert + +\* Custom Apps aktiviert + + + +\--- + + + +\# MQTT Topic + + + +```text + +awtrix/custom/TrashInfo + +``` + + + +\--- + + + +\# Konfiguration + + + +Im oberen Bereich des Skripts können folgende Werte angepasst werden: + + + +\## MQTT + + + +```javascript + +const MQTT\_INSTANCE = "mqtt.0"; + +const AWTRIX\_PREFIX = "awtrix"; + +``` + + + +\## Anzeige + + + +```javascript + +const ROTATION\_NAME = "TrashInfo"; + +const LIFETIME\_SEC = 600; + +``` + + + +\## Zeitfenster + + + +```javascript + +const CRON\_CHECK = "\*/5 \* \* \* \*"; + +``` + + + +Standard: + + + +\* Prüfung alle 5 Minuten + +\* Anzeige: + + + + \* ab Vortag 16:00 Uhr + + \* bis Abholtag 10:00 Uhr + + + +\--- + + + +\# Unterstützte Müllarten + + + +| Typ | Farbe | + +| ----------- | ----- | + +| Hausmüll | Weiß | + +| Biomüll | Braun | + +| Gelbe Tonne | Gelb | + + + +\--- + + + +\# Besonderheiten + + + +Das Skript nutzt einen regelmäßigen Keep-Alive Refresh, damit die AWTRIX-App nicht aus der Rotation verschwindet. + + + +Wenn kein relevanter Mülltermin mehr aktiv ist, wird die Anzeige automatisch entfernt. + + + +\--- + + + +\# Datei + + + +```text + +awtrix3-abfallkalender.js + +``` + + + diff --git a/awtrix3-abfallkalender.js b/awtrix3-abfallkalender.js new file mode 100644 index 0000000..1ee0055 --- /dev/null +++ b/awtrix3-abfallkalender.js @@ -0,0 +1,172 @@ +/*********** Einstellungen ***********/ +const MQTT_INSTANCE = "mqtt.0"; +const AWTRIX_PREFIX = "awtrix"; // Dein AWTRIX-Prefix +const USE_CUSTOM = true; // true = Rotation via /custom, false = Fallback /notify + +// Trashschedule-Datenpunkte (exakte Pfade laut Screenshot) +const DP = { + rest: "trashschedule.0.type.hausmuell.nextDate", + bio: "trashschedule.0.type.biotonne.nextDate", + gelb: "trashschedule.0.type.gelbetonne.nextDate" +}; + +// Labels, AWTRIX Icon-IDs und Farben +const BIN = { + rest: { + label: "Hausmüll", + iconId: 12155, + color: [255, 255, 255] // Weiß + }, + bio: { + label: "Biomüll", + iconId: 12442, + color: [198, 133, 53] // Braun + }, + gelb: { + label: "Gelbe Tonne", + iconId: 12212, + color: [255, 255, 0] // Gelb + } +}; + +// Zeitsteuerung / Anzeige +const CRON_CHECK = "*/5 * * * *"; // alle 5 Minuten prüfen +const ROTATION_NAME = "TrashInfo"; // Name der Custom-App (Topic: awtrix/custom/TrashInfo) +const LIFETIME_SEC = 600; // Eintrag verschwindet nach 10 Min ohne Update +/************************************/ + +/*********** Helper-Funktionen ***********/ +// robust: unterstützt number (ms), Date-Objekte und Strings +function parseDate(val) { + if (val === null || val === undefined) return null; + + if (typeof val === "number") { + const d = new Date(val); + return isNaN(d) ? null : d; + } + if (Object.prototype.toString.call(val) === "[object Date]") { + return isNaN(val) ? null : val; + } + const s = String(val).trim(); + if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return new Date(s + "T00:00:00"); + const d = new Date(s); + return isNaN(d) ? null : d; +} + +function startOfDay(d) { + return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0,0,0,0); +} + +// Fenster: Vortag 16:00 → Abholtag 10:00 +function withinWindow(next) { + const day = startOfDay(next); // Abholtag 00:00 + const windowStart = new Date(day); + windowStart.setDate(windowStart.getDate() - 1); + windowStart.setHours(16,0,0,0); // Vortag 16:00 + const windowEnd = new Date(day); + windowEnd.setHours(10,0,0,0); // Abholtag 10:00 + const now = new Date(); + return now >= windowStart && now <= windowEnd; +} + +// gibt "morgen" oder "heute" zurück +function phaseText(next) { + const now = new Date(); + return now < startOfDay(next) ? "morgen" : "heute"; +} + +function readNext(dpId) { + const st = getState(dpId); + return st ? parseDate(st.val) : null; +} + +function sendMQTT(topic, payloadObj) { + sendTo(MQTT_INSTANCE, "sendMessage2Client", { + topic, + message: JSON.stringify(payloadObj), + retain: false, + qos: 0 + }); +} + +/*********** Text & Icon für AWTRIX bauen ***********/ +function buildAppData() { + const items = [ + { key: "rest", dp: DP.rest, meta: BIN.rest }, + { key: "bio", dp: DP.bio, meta: BIN.bio }, + { key: "gelb", dp: DP.gelb, meta: BIN.gelb } + ]; + + const active = items.filter(it => { + const next = readNext(it.dp); + if (!next) return false; + if (!withinWindow(next)) return false; + it.next = next; + return true; + }); + + if (active.length === 0) return null; + + // Eine Zeile anzeigen (erste aktive Tonne) + const first = active[0]; + return { + text: `${first.meta.label} wird ${phaseText(first.next)} abgeholt!`, + icon: first.meta.iconId, + color: first.meta.color, // <-- Farbe je nach Müllart + lifetime: LIFETIME_SEC, + textCase: 2, // Behalte Groß-/Kleinschreibung bei + + }; +} + +/*********** Ausgabe ***********/ +let lastPayload = ""; +function tick() { + const data = buildAppData(); + + if (data) { + const payloadStr = JSON.stringify(data); + const topic = `${AWTRIX_PREFIX}/custom/${ROTATION_NAME}`; + + if (payloadStr !== lastPayload) { + if (USE_CUSTOM) { + sendMQTT(topic, data); + log(`AWTRIX aktualisiert: ${data.text} (Icon ${data.icon}, Farbe ${data.color})`); + } else { + // Fallback über Notify + sendMQTT(`${AWTRIX_PREFIX}/notify`, { + text: data.text, + icon: data.icon, + color: data.color, + duration: 15, + ticker: true, + sound: "notif" + }); + } + lastPayload = payloadStr; + } else { + // Heartbeat, damit lifetime nicht abläuft + if (USE_CUSTOM) sendMQTT(topic, data); + } + } else { + lastPayload = ""; + log("Keine Mülltermine im aktuellen Zeitfenster."); + } +} + +/*********** Scheduler ***********/ +schedule(CRON_CHECK, tick); + +// Optional: Direkt ausführen (zum Test kurz aktivieren, danach wieder auskommentieren) +// tick(); + +/*********** Optionales Debug (zum Prüfen der Rohwerte) ***********/ +// function dumpDps() { +// Object.entries(DP).forEach(([k, id]) => { +// const st = getState(id); +// const raw = st?.val; +// const dt = parseDate(raw); +// log(`[DP:${k}] ${id} -> raw=${raw} | parsed=${dt ? dt.toISOString() : "null"}`); +// }); +// } +// dumpDps();