- ioBroker Community Home
- Deutsch
- Skripten / Logik
- verschiedene Skripte
verschiedene Skripte
-
Hallo zusammen,
habe mal bei mir aufgeräumt und habe eine ganze Menge an Skripten die ich von hier habe und stellenweise auch umgebaut habe die ich jetzt gerne mit euch teilen möchte:z.B.
Musikstatus je Raum mit Alexa und/oder Sonos mit States Erstellung.Voraussetzung ist der Alexa Adapter und der Sonos Adapter:

Ihr müsst nur die Geräte im Skript ändern.
/************************************************* * Musikstatus je Raum * mit State-Erstellung + Provider-Erkennung *************************************************/ const rooms = [ { name: "Wohnzimmer", target: "0_userdata.0.Status_Musikboxen.Wohnzimmer_Status", alexaState: "Deine Alexa.Player.currentState", alexaProvider: "Deine Alexa.Player.providerName", sonosState: "sonos.0.root.192_168_178_XXX.state_simple", sonosType: "sonos.0.root.192_168_178_XXX.current_type", delayMs: 0, config: { alexaDirectProvider: false, sonosMap: {0:"Musik von Platte",1:"TuneIn-Liveradio",2:"Fernseher"} } }, { name: "Esszimmer", target: "0_userdata.0.Status_Musikboxen.Esszimmer_Status", alexaState: "Deine Alexa.Player.currentState", alexaProvider: "Deine Alexa.Player.providerName", sonosState: "sonos.0.root.192_168_178_XXX.state_simple", sonosType: "sonos.0.root.192_168_178_XXX.current_type", delayMs: 0, config: { alexaDirectProvider: false, sonosMap: {0:"Musik von Platte",1:"TuneIn-Liveradio"} } }, { name: "Küche", target: "0_userdata.0.Status_Musikboxen.Küche_Status", alexaState: "Deine Alexa.Player.currentState", alexaProvider: "Deine Alexa.Player.providerName", sonosState: "sonos.0.root.192_168_178_XXX.state_simple", sonosType: "sonos.0.root.192_168_178_XXX.current_type", delayMs: 0, config: { alexaDirectProvider: false, sonosMap: {0:"Musik von Platte",1:"TuneIn-Liveradio"} } }, { name: "Garten", target: "0_userdata.0.Status_Musikboxen.Garten_Status", alexaState: "Deine Alexa.Player.currentState", alexaProvider: "Deine Alexa.Player.providerName", delayMs: 2000, config: { alexaDirectProvider: true } }, { name: "Schlafzimmer", target: "0_userdata.0.Status_Musikboxen.Schlafzimmer_Status", alexaState: "Deine Alexa.Player.currentState", alexaProvider: "Deine Alexa.Player.providerName", delayMs: 2000, config: { alexaDirectProvider: true } }, { name: "Spielzimmer", target: "0_userdata.0.Status_Musikboxen.Spielzimmer_Status", alexaState: "Deine Alexa.Player.currentState", alexaProvider: "Deine Alexa.Player.providerName", delayMs: 0, config: { alexaDirectProvider: true } }, { name: "Kinderzimmer", target: "0_userdata.0.Status_Musikboxen.Kinderzimmer_Status", alexaState: "Deine Alexa.Player.currentState", alexaProvider: "Deine Alexa.Player.providerName", delayMs: 2000, config: { alexaDirectProvider: true } }, { name: "Gäste WC", target: "0_userdata.0.Status_Musikboxen.Gaeste_WC", alexaState: "Deine Alexa.Player.currentState", alexaProvider: "Deine Alexa.Player.providerName", delayMs: 0, config: { alexaDirectProvider: true } }, { name: "Badezimmer", target: "0_userdata.0.Status_Musikboxen.Badezimmer_Status", alexaState: "Deine Alexa.Player.currentState", alexaProvider: "Deine Alexa.Player.providerName", sonosState: "sonos.0.root.192_168_178_XXX.state_simple", sonosType: "sonos.0.root.192_168_178_XXX.current_type", delayMs: 0, config: { alexaDirectProvider: false, sonosMap: {0:"Musik von Platte",1:"TuneIn-Liveradio"} } }, { name: "Hobbyraum", target: "0_userdata.0.Status_Musikboxen.Hobbyraum_Status", alexaState: "Deine Alexa.Player.currentState", alexaProvider: "Deine Alexa.Player.providerName", sonosState: "sonos.0.root.192_168_178_XXX.state_simple", sonosType: "sonos.0.root.192_168_178_XXX.current_type", delayMs: 0, config: { alexaDirectProvider: false, sonosMap: {0:"Musik von Platte",1:"TuneIn-Liveradio"} } } ]; /*********************** STATES ERSTELLEN ***********************/ function createTargetState(room) { createState(room.target, "nichts", { name: `Musikstatus ${room.name}`, type: "string", role: "text", read: true, write: true }); } /*********************** LOGIK ***********************/ function readVal(id, def = null) { if (!id) return def; const s = getState(id); return s ? s.val : def; } function normalizeProvider(p) { if (!p) return ""; const raw = String(p).trim(); const t = raw.toLowerCase(); if (t.includes("amazon")) return "Amazon Music"; if (t.includes("spotify")) return "Spotify"; if (t.includes("tunein") || t.includes("radio")) return "TuneIn-Liveradio"; if (t.includes("audible")) return "Audible"; if (t.includes("apple")) return "Apple Music"; if (t.includes("deezer")) return "Deezer"; if (t.includes("youtube")) return "YouTube"; if (t.includes("podcast")) return "Podcast"; if (t.includes("bluetooth") || t.includes("aux") || t.includes("line")) return "Externe Quelle"; return raw; } function writeStatus(room, val) { if (room.delayMs > 0) { setStateDelayed(room.target, val, true, room.delayMs, true); } else { setState(room.target, val, true); } } function evalRoom(room) { const alexaPlaying = readVal(room.alexaState, false) === true; const alexaProvider = normalizeProvider(readVal(room.alexaProvider, "")); const sonosPlaying = readVal(room.sonosState, false) === true; const sonosType = readVal(room.sonosType, null); if (room.config.alexaDirectProvider) { if (alexaPlaying) { writeStatus(room, alexaProvider || "Andere Quelle"); log(`+++ ${room.name}: ${alexaProvider || "Andere Quelle"} +++`, "debug"); } else { writeStatus(room, "nichts"); } return; } if (alexaPlaying && alexaProvider) { writeStatus(room, alexaProvider); return; } if ( sonosPlaying && room.config.sonosMap && room.config.sonosMap[sonosType] !== undefined ) { writeStatus(room, room.config.sonosMap[sonosType]); return; } writeStatus(room, "nichts"); } function initRoom(room) { createTargetState(room); const ids = []; if (room.alexaState) ids.push(room.alexaState); if (room.alexaProvider) ids.push(room.alexaProvider); if (room.sonosState) ids.push(room.sonosState); if (room.sonosType) ids.push(room.sonosType); if (ids.length === 0) { log(`Keine Trigger für ${room.name} konfiguriert`, "warn"); return; } on({ id: ids, change: "ne" }, () => evalRoom(room)); setTimeout(() => { evalRoom(room); }, 1000); } /*********************** START ***********************/ rooms.forEach(initRoom);
Lautstärken Reset in Verbindung mit dem Skript Musikstatus
Funktionen:-
- Reagiert auf Statusänderung eines Raums
-
- Prüft auf Zielstatus "nichts"
-
- Setzt Lautstärke definierter Geräte nach optionaler Verzögerung
-
- Verhindert Mehrfach-Timer pro Raum
-
- Optionales Debug-Logging
-
- Alexa notificationVolume wird explizit über notificationId gesetzt
-
- Sonderfall Kinderzimmer: nur notificationVolume, kein Player.volume
/** * Echo Stati / Lautstärke-Reset * * Funktionen: * - Reagiert auf Statusänderung eines Raums * - Prüft auf Zielstatus "nichts" * - Setzt Lautstärke definierter Geräte nach optionaler Verzögerung * - Verhindert Mehrfach-Timer pro Raum * - Optionales Debug-Logging * - Alexa notificationVolume wird explizit über notificationId gesetzt * - Sonderfall Kinderzimmer: nur notificationVolume, kein Player.volume */ const CONFIG = { targetStatus: 'nichts', defaultDelayMs: 5000, debug: true, logPrefix: '[Musikboxen]', minNotificationVolume: 0, maxNotificationVolume: 100, defaultNotificationOffset: 5 }; const rooms = [ { name: 'Küche', enabled: true, statusId: '0_userdata.0.Status_Musikboxen.Küche_Status', delayMs: 0, trigger: 'ne', devices: [ { id: 'alexa2.0.Echo-Devices.661b5e9aa0b24161a4ec52b340173846.Player.volume', notificationId: 'alexa2.0.Echo-Devices.661b5e9aa0b24161a4ec52b340173846.Preferences.notificationVolume', volume: 10, notificationOffset: 5 }, { id: 'alexa2.0.Echo-Devices.4879f7942cdb42d596f4e24aac81e908.Player.volume', notificationId: 'alexa2.0.Echo-Devices.661b5e9aa0b24161a4ec52b340173846.Preferences.notificationVolume', volume: 10, notificationOffset: 5 }, { id: 'sonos.0.root.192_168_178_18.volume', volume: 10 } ] }, { name: 'Wohnzimmer', enabled: true, statusId: '0_userdata.0.Status_Musikboxen.Wohnzimmer_Status', delayMs: CONFIG.defaultDelayMs, trigger: 'any', devices: [ { id: 'alexa2.0.Echo-Devices.025b455db7bb4720ad941b7139bcc805.Player.volume', notificationId: 'alexa2.0.Echo-Devices.d5794400dafd4c2cb0c8889fc15f0653.Preferences.notificationVolume', volume: 10, notificationOffset: 5 }, { id: 'alexa2.0.Echo-Devices.d5794400dafd4c2cb0c8889fc15f0653.Player.volume', notificationId: 'alexa2.0.Echo-Devices.d5794400dafd4c2cb0c8889fc15f0653.Preferences.notificationVolume', volume: 10, notificationOffset: 5 }, { id: 'sonos.0.root.192_168_178_21.volume', volume: 10 } ] }, { name: 'Esszimmer', enabled: true, statusId: '0_userdata.0.Status_Musikboxen.Esszimmer_Status', delayMs: CONFIG.defaultDelayMs, trigger: 'any', devices: [ { id: 'alexa2.0.Echo-Devices.084e4de76a484be18aeda245e8f3f4d4.Player.volume', notificationId: 'alexa2.0.Echo-Devices.c31ec0fc26584e078ca0555aecfc9188.Preferences.notificationVolume', volume: 10, notificationOffset: 5 }, { id: 'alexa2.0.Echo-Devices.c31ec0fc26584e078ca0555aecfc9188.Player.volume', notificationId: 'alexa2.0.Echo-Devices.c31ec0fc26584e078ca0555aecfc9188.Preferences.notificationVolume', volume: 10, notificationOffset: 5 }, { id: 'sonos.0.root.192_168_178_20.volume', volume: 10 } ] }, { name: 'Schlafzimmer', enabled: true, statusId: '0_userdata.0.Status_Musikboxen.Schlafzimmer_Status', delayMs: CONFIG.defaultDelayMs, trigger: 'ne', devices: [ { id: 'alexa2.0.Echo-Devices.G091MK0614730BNS.Player.volume', notificationId: 'alexa2.0.Echo-Devices.G091MK0614730BNS.Preferences.notificationVolume', volume: 18, notificationOffset: 12 } ] }, { name: 'Hobbyraum', enabled: true, statusId: '0_userdata.0.Status_Musikboxen.Hobbyraum_Status', delayMs: CONFIG.defaultDelayMs, trigger: 'ne', devices: [ { id: 'alexa2.0.Echo-Devices.d3127dcdc8bc4158807fcab2fcfe7fba.Player.volume', notificationId: 'alexa2.0.Echo-Devices.c9d487971e914e939baad79585dd7b2e.Preferences.notificationVolume', volume: 10, notificationOffset: 5 } ] }, { name: 'Gäste WC', enabled: true, statusId: '0_userdata.0.Status_Musikboxen.Gaeste_WC', delayMs: CONFIG.defaultDelayMs, trigger: 'ne', devices: [ { id: 'alexa2.0.Echo-Devices.G090LF10718513KQ.Player.volume', notificationId: 'alexa2.0.Echo-Devices.G090LF10718513KQ.Preferences.notificationVolume', volume: 15, notificationOffset: 8 } ] }, { name: 'Badezimmer', enabled: true, statusId: '0_userdata.0.Status_Musikboxen.Badezimmer_Status', delayMs: CONFIG.defaultDelayMs, trigger: 'any', devices: [ { id: 'alexa2.0.Echo-Devices.828b54738e2a44a29d9331868a046c38.Player.volume', notificationId: 'alexa2.0.Echo-Devices.eba89b7180384792a24f92847baea082.Preferences.notificationVolume', volume: 10, notificationOffset: 5 }, { id: 'alexa2.0.Echo-Devices.eba89b7180384792a24f92847baea082.Player.volume', notificationId: 'alexa2.0.Echo-Devices.eba89b7180384792a24f92847baea082.Preferences.notificationVolume', volume: 10, notificationOffset: 5 }, { id: 'sonos.0.root.192_168_178_17.volume', volume: 10 } ] }, { name: 'Spielzimmer', enabled: true, statusId: '0_userdata.0.Status_Musikboxen.Spielzimmer_Status', delayMs: CONFIG.defaultDelayMs, trigger: 'any', devices: [ { id: 'alexa2.0.Echo-Devices.G2A0P3077407031K.Player.volume', notificationId: 'alexa2.0.Echo-Devices.G2A0P3077407031K.Preferences.notificationVolume', volume: 25, notificationOffset: 15 } ] }, { name: 'Kinderzimmer', enabled: true, statusId: '0_userdata.0.Status_Musikboxen.Kinderzimmer_Status', delayMs: CONFIG.defaultDelayMs, trigger: 'any', devices: [ { notificationId: 'alexa2.0.Echo-Devices.G0911B05926204Q7.Preferences.notificationVolume', volume: 11, notificationOffset: 6, notificationOnly: true } ] }, { name: 'Garten', enabled: true, statusId: '0_userdata.0.Status_Musikboxen.Garten_Status', delayMs: CONFIG.defaultDelayMs, trigger: 'any', devices: [ { id: 'alexa2.0.Echo-Devices.G091AA08052203UL.Player.volume', notificationId: 'alexa2.0.Echo-Devices.G091AA08052203UL.Preferences.notificationVolume', volume: 15, notificationOffset: 8 } ] } ]; const activeTimers = {}; function logInfo(msg) { log(`${CONFIG.logPrefix} ${msg}`, 'info'); } function logDebug(msg) { if (CONFIG.debug) { log(`${CONFIG.logPrefix} ${msg}`, 'debug'); } } function logWarn(msg) { log(`${CONFIG.logPrefix} ${msg}`, 'warn'); } function logError(msg) { log(`${CONFIG.logPrefix} ${msg}`, 'error'); } function isTargetStatus(value) { if (value === null || value === undefined) return false; return String(value).trim() === CONFIG.targetStatus; } function isValidVolume(volume) { return typeof volume === 'number' && isFinite(volume) && volume >= 0 && volume <= 100; } function stateExists(id) { try { return !!getObject(id); } catch (e) { return false; } } function clamp(value, min, max) { return Math.max(min, Math.min(max, value)); } function getNotificationVolume(volume, notificationOffset) { const offset = typeof notificationOffset === 'number' ? notificationOffset : CONFIG.defaultNotificationOffset; return clamp( volume - offset, CONFIG.minNotificationVolume, CONFIG.maxNotificationVolume ); } function clearRoomTimer(roomName) { if (activeTimers[roomName]) { clearTimeout(activeTimers[roomName]); activeTimers[roomName] = null; logDebug(`${roomName}: vorhandener Timer gelöscht`); } } function setDeviceVolume(device, roomName) { if (!device) { logWarn(`${roomName}: ungültiges Gerät übersprungen`); return; } if (!isValidVolume(device.volume)) { logWarn(`${roomName}: ungültige Lautstärke ${device.volume}`); return; } if (!device.notificationOnly) { if (!device.id) { logWarn(`${roomName}: Gerät ohne ID übersprungen`); return; } if (!stateExists(device.id)) { logWarn(`${roomName}: Datenpunkt nicht gefunden: ${device.id}`); return; } try { setState(device.id, device.volume); logInfo(`${roomName}: Volume ${device.volume} gesetzt -> ${device.id}`); } catch (e) { logError(`${roomName}: Fehler beim Setzen von ${device.id}: ${e}`); return; } } if (device.notificationId) { if (!stateExists(device.notificationId)) { logWarn(`${roomName}: notificationVolume-Datenpunkt nicht gefunden: ${device.notificationId}`); return; } const notificationVolume = getNotificationVolume(device.volume, device.notificationOffset); try { setState(device.notificationId, notificationVolume); logInfo(`${roomName}: notificationVolume ${notificationVolume} gesetzt -> ${device.notificationId}`); } catch (e) { logError(`${roomName}: Fehler beim Setzen von ${device.notificationId}: ${e}`); } } } function resetRoomVolume(room) { if (!room || !Array.isArray(room.devices) || room.devices.length === 0) { logWarn(`${room && room.name ? room.name : 'Unbekannter Raum'}: keine Geräte konfiguriert`); activeTimers[room && room.name ? room.name : 'unknown'] = null; return; } room.devices.forEach(device => setDeviceVolume(device, room.name)); activeTimers[room.name] = null; } function scheduleReset(room) { clearRoomTimer(room.name); const delayMs = typeof room.delayMs === 'number' && room.delayMs >= 0 ? room.delayMs : CONFIG.defaultDelayMs; if (delayMs === 0) { logDebug(`${room.name}: sofortiger Reset`); resetRoomVolume(room); return; } logDebug(`${room.name}: Reset in ${delayMs} ms geplant`); activeTimers[room.name] = setTimeout(() => { activeTimers[room.name] = null; resetRoomVolume(room); }, delayMs); } function registerRoom(room) { if (!room || !room.name || !room.statusId) { logWarn('Ungültige Raum-Konfiguration übersprungen'); return; } if (!room.enabled) { logDebug(`${room.name}: deaktiviert`); return; } if (!stateExists(room.statusId)) { logWarn(`${room.name}: Status-Datenpunkt nicht gefunden: ${room.statusId}`); return; } const trigger = room.trigger || 'ne'; on({ id: room.statusId, change: trigger }, obj => { try { if (!obj || !obj.state) { logDebug(`${room.name}: Event ohne state erhalten`); return; } const newValue = obj.state.val; const oldValue = obj.oldState ? obj.oldState.val : undefined; const ack = obj.state.ack; logDebug(`${room.name}: Event | alt="${oldValue}" neu="${newValue}" ack=${ack}`); if (!isTargetStatus(newValue)) { logDebug(`${room.name}: Status ist nicht "${CONFIG.targetStatus}", kein Reset`); return; } scheduleReset(room); } catch (e) { logError(`${room.name}: Fehler im Event-Handler: ${e}`); } }); logInfo(`${room.name}: Trigger registriert (${trigger}) auf ${room.statusId}`); } rooms.forEach(registerRoom); logInfo('Lautstärke-Reset-Skript mit expliziten notificationIds gestartet');
es werden noch weitere folgenIntel NUC mit Iobroker
-
-
Blink Kamera im Wechsel:
Vorraussetzung ist der Blink Adapter und das von mir entworfene Skript:
// =============================== // Blink aktive Kamera / Bild / Zeit // Version: 1.2.0 // =============================== const VERSION = '1.2.0'; const aktiveKamera = '0_userdata.0.Blink.aktive_Kamera'; const aktivesBild = '0_userdata.0.Blink.aktives_Bild'; const aktiveZeit = '0_userdata.0.Blink.aktive_Zeit'; // Gemeinsame Blink-Sperre für alle Blink-Scripts const blinkBusyUntil = '0_userdata.0.Blink.busy_until'; const blinkBusyInfo = '0_userdata.0.Blink.busy_info'; const kamera1 = 'blink.0.cameras.706479.live.image_base64'; const kamera2 = 'blink.0.cameras.1327710.live.image_base64'; const zeit1 = 'blink.0.cameras.706479.live.timestamp'; const zeit2 = 'blink.0.cameras.1327710.live.timestamp'; const snapshot1 = 'blink.0.cameras.706479.commands.snapshot'; const snapshot2 = 'blink.0.cameras.1327710.commands.snapshot'; const DEFAULT_KAMERA = '706479'; // Blink ist empfindlich bei zu vielen Befehlen const BLINK_SPERRE_MS = 90000; // 90 Sekunden Pause nach Snapshot const SNAPSHOT_TIMEOUT_MS = 30000; // 30 Sekunden auf neuen Timestamp warten const DEBOUNCE_MS = 1500; // Kamerawechsel entprellen let letzterTimestamp706479 = ''; let letzterTimestamp1327710 = ''; let snapshotLaeuft = false; let pendingSnapshot = false; let debounceTimer = null; let waitTimer = null; let pollTimer = null; function ensureState(id, def, common) { if (!existsState(id)) { createState(id, def, common); } } ensureState(aktiveKamera, DEFAULT_KAMERA, { type: 'string', role: 'text', name: 'Aktive Blink Kamera', read: true, write: true }); ensureState(aktivesBild, '', { type: 'string', role: 'text', name: 'Aktives Blink Bild', read: true, write: true }); ensureState(aktiveZeit, '', { type: 'string', role: 'text', name: 'Aktive Blink Zeit', read: true, write: true }); ensureState(blinkBusyUntil, 0, { type: 'number', role: 'value', name: 'Blink busy until timestamp', read: true, write: true }); ensureState(blinkBusyInfo, '', { type: 'string', role: 'text', name: 'Blink busy info', read: true, write: true }); function getBusyUntil() { const state = getState(blinkBusyUntil); if (!state || state.val === null || state.val === undefined) { return 0; } const value = Number(state.val); return isNaN(value) ? 0 : value; } function istBlinkBusy() { return Date.now() < getBusyUntil(); } function getRestzeitMs() { return Math.max(0, getBusyUntil() - Date.now()); } function setBlinkBusy(ms, info) { const until = Date.now() + ms; setState(blinkBusyUntil, until, true); setState(blinkBusyInfo, info, true); log(`[v${VERSION}] Blink gesperrt für ${Math.ceil(ms / 1000)}s: ${info}`, 'info'); } function getKameraWert() { const state = getState(aktiveKamera); if (!state || state.val === null || state.val === undefined || state.val === '') { return DEFAULT_KAMERA; } const cam = String(state.val).replace(/"/g, ''); if (cam !== '706479' && cam !== '1327710') { log(`[v${VERSION}] Blink: Ungültige Kamera "${cam}", verwende ${DEFAULT_KAMERA}`, 'warn'); return DEFAULT_KAMERA; } return cam; } function getBildQuelle(cam) { return cam === '1327710' ? kamera2 : kamera1; } function getZeitQuelle(cam) { return cam === '1327710' ? zeit2 : zeit1; } function getSnapshotQuelle(cam) { return cam === '1327710' ? snapshot2 : snapshot1; } function getLetztenTimestamp(cam) { return cam === '1327710' ? letzterTimestamp1327710 : letzterTimestamp706479; } function setLetztenTimestamp(cam, value) { if (cam === '1327710') { letzterTimestamp1327710 = value; } else { letzterTimestamp706479 = value; } } function formatZeitBerlin(timestamp) { const datum = new Date(timestamp); if (isNaN(datum.getTime())) { return String(timestamp); } return datum.toLocaleString('de-DE', { timeZone: 'Europe/Berlin', day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }).replace(',', ''); } function updateBild(cam) { const quelle = getBildQuelle(cam); const bildState = getState(quelle); if (!bildState || !bildState.val) { log(`[v${VERSION}] Blink: Kein Bild vorhanden von ${quelle}`, 'warn'); return false; } if (getKameraWert() === cam) { setState(aktivesBild, String(bildState.val), true); } return true; } function updateZeit(cam) { const quelle = getZeitQuelle(cam); const zeitState = getState(quelle); if (!zeitState || !zeitState.val) { log(`[v${VERSION}] Blink: Keine Zeit vorhanden von ${quelle}`, 'warn'); return false; } const formatiert = formatZeitBerlin(zeitState.val); if (getKameraWert() === cam) { setState(aktiveZeit, formatiert, true); } return true; } function updateAlles(cam) { cam = cam || getKameraWert(); updateBild(cam); updateZeit(cam); } function snapshotAusloesen(cam) { const snapshot = getSnapshotQuelle(cam); log(`[v${VERSION}] Blink: Snapshot auslösen: ${cam}`, 'info'); // Befehl aktiv auslösen setState(snapshot, true, false); } function planeSnapshotNachSperre() { if (waitTimer) { clearTimeout(waitTimer); } const wait = getRestzeitMs() + 1000; log(`[v${VERSION}] Blink ist busy. Snapshot wird in ${Math.ceil(wait / 1000)}s erneut versucht.`, 'warn'); waitTimer = setTimeout(function () { waitTimer = null; if (pendingSnapshot && !snapshotLaeuft) { pendingSnapshot = false; updateNachSnapshot(); } }, wait); } function updateNachSnapshot() { const cam = getKameraWert(); if (snapshotLaeuft) { pendingSnapshot = true; log(`[v${VERSION}] Blink: Snapshot läuft bereits, neuer Wunsch wird gemerkt: ${cam}`, 'warn'); return; } if (istBlinkBusy()) { pendingSnapshot = true; planeSnapshotNachSperre(); return; } snapshotLaeuft = true; pendingSnapshot = false; const zeitQuelle = getZeitQuelle(cam); const alterTimestampState = getState(zeitQuelle); const alterTimestamp = alterTimestampState && alterTimestampState.val ? String(alterTimestampState.val) : ''; setLetztenTimestamp(cam, alterTimestamp); setBlinkBusy(BLINK_SPERRE_MS, `Snapshot Kamera ${cam}`); snapshotAusloesen(cam); let vergangen = 0; if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } pollTimer = setInterval(function () { vergangen += 1000; const aktuelleCam = getKameraWert(); const neuerTimestampState = getState(zeitQuelle); const neuerTimestamp = neuerTimestampState && neuerTimestampState.val ? String(neuerTimestampState.val) : ''; if (neuerTimestamp && neuerTimestamp !== getLetztenTimestamp(cam)) { setLetztenTimestamp(cam, neuerTimestamp); if (aktuelleCam === cam) { updateAlles(cam); } clearInterval(pollTimer); pollTimer = null; snapshotLaeuft = false; log(`[v${VERSION}] Blink: Snapshot erfolgreich aktualisiert: ${cam}`, 'info'); if (pendingSnapshot) { planeSnapshotNachSperre(); } return; } if (vergangen >= SNAPSHOT_TIMEOUT_MS) { if (aktuelleCam === cam) { updateAlles(cam); } clearInterval(pollTimer); pollTimer = null; snapshotLaeuft = false; log(`[v${VERSION}] Blink: Snapshot hat nach ${SNAPSHOT_TIMEOUT_MS / 1000}s keinen neuen Timestamp geliefert: ${cam}`, 'warn'); if (pendingSnapshot) { planeSnapshotNachSperre(); } } }, 1000); } function kameraWechselEntprellt() { if (debounceTimer) { clearTimeout(debounceTimer); } debounceTimer = setTimeout(function () { debounceTimer = null; updateNachSnapshot(); }, DEBOUNCE_MS); } // Nur bei echter Änderung reagieren on({ id: aktiveKamera, change: 'ne' }, function () { kameraWechselEntprellt(); }); on({ id: kamera1, change: 'ne' }, function () { if (getKameraWert() === '706479') { updateBild('706479'); } }); on({ id: kamera2, change: 'ne' }, function () { if (getKameraWert() === '1327710') { updateBild('1327710'); } }); on({ id: zeit1, change: 'ne' }, function () { if (getKameraWert() === '706479') { updateZeit('706479'); } }); on({ id: zeit2, change: 'ne' }, function () { if (getKameraWert() === '1327710') { updateZeit('1327710'); } }); log(`[v${VERSION}] Blink aktive Kamera Script gestartet`, 'info'); setTimeout(function () { updateAlles(); }, 1000);
Habe an der Terrassentür ein Türkontakt wenn die Tür auf ist nimmt die Kamera im Garten nicht auf das gleiche ist auch bei der Haustür.
Intel NUC mit Iobroker
-
Ecovacs Deebot interaktive Karte:

