Integrating Magic Home LED Strips into Gladys via MQTT
The Problem
The Magic Home LED strips (also sold under brands like Arilux, ZJ-WFMN-A/B/C, etc.) use the proprietary flux_led protocol over local WiFi. There is no native integration in Gladys for these controllers.
Common solutions:
-
Replacing controllers with Zigbee: Possible, but requires soldering if connectors are incompatible (often the case with 5-pin RGBWW models) and is a hassle when controllers are in false ceilings
-
Flashed with Tasmota: Not always possible depending on the chipset
The Solution: A Custom MQTT Bridge
We create a small Node.js bridge that connects Gladys (via MQTT) and Magic Home controllers (via the local flux_led protocol).
Architecture
Gladys UI → MQTT (Mosquitto) → magic-home-bridge → Local WiFi → Magic Home Controller → LED Strip
↓
Return state → MQTT → Gladys
What You Need
-
Gladys with the MQTT integration configured and connected to a Mosquitto broker
-
Magic Home controllers on the same local network as the Gladys server
-
Docker to run the bridge
-
The IP addresses of your Magic Home controllers (set them in your DHCP!)
Step 1: Test Connectivity
First, verify that the controllers respond. Use magic-home-rest to test:
# Run the test container
docker run -d --name magic-home-rest --network host \
-e PORT=8888 \
casperverswijvelt/magic-home-rest:latest
# Test a controller (replace with your IP)
curl -X POST http://localhost:8888/api/power/ \
-H "Content-Type: application/json" \
-d '{"address": "192.168.1.100", "power": true}'
If you get OK, it’s good. The strip should turn on.
Also test the color:
curl -X POST http://localhost:8888/api/color/ \
-H "Content-Type: application/json" \
-d '{"address": "192.168.1.100", "color": "#ff0000"}'
Note: Automatic discovery (UDP scan) does not always work depending on your network. That’s why we use direct IPs.
Step 2: Create MQTT Devices in Gladys
In Gladys, go to Integrations → MQTT and create a device per LED strip.
For each device, create 3 features:
| Name |
Category |
Type |
Sensor? |
Min |
Max |
| Turn On |
Light |
Binary (on/off) |
No |
0 |
1 |
| Brightness |
Light |
Brightness |
No |
0 |
100 |
| Color |
Light |
Color |
No |
0 |
0 |
Use a consistent naming convention for external IDs:
-
Device: mqtt:kitchen:island-led-strip
-
Features: mqtt:kitchen:island-led-strip:power, mqtt:kitchen:island-led-strip:brightness, mqtt:kitchen:island-led-strip:color
Step 3: Deploy the Bridge
Create the Files
Create a folder for the bridge (e.g., /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");
// ============================================================
// CONFIGURATION — Adapt this section to your installation
// ============================================================
const MQTT_URL = process.env.MQTT_URL || "mqtt://localhost:1883";
const POLL_INTERVAL = parseInt(process.env.POLL_INTERVAL || "30000", 10);
// List your controllers here:
// - extId: the external ID of the device in Gladys
// - ip: the fixed IP address of the Magic Home controller
// - name: a readable name for the logs
const DEVICES = [
{ extId: "mqtt:cuisine:bdled-ilot", ip: "192.168.1.100", name: "Kitchen Island" },
{ extId: "mqtt:chambre:bdled-lit", ip: "192.168.1.101", name: "Bed" },
// Add yours...
];
// ============================================================
// Do not modify anything below unless you know what you are doing
// ============================================================
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));
}
// --- Command Handling ---
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);
}
// --- Periodic Poll ---
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 Connection ---
const client = mqtt.connect(MQTT_URL, {
clientId: "magic-home-bridge",
clean: true,
reconnectPeriod: 5000,
});
client.on("connect", () => {
console.log("[MQTT] Connected to " + 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] Subscribed to " + (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] Reconnecting..."));
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 + " devices, poll every " + (POLL_INTERVAL / 1000) + "s");
Install Dependencies
docker run --rm \
-v /path/to/magic-home-bridge:/app \
-w /app \
node:22-alpine npm install --production
Run the Bridge
docker run -d \
--name magic-home-bridge \
--network host \
--restart unless-stopped \
-v /path/to/magic-home-bridge:/app \
-w /app \
-e MQTT_URL=mqtt://localhost:1883 \
-e POLL_INTERVAL=30000 \
node:22-alpine \
node index.js
Check Logs
docker logs magic-home-bridge
You should see:
[BRIDGE] Magic Home MQTT Bridge v1.0
[BRIDGE] 2 devices, poll every 30s
[MQTT] Connected to mqtt://localhost:1883
[MQTT] Subscribed to 6 topics
[POLL] Kitchen Island: power=1 color=#ff6e54 brightness=100%
[POLL] Bed: power=0 color=#000000 brightness=0%
How It Works
Command Flow (Gladys → LED)
-
You click « Turn On » in the Gladys dashboard
-
Gladys publishes 1 on the MQTT topic gladys/device/mqtt:cuisine:bdled-ilot/feature/mqtt:cuisine:bdled-ilot:power/state
-
The bridge receives the message, calls Control.turnOn() on the controller’s IP
-
The controller turns on the LED strip via local WiFi
-
The bridge publishes the confirmed state on gladys/master/device/.../state
-
Gladys updates the interface
State Flow (LED → Gladys)
Every 30 seconds, the bridge queries each controller via TCP and publishes the state (power, color, brightness) to Gladys. This allows:
Color + Brightness Management
Magic Home controllers do not have a separate brightness channel — everything goes through the RGB values. The bridge handles this:
-
Gladys sends the color as an integer (e.g., 16711680 = pure red)
-
Gladys sends the brightness as a percentage (0-100)
-
The bridge multiplies the RGB components by the brightness percentage before sending to the controller
Known Limitations
-
No Warm White / Cold White: the bridge only handles RGB for now. If your strips are RGBWW (5 channels), the warm/cold white channels are not controlled
-
Approximate Brightness: as brightness is simulated by scaling the RGB, the precision is not perfect on return (poll)
-
No Automatic Discovery: you must know the IPs of your controllers and set them in the DHCP
-
One controller at a time: Commands are sent sequentially (no practical issues, it’s instantaneous)
Requirements
-
Gladys with MQTT integration configured
-
Mosquitto (or another MQTT broker)
-
Docker with host network access
-
Magic Home controllers with static IP on the same LAN
Successfully tested on a Synology DS1520+ NAS (Docker) with Gladys v4 and 5 RGBWW LED strips.