// Décodeur LoRaWAN officiel : Milesight WS50x Wall Switch (Milesight) // Source : https://github.com/TheThingsNetwork/lorawan-devices/blob/master/vendor/milesight-iot/ws50x.js // The Things Network Device Repository, fichier publié par le constructeur, distribué sans modification. // Format : decodeUplink(input) (TTN v3 / ChirpStack v4). Hébergé par smartbuildings.fr. /** * Payload Decoder * * Copyright 2025 Milesight IoT * * @product WS503 */ var RAW_VALUE = 0x00; /* eslint no-redeclare: "off" */ /* eslint-disable */ // Chirpstack v4 function decodeUplink(input) { var decoded = milesightDeviceDecode(input.bytes); return { data: decoded }; } // Chirpstack v3 function Decode(fPort, bytes) { return milesightDeviceDecode(bytes); } // The Things Network function Decoder(bytes, port) { return milesightDeviceDecode(bytes); } /* eslint-enable */ function milesightDeviceDecode(bytes) { var decoded = {}; for (var i = 0; i < bytes.length;) { var channel_id = bytes[i++]; var channel_type = bytes[i++]; // IPSO VERSION if (channel_id === 0xff && channel_type === 0x01) { decoded.ipso_version = readProtocolVersion(bytes[i]); i += 1; } // HARDWARE VERSION else if (channel_id === 0xff && channel_type === 0x09) { decoded.hardware_version = readHardwareVersion(bytes.slice(i, i + 2)); i += 2; } // FIRMWARE VERSION else if (channel_id === 0xff && channel_type === 0x0a) { decoded.firmware_version = readFirmwareVersion(bytes.slice(i, i + 2)); i += 2; } // TSL VERSION else if (channel_id === 0xff && channel_type === 0xff) { decoded.tsl_version = readTslVersion(bytes.slice(i, i + 2)); i += 2; } // SERIAL NUMBER else if (channel_id === 0xff && channel_type === 0x08) { decoded.sn = readSerialNumber(bytes.slice(i, i + 6)); i += 6; } // LORAWAN CLASS TYPE else if (channel_id === 0xff && channel_type === 0x0f) { decoded.lorawan_class = readLoRaWANClass(bytes[i]); i += 1; } // RESET EVENT else if (channel_id === 0xff && channel_type === 0xfe) { decoded.reset_event = readResetEvent(1); i += 1; } // DEVICE STATUS else if (channel_id === 0xff && channel_type === 0x0b) { decoded.device_status = readDeviceStatus(1); i += 1; } // SWITCH STATE else if (channel_id === 0xff && channel_type === 0x29) { // payload (0 0 0 0 0 0 0 0) // Switch 3 2 1 3 2 1 // ------- ------- // bit mask change state var value = bytes[i]; decoded.switch_1 = readOnOffStatus((value >>> 0) & 1); decoded.switch_1_change = readYesNoStatus((value >>> 4) & 1); decoded.switch_2 = readOnOffStatus((value >>> 1) & 1); decoded.switch_2_change = readYesNoStatus((value >>> 5) & 1); decoded.switch_3 = readOnOffStatus((value >>> 2) & 1); decoded.switch_3_change = readYesNoStatus((value >>> 6) & 1); i += 1; } // BUTTON STATUS else if (channel_id === 0xff && channel_type === 0x2b) { decoded.function_key_event = readYesNoStatus(1); i += 1; } // DOWNLINK RESPONSE else if (channel_id === 0xfe || channel_id === 0xff) { var result = handle_downlink_response(channel_type, bytes, i); decoded = Object.assign(decoded, result.data); i = result.offset; } else { break; } } return decoded; } function handle_downlink_response(channel_type, bytes, offset) { var decoded = {}; switch (channel_type) { case 0x03: decoded.report_interval = readUInt16LE(bytes.slice(offset, offset + 2)); offset += 2; break; case 0x10: decoded.reboot = readYesNoStatus(1); offset += 1; break; case 0x22: decoded.delay_task = {}; decoded.delay_task.frame_count = readUInt8(bytes[offset]); decoded.delay_task.delay_time = readUInt16LE(bytes.slice(offset + 1, offset + 3)); var data = readUInt8(bytes[offset + 3]); var switch_bit_offset = { switch_1: 0, switch_2: 1, switch_3: 2 }; for (var key in switch_bit_offset) { if ((data >>> (switch_bit_offset[key] + 4)) & 0x01) { decoded.delay_task[key] = readOnOffStatus((data >> switch_bit_offset[key]) & 0x01); } } offset += 4; break; case 0x23: decoded.cancel_delay_task = readUInt8(bytes[offset]); // ignore the second byte offset += 2; break; case 0x25: var data = readUInt16LE(bytes.slice(offset, offset + 2)); decoded.child_lock_config = {}; decoded.child_lock_config.enable = readEnableStatus((data >>> 15) & 0x01); decoded.child_lock_config.lock_time = data & 0x7fff; offset += 2; break; case 0x28: decoded.report_status = readYesNoStatus(1); offset += 1; break; case 0x29: var data = readUInt8(bytes[offset]); var switch_bit_offset = { switch_1: 0, switch_2: 1, switch_3: 2 }; for (var key in switch_bit_offset) { if ((data >>> (switch_bit_offset[key] + 4)) & 0x01) { decoded[key] = readOnOffStatus((data >> switch_bit_offset[key]) & 0x01); } } offset += 1; break; case 0x2c: decoded.report_attribute = readYesNoStatus(1); offset += 1; break; case 0x2f: decoded.led_mode = readLedMode(bytes[offset]); offset += 1; break; default: throw new Error("unknown downlink response"); } return { data: decoded, offset: offset }; } function readProtocolVersion(bytes) { var major = (bytes & 0xf0) >> 4; var minor = bytes & 0x0f; return "v" + major + "." + minor; } function readHardwareVersion(bytes) { var major = (bytes[0] & 0xff).toString(16); var minor = (bytes[1] & 0xff) >> 4; return "v" + major + "." + minor; } function readFirmwareVersion(bytes) { var major = (bytes[0] & 0xff).toString(16); var minor = (bytes[1] & 0xff).toString(16); return "v" + major + "." + minor; } function readTslVersion(bytes) { var major = bytes[0] & 0xff; var minor = bytes[1] & 0xff; return "v" + major + "." + minor; } function readSerialNumber(bytes) { var temp = []; for (var idx = 0; idx < bytes.length; idx++) { temp.push(("0" + (bytes[idx] & 0xff).toString(16)).slice(-2)); } return temp.join(""); } function readLoRaWANClass(type) { var class_map = { 0: "Class A", 1: "Class B", 2: "Class C", 3: "Class CtoB", }; return getValue(class_map, type); } function readResetEvent(status) { var status_map = { 0: "normal", 1: "reset" }; return getValue(status_map, status); } function readDeviceStatus(status) { var status_map = { 0: "off", 1: "on" }; return getValue(status_map, status); } function readOnOffStatus(status) { var status_map = { 0: "off", 1: "on" }; return getValue(status_map, status); } function readYesNoStatus(status) { var status_map = { 0: "no", 1: "yes" }; return getValue(status_map, status); } function readEnableStatus(status) { var status_map = { 0: "disable", 1: "enable" }; return getValue(status_map, status); } function readLedMode(type) { var led_mode_map = { 0: "off", 1: "on_inverted", 2: "on_synced" }; return getValue(led_mode_map, type); } /* eslint-disable */ function readUInt8(bytes) { return bytes & 0xff; } function readInt8(bytes) { var ref = readUInt8(bytes); return ref > 0x7f ? ref - 0x100 : ref; } function readUInt16LE(bytes) { var value = (bytes[1] << 8) + bytes[0]; return value & 0xffff; } function readInt16LE(bytes) { var ref = readUInt16LE(bytes); return ref > 0x7fff ? ref - 0x10000 : ref; } function readUInt32LE(bytes) { var value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0]; return (value & 0xffffffff) >>> 0; } function readInt32LE(bytes) { var ref = readUInt32LE(bytes); return ref > 0x7fffffff ? ref - 0x100000000 : ref; } function getValue(map, key) { if (RAW_VALUE) return key; var value = map[key]; if (!value) value = "unknown"; return value; } //if (!Object.assign) { Object.defineProperty(Object, "assign", { enumerable: false, configurable: true, writable: true, value: function (target) { "use strict"; if (target == null) { throw new TypeError("Cannot convert first argument to object"); } var to = Object(target); for (var i = 1; i < arguments.length; i++) { var nextSource = arguments[i]; if (nextSource == null) { continue; } nextSource = Object(nextSource); var keysArray = Object.keys(Object(nextSource)); for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) { var nextKey = keysArray[nextIndex]; var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); if (desc !== undefined && desc.enumerable) { // concat array if (Array.isArray(to[nextKey]) && Array.isArray(nextSource[nextKey])) { to[nextKey] = to[nextKey].concat(nextSource[nextKey]); } else { to[nextKey] = nextSource[nextKey]; } } } } return to; }, }); //}