Habe eine Skript entwickelt das die Karte einsetzt und die Fahrt des Saugers mit aufzeichnet. Man kann auch über die Karte die Räume auswählen.Für das Widget der Karte muss man folgenden Wert eintragen:
<!-- LUNA VIS HTML v1.1 --> <div style="position:relative;width:540px;height:231px;overflow:hidden;background:none;border:none;"> <img src="http://192.168.178.59:8082/files/ecovacs-deebot.2/lastestCleaningMapImage.png" style="position:absolute;left:0;top:0;width:540px;height:231px;z-index:1;" > <svg viewBox="0 0 540 231" style="position:absolute;left:0;top:0;width:540px;height:231px;z-index:2;pointer-events:auto;"> <polyline points="{0_userdata.0.Luna.Map.trailPoints}" fill="none" stroke="#ff3b30" stroke-width="2" opacity="0.8" style="pointer-events:none;" /> <polygon fill="orange" fill-opacity="{0_userdata.0.Luna.Map.room0Opacity}" style="cursor:pointer;" onclick=" const mapId = vis.states['0_userdata.0.Luna.Map.mapId.val'] || '1245233875'; const id = 'ecovacs-deebot.2.map.' + mapId + '.spotAreas.0.markForNextSpotAreaCleaning'; vis.setValue(id, !(vis.states[id + '.val'])); " points="0,231 2.5,196.9 7.4,192.1 2.5,187.2 2.5,119.2 4.9,116.7 2.5,68.1 7.4,60.8 7.4,4.9 22.2,4.9 24.7,26.8 34.6,34 44.4,34 44.4,114.3 51.8,121.6 140.7,121.6 145.5,116.7 145.5,36.5 162.9,34 170.3,24.3 177.7,21.9 180.1,36.5 187.5,43.8 187.5,97.2 180.1,104.5 180.1,116.7 185.1,121.6 185.1,153.2 180.1,153.2 170.3,162.9 155.4,170.2 165.3,179.9 177.7,170.2 177.7,175 170.3,179.9 177.7,187.2 175.2,206.6 172.8,204.2 19.7,204.2 14.8,209.1 7.4,231 0,231" /> <polygon fill="lime" fill-opacity="{0_userdata.0.Luna.Map.room1Opacity}" style="cursor:pointer;" onclick=" const mapId = vis.states['0_userdata.0.Luna.Map.mapId.val'] || '1245233875'; const id = 'ecovacs-deebot.2.map.' + mapId + '.spotAreas.1.markForNextSpotAreaCleaning'; vis.setValue(id, !(vis.states[id + '.val'])); " points="190.0,153.2 190.0,121.6 197.5,116.7 271.4,116.7 278.9,99.6 293.7,104.5 308.5,104.5 313.4,111.8 313.4,126.5 323.3,131.3 318.4,153.2 311.0,158.1 313.4,221.9 308.5,231 202.4,231 202.4,175 214.7,187.2 219.7,187.2 246.8,175 254.2,162.9 246.8,155.6 244.3,158.1 209.8,155.6 204.9,160.5 192.5,153.2 190.0,153.2" /> <polygon fill="violet" fill-opacity="{0_userdata.0.Luna.Map.room2Opacity}" style="cursor:pointer;" onclick=" const mapId = vis.states['0_userdata.0.Luna.Map.mapId.val'] || '1245233875'; const id = 'ecovacs-deebot.2.map.' + mapId + '.spotAreas.2.markForNextSpotAreaCleaning'; vis.setValue(id, !(vis.states[id + '.val'])); " points="273.8,63.2 276.3,7.3 288.7,9.7 293.7,26.8 362.9,26.8 370.3,19.5 372.8,7.3 407.3,7.3 414.7,14.6 409.7,19.5 409.7,34 417.2,41.3 429.5,41.3 439.4,55.9 540,55.9 537.5,99.6 443.8,99.6 433.9,87.5 429.5,92.4 417.2,87.5 407.3,99.6 375.2,99.6 367.8,85.1 360.4,89.9 325.8,85.1 313.4,99.6 293.7,99.6 281.3,97.2 283.8,63.2 278.9,58.3 273.8,63.2" /> <polygon fill="cyan" fill-opacity="{0_userdata.0.Luna.Map.room3Opacity}" style="cursor:pointer;" onclick=" const mapId = vis.states['0_userdata.0.Luna.Map.mapId.val'] || '1245233875'; const id = 'ecovacs-deebot.2.map.' + mapId + '.spotAreas.3.markForNextSpotAreaCleaning'; vis.setValue(id, !(vis.states[id + '.val'])); " points="323.3,155.6 328.2,133.7 362.9,133.7 370.3,119.2 375.2,116.7 464.1,116.7 469.0,121.6 476.4,116.7 540,116.7 542.5,119.2 542.5,143.5 513.4,148.3 513.4,187.2 518.3,192.1 542.5,192.1 540,201.8 488.7,201.8 483.8,196.9 466.5,196.9 461.5,201.8 439.4,201.8 432.0,209.1 434.4,221.2 427.0,226.1 422.1,226.1 422.1,218.8 414.7,211.5 404.8,218.8 404.8,226.1 390.0,226.1 382.6,221.2 382.6,189.6 375.2,182.3 335.6,182.3 333.2,155.6 323.3,155.6" /> </svg> <div style=" position:absolute; left:{0_userdata.0.Luna.Map.pixelXcss}; top:{0_userdata.0.Luna.Map.pixelYcss}; transform:{0_userdata.0.Luna.Map.rotateCss}; width:18px;height:18px; z-index:9999; pointer-events:none; "> <img src="https://api-app.dc-eu.ww.ecouser.net/api/pim/file/get/5ba4a2cb6c2f120001c32839" style="width:18px;height:18px;"> </div> <span style="display:none;"> {0_userdata.0.Luna.Map.mapId} {0_userdata.0.Luna.Map.pixelXcss} {0_userdata.0.Luna.Map.pixelYcss} {0_userdata.0.Luna.Map.rotateCss} {0_userdata.0.Luna.Map.trailPoints} {0_userdata.0.Luna.Map.room0Opacity} {0_userdata.0.Luna.Map.room1Opacity} {0_userdata.0.Luna.Map.room2Opacity} {0_userdata.0.Luna.Map.room3Opacity} </span> </div>Demensprechend muss man seine Daten ersetzen sowie beim Hauptskript auch:
/************************************************* * SAUGROBOTER-STEUERUNG LUNA * Version: 1.2.1 * Automatik + Alexa + VIS + Telegram + Karte/Trail * Ohne zusätzliche ioBroker-Logs *************************************************/ const VERSION = '1.2.1'; const ROBOT_NAME = 'Luna'; const TELEGRAM_INSTANCE = 'telegram.0'; const TELEGRAM_USER_ID = '1365513925'; const TELEGRAM_REQUEST = `${TELEGRAM_INSTANCE}.communicate.request`; const STATE_KERSTIN = 'fb-checkpresence.0.Kerstin-Handy.presence'; const STATE_THOMAS = 'fb-checkpresence.0.Thomas-IPhone.presence'; const STATE_FEIERTAG = 'feiertage.0.heute.boolean'; const STATE_BASE = '0_userdata.0.Luna'; const STATE_SAUGEN_FREIGABE = `${STATE_BASE}.saugen`; const STATE_STATUS_TEXT = `${STATE_BASE}.statusText`; const STATE_LAST_RUN = `${STATE_BASE}.lastRun`; const STATE_LAST_DECISION = `${STATE_BASE}.lastDecision`; const STATE_MANUAL_START = `${STATE_BASE}.manualStart`; const STATE_MANUAL_RESET = `${STATE_BASE}.manualReset`; const STATE_POSITION_TEXT = `${STATE_BASE}.Position`; const STATE_WAS_SAUGEN = `${STATE_BASE}.Was_saugen`; const STATE_LAST_HOLIDAY_NOTIFY = `${STATE_BASE}.lastHolidayNotification`; const STATE_LAST_CLEANED_ROOMS = `${STATE_BASE}.lastCleanedRooms`; const ALEXA_STATE = 'alexa2.0.History.summary'; const ROBOT_LUNA = 'luna'; const ROBOT_SKY = 'sky'; const DEEBOT_PREFIX = 'ecovacs-deebot.2'; const DEEBOT_AREA = `${DEEBOT_PREFIX}.control.spotArea`; const DEEBOT_CHARGE = `${DEEBOT_PREFIX}.info.chargestatus`; const DEEBOT_POSITION = `${DEEBOT_PREFIX}.map.deebotPositionCurrentSpotAreaName`; const DEFAULT_MAP_ID = '1245233875'; const DEEBOT_POS_STATE = `${DEEBOT_PREFIX}.map.deebotPosition`; const DEEBOT_STATUS_STATE = `${DEEBOT_PREFIX}.info.deviceStatus`; const MAP_BASE = `${STATE_BASE}.Map`; const STATE_MAP_ID = `${MAP_BASE}.mapId`; const STATE_PIXEL_X = `${MAP_BASE}.pixelXcss`; const STATE_PIXEL_Y = `${MAP_BASE}.pixelYcss`; const STATE_ROTATE = `${MAP_BASE}.rotateCss`; const STATE_TRAIL = `${MAP_BASE}.trailPoints`; const minX = -250; const maxX = 10700; const minY = -4350; const maxY = 400; const imgW = 540; const imgH = 231; const CRON_ENABLE = '17 19 * * *'; const CRON_CHECK = '*/5 9-12 * * 1-5'; const CRON_RESET_DAILY = '0 0 * * *'; const RESET_AFTER_CHARGING_MS = 10000; const DEBOUNCE_MS = 10000; const SETTINGS = { SEND_TELEGRAM: true, SKIP_ON_HOLIDAY: true }; const DAYS = { MONDAY: 1, WEDNESDAY: 3, FRIDAY: 5 }; const ROOMS = [ { id: 0, name: 'Schlafzimmer', mapLetter: 'A', roomState: `${STATE_BASE}.Schlafzimmer`, spotAlias: 'alias.0.Luna.Spot0.ACTUAL', markAlias: 'alias.0.Luna.Mark0.ACTUAL', aliases: ['schlafzimmer'] }, { id: 1, name: 'Flur', mapLetter: 'B', roomState: `${STATE_BASE}.Flur`, spotAlias: 'alias.0.Luna.Spot1.ACTUAL', markAlias: 'alias.0.Luna.Mark1.ACTUAL', aliases: ['flur'] }, { id: 2, name: 'Kinderzimmer', mapLetter: 'C', roomState: `${STATE_BASE}.Kinderzimmer`, spotAlias: 'alias.0.Luna.Spot2.ACTUAL', markAlias: 'alias.0.Luna.Mark2.ACTUAL', aliases: ['kinderzimmer'] }, { id: 3, name: 'Badezimmer', mapLetter: 'D', roomState: `${STATE_BASE}.Badezimmer`, spotAlias: 'alias.0.Luna.Spot3.ACTUAL', markAlias: 'alias.0.Luna.Mark3.ACTUAL', aliases: ['badezimmer', 'bad'] } ]; const ALL_ALIASES = ['obergeschoss', 'oben', 'alles']; const CANCEL_ALIASES = [ 'stopp', 'stop', 'abbrechen', 'abbruch', 'nichts', 'doch nicht' ]; const ENABLE_ALIASES = [ 'freigeben', 'freigabe', 'darf saugen', 'saugen erlauben', 'automatik an', 'automatik aktivieren' ]; const DISABLE_ALIASES = [ 'nicht saugen', 'freigabe aus', 'freigabe deaktivieren', 'saugen verbieten', 'automatik aus', 'automatik deaktivieren' ]; const AUTOMATIC_ROOM_ID = 0; const AUTOMATIC_DAYS = [DAYS.MONDAY, DAYS.WEDNESDAY, DAYS.FRIDAY]; let lastActionText = ''; let lastActionTime = 0; let lastSpotArea = ''; let resetTimer = null; let trail = []; let wasCleaning = false; let chargingNotified = false; function notify(text) { if (!SETTINGS.SEND_TELEGRAM) return; sendTo(TELEGRAM_INSTANCE, 'send', { chatId: TELEGRAM_USER_ID, text: `${ROBOT_NAME}: ${text}` }); } function safeExistsState(id) { try { return existsState(id); } catch (e) { return false; } } function getBoolState(id, fallback = false) { try { if (!safeExistsState(id)) return fallback; const s = getState(id); if (!s || s.val === null || s.val === undefined) return fallback; return !!s.val; } catch (e) { return fallback; } } function getStringState(id, fallback = '') { try { if (!safeExistsState(id)) return fallback; const s = getState(id); if (!s || s.val === null || s.val === undefined) return fallback; return String(s.val); } catch (e) { return fallback; } } function normalize(text) { return String(text || '') .toLowerCase() .normalize('NFD').replace(/[\u0300-\u036f]/g, '') .replace(/[^\w\s]/g, ' ') .replace(/\s+/g, ' ') .trim(); } function includesAlias(text, aliases) { return aliases.some(alias => text.includes(normalize(alias))); } function getTimestamp() { return formatDate(new Date(), 'YYYY-MM-DD hh:mm:ss'); } function setStatus(text) { setState(STATE_STATUS_TEXT, text, true); } function setDecision(text) { setState(STATE_LAST_DECISION, text, true); } function logAction(text) { const now = Date.now(); if (text === lastActionText && now - lastActionTime < DEBOUNCE_MS) { return; } lastActionText = text; lastActionTime = now; setState(STATE_WAS_SAUGEN, text, true); setStatus(text); notify(text); } function isSomeoneHome() { return getBoolState(STATE_KERSTIN) || getBoolState(STATE_THOMAS); } function isCleaningEnabled() { return getBoolState(STATE_SAUGEN_FREIGABE); } function setCleaningEnabled(value) { setState(STATE_SAUGEN_FREIGABE, value, true); } function isHoliday() { return getBoolState(STATE_FEIERTAG, false); } function getWeekday() { return new Date().getDay(); } function isSameDay(ts1, ts2) { if (!ts1 || !ts2) return false; return new Date(ts1).toDateString() === new Date(ts2).toDateString(); } function sendHolidayNotificationOncePerDay(message) { const now = new Date(); const lastNotify = getStringState(STATE_LAST_HOLIDAY_NOTIFY, ''); if (!isSameDay(now, lastNotify)) { notify(message); setState(STATE_LAST_HOLIDAY_NOTIFY, now.toISOString(), true); } } function getRoomById(id) { return ROOMS.find(room => String(room.id) === String(id)); } function getRoomByLetter(letter) { return ROOMS.find(room => room.mapLetter === String(letter)); } function getRoomNamesBySpotArea(spotArea) { if (spotArea === '0,1,2,3') { return ROOMS.map(room => room.name); } const room = getRoomById(spotArea); return room ? [room.name] : []; } function roomListText(roomNames) { if (!roomNames || roomNames.length === 0) return 'keine Räume'; return roomNames.join(', '); } function clearTimer(timer) { if (timer) clearTimeout(timer); } function getCurrentMapId() { return getStringState(STATE_MAP_ID, DEFAULT_MAP_ID); } function getMapMarkState(roomId) { return `${DEEBOT_PREFIX}.map.${getCurrentMapId()}.spotAreas.${roomId}.markForNextSpotAreaCleaning`; } function clearAllMarks() { for (const room of ROOMS) { setState(room.markAlias, false); } for (const room of ROOMS) { const mapMark = getMapMarkState(room.id); if (safeExistsState(mapMark)) { setState(mapMark, false); } } } function resetRoomStates() { for (const room of ROOMS) { setState(room.roomState, false, true); } } function resetSelection(reason) { clearAllMarks(); resetRoomStates(); setState(DEEBOT_AREA, ' ', true); lastSpotArea = ''; setDecision(`Auswahl zurückgesetzt: ${reason}`); } function saveCleanedRooms(roomNames) { const text = roomListText(roomNames); setState(STATE_LAST_CLEANED_ROOMS, text, true); setState(STATE_WAS_SAUGEN, text, true); } function startRoomCleaning(room, source) { if (!room) return; chargingNotified = false; setState(room.spotAlias, true); const cleanedRooms = [room.name]; saveCleanedRooms(cleanedRooms); setState(STATE_LAST_RUN, getTimestamp(), true); setDecision(`${source}: ${room.name} gestartet`); logAction(`${source}: ${room.name} wird gesaugt`); } function startAllCleaning(source) { chargingNotified = false; setState(DEEBOT_AREA, '0,1,2,3'); const cleanedRooms = ROOMS.map(room => room.name); saveCleanedRooms(cleanedRooms); setState(STATE_LAST_RUN, getTimestamp(), true); setDecision(`${source}: Obergeschoss gestartet`); logAction(`${source}: Obergeschoss wird gesaugt: ${roomListText(cleanedRooms)}`); } /************************************************* * TELEGRAM STATUS *************************************************/ function getCleaningStateText() { const deviceStatus = getStringState(DEEBOT_STATUS_STATE, '').toLowerCase(); const chargeStatus = getStringState(DEEBOT_CHARGE, '').toLowerCase(); const isCleaning = deviceStatus.includes('clean') || deviceStatus.includes('spot') || deviceStatus.includes('auto') || deviceStatus.includes('edge'); if (isCleaning) return 'Ja'; if (chargeStatus === 'charging') return 'Nein, Luna steht auf der Ladestation'; return 'Unbekannt'; } function sendLunaStatus() { const saugtWas = getStringState(STATE_WAS_SAUGEN, 'nichts bekannt'); const lastCleaned = getStringState(STATE_LAST_CLEANED_ROOMS, 'nichts bekannt'); const freigabe = isCleaningEnabled() ? 'Ja' : 'Nein'; const position = getStringState(STATE_POSITION_TEXT, 'Unbekannt'); const statusText = getStringState(STATE_STATUS_TEXT, '-'); const letzteEntscheidung = getStringState(STATE_LAST_DECISION, '-'); const lastRun = getStringState(STATE_LAST_RUN, '-'); const mapId = getCurrentMapId(); const saugtGerade = getCleaningStateText(); notify( `Status:\n` + `Saugt gerade: ${saugtGerade}\n` + `Was: ${saugtWas}\n` + `Zuletzt gereinigte Räume: ${lastCleaned}\n` + `Freigabe nächster Automatiklauf: ${freigabe}\n` + `Position: ${position}\n` + `Letzter Lauf: ${lastRun}\n` + `Status VIS: ${statusText}\n` + `Letzte Entscheidung: ${letzteEntscheidung}\n` + `Map-ID: ${mapId}` ); } on({ id: TELEGRAM_REQUEST, change: 'any' }, obj => { if (!obj || !obj.state || !obj.state.val) return; let raw = String(obj.state.val); raw = raw.replace(/^\[[^\]]+\]/, '').trim(); const text = normalize(raw); const isLunaStatusRequest = text.includes('was macht luna') || text.includes('luna status') || text.includes('status luna') || text.includes('luna freigabe') || text.includes('luna saugt') || text.includes('luna info') || text === 'luna'; if (!isLunaStatusRequest) { return; } sendLunaStatus(); }); /************************************************* * VIS KARTE / POSITION / TRAIL *************************************************/ function toPixel(x, y) { return { x: ((x - minX) / (maxX - minX)) * imgW, y: imgH - ((y - minY) / (maxY - minY)) * imgH }; } function parseBotPosition(value) { if (!value) return null; const p = String(value).split(',').map(Number); if (p.length < 3 || p.some(isNaN)) return null; return { x: p[0], y: p[1], angle: p[2] }; } function updateBotOnMap(value) { const pos = parseBotPosition(value); if (!pos) return; const pixel = toPixel(pos.x, pos.y); setState(STATE_PIXEL_X, pixel.x.toFixed(1) + 'px', true); setState(STATE_PIXEL_Y, pixel.y.toFixed(1) + 'px', true); setState(STATE_ROTATE, `translate(-50%,-50%) rotate(${pos.angle}deg)`, true); const last = trail[trail.length - 1]; if (!last || Math.abs(last.x - pixel.x) > 1 || Math.abs(last.y - pixel.y) > 1) { trail.push(pixel); if (trail.length > 500) { trail.shift(); } const points = trail .map(pt => `${pt.x.toFixed(1)},${pt.y.toFixed(1)}`) .join(' '); setState(STATE_TRAIL, points, true); } } function resetTrail() { trail = []; setState(STATE_TRAIL, '', true); } function checkResetTrailByStatus(status) { if (!status) return; const s = String(status).toLowerCase(); const isCleaning = s.includes('clean') || s.includes('spot') || s.includes('auto') || s.includes('edge'); if (isCleaning && !wasCleaning) { resetTrail(); wasCleaning = true; chargingNotified = false; } const isFinished = s.includes('charging') || s.includes('charge') || s.includes('dock') || s.includes('idle') || s.includes('stop') || s.includes('finish') || s.includes('complete') || s.includes('standby'); if (isFinished) { wasCleaning = false; } } function updateRoomOpacity(roomNumber, value) { const selected = value === true || value === 'true' || value === 1 || value === '1'; const opacity = selected ? 0.75 : 0.25; setState(`${MAP_BASE}.room${roomNumber}Opacity`, opacity, true); } /************************************************* * STATES AUTOMATISCH ANLEGEN *************************************************/ function createUserStates() { createState(STATE_SAUGEN_FREIGABE, false, { name: 'Saugfreigabe für nächsten Reinigungslauf', type: 'boolean', role: 'switch.enable', read: true, write: true, def: false }); createState(STATE_STATUS_TEXT, '', { name: 'Statusanzeige Luna', type: 'string', role: 'text', read: true, write: false, def: '' }); createState(STATE_LAST_RUN, '', { name: 'Letzter Luna Reinigungslauf', type: 'string', role: 'text', read: true, write: false, def: '' }); createState(STATE_LAST_DECISION, '', { name: 'Letzte Luna Entscheidung', type: 'string', role: 'text', read: true, write: false, def: '' }); createState(STATE_MANUAL_START, false, { name: 'Manueller Luna Start per VIS', type: 'boolean', role: 'button', read: true, write: true, def: false }); createState(STATE_MANUAL_RESET, false, { name: 'Manueller Luna Reset', type: 'boolean', role: 'button', read: true, write: true, def: false }); createState(STATE_POSITION_TEXT, '', { name: 'Aktuelle Luna Position', type: 'string', role: 'text', read: true, write: false, def: '' }); createState(STATE_WAS_SAUGEN, '', { name: 'Was saugt Luna', type: 'string', role: 'text', read: true, write: false, def: '' }); createState(STATE_LAST_HOLIDAY_NOTIFY, '', { name: 'Letzte Feiertags-Benachrichtigung Luna', type: 'string', role: 'text', read: true, write: false, def: '' }); createState(STATE_LAST_CLEANED_ROOMS, '', { name: 'Zuletzt gesaugte Räume Luna', type: 'string', role: 'text', read: true, write: false, def: '' }); for (const room of ROOMS) { createState(room.roomState, false, { name: `Luna Raum aktiv: ${room.name}`, type: 'boolean', role: 'indicator', read: true, write: false, def: false }); } createState(STATE_MAP_ID, DEFAULT_MAP_ID, { name: 'Luna aktuelle Map ID', type: 'string', role: 'text', read: true, write: true, def: DEFAULT_MAP_ID }); createState(STATE_PIXEL_X, '0px', { name: 'Luna Karte X Position CSS', type: 'string', role: 'text', read: true, write: true, def: '0px' }); createState(STATE_PIXEL_Y, '0px', { name: 'Luna Karte Y Position CSS', type: 'string', role: 'text', read: true, write: true, def: '0px' }); createState(STATE_ROTATE, 'translate(-50%,-50%) rotate(0deg)', { name: 'Luna Karte Rotation CSS', type: 'string', role: 'text', read: true, write: true, def: 'translate(-50%,-50%) rotate(0deg)' }); createState(STATE_TRAIL, '', { name: 'Luna Karte Fahrspur Punkte', type: 'string', role: 'text', read: true, write: true, def: '' }); for (const room of ROOMS) { createState(`${MAP_BASE}.room${room.id}Opacity`, 0.25, { name: `Luna Karte Raum ${room.id} Deckkraft`, type: 'number', role: 'value', read: true, write: true, def: 0.25 }); } } /************************************************* * AUTOMATIK *************************************************/ function shouldCleanNow() { if (!isCleaningEnabled()) { setDecision('Abbruch: Freigabe ist false'); return false; } if (!AUTOMATIC_DAYS.includes(getWeekday())) { setDecision('Heute ist kein Luna Reinigungstag'); return false; } if (SETTINGS.SKIP_ON_HOLIDAY && isHoliday()) { const room = getRoomById(AUTOMATIC_ROOM_ID); const roomsText = room ? room.name : 'Automatikraum'; const msg = `Heute keine Reinigung wegen Feiertag. Geplant gewesen wäre: ${roomsText}.`; setStatus(msg); setDecision('Abbruch: Feiertag'); sendHolidayNotificationOncePerDay(msg); return false; } if (isSomeoneHome()) { setDecision('Abbruch: jemand ist zuhause'); return false; } return true; } function runAutomaticCleaning() { if (!shouldCleanNow()) return; const room = getRoomById(AUTOMATIC_ROOM_ID); if (!room) { setDecision('Abbruch: Automatikraum nicht gefunden'); return; } startRoomCleaning(room, 'Automatik'); setCleaningEnabled(false); } function runManualCleaning() { const room = getRoomById(AUTOMATIC_ROOM_ID); startRoomCleaning(room, 'Manueller Start'); } /************************************************* * ALEXA *************************************************/ on({ id: ALEXA_STATE, change: 'any' }, obj => { const raw = obj.state.val; const spoken = normalize(raw); if (!spoken) return; if (spoken.includes(ROBOT_SKY)) { setDecision(`Sky-Befehl ignoriert: ${raw}`); return; } if (!spoken.includes(ROBOT_LUNA)) { return; } if (includesAlias(spoken, ENABLE_ALIASES)) { setCleaningEnabled(true); setStatus('Freigabe per Alexa aktiviert'); setDecision('Freigabe per Alexa gesetzt'); notify('Freigabe wurde per Alexa aktiviert'); return; } if (includesAlias(spoken, DISABLE_ALIASES)) { setCleaningEnabled(false); setStatus('Freigabe per Alexa deaktiviert'); setDecision('Freigabe per Alexa deaktiviert'); notify('Freigabe wurde per Alexa deaktiviert'); return; } if (includesAlias(spoken, CANCEL_ALIASES)) { resetSelection('Abbruch per Sprache'); notify('Auswahl abgebrochen'); return; } if (includesAlias(spoken, ALL_ALIASES)) { startAllCleaning('Alexa'); return; } let matchedRoom = null; for (const room of ROOMS) { if (includesAlias(spoken, room.aliases)) { matchedRoom = room; break; } } if (!matchedRoom) { setDecision(`Luna-Befehl ohne bekannten Raum: ${raw}`); return; } startRoomCleaning(matchedRoom, 'Alexa'); }); /************************************************* * LADESTATION *************************************************/ on({ id: DEEBOT_CHARGE, change: 'ne' }, obj => { if (!obj || !obj.state) return; const val = String(obj.state.val || '').toLowerCase(); if (val !== 'charging') { chargingNotified = false; return; } if (chargingNotified) return; chargingNotified = true; clearTimer(resetTimer); resetTimer = setTimeout(() => { resetSelection('Luna lädt'); logAction('steht auf der Ladestation. Auswahl wurde zurückgesetzt.'); resetTimer = null; }, RESET_AFTER_CHARGING_MS); }); /************************************************* * LISTENER *************************************************/ function updateVisPosition(value) { const room = getRoomByLetter(value); const position = room ? room.name : 'Unbekannt'; setState(STATE_POSITION_TEXT, position, true); } on({ id: DEEBOT_POSITION, change: 'any' }, obj => { updateVisPosition(obj.state.val); }); on({ id: DEEBOT_POS_STATE, change: 'any' }, obj => { updateBotOnMap(obj.state.val); }); on({ id: DEEBOT_STATUS_STATE, change: 'any' }, obj => { checkResetTrailByStatus(obj.state.val); }); for (const room of ROOMS) { on({ id: getMapMarkState(room.id), change: 'any' }, obj => { updateRoomOpacity(room.id, obj.state.val); }); } on({ id: STATE_MAP_ID, change: 'ne' }, obj => { setDecision(`Map-ID geändert auf ${obj.state.val}`); for (const room of ROOMS) { const mapMark = getMapMarkState(room.id); updateRoomOpacity(room.id, safeExistsState(mapMark) ? getState(mapMark)?.val : false); } }); on({ id: [ DEEBOT_AREA, ...ROOMS.map(room => room.markAlias) ], change: 'ne' }, () => { const spotArea = getStringState(DEEBOT_AREA); if (spotArea === lastSpotArea) { return; } lastSpotArea = spotArea; resetRoomStates(); const roomNames = getRoomNamesBySpotArea(spotArea); if (spotArea === '0,1,2,3') { for (const room of ROOMS) { setState(room.roomState, true, true); } saveCleanedRooms(roomNames); setStatus(`saugt das Obergeschoss: ${roomListText(roomNames)}`); return; } const room = getRoomById(spotArea); if (room) { setState(room.roomState, true, true); saveCleanedRooms([room.name]); setStatus(`saugt ${room.name}`); } }); /************************************************* * INITIALISIERUNG *************************************************/ createUserStates(); setTimeout(() => { setStatus(`Luna Skript gestartet | Version ${VERSION}`); setDecision(`Initialisierung abgeschlossen | Version ${VERSION}`); const currentPosition = getState(DEEBOT_POSITION); if (currentPosition) { updateVisPosition(currentPosition.val); } const currentBotPosition = getState(DEEBOT_POS_STATE); if (currentBotPosition && currentBotPosition.val) { updateBotOnMap(currentBotPosition.val); } const currentDeviceStatus = getState(DEEBOT_STATUS_STATE); if (currentDeviceStatus && currentDeviceStatus.val) { checkResetTrailByStatus(currentDeviceStatus.val); } for (const room of ROOMS) { const mapMark = getMapMarkState(room.id); updateRoomOpacity(room.id, safeExistsState(mapMark) ? getState(mapMark)?.val : false); } }, 2000); /************************************************* * ZEITSTEUERUNG *************************************************/ schedule(CRON_ENABLE, () => { setCleaningEnabled(true); setStatus('Freigabe für nächsten Luna Reinigungstag gesetzt'); setDecision('19:17-Freigabe aktiviert'); notify('tägliche Freigabe wurde aktiviert. Luna darf beim nächsten Automatiklauf saugen.'); }); schedule(CRON_CHECK, () => { runAutomaticCleaning(); }); schedule(CRON_RESET_DAILY, () => { setState(STATE_LAST_HOLIDAY_NOTIFY, '', true); setDecision('Täglicher Luna Reset abgeschlossen'); }); /************************************************* * VIS / MANUELLE BEDIENUNG *************************************************/ on({ id: STATE_MANUAL_START, change: 'any' }, obj => { if (obj && obj.state && obj.state.val === true) { setDecision('Manueller Start per VIS ausgelöst'); runManualCleaning(); setState(STATE_MANUAL_START, false, true); } }); on({ id: STATE_MANUAL_RESET, change: 'any' }, obj => { if (obj && obj.state && obj.state.val === true) { setCleaningEnabled(false); resetSelection('manueller VIS Reset'); setStatus('Luna Freigabe manuell zurückgesetzt'); setDecision('Manueller Reset per VIS'); notify('manueller Reset ausgeführt'); setState(STATE_MANUAL_RESET, false, true); } });Intel NUC mit Iobroker
-
Ecovacs Deebot interaktive Karte:

