Integrar tiras LED Magic Home en Gladys a través de MQTT
El problema
Las tiras LED Magic Home (también vendidas bajo las marcas Arilux, ZJ-WFMN-A/B/C, etc.) utilizan el protocolo propietario flux_led en WiFi local. No existe una integración nativa en Gladys para estos controladores.
Las soluciones habituales:
-
Reemplazar los controladores por Zigbee: posible, pero implica soldadura si los conectores no son compatibles (a menudo es el caso con los modelos RGBWW de 5 pines) y es un lío cuando los controladores están en techos falsos.
-
Flashear con Tasmota: no siempre posible según el chipset.
La solución: un puente MQTT personalizado
Creamos un pequeño puente Node.js que actúa como enlace entre Gladys (a través de MQTT) y los controladores Magic Home (a través del protocolo flux_led local).
Arquitectura
Gladys UI → MQTT (Mosquitto) → magic-home-bridge → WiFi local → Controlador Magic Home → Tira LED
↓
estado de retorno → MQTT → Gladys
Lo que necesitas
-
Gladys con la integración MQTT configurada y conectada a un broker Mosquitto.
-
Los controladores Magic Home en la misma red local que el servidor Gladys.
-
Docker para ejecutar el puente.
-
Las direcciones IP de tus controladores Magic Home (fíjalas en tu DHCP).
Paso 1: Probar la conectividad
Primero, verifica que los controladores respondan. Usamos magic-home-rest para probar:
# Ejecutar el contenedor de prueba
docker run -d --name magic-home-rest --network host \
-e PORT=8888 \
casperverswijvelt/magic-home-rest:latest
# Probar un controlador (reemplaza con tu IP)
curl -X POST http://localhost:8888/api/power/ \
-H "Content-Type: application/json" \
-d '{"address": "192.168.1.100", "power": true}'
Si obtienes OK, está bien. La tira debería encenderse.
Prueba también el color:
curl -X POST http://localhost:8888/api/color/ \
-H "Content-Type: application/json" \
-d '{"address": "192.168.1.100", "color": "#ff0000"}'
Nota: el escaneo automático (scan UDP) no siempre funciona según tu red. Por eso usamos las IP directamente.
Paso 2: Crear los dispositivos MQTT en Gladys
En Gladys, ve a Integrations → MQTT y crea un dispositivo por tira LED.
Para cada dispositivo, crea 3 funcionalidades:
| Nombre |
Categoría |
Tipo |
¿Sensor? |
Min |
Max |
| Encender |
Luz |
Binario (on/off) |
No |
0 |
1 |
| Brillo |
Luz |
Brillo |
No |
0 |
100 |
| Color |
Luz |
Color |
No |
0 |
0 |
Usa una convención de nombres coherente para los identificadores externos:
-
Dispositivo: mqtt:cocina:tiraled-isla
-
Funcionalidades: mqtt:cocina:tiraled-isla:power, mqtt:cocina:tiraled-isla:brightness, mqtt:cocina:tiraled-isla:color
Paso 3: Desplegar el puente
Crear los archivos
Crea una carpeta para el puente (ej: /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");
// ============================================================
// CONFIGURACIÓN — Adapte esta sección a su instalación
// ============================================================
const MQTT_URL = process.env.MQTT_URL || "mqtt://localhost:1883";
const POLL_INTERVAL = parseInt(process.env.POLL_INTERVAL || "30000", 10);
// Liste sus controladores aquí:
// - extId: el identificador externo del dispositivo en Gladys
// - ip: la dirección IP fija del controlador Magic Home
// - name: un nombre legible para los registros
const DEVICES = [
{ extId: "mqtt:cuisine:bdled-ilot", ip: "192.168.1.100", name: "Ilot cocina" },
{ extId: "mqtt:chambre:bdled-lit", ip: "192.168.1.101", name: "Cama" },
// Agregue los suyos...
];
// ============================================================
// No modifique nada a continuación a menos que sepa lo que hace
// ============================================================
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));
}
// --- Gestión de comandos ---
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);
}
// --- Interrogación periódica ---
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);
}
}
}
// --- Conexión MQTT ---
const client = mqtt.connect(MQTT_URL, {
clientId: "magic-home-bridge",
clean: true,
reconnectPeriod: 5000,
});
client.on("connect", () => {
console.log("[MQTT] Conectado a " + 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] Suscrito a " + (DEVICES.length * 3) + " temas");
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] Reconectando..."));
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 + " dispositivos, interrogación cada " + (POLL_INTERVAL / 1000) + "s");
Instalar las dependencias
docker run --rm \
-v /ruta/vers/magic-home-bridge:/app \
-w /app \
node:22-alpine npm install --production
Ejecutar el puente
docker run -d \
--name magic-home-bridge \
--network host \
--restart unless-stopped \
-v /ruta/vers/magic-home-bridge:/app \
-w /app \
-e MQTT_URL=mqtt://localhost:1883 \
-e POLL_INTERVAL=30000 \
node:22-alpine \
node index.js
Verificar los registros
docker logs magic-home-bridge
Debería ver:
[BRIDGE] Magic Home MQTT Bridge v1.0
[BRIDGE] 2 dispositivos, interrogación cada 30s
[MQTT] Conectado a mqtt://localhost:1883
[MQTT] Suscrito a 6 temas
[POLL] Ilot cocina: power=1 color=#ff6e54 brightness=100%
[POLL] Cama: power=0 color=#000000 brightness=0%
¿Cómo funciona?
Flujo de comando (Gladys → LED)
-
Haga clic en « Encender » en el panel de control de Gladys
-
Gladys publica 1 en el tema MQTT gladys/device/mqtt:cuisine:bdled-ilot/feature/mqtt:cuisine:bdled-ilot:power/state
-
El puente recibe el mensaje, llama a Control.turnOn() en la IP del controlador
-
El controlador enciende la tira LED a través de WiFi local
-
El puente publica el estado confirmado en gladys/master/device/.../state
-
Gladys actualiza la interfaz
Flujo de estado (LED → Gladys)
Cada 30 segundos, el puente consulta cada controlador a través de TCP y publica el estado (potencia, color, brillo) hacia Gladys. Esto permite:
Gestión de color + brillo
Los controladores Magic Home no tienen un canal de brillo separado — todo pasa por los valores RGB. El puente lo maneja:
-
Gladys envía el color como un entero (ej: 16711680 = rojo puro)
-
Gladys envía el brillo como un porcentaje (0-100)
-
El puente multiplica los componentes RGB por el porcentaje de brillo antes de enviar al controlador
Limitaciones conocidas
-
Sin warm white / cold white: el puente solo maneja RGB por ahora. Si sus tiras son RGBWW (5 canales), los canales blanco cálido/frio no se controlan
-
Brillo aproximado: como el brillo se simula escalando el RGB, la precisión no es perfecta al volver (interrogación)
-
Sin descubrimiento automático: debe conocer las IP de sus controladores y fijarlas en el DHCP
-
Solo un controlador a la vez: los comandos se envían secuencialmente (no hay problema en la práctica, es instantáneo)
Requisitos
-
Gladys con integración MQTT configurada
-
Mosquitto (u otro broker MQTT)
-
Docker con acceso a la red host
-
Controladores Magic Home con IP fija en la misma LAN
Probado con éxito en un NAS Synology DS1520+ (Docker) con Gladys v4 y 5 tiras LED RGBWW.