Gedimmter warmweißer LED-Streifen als gleichmäßige Lichtlinie in einer Deckenvoute über einer dunkelgrünen Wand

Shelly 0–10 V: Warmweiße COB-LEDs dimmen

Anzeige: Dieser Beitrag enthält Amazon-Partnerlinks (mit * markiert). Kaufst du darüber, erhalte ich eine kleine Provision, der Preis bleibt für dich gleich.

Für einen Freund habe ich eine dimmbare Beleuchtung im Flur und an der Treppe umgesetzt. Warmweiße COB-LED-Streifen liefern das Licht, ein 24-Volt-Netzteil mit 0–10-Volt-Eingang regelt die Versorgung, und ein Shelly-Dimmer übernimmt die Steuerung. Hier zeige ich, wie die Komponenten zusammenspielen und welche ich für das Projekt verwendet habe.

Hinweis: Ich zeige hier, wie es bei mir läuft. Nachbau auf eigene Gefahr: Mach vorher ein Backup deiner Daten und Einstellungen. Für Schäden oder Datenverlust übernehme ich keine Haftung.

Technische Daten im Überblick

  • Eingangsspannung 110–240 V~, Leistungsaufnahme unter 1,5 W, Neutralleiter erforderlich
  • 0/1-10V-Steuerausgang (Stromquelle) für dimmbare LED-Treiber, Motoren und Ventile, max. 35 mA Steuerstrom
  • Integrierte Leistungsmessung: Strom, Spannung, Wirk- und Scheinleistung, Leistungsfaktor
  • Abmessungen ca. 38,5 × 43,5 × 17 mm, Gewicht ca. 28,5 g, Einbau im Unterputz-/Deckendosen möglich
  • Zwei Taster-/Schaltereingänge (S1, S2) für klassische Lichtschalter parallel zur App-Steuerung
  • WLAN 802.11 b/g/n sowie Bluetooth 4.2 für Einrichtung und Automatisierungen
  • Steuerung stufenlos von 0–100 % über die Shelly-App, Szenen, Zeitpläne oder Sprachassistenten

Das Setup: Netzteil, COB-LED-Strip und Dimmer

Bei diesem Setup hängt der Shelly 0-10V Dimmer nicht an einer klassischen LED-Lampe, sondern steuert ein dimmbares 24-V-Konstantspannungsnetzteil mit 0-10V-Steuereingang, an das ein warmweißer COB-LED-Strip (2700 K) angeschlossen ist. COB-Strips ohne sichtbare einzelne LED-Punkte geben ein sehr gleichmäßiges, blendfreies Licht ab und eignen sich deshalb besonders gut für indirekte Beleuchtung, zum Beispiel im Flur oder entlang einer Treppe. Der Shelly sitzt dabei nicht am Strip selbst, sondern direkt am Netzteil und regelt über die 0-10V-Leitung, wie hell das Netzteil den Strip versorgt – vom kompletten Ausschalten bis zur vollen Helligkeit stufenlos regelbar. Die folgenden Komponenten setze ich in diesem Setup konkret ein:

  • Shelly 0-10V Dimmer (Unterputz-Dimmeraktor mit Leistungsmessung)
  • Dimmbares 24-V-LED-Netzteil mit 0-10V-Steuereingang
  • Warmweißer COB-LED-Strip, 2700 K, 24 V

Praktische Hinweise zur Installation

Ein paar Punkte, die mir bei der Installation wichtig waren: Der Shelly 0-10V Dimmer braucht zwingend einen Neutralleiter am Einbauort, was in älteren Installationen an der Lampenausschaltstelle nicht immer vorhanden ist – hier lohnt sich ein Blick in die Unterverteilung, bevor man das Gerät kauft. Das Netzteil selbst sollte an einem gut belüfteten Ort montiert werden, da es unter Last spürbar warm wird; ich habe meines in einem Zwischenboden in der Nähe des LED-Strips platziert. Wichtig ist außerdem, die Polung am 0-10V-Ausgang des Shelly korrekt mit dem Steuereingang des Netzteils zu verbinden, sonst reagiert das Netzteil gar nicht oder nur invertiert auf die Dimmbefehle. Die vorhandenen Wandschalter habe ich an den S1/S2-Eingang des Dimmers angeschlossen, sodass ich weiterhin klassisch von Hand dimmen kann, parallel zur Steuerung über die Shelly-App.

