Magic Home LED-Streifen in Gladys über MQTT integrieren
Das Problem
Die LED-Streifen Magic Home (auch unter den Marken Arilux, ZJ-WFMN-A/B/C usw. erhältlich) verwenden das proprietäre flux_led-Protokoll über lokales WiFi. Es gibt keine native Integration in Gladys für diese Controller.
Die üblichen Lösungen:
-
Ersetzen der Controller durch Zigbee: möglich, aber erfordert Lötarbeiten, wenn die Stecker nicht kompatibel sind (häufig der Fall bei RGBWW-Modellen mit 5 Pins) und ist umständlich, wenn die Controller in abgehängten Decken eingebaut sind
-
Auf Tasmota flashen: nicht immer möglich, je nach Chipset
Die Lösung: Ein benutzerdefinierter MQTT-Bridge
Man erstellt einen kleinen Node.js-Bridge, der die Verbindung zwischen Gladys (über MQTT) und den Magic Home-Controllern (über das lokale flux_led-Protokoll) herstellt.
Architektur
Gladys UI → MQTT (Mosquitto) → magic-home-bridge → Lokales WiFi → Magic Home-Controller → LED-Streifen
↓
Rückmeldung → MQTT → Gladys
Was benötigt wird
-
Gladys mit der MQTT-Integration konfiguriert und mit einem Mosquitto-Broker verbunden
-
Die Magic Home-Controller im gleichen lokalen Netzwerk wie der Gladys-Server
-
Docker zum Ausführen des Bridges
-
Die IP-Adressen Ihrer Magic Home-Controller (fixieren Sie diese in Ihrem DHCP!)
Schritt 1: Verbindungsfähigkeit testen
Zuerst überprüft man, ob die Controller antworten. Man verwendet magic-home-rest zum Testen:
# Testcontainer starten
docker run -d --name magic-home-rest --network host \
-e PORT=8888 \
casperverswijvelt/magic-home-rest:latest
# Einen Controller testen (IP-Adresse anpassen)
curl -X POST http://localhost:8888/api/power/ \
-H "Content-Type: application/json" \
-d '{"address": "192.168.1.100", "power": true}'
Wenn Sie OK erhalten, ist alles gut. Der Streifen sollte sich einschalten.
Testen Sie auch die Farbe:
curl -X POST http://localhost:8888/api/color/ \
-H "Content-Type: application/json" \
-d '{"address": "192.168.1.100", "color": "#ff0000"}'
Hinweis: Die automatische Erkennung (UDP-Scan) funktioniert nicht immer je nach Netzwerk. Deshalb verwendet man die IP-Adressen direkt.
Schritt 2: MQTT-Geräte in Gladys erstellen
In Gladys gehen Sie zu Integrationen → MQTT und erstellen ein Gerät pro LED-Streifen.
Für jedes Gerät erstellen Sie 3 Funktionen:
| Name |
Kategorie |
Typ |
Sensor? |
Min |
Max |
| Einschalten |
Licht |
Binär (ein/aus) |
Nein |
0 |
1 |
| Helligkeit |
Licht |
Helligkeit |
Nein |
0 |
100 |
| Farbe |
Licht |
Farbe |
Nein |
0 |
0 |
Verwenden Sie eine konsistente Namenskonvention für die externen Identifikatoren:
-
Gerät: mqtt:küche:bdled-insel
-
Funktionen: mqtt:küche:bdled-insel:power, mqtt:küche:bdled-insel:brightness, mqtt:küche:bdled-insel:color
Schritt 3: Bridge bereitstellen
Dateien erstellen
Erstellen Sie einen Ordner für den Bridge (z. B. /volume1/docker/magic-home-bridge/).
package.json:
{
"name": "magic-home-bridge",
"version": "1.0.0",
"private": true,
"dependencies": {
"magic-home": "^2.4.0",
"mqtt": "^5.10.0"
}
}
index.js :
const mqtt = require("mqtt");
const { Control } = require("magic-home");
// ============================================================
// KONFIGURATION — Passen Sie diesen Abschnitt an Ihre Installation an
// ============================================================
const MQTT_URL = process.env.MQTT_URL || "mqtt://localhost:1883";
const POLL_INTERVAL = parseInt(process.env.POLL_INTERVAL || "30000", 10);
// Listen Sie Ihre Controller hier auf:
// - extId: Die externe ID des Geräts in Gladys
// - ip: Die feste IP-Adresse des Magic Home Controllers
// - name: Ein lesbarer Name für die Logs
const DEVICES = [
{ extId: "mqtt:cuisine:bdled-ilot", ip: "192.168.1.100", name: "Kochinsel" },
{ extId: "mqtt:chambre:bdled-lit", ip: "192.168.1.101", name: "Bett" },
// Fügen Sie Ihre hinzu...
];
// ============================================================
// Ändern Sie nichts darunter, es sei denn, Sie wissen, was Sie tun
// ============================================================
const stateCache = {};
DEVICES.forEach(d => {
stateCache[d.extId] = { power: 0, brightness: 100, color: 16777215 };
});
function createControl(ip) {
return new Control(ip, { connect_timeout: 5000, command_timeout: 5000 });
}
function getScaledRGB(extId) {
const { color, brightness } = stateCache[extId];
const r = (color >> 16) & 0xFF;
const g = (color >> 8) & 0xFF;
const b = color & 0xFF;
const scale = brightness / 100;
return [Math.round(r * scale), Math.round(g * scale), Math.round(b * scale)];
}
function publishState(extId, feature, value) {
client.publish(
"gladys/master/device/" + extId + "/feature/" + extId + ":" + feature + "/state",
String(value)
);
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// --- Befehlsverarbeitung ---
async function handlePower(extId, ip, value) {
const ctrl = createControl(ip);
const on = parseInt(value, 10) === 1;
if (on) {
await ctrl.turnOn();
await sleep(150);
const [r, g, b] = getScaledRGB(extId);
await ctrl.setColor(r, g, b);
} else {
await ctrl.turnOff();
}
stateCache[extId].power = on ? 1 : 0;
publishState(extId, "power", stateCache[extId].power);
}
async function handleBrightness(extId, ip, value) {
const brightness = Math.max(0, Math.min(100, parseInt(value, 10)));
stateCache[extId].brightness = brightness;
const ctrl = createControl(ip);
const [r, g, b] = getScaledRGB(extId);
await ctrl.setColor(r, g, b);
publishState(extId, "brightness", brightness);
}
async function handleColor(extId, ip, value) {
const colorInt = parseInt(value, 10);
stateCache[extId].color = colorInt;
const ctrl = createControl(ip);
const [r, g, b] = getScaledRGB(extId);
await ctrl.setColor(r, g, b);
publishState(extId, "color", colorInt);
}
// --- Periodische Abfrage ---
async function pollAllDevices() {
for (const dev of DEVICES) {
try {
const ctrl = createControl(dev.ip);
const state = await ctrl.queryState();
const power = state.on ? 1 : 0;
stateCache[dev.extId].power = power;
publishState(dev.extId, "power", power);
const { red, green, blue } = state.color;
const colorInt = (red << 16) | (green << 8) | blue;
publishState(dev.extId, "color", colorInt);
const maxC = Math.max(red, green, blue);
const brightness = maxC > 0 ? Math.round((maxC / 255) * 100) : 0;
stateCache[dev.extId].brightness = brightness;
publishState(dev.extId, "brightness", brightness);
console.log("[POLL] " + dev.name + ": power=" + power +
" color=#" + colorInt.toString(16).padStart(6, "0") +
" brightness=" + brightness + "%");
} catch (err) {
console.error("[POLL ERR] " + dev.name + ":", err.message);
}
}
}
// --- MQTT-Verbindung ---
const client = mqtt.connect(MQTT_URL, {
clientId: "magic-home-bridge",
clean: true,
reconnectPeriod: 5000,
});
client.on("connect", () => {
console.log("[MQTT] Verbunden mit " + MQTT_URL);
for (const dev of DEVICES) {
const base = "gladys/device/" + dev.extId + "/feature/" + dev.extId;
client.subscribe(base + ":power/state");
client.subscribe(base + ":brightness/state");
client.subscribe(base + ":color/state");
}
console.log("[MQTT] Abonniert auf " + (DEVICES.length * 3) + " Topics");
setTimeout(pollAllDevices, 2000);
setInterval(pollAllDevices, POLL_INTERVAL);
});
client.on("message", async (topic, message) => {
const value = message.toString().trim();
const match = topic.match(/^gladys\/device\/([^/]+)\/feature\/([^/]+)\/state$/);
if (!match) return;
const deviceExtId = match[1];
const featureExtId = match[2];
const featureType = featureExtId.split(":").pop();
const dev = DEVICES.find(d => d.extId === deviceExtId);
if (!dev) return;
console.log("[CMD] " + dev.name + " > " + featureType + " = " + value);
try {
switch (featureType) {
case "power": await handlePower(deviceExtId, dev.ip, value); break;
case "brightness": await handleBrightness(deviceExtId, dev.ip, value); break;
case "color": await handleColor(deviceExtId, dev.ip, value); break;
}
} catch (err) {
console.error("[ERR] " + dev.name + " " + featureType + ":", err.message);
}
});
client.on("error", (err) => console.error("[MQTT ERR]", err.message));
client.on("reconnect", () => console.log("[MQTT] Wiederverbindung..."));
process.on("SIGTERM", () => { client.end(); process.exit(0); });
process.on("SIGINT", () => { client.end(); process.exit(0); });
console.log("[BRIDGE] Magic Home MQTT Bridge v1.0");
console.log("[BRIDGE] " + DEVICES.length + " Geräte, Abfrage alle " + (POLL_INTERVAL / 1000) + "s");
Abhängigkeiten installieren
docker run --rm \
-v /pfad/zu/magic-home-bridge:/app \
-w /app \
node:22-alpine npm install --production
Bridge starten
docker run -d \
--name magic-home-bridge \
--network host \
--restart unless-stopped \
-v /pfad/zu/magic-home-bridge:/app \
-w /app \
-e MQTT_URL=mqtt://localhost:1883 \
-e POLL_INTERVAL=30000 \
node:22-alpine \
node index.js
Logs überprüfen
docker logs magic-home-bridge
Sie sollten sehen:
[BRIDGE] Magic Home MQTT Bridge v1.0
[BRIDGE] 2 Geräte, Abfrage alle 30s
[MQTT] Verbunden mit mqtt://localhost:1883
[MQTT] Abonniert auf 6 Topics
[POLL] Kochinsel: power=1 color=#ff6e54 brightness=100%
[POLL] Bett: power=0 color=#000000 brightness=0%
Wie es funktioniert
Befehlsfluss (Gladys → LED)
-
Sie klicken auf « Anschalten » im Gladys-Dashboard
-
Gladys veröffentlicht 1 auf dem MQTT-Topic gladys/device/mqtt:cuisine:bdled-ilot/feature/mqtt:cuisine:bdled-ilot:power/state
-
Der Bridge empfängt die Nachricht und ruft Control.turnOn() auf der IP des Controllers auf
-
Der Controller schaltet das LED-Band über lokales WiFi ein
-
Der Bridge veröffentlicht den bestätigten Zustand auf gladys/master/device/.../state
-
Gladys aktualisiert die Benutzeroberfläche
Zustandsfluss (LED → Gladys)
Alle 30 Sekunden fragt der Bridge jeden Controller über TCP ab und veröffentlicht den Zustand (power, Farbe, Helligkeit) an Gladys. Dies ermöglicht:
-
Synchronisierung des Zustands, wenn jemand die Magic Home-App verwendet
-
Erkennung von ausgeschalteten/abgemeldeten Controllern
Farb- + Helligkeitsverwaltung
Die Magic Home Controller haben keinen separaten Helligkeitskanal — alles läuft über die RGB-Werte. Der Bridge verwaltet dies:
-
Gladys sendet die Farbe als Ganzzahl (z. B. 16711680 = reines Rot)
-
Gladys sendet die Helligkeit als Prozentsatz (0-100)
-
Der Bridge multipliziert die RGB-Komponenten mit dem Helligkeitsprozentsatz, bevor er sie an den Controller sendet
Bekannte Einschränkungen
-
Kein Warm White / Cold White: Der Bridge verwaltet derzeit nur RGB. Wenn Ihre Bänder RGBWW (5 Kanäle) sind, werden die Warm White/Cold White Kanäle nicht gesteuert
-
Ungefähre Helligkeit: Da die Helligkeit durch Skalieren von RGB simuliert wird, ist die Genauigkeit beim Rücklauf (Abfrage) nicht perfekt
-
Keine automatische Erkennung: Sie müssen die IP-Adressen Ihrer Controller kennen und sie im DHCP festlegen* Nur ein Controller zur gleichen Zeit: Befehle werden sequenziell gesendet (kein Problem in der Praxis, es ist instantan)
Voraussetzungen
-
Gladys mit konfigurierter MQTT-Integration
-
Mosquitto (oder ein anderer MQTT-Broker)
-
Docker mit Netzwerkzugriff auf den Host
-
Magic Home Controller mit fester IP im gleichen LAN
Erfolgreich getestet auf einem Synology NAS DS1520+ (Docker) mit Gladys v4 und 5 RGBWW-LED-Streifen.