diff --git a/vendor/milesight-iot/ts301.js b/vendor/milesight-iot/ts301.js new file mode 100644 index 0000000000..65628f6fe6 --- /dev/null +++ b/vendor/milesight-iot/ts301.js @@ -0,0 +1,178 @@ +/** + * Payload Decoder + * + * Copyright 2024 Milesight IoT + * + * @product TS301 + */ +// 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); +} + +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; + } + // DEVICE STATUS + else if (channel_id === 0xff && channel_type === 0x0b) { + decoded.device_status = 1; + i += 1; + } + // LORAWAN CLASS TYPE + else if (channel_id === 0xff && channel_type === 0x0f) { + decoded.lorawan_class = bytes[i]; + i += 1; + } + // SERIAL NUMBER + else if (channel_id === 0xff && channel_type === 0x16) { + decoded.sn = readSerialNumber(bytes.slice(i, i + 8)); + i += 8; + } + // BATTERY + else if (channel_id === 0x01 && channel_type === 0x75) { + decoded.battery = bytes[i]; + i += 1; + } + // TEMPERATURE + else if (channel_id === 0x03 && channel_type === 0x67) { + decoded.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10; + i += 2; + } + // MAGNET STATUS + else if (channel_id === 0x03 && channel_type === 0x00) { + decoded.magnet = bytes[i]; + i += 1; + } + // TEMPERATURE ALARM + else if (channel_id === 0x83 && channel_type === 0x67) { + decoded.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10; + decoded.temperature_alarm = bytes[i + 2]; + i += 3; + } + // TEMPERATURE ALARM + else if (channel_id === 0x93 && channel_type === 0xd7) { + decoded.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10; + decoded.temperature_change = readInt16LE(bytes.slice(i + 2, i + 4)) / 100; + decoded.temperature_alarm = bytes[i + 4]; + i += 5; + } + // HISTORY + else if (channel_id === 0x20 && channel_type === 0xce) { + var timestamp = readUInt32LE(bytes.slice(i, i + 4)); + var mask = bytes[i + 4]; + i += 5; + + var data = { timestamp: timestamp }; + var chn1_event = mask >>> 4; + var chn2_event = mask & 0x0f; + switch (chn1_event) { + case 0x01: + case 0x02: + case 0x03: + case 0x04: + data.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10; + data.report_event = chn1_event; + break; + case 0x05: + case 0x06: + data.magnet = readInt16LE(bytes.slice(i, i + 2)); + data.report_event = chn1_event; + break; + default: + break; + } + i += 4; + + decoded.history = decoded.history || []; + decoded.history.push(data); + } else { + break; + } + } + + return decoded; +} + +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 readProtocolVersion(bytes) { + var major = (bytes & 0xf0) >> 4; + var minor = bytes & 0x0f; + return "v" + major + "." + minor; +} + +function readHardwareVersion(bytes) { + var major = bytes[0] & 0xff; + var minor = (bytes[1] & 0xff) >> 4; + return "v" + major + "." + minor; +} + +function readFirmwareVersion(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 readAlarmType(type) { + switch (type) { + case 0: + return "threshold release"; + case 1: + return "threshold"; + case 2: + return "mutation"; + default: + return "unkown"; + } +}