Nur falls es einen Interessiert, hier der Code, als Grundlage habe ich das hier genommen:
https://draeger-it.blog/shelly-scripti…-programmieren/
JavaScript
/********************************************************************************
* Shelly BLU Motion → Licht einschalten (nur bei Dunkelheit)
********************************************************************************/
const CONFIG = {
bluMotionMac: "e8:e0:7e:bf:36:5b", // MAC deines BLU Motion (kleingeschrieben!)
lightOnTime: 180, // Sekunden wie lange Licht an bleibt
luxThreshold: 50, // Nur schalten wenn dunkler als X Lux
// Zeitfenster (optional, zusätzlich zu Lux)
timeWindow: {
enabled: false,
winterStart: 10, // Oktober
winterEnd: 3, // März
winterHours: { start: 18, end: 8 }, // 18:00 - 08:00
summerHours: { start: 21, end: 6 } // 21:00 - 06:00
}
};
/************* BTHome v2 Decoder **************/
const BTHOME_SVC_ID = "fcd2";
const T = { u8:0, i8:1, u16:2, i16:3, u24:4, i24:5 };
const BTH = {
0x00: { n:"pid", t:T.u8 },
0x01: { n:"battery", t:T.u8 },
0x05: { n:"illuminance", t:T.u24, f:0.01 },
0x21: { n:"motion", t:T.u8 }
};
function utoi(num, bits) {
let m = 1 << (bits - 1);
return (num & m) ? (num - (1 << bits)) : num;
}
function getLE(buf, size) {
if (buf.length < size) return null;
let v = 0;
for (let i = size - 1; i >= 0; i--) v = (v << 8) | buf.at(i);
return v >>> 0;
}
function readTyped(t, buf) {
if (t === T.u8) return buf.length >= 1 ? buf.at(0) : null;
if (t === T.i8) return buf.length >= 1 ? utoi(buf.at(0), 8) : null;
if (t === T.u16) return utoi(getLE(buf, 2), 16) & 0xFFFF;
if (t === T.i16) return utoi(getLE(buf, 2), 16);
if (t === T.u24) return getLE(buf, 3) & 0xFFFFFF;
if (t === T.i24) return utoi(getLE(buf, 3), 24);
return null;
}
function bthomeUnpack(sd) {
if (typeof sd !== "string" || sd.length === 0) return null;
let dib = sd.at(0);
let enc = !!(dib & 0x01);
let ver = dib >> 5;
if (ver !== 2) return null;
if (enc) return { encryption: true };
let buf = sd.slice(1);
let out = { encryption: false, version: 2 };
while (buf.length > 0) {
let id = buf.at(0);
let spec = BTH[id];
if (!spec) break;
buf = buf.slice(1);
let need = (spec.t === T.u8 || spec.t === T.i8) ? 1 :
(spec.t === T.u16 || spec.t === T.i16) ? 2 : 3;
let raw = readTyped(spec.t, buf);
if (raw === null) break;
let val = (typeof spec.f === "number") ? raw * spec.f : raw;
out[spec.n] = val;
buf = buf.slice(need);
}
return out;
}
/************* State **************/
let lastMotion = null;
let lastPid = -1;
let lightTimer = null;
/************* Zeitprüfung **************/
function isNightTime() {
if (!CONFIG.timeWindow.enabled) return true;
let now = new Date();
let hour = now.getHours();
let month = now.getMonth() + 1;
let isWinter = (month >= CONFIG.timeWindow.winterStart || month <= CONFIG.timeWindow.winterEnd);
let hours = isWinter ? CONFIG.timeWindow.winterHours : CONFIG.timeWindow.summerHours;
return (hour >= hours.start || hour < hours.end);
}
/************* Licht steuern **************/
function turnLightOn() {
print("[MOTION] Licht EIN für " + CONFIG.lightOnTime + " Sekunden");
Shelly.call("Switch.Set", { id: 0, on: true });
if (lightTimer !== null) Timer.clear(lightTimer);
lightTimer = Timer.set(CONFIG.lightOnTime * 1000, false, function() {
print("[MOTION] Licht AUS (Timer)");
Shelly.call("Switch.Set", { id: 0, on: false });
lightTimer = null;
});
}
/************* BLE Event Handler **************/
function onBleScan(event, res) {
if (event !== BLE.Scanner.SCAN_RESULT) return;
if (!res || res.addr !== CONFIG.bluMotionMac) return;
let sd = res.service_data;
if (event !== BLE.Scanner.SCAN_RESULT) return;
if (!res || res.addr !== CONFIG.bluMotionMac) return;
let sd = res.service_data;
if (!sd || typeof sd[BTHOME_SVC_ID] === "undefined") return;
let data = bthomeUnpack(sd[BTHOME_SVC_ID]);
if (!data || data.encryption) return;
// Duplikate filtern
if (typeof data.pid === "number") {
if (data.pid === lastPid) return;
lastPid = data.pid;
}
// Motion-Event verarbeiten
if (typeof data.motion === "number" && data.motion !== lastMotion) {
lastMotion = data.motion;
let lux = (typeof data.illuminance === "number") ? data.illuminance : 9999;
let isDark = (lux < CONFIG.luxThreshold);
let isNight = isNightTime();
print("[MOTION] " + (data.motion ? "DETECTED" : "CLEARED") +
" | Lux: " + lux + " | Dark: " + isDark + " | Night: " + isNight);
if (data.motion === 1 && isDark && isNight) {
turnLightOn();
}
}
}
/************* Start **************/
function startBle() {
let cfg = Shelly.getComponentConfig("ble");
if (!cfg || cfg.enable !== true) {
print("[ERROR] BLE ist deaktiviert!");
return;
}
let ok = BLE.Scanner.Start({
duration_ms: BLE.Scanner.INFINITE_SCAN,
active: true
});
if (!ok) {
print("[ERROR] BLE-Scanner konnte nicht gestartet werden!");
return;
}
BLE.Scanner.Subscribe(onBleScan);
print("[INIT] BLE-Scanner läuft, warte auf Motion Events...");
print("[CONFIG] MAC: " + CONFIG.bluMotionMac);
print("[CONFIG] Lux-Schwelle: " + CONFIG.luxThreshold);
print("[CONFIG] Licht-Dauer: " + CONFIG.lightOnTime + "s");
}
startBle();
Alles anzeigen