Weiter zum Inhalt
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Hell
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dunkel
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. JavaScript
  5. Homepilot Rolladensteuerung per Telegram

NEWS

  • Neuer ioBroker-Blog online: Monatsrückblick März/April 2026
    BluefoxB
    Bluefox
    6
    1
    220

  • Verwendung von KI bitte immer deutlich kennzeichnen
    HomoranH
    Homoran
    8
    1
    217

  • Monatsrückblick Januar/Februar 2026 ist online!
    BluefoxB
    Bluefox
    18
    1
    891

Homepilot Rolladensteuerung per Telegram

Geplant Angeheftet Gesperrt Verschoben JavaScript
javascript
1 Beiträge 1 Kommentatoren 12 Aufrufe 1 Beobachtet
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • H Online
    H Online
    helfi9999
    schrieb zuletzt editiert von helfi9999
    #1

    Hallo zusammen,
    es ist richtig schön mit chat-gpt zu arbeiten habe ein Script entworfen. Habe folgende Hardware: Homepilot, Aqara Sensoren
    Iobroker folgende Adapter: Homepilot 2.0, Deconz, Telegram

    Das Skript hat im aktuellen Stand diese Eigenschaften:
    Grundfunktion
    Es steuert drei Rolläden:
    Fenster
    Terrasse
    Küche
    und kombiniert dabei:
    feste Zeitfenster
    Zufallszeiten
    Astrozeiten (dawn, dusk)
    Fenster-/Türsensoren
    Telegram-Steuerung
    VIS-konfigurierbare Zeiten
    Urlaubsmodus
    Zeitlogik
    Für jeden Tag werden Zufallszeiten neu berechnet für:
    Morgens
    Fenster
    Terrasse
    Küche

    Abends
    Küche
    Fenster
    Terrasse

    Dabei gilt:

    morgens wird ab Dämmerung (dawn) gestartet, aber nie vor deinem konfigurierten Startfenster
    abends wird ab Dämmerung (dusk) geschlossen, aber nie vor deinem konfigurierten Abendfenster
    die berechneten Zeiten liegen immer innerhalb deiner konfigurierten Zeitfenster
    die Reihenfolge bleibt erhalten:
    morgens: Fenster → Terrasse, Küche separat
    abends: Küche → Fenster → Terrasse
    Unterschied Werktag / Frei

    Das Skript unterscheidet automatisch:
    Werktag
    Frei = Samstag, Sonntag oder Feiertag

    Der Feiertag kommt über: feiertage.0.heute.boolean
    Urlaubsmodus
    Es gibt einen State:
    0_userdata.0.Rolladen.Urlaubsmodus
    Wenn der aktiv ist:
    werden die Zeitabstände größer zufällig gestreut
    Telegram meldet zusätzlich 🏖️ Urlaubsmodus aktiv
    Sensorlogik
    Küche
    Wenn das Küchenfenster offen ist und der Rolladen ganz zu ist:
    fährt der Küchenrolladen automatisch auf Spaltposition
    Standard: KUECHE_SPALT = 20
    Terrasse
    Wenn Terrassentür offen oder gekippt ist und der Rolladen ganz zu ist:
    fährt der Terrassenrolladen automatisch auf Spaltposition
    Standard: TERRASSE_SPALT = 16
    Wenn der Terrassenrolladen abends schließen soll, die Tür aber noch offen ist:
    bleibt er erst offen
    sobald die Tür geschlossen wird, fährt er nachträglich runter
    Prozentanzeige invertiert
    Das Skript zeigt Prozentwerte in Meldungen andersherum an:
    anzeigeProzent(val) = 100 - val
    Das heißt z. B.:
    interner Wert 100 → Anzeige 0%
    interner Wert 0 → Anzeige 100%
    interner Wert 16 → Anzeige 84%
    Das betrifft:
    manuelle Telegram-Meldungen
    automatische Telegram-Meldungen
    Testfahrten
    Telegram-Funktionen
    Hauptmenü

    Per Telegram gibt es ein Menü mit Buttons für:

    • Morgen
    • Abend
    • Infozeiten
    • aktuelle Zeiten anzeigen
    • heutige Zufallszeiten anzeigen
    • Urlaubsmodus an/aus
    • Testfahrten
    • Hilfe
      Telegram-Zeitänderung
      Du kannst Zeiten per Menü ändern:

    Für Morgen / Abend:

    • Bereich wählen:
      Fenster/Terrasse
      Küche
    • Tagtyp wählen:
      Werktag
      Frei
    • Grenze wählen:
      Start
      Ende
    • dann nur noch Zeit schicken:
      z. B. 06:45
      Für Infozeiten:
    • Erste Meldung
    • Zweite Meldung
    • dann Uhrzeit senden
      Nach dem Speichern bekommst du:
    • das geänderte Feld
    • den neuen Wert
    • die komplette aktuelle Übersicht
      Telegram-Testfahrten
      Es gibt ein Testfahrt-Menü für:
    • Fenster
    • Terrasse
    • Küche

    Pro Bereich:

    • Auf
    • Zu
    • Spalt
      Zusätzlich:
    • Zufallszeiten neu berechnen
    • Heutige Zeiten senden
      Telegram-Direktbefehle
      Zusätzlich zum Menü gehen weiterhin Textbefehle wie:
      /zeit morgens ft werktag start 06:45
      /zeit abends kueche frei ende 21:00
      /zeit info 1 12:15
      /urlaub an
      /urlaub aus
      /zeiten
      /heute
      /menu
      VIS / States
      Das Skript legt automatisch Config-States an unter: 0_userdata.0.Rolladen.Config
      Dort gibt es getrennt:
    • Morgens
    • Abends
    • FensterTerrasse
    • Kueche
    • Werktag
    • Frei
    • Start
    • Ende
    • Info
    • ErsteMeldung
    • ZweiteMeldung
      Außerdem Anzeige-States unter: 0_userdata.0.Rolladen.Heute.
      für:
    • heute Morgen Fenster
    • heute Morgen Terrasse
    • heute Morgen Küche
    • heute Abend Fenster
    • heute Abend Terrasse
    • heute Abend Küche
    • Heute.Info
      Tägliche Meldungen
      Es gibt zwei konfigurierbare Infozeiten:
    • ErsteMeldung
    • ZweiteMeldung
      Zu diesen Zeiten sendet das Skript per Telegram die heute berechneten Rolladenzeiten.
      Manuelle Bedienung
      Wenn ein Rolladen manuell bewegt wird und es war keine automatische Fahrt:
    • sendet das Skript eine Telegram-Meldung
      Beispiel:
    • Terrassenrolladen manuell bewegt
    • Fensterrolladen manuell bewegt
    • Küchenrolladen manuell bewegt
      Schutz gegen Doppelmeldungen / Doppelaktionen
      Das Skript arbeitet mit Tagesflags wie:
    • morgenHeuteFenster
    • abendHeuteTerrasse
      damit eine automatische Aktion pro Tag nur einmal ausgeführt wird.
      Robuste Punkte
      Das Skript ist robuster gemacht durch:
    • saubere Groß-/Kleinschreibung der Config-States
    • tolerantes Einlesen von Telegram-Nachrichten
    • Entfernen von Präfixen
    • sicheres Parsen von Uhrzeiten
    • Begrenzung von Zufallszeiten auf erlaubte Fenster
    • Fallback auf Default-Zeiten, wenn Config fehlt
      Konfigurierbare Kernwerte oben im Skript
      Direkt oben anpassbar sind u. a.:
    • Geräte-IDs
    • Sensor-IDs
    • Telegram-/Push-Instanz
    • Spaltwerte:
    • FENSTER_SPALT
    • TERRASSE_SPALT
    • KUECHE_SPALT
    • Zufallsstreuung normal
    • Zufallsstreuung Urlaub
    • Default-Zeitfenster
    'use strict';
    
    /* =========================
       Geräte / IDs
       ========================= */
    
    const TERRASSE              = 'homepilot20.0.Actuator.3-10182345.Position_inverted';
    const FENSTER               = 'homepilot20.0.Actuator.6-10182345.Position_inverted';
    const KUECHE                = 'homepilot20.0.Actuator.7-10122345.Position_inverted';
    
    const TERRASSEN_TUER_AUF    = 'deconz.0.Sensors.18.open';
    const TERRASSEN_TUER_KIPP   = 'deconz.0.Sensors.17.open';
    const KUECHE_FENSTER        = 'deconz.0.Sensors.16.open';
    const FEIERTAG              = 'feiertage.0.heute.boolean';
    
    const TELEGRAM_INSTANCE     = 'telegram.0';
    const PUSH_INSTANCE         = 'pushover.0'; // leer lassen '' wenn kein Push genutzt wird
    
    /* =========================
       Basis-Pfade
       ========================= */
    
    const BASE_STATE            = '0_userdata.0.Rolladen';
    const CONFIG_STATE          = BASE_STATE + '.Config';
    const URLAUBSMODUS          = BASE_STATE + '.Urlaubsmodus';
    
    const AUF                   = 100;
    const ZU                    = 0;
    
    const FENSTER_SPALT         = 16;
    const TERRASSE_SPALT        = 16;
    const KUECHE_SPALT          = 20;
    
    // normale tägliche Streuung in Minuten
    const NORMAL_MORGEN_MIN     = 1;
    const NORMAL_MORGEN_MAX     = 5;
    const NORMAL_ABEND_MIN      = 1;
    const NORMAL_ABEND_MAX      = 5;
    
    // größere Streuung im Urlaubsmodus
    const URLAUB_MORGEN_MIN     = 2;
    const URLAUB_MORGEN_MAX     = 10;
    const URLAUB_ABEND_MIN      = 2;
    const URLAUB_ABEND_MAX      = 10;
    
    /* =========================
       Default-Zeiten für Config-States
       ========================= */
    
    const DEFAULT_TIMES = {
        morgens: {
            fensterTerrasse: {
                werktag: { start: '06:40', ende: '07:00' },
                frei:    { start: '08:00', ende: '08:30' }
            },
            kueche: {
                werktag: { start: '06:30', ende: '07:00' },
                frei:    { start: '08:00', ende: '08:30' }
            }
        },
        abends: {
            fensterTerrasse: {
                werktag: { start: '20:25', ende: '21:03' },
                frei:    { start: '20:30', ende: '21:30' }
            },
            kueche: {
                werktag: { start: '19:30', ende: '20:00' },
                frei:    { start: '20:30', ende: '21:30' }
            }
        },
        info: {
            ersteMeldung:  '12:00',
            zweiteMeldung: '18:00'
        }
    };
    
    /* =========================
       Laufzeitvariablen
       ========================= */
    
    let terrassePending         = false;
    
    let morgenHeuteFenster      = false;
    let morgenHeuteTerrasse     = false;
    let morgenHeuteKueche       = false;
    
    let abendHeuteFenster       = false;
    let abendHeuteTerrasse      = false;
    let abendHeuteKueche        = false;
    
    let randomMorningFenster    = null;
    let randomMorningTerrasse   = null;
    let randomMorningKueche     = null;
    
    let randomEveningFenster    = null;
    let randomEveningTerrasse   = null;
    let randomEveningKueche     = null;
    
    let randomDateKey           = '';
    let lastInfoSent            = {};
    
    // Kennzeichen für automatische Fahrten
    let autoMoveTerrasse        = false;
    let autoMoveFenster         = false;
    let autoMoveKueche          = false;
    
    // Telegram Menü / Eingabe
    let telegramEditTarget      = null;
    let telegramMenuState       = null;
    
    /* =========================
       Hilfsfunktionen
       ========================= */
    
    function tg(msg) {
        try {
            sendTo(TELEGRAM_INSTANCE, 'send', { text: msg });
        } catch (e) {
            log('Telegram Fehler: ' + e, 'warn');
        }
    }
    
    function tgWithKeyboard(msg, keyboardRows) {
        try {
            sendTo(TELEGRAM_INSTANCE, 'send', {
                text: msg,
                reply_markup: {
                    keyboard: keyboardRows,
                    resize_keyboard: true,
                    one_time_keyboard: false
                }
            });
        } catch (e) {
            log('Telegram Keyboard Fehler: ' + e, 'warn');
            tg(msg);
        }
    }
    
    function tgMainMenu(msg) {
        tgWithKeyboard(msg || '📋 Rolladen-Menü', [
            ['🌅 Morgen', '🌙 Abend'],
            ['🕒 Infozeiten', '🕒 /zeiten'],
            ['🧪 Testfahrten', '🔄 /heute'],
            ['🏖️ Urlaub an', '🏠 Urlaub aus'],
            ['ℹ️ Hilfe']
        ]);
    }
    
    function tgAreaMenuMorgen(msg) {
        tgWithKeyboard(msg || '🌅 Morgen: Bereich wählen', [
            ['🪟 Fenster/Terrasse', '🍽️ Küche'],
            ['⬅️ Zurück', '❌ Abbrechen']
        ]);
    }
    
    function tgAreaMenuAbend(msg) {
        tgWithKeyboard(msg || '🌙 Abend: Bereich wählen', [
            ['🪟 Fenster/Terrasse', '🍽️ Küche'],
            ['⬅️ Zurück', '❌ Abbrechen']
        ]);
    }
    
    function tgDaytypeMenu(msg) {
        tgWithKeyboard(msg || 'Bitte Tagtyp wählen', [
            ['💼 Werktag', '🎉 Frei'],
            ['⬅️ Zurück', '❌ Abbrechen']
        ]);
    }
    
    function tgBoundaryMenu(msg) {
        tgWithKeyboard(msg || 'Bitte Start oder Ende wählen', [
            ['▶️ Start', '⏹️ Ende'],
            ['⬅️ Zurück', '❌ Abbrechen']
        ]);
    }
    
    function tgInfoMenu(msg) {
        tgWithKeyboard(msg || '🕒 Infozeiten wählen', [
            ['1️⃣ Erste Meldung', '2️⃣ Zweite Meldung'],
            ['⬅️ Zurück', '❌ Abbrechen']
        ]);
    }
    
    function tgTestMenu(msg) {
        tgWithKeyboard(msg || '🧪 Testfahrten wählen', [
            ['🪟 Fenster', '🏡 Terrasse'],
            ['🍽️ Küche', '📅 Neu berechnen'],
            ['📤 Zeiten senden'],
            ['⬅️ Zurück', '❌ Abbrechen']
        ]);
    }
    
    function tgTestFensterMenu(msg) {
        tgWithKeyboard(msg || '🪟 Fenster Testfahrt', [
            ['⬆️ Auf', '⬇️ Zu'],
            ['↔️ Spalt'],
            ['⬅️ Zurück', '❌ Abbrechen']
        ]);
    }
    
    function tgTestTerrasseMenu(msg) {
        tgWithKeyboard(msg || '🏡 Terrasse Testfahrt', [
            ['⬆️ Auf', '⬇️ Zu'],
            ['↔️ Spalt'],
            ['⬅️ Zurück', '❌ Abbrechen']
        ]);
    }
    
    function tgTestKuecheMenu(msg) {
        tgWithKeyboard(msg || '🍽️ Küche Testfahrt', [
            ['⬆️ Auf', '⬇️ Zu'],
            ['↔️ Spalt'],
            ['⬅️ Zurück', '❌ Abbrechen']
        ]);
    }
    
    function tgCancelMenu(msg) {
        telegramMenuState = null;
        telegramEditTarget = null;
        tgMainMenu(msg || '❌ Abgebrochen');
    }
    
    function push(msg, title) {
        if (!PUSH_INSTANCE) return;
        try {
            sendTo(PUSH_INSTANCE, 'send', {
                message: msg,
                title: title || 'Rolladen'
            });
        } catch (e) {
            log('Push Fehler: ' + e, 'warn');
        }
    }
    
    function notifyAction(title, msg) {
        log(msg, 'info');
        tg(msg);
        push(msg, title);
    }
    
    function formatZeit(d) {
        return d ? d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) : '--:--';
    }
    
    function anzeigeProzent(val) {
        return 100 - Number(val);
    }
    
    function posText(val) {
        return anzeigeProzent(val) + '%';
    }
    
    function feiertag() {
        try {
            return !!getState(FEIERTAG).val;
        } catch (e) {
            return false;
        }
    }
    
    function urlaubsmodus() {
        try {
            return !!getState(URLAUBSMODUS).val;
        } catch (e) {
            return false;
        }
    }
    
    function wochenendeOderFeiertag() {
        const d = new Date().getDay();
        return d === 0 || d === 6 || feiertag();
    }
    
    function gleicheMinute(a, b) {
        return a && b &&
            a.getHours() === b.getHours() &&
            a.getMinutes() === b.getMinutes() &&
            a.getDate() === b.getDate() &&
            a.getMonth() === b.getMonth() &&
            a.getFullYear() === b.getFullYear();
    }
    
    function getDateKey(d) {
        return d.getFullYear() + '-' +
            String(d.getMonth() + 1).padStart(2, '0') + '-' +
            String(d.getDate()).padStart(2, '0');
    }
    
    function terrassenTuerOffenOderGekippt() {
        return !!getState(TERRASSEN_TUER_AUF).val || !!getState(TERRASSEN_TUER_KIPP).val;
    }
    
    function randomInt(min, max) {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }
    
    function addMinutes(date, minutes) {
        return new Date(date.getTime() + minutes * 60000);
    }
    
    function clampDate(date, min, max) {
        if (date < min) return new Date(min);
        if (date > max) return new Date(max);
        return new Date(date);
    }
    
    function normalizeWindow(start, end) {
        if (start > end) {
            return {
                start: new Date(end),
                end: new Date(end)
            };
        }
        return {
            start: new Date(start),
            end: new Date(end)
        };
    }
    
    function safeRandomMinuteBetween(start, end) {
        const win = normalizeWindow(start, end);
        const s = Math.floor(win.start.getTime() / 60000);
        const e = Math.floor(win.end.getTime() / 60000);
    
        if (e <= s) return new Date(win.end);
    
        const rnd = Math.floor(Math.random() * (e - s + 1)) + s;
        return new Date(rnd * 60000);
    }
    
    function ensureState(id, def, common) {
        if (!existsState(id)) {
            createState(id, def, common);
        }
    }
    
    function ensureConfigTimeState(id, def, name) {
        ensureState(id, def, {
            name: name,
            type: 'string',
            role: 'text',
            read: true,
            write: true,
            def: def
        });
    }
    
    function parseTimeString(str, fallback) {
        const val = (str || fallback || '').toString().trim();
        const m = /^(\d{1,2}):(\d{2})$/.exec(val);
    
        if (!m) {
            const fb = /^(\d{1,2}):(\d{2})$/.exec((fallback || '').toString().trim());
            if (!fb) return { h: 0, m: 0 };
            return {
                h: Math.min(23, Math.max(0, Number(fb[1]))),
                m: Math.min(59, Math.max(0, Number(fb[2])))
            };
        }
    
        return {
            h: Math.min(23, Math.max(0, Number(m[1]))),
            m: Math.min(59, Math.max(0, Number(m[2])))
        };
    }
    
    function setZeit(date, zeitObj) {
        const d = new Date(date);
        d.setHours(zeitObj.h, zeitObj.m, 0, 0);
        return d;
    }
    
    function getConfigTime(path, fallback) {
        try {
            if (!existsState(path)) return parseTimeString(fallback, fallback);
            const s = getState(path);
            if (!s) return parseTimeString(fallback, fallback);
            return parseTimeString(s.val, fallback);
        } catch (e) {
            return parseTimeString(fallback, fallback);
        }
    }
    
    function getConfigTimeString(path, fallback) {
        try {
            if (!existsState(path)) {
                const p = parseTimeString(fallback, fallback);
                return String(p.h).padStart(2, '0') + ':' + String(p.m).padStart(2, '0');
            }
    
            const s = getState(path);
            if (!s) {
                const p = parseTimeString(fallback, fallback);
                return String(p.h).padStart(2, '0') + ':' + String(p.m).padStart(2, '0');
            }
    
            const v = s.val;
            if (typeof v === 'string' && /^\d{1,2}:\d{2}$/.test(v.trim())) {
                const p = parseTimeString(v.trim(), fallback);
                return String(p.h).padStart(2, '0') + ':' + String(p.m).padStart(2, '0');
            }
        } catch (e) {
            // ignore
        }
    
        const p = parseTimeString(fallback, fallback);
        return String(p.h).padStart(2, '0') + ':' + String(p.m).padStart(2, '0');
    }
    
    function getWindowTimes(group, area, isFreeDay) {
        const cfgGroup = group === 'morgens' ? 'Morgens' : 'Abends';
        const cfgArea = area === 'fensterTerrasse' ? 'FensterTerrasse' : 'Kueche';
        const cfgDay = isFreeDay ? 'Frei' : 'Werktag';
    
        const dayKey = isFreeDay ? 'frei' : 'werktag';
    
        const startPath = `${CONFIG_STATE}.${cfgGroup}.${cfgArea}.${cfgDay}.Start`;
        const endPath   = `${CONFIG_STATE}.${cfgGroup}.${cfgArea}.${cfgDay}.Ende`;
    
        const fallbackStart = DEFAULT_TIMES[group][area][dayKey].start;
        const fallbackEnd   = DEFAULT_TIMES[group][area][dayKey].ende;
    
        return {
            start: getConfigTime(startPath, fallbackStart),
            ende:  getConfigTime(endPath, fallbackEnd)
        };
    }
    
    function getInfoTime(which) {
        const map = {
            ersteMeldung: `${CONFIG_STATE}.Info.ErsteMeldung`,
            zweiteMeldung: `${CONFIG_STATE}.Info.ZweiteMeldung`
        };
        return getConfigTimeString(map[which], DEFAULT_TIMES.info[which]);
    }
    
    function isValidTimeText(txt) {
        return /^\d{1,2}:\d{2}$/.test((txt || '').trim());
    }
    
    function normalizeTimeText(txt) {
        const p = parseTimeString(txt, '00:00');
        return String(p.h).padStart(2, '0') + ':' + String(p.m).padStart(2, '0');
    }
    
    function setConfigTimeState(path, value) {
        const normalized = normalizeTimeText(value);
        setState(path, normalized);
        return normalized;
    }
    
    function getTodayOverviewText() {
        return (
            '📅 Heutige Zufallszeiten\n\n' +
            '🌅 Morgen:\n' +
            '- Fenster: ' + formatZeit(randomMorningFenster) + '\n' +
            '- Terrasse: ' + formatZeit(randomMorningTerrasse) + '\n' +
            '- Küche: ' + formatZeit(randomMorningKueche) + '\n\n' +
            '🌙 Abend:\n' +
            '- Küche: ' + formatZeit(randomEveningKueche) + '\n' +
            '- Fenster: ' + formatZeit(randomEveningFenster) + '\n' +
            '- Terrasse: ' + formatZeit(randomEveningTerrasse) +
            (urlaubsmodus() ? '\n\n🏖️ Urlaubsmodus aktiv' : '')
        );
    }
    
    function getConfigOverviewText() {
        return (
            '🕒 Aktuelle Rolladen-Zeiten\n\n' +
    
            '🌅 Morgens Werktag\n' +
            '- Fenster/Terrasse Start: ' + getConfigTimeString(`${CONFIG_STATE}.Morgens.FensterTerrasse.Werktag.Start`, DEFAULT_TIMES.morgens.fensterTerrasse.werktag.start) + '\n' +
            '- Fenster/Terrasse Ende: ' + getConfigTimeString(`${CONFIG_STATE}.Morgens.FensterTerrasse.Werktag.Ende`, DEFAULT_TIMES.morgens.fensterTerrasse.werktag.ende) + '\n' +
            '- Küche Start: ' + getConfigTimeString(`${CONFIG_STATE}.Morgens.Kueche.Werktag.Start`, DEFAULT_TIMES.morgens.kueche.werktag.start) + '\n' +
            '- Küche Ende: ' + getConfigTimeString(`${CONFIG_STATE}.Morgens.Kueche.Werktag.Ende`, DEFAULT_TIMES.morgens.kueche.werktag.ende) + '\n\n' +
    
            '🌅 Morgens Frei\n' +
            '- Fenster/Terrasse Start: ' + getConfigTimeString(`${CONFIG_STATE}.Morgens.FensterTerrasse.Frei.Start`, DEFAULT_TIMES.morgens.fensterTerrasse.frei.start) + '\n' +
            '- Fenster/Terrasse Ende: ' + getConfigTimeString(`${CONFIG_STATE}.Morgens.FensterTerrasse.Frei.Ende`, DEFAULT_TIMES.morgens.fensterTerrasse.frei.ende) + '\n' +
            '- Küche Start: ' + getConfigTimeString(`${CONFIG_STATE}.Morgens.Kueche.Frei.Start`, DEFAULT_TIMES.morgens.kueche.frei.start) + '\n' +
            '- Küche Ende: ' + getConfigTimeString(`${CONFIG_STATE}.Morgens.Kueche.Frei.Ende`, DEFAULT_TIMES.morgens.kueche.frei.ende) + '\n\n' +
    
            '🌙 Abends Werktag\n' +
            '- Fenster/Terrasse Start: ' + getConfigTimeString(`${CONFIG_STATE}.Abends.FensterTerrasse.Werktag.Start`, DEFAULT_TIMES.abends.fensterTerrasse.werktag.start) + '\n' +
            '- Fenster/Terrasse Ende: ' + getConfigTimeString(`${CONFIG_STATE}.Abends.FensterTerrasse.Werktag.Ende`, DEFAULT_TIMES.abends.fensterTerrasse.werktag.ende) + '\n' +
            '- Küche Start: ' + getConfigTimeString(`${CONFIG_STATE}.Abends.Kueche.Werktag.Start`, DEFAULT_TIMES.abends.kueche.werktag.start) + '\n' +
            '- Küche Ende: ' + getConfigTimeString(`${CONFIG_STATE}.Abends.Kueche.Werktag.Ende`, DEFAULT_TIMES.abends.kueche.werktag.ende) + '\n\n' +
    
            '🌙 Abends Frei\n' +
            '- Fenster/Terrasse Start: ' + getConfigTimeString(`${CONFIG_STATE}.Abends.FensterTerrasse.Frei.Start`, DEFAULT_TIMES.abends.fensterTerrasse.frei.start) + '\n' +
            '- Fenster/Terrasse Ende: ' + getConfigTimeString(`${CONFIG_STATE}.Abends.FensterTerrasse.Frei.Ende`, DEFAULT_TIMES.abends.fensterTerrasse.frei.ende) + '\n' +
            '- Küche Start: ' + getConfigTimeString(`${CONFIG_STATE}.Abends.Kueche.Frei.Start`, DEFAULT_TIMES.abends.kueche.frei.start) + '\n' +
            '- Küche Ende: ' + getConfigTimeString(`${CONFIG_STATE}.Abends.Kueche.Frei.Ende`, DEFAULT_TIMES.abends.kueche.frei.ende) + '\n\n' +
    
            'ℹ️ Info\n' +
            '- 1. Meldung: ' + getConfigTimeString(`${CONFIG_STATE}.Info.ErsteMeldung`, DEFAULT_TIMES.info.ersteMeldung) + '\n' +
            '- 2. Meldung: ' + getConfigTimeString(`${CONFIG_STATE}.Info.ZweiteMeldung`, DEFAULT_TIMES.info.zweiteMeldung)
        );
    }
    
    function resolveTelegramTimePath(tokens) {
        if (!tokens || !tokens.length) return null;
    
        const scope = (tokens[0] || '').toLowerCase();
    
        if (scope === 'info') {
            const which = (tokens[1] || '').toLowerCase();
            if (which === '1' || which === 'erste') return `${CONFIG_STATE}.Info.ErsteMeldung`;
            if (which === '2' || which === 'zweite') return `${CONFIG_STATE}.Info.ZweiteMeldung`;
            return null;
        }
    
        const tageszeit = scope;
        const bereich = (tokens[1] || '').toLowerCase();
        const tagtyp = (tokens[2] || '').toLowerCase();
        const grenze = (tokens[3] || '').toLowerCase();
    
        let part1 = null;
        let part2 = null;
        let part3 = null;
        let part4 = null;
    
        if (tageszeit === 'morgens') part1 = 'Morgens';
        if (tageszeit === 'abends')  part1 = 'Abends';
    
        if (bereich === 'ft' || bereich === 'fensterterrasse' || bereich === 'fenster/terrasse') part2 = 'FensterTerrasse';
        if (bereich === 'kueche' || bereich === 'küche') part2 = 'Kueche';
    
        if (tagtyp === 'werktag') part3 = 'Werktag';
        if (tagtyp === 'frei')    part3 = 'Frei';
    
        if (grenze === 'start') part4 = 'Start';
        if (grenze === 'ende')  part4 = 'Ende';
    
        if (!part1 || !part2 || !part3 || !part4) return null;
    
        return `${CONFIG_STATE}.${part1}.${part2}.${part3}.${part4}`;
    }
    
    function getMenuTargetPath(context) {
        if (!context) return null;
    
        if (context.mode === 'info') {
            if (context.which === 'erste') return `${CONFIG_STATE}.Info.ErsteMeldung`;
            if (context.which === 'zweite') return `${CONFIG_STATE}.Info.ZweiteMeldung`;
            return null;
        }
    
        const part1 = context.tageszeit === 'morgens' ? 'Morgens' : context.tageszeit === 'abends' ? 'Abends' : null;
        const part2 = context.bereich === 'ft' ? 'FensterTerrasse' : context.bereich === 'kueche' ? 'Kueche' : null;
        const part3 = context.tagtyp === 'werktag' ? 'Werktag' : context.tagtyp === 'frei' ? 'Frei' : null;
        const part4 = context.grenze === 'start' ? 'Start' : context.grenze === 'ende' ? 'Ende' : null;
    
        if (!part1 || !part2 || !part3 || !part4) return null;
        return `${CONFIG_STATE}.${part1}.${part2}.${part3}.${part4}`;
    }
    
    function getMenuTargetLabel(context) {
        if (!context) return '';
    
        if (context.mode === 'info') {
            return context.which === 'erste' ? 'Info / Erste Meldung' : 'Info / Zweite Meldung';
        }
    
        return (
            (context.tageszeit === 'morgens' ? 'Morgens' : 'Abends') + ' / ' +
            (context.bereich === 'ft' ? 'Fenster/Terrasse' : 'Küche') + ' / ' +
            (context.tagtyp === 'werktag' ? 'Werktag' : 'Frei') + ' / ' +
            (context.grenze === 'start' ? 'Start' : 'Ende')
        );
    }
    
    function extractTelegramText(val) {
        if (val === null || val === undefined) return '';
    
        let text = '';
    
        if (typeof val === 'string') {
            const trimmed = val.trim();
    
            if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
                try {
                    const obj = JSON.parse(trimmed);
                    if (obj && typeof obj.text === 'string') {
                        text = obj.text.trim();
                    } else if (obj && typeof obj.data === 'string') {
                        text = obj.data.trim();
                    } else {
                        text = trimmed;
                    }
                } catch (e) {
                    text = trimmed;
                }
            } else {
                text = trimmed;
            }
        } else if (typeof val === 'object') {
            if (typeof val.text === 'string') {
                text = val.text.trim();
            } else if (typeof val.data === 'string') {
                text = val.data.trim();
            } else {
                text = String(val).trim();
            }
        } else {
            text = String(val).trim();
        }
    
        text = text.replace(/^\[[^\]]+\]\s*/, '').trim();
    
        return text;
    }
    
    function telegramSavedMessage(label, saved) {
        return (
            '✅ Gespeichert:\n' +
            label + ' = ' + saved + '\n\n' +
            getConfigOverviewText()
        );
    }
    
    /* =========================
       States anlegen
       ========================= */
    
    function ensureStates() {
        ensureState(URLAUBSMODUS, false, {
            name: 'Urlaubsmodus Rolladen',
            type: 'boolean',
            role: 'switch',
            read: true,
            write: true,
            def: false
        });
    
        ensureState(BASE_STATE + '.Heute.Morgen.Fenster', '--:--', {
            name: 'Heute Morgen Fenster',
            type: 'string',
            role: 'text',
            read: true,
            write: true,
            def: '--:--'
        });
    
        ensureState(BASE_STATE + '.Heute.Morgen.Terrasse', '--:--', {
            name: 'Heute Morgen Terrasse',
            type: 'string',
            role: 'text',
            read: true,
            write: true,
            def: '--:--'
        });
    
        ensureState(BASE_STATE + '.Heute.Morgen.Kueche', '--:--', {
            name: 'Heute Morgen Küche',
            type: 'string',
            role: 'text',
            read: true,
            write: true,
            def: '--:--'
        });
    
        ensureState(BASE_STATE + '.Heute.Abend.Fenster', '--:--', {
            name: 'Heute Abend Fenster',
            type: 'string',
            role: 'text',
            read: true,
            write: true,
            def: '--:--'
        });
    
        ensureState(BASE_STATE + '.Heute.Abend.Terrasse', '--:--', {
            name: 'Heute Abend Terrasse',
            type: 'string',
            role: 'text',
            read: true,
            write: true,
            def: '--:--'
        });
    
        ensureState(BASE_STATE + '.Heute.Abend.Kueche', '--:--', {
            name: 'Heute Abend Küche',
            type: 'string',
            role: 'text',
            read: true,
            write: true,
            def: '--:--'
        });
    
        ensureState(BASE_STATE + '.Heute.Info', '', {
            name: 'Heute Info',
            type: 'string',
            role: 'text',
            read: true,
            write: true,
            def: ''
        });
    
        ensureConfigTimeState(`${CONFIG_STATE}.Morgens.FensterTerrasse.Werktag.Start`, DEFAULT_TIMES.morgens.fensterTerrasse.werktag.start, 'Morgens Fenster/Terrasse Werktag Start');
        ensureConfigTimeState(`${CONFIG_STATE}.Morgens.FensterTerrasse.Werktag.Ende`, DEFAULT_TIMES.morgens.fensterTerrasse.werktag.ende, 'Morgens Fenster/Terrasse Werktag Ende');
        ensureConfigTimeState(`${CONFIG_STATE}.Morgens.FensterTerrasse.Frei.Start`, DEFAULT_TIMES.morgens.fensterTerrasse.frei.start, 'Morgens Fenster/Terrasse Frei Start');
        ensureConfigTimeState(`${CONFIG_STATE}.Morgens.FensterTerrasse.Frei.Ende`, DEFAULT_TIMES.morgens.fensterTerrasse.frei.ende, 'Morgens Fenster/Terrasse Frei Ende');
    
        ensureConfigTimeState(`${CONFIG_STATE}.Morgens.Kueche.Werktag.Start`, DEFAULT_TIMES.morgens.kueche.werktag.start, 'Morgens Küche Werktag Start');
        ensureConfigTimeState(`${CONFIG_STATE}.Morgens.Kueche.Werktag.Ende`, DEFAULT_TIMES.morgens.kueche.werktag.ende, 'Morgens Küche Werktag Ende');
        ensureConfigTimeState(`${CONFIG_STATE}.Morgens.Kueche.Frei.Start`, DEFAULT_TIMES.morgens.kueche.frei.start, 'Morgens Küche Frei Start');
        ensureConfigTimeState(`${CONFIG_STATE}.Morgens.Kueche.Frei.Ende`, DEFAULT_TIMES.morgens.kueche.frei.ende, 'Morgens Küche Frei Ende');
    
        ensureConfigTimeState(`${CONFIG_STATE}.Abends.FensterTerrasse.Werktag.Start`, DEFAULT_TIMES.abends.fensterTerrasse.werktag.start, 'Abends Fenster/Terrasse Werktag Start');
        ensureConfigTimeState(`${CONFIG_STATE}.Abends.FensterTerrasse.Werktag.Ende`, DEFAULT_TIMES.abends.fensterTerrasse.werktag.ende, 'Abends Fenster/Terrasse Werktag Ende');
        ensureConfigTimeState(`${CONFIG_STATE}.Abends.FensterTerrasse.Frei.Start`, DEFAULT_TIMES.abends.fensterTerrasse.frei.start, 'Abends Fenster/Terrasse Frei Start');
        ensureConfigTimeState(`${CONFIG_STATE}.Abends.FensterTerrasse.Frei.Ende`, DEFAULT_TIMES.abends.fensterTerrasse.frei.ende, 'Abends Fenster/Terrasse Frei Ende');
    
        ensureConfigTimeState(`${CONFIG_STATE}.Abends.Kueche.Werktag.Start`, DEFAULT_TIMES.abends.kueche.werktag.start, 'Abends Küche Werktag Start');
        ensureConfigTimeState(`${CONFIG_STATE}.Abends.Kueche.Werktag.Ende`, DEFAULT_TIMES.abends.kueche.werktag.ende, 'Abends Küche Werktag Ende');
        ensureConfigTimeState(`${CONFIG_STATE}.Abends.Kueche.Frei.Start`, DEFAULT_TIMES.abends.kueche.frei.start, 'Abends Küche Frei Start');
        ensureConfigTimeState(`${CONFIG_STATE}.Abends.Kueche.Frei.Ende`, DEFAULT_TIMES.abends.kueche.frei.ende, 'Abends Küche Frei Ende');
    
        ensureConfigTimeState(`${CONFIG_STATE}.Info.ErsteMeldung`, DEFAULT_TIMES.info.ersteMeldung, 'Info erste Meldung');
        ensureConfigTimeState(`${CONFIG_STATE}.Info.ZweiteMeldung`, DEFAULT_TIMES.info.zweiteMeldung, 'Info zweite Meldung');
    }
    
    function markAutoMove(name, id, value) {
        if (name === 'Terrasse') autoMoveTerrasse = true;
        if (name === 'Fenster')  autoMoveFenster  = true;
        if (name === 'Kueche')   autoMoveKueche   = true;
    
        setState(id, value);
    
        setTimeout(() => {
            if (name === 'Terrasse') autoMoveTerrasse = false;
            if (name === 'Fenster')  autoMoveFenster  = false;
            if (name === 'Kueche')   autoMoveKueche   = false;
        }, 5000);
    }
    
    /* =========================
       Zeitfenster aus Config
       ========================= */
    
    function getFensterTerrasseMorningWindow(now) {
        const cfg = getWindowTimes('morgens', 'fensterTerrasse', wochenendeOderFeiertag());
        return { frueh: setZeit(now, cfg.start), spaet: setZeit(now, cfg.ende) };
    }
    
    function getFensterTerrasseEveningWindow(now) {
        const cfg = getWindowTimes('abends', 'fensterTerrasse', wochenendeOderFeiertag());
        return { frueh: setZeit(now, cfg.start), spaet: setZeit(now, cfg.ende) };
    }
    
    function getKuecheMorningWindow(now) {
        const cfg = getWindowTimes('morgens', 'kueche', wochenendeOderFeiertag());
        return { frueh: setZeit(now, cfg.start), spaet: setZeit(now, cfg.ende) };
    }
    
    function getKuecheEveningWindow(now) {
        const cfg = getWindowTimes('abends', 'kueche', wochenendeOderFeiertag());
        return { frueh: setZeit(now, cfg.start), spaet: setZeit(now, cfg.ende) };
    }
    
    function sendeTageszeiten() {
        tg(getTodayOverviewText());
    }
    
    function sendeMittagsInfo() {
        const now = new Date();
        const key = getDateKey(now) + '_' + now.getHours();
    
        if (lastInfoSent[key]) return;
    
        berechneRandomZeiten();
    
        tg(
            '📅 Rolladen-Zeiten heute\n\n' +
            '🌅 Morgens öffnen:\n' +
            '- Fenster: ' + formatZeit(randomMorningFenster) + '\n' +
            '- Terrasse: ' + formatZeit(randomMorningTerrasse) + '\n' +
            '- Küche: ' + formatZeit(randomMorningKueche) + '\n\n' +
            '🌙 Abends schließen:\n' +
            '- Küche: ' + formatZeit(randomEveningKueche) + '\n' +
            '- Fenster: ' + formatZeit(randomEveningFenster) + '\n' +
            '- Terrasse: ' + formatZeit(randomEveningTerrasse) +
            (urlaubsmodus() ? '\n\n🏖️ Urlaubsmodus aktiv' : '')
        );
    
        lastInfoSent[key] = true;
    }
    
    function updateVisStates() {
        setState(BASE_STATE + '.Heute.Morgen.Fenster', formatZeit(randomMorningFenster), true);
        setState(BASE_STATE + '.Heute.Morgen.Terrasse', formatZeit(randomMorningTerrasse), true);
        setState(BASE_STATE + '.Heute.Morgen.Kueche', formatZeit(randomMorningKueche), true);
    
        setState(BASE_STATE + '.Heute.Abend.Fenster', formatZeit(randomEveningFenster), true);
        setState(BASE_STATE + '.Heute.Abend.Terrasse', formatZeit(randomEveningTerrasse), true);
        setState(BASE_STATE + '.Heute.Abend.Kueche', formatZeit(randomEveningKueche), true);
    
        const info =
            'Morgen: Fenster ' + formatZeit(randomMorningFenster) +
            ', Terrasse ' + formatZeit(randomMorningTerrasse) +
            ', Küche ' + formatZeit(randomMorningKueche) +
            ' | Abend: Küche ' + formatZeit(randomEveningKueche) +
            ', Fenster ' + formatZeit(randomEveningFenster) +
            ', Terrasse ' + formatZeit(randomEveningTerrasse) +
            (urlaubsmodus() ? ' | Urlaubsmodus aktiv' : '');
    
        setState(BASE_STATE + '.Heute.Info', info, true);
    }
    
    /* =========================
       Zufallszeiten berechnen
       ========================= */
    
    function berechneRandomZeiten() {
        const jetzt = new Date();
        const key = getDateKey(jetzt);
    
        if (randomDateKey === key) return;
    
        const dawn = getAstroDate('dawn', jetzt);
        const dusk = getAstroDate('dusk', jetzt);
    
        const ftMorningWindow = getFensterTerrasseMorningWindow(jetzt);
        const ftEveningWindow = getFensterTerrasseEveningWindow(jetzt);
        const kMorningWindow  = getKuecheMorningWindow(jetzt);
        const kEveningWindow  = getKuecheEveningWindow(jetzt);
    
        const ftMorningStart = clampDate(
            dawn > ftMorningWindow.frueh ? dawn : ftMorningWindow.frueh,
            ftMorningWindow.frueh,
            ftMorningWindow.spaet
        );
        const ftMorningEnd = new Date(ftMorningWindow.spaet);
    
        const ftEveningStart = clampDate(
            dusk > ftEveningWindow.frueh ? dusk : ftEveningWindow.frueh,
            ftEveningWindow.frueh,
            ftEveningWindow.spaet
        );
        const ftEveningEnd = new Date(ftEveningWindow.spaet);
    
        const kMorningStart = clampDate(
            dawn > kMorningWindow.frueh ? dawn : kMorningWindow.frueh,
            kMorningWindow.frueh,
            kMorningWindow.spaet
        );
        const kMorningEnd = new Date(kMorningWindow.spaet);
    
        const kEveningStart = clampDate(
            dusk > kEveningWindow.frueh ? dusk : kEveningWindow.frueh,
            kEveningWindow.frueh,
            kEveningWindow.spaet
        );
        const kEveningEnd = new Date(kEveningWindow.spaet);
    
        const morgenMin = urlaubsmodus() ? URLAUB_MORGEN_MIN : NORMAL_MORGEN_MIN;
        const morgenMax = urlaubsmodus() ? URLAUB_MORGEN_MAX : NORMAL_MORGEN_MAX;
        const abendMin  = urlaubsmodus() ? URLAUB_ABEND_MIN  : NORMAL_ABEND_MIN;
        const abendMax  = urlaubsmodus() ? URLAUB_ABEND_MAX  : NORMAL_ABEND_MAX;
    
        randomMorningFenster = safeRandomMinuteBetween(ftMorningStart, ftMorningEnd);
    
        randomMorningTerrasse = addMinutes(randomMorningFenster, randomInt(morgenMin, morgenMax));
        randomMorningTerrasse = clampDate(randomMorningTerrasse, ftMorningStart, ftMorningEnd);
    
        if (gleicheMinute(randomMorningFenster, randomMorningTerrasse) && randomMorningTerrasse < ftMorningEnd) {
            randomMorningTerrasse = addMinutes(randomMorningTerrasse, 1);
            randomMorningTerrasse = clampDate(randomMorningTerrasse, ftMorningStart, ftMorningEnd);
        }
    
        randomMorningKueche = safeRandomMinuteBetween(kMorningStart, kMorningEnd);
    
        if (gleicheMinute(randomMorningKueche, randomMorningFenster) && randomMorningKueche < kMorningEnd) {
            randomMorningKueche = addMinutes(randomMorningKueche, 1);
            randomMorningKueche = clampDate(randomMorningKueche, kMorningStart, kMorningEnd);
        }
        if (gleicheMinute(randomMorningKueche, randomMorningTerrasse) && randomMorningKueche < kMorningEnd) {
            randomMorningKueche = addMinutes(randomMorningKueche, 1);
            randomMorningKueche = clampDate(randomMorningKueche, kMorningStart, kMorningEnd);
        }
    
        randomEveningKueche = safeRandomMinuteBetween(kEveningStart, kEveningEnd);
    
        let fensterStart = addMinutes(randomEveningKueche, randomInt(abendMin, abendMax));
        fensterStart = clampDate(fensterStart, ftEveningStart, ftEveningEnd);
        randomEveningFenster = safeRandomMinuteBetween(fensterStart, ftEveningEnd);
    
        let terrasseStart = addMinutes(randomEveningFenster, randomInt(abendMin, abendMax));
        terrasseStart = clampDate(terrasseStart, ftEveningStart, ftEveningEnd);
        randomEveningTerrasse = safeRandomMinuteBetween(terrasseStart, ftEveningEnd);
    
        if (gleicheMinute(randomEveningKueche, randomEveningFenster) && randomEveningFenster < ftEveningEnd) {
            randomEveningFenster = addMinutes(randomEveningFenster, 1);
            randomEveningFenster = clampDate(randomEveningFenster, ftEveningStart, ftEveningEnd);
        }
        if (gleicheMinute(randomEveningFenster, randomEveningTerrasse) && randomEveningTerrasse < ftEveningEnd) {
            randomEveningTerrasse = addMinutes(randomEveningTerrasse, 1);
            randomEveningTerrasse = clampDate(randomEveningTerrasse, ftEveningStart, ftEveningEnd);
        }
        if (gleicheMinute(randomEveningKueche, randomEveningTerrasse) && randomEveningTerrasse < ftEveningEnd) {
            randomEveningTerrasse = addMinutes(randomEveningTerrasse, 1);
            randomEveningTerrasse = clampDate(randomEveningTerrasse, ftEveningStart, ftEveningEnd);
        }
    
        randomDateKey = key;
        updateVisStates();
    
        log(
            'Zufallszeiten heute: ' +
            'Morgen Fenster ' + formatZeit(randomMorningFenster) +
            ', Terrasse ' + formatZeit(randomMorningTerrasse) +
            ', Küche ' + formatZeit(randomMorningKueche) +
            ' | Abend Küche ' + formatZeit(randomEveningKueche) +
            ', Fenster ' + formatZeit(randomEveningFenster) +
            ', Terrasse ' + formatZeit(randomEveningTerrasse) +
            (urlaubsmodus() ? ' [Urlaubsmodus]' : ''),
            'info'
        );
    }
    
    /* =========================
       Sensorlogik
       ========================= */
    
    function pruefeKuecheFenster() {
        if (!!getState(KUECHE_FENSTER).val && Number(getState(KUECHE).val) === ZU) {
            markAutoMove('Kueche', KUECHE, KUECHE_SPALT);
            notifyAction('Küche', 'Küche: Fenster offen → Rolladen auf ' + posText(KUECHE_SPALT));
        }
    }
    
    function pruefeTerrasseSpalt() {
        if (terrassenTuerOffenOderGekippt() && Number(getState(TERRASSE).val) === ZU) {
            markAutoMove('Terrasse', TERRASSE, TERRASSE_SPALT);
            notifyAction('Terrasse', 'Terrasse: Tür offen/gekippt → Rolladen auf ' + posText(TERRASSE_SPALT));
        }
    }
    
    function resetTagesflags() {
        terrassePending = false;
    
        morgenHeuteFenster = false;
        morgenHeuteTerrasse = false;
        morgenHeuteKueche = false;
    
        abendHeuteFenster = false;
        abendHeuteTerrasse = false;
        abendHeuteKueche = false;
    
        randomMorningFenster = null;
        randomMorningTerrasse = null;
        randomMorningKueche = null;
        randomEveningFenster = null;
        randomEveningTerrasse = null;
        randomEveningKueche = null;
        randomDateKey = '';
        lastInfoSent = {};
    }
    
    /* =========================
       Zeitpläne
       ========================= */
    
    schedule('0 1 0 * * *', () => {
        resetTagesflags();
    });
    
    schedule('* * * * *', () => {
        const now = new Date();
        const hhmm = String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0');
    
        if (hhmm === getInfoTime('ersteMeldung')) sendeMittagsInfo();
        if (hhmm === getInfoTime('zweiteMeldung')) sendeMittagsInfo();
    });
    
    schedule('0 * 0-23 * * *', () => {
        berechneRandomZeiten();
        const jetzt = new Date();
    
        if (randomMorningFenster && gleicheMinute(jetzt, randomMorningFenster) && !morgenHeuteFenster) {
            markAutoMove('Fenster', FENSTER, AUF);
            morgenHeuteFenster = true;
            notifyAction('Fenster', (urlaubsmodus() ? '🏖️ ' : '🌅 ') + 'Fenster morgens auf ' + posText(AUF) + ' um ' + formatZeit(randomMorningFenster));
        }
    
        if (randomMorningTerrasse && gleicheMinute(jetzt, randomMorningTerrasse) && !morgenHeuteTerrasse) {
            markAutoMove('Terrasse', TERRASSE, AUF);
            morgenHeuteTerrasse = true;
            notifyAction('Terrasse', (urlaubsmodus() ? '🏖️ ' : '🌅 ') + 'Terrasse morgens auf ' + posText(AUF) + ' um ' + formatZeit(randomMorningTerrasse));
        }
    
        if (randomMorningKueche && gleicheMinute(jetzt, randomMorningKueche) && !morgenHeuteKueche) {
            markAutoMove('Kueche', KUECHE, AUF);
            morgenHeuteKueche = true;
            notifyAction('Küche', (urlaubsmodus() ? '🏖️ ' : '🌅 ') + 'Küche morgens auf ' + posText(AUF) + ' um ' + formatZeit(randomMorningKueche));
        }
    
        if (randomEveningKueche && gleicheMinute(jetzt, randomEveningKueche) && !abendHeuteKueche) {
            if (!!getState(KUECHE_FENSTER).val) {
                markAutoMove('Kueche', KUECHE, KUECHE_SPALT);
                notifyAction('Küche', (urlaubsmodus() ? '🏖️ ' : '🌙 ') + 'Küche abends auf ' + posText(KUECHE_SPALT) + ' um ' + formatZeit(randomEveningKueche) + ' (Fenster offen)');
            } else {
                markAutoMove('Kueche', KUECHE, ZU);
                notifyAction('Küche', (urlaubsmodus() ? '🏖️ ' : '🌙 ') + 'Küche abends auf ' + posText(ZU) + ' um ' + formatZeit(randomEveningKueche));
            }
            abendHeuteKueche = true;
            pruefeKuecheFenster();
        }
    
        if (randomEveningFenster && gleicheMinute(jetzt, randomEveningFenster) && !abendHeuteFenster) {
            markAutoMove('Fenster', FENSTER, ZU);
            abendHeuteFenster = true;
            notifyAction('Fenster', (urlaubsmodus() ? '🏖️ ' : '🌙 ') + 'Fenster abends auf ' + posText(ZU) + ' um ' + formatZeit(randomEveningFenster));
        }
    
        if (randomEveningTerrasse && gleicheMinute(jetzt, randomEveningTerrasse) && !abendHeuteTerrasse) {
            if (!terrassenTuerOffenOderGekippt()) {
                markAutoMove('Terrasse', TERRASSE, ZU);
                terrassePending = false;
                pruefeTerrasseSpalt();
                notifyAction('Terrasse', (urlaubsmodus() ? '🏖️ ' : '🌙 ') + 'Terrasse abends auf ' + posText(ZU) + ' um ' + formatZeit(randomEveningTerrasse));
            } else {
                terrassePending = true;
                tg((urlaubsmodus() ? '🏖️ ' : '🌙 ') + 'Terrasse abends offen/gekippt um ' + formatZeit(randomEveningTerrasse) + ', bleibt vorerst auf ' + posText(AUF));
            }
            abendHeuteTerrasse = true;
        }
    });
    
    /* =========================
       Trigger
       ========================= */
    
    on({ id: TERRASSEN_TUER_AUF, change: 'ne' }, () => {
        if (terrassenTuerOffenOderGekippt()) {
            pruefeTerrasseSpalt();
        } else if (terrassePending) {
            markAutoMove('Terrasse', TERRASSE, ZU);
            terrassePending = false;
            notifyAction('Terrasse', 'Terrasse nachträglich auf ' + posText(ZU) + ' gefahren');
        }
    });
    
    on({ id: TERRASSEN_TUER_KIPP, change: 'ne' }, () => {
        if (terrassenTuerOffenOderGekippt()) {
            pruefeTerrasseSpalt();
        } else if (terrassePending) {
            markAutoMove('Terrasse', TERRASSE, ZU);
            terrassePending = false;
            notifyAction('Terrasse', 'Terrasse nachträglich auf ' + posText(ZU) + ' gefahren');
        }
    });
    
    on({ id: KUECHE_FENSTER, change: 'ne' }, () => {
        pruefeKuecheFenster();
    });
    
    on({ id: URLAUBSMODUS, change: 'ne' }, () => {
        randomDateKey = '';
        berechneRandomZeiten();
        tg('Urlaubsmodus ist jetzt ' + (urlaubsmodus() ? 'aktiv' : 'deaktiviert'));
    });
    
    on({ id: new RegExp('^' + CONFIG_STATE.replace(/\./g, '\\.') + '\\..*'), change: 'ne' }, () => {
        randomDateKey = '';
        lastInfoSent = {};
        berechneRandomZeiten();
        tg('🛠️ Rolladen-Zeiten wurden aktualisiert.');
    });
    
    /* =========================
       Telegram-Steuerung
       ========================= */
    
    on({ id: TELEGRAM_INSTANCE + '.communicate.request', change: 'any' }, obj => {
        try {
            if (!obj || !obj.state) return;
    
            const rawText = extractTelegramText(obj.state.val);
            if (!rawText) return;
    
            const lower = rawText.toLowerCase();
    
            log('Telegram empfangen: ' + rawText, 'info');
    
            if (lower === '❌ abbrechen') {
                tgCancelMenu();
                return;
            }
    
            if (lower === '⬅️ zurück') {
                if (!telegramMenuState || !telegramMenuState.step) {
                    tgMainMenu('📋 Hauptmenü');
                    return;
                }
    
                if (telegramMenuState.step === 'area') {
                    telegramMenuState = null;
                    tgMainMenu('📋 Hauptmenü');
                    return;
                }
    
                if (telegramMenuState.step === 'info_select') {
                    telegramMenuState = null;
                    tgMainMenu('📋 Hauptmenü');
                    return;
                }
    
                if (telegramMenuState.step === 'daytype') {
                    const tz = telegramMenuState.tageszeit;
                    telegramMenuState = { step: 'area', tageszeit: tz };
                    if (tz === 'morgens') tgAreaMenuMorgen('🌅 Morgen: Bereich wählen');
                    else tgAreaMenuAbend('🌙 Abend: Bereich wählen');
                    return;
                }
    
                if (telegramMenuState.step === 'boundary') {
                    telegramMenuState = {
                        step: 'daytype',
                        tageszeit: telegramMenuState.tageszeit,
                        bereich: telegramMenuState.bereich
                    };
                    tgDaytypeMenu('Bitte Tagtyp wählen');
                    return;
                }
    
                if (telegramMenuState.step === 'input') {
                    if (telegramMenuState.mode === 'info') {
                        telegramEditTarget = null;
                        telegramMenuState = { step: 'info_select' };
                        tgInfoMenu('🕒 Infozeiten wählen');
                        return;
                    }
    
                    telegramEditTarget = null;
                    telegramMenuState = {
                        step: 'boundary',
                        tageszeit: telegramMenuState.tageszeit,
                        bereich: telegramMenuState.bereich,
                        tagtyp: telegramMenuState.tagtyp
                    };
                    tgBoundaryMenu('Bitte Start oder Ende wählen');
                    return;
                }
    
                if (telegramMenuState.step === 'test_main') {
                    telegramMenuState = null;
                    tgMainMenu('📋 Hauptmenü');
                    return;
                }
    
                if (telegramMenuState.step === 'test_fenster' || telegramMenuState.step === 'test_terrasse' || telegramMenuState.step === 'test_kueche') {
                    telegramMenuState = { step: 'test_main' };
                    tgTestMenu('🧪 Testfahrten wählen');
                    return;
                }
            }
    
            if (telegramMenuState && telegramMenuState.step === 'input' && telegramEditTarget) {
                if (!isValidTimeText(rawText)) {
                    tgWithKeyboard(
                        '❌ Ungültiges Format.\nBitte Zeit als HH:MM senden, z. B. 06:45',
                        [['⬅️ Zurück', '❌ Abbrechen']]
                    );
                    return;
                }
    
                const saved = setConfigTimeState(telegramEditTarget, rawText);
    
                randomDateKey = '';
                lastInfoSent = {};
                berechneRandomZeiten();
    
                const label = getMenuTargetLabel(telegramMenuState);
    
                telegramEditTarget = null;
                telegramMenuState = null;
    
                tgMainMenu(telegramSavedMessage(label, saved));
                return;
            }
    
            if (lower === '/menu' || lower === '📋 /menu') {
                telegramMenuState = null;
                telegramEditTarget = null;
                tgMainMenu('📋 Rolladen-Menü');
                return;
            }
    
            if (lower === '/hilfe' || lower === 'ℹ️ hilfe') {
                telegramMenuState = null;
                telegramEditTarget = null;
                tgMainMenu(
                    'ℹ️ Bedienung\n\n' +
                    'Morgen/Abend:\n' +
                    '1. Tageszeit\n' +
                    '2. Bereich\n' +
                    '3. Werktag/Frei\n' +
                    '4. Start/Ende\n' +
                    '5. HH:MM senden\n\n' +
                    'Infozeiten:\n' +
                    '1. Infozeiten\n' +
                    '2. Erste/Zweite Meldung\n' +
                    '3. HH:MM senden\n\n' +
                    'Testfahrten:\n' +
                    '1. Testfahrten\n' +
                    '2. Bereich wählen\n' +
                    '3. Auf / Zu / Spalt'
                );
                return;
            }
    
            if (lower === '/zeiten' || lower === '🕒 /zeiten') {
                telegramMenuState = null;
                telegramEditTarget = null;
                tgMainMenu(getConfigOverviewText());
                return;
            }
    
            if (lower === '/heute' || lower === '🔄 /heute') {
                berechneRandomZeiten();
                telegramMenuState = null;
                telegramEditTarget = null;
                tgMainMenu(getTodayOverviewText());
                return;
            }
    
            if (lower === '/urlaub an' || lower === '🏖️ urlaub an') {
                setState(URLAUBSMODUS, true);
                telegramMenuState = null;
                telegramEditTarget = null;
                tgMainMenu('🏖️ Urlaubsmodus aktiviert');
                return;
            }
    
            if (lower === '/urlaub aus' || lower === '🏠 urlaub aus') {
                setState(URLAUBSMODUS, false);
                telegramMenuState = null;
                telegramEditTarget = null;
                tgMainMenu('🏠 Urlaubsmodus deaktiviert');
                return;
            }
    
            if (lower === '🌅 morgen') {
                telegramMenuState = { step: 'area', tageszeit: 'morgens' };
                tgAreaMenuMorgen('🌅 Morgen: Bereich wählen');
                return;
            }
    
            if (lower === '🌙 abend') {
                telegramMenuState = { step: 'area', tageszeit: 'abends' };
                tgAreaMenuAbend('🌙 Abend: Bereich wählen');
                return;
            }
    
            if (lower === '🕒 infozeiten') {
                telegramMenuState = { step: 'info_select' };
                tgInfoMenu('🕒 Infozeiten wählen');
                return;
            }
    
            if (lower === '🧪 testfahrten') {
                telegramMenuState = { step: 'test_main' };
                tgTestMenu('🧪 Testfahrten wählen');
                return;
            }
    
            if (lower === '1️⃣ erste meldung') {
                telegramMenuState = {
                    step: 'input',
                    mode: 'info',
                    which: 'erste'
                };
                telegramEditTarget = getMenuTargetPath(telegramMenuState);
                tgWithKeyboard(
                    '⏱ Bitte neue Zeit senden für:\n' + getMenuTargetLabel(telegramMenuState) + '\n\nFormat: HH:MM',
                    [['⬅️ Zurück', '❌ Abbrechen']]
                );
                return;
            }
    
            if (lower === '2️⃣ zweite meldung') {
                telegramMenuState = {
                    step: 'input',
                    mode: 'info',
                    which: 'zweite'
                };
                telegramEditTarget = getMenuTargetPath(telegramMenuState);
                tgWithKeyboard(
                    '⏱ Bitte neue Zeit senden für:\n' + getMenuTargetLabel(telegramMenuState) + '\n\nFormat: HH:MM',
                    [['⬅️ Zurück', '❌ Abbrechen']]
                );
                return;
            }
    
            /* ===== FIX: Fenster Testfahrt getrennt von Fenster/Terrasse Zeitmenü ===== */
            if ((lower === '🪟 fenster' || lower === 'fenster') && telegramMenuState && telegramMenuState.step === 'test_main') {
                telegramMenuState = { step: 'test_fenster' };
                tgTestFensterMenu('🪟 Fenster Testfahrt');
                return;
            }
    
            if (lower === '🪟 fenster/terrasse') {
                if (!telegramMenuState || !telegramMenuState.tageszeit) {
                    tgMainMenu('Bitte zuerst Morgen oder Abend wählen');
                    return;
                }
    
                telegramMenuState = {
                    step: 'daytype',
                    tageszeit: telegramMenuState.tageszeit,
                    bereich: 'ft'
                };
                tgDaytypeMenu('Bitte Tagtyp wählen');
                return;
            }
    
            if (lower === '🏡 terrasse' && telegramMenuState && telegramMenuState.step === 'test_main') {
                telegramMenuState = { step: 'test_terrasse' };
                tgTestTerrasseMenu('🏡 Terrasse Testfahrt');
                return;
            }
    
            if (lower === '🍽️ küche' || lower === '🍽️ k\u00fcche') {
                if (telegramMenuState && telegramMenuState.step === 'test_main') {
                    telegramMenuState = { step: 'test_kueche' };
                    tgTestKuecheMenu('🍽️ Küche Testfahrt');
                    return;
                }
    
                if (!telegramMenuState || !telegramMenuState.tageszeit) {
                    tgMainMenu('Bitte zuerst Morgen oder Abend wählen');
                    return;
                }
    
                telegramMenuState = {
                    step: 'daytype',
                    tageszeit: telegramMenuState.tageszeit,
                    bereich: 'kueche'
                };
                tgDaytypeMenu('Bitte Tagtyp wählen');
                return;
            }
    
            if (lower === '📅 neu berechnen' && telegramMenuState && telegramMenuState.step === 'test_main') {
                randomDateKey = '';
                lastInfoSent = {};
                berechneRandomZeiten();
                tgTestMenu('📅 Zufallszeiten neu berechnet');
                return;
            }
    
            if (lower === '📤 zeiten senden' && telegramMenuState && telegramMenuState.step === 'test_main') {
                berechneRandomZeiten();
                tgTestMenu(getTodayOverviewText());
                return;
            }
    
            if (telegramMenuState && telegramMenuState.step === 'test_fenster') {
                if (lower === '⬆️ auf') {
                    markAutoMove('Fenster', FENSTER, AUF);
                    tgTestFensterMenu('🪟 Fenster auf ' + posText(AUF));
                    return;
                }
                if (lower === '⬇️ zu') {
                    markAutoMove('Fenster', FENSTER, ZU);
                    tgTestFensterMenu('🪟 Fenster auf ' + posText(ZU));
                    return;
                }
                if (lower === '↔️ spalt') {
                    markAutoMove('Fenster', FENSTER, FENSTER_SPALT);
                    tgTestFensterMenu('🪟 Fenster auf ' + posText(FENSTER_SPALT));
                    return;
                }
            }
    
            if (telegramMenuState && telegramMenuState.step === 'test_terrasse') {
                if (lower === '⬆️ auf') {
                    markAutoMove('Terrasse', TERRASSE, AUF);
                    tgTestTerrasseMenu('🏡 Terrasse auf ' + posText(AUF));
                    return;
                }
                if (lower === '⬇️ zu') {
                    markAutoMove('Terrasse', TERRASSE, ZU);
                    tgTestTerrasseMenu('🏡 Terrasse auf ' + posText(ZU));
                    return;
                }
                if (lower === '↔️ spalt') {
                    markAutoMove('Terrasse', TERRASSE, TERRASSE_SPALT);
                    tgTestTerrasseMenu('🏡 Terrasse auf ' + posText(TERRASSE_SPALT));
                    return;
                }
            }
    
            if (telegramMenuState && telegramMenuState.step === 'test_kueche') {
                if (lower === '⬆️ auf') {
                    markAutoMove('Kueche', KUECHE, AUF);
                    tgTestKuecheMenu('🍽️ Küche auf ' + posText(AUF));
                    return;
                }
                if (lower === '⬇️ zu') {
                    markAutoMove('Kueche', KUECHE, ZU);
                    tgTestKuecheMenu('🍽️ Küche auf ' + posText(ZU));
                    return;
                }
                if (lower === '↔️ spalt') {
                    markAutoMove('Kueche', KUECHE, KUECHE_SPALT);
                    tgTestKuecheMenu('🍽️ Küche auf ' + posText(KUECHE_SPALT));
                    return;
                }
            }
    
            if (lower === '💼 werktag') {
                if (!telegramMenuState || !telegramMenuState.tageszeit || !telegramMenuState.bereich) {
                    tgMainMenu('Bitte neu starten');
                    return;
                }
    
                telegramMenuState = {
                    step: 'boundary',
                    tageszeit: telegramMenuState.tageszeit,
                    bereich: telegramMenuState.bereich,
                    tagtyp: 'werktag'
                };
                tgBoundaryMenu('Bitte Start oder Ende wählen');
                return;
            }
    
            if (lower === '🎉 frei') {
                if (!telegramMenuState || !telegramMenuState.tageszeit || !telegramMenuState.bereich) {
                    tgMainMenu('Bitte neu starten');
                    return;
                }
    
                telegramMenuState = {
                    step: 'boundary',
                    tageszeit: telegramMenuState.tageszeit,
                    bereich: telegramMenuState.bereich,
                    tagtyp: 'frei'
                };
                tgBoundaryMenu('Bitte Start oder Ende wählen');
                return;
            }
    
            if (lower === '▶️ start' || lower === '⏹️ ende') {
                if (!telegramMenuState || !telegramMenuState.tageszeit || !telegramMenuState.bereich || !telegramMenuState.tagtyp) {
                    tgMainMenu('Bitte neu starten');
                    return;
                }
    
                const grenze = lower === '▶️ start' ? 'start' : 'ende';
    
                telegramMenuState = {
                    step: 'input',
                    tageszeit: telegramMenuState.tageszeit,
                    bereich: telegramMenuState.bereich,
                    tagtyp: telegramMenuState.tagtyp,
                    grenze: grenze
                };
    
                telegramEditTarget = getMenuTargetPath(telegramMenuState);
    
                if (!telegramEditTarget) {
                    tgMainMenu('❌ Ziel konnte nicht ermittelt werden');
                    telegramMenuState = null;
                    return;
                }
    
                tgWithKeyboard(
                    '⏱ Bitte neue Zeit senden für:\n' + getMenuTargetLabel(telegramMenuState) + '\n\nFormat: HH:MM',
                    [['⬅️ Zurück', '❌ Abbrechen']]
                );
                return;
            }
    
            if (lower.startsWith('/zeit ')) {
                const parts = rawText.split(/\s+/);
    
                if ((parts[1] || '').toLowerCase() === 'info') {
                    if (parts.length < 4) {
                        tgMainMenu('❌ Ungültiger Befehl.\nBeispiel:\n/zeit info 1 12:00');
                        return;
                    }
    
                    const path = resolveTelegramTimePath(parts.slice(1, 3));
                    const value = parts[3];
    
                    if (!path || !isValidTimeText(value)) {
                        tgMainMenu('❌ Ungültiger Info-Befehl.\nBeispiele:\n/zeit info 1 12:00\n/zeit info 2 18:00');
                        return;
                    }
    
                    const saved = setConfigTimeState(path, value);
                    randomDateKey = '';
                    lastInfoSent = {};
                    berechneRandomZeiten();
    
                    tgMainMenu(telegramSavedMessage(path, saved));
                    return;
                }
    
                if (parts.length < 6) {
                    tgMainMenu(
                        '❌ Ungültiger Befehl.\n\n' +
                        'Beispiele:\n' +
                        '/zeit morgens ft werktag start 06:45\n' +
                        '/zeit morgens kueche frei ende 08:30\n' +
                        '/zeit abends ft frei start 20:40'
                    );
                    return;
                }
    
                const path = resolveTelegramTimePath(parts.slice(1, 5));
                const value = parts[5];
    
                if (!path || !isValidTimeText(value)) {
                    tgMainMenu(
                        '❌ Ungültiger Zeit-Befehl.\n\n' +
                        'Beispiele:\n' +
                        '/zeit morgens ft werktag start 06:45\n' +
                        '/zeit morgens kueche frei ende 08:30\n' +
                        '/zeit abends ft frei start 20:40'
                    );
                    return;
                }
    
                const saved = setConfigTimeState(path, value);
                randomDateKey = '';
                lastInfoSent = {};
                berechneRandomZeiten();
    
                tgMainMenu(telegramSavedMessage(path, saved));
                return;
            }
        } catch (e) {
            log('Telegram Zeitsteuerung Fehler: ' + e, 'warn');
            tgMainMenu('❌ Fehler beim Verarbeiten des Telegram-Befehls.');
        }
    });
    
    /* =========================
       Manuelle Bedienung melden
       ========================= */
    
    on({ id: TERRASSE, change: 'ne' }, obj => {
        if (autoMoveTerrasse) return;
        tg('🖐️ Terrassenrolladen wurde manuell bewegt auf ' + posText(obj.state.val));
    });
    
    on({ id: FENSTER, change: 'ne' }, obj => {
        if (autoMoveFenster) return;
        tg('🖐️ Fensterrolladen wurde manuell bewegt auf ' + posText(obj.state.val));
    });
    
    on({ id: KUECHE, change: 'ne' }, obj => {
        if (autoMoveKueche) return;
        tg('🖐️ Küchenrolladen wurde manuell bewegt auf ' + posText(obj.state.val));
    });
    
    /* =========================
       Start
       ========================= */
    
    ensureStates();
    berechneRandomZeiten();
    tgMainMenu('✅ Rolladen-Skript gestartet');
    

    Vielleicht ist das was für jemanden

    Intel NUC mit Iobroker

    1 Antwort Letzte Antwort
    0

    Hey! Du scheinst an dieser Unterhaltung interessiert zu sein, hast aber noch kein Konto.

    Hast du es satt, bei jedem Besuch durch die gleichen Beiträge zu scrollen? Wenn du dich für ein Konto anmeldest, kommst du immer genau dorthin zurück, wo du zuvor warst, und kannst dich über neue Antworten benachrichtigen lassen (entweder per E-Mail oder Push-Benachrichtigung). Du kannst auch Lesezeichen speichern und Beiträge positiv bewerten, um anderen Community-Mitgliedern deine Wertschätzung zu zeigen.

    Mit deinem Input könnte dieser Beitrag noch besser werden 💗

    Registrieren Anmelden
    Antworten
    • In einem neuen Thema antworten
    Anmelden zum Antworten
    • Älteste zuerst
    • Neuste zuerst
    • Meiste Stimmen


    Support us

    ioBroker
    Community Adapters
    Donate

    450

    Online

    32.8k

    Benutzer

    82.8k

    Themen

    1.3m

    Beiträge
    Community
    Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
    ioBroker Community 2014-2025
    logo
    • Anmelden

    • Du hast noch kein Konto? Registrieren

    • Anmelden oder registrieren, um zu suchen
    • Erster Beitrag
      Letzter Beitrag
    0
    • Home
    • Aktuell
    • Tags
    • Ungelesen 0
    • Kategorien
    • Unreplied
    • Beliebt
    • GitHub
    • Docu
    • Hilfe