🗓️ Abfuhrtermine (Abfall)
ioBroker Beispiel: Automatische VIS2-Visualisierung von iCal-Abfuhrtermine mit dynamischer Icon-Zuordnung und Echtzeit-Aktualisierung
Verwende in VIS2 z.B. das "Basic"-Widget mit der Option "String (unescaped)", um die Termine anzuzeigen:

Funktionen
- Zeigt Abfuhrtermine als übersichtliche HTML-Tabelle
- Unterstützt alle gängigen Abfallarten (Restmüll, Papier, Gelber Sack etc.)
- Wählbares Datumsformat:
Mo, 01.01.(weekday_first)01.01. (Mo)(short)01.01.2024 (Mo)(long)
- Anzahl der angezeigten Termine einstellbar (Standard: 4)
- Es werden nur heutige und zukünftige Termine angezeigt
- Termine werden automatisch nach Datum sortiert
- Hervorhebung für heutige Termine: Hellgrüner Hintergrund
- Hervorhebung für morgen fällige Termine: Hellgelber Hintergrund
- Automatische Anpassung der Textfarbe für optimale Lesbarkeit auf den Hervorhebungsfarben
- Textschatten für bessere Lesbarkeit auf dunklem Hintergrund (wird bei Hervorhebungen deaktiviert)
Voraussetzungen
| Was du brauchst | Details |
|---|---|
| ical-Adapter | Muss installiert und mit deinem Abfuhrtermin (Kalender URL) verbunden sein |
| ical-URL | Beispiel: https://art-trier.de/ics-feed/[PLZ]:[ORT]@.ics |
| Datenpunkt | ical.0.data.table wird automatisch vom ical-Adapter erstellt und mit den Kalenderdaten gefüllt |
| Icons | Abfall-Symbole im Ordner /vis-2.0/Abfall/ |
| JavaScript-Adapter | Für das Verarbeitungs-Skript (z.B. javascript.0) |
Funktionsweise
-
Datenpunkt
ical.0.data.table:- Der ical-Adapter erstellt diesen Datenpunkt automatisch, sobald er die Kalenderdaten erfolgreich abruft und verarbeitet.
-
JavaScript-Skript:
- Ein Skript registriert einen Event-Handler für Änderungen an
ical.0.data.table. - Bei Änderungen generiert das Skript HTML-Code für die Anzeige der Abfalltermine.
- Ein Skript registriert einen Event-Handler für Änderungen an
Zusammenfassung
- Der ical-Adapter liest die Kalenderdaten und erstellt automatisch den Datenpunkt
ical.0.data.table. - Ein JavaScript-Skript reagiert auf Änderungen dieses Datenpunkts und erstellt eine benutzerfreundliche Anzeige der Abfalltermine.
⚡ JavaScript (Skriptname: "Abfall")
// ioBroker Abfuhrtermine (Abfall)
// V 2026-04-01b
// =====================================
// Um manuell auszulösen, starte einfach den ical Adapter neu (reload)
// ===== EINSTELLUNGEN =====
const DATE_FORMAT = "weekday_first"; // "short", "long", "weekday_first"
const MAX_TERMINE = 4;
const TEST_ALL_TODAY = false; // TESTSCHALTER (true = alles wird "today")
const STATE_ID = 'javascript.0.Abfall';
// ===== KONSTANTEN =====
const ABFALL_TYPES = {
PAPIER_GELBER_SACK: {
name: ["Papier & Gelber Sack", "Papierabfall", "Gelber Sack", "Papier & Gelber Sack"],
image: "/vis-2.0/Abfall/gelber_sack_und_papierabfall.png"
},
PAPIER: {
name: "Papier",
image: "/vis-2.0/Abfall/papierabfall.png"
},
GELBER_SACK: {
name: "Gelber Sack",
image: "/vis-2.0/Abfall/gelber_sack.png"
},
RESTMUELL: {
name: ["Restabfall", "Restmüll"],
image: "/vis-2.0/Abfall/restmuell.png"
}
};
// ===== HILFSFUNKTIONEN =====
function escapeHTML(str) {
if (!str) return "";
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
}[m]));
}
function getAbfallType(eventText) {
if (!eventText) return ABFALL_TYPES.RESTMUELL;
const normalizedEventText = eventText.toLowerCase().trim();
for (const type of Object.values(ABFALL_TYPES)) {
const names = Array.isArray(type.name) ? type.name : [type.name];
for (const name of names) {
if (normalizedEventText.includes(name.toLowerCase().trim())) {
return type;
}
}
}
return ABFALL_TYPES.RESTMUELL;
}
function parseDate(dateStr) {
if (!dateStr) return null;
const match = dateStr.match(/^(\d{2})\.(\d{2})\.(\d{4})/);
if (!match) return null;
const day = Number(match[1]);
const month = Number(match[2]);
const year = Number(match[3]);
const date = new Date(year, month - 1, day);
return isNaN(date) ? null : date;
}
function formatDateFromDate(dateObj, format = DATE_FORMAT) {
if (!dateObj || isNaN(dateObj)) return "–";
const weekdays = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"];
const weekday = weekdays[dateObj.getDay()];
const day = String(dateObj.getDate()).padStart(2, "0");
const month = String(dateObj.getMonth() + 1).padStart(2, "0");
const year = dateObj.getFullYear();
switch (format) {
case "long":
return `${day}.${month}.${year} (${weekday})`;
case "weekday_first":
return `${weekday}, ${day}.${month}.`;
case "short":
default:
return `${day}.${month}. (${weekday})`;
}
}
function normalizeDate(date) {
const d = new Date(date);
d.setHours(0, 0, 0, 0);
return d;
}
function isToday(date) {
const today = normalizeDate(new Date());
return normalizeDate(date).getTime() === today.getTime();
}
function isTomorrow(date) {
const tomorrow = normalizeDate(new Date());
tomorrow.setDate(tomorrow.getDate() + 1);
return normalizeDate(date).getTime() === tomorrow.getTime();
}
// ===== HAUPTFUNKTION =====
function MakeAbfallHTML(obj) {
if (!obj || !Array.isArray(obj)) {
setState(STATE_ID, "<div style='color:red;'>Fehler: Ungültige Kalenderdaten</div>", true);
return;
}
if (obj.length === 0) {
setState(STATE_ID, "<div>Keine Termine vorhanden</div>", true);
return;
}
const today = normalizeDate(new Date());
const cleaned = obj
.map(entry => {
const date = parseDate(entry?.date);
return { ...entry, parsedDate: date };
})
.filter(entry => entry.parsedDate && normalizeDate(entry.parsedDate) >= today)
.sort((a, b) => a.parsedDate - b.parsedDate);
if (cleaned.length === 0) {
setState(STATE_ID, "<div>Keine zukünftigen Termine</div>", true);
return;
}
const termineCount = Math.min(MAX_TERMINE, cleaned.length);
let html = "<table class='Abfall'><tr>";
// ===== BILDERZEILE =====
for (let i = 0; i < termineCount; i++) {
const entry = cleaned[i];
if (entry?.event) {
const abfallType = getAbfallType(entry.event);
const label = Array.isArray(abfallType.name) ? abfallType.name[0] : abfallType.name;
html += `
<td class='Abfallimage'>
<img
width='80'
height='80'
src='${abfallType.image}'
alt='${escapeHTML(label)}'
>
</td>`;
} else {
html += "<td class='Abfallimage'></td>";
}
}
// ===== DATUMSZEILE =====
html += "</tr><tr>";
for (let i = 0; i < termineCount; i++) {
const entry = cleaned[i];
const eventDate = entry.parsedDate;
let dateClass = 'AbfallText';
if (TEST_ALL_TODAY) {
dateClass += ' today';
} else if (eventDate) {
if (isToday(eventDate)) {
dateClass += ' today';
} else if (isTomorrow(eventDate)) {
dateClass += ' tomorrow';
}
}
const dateStr = formatDateFromDate(eventDate);
html += `<td class='${dateClass}'>${escapeHTML(dateStr)}</td>`;
}
html += "</tr></table>";
setState(STATE_ID, html, true);
}
// ===== EVENT-HANDLER für Änderungen an 'ical.0.data.table' =====
on('ical.0.data.table', function (theObj) {
try {
MakeAbfallHTML(theObj?.state?.val);
} catch (error) {
console.error("Fehler im Abfall-Script:", error.stack || error);
setState(
STATE_ID,
`<div style='color:red;'>Script-Fehler: ${escapeHTML(error.message)}</div>`,
true
);
}
});
🎨 Beispiel CSS für das VIS2
/* Abfuhrtermine-Stil */
.Abfall {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
.Abfall td {
padding: 2px;
text-align: center;
vertical-align: middle;
}
/* Bilder */
.Abfallimage img {
max-width: 80px;
max-height: 80px;
}
/* Basis-Text */
.AbfallText {
width: 100%;
color: #fff;
font: 14px Arial, sans-serif;
text-shadow: 1px 1px 2px #000;
}
/* Hervorhebung */
.AbfallText.today,
.AbfallText.tomorrow {
font-weight: bold;
color: #000;
text-shadow: none;
padding: 4px 6px;
border-radius: 4px;
}
/* Spezifische Farben */
.AbfallText.today {
background-color: #e6ffe6;
}
.AbfallText.tomorrow {
background-color: #fff8e6;
}