Automatisierung: Shelly-Script für den Bewegungsmelder

Damit das Licht im Flur und auf der Treppe automatisch angeht, läuft direkt auf dem Shelly 0-10V Dimmer ein kleines Script. Es empfängt die Bewegungsmeldungen eines Shelly-BLU-Bewegungsmelders per Bluetooth (BLE) und wertet sie aus: Erkennt der Sensor eine Bewegung, schaltet das Script das Licht ein; bleibt es für eine festgelegte Nachlaufzeit ruhig, geht es wieder aus. Je nachdem, welcher Sensor zum Einsatz kommt, gibt es zwei leicht unterschiedliche Varianten zum Kopieren.

Variante 1: Shelly BLU Motion

Diese Version schaltet das Licht bei jeder erkannten Bewegung ein und nach der eingestellten Nachlaufzeit (Standard: 60 Sekunden) wieder aus. Die MAC-Adresse in MOTION_MAC muss durch die Adresse des eigenen Sensors ersetzt werden – zu finden in der Shelly-App unter den Geräteinformationen des Bewegungsmelders.

let MOTION_MAC = "94:b2:16:04:82:e6"; // MAC des Sensors
let LIGHT_ID = 0;
let NACHLAUFZEIT = 60000; // 1 Minuten
let offTimer = null;

function lightOn() {
  if (offTimer !== null) {
    Timer.clear(offTimer);
    offTimer = null;
  }
  Shelly.call("Light.Set", {
    id: LIGHT_ID,
    on: true
  });
  console.log("MOTION -> Licht EIN");
}

function startOffTimer() {
  // Nicht neu starten, wenn der Timer schon läuft - sonst verlängert
  // jede wiederholte "no motion"-Meldung des Sensors die Nachlaufzeit
  // immer weiter und das Licht geht nie aus.
  if (offTimer !== null) return;
  console.log("NO MOTION -> Nachlauf gestartet");
  offTimer = Timer.set(NACHLAUFZEIT, false, function () {
    Shelly.call("Light.Set", {
      id: LIGHT_ID,
      on: false
    });
    console.log("Licht AUS");
    offTimer = null;
  });
}

/*
 * Minimaler BTHome v2 Decoder (nur unverschlüsselt), unabhängig von der
 * Firmware-Version - basiert auf Shellys eigenem Beispielskript
 * ble-shelly-motion.shelly.js. Es gibt kein natives BTHome.parseAdvertisement().
 */
const uint8 = 0, int8 = 1, uint16 = 2, int16 = 3, uint24 = 4, int24 = 5;

const BTH = {
  0x00: { n: "pid", t: uint8 },
  0x01: { n: "battery", t: uint8 },
  0x02: { n: "temperature", t: int16, f: 0.01 },
  0x03: { n: "humidity", t: uint16, f: 0.01 },
  0x05: { n: "illuminance", t: uint24, f: 0.01 },
  0x21: { n: "motion", t: uint8 },
  0x2d: { n: "window", t: uint8 },
};

function getByteSize(type) {
  if (type === uint8 || type === int8) return 1;
  if (type === uint16 || type === int16) return 2;
  if (type === uint24 || type === int24) return 3;
  return 255;
}