Habe eine Skript entwickelt das die Karte einsetzt und die Fahrt des Saugers mit aufzeichnet. Man kann auch über die Karte die Räume auswählen.Für das Widget der Karte muss man folgenden Wert eintragen:
<!-- LUNA VIS HTML v1.1 --> <div style="position:relative;width:540px;height:231px;overflow:hidden;background:none;border:none;"> <img src="http://192.168.178.59:8082/files/ecovacs-deebot.2/lastestCleaningMapImage.png" style="position:absolute;left:0;top:0;width:540px;height:231px;z-index:1;" > <svg viewBox="0 0 540 231" style="position:absolute;left:0;top:0;width:540px;height:231px;z-index:2;pointer-events:auto;"> <polyline points="{0_userdata.0.Luna.Map.trailPoints}" fill="none" stroke="#ff3b30" stroke-width="2" opacity="0.8" style="pointer-events:none;" /> <polygon fill="orange" fill-opacity="{0_userdata.0.Luna.Map.room0Opacity}" style="cursor:pointer;" onclick=" const mapId = vis.states['0_userdata.0.Luna.Map.mapId.val'] || '1245233875'; const id = 'ecovacs-deebot.2.map.' + mapId + '.spotAreas.0.markForNextSpotAreaCleaning'; vis.setValue(id, !(vis.states[id + '.val'])); " points="0,231 2.5,196.9 7.4,192.1 2.5,187.2 2.5,119.2 4.9,116.7 2.5,68.1 7.4,60.8 7.4,4.9 22.2,4.9 24.7,26.8 34.6,34 44.4,34 44.4,114.3 51.8,121.6 140.7,121.6 145.5,116.7 145.5,36.5 162.9,34 170.3,24.3 177.7,21.9 180.1,36.5 187.5,43.8 187.5,97.2 180.1,104.5 180.1,116.7 185.1,121.6 185.1,153.2 180.1,153.2 170.3,162.9 155.4,170.2 165.3,179.9 177.7,170.2 177.7,175 170.3,179.9 177.7,187.2 175.2,206.6 172.8,204.2 19.7,204.2 14.8,209.1 7.4,231 0,231" /> <polygon fill="lime" fill-opacity="{0_userdata.0.Luna.Map.room1Opacity}" style="cursor:pointer;" onclick=" const mapId = vis.states['0_userdata.0.Luna.Map.mapId.val'] || '1245233875'; const id = 'ecovacs-deebot.2.map.' + mapId + '.spotAreas.1.markForNextSpotAreaCleaning'; vis.setValue(id, !(vis.states[id + '.val'])); " points="190.0,153.2 190.0,121.6 197.5,116.7 271.4,116.7 278.9,99.6 293.7,104.5 308.5,104.5 313.4,111.8 313.4,126.5 323.3,131.3 318.4,153.2 311.0,158.1 313.4,221.9 308.5,231 202.4,231 202.4,175 214.7,187.2 219.7,187.2 246.8,175 254.2,162.9 246.8,155.6 244.3,158.1 209.8,155.6 204.9,160.5 192.5,153.2 190.0,153.2" /> <polygon fill="violet" fill-opacity="{0_userdata.0.Luna.Map.room2Opacity}" style="cursor:pointer;" onclick=" const mapId = vis.states['0_userdata.0.Luna.Map.mapId.val'] || '1245233875'; const id = 'ecovacs-deebot.2.map.' + mapId + '.spotAreas.2.markForNextSpotAreaCleaning'; vis.setValue(id, !(vis.states[id + '.val'])); " points="273.8,63.2 276.3,7.3 288.7,9.7 293.7,26.8 362.9,26.8 370.3,19.5 372.8,7.3 407.3,7.3 414.7,14.6 409.7,19.5 409.7,34 417.2,41.3 429.5,41.3 439.4,55.9 540,55.9 537.5,99.6 443.8,99.6 433.9,87.5 429.5,92.4 417.2,87.5 407.3,99.6 375.2,99.6 367.8,85.1 360.4,89.9 325.8,85.1 313.4,99.6 293.7,99.6 281.3,97.2 283.8,63.2 278.9,58.3 273.8,63.2" /> <polygon fill="cyan" fill-opacity="{0_userdata.0.Luna.Map.room3Opacity}" style="cursor:pointer;" onclick=" const mapId = vis.states['0_userdata.0.Luna.Map.mapId.val'] || '1245233875'; const id = 'ecovacs-deebot.2.map.' + mapId + '.spotAreas.3.markForNextSpotAreaCleaning'; vis.setValue(id, !(vis.states[id + '.val'])); " points="323.3,155.6 328.2,133.7 362.9,133.7 370.3,119.2 375.2,116.7 464.1,116.7 469.0,121.6 476.4,116.7 540,116.7 542.5,119.2 542.5,143.5 513.4,148.3 513.4,187.2 518.3,192.1 542.5,192.1 540,201.8 488.7,201.8 483.8,196.9 466.5,196.9 461.5,201.8 439.4,201.8 432.0,209.1 434.4,221.2 427.0,226.1 422.1,226.1 422.1,218.8 414.7,211.5 404.8,218.8 404.8,226.1 390.0,226.1 382.6,221.2 382.6,189.6 375.2,182.3 335.6,182.3 333.2,155.6 323.3,155.6" /> </svg> <div style=" position:absolute; left:{0_userdata.0.Luna.Map.pixelXcss}; top:{0_userdata.0.Luna.Map.pixelYcss}; transform:{0_userdata.0.Luna.Map.rotateCss}; width:18px;height:18px; z-index:9999; pointer-events:none; "> <img src="https://api-app.dc-eu.ww.ecouser.net/api/pim/file/get/5ba4a2cb6c2f120001c32839" style="width:18px;height:18px;"> </div> <span style="display:none;"> {0_userdata.0.Luna.Map.mapId} {0_userdata.0.Luna.Map.pixelXcss} {0_userdata.0.Luna.Map.pixelYcss} {0_userdata.0.Luna.Map.rotateCss} {0_userdata.0.Luna.Map.trailPoints} {0_userdata.0.Luna.Map.room0Opacity} {0_userdata.0.Luna.Map.room1Opacity} {0_userdata.0.Luna.Map.room2Opacity} {0_userdata.0.Luna.Map.room3Opacity} </span> </div>Demensprechend muss man seine Daten ersetzen sowie beim Hauptskript auch:
/************************************************* * SAUGROBOTER-STEUERUNG LUNA * Version: 1.2.1 * Automatik + Alexa + VIS + Telegram + Karte/Trail * Ohne zusätzliche ioBroker-Logs *************************************************/ const VERSION = '1.2.1'; const ROBOT_NAME = 'Luna'; const TELEGRAM_INSTANCE = 'telegram.0'; const TELEGRAM_USER_ID = '1365513925'; const TELEGRAM_REQUEST = `${TELEGRAM_INSTANCE}.communicate.request`; const STATE_KERSTIN = 'fb-checkpresence.0.Kerstin-Handy.presence'; const STATE_THOMAS = 'fb-checkpresence.0.Thomas-IPhone.presence'; const STATE_FEIERTAG = 'feiertage.0.heute.boolean'; const STATE_BASE = '0_userdata.0.Luna'; const STATE_SAUGEN_FREIGABE = `${STATE_BASE}.saugen`; const STATE_STATUS_TEXT = `${STATE_BASE}.statusText`; const STATE_LAST_RUN = `${STATE_BASE}.lastRun`; const STATE_LAST_DECISION = `${STATE_BASE}.lastDecision`; const STATE_MANUAL_START = `${STATE_BASE}.manualStart`; const STATE_MANUAL_RESET = `${STATE_BASE}.manualReset`; const STATE_POSITION_TEXT = `${STATE_BASE}.Position`; const STATE_WAS_SAUGEN = `${STATE_BASE}.Was_saugen`; const STATE_LAST_HOLIDAY_NOTIFY = `${STATE_BASE}.lastHolidayNotification`; const STATE_LAST_CLEANED_ROOMS = `${STATE_BASE}.lastCleanedRooms`; const ALEXA_STATE = 'alexa2.0.History.summary'; const ROBOT_LUNA = 'luna'; const ROBOT_SKY = 'sky'; const DEEBOT_PREFIX = 'ecovacs-deebot.2'; const DEEBOT_AREA = `${DEEBOT_PREFIX}.control.spotArea`; const DEEBOT_CHARGE = `${DEEBOT_PREFIX}.info.chargestatus`; const DEEBOT_POSITION = `${DEEBOT_PREFIX}.map.deebotPositionCurrentSpotAreaName`; const DEFAULT_MAP_ID = '1245233875'; const DEEBOT_POS_STATE = `${DEEBOT_PREFIX}.map.deebotPosition`; const DEEBOT_STATUS_STATE = `${DEEBOT_PREFIX}.info.deviceStatus`; const MAP_BASE = `${STATE_BASE}.Map`; const STATE_MAP_ID = `${MAP_BASE}.mapId`; const STATE_PIXEL_X = `${MAP_BASE}.pixelXcss`; const STATE_PIXEL_Y = `${MAP_BASE}.pixelYcss`; const STATE_ROTATE = `${MAP_BASE}.rotateCss`; const STATE_TRAIL = `${MAP_BASE}.trailPoints`; const minX = -250; const maxX = 10700; const minY = -4350; const maxY = 400; const imgW = 540; const imgH = 231; const CRON_ENABLE = '17 19 * * *'; const CRON_CHECK = '*/5 9-12 * * 1-5'; const CRON_RESET_DAILY = '0 0 * * *'; const RESET_AFTER_CHARGING_MS = 10000; const DEBOUNCE_MS = 10000; const SETTINGS = { SEND_TELEGRAM: true, SKIP_ON_HOLIDAY: true }; const DAYS = { MONDAY: 1, WEDNESDAY: 3, FRIDAY: 5 }; const ROOMS = [ { id: 0, name: 'Schlafzimmer', mapLetter: 'A', roomState: `${STATE_BASE}.Schlafzimmer`, spotAlias: 'alias.0.Luna.Spot0.ACTUAL', markAlias: 'alias.0.Luna.Mark0.ACTUAL', aliases: ['schlafzimmer'] }, { id: 1, name: 'Flur', mapLetter: 'B', roomState: `${STATE_BASE}.Flur`, spotAlias: 'alias.0.Luna.Spot1.ACTUAL', markAlias: 'alias.0.Luna.Mark1.ACTUAL', aliases: ['flur'] }, { id: 2, name: 'Kinderzimmer', mapLetter: 'C', roomState: `${STATE_BASE}.Kinderzimmer`, spotAlias: 'alias.0.Luna.Spot2.ACTUAL', markAlias: 'alias.0.Luna.Mark2.ACTUAL', aliases: ['kinderzimmer'] }, { id: 3, name: 'Badezimmer', mapLetter: 'D', roomState: `${STATE_BASE}.Badezimmer`, spotAlias: 'alias.0.Luna.Spot3.ACTUAL', markAlias: 'alias.0.Luna.Mark3.ACTUAL', aliases: ['badezimmer', 'bad'] } ]; const ALL_ALIASES = ['obergeschoss', 'oben', 'alles']; const CANCEL_ALIASES = [ 'stopp', 'stop', 'abbrechen', 'abbruch', 'nichts', 'doch nicht' ]; const ENABLE_ALIASES = [ 'freigeben', 'freigabe', 'darf saugen', 'saugen erlauben', 'automatik an', 'automatik aktivieren' ]; const DISABLE_ALIASES = [ 'nicht saugen', 'freigabe aus', 'freigabe deaktivieren', 'saugen verbieten', 'automatik aus', 'automatik deaktivieren' ]; const AUTOMATIC_ROOM_ID = 0; const AUTOMATIC_DAYS = [DAYS.MONDAY, DAYS.WEDNESDAY, DAYS.FRIDAY]; let lastActionText = ''; let lastActionTime = 0; let lastSpotArea = ''; let resetTimer = null; let trail = []; let wasCleaning = false; let chargingNotified = false; function notify(text) { if (!SETTINGS.SEND_TELEGRAM) return; sendTo(TELEGRAM_INSTANCE, 'send', { chatId: TELEGRAM_USER_ID, text: `${ROBOT_NAME}: ${text}` }); } function safeExistsState(id) { try { return existsState(id); } catch (e) { return false; } } function getBoolState(id, fallback = false) { try { if (!safeExistsState(id)) return fallback; const s = getState(id); if (!s || s.val === null || s.val === undefined) return fallback; return !!s.val; } catch (e) { return fallback; } } function getStringState(id, fallback = '') { try { if (!safeExistsState(id)) return fallback; const s = getState(id); if (!s || s.val === null || s.val === undefined) return fallback; return String(s.val); } catch (e) { return fallback; } } function normalize(text) { return String(text || '') .toLowerCase() .normalize('NFD').replace(/[\u0300-\u036f]/g, '') .replace(/[^\w\s]/g, ' ') .replace(/\s+/g, ' ') .trim(); } function includesAlias(text, aliases) { return aliases.some(alias => text.includes(normalize(alias))); } function getTimestamp() { return formatDate(new Date(), 'YYYY-MM-DD hh:mm:ss'); } function setStatus(text) { setState(STATE_STATUS_TEXT, text, true); } function setDecision(text) { setState(STATE_LAST_DECISION, text, true); } function logAction(text) { const now = Date.now(); if (text === lastActionText && now - lastActionTime < DEBOUNCE_MS) { return; } lastActionText = text; lastActionTime = now; setState(STATE_WAS_SAUGEN, text, true); setStatus(text); notify(text); } function isSomeoneHome() { return getBoolState(STATE_KERSTIN) || getBoolState(STATE_THOMAS); } function isCleaningEnabled() { return getBoolState(STATE_SAUGEN_FREIGABE); } function setCleaningEnabled(value) { setState(STATE_SAUGEN_FREIGABE, value, true); } function isHoliday() { return getBoolState(STATE_FEIERTAG, false); } function getWeekday() { return new Date().getDay(); } function isSameDay(ts1, ts2) { if (!ts1 || !ts2) return false; return new Date(ts1).toDateString() === new Date(ts2).toDateString(); } function sendHolidayNotificationOncePerDay(message) { const now = new Date(); const lastNotify = getStringState(STATE_LAST_HOLIDAY_NOTIFY, ''); if (!isSameDay(now, lastNotify)) { notify(message); setState(STATE_LAST_HOLIDAY_NOTIFY, now.toISOString(), true); } } function getRoomById(id) { return ROOMS.find(room => String(room.id) === String(id)); } function getRoomByLetter(letter) { return ROOMS.find(room => room.mapLetter === String(letter)); } function getRoomNamesBySpotArea(spotArea) { if (spotArea === '0,1,2,3') { return ROOMS.map(room => room.name); } const room = getRoomById(spotArea); return room ? [room.name] : []; } function roomListText(roomNames) { if (!roomNames || roomNames.length === 0) return 'keine Räume'; return roomNames.join(', '); } function clearTimer(timer) { if (timer) clearTimeout(timer); } function getCurrentMapId() { return getStringState(STATE_MAP_ID, DEFAULT_MAP_ID); } function getMapMarkState(roomId) { return `${DEEBOT_PREFIX}.map.${getCurrentMapId()}.spotAreas.${roomId}.markForNextSpotAreaCleaning`; } function clearAllMarks() { for (const room of ROOMS) { setState(room.markAlias, false); } for (const room of ROOMS) { const mapMark = getMapMarkState(room.id); if (safeExistsState(mapMark)) { setState(mapMark, false); } } } function resetRoomStates() { for (const room of ROOMS) { setState(room.roomState, false, true); } } function resetSelection(reason) { clearAllMarks(); resetRoomStates(); setState(DEEBOT_AREA, ' ', true); lastSpotArea = ''; setDecision(`Auswahl zurückgesetzt: ${reason}`); } function saveCleanedRooms(roomNames) { const text = roomListText(roomNames); setState(STATE_LAST_CLEANED_ROOMS, text, true); setState(STATE_WAS_SAUGEN, text, true); } function startRoomCleaning(room, source) { if (!room) return; chargingNotified = false; setState(room.spotAlias, true); const cleanedRooms = [room.name]; saveCleanedRooms(cleanedRooms); setState(STATE_LAST_RUN, getTimestamp(), true); setDecision(`${source}: ${room.name} gestartet`); logAction(`${source}: ${room.name} wird gesaugt`); } function startAllCleaning(source) { chargingNotified = false; setState(DEEBOT_AREA, '0,1,2,3'); const cleanedRooms = ROOMS.map(room => room.name); saveCleanedRooms(cleanedRooms); setState(STATE_LAST_RUN, getTimestamp(), true); setDecision(`${source}: Obergeschoss gestartet`); logAction(`${source}: Obergeschoss wird gesaugt: ${roomListText(cleanedRooms)}`); } /************************************************* * TELEGRAM STATUS *************************************************/ function getCleaningStateText() { const deviceStatus = getStringState(DEEBOT_STATUS_STATE, '').toLowerCase(); const chargeStatus = getStringState(DEEBOT_CHARGE, '').toLowerCase(); const isCleaning = deviceStatus.includes('clean') || deviceStatus.includes('spot') || deviceStatus.includes('auto') || deviceStatus.includes('edge'); if (isCleaning) return 'Ja'; if (chargeStatus === 'charging') return 'Nein, Luna steht auf der Ladestation'; return 'Unbekannt'; } function sendLunaStatus() { const saugtWas = getStringState(STATE_WAS_SAUGEN, 'nichts bekannt'); const lastCleaned = getStringState(STATE_LAST_CLEANED_ROOMS, 'nichts bekannt'); const freigabe = isCleaningEnabled() ? 'Ja' : 'Nein'; const position = getStringState(STATE_POSITION_TEXT, 'Unbekannt'); const statusText = getStringState(STATE_STATUS_TEXT, '-'); const letzteEntscheidung = getStringState(STATE_LAST_DECISION, '-'); const lastRun = getStringState(STATE_LAST_RUN, '-'); const mapId = getCurrentMapId(); const saugtGerade = getCleaningStateText(); notify( `Status:\n` + `Saugt gerade: ${saugtGerade}\n` + `Was: ${saugtWas}\n` + `Zuletzt gereinigte Räume: ${lastCleaned}\n` + `Freigabe nächster Automatiklauf: ${freigabe}\n` + `Position: ${position}\n` + `Letzter Lauf: ${lastRun}\n` + `Status VIS: ${statusText}\n` + `Letzte Entscheidung: ${letzteEntscheidung}\n` + `Map-ID: ${mapId}` ); } on({ id: TELEGRAM_REQUEST, change: 'any' }, obj => { if (!obj || !obj.state || !obj.state.val) return; let raw = String(obj.state.val); raw = raw.replace(/^\[[^\]]+\]/, '').trim(); const text = normalize(raw); const isLunaStatusRequest = text.includes('was macht luna') || text.includes('luna status') || text.includes('status luna') || text.includes('luna freigabe') || text.includes('luna saugt') || text.includes('luna info') || text === 'luna'; if (!isLunaStatusRequest) { return; } sendLunaStatus(); }); /************************************************* * VIS KARTE / POSITION / TRAIL *************************************************/ function toPixel(x, y) { return { x: ((x - minX) / (maxX - minX)) * imgW, y: imgH - ((y - minY) / (maxY - minY)) * imgH }; } function parseBotPosition(value) { if (!value) return null; const p = String(value).split(',').map(Number); if (p.length < 3 || p.some(isNaN)) return null; return { x: p[0], y: p[1], angle: p[2] }; } function updateBotOnMap(value) { const pos = parseBotPosition(value); if (!pos) return; const pixel = toPixel(pos.x, pos.y); setState(STATE_PIXEL_X, pixel.x.toFixed(1) + 'px', true); setState(STATE_PIXEL_Y, pixel.y.toFixed(1) + 'px', true); setState(STATE_ROTATE, `translate(-50%,-50%) rotate(${pos.angle}deg)`, true); const last = trail[trail.length - 1]; if (!last || Math.abs(last.x - pixel.x) > 1 || Math.abs(last.y - pixel.y) > 1) { trail.push(pixel); if (trail.length > 500) { trail.shift(); } const points = trail .map(pt => `${pt.x.toFixed(1)},${pt.y.toFixed(1)}`) .join(' '); setState(STATE_TRAIL, points, true); } } function resetTrail() { trail = []; setState(STATE_TRAIL, '', true); } function checkResetTrailByStatus(status) { if (!status) return; const s = String(status).toLowerCase(); const isCleaning = s.includes('clean') || s.includes('spot') || s.includes('auto') || s.includes('edge'); if (isCleaning && !wasCleaning) { resetTrail(); wasCleaning = true; chargingNotified = false; } const isFinished = s.includes('charging') || s.includes('charge') || s.includes('dock') || s.includes('idle') || s.includes('stop') || s.includes('finish') || s.includes('complete') || s.includes('standby'); if (isFinished) { wasCleaning = false; } } function updateRoomOpacity(roomNumber, value) { const selected = value === true || value === 'true' || value === 1 || value === '1'; const opacity = selected ? 0.75 : 0.25; setState(`${MAP_BASE}.room${roomNumber}Opacity`, opacity, true); } /************************************************* * STATES AUTOMATISCH ANLEGEN *************************************************/ function createUserStates() { createState(STATE_SAUGEN_FREIGABE, false, { name: 'Saugfreigabe für nächsten Reinigungslauf', type: 'boolean', role: 'switch.enable', read: true, write: true, def: false }); createState(STATE_STATUS_TEXT, '', { name: 'Statusanzeige Luna', type: 'string', role: 'text', read: true, write: false, def: '' }); createState(STATE_LAST_RUN, '', { name: 'Letzter Luna Reinigungslauf', type: 'string', role: 'text', read: true, write: false, def: '' }); createState(STATE_LAST_DECISION, '', { name: 'Letzte Luna Entscheidung', type: 'string', role: 'text', read: true, write: false, def: '' }); createState(STATE_MANUAL_START, false, { name: 'Manueller Luna Start per VIS', type: 'boolean', role: 'button', read: true, write: true, def: false }); createState(STATE_MANUAL_RESET, false, { name: 'Manueller Luna Reset', type: 'boolean', role: 'button', read: true, write: true, def: false }); createState(STATE_POSITION_TEXT, '', { name: 'Aktuelle Luna Position', type: 'string', role: 'text', read: true, write: false, def: '' }); createState(STATE_WAS_SAUGEN, '', { name: 'Was saugt Luna', type: 'string', role: 'text', read: true, write: false, def: '' }); createState(STATE_LAST_HOLIDAY_NOTIFY, '', { name: 'Letzte Feiertags-Benachrichtigung Luna', type: 'string', role: 'text', read: true, write: false, def: '' }); createState(STATE_LAST_CLEANED_ROOMS, '', { name: 'Zuletzt gesaugte Räume Luna', type: 'string', role: 'text', read: true, write: false, def: '' }); for (const room of ROOMS) { createState(room.roomState, false, { name: `Luna Raum aktiv: ${room.name}`, type: 'boolean', role: 'indicator', read: true, write: false, def: false }); } createState(STATE_MAP_ID, DEFAULT_MAP_ID, { name: 'Luna aktuelle Map ID', type: 'string', role: 'text', read: true, write: true, def: DEFAULT_MAP_ID }); createState(STATE_PIXEL_X, '0px', { name: 'Luna Karte X Position CSS', type: 'string', role: 'text', read: true, write: true, def: '0px' }); createState(STATE_PIXEL_Y, '0px', { name: 'Luna Karte Y Position CSS', type: 'string', role: 'text', read: true, write: true, def: '0px' }); createState(STATE_ROTATE, 'translate(-50%,-50%) rotate(0deg)', { name: 'Luna Karte Rotation CSS', type: 'string', role: 'text', read: true, write: true, def: 'translate(-50%,-50%) rotate(0deg)' }); createState(STATE_TRAIL, '', { name: 'Luna Karte Fahrspur Punkte', type: 'string', role: 'text', read: true, write: true, def: '' }); for (const room of ROOMS) { createState(`${MAP_BASE}.room${room.id}Opacity`, 0.25, { name: `Luna Karte Raum ${room.id} Deckkraft`, type: 'number', role: 'value', read: true, write: true, def: 0.25 }); } } /************************************************* * AUTOMATIK *************************************************/ function shouldCleanNow() { if (!isCleaningEnabled()) { setDecision('Abbruch: Freigabe ist false'); return false; } if (!AUTOMATIC_DAYS.includes(getWeekday())) { setDecision('Heute ist kein Luna Reinigungstag'); return false; } if (SETTINGS.SKIP_ON_HOLIDAY && isHoliday()) { const room = getRoomById(AUTOMATIC_ROOM_ID); const roomsText = room ? room.name : 'Automatikraum'; const msg = `Heute keine Reinigung wegen Feiertag. Geplant gewesen wäre: ${roomsText}.`; setStatus(msg); setDecision('Abbruch: Feiertag'); sendHolidayNotificationOncePerDay(msg); return false; } if (isSomeoneHome()) { setDecision('Abbruch: jemand ist zuhause'); return false; } return true; } function runAutomaticCleaning() { if (!shouldCleanNow()) return; const room = getRoomById(AUTOMATIC_ROOM_ID); if (!room) { setDecision('Abbruch: Automatikraum nicht gefunden'); return; } startRoomCleaning(room, 'Automatik'); setCleaningEnabled(false); } function runManualCleaning() { const room = getRoomById(AUTOMATIC_ROOM_ID); startRoomCleaning(room, 'Manueller Start'); } /************************************************* * ALEXA *************************************************/ on({ id: ALEXA_STATE, change: 'any' }, obj => { const raw = obj.state.val; const spoken = normalize(raw); if (!spoken) return; if (spoken.includes(ROBOT_SKY)) { setDecision(`Sky-Befehl ignoriert: ${raw}`); return; } if (!spoken.includes(ROBOT_LUNA)) { return; } if (includesAlias(spoken, ENABLE_ALIASES)) { setCleaningEnabled(true); setStatus('Freigabe per Alexa aktiviert'); setDecision('Freigabe per Alexa gesetzt'); notify('Freigabe wurde per Alexa aktiviert'); return; } if (includesAlias(spoken, DISABLE_ALIASES)) { setCleaningEnabled(false); setStatus('Freigabe per Alexa deaktiviert'); setDecision('Freigabe per Alexa deaktiviert'); notify('Freigabe wurde per Alexa deaktiviert'); return; } if (includesAlias(spoken, CANCEL_ALIASES)) { resetSelection('Abbruch per Sprache'); notify('Auswahl abgebrochen'); return; } if (includesAlias(spoken, ALL_ALIASES)) { startAllCleaning('Alexa'); return; } let matchedRoom = null; for (const room of ROOMS) { if (includesAlias(spoken, room.aliases)) { matchedRoom = room; break; } } if (!matchedRoom) { setDecision(`Luna-Befehl ohne bekannten Raum: ${raw}`); return; } startRoomCleaning(matchedRoom, 'Alexa'); }); /************************************************* * LADESTATION *************************************************/ on({ id: DEEBOT_CHARGE, change: 'ne' }, obj => { if (!obj || !obj.state) return; const val = String(obj.state.val || '').toLowerCase(); if (val !== 'charging') { chargingNotified = false; return; } if (chargingNotified) return; chargingNotified = true; clearTimer(resetTimer); resetTimer = setTimeout(() => { resetSelection('Luna lädt'); logAction('steht auf der Ladestation. Auswahl wurde zurückgesetzt.'); resetTimer = null; }, RESET_AFTER_CHARGING_MS); }); /************************************************* * LISTENER *************************************************/ function updateVisPosition(value) { const room = getRoomByLetter(value); const position = room ? room.name : 'Unbekannt'; setState(STATE_POSITION_TEXT, position, true); } on({ id: DEEBOT_POSITION, change: 'any' }, obj => { updateVisPosition(obj.state.val); }); on({ id: DEEBOT_POS_STATE, change: 'any' }, obj => { updateBotOnMap(obj.state.val); }); on({ id: DEEBOT_STATUS_STATE, change: 'any' }, obj => { checkResetTrailByStatus(obj.state.val); }); for (const room of ROOMS) { on({ id: getMapMarkState(room.id), change: 'any' }, obj => { updateRoomOpacity(room.id, obj.state.val); }); } on({ id: STATE_MAP_ID, change: 'ne' }, obj => { setDecision(`Map-ID geändert auf ${obj.state.val}`); for (const room of ROOMS) { const mapMark = getMapMarkState(room.id); updateRoomOpacity(room.id, safeExistsState(mapMark) ? getState(mapMark)?.val : false); } }); on({ id: [ DEEBOT_AREA, ...ROOMS.map(room => room.markAlias) ], change: 'ne' }, () => { const spotArea = getStringState(DEEBOT_AREA); if (spotArea === lastSpotArea) { return; } lastSpotArea = spotArea; resetRoomStates(); const roomNames = getRoomNamesBySpotArea(spotArea); if (spotArea === '0,1,2,3') { for (const room of ROOMS) { setState(room.roomState, true, true); } saveCleanedRooms(roomNames); setStatus(`saugt das Obergeschoss: ${roomListText(roomNames)}`); return; } const room = getRoomById(spotArea); if (room) { setState(room.roomState, true, true); saveCleanedRooms([room.name]); setStatus(`saugt ${room.name}`); } }); /************************************************* * INITIALISIERUNG *************************************************/ createUserStates(); setTimeout(() => { setStatus(`Luna Skript gestartet | Version ${VERSION}`); setDecision(`Initialisierung abgeschlossen | Version ${VERSION}`); const currentPosition = getState(DEEBOT_POSITION); if (currentPosition) { updateVisPosition(currentPosition.val); } const currentBotPosition = getState(DEEBOT_POS_STATE); if (currentBotPosition && currentBotPosition.val) { updateBotOnMap(currentBotPosition.val); } const currentDeviceStatus = getState(DEEBOT_STATUS_STATE); if (currentDeviceStatus && currentDeviceStatus.val) { checkResetTrailByStatus(currentDeviceStatus.val); } for (const room of ROOMS) { const mapMark = getMapMarkState(room.id); updateRoomOpacity(room.id, safeExistsState(mapMark) ? getState(mapMark)?.val : false); } }, 2000); /************************************************* * ZEITSTEUERUNG *************************************************/ schedule(CRON_ENABLE, () => { setCleaningEnabled(true); setStatus('Freigabe für nächsten Luna Reinigungstag gesetzt'); setDecision('19:17-Freigabe aktiviert'); notify('tägliche Freigabe wurde aktiviert. Luna darf beim nächsten Automatiklauf saugen.'); }); schedule(CRON_CHECK, () => { runAutomaticCleaning(); }); schedule(CRON_RESET_DAILY, () => { setState(STATE_LAST_HOLIDAY_NOTIFY, '', true); setDecision('Täglicher Luna Reset abgeschlossen'); }); /************************************************* * VIS / MANUELLE BEDIENUNG *************************************************/ on({ id: STATE_MANUAL_START, change: 'any' }, obj => { if (obj && obj.state && obj.state.val === true) { setDecision('Manueller Start per VIS ausgelöst'); runManualCleaning(); setState(STATE_MANUAL_START, false, true); } }); on({ id: STATE_MANUAL_RESET, change: 'any' }, obj => { if (obj && obj.state && obj.state.val === true) { setCleaningEnabled(false); resetSelection('manueller VIS Reset'); setStatus('Luna Freigabe manuell zurückgesetzt'); setDecision('Manueller Reset per VIS'); notify('manueller Reset ausgeführt'); setState(STATE_MANUAL_RESET, false, true); } });@helfi9999
So ganz begreife ich nicht wo ich was ersetzen muss, werden die DP automatisch angelegt?Bitte benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.
Immer Daten sichern! -
@helfi9999
So ganz begreife ich nicht wo ich was ersetzen muss, werden die DP automatisch angelegt?
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