Milesight WS502: LoRaWAN 2-Gang Smart Wall Switch

Milesight WS502 LoRaWAN 2-gang wall switch: own ChirpStack decoder for switch state and button events, decoded example and downlink control.

Milesight WS502
WS502Sensor
LoRaWAN
Class C, OTAA
Gangs
2 buttons (switch_1, switch_2)
Wiring
Live + load, replaces standard wall switch
Local control
Physical buttons plus LoRaWAN downlink
D2D
Milesight gateway-free direct control
Power
Mains powered (no battery)
Configuration
NFC (Milesight ToolBox)
Measurements

What the WS502 measures

Switch state

On/off status of switch_1 and switch_2, reported on change and on interval.

Change flag

Per-gang flag marks which switch toggled in this uplink.

Function key event

Press event of the configurable function key.

Device status

Reset, reboot and LoRaWAN class reported on join and power-on.

Data into your dashboard

Integration

Sensor / controller

Measures or controls in the field and sends LoRaWAN uplinks.

LoRaWAN gateway

Receives the radio packets and forwards them to the server.

ChirpStack

Network server: manages sessions and decodes the payload.

ThingsBoard / Grafana

Dashboards, alarms, rules and reports.

ChirpStack v4 · decodeUplink
function decodeUplink(input) {
  var bytes = input.bytes;
  var data = {};

  for (var i = 0; i < bytes.length; ) {
    var channel = bytes[i++];
    var type = bytes[i++];

    // Switch state: 1 byte. bits 0/1 = state of switch_1/switch_2,
    // bits 4/5 = "changed in this uplink" flag for switch_1/switch_2.
    if (channel === 0xff && type === 0x29) {
      var v = bytes[i]; i += 1;
      data.switch_1 = (v & 0x01) ? "on" : "off";
      data.switch_2 = (v & 0x02) ? "on" : "off";
      data.switch_1_change = ((v >> 4) & 0x01) ? "yes" : "no";
      data.switch_2_change = ((v >> 5) & 0x01) ? "yes" : "no";

    // Function key event: 1 byte (0 = no, 1 = yes)
    } else if (channel === 0xff && type === 0x2b) {
      data.function_key_event = bytes[i] ? "yes" : "no"; i += 1;

    // Device status: 1 byte (0 = off, 1 = on)
    } else if (channel === 0xff && type === 0x0b) {
      data.device_status = bytes[i] ? "on" : "off"; i += 1;

    // LoRaWAN class
    } else if (channel === 0xff && type === 0x0f) {
      data.lorawan_class = readClass(bytes[i]); i += 1;

    // Reset / reboot event
    } else if (channel === 0xff && type === 0xfe) {
      data.reset_event = "reset"; i += 1;

    // Serial number (6 bytes)
    } else if (channel === 0xff && type === 0x08) {
      data.sn = readHex(bytes, i, 6); i += 6;

    // IPSO version (1 byte, skip)
    } else if (channel === 0xff && type === 0x01) {
      i += 1;
    // Hardware / firmware version (2 bytes each, skip)
    } else if (channel === 0xff && (type === 0x09 || type === 0x0a)) {
      i += 2;
    // TSL version (2 bytes, skip)
    } else if (channel === 0xff && type === 0xff) {
      i += 2;
    } else {
      break;
    }
  }
  return { data: data };
}

function readClass(b) {
  var map = { 0: "Class A", 1: "Class B", 2: "Class C", 3: "Class CtoB" };
  return map[b] || "unknown";
}

function readHex(b, i, len) {
  var s = "";
  for (var k = 0; k < len; k++) {
    s += ("0" + (b[i + k] & 0xff).toString(16)).slice(-2);
  }
  return s;
}

Implemented from the published Milesight byte specification (Communication Protocol / User Guide).

Channel format implemented from the published Milesight byte specification: ff 29 switch state (bit0/1 = switch_1/switch_2 state, bit4/5 = change flag), ff 2b function key event, ff 0f LoRaWAN class, ff fe reset. As a Class C device the WS502 accepts downlinks in near real time to toggle either gang, and version/SN segments on join are skipped. For ThingsBoard the same logic goes into an uplink converter.

Uplink (hex)

FF2913

Decoded JSON

{ "switch_1": "on", "switch_2": "on", "switch_1_change": "yes", "switch_2_change": "no" }
From the field

Configuration & pitfalls

Class C power

The WS502 keeps its receive window open for near-real-time control, so it is mains powered, plan wiring accordingly, there is no battery fallback.

Wiring options

Two-wire and three-wire variants exist. Match the variant to your circuit; a missing neutral changes the supported load behaviour.

Change-flag handling

Each uplink reports both gangs plus a per-gang change flag. Dashboard rules should act on the change flag, not just the state, to avoid duplicate triggers on interval uplinks.

Child lock & LED

Child lock time and LED mode are set by downlink (0x25, 0x2f). Document these so on-site behaviour matches dashboard expectations.

D2D control

When a D2D command toggles the switch, it may not send a normal uplink. Account for this in monitoring so state stays consistent.

Your partner

How merkaio supports your WS502

From sourcing to day-to-day operation, all from one partner on our own European infrastructure.

Pre-staging & provisioning

We configure the WS502, set keys, intervals and alarms, and ship it ready to deploy.

Own decoder

Payload codec for ChirpStack v4 and ThingsBoard, implemented from the Milesight specification.

Dashboard integration

Data lands in your ThingsBoard or Grafana, with alarms and reports.

Operations & monitoring

We run the LoRaWAN stack and dashboards on European infrastructure, you just use the data.

Frequently asked questions

Yes. It is a standard LoRaWAN device, no Milesight gateway or cloud required. You add the codec to the device profile and provision it via OTAA.
Yes, for both ChirpStack and ThingsBoard, implemented from the published Milesight byte specification. The same channel logic goes into a ThingsBoard uplink converter.
Two independent gangs, reported as switch_1 and switch_2. Each uplink carries the on/off state of both plus a change flag marking which one toggled.
Yes. As a Class C device the WS502 listens continuously and accepts near-real-time downlinks to toggle either gang from the dashboard, alongside the physical buttons.
No. It is a mains-powered Class C device that keeps its receive window open continuously, so it replaces a wired wall switch rather than running on battery.
A configurable button press reported on channel ff 2b as a discrete event, which you can use to trigger scenes or automations independently of the switch state.
Yes. The WS502 supports the Milesight D2D protocol for ultra-low-latency direct control of another Milesight device without routing through a gateway.
From the same series

Related devices

Let's discuss your infrastructure. Digital and on-site.

Whether it's IoT platform development, hardware selection, managed hosting for ChirpStack, ThingsBoard, Grafana or NetBird VPN, or migration from a self-hosted setup - we'll find the right solution for your use case. Book a free 30-minute consultation, no commitment required.

Timo Wevelsiep

Your contact

Timo Wevelsiep

Founder, merkaio

15 minutes, no commitment, directly with Timo.

By submitting, you agree to our Privacy Policy.

Decoder for ChirpStack v4. merkaio is an independent integrator and is not affiliated with Milesight.