const BTHomeDecoder = {
  utoi: function (num, bitsz) {
    const mask = 1 << (bitsz - 1);
    return num & mask ? num - (1 << bitsz) : num;
  },
  getUInt8: function (buf) { return buf.at(0); },
  getInt8: function (buf) { return this.utoi(this.getUInt8(buf), 8); },
  getUInt16LE: function (buf) { return 0xffff & ((buf.at(1) << 8) | buf.at(0)); },
  getInt16LE: function (buf) { return this.utoi(this.getUInt16LE(buf), 16); },
  getUInt24LE: function (buf) { return 0x00ffffff & ((buf.at(2) << 16) | (buf.at(1) << 8) | buf.at(0)); },
  getInt24LE: function (buf) { return this.utoi(this.getUInt24LE(buf), 24); },
  getBufValue: function (type, buf) {
    if (buf.length < getByteSize(type)) return null;
    if (type === uint8) return this.getUInt8(buf);
    if (type === int8) return this.getInt8(buf);
    if (type === uint16) return this.getUInt16LE(buf);
    if (type === int16) return this.getInt16LE(buf);
    if (type === uint24) return this.getUInt24LE(buf);
    if (type === int24) return this.getInt24LE(buf);
    return null;
  },
  unpack: function (buffer) {
    if (typeof buffer !== "string" || buffer.length === 0) return null;
    let result = {};
    let dib = buffer.at(0);
    result.encryption = (dib & 0x1) ? true : false;
    result.BTHome_version = dib >> 5;
    if (result.BTHome_version !== 2) return null;
    if (result.encryption) return result; // Verschlüsselung wird hier nicht unterstützt
    buffer = buffer.slice(1);

    while (buffer.length > 0) {
      let bth = BTH[buffer.at(0)];
      if (typeof bth === "undefined") break; // unbekanntes Objekt -> Rest verwerfen
      buffer = buffer.slice(1);
      let value = this.getBufValue(bth.t, buffer);
      if (value === null) break;
      if (typeof bth.f !== "undefined") value = value * bth.f;
      result[bth.n] = value;
      buffer = buffer.slice(getByteSize(bth.t));
    }
    return result;
  },
};

let lastPacketId = -1;

function bleHandler(event, result) {
  if (event !== BLE.Scanner.SCAN_RESULT) return;
  if (!result || !result.addr) return;
  if (result.addr.toLowerCase() !== MOTION_MAC) return;

  let serviceData = result.service_data;
  if (!serviceData) return;
  let raw = serviceData["fcd2"];
  if (raw === undefined) return;

  let decoded = BTHomeDecoder.unpack(raw);
  if (!decoded || decoded.encryption) return;

  // Duplikate ignorieren (Sensor sendet denselben Zustand periodisch erneut)
  if (decoded.pid === lastPacketId) return;
  lastPacketId = decoded.pid;

  console.log(JSON.stringify(decoded));

  if (typeof decoded.motion !== "undefined") {
    if (decoded.motion === 1) {
      lightOn();
    } else {
      startOffTimer();
    }
  }
}

BLE.Scanner.Subscribe(bleHandler);
BLE.Scanner.Start({
  duration_ms: BLE.Scanner.INFINITE_SCAN,
  active: false
});

console.log("BLU Motion Script gestartet");
console.log("Sensor: " + MOTION_MAC);

Variante 2: Shelly BLU Motion ZB (mit Helligkeitsstufe)

Der BLU Motion ZB liefert zusätzlich eine grobe Helligkeitsstufe (0 = Dunkel, 1 = Dämmerung, 2 = Hell). Diese Variante nutzt das, um das Licht bei ausreichend Tageslicht trotz erkannter Bewegung nicht einzuschalten – über LIGHT_LEVEL_SCHWELLE lässt sich einstellen, ab welcher Stufe blockiert wird.

let MOTION_MAC = "08:b9:5f:c5:5d:a5"; // MAC des Sensors
let LIGHT_ID = 0;
let NACHLAUFZEIT = 60000; // 1 Minute
// Shelly BLU Motion sendet KEINEN Lux-Wert, sondern eine Stufe (Objekt 0x64):
// 0 = Dunkel, 1 = Dämmerung, 2 = Hell
// Ab dieser Stufe schaltet das Licht bei Bewegung NICHT ein:
let LIGHT_LEVEL_SCHWELLE = 2; // 2 = nur bei "Hell" blockieren; 1 = auch bei "Dämmerung" blockieren
let offTimer = null;
let lastLightLevel = null; // letzte bekannte Helligkeitsstufe (0/1/2)

function lightOn() {
  if (offTimer !== null) {
    Timer.clear(offTimer);
    offTimer = null;
  }
  Shelly.call("Light.Set", {
    id: LIGHT_ID,
    on: true
  });
  console.log("MOTION -> Licht EIN");
}

