Integrate Magic Home LED strips into Gladys via MQTT

Hello,

First of all, I want to make it clear that I have a fairly advanced technical profile in many areas, but I’m not a developer. However, I’ve become a vibe coder these past few months. Some people like this approach, others don’t; in any case, when it’s very well organized and you’ve properly learned how to use agentic artificial intelligences, you can produce some really nice things. I wanted to tell you that because what I’m going to post next was done by me but mainly by Claude, my friend at Anthropic.

Having recently started using Gladys, and after making a few small sacrifices, I was able to reconnect everything with Z-Wave, Zigbee and Tuya. The only important thing missing is my Magic Home LED strips. I looked into converting them to Zigbee but it’s a bit too complicated, especially since I have modules in drop ceilings, etc.

When I discovered what could be done with MQTT, it gave me ideas. With a bit of research, I came across this GitHub: GitHub - CasperVerswijvelt/magic-home-rest: Simple REST API to control magic-home lights on the same network · GitHub

So in short I had everything needed to build something nice. I did it in two hours and have only just started using it, so I don’t yet know if it’s stable in the long term.

For info, the local IPs in the code snippets are not my real IPs.

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)

  1. You click « Turn On » in the Gladys dashboard

  2. Gladys publishes 1 on the MQTT topic gladys/device/mqtt:cuisine:bdled-ilot/feature/mqtt:cuisine:bdled-ilot:power/state

  3. The bridge receives the message, calls Control.turnOn() on the controller’s IP

  4. The controller turns on the LED strip via local WiFi

  5. The bridge publishes the confirmed state on gladys/master/device/.../state

  6. 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:

  • Synchronizing the state if someone uses the Magic Home app

  • Detecting turned off/disconnected controllers

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.

Great tutorial @David-Digitis :ok_hand:
I took the liberty of changing the category to … Tutorials :wink:

Thank you, you did well :wink: