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.
- 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)
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.
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.
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)
FF2913Decoded JSON
{ "switch_1": "on", "switch_2": "on", "switch_1_change": "yes", "switch_2_change": "no" }Use cases
Smart office lighting
Replace existing wall switches and control lights over LoRaWAN without rewiring.
MoreRetail scene control
Switch displays, signage and zone lighting from the dashboard or local buttons.
MoreFacility automation
Class C downlinks and D2D drive fans, heaters and machines on demand.
MoreConfiguration & 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.
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
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.
Your contact
Timo Wevelsiep
Founder, merkaio
15 minutes, no commitment, directly with Timo.
Decoder for ChirpStack v4. merkaio is an independent integrator and is not affiliated with Milesight.