function startOffTimer() {
  // Nicht neu starten, wenn der Timer schon läuft - sonst verlängert
  // jede wiederholte "no motion"-Meldung des Sensors die Nachlaufzeit
  // immer weiter und das Licht geht nie aus.
  if (offTimer !== null) return;
  console.log("NO MOTION -> Nachlauf gestartet");
  offTimer = Timer.set(NACHLAUFZEIT, false, function () {
    Shelly.call("Light.Set", {
      id: LIGHT_ID,
      on: false
    });
    console.log("Licht AUS");
    offTimer = null;
  });
}

/*
 * Minimaler BTHome v2 Decoder (nur unverschlüsselt), unabhängig von der
 * Firmware-Version - basiert auf Shellys eigenem Beispielskript
 * ble-shelly-motion.shelly.js. Es gibt kein natives BTHome.parseAdvertisement().
 * Objekt 0x64 (light_level) ist kein offizielles BTHome-Objekt, sondern
 * eine Shelly-spezifische Erweiterung (0=Dunkel, 1=Dämmerung, 2=Hell).
 */
const uint8 = 0, int8 = 1, uint16 = 2, int16 = 3, uint24 = 4, int24 = 5;
const BTH = {
  0x00: { n: "pid", t: uint8 },
  0x01: { n: "battery", t: uint8 },
  0x02: { n: "temperature", t: int16, f: 0.01 },
  0x03: { n: "humidity", t: uint16, f: 0.01 },
  0x05: { n: "illuminance", t: uint24, f: 0.01 },
  0x21: { n: "motion", t: uint8 },
  0x2d: { n: "window", t: uint8 },
  0x64: { n: "light_level", t: uint8 }, // Shelly-spezifisch: 0=Dunkel, 1=Dämmerung, 2=Hell
};
function getByteSize(type) {
  if (type === uint8 || type === int8) return 1;
  if (type === uint16 || type === int16) return 2;
  if (type === uint24 || type === int24) return 3;
  return 255;
}
const BTHomeDecoder = {
  utoi: function (num, bitsz) {
    const mask = 1 << (bitsz - 1);
    return num & mask ? num - (1 << bitsz) : num;
  },
  getUInt8: function (buf) { return buf.at(0); },
  getInt8: function (buf) { return this.utoi(this.getUInt8(buf), 8); },
  getUInt16LE: function (buf) { return 0xffff & ((buf.at(1) << 8) | buf.at(0)); },
  getInt16LE: function (buf) { return this.utoi(this.getUInt16LE(buf), 16); },
  getUInt24LE: function (buf) { return 0x00ffffff & ((buf.at(2) << 16) | (buf.at(1) << 8) | buf.at(0)); },
  getInt24LE: function (buf) { return this.utoi(this.getUInt24LE(buf), 24); },
  getBufValue: function (type, buf) {
    if (buf.length < getByteSize(type)) return null;
    if (type === uint8) return this.getUInt8(buf);
    if (type === int8) return this.getInt8(buf);
    if (type === uint16) return this.getUInt16LE(buf);
    if (type === int16) return this.getInt16LE(buf);
    if (type === uint24) return this.getUInt24LE(buf);
    if (type === int24) return this.getInt24LE(buf);
    return null;
  },
  unpack: function (buffer) {
    if (typeof buffer !== "string" || buffer.length === 0) return null;
    let result = {};
    let dib = buffer.at(0);
    result.encryption = (dib & 0x1) ? true : false;
    result.BTHome_version = dib >> 5;
    if (result.BTHome_version !== 2) return null;
    if (result.encryption) return result; // Verschlüsselung wird hier nicht unterstützt
    buffer = buffer.slice(1);
    while (buffer.length > 0) {
      let bth = BTH[buffer.at(0)];
      if (typeof bth === "undefined") break; // unbekanntes Objekt -> Rest verwerfen
      buffer = buffer.slice(1);
      let value = this.getBufValue(bth.t, buffer);
      if (value === null) break;
      if (typeof bth.f !== "undefined") value = value * bth.f;
      result[bth.n] = value;
      buffer = buffer.slice(getByteSize(bth.t));
    }
    return result;
  },
};

let lastPacketId = -1;

