A MESHLE Gateway wired over Ethernet to a Raspberry Pi running Node-RED, with Node-RED flow nodes overlaid for a local MQTT integration

Connecting MESHLE Devices to Node-RED via Local MQTT

·MESHLE

MESHLE devices are wirelessly connected over MESHLE Bluetooth Mesh — a proprietary, offline-first mesh that keeps your lighting, sensors, and actuators running without the cloud. The MESHLE Gateway opens that mesh to your own network over a local MQTT interface: every time a device reports its state — a light changes colour, a sensor detects occupancy, a blind moves — the gateway publishes a JSON message to your local broker.

The interface works both ways. To control any device, you publish a JSON payload to a single command topic, and the gateway translates it into the corresponding mesh frame. No cloud round-trip, no polling — just event-driven telemetry and commands on a broker you run yourself.

This guide walks the full setup end to end: configuring the gateway for local MQTT from the MESHLE app, running a Mosquitto broker, connecting Node-RED, receiving live telemetry, and sending control commands. It's written for developers, building-automation and BMS integrators, and OEM engineers who want MESHLE lighting wired into a local automation flow.

What You Need

Have the following in place before you start.

  • MESHLE gateway powered on and connected to the same network as your broker
  • A machine to run Mosquitto and Node-RED (Mac, Linux, or Windows with Docker)
  • The MESHLE app (iOS, Android, or desktop) to configure the gateway

Supported Device Types

Every telemetry message carries a device_type integer that tells you which fields to expect. Sensors are read-only; every other type also accepts commands.

device_typeDeviceTelemetry fieldsControllable
1HSV colour lightpower_on, hue_deg (0–360), saturation_pct, brightness_pctYes
3HCL tunable-white lightpower_on, kelvin (2700–6500), brightness_pctYes
4Dimmable lightpower_on, brightness_pctYes
6Window coveringpower_on, position_pct, tilt_deg, stateYes
32OUT32 multi-channel dimmerpower_on, channel_mask, ch0_pctch3_pctYes
69Occupancy and illuminance sensoroccupancy, illuminance_luxNo
83Contact and humidity sensorcontact_closed, temperature_c, humidity_pctNo

Part 1: Gateway Setup via the MESHLE App (one-time)

This is a one-time step. You provision the gateway from the MESHLE app and point it at your local broker instead of the MESHLE cloud broker.

  1. On the home screen, tap the + button in the bottom-right corner.
  2. In the Add menu (Timer, Group, Device, Peripheral), tap Device.
  3. Wait for the BLE scan. When MESHLE Matter Gateway appears, tap it.
  4. On the Setup screen ("Gateway Setup in MESHLE Cloud"), tap the three-dot menu in the top-right corner before tapping Next.
  5. In the Select MQTT Broker sheet, tap Setup Locally, then Next.
  6. Enter the IP address of the machine running your Mosquitto broker — the same machine where Node-RED runs.
  7. Choose the network type — LAN (Ethernet) or Wi-Fi. For Wi-Fi, enter the SSID and password; for LAN, no credentials are needed.
  8. Wait for the provisioning sequence to finish, then tap Finish.
MESHLE app home screen with the + button highlighted in the bottom-right corner
Step 1 of 8

Open the Add menu

On the home screen, tap the + button in the bottom-right corner.

Verify: Run the command below. JSON messages should appear within seconds of any device changing state.
mosquitto_sub -h 127.0.0.1 -p 1883 -t 'v1/gateway/telemetry/json' -v

Part 2: MQTT Broker Setup (Mosquitto)

Install Mosquitto on macOS with Homebrew, on Debian/Ubuntu with apt, or via Docker.

macOS:

brew install mosquitto

Linux (Debian/Ubuntu):

sudo apt-get install mosquitto

Docker:

docker run -d --name mosquitto \
  -p 1883:1883 \
  eclipse-mosquitto:latest \
  mosquitto -c /mosquitto/config/mosquitto.conf

By default Mosquitto binds to 127.0.0.1 only, so neither the gateway nor Node-RED-in-Docker can reach it. Make it listen on all interfaces:

# mosquitto-local.conf
listener 1883 0.0.0.0
allow_anonymous true

Start Mosquitto with the config:

mosquitto -c /path/to/mosquitto-local.conf -v
Note: allow_anonymous true is fine for a closed local network. For untrusted clients, use a password_file instead.

