Files
awtrix-abfallkalender/awtrix3-abfallkalender.js
T
2026-05-15 13:42:15 +02:00

173 lines
4.8 KiB
JavaScript

/*********** 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();