Weiter zum Inhalt

Skripten / Logik

16.6k Themen 214.4k Beiträge

Hilfe zu JavaScript, Blockly, TypeScript, Node-RED, Scenes und text2command

NEWS

Unterkategorien


  • Hilfe für Skripterstellung mit JavaScript

    3k 49k
    3k Themen
    49k Beiträge
    H
    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
  • Hilfe für Skripterstellung mit Blockly

    7k 80k
    7k Themen
    80k Beiträge
    vowillV
    Offenbar schaltet Amazon nach und nach die Zugänge für Fremdanbieter sukzessive ab. Letzte Woche lief auch mein Plex-Skill nicht mehr im Multiroom, sondern nur noch auf 1 Lautsprecher. Und jetzt gibt es den Plex-Skill gar nicht mehr zur Installation. Also: Entweder Amazon unlimited, oder weg damit.
  • Hilfe für Skripterstellung mit Node-RED

    954 13k
    954 Themen
    13k Beiträge
    F
    Hallo ich habe ein Problem mit Nodered und Alexa Echo. Ich habe in Nodered die Wemo Emulation installiert und eine Wemo Steckdose emuliert um an einer Zigbeesteckdosenleiste einen Platz der Leiste zu schalten. "node-red-contrib-wemo-emulator" Das klappt auch wunderbar leider nicht länger als ein zwei Tage. Wenn ich dann den Echo der mit der emulierten Steckdose verbunden ist neu starte geht es wieder. Leider ist das sehr nervig. Hat schon mal jemand gehabt? Durch die Forumssuche habe ich leider nicht gefunden. Gruss Achim
  • [gelöst]Dimmen mit "Long Press"

    Verschoben
    39
    2
    0 Stimmen
    39 Beiträge
    7k Aufrufe
    coyoteC
    @paul53 coole Idee, funktioniert 1A. Danke
  • Summe aus verschiedenen Datenpunkte

    javascript
    20
    0 Stimmen
    20 Beiträge
    2k Aufrufe
    T
    @tigertim08 Werfe mal das parseFloat() raus und füge: log('a: '+a); log('b: '+b); log('c: '+c); log('d: '+d); log('e: '+e); log('f: '+f); log('g: '+g); nach Zeile 20 ein. Poste die Logeinträge. Denke mal das es ein Problem mit ".," oder Einheitenangaben ist.
  • Auswertung neues Objekt und Inhalt weiterverarbeiten

    blockly
    2
    0 Stimmen
    2 Beiträge
    214 Aufrufe
    T
    Keiner eine Idee?
  • Mähroboter per Blockly starten

    blockly
    5
    3
    0 Stimmen
    5 Beiträge
    2k Aufrufe
    arteckA
    @Bastian-Rüggen da hat der @Sven_Topp doch ein schickes script geschrieben der genau das macht was du suchst .. warum das Rad 2 mal erfinden
  • Datenpunkt Date

    javascript
    2
    1
    0 Stimmen
    2 Beiträge
    134 Aufrufe
    M
    Edit: klappt nur Verzögert, offenbar klappt die Datenübergabe nur nach Aktualisierung Es hat was mit dem Eingabetype zu tun. Bei Date klappte es nur nach Aktualisierung, bei Text/String sofort
  • Skriptleichen entfernen

    javascript
    5
    0 Stimmen
    5 Beiträge
    498 Aufrufe
    B
    Cool! Danke
  • Echo (Alexa) Musik bei Präsenz - Geht das?

    multimedia javascript
    4
    0 Stimmen
    4 Beiträge
    615 Aufrufe
    J
    @Hansi123 dann hast das Problem schon erkannt. Synchron geht eigentlich nur bei Multiroom, da so der Alexa Server das steuert. Wenn je ein Dot dann noch dazu soll, wird der separat versorgt, was in der regel dafür sorgt, dass zwar die selbe Musik läuft, aber leider nicht ganz synchron. Die einzige Möglichkeit, die ich sehe, Du startest grundsätzlich Multiroom und steuerst über die BWMs nur "Ton an/aus", so ist das auf alle Fälle synchron.
  • [gelöst] Schleife nach 3 min abbrechen

    blockly
    6
    1
    0 Stimmen
    6 Beiträge
    311 Aufrufe
    Dr. BakteriusD
    @skokarl Kommt auf den Button an, aber vielleicht so: [image: 1590555964546-blockly.png]
  • Staubsaugersteuerung - sequentielles script möglich?

    blockly scenes
    15
    0 Stimmen
    15 Beiträge
    1k Aufrufe
    B
    hehe ;) habe das bei mir noch geupgraded: [image: 1590490455125-unbenannt.png] so kommt er jeden 3. Tag zur Tür gefahren, wenn ich heim komme
  • Einfügen aus Zwischenablage[gelöst]

    javascript
    5
    0 Stimmen
    5 Beiträge
    629 Aufrufe
    S
    @mcgl
  • falls wert länger als 2 minuten immer <x, dann...?

    javascript blockly
    4
    0 Stimmen
    4 Beiträge
    602 Aufrufe
    Amnesia1211A
    @chrbo80 Hoffe es hat geklappt, kein Problem.
  • Erkenneung ob Gerät fertig ist via Leistungsaufnahme

    javascript blockly monitoring
    16
    2
    0 Stimmen
    16 Beiträge
    2k Aufrufe
    Albert KA
    @dehein2 Hatte die gleichen Probleme mit meinen Maschinen. Die Analyse der Leistungsaufnahme Diagramme haben mich dann auf die richtige Spur gebracht (zumindest eine die für mich passt) Nicht nur die Leistungsaufnahme ist interessant sondern auch deren Laufzeiten. Die Unterschiede in der Leistungaufnahme von zB Trocken Phasen und Standby sind bei meinen Geräten nicht unterscheidbar. Allerdings ist die Zeitdauer dieser Endphasen einigermaßen konstant. Beispiel Spülmaschine (läuft je nach Beladung und Verschmutzungsgrad 60 min bis zu über 2 Std). Die letzte Trocken Phase vor dem letzten Abpumpen dauert aber immer zwischen 8 und 12 Minuten und zieht 3 bis 4 Watt (so wie auch der Standby Betrieb) Wir waschen, trocknen und spülen nachts (Nacht Tarif) und deshalb laufen dann die Geräte im Standby weiter bis sie morgens abgeschaltet werden (oder auch vergessen werden). Das stört mich. Mit einer 15 minütigen Standby Zeit kann ich aber leben, d.h. nach kontinuierlichen 15 Minuten unter 5 Watt schalte ich den Strom der Steckdose ab. Mit einem minütlichen Intervall und einem Zähler zähle ich die Minuten unter 5W Leistungsaufnahme. Wenn die Leistungsaufnahme innerhalb von 15 Minuten wieder ansteigen sollte (wie zB nach 8 bis 12 Minuten trocknen zum letzten Abpumpen mit 25W)), dann wird eben der Zähler wieder auf Null gesetzt und startet von vorne wenn die Leistungsaufnahme wieder unter 5W sinkt.. Ähnlich könnte man ja auch einen Knitterschutz erkennen wenn dieser in einigermassen festen Zeitabständen abläuft. Nach 5 mal Knitterschutz kann man dann sicher annehmen dass die Maschine fertig ist und den Strom abschalten und/oder eine Nachricht versenden. Ich habe das gleiche Skript für mehrere Geräte die im Standby Strom ziehen nur leicht angepasst (Mikrowelle, Dampfgarer, Waschmaschine, Trockner) und bin damit eigentlich zufrieden. Lieber 15 Minuten im Standby als 24 Stunden oder mehr im Standby. Vorausetzung ist natürlich eine entsprechende Smart Steckdose, oder ähnliches, die die Leistungsaufnahme im Sekunden oder Milli Sekunden Bereich aktualisiert.
  • Text2Command und virtueller Tastendruck

    security template blockly
    19
    0 Stimmen
    19 Beiträge
    1k Aufrufe
    BBTownB
    @samsungfreak so sieht bei mir beispielsweise das Einschalten und das Ausschalten eines solchen Datenpunktes (hier meine Außenlampen) per telegram-Kommando aus: [image: 1590339702995-c4ed3c16-d81e-4a67-a50d-30f663f34a87-image.png]
  • Timeout (Blockly) Restlaufzeit anzeigen in VIS

    blockly
    2
    2
    0 Stimmen
    2 Beiträge
    1k Aufrufe
    paul53P
    @overfl0w Intervall anstelle timeout verwenden und die Restzeit runterzählen und in einen Datenpunkt schreiben, der in Vis angezeigt wird. Siehe hier oder [image: 1590337067404-blockly_temp.jpg]
  • Pool Zeituhr für Pumpe

    blockly javascript
    33
    0 Stimmen
    33 Beiträge
    3k Aufrufe
    D
    Also nach langen hin und her habe ich und kann es jetzt auch nur jedem Empfehlen das Skript von @Pittini genommen. Danke an allen, ich hab wieder mal einiges gelernt :-)
  • Tabelle / Liste mit Blockly und VIS

    javascript blockly
    2
    0 Stimmen
    2 Beiträge
    570 Aufrufe
    M
    so... selbst ist der Mann ! falls mal jemand sowas braucht hier meine Lösung: Javascriptfunktion , die die Json-Tabelle erweitert... if (getState("0_userdata.0.Anwesenheit.Putzfrau.JSON").val=="") { setState("0_userdata.0.Anwesenheit.Putzfrau.JSON", JSON.stringify([])); } liste = JSON.parse(getState("0_userdata.0.Anwesenheit.Putzfrau.JSON").val); liste.push({ "Nr": getState("0_userdata.0.Anwesenheit.Putzfrau.Anzahl_Monat").val, "kommen": getState("0_userdata.0.Anwesenheit.Putzfrau.Kommen").val, "gehen": getState("0_userdata.0.Anwesenheit.Putzfrau.Gehen").val, "dauer": getState("0_userdata.0.Anwesenheit.Putzfrau.Anwesend").val, "lohn": getState("0_userdata.0.Anwesenheit.Putzfrau.Verguetung_Heute").val}) setState("0_userdata.0.Anwesenheit.Putzfrau.JSON", JSON.stringify(liste));
  • Rollladen Höhe entspricht nicht Datenpunkt

    blockly
    11
    1
    0 Stimmen
    11 Beiträge
    452 Aufrufe
    M
    (un)fun Fact: Ist der Rollladen noch unten und ich geb den Befehl er soll hochgehen, läuft er statt bis 55% bis 100%. Ist der Rollladen schon oben und ich geb den Befehl er soll hochgehen, läuft er auf 0%.
  • [gelöst] Differenz von 2 timestamps berechnen

    javascript
    5
    0 Stimmen
    5 Beiträge
    800 Aufrufe
    P
    @paul53 sagte in Differenz von 2 timestamps berechnen: // Erfassen Start-Stop-Zeiten Kreis-1 if(dp.common.name === 'Gartenberegnung - Ventil 1'){ if(dp.state.val) { var StartzeitKreis1 = dp.state.lc; setState('javascript.4.OpenSprinklerPi.VIS.Zeiterfassung.timestamp-Kreis1-start', StartzeitKreis1 ); log('Startzeit Beregnung Kreis-1 gesetzt'); } else { var StopzeitKreis1 = dp.state.lc; var GesamtKreis1 = (StopzeitKreis1 - dp.oldState.lc) / 1000; setState('javascript.4.OpenSprinklerPi.VIS.Zeiterfassung.timestamp-Kreis1-stop', StopzeitKreis1); setState('javascript.4.OpenSprinklerPi.VIS.Zeiterfassung.timestamp-Kreis1-gesamt', GesamtKreis1 ); log('Stopzeit Beregnung Kreis-1 gesetzt'); log('Gesamtzeit: ' + GesamtKreis1 ); } } Hi Paul, danke hat super geklappt. Gruß Johnny
  • Bei Anwesenheit soll das Licht an bleiben.

    blockly
    3
    2
    0 Stimmen
    3 Beiträge
    308 Aufrufe
    paul53P
    @Paul-Che sagte: Aktuell einfach nur Licht: Selbst dass funktioniert nicht richtig. Trigger innerhalb eines Triggers ist keine gute Idee ! Das, was mit diesem Blockly wohl erreicht werden soll, kann etwa so aussehen: [image: 1589817199748-blockly_temp.jpg]
  • Alexa commands Speak Pausen

    blockly
    2
    0 Stimmen
    2 Beiträge
    317 Aufrufe
    padrinoP
    @TTBerlin Yep https://forum.iobroker.net/post/433163

655

Online

32.8k

Benutzer

82.8k

Themen

1.3m

Beiträge