Verify the broker with two terminals — hello should appear in the first terminal:

# Terminal 1 -- subscribe
mosquitto_sub -h 127.0.0.1 -p 1883 -t 'test' -v

# Terminal 2 -- publish
mosquitto_pub -h 127.0.0.1 -p 1883 -t 'test' -m 'hello'

Part 3: Node-RED Setup

Run Node-RED in Docker on port 1880 and open http://127.0.0.1:1880.

docker run -it --name nodered \
  -p 1880:1880 \
  -v node_red_data:/data \
  --restart unless-stopped \
  nodered/node-red:latest

In the MQTT broker config node, set the broker hostname to host.docker.internal — Docker resolves that name to the host machine, so 127.0.0.1 would point back into the container. When you run Node-RED natively (not in Docker), use 127.0.0.1. Then install the @flowfuse/node-red-dashboard palette via Manage palette.

Note: Open the dashboard at http://127.0.0.1:1880/dashboard. Toggle a device from the MESHLE app and the gauges update within a second.

Part 4: Receiving Telemetry

Subscribe to v1/gateway/telemetry/json. Messages are event-driven — there is no polling. Add an mqtt in node and set its output to a parsed JSON object.

Each payload is a flat JSON object: device_type and device_id are always present, while the remaining fields depend on the type.

{
  "device_type": 1,
  "device_id": 1,
  "power_on": true,
  "error_flag": false,
  "hue_deg": 180,
  "saturation_pct": 100,
  "brightness_pct": 55
}

Payload examples by device type

HSV colour light (device_type: 1):

{
  "device_type": 1, "device_id": 1,
  "power_on": true, "error_flag": false,
  "hue_deg": 180, "saturation_pct": 100, "brightness_pct": 55
}

HCL tunable-white light (device_type: 3):

{
  "device_type": 3, "device_id": 2,
  "power_on": true, "error_flag": false,
  "kelvin": 3500, "brightness_pct": 70
}

Dimmable light (device_type: 4):

{
  "device_type": 4, "device_id": 3,
  "power_on": true, "error_flag": false,
  "brightness_pct": 40
}

Window covering (device_type: 6): state 0 = stopped, 1 = moving down/closing, 2 = moving up/opening.

{
  "device_type": 6, "device_id": 5,
  "power_on": false, "error_flag": false,
  "position_pct": 60, "tilt_deg": 30, "state": 0
}

OUT32 multi-channel dimmer (device_type: 32): channel_mask is a bitmask — a value of 3 (binary 0011) means channels 0 and 1 are active.

{
  "device_type": 32, "device_id": 4,
  "power_on": true, "error_flag": false,
  "channel_mask": 3, "ch0_pct": 80, "ch1_pct": 60, "ch2_pct": 0, "ch3_pct": 0
}

Occupancy and illuminance sensor (device_type: 69):

{
  "device_type": 69, "device_id": 6,
  "power_on": false, "error_flag": false,
  "occupancy": true, "illuminance_lux": 340
}

Contact and humidity sensor (device_type: 83):

{
  "device_type": 83, "device_id": 7,
  "power_on": false, "error_flag": false,
  "contact_closed": true, "temperature_c": 22.5, "humidity_pct": 48.5
}

Parsing the payload in a function node

Add a function node after the mqtt in node. This pattern handles all device types safely: a safe number helper, a filter by device, booleans converted to ON/OFF strings, and values routed to separate outputs.

var p = msg.payload || {};

// Safe number helper -- returns fallback when field is absent or null
function num(v, fallback) {
    return (v == null || isNaN(v)) ? fallback : Number(v);
}

// Filter to a specific device if needed
if (p.device_type !== 1 || p.device_id !== 1) return null;

// Power -- convert boolean to string for dashboard display
var powerOn = p.power_on === true ? 'ON' : (p.power_on === false ? 'OFF' : '-');

// HSV fields
var hue        = num(p.hue_deg,        0);   // 0-360 degrees
var saturation = num(p.saturation_pct, 0);   // 0-100 percent
var brightness = num(p.brightness_pct, 0);   // 0-100 percent

// Route each value to its own output
return [
    { payload: powerOn },
    { payload: hue },
    { payload: saturation },
    { payload: brightness }
];