function bleHandler(event, result) {
  if (event !== BLE.Scanner.SCAN_RESULT) return;
  if (!result || !result.addr) return;
  if (result.addr.toLowerCase() !== MOTION_MAC) return;
  let serviceData = result.service_data;
  if (!serviceData) return;
  let raw = serviceData["fcd2"];
  if (raw === undefined) return;
  let decoded = BTHomeDecoder.unpack(raw);
  if (!decoded || decoded.encryption) return;
  // Duplikate ignorieren (Sensor sendet denselben Zustand periodisch erneut)
  if (decoded.pid === lastPacketId) return;
  lastPacketId = decoded.pid;
  console.log(JSON.stringify(decoded));

  // Helligkeitsstufe merken, sobald sie in einem Paket mitkommt
  if (typeof decoded.light_level !== "undefined") {
    lastLightLevel = decoded.light_level;
  }

  if (typeof decoded.motion !== "undefined") {
    if (decoded.motion === 1) {
      if (lastLightLevel !== null && lastLightLevel >= LIGHT_LEVEL_SCHWELLE) {
        console.log("Bewegung erkannt, aber hell genug (Stufe " + lastLightLevel + ") -> Licht bleibt aus");
      } else {
        lightOn();
      }
    } else {
      startOffTimer();
    }
  }
}

BLE.Scanner.Subscribe(bleHandler);
BLE.Scanner.Start({
  duration_ms: BLE.Scanner.INFINITE_SCAN,
  active: false
});

console.log("BLU Motion Script gestartet");
console.log("Sensor: " + MOTION_MAC);
console.log("Licht-Schwelle: Stufe " + LIGHT_LEVEL_SCHWELLE + " (0=Dunkel, 1=Dämmerung, 2=Hell)");

Beide Scripte werden über die Shelly-App bzw. das Webinterface des Dimmers unter „Scripts“ angelegt und gestartet. LIGHT_ID muss passend zum verwendeten Ausgang gesetzt werden, und die MAC-Adresse muss jeweils auf den eigenen Sensor angepasst werden.

Vor- und Nachteile

Vorteile

  • Steuert klassische 0-10V-dimmbare LED-Trafos bzw. Netzteile
  • Per App, Szenen oder eigenen Shelly-Scripts automatisierbar
  • Lokale Steuerung ohne Cloud-Zwang möglich
  • Kompakte Unterputzmontage im vorhandenen Schalterkasten

Nachteile

  • Nur für 0-10V-dimmbare Netzteile/Leuchtmittel geeignet, nicht universell einsetzbar
  • Eigene Shelly-Scripts einzurichten erfordert etwas Einarbeitung
  • Elektroinstallation im Schalterkasten nötig

Mein Fazit: Der Shelly 0-10V Dimmer war die einzige Lösung, um das vorhandene dimmbare 24-V-Netzteil und die warmweißen COB-LED-Strips sauber ins Smart Home zu integrieren, ohne die komplette Beleuchtung zu tauschen – App, Sprachassistent und die klassischen Wandschalter über S1/S2 machen das Dimmen im Alltag angenehm flexibel.

Hardware in diesem Beitrag

Werbung

Die folgenden Produktlinks sind Amazon-Partnerlinks (Affiliate-Links). Wenn du darüber einkaufst, erhalte ich ggf. eine kleine Provision – der Preis für dich bleibt dabei gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen.

→ Mehr Werkzeug, das ich benutze

Titelbild KI-generiert.

Schreibe einen Kommentar

Kommentarregeln: Bleib sachlich und freundlich. Keine Beleidigungen, keine Werbung, keine Links zu fremden Shops, keine persönlichen Daten anderer und nichts Rechtswidriges. Ich behalte mir vor, Kommentare nicht freizuschalten, zu kürzen oder zu löschen. Rechtswidrige Inhalte kannst du mir über das Kontaktformular melden (bitte Link zum Kommentar und kurze Begründung). Ich prüfe Meldungen zeitnah.

Deine E-Mail-Adresse wird nicht veröffentlicht. Pflichtfelder sind mit * markiert.

Mit dem Absenden werden Name, E-Mail-Adresse, Kommentar und IP-Adresse gespeichert. Name und Kommentar sind öffentlich sichtbar. Mehr dazu in der Datenschutzerklärung. Der erste Kommentar wird vor der Veröffentlichung von mir geprüft.

Nach oben scrollen