🧩Skripte: JavaScript und Blockly

Die Logik im Smart Home steckt meist im JavaScript-Adapter (javascript.0). Er führt JavaScript, TypeScript und Blockly aus. Blockly ist eine grafische Oberfläche, die im Hintergrund ebenfalls JavaScript erzeugt – hier siehst du beides nebeneinander.

🧪Drei Beispiele

Trigger:

Meldet der Bewegungsmelder im Flur Bewegung und ist es dunkel (< 50 lux), geht das Flurlicht an und nach 2 Minuten wieder aus.

🧩 Blockly (nachgebaute Darstellung)
Falls Objekt Bewegung Flurwurde geändert ▾Auslösung durchegal ▾
falls value= ▾wahrund ▾Zustandswert nehmen Wert ▾ vom Objekt ID Helligkeit Flur< ▾50
mache
steuere Deckenlampe Flur mit wahr
stop lichtTimer
Ausführen lichtTimer in 2Min ▾
steuere Deckenlampe Flur mit falsch
debug output „Flurlicht nach 2 Minuten aus“ log ▾
⚙️ daraus erzeugtes JavaScript (wie der Blockly-Generator)
var lichtTimer;

on({ id: 'hm-rpc.0.MUSTER0003.1.MOTION' /* Bewegung Flur */, change: 'ne' }, async (obj) => {
  let value = obj.state.val;
  let oldValue = obj.oldState.val;
  if (value == true && getState('hm-rpc.0.MUSTER0003.1.ILLUMINATION').val < 50) {
    setState('shelly.0.shellyplus1#0a1b2c3d4e02#1.Relay0.Switch' /* Deckenlampe Flur */, true);
    (() => { if (lichtTimer) { clearTimeout(lichtTimer); lichtTimer = null; }})();
    lichtTimer = setTimeout(async () => {
      lichtTimer = null;
      setState('shelly.0.shellyplus1#0a1b2c3d4e02#1.Relay0.Switch' /* Deckenlampe Flur */, false);
      console.log('Flurlicht nach 2 Minuten aus');
    }, 120000);
  }
});
✍️ Dasselbe von Hand im JavaScript-Adapter geschrieben
// Licht bei Bewegung – JavaScript-Adapter (javascript.0)
const BEWEGUNG = 'hm-rpc.0.MUSTER0003.1.MOTION';
const HELLIGKEIT = 'hm-rpc.0.MUSTER0003.1.ILLUMINATION';
const LAMPE = 'shelly.0.shellyplus1#0a1b2c3d4e02#1.Relay0.Switch';
let timer = null;

on({ id: BEWEGUNG, change: 'ne' }, (obj) => {
    if (obj.state.val !== true) return;               // nur „Bewegung erkannt“
    if (getState(HELLIGKEIT).val >= 50) return;        // hell genug → nichts tun

    setState(LAMPE, true);                             // Befehl: ack = false
    if (timer) clearTimeout(timer);                    // Nachlauf neu starten
    timer = setTimeout(() => {
        setState(LAMPE, false);
        timer = null;
        log('Flurlicht nach 2 Minuten aus');
    }, 2 * 60 * 1000);
});
change: 'ne' vs. 'any': Viele Bewegungsmelder senden bei anhaltender Bewegung erneut true. Mit 'ne' (not equal) wird das ignoriert – das Licht geht nach 2 Minuten aus, obwohl noch jemand da ist. Mit 'any' startet jede Meldung den Nachlauf neu. Probiere es im Simulator aus.

📚Die wichtigsten Funktionen

Auswahl aus der Dokumentation des JavaScript-Adapters (ioBroker.javascript, docs/en/javascript.md). IDs gekürzt.

on / subscribe

Abonniert Änderungen. change: 'ne' (Wert anders), 'any' (jede Aktualisierung), 'gt', 'lt' … Weitere Filter: val, ack, q, oldVal …

on({ id: 'hm-rpc.0.MUSTER0003.1.MOTION', change: 'ne' }, (obj) => {
    log(obj.id + ': ' + obj.oldState.val + ' → ' + obj.state.val);
});
// Kurzform: on('ID', cb) → change ist dann 'ne'
// Objektform ohne change → 'any'

setState

Schreibt einen Wert. Drittes Argument ist ack – für Geräte weglassen (Befehl), für eigene Datenpunkte true.

setState('shelly.0.…Relay0.Switch', true);          // Befehl (ack = false)
setState('0_userdata.0.Nachtmodus', true, true);     // Tatsache (ack = true)
setState('0_userdata.0.Nachtmodus', { val: true, ack: true });

getState

Liest den aktuellen State mit val, ack, ts, lc, from. Existiert er nicht, kommt { val: null, notExist: true } und eine Warnung.

const t = getState('zigbee.0.00158d0000000b01.temperature');
log(t.val + ' °C, geändert: ' + new Date(t.lc).toLocaleString());

schedule

Zeitplan im Cron-Format (5 Felder, mit Sekunden 6) oder astronomisch (sunrise, sunset …).

schedule('0 22 * * *', () => { /* täglich 22:00 */ });
schedule('*/3 * * * * *', () => { /* alle 3 Sekunden (6 Felder) */ });
schedule({ astro: 'sunset', shift: 30 }, () => { /* 30 min nach Sonnenuntergang */ });

setTimeout / clearTimeout

Wie in normalem JavaScript. Beim Stoppen eines Skripts räumt der Adapter laufende Timer und Abos auf.

let t = setTimeout(() => setState(LAMPE, false), 2 * 60 * 1000);
clearTimeout(t);

createState / sendTo / $-Selektor

Eigene Datenpunkte anlegen, Nachrichten an andere Instanzen schicken, viele States über Enums auf einmal ansprechen.

createState('0_userdata.0.Zaehler', 0, { type: 'number', role: 'value' });
sendTo('history.0', 'getHistory', { id, options }, (res) => { … });
$('state(functions=Licht)(rooms=Wohnzimmer)').setState(false);

🧠Wie ein Skript intern abläuft

  States-DB                  javascript.0
 ┌────────────┐ stateChange ┌─────────────────────────────┐
 │ MOTION     │ ──────────▶ │ für jedes Abo: passt der    │
 │ val: true  │             │ Filter? (id, change, ack, q)│
 └────────────┘             │  └ ja → Callback ausführen  │
       ▲                    │     setState(LAMPE, true)   │
       │                    └──────────────┬──────────────┘
       │  setState(LAMPE, true), ack:false │
       └───────────────────────────────────┘
   danach: Adapter shelly.0 schaltet und bestätigt (ack:true)
💡 Skripte als Objekte
Jedes Skript ist selbst ein Objekt (Typ script) unter script.js.…, mit dem Quelltext in common.source. Bei Blockly-Skripten hängen die Blöcke zusätzlich als kodiertes XML am Quelltext.
⚠️ Endlosschleifen vermeiden
Ein Skript, das auf change: 'any' eines States hört und denselben State schreibt, ruft sich selbst immer wieder auf. Abhilfe: 'ne', ein ack-Filter oder ein anderer Ziel-State.
✅ Eigene Datenpunkte
Hilfswerte (Nachtmodus, Zähler, Texte) gehören nach 0_userdata.0 – dort bleiben sie auch erhalten, wenn der JavaScript-Adapter neu installiert wird.