Set the function node to 4 outputs and wire each output to its dashboard widget. Gauges suit hue_deg, saturation_pct, brightness_pct, kelvin, position_pct, the channels, temperature_c, and humidity_pct; text displays suit power_on (ON/OFF) and occupancy (PRESENT/CLEAR).

FlowFuse Dashboard 2.0 note: The format field does not evaluate JavaScript expressions. Convert booleans and numbers to display strings in the function node first, then reference {{msg.payload}}.
Node-RED flow canvas parsing MESHLE telemetry and wiring HSV control sliders to a dashboardNode-RED HSV Light Control dashboard with a power toggle and hue, saturation, and brightness sliders

Part 5: Controlling Devices

Publish to v1/gateway/command/json. The gateway translates each JSON payload into the corresponding BLE mesh frame. Use QoS 0 and no retain.

Command: power

Supported by all controllable device types (value: true = on, false = off).

{
  "device_type": 1,
  "device_id": 1,
  "command": "power",
  "value": true
}

Command: color

HSV lights only (device_type: 1). Sets hue, saturation, and brightness in one command.

{
  "device_type": 1,
  "device_id": 1,
  "command": "color",
  "hue": 180,
  "saturation": 100,
  "brightness": 55
}

Command: brightness

Adjusts brightness without changing colour. Supported for HSV (1), HCL (3), and Dimmer (4) — hue and saturation stay unchanged.

{
  "device_type": 1,
  "device_id": 1,
  "command": "brightness",
  "value": 75
}

Sending commands from Node-RED

A function node sets msg.topic = 'v1/gateway/command/json' and builds the msg.payload object, wired to an mqtt out node (leave the topic blank on the out node).

msg.topic   = 'v1/gateway/command/json';
msg.payload = {
    device_type: 1,
    device_id:   1,
    command: 'power',
    value:   msg.payload   // boolean from switch widget
};
return msg;

For three HSV sliders, merge their values via flow context before publishing — each slider fires independently and would otherwise overwrite the others.

// Wire all three sliders to this single function node
// Set each slider's msg.topic to: 'hue' | 'saturation' | 'brightness'

var ctrl = flow.get('hsvCtrl') || { hue: 0, sat: 100, bri: 55 };

if      (msg.topic === 'hue')        ctrl.hue = Number(msg.payload);
else if (msg.topic === 'saturation') ctrl.sat = Number(msg.payload);
else if (msg.topic === 'brightness') ctrl.bri = Number(msg.payload);

flow.set('hsvCtrl', ctrl);

msg.topic   = 'v1/gateway/command/json';
msg.payload = {
    device_type: 1,
    device_id:   1,
    command:     'color',
    hue:         ctrl.hue,
    saturation:  ctrl.sat,
    brightness:  ctrl.bri
};
return msg;
Slidermsg.topicMinMaxOutput trigger
Huehue0360On release
Saturationsaturation0100On release
Brightnessbrightness0100On release
Note: Set each slider's trigger to "on release" so you don't flood the broker while the user is dragging.

Frequently Asked Questions

I see no telemetry after setting up the gateway. What's wrong?

First confirm Mosquitto is running and listening on 0.0.0.0, not just 127.0.0.1. Then subscribe to # to see all broker traffic, and make sure the gateway actually finished provisioning in the MESHLE app.

The Node-RED MQTT node shows "disconnected".

In Docker, set the broker hostname to host.docker.internal, not 127.0.0.1; when running natively, use 127.0.0.1. Confirm port 1883 matches in both the broker config node and your Mosquitto config.

Power shows true/false instead of ON/OFF.

FlowFuse Dashboard 2.0 does not evaluate JavaScript in the widget format field. Convert the boolean to a string in the function node first, then display {{msg.payload}}.

My commands publish but the device doesn't respond.

Make sure device_type and device_id match exactly what the telemetry shows. The color command is only valid for device_type 1 — use brightness for HCL (3) and Dimmer (4) — and confirm the device is active on the mesh.

HSV or sensor fields are missing or showing 0.

Those fields only appear for the matching device_type. Check the device_type in the debug panel and read only the fields documented for that type.

Planning a local MQTT or BMS integration around MESHLE lighting? Talk to us about your project — we support developers, integrators, and OEMs through the design-in.

Further Reading

BACnet™ is a trademark of ASHRAE.