Weiter zum Inhalt

Skripten / Logik

16.7k Themen 216.1k Beiträge

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

NEWS

Unterkategorien


  • Hilfe für Skripterstellung mit JavaScript

    3k 50k
    3k Themen
    50k Beiträge
    H
    Hallo zusammen, bei mir steht seit ein paar Tagen ein JONR T5 Pro. Der läuft über die Xiaomi-Home-App, also dachte ich, mit mihome-vacuum ist die Anbindung eine Sache von zehn Minuten. War sie nicht. Der Adapter kennt das Modell nicht — im Log steht schlicht Model xtl.vacuum.2407a not supported. Man kann in den Einstellungen zwar von Hand einen anderen Manager erzwingen, aber dann kracht es an der nächsten Stelle, weil das Gerät seine Antworten anders aufbaut als erwartet. Und der bequeme Weg über die Cloud fällt auch aus, der Login kommt nicht mehr durch. Das liegt nicht am Adapter, sondern daran, dass die JONR-Geräte zwar MIoT sprechen, aber eben ihr eigenes Vokabular haben — und das kennt niemand, der das Gerät nicht vor sich stehen hat. Ich hab mir dann angeschaut, was der Sauger lokal so von sich gibt. Rausgekommen ist ein Skript für den javascript-Adapter, das direkt per UDP mit ihm redet. Keine Cloud, kein npm-Paket, nur dgram und crypto — beides ist in Node sowieso drin. Das kann es: Starten, Stoppen, Pause, Fortsetzen nur Saugen, nur Wischen oder beides gleichzeitig Saugstufe und Wassermenge einzelne Räume reinigen Status auslesen: Akku, Fehlercode, was er gerade macht und über den iot-Adapter läuft das Ganze per Alexa Ich schreib das hier so ausführlich auf, weil ich an ein paar Stellen ziemlich lange im Dunkeln getappt bin. Wer ein anderes JONR-Modell hat, sollte damit deutlich schneller ans Ziel kommen — die Geräte sprechen alle dasselbe Protokoll, nur mit teils anderen Nummern. Wie man die fürs eigene Gerät rausbekommt, steht weiter unten. 1. Voraussetzungen Sauger in der Xiaomi-Home-App eingerichtet und im WLAN Adapter javascript installiert (getestet mit 9.0.18) Optional für Sprachsteuerung: Adapter iot Feste IP für den Sauger im Router vergeben — er hängt sonst irgendwann woanders 2. Token und Device-ID besorgen Für die lokale Verbindung braucht man einen 32-stelligen Hex-Token, den das Gerät bei der Einrichtung bekommen hat. Der Token steht nicht in der App — man holt ihn sich über das Xiaomi-Konto. Dafür gibt es ein etabliertes Werkzeug, den Xiaomi Cloud Tokens Extractor: 👉 https://github.com/PiotrMachowski/Xiaomi-cloud-tokens-extractor Unter Releases gibt es eine fertige token_extractor.exe für Windows — Python muss man dafür nicht installieren. Ablauf: token_extractor.exe herunterladen und starten (Konsolenfenster) Xiaomi-Konto (E-Mail und Passwort) eingeben Server-Region wählen — für Deutschland de Das Tool listet alle Geräte des Kontos auf Für jedes Gerät bekommt man unter anderem: Name: Jonr T5 Pro ID: 1234567890 <- das ist die DID Token: a1b2c3d4e5f6... <- 32 Hex-Zeichen Model: xtl.vacuum.2407a IP: 192.168.x.y MAC: xx:xx:xx:xx:xx:xx Token, DID und IP brauchen wir gleich. Das Model sagt euch, ob euer Gerät überhaupt zu diesem Skript passt. ⚠️ Der Token ist ein Zugangsschlüssel zum Gerät. Nicht in öffentliche Repos, Foren oder Screenshots — beim Posten schwärzen. 3. Woher die ganzen Nummern kommen (siid / piid / aiid) Damit das Skript nicht wie Magie wirkt, kurz die Herkunft — das ist auch der Weg, um es auf andere Modelle anzupassen. Xiaomi-Geräte beschreiben sich selbst über eine MIoT-Spezifikation, die öffentlich abrufbar ist: https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:vacuum:0000A006:xtl-2407a:2 Der entscheidende Teil im URN ist das Modellkürzel (xtl-2407a). Die passende URN zum eigenen Gerät findet man über die Suche auf https://miot-spec.org/. Die Datei ist JSON und hat drei Ebenen: Bedeutung Beispiel siid Service — ein Funktionsblock 2 = Robot Cleaner, 16 = Battery, 17 = herstellereigener Block piid Property — ein Wert in diesem Block 2.2 = Status, 16.1 = Akku aiid Action — etwas, das man auslösen kann 2.1 = Start, 17.3 = stop-clean ⚠️ Wichtigste Falle überhaupt: piid und aiid sind getrennte Nummernkreise. Bei meinem Gerät ist 17.46 als Action set-clean-mode, als Property dagegen die Reinigungshistorie. Wer das verwechselt, sucht sich stundenlang tot. Zweite Falle: Viele Einträge haben eine leere description. Der echte Name steht dann im type-URN: { "iid": 34, "type": "urn:xtl-spec:property:clean-values:00000022:xtl-2407a:1", "description": "", "format": "string" } description ist leer, aber der URN verrät clean-values — die Raumliste. So habe ich die halbe Landkarte erschlossen. 4. Die wichtigsten Werte (hier vom T5 Pro, andere Modelle ähnlich) Zum Auslesen: siid.piid Bedeutung 2.2 Status: 1 Idle · 2 Busy · 4 Wischen · 5 Saugen+Wischen · 6 Pausiert · 7 Saugen · 8 Fehler · 9 Lädt · 10 Fährt zur Basis · 12 Fährt zum Waschen · 13 Geladen · 16 Schläft · 18 Station arbeitet 16.1 Akku in % 17.35 Fehlercode (0 = alles gut) 17.9 clean-type-status — Full/Area/Zone/Spot Clean, jeweils mit Pause und Resume 17.10 clean-mode: 0 BothWork · 1 OnlySweep · 2 OnlyMop · 3 SweepFirst · 4 Custom 17.12 Wassermenge: 0 Low · 1 Mid · 2 High 17.13 Saugstufe: 0 Quiet · 1 Auto · 2 Strong · 3 Max 17.34 clean-values — der laufende Auftrag (Raumliste) 17.49 Klartextstatus, z. B. "HotDry" Zu 17.10 ein Hinweis, weil die deutsche App das unglücklich übersetzt: „saugen&wischen" ist BothWork (gleichzeitig), „saugen und wischen" ist SweepFirst (erst alles saugen, danach wischen). Die englischen Bezeichner in der Spec sind eindeutiger als die Übersetzung. Zum Steuern — es gibt keine schreibbaren Properties, alles läuft über Actions: siid.aiid Wirkung Parameter 2.1 Start (ganzes Haus) / Fortsetzen — 2.2 Fahrt anhalten — 2.7 Pause — 16.1 Zurück zur Basis — 17.1 start-clean (raumweise) piid 8 = Art, piid 34 = Raumliste 17.3 stop-clean — Auftrag beenden — 17.26 Saugstufe setzen piid 13 17.27 Wassermenge setzen piid 12 17.46 Reinigungsart setzen piid 10 17.28 Anzahl Durchgänge piid 14 (1 oder 2) 5. Die vier Stolpersteine Das hier ist der eigentliche Grund für diesen Beitrag — daran hängt man sonst garantiert fest. 5.1 Nie zwei Clients gleichzeitig Das Gerät verträgt keine parallelen miIO-Sitzungen. Läuft das Skript (das ja pollt) und man testet parallel etwas per Konsole, quittiert der Sauger Befehle mit code 0 und tut nichts. Das sieht exakt aus wie ein falscher Parameter — ist aber eine Kollision. Mich hat das einen halben Tag gekostet: Ich habe ein Dutzend Parameterformate durchprobiert, obwohl das allererste schon richtig war. Vor manuellen Tests das Skript deaktivieren. Parallel darf aber APP und Skript laufen. Die Befehle die ich lokal an den Sauger per Skript sende, werden in der APP ordentlich angezeigt. Z.B. bei einer Zimmerreinigung sind Statusangaben und auch die Hervorhebung der Karte korrekt. Auch können Aufgaben/Befehle die per Skript oder APP kamen, auch auf dem Gegenstück z.B. gestoppt werden. Alles kein Problem. 5.2 Befehle brauchen Pausen Vier Actions direkt hintereinander (Saugstufe → Wasser → Modus → Start) — und der Sauger verschluckt die letzte. 1,5 Sekunden zwischen den Aufrufen genügen. 5.3 Stoppen heißt Auftrag beenden 2.2 hält nur die Fahrt an. Der Auftrag bleibt danach im Zustand „Area Pause" stehen (17.9 = 9), und 17.34 trägt weiter die Raumliste. Folgen: Die App verweigert einen Moduswechsel („Auftrag muss erst abgebrochen werden") Der Sauger fährt später von selbst wieder los — etwa nachdem die Station die Mopps gewaschen hat, nimmt er den pausierten Auftrag wieder auf Es läuft keine Nacharbeit, weil der Auftrag nie abgeschlossen wurde Richtig ist 17.3 stop-clean. Danach geht 17.9 auf Idle und 17.34 wird leer. Merksatz: Bei merkwürdigem Verhalten immer 17.9 mitlesen — 2.2 zeigt „Lädt", während im Hintergrund noch ein Auftrag pausiert. 5.4 Große Antworten kommen gar nicht an Wird eine Property-Antwort zu groß, sendet das Gerät überhaupt kein Paket — kein Fehler, einfach Stille. Bei mir passierte das ab etwa acht Zeitplänen in 17.17. Also nicht die dicken JSON-Properties (Historie, Zeitpläne) im Minutentakt pollen. Und noch eine Kleinigkeit: Bei get_properties kommen manchmal weniger Ergebnisse zurück als angefragt. Deshalb die Antworten immer über die mitgelieferten siid/piid zuordnen, nie über die Reihenfolge. Batchgröße 5 hat sich bewährt. 6. Raum-IDs herausfinden Für die raumweise Reinigung braucht man die internen IDs. Die stehen weder in der App noch sind sie lokal abrufbar — die Karte liegt in der Xiaomi-Cloud, get-map-infos liefert nur eine Referenz. Der Trick: über die Zeitpläne. Ein Zeitplan enthält die Raumliste im Klartext, und Property 17.17 gibt die Zeitpläne aus. Vorgehen: In der App einen Zeitplan anlegen — deaktiviert, damit nichts losfährt — und genau einen Raum zuweisen 17.17 auslesen In der Ausgabe steht: { "id":"1", "on":0, "hour":12, "min":0, "mapId":1, "cleanValues":[6], <-- das ist die gesuchte Raum-ID "workMode":0, "fanMode":3, "waterMode":1, "cleanCount":1, "routePrefer":1 } Für jeden Raum wiederholen, danach die Test-Zeitpläne wieder löschen ⚠️ Höchstens drei Zeitpläne gleichzeitig im Gerät lassen — sonst wird die Antwort zu groß (siehe 5.4) und 17.17 liefert gar nichts mehr. ⚠️ Die IDs gelten nur für die aktuelle Karte. Nach einer Neukartierung oder nach dem Teilen/Zusammenlegen von Räumen werden sie neu vergeben, und vorhandene Zeitpläne muss man neu anlegen — die App kann sie nicht umhängen. Nebenbei: Bei mir zeigte die App die Räume vor dem Umbenennen als „Zimmer1…n" an, und die interne ID lag konstant zwei höher als diese Nummer. Ob das allgemein gilt, weiß ich nicht — verifiziert habe ich jede ID einzeln über die Zeitplan-Methode. Verlasst euch nicht auf die Rechnung. 7. Das Format der Raumliste — der Knackpunkt piid 34 will die Raum-IDs als Array-Notation in einem String: action(17.1, [{ piid: 8, value: 3 }, { piid: 34, value: "[6]" }]) // AreaClean Raumliste Mehrere Räume als "[3,8,11]". Die Reihenfolge im Array ist die Reinigungsreihenfolge. Was nicht funktioniert (alles durchprobiert): Versuch Ergebnis nackte Zahl "6" fährt los, kehrt um: „Ausgewählter Bereich nicht gefunden" echtes Array [6] statt String -9999 user ack timeout JSON-Objekt {"mapId":1,"cleanValues":[6]} still verworfen, keine Fahrt nur piid 34 ohne piid 8 -9999 user ack timeout positionale Parameter in: [3, "[6]"] -9999 user ack timeout Wie ich das Format gefunden habe — und das ist die Methode, die ich jedem empfehle, der an einem unklaren Parameter hängt: Das Gerät einmal selbst machen lassen und dabei zuschauen. Ich habe einen Zeitplan auf „in drei Minuten" gestellt und währenddessen 17.34 mitgelesen. Da stand dann "[7,6]" — und damit war das Format klar, statt weiter zu raten. 8. Das Skript Anlegen im javascript-Adapter als normales JavaScript (nicht Blockly). Oben IP, Token und DID eintragen und die Raumtabelle mit den eigenen IDs füllen. Es legt alle States selbst an unter 0_userdata.0.Saugroboter.Jonr.* — der Pfad steht oben im Skript und ist frei wählbar: Anzeige (info.*): Status als Code und Klartext, Akku, Fehlercode, Reinigungsart, Saugstufe, Wassermenge, Erreichbarkeit, Zeitpunkt der letzten Abfrage Steuerung (echte Schalter, read+write): State „ein" „aus" Sauger Max + Mid + BothWork, ganzes Haus stoppen und zur Basis Sauger_Wischen nur wischen stoppen und zur Basis Sauger_Pause pausieren fortsetzen raum.<Name> diesen Raum reinigen stoppen und zur Basis Die Namen sind frei wählbar — sie stehen oben im Skript und tauchen genauso bei Alexa auf. Gepollt wird alle 60 Sekunden, während der Arbeit alle 15. /* * ============================================================================ * JONR T5 Pro (xtl.vacuum.2407a) lokal steuern -- ioBroker javascript-Adapter * ============================================================================ * Spricht den Sauger direkt per miIO/MIoT ueber UDP an. Keine Cloud, kein * zusaetzliches npm-Paket -- nur die Node-Bordmittel dgram und crypto. * * Getestet mit dem JONR T5 Pro. Andere xtl.*-Modelle (P20 Pro = xm2216, * X1 MAX = xt3411 u. a.) sprechen dasselbe Protokoll, koennen aber * abweichende siid/piid haben -- Spec pruefen, siehe Forumsbeitrag. * * Keine Gewaehr, keine Wartungszusage. Verbesserungen gerne. * * --- Vor dem Start ausfuellen ----------------------------------------------- * IP, TOKEN und DID unten eintragen, ROOMS mit den eigenen Raum-IDs fuellen. * * --- Fallstricke, alle in der Praxis getroffen --------------------------------------- * 1. Action-IIDs und Property-IIDs sind GETRENNTE Nummernkreise! * 17.46 als Action = set-clean-mode, 17.46 als Property = Reinigungshistorie. * 2. Das Geraet liefert bei get_properties teils WENIGER Ergebnisse als * angefragt. Zuordnung immer ueber die mitgelieferten siid/piid, nie ueber * die Reihenfolge. Batchgroesse 5 hat sich bewaehrt. * 3. Grosse Antworten liefert das Geraet GAR NICHT aus -- es sendet dann kein * Paket statt eines Fehlers. Also keine dicken JSON-Properties pollen. * 4. NIE zwei miIO-Clients gleichzeitig auf dem Geraet. Laeuft parallel ein * zweites Skript, quittiert der Sauger Befehle mit code 0 und tut nichts. * Das sieht wie ein falscher Parameter aus, ist aber eine Kollision. * 5. Befehle nicht ohne Pause hintereinander senden -- der Sauger verschluckt * sonst den naechsten. 1,5 s dazwischen reichen. * 6. Stoppen heisst Auftrag BEENDEN (17.3), nicht nur anhalten (2.2). * Sonst bleibt der Auftrag pausiert und der Sauger faehrt spaeter von * selbst weiter. * ============================================================================ */ 'use strict'; const dgram = require('dgram'); const crypto = require('crypto'); // ---------------------------------------------------------------- Konfiguration const IP = '192.168.x.y'; // IP des Saugers (feste Adresse im Router vergeben!) const TOKEN = Buffer.from('DEIN_32_STELLIGER_TOKEN', 'hex'); const DID = 'DEINE_DID'; // Device-ID, kommt aus demselben Tool wie der Token const PORT = 54321; const BASE = '0_userdata.0.Saugroboter.Jonr'; // frei waehlbar const POLL_IDLE = 60000; // ms — Sauger ruht const POLL_ACTIVE = 15000; // ms — Sauger arbeitet /* Raum-IDs. ACHTUNG: Sie gelten nur fuer die aktuelle Karte -- nach einer * Neukartierung oder nach dem Teilen/Zusammenlegen von Raeumen werden sie neu * vergeben. Wie man sie ermittelt, steht im Forumsbeitrag (ueber Zeitplaene). */ const ROOMS = { // BEISPIEL -- eigene Raeume eintragen! Wie man die IDs ermittelt, steht im Forumsbeitrag. // Schluessel = Name im Objektbaum (ASCII), alexa = Name fuer die Sprachsteuerung. Raum1: { id: 3, alexa: 'Sauger Raum eins' }, Raum2: { id: 4, alexa: 'Sauger Raum zwei' }, }; // siid.aiid der Aktionen, die wir auslösen const ACT = { START_SWEEP: { siid: 2, aiid: 1 }, // parameterlos STOP: { siid: 2, aiid: 2 }, // haelt nur die Fahrt an STOP_CLEAN: { siid: 17, aiid: 3 }, // beendet den AUFTRAG -- siehe stopAndHome() PAUSE: { siid: 2, aiid: 7 }, CHARGE: { siid: 16, aiid: 1 }, // zurück zur Basis START_CLEAN: { siid: 17, aiid: 1 }, // in: piid 8 = Art, piid 34 = Räume SET_FAN: { siid: 17, aiid: 26 }, // in: piid 13 SET_WATER: { siid: 17, aiid: 27 }, // in: piid 12 SET_MODE: { siid: 17, aiid: 46 }, // in: piid 10 }; // Werte const CLEAN_FULL = 1, CLEAN_AREA = 3; // piid 8 const MODE_BOTH = 0, MODE_MOP = 2; // piid 10 const FAN_MAX = 3; // piid 13 const WATER_MID = 1; // piid 12 // Statuscodes (Property 2.2) const STATUS = { 1:'Idle', 2:'Busy', 4:'Wischen', 5:'Saugen+Wischen', 6:'Pausiert', 7:'Saugen', 8:'Fehler', 9:'Lädt', 10:'Fährt zur Basis', 12:'Fährt zum Waschen', 13:'Geladen', 16:'Schläft', 18:'Station arbeitet', }; // Bei diesen Zuständen ist der Sauger unterwegs -> schnelleres Polling, // die Schalter stehen dann auf true const RUNNING = [2, 4, 5, 7]; const PAUSED = [6]; // ---------------------------------------------------------------- miIO-Protokoll const key = crypto.createHash('md5').update(TOKEN).digest(); const iv = crypto.createHash('md5').update(Buffer.concat([key, TOKEN])).digest(); const encrypt = d => { const c = crypto.createCipheriv('aes-128-cbc', key, iv); return Buffer.concat([c.update(d, 'utf8'), c.final()]); }; const decrypt = b => { const d = crypto.createDecipheriv('aes-128-cbc', key, iv); return Buffer.concat([d.update(b), d.final()]).toString('utf8').replace(/\0+$/, ''); }; let sock = null; let devId = null, stamp = 0, stampAt = 0, msgId = 100; let pending = null; let stopping = false; function openSocket() { sock = dgram.createSocket('udp4'); sock.on('message', m => { if (pending) { const p = pending; pending = null; p(m); } }); sock.on('error', e => log('Socket-Fehler: ' + e.message, 'warn')); } function tx(buf, timeout) { return new Promise((res, rej) => { if (!sock || stopping) return rej(new Error('Socket zu')); const t = setTimeout(() => { pending = null; rej(new Error('timeout')); }, timeout); pending = m => { clearTimeout(t); res(m); }; sock.send(buf, 0, buf.length, PORT, IP, e => { if (e) { clearTimeout(t); pending = null; rej(e); } }); }); } async function retry(fn, n = 4) { let last; for (let i = 0; i < n && !stopping; i++) { try { return await fn(); } catch (e) { last = e; } } throw last || new Error('abgebrochen'); } function helloPacket() { const b = Buffer.alloc(32, 0xff); b.writeUInt16BE(0x2131, 0); b.writeUInt16BE(32, 2); return b; } /* Der Zeitstempel des Geräts muss mitgezählt werden. Nach ein paar Minuten * Funkstille lieber neu synchronisieren, sonst verwirft das Gerät die Pakete. */ async function handshake() { const r = await retry(() => tx(helloPacket(), 4000)); devId = Buffer.from(r.subarray(8, 12)); stamp = r.readUInt32BE(12); stampAt = Date.now(); } async function ensureHandshake() { if (!devId || Date.now() - stampAt > 120000) await handshake(); } function packet(obj) { const enc = encrypt(JSON.stringify(obj)); const h = Buffer.alloc(16); h.writeUInt16BE(0x2131, 0); h.writeUInt16BE(32 + enc.length, 2); h.writeUInt32BE(0, 4); devId.copy(h, 8); h.writeUInt32BE(stamp + Math.floor((Date.now() - stampAt) / 1000), 12); const sum = crypto.createHash('md5').update(Buffer.concat([h, TOKEN, enc])).digest(); return Buffer.concat([h, sum, enc]); } async function call(method, params, timeout = 8000) { await ensureHandshake(); const r = await retry(() => tx(packet({ id: msgId++, method, params }), timeout)); return JSON.parse(decrypt(r.subarray(32))); } /** Properties lesen. Rückgabe: { "siid.piid": value } — fehlende bleiben weg. */ async function getProps(list) { const out = {}; for (let i = 0; i < list.length; i += 5) { // Batchgröße 5 const batch = list.slice(i, i + 5); try { const r = await call('get_properties', batch.map(p => ({ did: DID, siid: p.siid, piid: p.piid }))); for (const v of (r.result || [])) { if (v.code === 0) out[v.siid + '.' + v.piid] = v.value; // s. Fallstrick 2 } } catch (e) { log('get_properties fehlgeschlagen: ' + e.message, 'warn'); } } return out; } /** Aktion auslösen. `params` sind die in-Werte in der Reihenfolge der Spec. */ async function action(a, params = []) { const r = await call('action', { did: DID, siid: a.siid, aiid: a.aiid, in: params }); const code = r && r.result ? r.result.code : (r && r.error ? r.error.code : -1); if (code !== 0) throw new Error('Action ' + a.siid + '.' + a.aiid + ' -> code ' + code); return r; } // ---------------------------------------------------------------- States anlegen /* createStateAsync statt setObjectNotExistsAsync: letzteres ist Adapter- * Entwickler-API und in der javascript-Sandbox nicht verfügbar. * forceCreation=false lässt vorhandene States unangetastet. */ async function def(id, common, initial) { const full = BASE + '.' + id; const c = Object.assign({ read: true, write: false, role: 'state' }, common); await createStateAsync(full, initial === undefined ? null : initial, false, c); /* createStateAsync laesst ein vorhandenes Objekt in Ruhe. Damit spaetere * Aenderungen am common (z. B. ein neuer smartName) auch bei bestehenden * States ankommen, wird es hier nachgezogen. */ try { await extendObjectAsync(full, { common: c }); } catch (e) { log('common von ' + id + ' nicht aktualisierbar: ' + e.message, 'warn'); } } async function createStates() { // --- Anzeige await def('info.status', { name: 'Status (Code)', type: 'number', role: 'value' }); await def('info.statusText', { name: 'Status', type: 'string', role: 'text' }); await def('info.battery', { name: 'Akku', type: 'number', role: 'value.battery', unit: '%' }); await def('info.error', { name: 'Fehlercode', type: 'number', role: 'value' }); await def('info.cleanMode', { name: 'Reinigungsart', type: 'number', role: 'value' }); await def('info.fanMode', { name: 'Saugstufe', type: 'number', role: 'value' }); await def('info.waterMode', { name: 'Wassermenge', type: 'number', role: 'value' }); await def('info.online', { name: 'erreichbar', type: 'boolean', role: 'indicator.reachable' }); await def('info.lastUpdate', { name: 'letzte Abfrage', type: 'string', role: 'text' }); // --- Alexa-Schalter. Echte Schalter (read+write), nicht Buttons: // dadurch wirkt auch "aus", und die Routine braucht kein Zuruecksetzen. await def('Sauger', { name: 'Sauger (ganzes Haus, saugen und wischen)', type: 'boolean', role: 'switch', write: true, smartName: { de: 'Sauger', smartType: 'SWITCH' }, }, false); await def('Sauger_Wischen', { name: 'Sauger wischen (nur wischen)', type: 'boolean', role: 'switch', write: true, smartName: { de: 'Sauger wischen', smartType: 'SWITCH' }, }, false); await def('Sauger_Pause', { name: 'Sauger Pause', type: 'boolean', role: 'switch', write: true, smartName: { de: 'Sauger Pause', smartType: 'SWITCH' }, }, false); // --- Räume. Bewusst OHNE smartName: erst Grundfunktion testen, dann // einzeln fuer Alexa freigeben (smartName im Objekt ergaenzen). for (const [name, r] of Object.entries(ROOMS)) { await def('raum.' + name, { name: name + ' saugen (Raum-ID ' + r.id + ')', type: 'boolean', role: 'switch', write: true, smartName: { de: r.alexa, smartType: 'SWITCH' }, }, false); } } // ---------------------------------------------------------------- Ablaufsteuerung let pollTimer = null; let busy = false; // verhindert überlappende Funkdialoge /* Kurze Pause zwischen Funkbefehlen. Ohne sie verschluckt das Geraet den * naechsten Aufruf -- vier miIO-Dialoge in Folge sind ihm zu viel. */ const pause = ms => new Promise(r => setTimeout(r, ms)); /** Vor jedem Start: Saugstufe, Wassermenge und Reinigungsart setzen. */ async function applyDefaults(mode) { await action(ACT.SET_FAN, [{ piid: 13, value: FAN_MAX }]); await pause(1500); await action(ACT.SET_WATER, [{ piid: 12, value: WATER_MID }]); await pause(1500); await action(ACT.SET_MODE, [{ piid: 10, value: mode }]); await pause(1500); } /* Ganzes Haus laeuft ueber die parameterlose Action 2.1. * 17.1 start-clean mit piid 8 = FullClean und leerem piid 34 wird vom Geraet * mit code -9999 abgelehnt -- den leeren String mag es nicht. * Reinigungsart, Saugstufe und Wassermenge stehen durch applyDefaults() ohnehin * schon, 2.1 startet dann einfach mit diesen Einstellungen. */ async function startWholeHome(mode) { await applyDefaults(mode); await action(ACT.START_SWEEP); } /* Raumreinigung. piid 34 traegt die Raumliste als Array-String ("[6]") -- * exakt das Format, das sich das Geraet bei einem Zeitplan-Lauf selbst setzt * (in 17.34 mitzulesen, dort steht dann z. B. "[7,6]"). * * BEWUSST OHNE applyDefaults: Der Raumbefehl scheiterte reproduzierbar, wenn * kurz davor die drei Setzbefehle liefen -- als Einzelaufruf funktioniert er. * Die Modi stehen ohnehin noch vom letzten Start. */ async function startRooms(ids) { await action(ACT.START_CLEAN, [{ piid: 8, value: CLEAN_AREA }, { piid: 34, value: JSON.stringify(ids) }]); } /* Stoppen heisst: Auftrag BEENDEN, nicht nur anhalten. * * Action 2.1/2.2 haelt lediglich die Fahrt an -- der Auftrag bleibt danach im * Zustand "Area Pause" stehen (17.9 = 9) und 17.34 traegt weiter die Raumliste. * Folgen: * - die App verweigert einen Moduswechsel ("Auftrag erst abbrechen") * - der Sauger nimmt die Reinigung spaeter von selbst wieder auf, etwa nachdem * in der Station die Mopps gewaschen wurden * - es laeuft keine Nacharbeit, weil der Auftrag nie abgeschlossen wurde * * 17.3 stop-clean beendet ihn richtig: 17.9 geht auf Idle, 17.34 wird leer. */ async function stopAndHome() { await action(ACT.STOP_CLEAN); await pause(1500); await action(ACT.CHARGE); } // ---------------------------------------------------------------- Polling async function poll() { if (busy || stopping) return; busy = true; try { const p = await getProps([ { siid: 2, piid: 2 }, // Status { siid: 16, piid: 1 }, // Akku { siid: 17, piid: 35 }, // Fehlercode { siid: 17, piid: 10 }, // clean-mode { siid: 17, piid: 13 }, // fan-mode { siid: 17, piid: 12 }, // water-mode ]); const st = p['2.2']; if (st === undefined) { // gar nichts gekommen await setStateAsync(BASE + '.info.online', false, true); return; } await setStateAsync(BASE + '.info.online', true, true); await setStateAsync(BASE + '.info.status', st, true); await setStateAsync(BASE + '.info.statusText', STATUS[st] || ('unbekannt (' + st + ')'), true); if (p['16.1'] !== undefined) await setStateAsync(BASE + '.info.battery', p['16.1'], true); if (p['17.35'] !== undefined) await setStateAsync(BASE + '.info.error', p['17.35'], true); if (p['17.10'] !== undefined) await setStateAsync(BASE + '.info.cleanMode', p['17.10'], true); if (p['17.13'] !== undefined) await setStateAsync(BASE + '.info.fanMode', p['17.13'], true); if (p['17.12'] !== undefined) await setStateAsync(BASE + '.info.waterMode', p['17.12'], true); await setStateAsync(BASE + '.info.lastUpdate', formatDate(new Date(), 'TT.MM.JJJJ SS:mm:ss'), true); /* Schalter nachfuehren: faehrt er von selbst in die Station oder wird er * in der App gestoppt, sollen die Alexa-Schalter das widerspiegeln — * sonst zeigt die App "an", obwohl er laengst laedt. */ const running = RUNNING.includes(st); const paused = PAUSED.includes(st); const mopOnly = p['17.10'] === MODE_MOP; await syncSwitch('Sauger', running && !mopOnly); await syncSwitch('Sauger_Wischen', running && mopOnly); await syncSwitch('Sauger_Pause', paused); if (!running) for (const n of Object.keys(ROOMS)) await syncSwitch('raum.' + n, false); reschedule(running || paused ? POLL_ACTIVE : POLL_IDLE); } catch (e) { log('Polling-Fehler: ' + e.message, 'warn'); await setStateAsync(BASE + '.info.online', false, true); } finally { busy = false; } } /** Schalter nur anfassen, wenn er wirklich falsch steht (ack=true, kein Trigger). */ async function syncSwitch(id, soll) { const cur = await getStateAsync(BASE + '.' + id); if (!cur || cur.val !== soll) await setStateAsync(BASE + '.' + id, soll, true); } function reschedule(ms) { if (pollTimer) clearTimeout(pollTimer); if (stopping) return; pollTimer = setTimeout(poll, ms); } // ---------------------------------------------------------------- Steuer-Handler /** Gemeinsame Hülle: Fehler abfangen, danach zeitnah nachpollen. */ async function handle(what, fn) { if (busy) { log(what + ': Funk gerade belegt, ignoriert', 'info'); return; } busy = true; try { await fn(); log('Sauger: ' + what); } catch (e) { /* Der Status hilft bei der Ursachensuche: das Geraet lehnt Befehle auch * ab, wenn es gerade in der Station arbeitet (Mopp waschen/trocknen). */ let st = '?'; try { const s = await getStateAsync(BASE + '.info.statusText'); if (s) st = s.val; } catch (_) {} log('Sauger: ' + what + ' FEHLGESCHLAGEN — ' + e.message + ' (Status war: ' + st + ')', 'error'); } finally { busy = false; reschedule(3000); // Status bald nachziehen } } on({ id: BASE + '.Sauger', change: 'any', ack: false }, async obj => { if (obj.state.val) await handle('Start ganzes Haus (saugen+wischen)', () => startWholeHome(MODE_BOTH)); else await handle('Stop und zur Basis', () => stopAndHome()); }); on({ id: BASE + '.Sauger_Wischen', change: 'any', ack: false }, async obj => { if (obj.state.val) await handle('Start nur wischen', () => startWholeHome(MODE_MOP)); else await handle('Stop und zur Basis', () => stopAndHome()); }); on({ id: BASE + '.Sauger_Pause', change: 'any', ack: false }, async obj => { if (obj.state.val) await handle('Pause', () => action(ACT.PAUSE)); else await handle('Fortsetzen', () => action(ACT.START_SWEEP)); }); for (const [name, r] of Object.entries(ROOMS)) { on({ id: BASE + '.raum.' + name, change: 'any', ack: false }, async obj => { if (obj.state.val) await handle('Start Raum ' + name + ' (Raum-ID ' + r.id + ')', () => startRooms([r.id])); else await handle('Stop und zur Basis', () => stopAndHome()); }); } // ---------------------------------------------------------------- Start / Ende onStop(() => { stopping = true; if (pollTimer) clearTimeout(pollTimer); try { if (sock) sock.close(); } catch (e) { /* egal */ } }, 2000); (async () => { await createStates(); openSocket(); log('Saugroboter-Skript gestartet — ' + IP + ', ' + Object.keys(ROOMS).length + ' Räume bekannt'); await poll(); })(); 9. Alexa-Anbindung (optional) Die Schalter tragen bereits ein smartName, der iot-Adapter meldet sie an Alexa. In der Alexa-App einmal „nach neuen Geräten suchen" — fertig. Warum echte Schalter und keine Buttons: Ein State mit role: button reagiert nur auf „ein" und bleibt danach auf true stehen. Deshalb brauchen Routinen für solche States immer den Dreisprung einschalten → 5 Sekunden warten → ausschalten. Bei einem echten Schalter, den das Polling nachführt, entfällt das: Fährt der Sauger von selbst in die Station, springt der Schalter zurück auf false, und „aus" funktioniert wie erwartet. ⚠️ Den Geräten keine Alexa-Raumgruppen zuweisen. Läge ein Gerät namens „Sauger Wohnzimmer" in der Alexa-Gruppe Wohnzimmer, würde „Alexa, Wohnzimmer aus" den Sauger mitschalten. Beim Einrichten den Gruppen-Assistenten überspringen. Für freie Formulierungen („Alexa, starte den Sauger") legt man in der Alexa-App eine Routine mit Sprachauslöser an, die den Schalter ein- oder ausschaltet. 10. Was nicht geht Kartenansicht. Die Kartendaten kommen als proprietärer Binärstrom und liegen ohnehin in der Cloud; get-map-data liefert lokal nur eine Referenz. Zonenreinigung per Koordinaten. Nicht probiert. Nacharbeit nach einem Abbruch erzwingen. Es gibt Actions dafür (17.40 set-wash-mop, 17.41 set-dry-mop, 17.42 set-collect-dust, jeweils bool), getestet habe ich sie nicht. 11. Anpassung an andere xtl-Modelle Es gibt mindestens xm2216 (P20 Pro), xt3411 (X1 MAX), xt3401, 3505, 3512. Das Vorgehen: Modellkürzel aus dem Token-Extractor ablesen Passende Spec von https://miot-spec.org/ laden siid/piid/aiid im Skript gegen die eigene Spec prüfen — Service 2 und 16 sind meist gleich, der herstellereigene Block (bei mir 17) kann abweichen Raum-IDs über die Zeitplan-Methode aus Abschnitt 6 ermitteln Wenn jemand das für ein anderes Modell durchzieht: gerne hier posten, dann sammeln wir die Unterschiede. Getestet ausschließlich mit dem JONR T5 Pro GEN 1 - der Gen2 hat Sprachsteuerung wohl schon inkludiert, kostet aber deutlich mehr. Keine Gewähr, keine Wartungszusage — aber Rückfragen beantworte ich gern; Über Verbesserungen oder Vorschläge freue ich mich sehr. Erarbeitet unter Zuhilfenahme von KI.
  • Hilfe für Skripterstellung mit Blockly

    7k 81k
    7k Themen
    81k Beiträge
    T
    @Murphy-0 sagte: Hier steuerst du ja den WR2 auch fix mit 5% an. Ausserdem triggerst du nach festem Zeitplan, soweit kein Problem. Nir innerhalb vom Blockly noch zusätzlich Trigger kenn ich so nicht, wird auch nicht funktionieren. Ja, ich wollte erstma den WR2 mit einem festen Wert ansteuern und nur WR1 regeln. Der Trigger ist einzeln.
  • Hilfe für Skripterstellung mit Node-RED

    966 13k
    966 Themen
    13k Beiträge
    OliverIOO
    @HomeUser dann würde ich einen issue auf github beim iobroker node-red adapter machen. es könnte eine nicht gut bereinigte ipv6 addresse sein so das aus http://[::1]:1880/admin/flows (localhost) das wird http://:::1880/admin/flows
  • Scriptsammlung Vol. 2

    Angeheftet
    3
    3 Stimmen
    3 Beiträge
    7k Aufrufe
    NegaleinN
    Achtung: Diese Scripts sind teils auch ungetestet bzw. nur vom Ersteller getestet worden. Blockly diverse Scripte Schimpfwortgenerator (BananaJoe, Nikolai Radke) Ein Schimpfwortgenerator ioBroker-Forum-Thread: Schimpfwortgenerator Witze aus API (mading) Ein Witzegenerator ioBroker-Forum-Thread: Witzegenerator Bilder mittels LLM ChatGPT Vision ananalysieren (David G.) Bilder mit ChatGPT ananalysieren ioBroker-Forum-Thread: Bilder mittels LLM ChatGPT Vision ananalysieren Visualisierung Agentdvr-Aufnahmen in der Visualisierung darstellen (David G.) Agentdvr-Aufnahmen anzeigen ioBroker-Forum-Thread: Agentdvr-Aufnahmen in der Visualisierung darstellen Trash HTML Widget VIS2 (skvarel) Trash HTML Widget VIS2 ioBroker-Forum-Thread: Trash HTML Widget VIS2 GitHub GitHub
  • Scriptsammlung Vol. 2 -- Diskussion

    Angeheftet
    71
    1 Stimmen
    71 Beiträge
    26k Aufrufe
    NegaleinN
    @fischi87 sagte: vielleicht etwas für die Sammlung. habs in "Hardware generell" eingetragen
  • verschiedene Skripte

    javascript
    5
    1
    0 Stimmen
    5 Beiträge
    354 Aufrufe
    H
    @sigi234 Hallo werde am Wochenende es umändern zum vereinfachen.
  • Proxmox-Updater (Host/LXC/VM) auch ioBroker,piHole,etc

    64
    3 Stimmen
    64 Beiträge
    15k Aufrufe
    Bass-TB
    Teaser #2 ;) [image: 1787495539661-92147e55-fdce-41f4-a994-befe162a0ca4-image.jpeg]
  • Blockly + Javascripte aus einer script.json extrahieren

    24
    5
    3 Stimmen
    24 Beiträge
    1k Aufrufe
    Michael SchmittM
    Nice, muss ich mir gleich nach der Arbeit anschauen. @shadowhunter23 .....
  • Anker Solix 4 Pro per Modbus regeln – 0-einspeisung mit ioB

    1
    0 Stimmen
    1 Beiträge
    149 Aufrufe
    Niemand hat geantwortet
  • NodeRed Iobroker Converter (WS Adapter kompatibel)

    node-red communication
    1
    0 Stimmen
    1 Beiträge
    92 Aufrufe
    Niemand hat geantwortet
  • Farbe bei Yahka(HomeKit) mit Yeelight

    javascript
    23
    0 Stimmen
    23 Beiträge
    3k Aufrufe
    AsgothianA
    Mir war schon klar was gemeint ist. Du musst folgendes testen: im Objektbaum, in den Datenpunkt der die Farbe steuert eine Zahl mit Nachkommastelle eingeben - stürzt der Yeelight Adapter dann ab ? an gleicher Stelle schnell, sprich 2 mal pro Sekunde jeweils einen anderen gültigen Wert eingeben. Stürzt der Yeelight Adapter dann ab ? an gleicher Stelle einen Wert ausserhalb der von Dir vorgegebenen Grenzen eingeben. Stürzt der Yeelight Adapter dann ab ? an gleicher Stelle Text eingeben. Stürzt der Yeelight Adapter dann ab ? A.
  • [Vorlage] Anwesenheitssimulation mit dauerhaftem Lernen

    javascript template security
    22
    2 Stimmen
    22 Beiträge
    2k Aufrufe
    G
    Moin, erstmal danke für diese Skript. Idee und Umsetzung ist hervorragend. ich habe mich in der vergangene Woche etwas an das ausprobieren gemacht. Meine Lampen habe ich über die Enums eingebunden dafür habe ich jeweils den direkten Schaltaktor der Lampe .on bzw. .state in das enum kopiert. Die Lampen werden erkannt und im Snapshot abgelegt (ich habe mir den Pfad nach javascript Verzeichniss const DP_STORAGE: string = 'javascript.0.Anwesenheitssimulation.Snapshot_DB'; abgelegt, finde ich persönlich etwas angenehmer den Datenpunkt dort liegen zu haben. Technisch sollte das egal sein. Dann ist mir ein Fehler aufgefallen. Ich habe angefangen mit 2 dimmbaren Lampen zu testen. Die erste ist über hue verknüpft und die zweite ist mittels zigbee verknüpft. Um es etwas übersichtlicher zu machen verzichte ich hier auf hue und beschreibe mein Problem mit dem Zigbee Leuchtmittel. Der Status (on/off) wurde immer korrekt vom Skript erkannt. Der Dimmwert wurde jedoch nicht erkannt. Das hat zu folgendem Snapshot geführt: ,"75":[{"zigbee2mqtt.0.0xxxxxxxxxxxxxxxcb.state":{"on":true}}] ,"76":[{"zigbee2mqtt.0.0xxxxxxxxxxxxxxxcb.state":{"on":true}}] ,"77":[{"zigbee2mqtt.0.0xxxxxxxxxxxxxxxcb.state":{"on":true}}] Also Fehlersuche durchgeführt. Das Ergebnis dann doch relativ simpel. Scheinbar ist Javascript (manchmal) CaseSensitiv. In der Zeile 93 werden die Namen des datenpunkte für den Dimmwert zusammengebaut. const briCandidates = ['LEVEL', 'ACTUAL', 'BRIGHTNESS', 'DIMMER', 'SET']; der von Zigbee angelegte Datenpunkt heißt jedoch .brightness also habe ich die Zeile entsprechend geändert und das .brightness mit eingefügt. const briCandidates = ['LEVEL', 'ACTUAL', 'BRIGHTNESS', 'brightness', 'bri', 'DIMMER', 'SET']; und schon wird der Dimmwert der Zigbeeleuchte erkannt. Der Snapshot sah dann wie folgt aus: ,"82":[{"zigbee2mqtt.0.0xf0d1b800001d0ccb.state":{"on":true}}] ,"83":[{"zigbee2mqtt.0.0xf0d1b800001d0ccb.state":{"on":true}}] ,"84":[{"zigbee2mqtt.0.0xf0d1b800001d0ccb.state":{"on":true,"bri":25}}] //Änderung mit .brightness aktiv ,"85":[{"zigbee2mqtt.0.0xf0d1b800001d0ccb.state":{"on":true,"bri":25}}] ,"86":[{"zigbee2mqtt.0.0xf0d1b800001d0ccb.state":{"on":true}}] //Rückgängig machen zur Verifizierung ,"87":[{"zigbee2mqtt.0.0xf0d1b800001d0ccb.state":{"on":true,"bri":25}}] ,"88":[{"zigbee2mqtt.0.0xf0d1b800001d0ccb.state":{"on":true,"bri":25}}] Anschließend habe ich noch .bri hinzugefügt. denn nun wird auch der Dimmwert der hue Lampe erkannt. Denn Hue verwendet den Namen .bri um das dimmen anzusteuern. Meine Zeile sieht nun so aus. const briCandidates = ['LEVEL', 'ACTUAL', 'BRIGHTNESS', 'brightness', 'bri', 'DIMMER', 'SET']; Die anderen Namen habe ich nicht in Verwendung. daher habe ich sie nicht angepasst. Ggf könne wir hier ja eine Sammlung erstellen die alle Schreibweisen enthält. .BRIGHTNESS lässt sich ja auch .Brightness schreiben. Warum scheint Javascript nur manchmal CaseSensitiv zu sein? in Zeile 84&85 wird der Pfad für den Status der Lampe gebaut. if (obj && obj.type === 'channel') { if (existsState(`${id}.STATE`)) powerId = `${id}.STATE`; else if (existsState(`${id}.ON`)) powerId = `${id}.ON`; } An dieser Stelle ist on und state ebenfalls als CapsLock geschrieben. hue verwendet die Bezeichnung .on und zigbee verwendet .state (beides vollständig klein geschrieben). An dieser Stelle funktioniert die Erkennung des Zustandes jedoch.
  • Dynamische SVG-Progress-Bar

    javascript multimedia monitoring
    7
    1
    0 Stimmen
    7 Beiträge
    2k Aufrufe
    Ro75R
    Version Version 1.0.4 !!!ACHTUNG WICHTIG!!! Wenn schon eine vorherige Version vom Script genutzt wurde, MÜSSEN mit dieser Version die Übergabewerte im Funktionskopf anpasst werden - kurz gesagt: die Reihenfolge hat sich geändert! *erweiterte Konfiguration: Neuer Parameter Zweck decimalPlaces Anzahl Nachkommastellen für das Hauptlabel fontWeight Schriftstärke (normal, bold, semibold, 400–700) fontStyle Schriftstil (normal, italic, oblique) leftLabelText Text für das linke Label (nur horizontal) rightLabelText Text für das rechte Label (nur horizontal) showMainLabel Hauptlabel ein-/ausschalten Aufgrund der neuen Parameter, wurde die Reihenfolge für die Übergabe der Parameter in der Funktion neu angeordnet Alle weiteren Informationen findest Du im Post #1. Das Script und der Beispielaufruf, sowie die Parameterbeschreibung sind aktualisiert. Ro75.
  • mqtt-Abruf WiCAN-OBD-Dongle mit mqtt-Adapter und Blockly

    15
    1
    0 Stimmen
    15 Beiträge
    4k Aufrufe
    K
    Nachtrag: Per Script (mit Hilfe von ChatGPT) kann ich mir auch die mobilen Daten herunterladen, wenn das Auto nicht im WLAN steht. Hatte dies ein paar Tage laufen - aber irgendwie benötigte ich die Daten noch immer nicht und deshalb habe ich es wieder deaktiviert. "Datensparsamkeit" :) Es bleibt dabei - ich hole mir die Daten nur über Wifi und MQTT.
  • [Vorlage] Anwesenheitssimulation - Script

    Verschoben
    272
    1
    1 Stimmen
    272 Beiträge
    91k Aufrufe
    S
    Hallo, ich weiß der Thread ist schon etwas älter. Seit kurzem funktioniert das Script leider nicht mehr, es hat sehr lange ohne Probleme funktioniert. Er kann wohl im Script laut Fehlermeldung nicht mehr die aktuelle Zeit bestimmen: Error: TypeError: Cannot read properties of null (reading 'getTime') Über eine Hilfe wäre ich sehr dankbar :) javascript.0 2026-07-15 14:00:29.083 error script.js.Licht.Anwesenheit_Simulator: at Script.runInContext (node:vm:149:12) javascript.0 2026-07-15 14:00:29.083 error script.js.Licht.Anwesenheit_Simulator: at script.js.Licht.Anwesenheit_Simulator:522:3 javascript.0 2026-07-15 14:00:29.083 error script.js.Licht.Anwesenheit_Simulator: at script.js.Licht.Anwesenheit_Simulator:147:1 javascript.0 2026-07-15 14:00:29.083 error script.js.Licht.Anwesenheit_Simulator: at Astrozeiten (script.js.Licht.Anwesenheit_Simulator:424:15) javascript.0 2026-07-15 14:00:29.083 error script.js.Licht.Anwesenheit_Simulator: at Array.forEach (<anonymous>) javascript.0 2026-07-15 14:00:29.083 error script.js.Licht.Anwesenheit_Simulator: at script.js.Licht.Anwesenheit_Simulator:425:18 javascript.0 2026-07-15 14:00:29.083 error script.js.Licht.Anwesenheit_Simulator: at getAstroDate (/opt/iobroker/node_modules/iobroker.javascript/build/lib/sandbox.js:1980:40) javascript.0 2026-07-15 14:00:29.083 error script.js.Licht.Anwesenheit_Simulator: Error: TypeError: Cannot read properties of null (reading 'getTime') javascript.0 2026-07-15 14:00:29.082 info script.js.Licht.Anwesenheit_Simulator: AWS-Logging: Astrokeyword nauticalDusk: Datum: 15:07 ----- Uhrzeit 23:17 javascript.0 2026-07-15 14:00:29.082 info script.js.Licht.Anwesenheit_Simulator: AWS-Logging: Astrokeyword dusk: Datum: 15:07 ----- Uhrzeit 22:08 javascript.0 2026-07-15 14:00:29.082 info script.js.Licht.Anwesenheit_Simulator: AWS-Logging: Astrokeyword sunset: Datum: 15:07 ----- Uhrzeit 21:21 javascript.0 2026-07-15 14:00:29.082 info script.js.Licht.Anwesenheit_Simulator: AWS-Logging: Astrokeyword sunsetStart: Datum: 15:07 ----- Uhrzeit 21:17 javascript.0 2026-07-15 14:00:29.082 info script.js.Licht.Anwesenheit_Simulator: AWS-Logging: Astrokeyword goldenHour: Datum: 15:07 ----- Uhrzeit 20:28 javascript.0 2026-07-15 14:00:29.082 info script.js.Licht.Anwesenheit_Simulator: AWS-Logging: Astrokeyword solarNoon: Datum: 15:07 ----- Uhrzeit 13:11 javascript.0 2026-07-15 14:00:29.082 info script.js.Licht.Anwesenheit_Simulator: AWS-Logging: Astrokeyword goldenHourEnd: Datum: 15:07 ----- Uhrzeit 05:54 javascript.0 2026-07-15 14:00:29.082 info script.js.Licht.Anwesenheit_Simulator: AWS-Logging: Astrokeyword sunriseEnd: Datum: 15:07 ----- Uhrzeit 05:05 javascript.0 2026-07-15 14:00:29.082 info script.js.Licht.Anwesenheit_Simulator: AWS-Logging: Astrokeyword sunrise: Datum: 15:07 ----- Uhrzeit 05:00 javascript.0 2026-07-15 14:00:29.082 info script.js.Licht.Anwesenheit_Simulator: AWS-Logging: Liste der gegenwärtigen Astrozeiten javascript.0 2026-07-15 14:00:29.081 info script.js.Licht.Anwesenheit_Simulator: AWS-Logging: Arrays scheinen konsistent angelegt worden zu sein. javascript.0 2026-07-15 14:00:29.081 info script.js.Licht.Anwesenheit_Simulator: AWS-Logging: Script: AWS Version 1.30 vom 02.08.2024 - DEBUG ist eingeschaltet javascript.0 2026-07-15 14:00:29.056 info script.js.Licht.Anwesenheit_Simulator: start JavaScript (Javascript/js) javascript.0 2026-07-15 14:00:29.009 info script.js.Licht.Anwesenheit_Simulator: Stopping script
  • Snapshot senden über email adapter

    Verschoben
    21
    0 Stimmen
    21 Beiträge
    8k Aufrufe
    jolicJ
    unsinn ;-(
  • [HowTo] Script Sticky Notes (PostIt) für VIS und Telegram

    164
    7
    7 Stimmen
    164 Beiträge
    39k Aufrufe
    holly200289H
    Mir ist auch aufgefallen das wenn der Satz eine gewisse länge hat das dann, im Telegramm nicht mehr das Löschmenü kommt, was immer automatisch mit erscheint wenn ich eine Nachricht verschicke. Liegt das vielleicht an der Text länge ?
  • [gelöst} NSPanel TypeScript v5.1.1.7 Fehler !!

    3
    0 Stimmen
    3 Beiträge
    1k Aufrufe
    GregorSG
    Nein, habe das Script so übernommen und angepasst. Allerdings komme ich gerade aus dem Urlaub und sehe, dass das Display dunkel bleibt. Bei Berühren des Displays kommt der Screensaver und danach wird es wieder dunkel. alias.0.NSPanel.1.ScreensaverInfo.activeBrightness.ACTUAL =100 ActiveDimmodeBrightness und Trigger Dimmode gibt es nicht mehr War mein Fehler, gibt es doch. TriggerDimMode on, jetzt habe ich wieder eine Anzeige.
  • Wie? Solar Produktion über Json Monatlich berechnen

    16
    0 Stimmen
    16 Beiträge
    4k Aufrufe
    machamsterM
    Ok super. Vielen Dank. Werde mir das morgen mal in Ruhe anschauen 😎
  • MQTT Keepalive im Skript

    50
    0 Stimmen
    50 Beiträge
    14k Aufrufe
    FichtendickichtF
    Vielen Dank für die ausführlichen Informationen. Ich betreibe selbst eine Victron Anlage mit einer Cerbo GX, IOBroker als Docker in einem Synology NAS DS218+. Bisher habe ich immer mittels Modbus ausgelesen, MQTT scheint mir aber viel eleganter. Ich habe das Keepalive mittels Blocky so gelöst: [image: 1783069381056-9ac29698-7d99-40cc-9520-8e2d58a3dac5-image.jpeg] Leider muss ich feststellen, dass scheinbar alle Werte publiziert werden und nicht nur die unter ["battery/+/Dc/#"]. Die Übersetzung der Werte von JSON auf Dezimalwerte habe ich mittels eines ALIAS gelöst, da kann man als Read-Funktion eine Java-Script (JS) Funktion einfügen die JSON in ein FLOAT umwandelt und als Fallback "NULL" ausgibt: [image: 1783069574957-9ab03ccd-1244-42f8-b1e5-4aebeea11c31-image.jpeg] Hier die Ausgabe die dann auch mittels des Adapters sql0 in eine Datenbank archiviert werden kann: [image: 1783069712165-6bd23d7c-15ef-4e5c-92bf-1d83165ec61f-image.jpeg] EDIT: Ich habe noch eine Einschränkung festgestellt: Das Blockly Script funktioniert nur mit dem mqtt Adapter, mit dem mqtt-client Adapter funktioniert das nicht!
  • Alexa Shopping List mit Bring synchronisieren

    187
    0 Stimmen
    187 Beiträge
    61k Aufrufe
    LocosL
    @mcBirne danke für dein Feedback. habe das Skript nochmal etwas überarbeitet. Siehe vorherigen Post. Liebe Grüße
  • Blockly funktioniert plötzlich nicht mehr

    15
    1
    0 Stimmen
    15 Beiträge
    5k Aufrufe
    H
    @mickemup sagte: Aber generell...Adapter immer frisch starten ist nur Symptombekämpfung. Man sollte versuchen der Fehlerquelle auf den Grund zu gehen. ich weis, wenn ich mal etwas Zeit habe möchte ich sowieso alle Shelly Geräte auf mqtt umstellen. Dann dürfte das Problem behoben sein.
  • Shuttercontrol Sonnenschutz funktioniert nicht (Homematic)

    7
    6
    0 Stimmen
    7 Beiträge
    3k Aufrufe
    M
    Seit der Haken bei "Überprüfen des aktuellen Rollladenstatus" gesetzt ist geht es jetzt.

427

Online

33.0k

Benutzer

83.7k

Themen

1.3m

Beiträge