|
| 1 | +/** |
| 2 | + * Payload Decoder |
| 3 | + * |
| 4 | + * Copyright 2024 Milesight IoT |
| 5 | + * |
| 6 | + * @product EM400-TLD |
| 7 | + */ |
| 8 | +var RAW_VALUE = 0x00; |
| 9 | + |
| 10 | +/* eslint no-redeclare: "off" */ |
| 11 | +/* eslint-disable */ |
| 12 | +// Chirpstack v4 |
| 13 | +function decodeUplink(input) { |
| 14 | + var decoded = milesightDeviceDecode(input.bytes); |
| 15 | + return { data: decoded }; |
| 16 | +} |
| 17 | + |
| 18 | +// Chirpstack v3 |
| 19 | +function Decode(fPort, bytes) { |
| 20 | + return milesightDeviceDecode(bytes); |
| 21 | +} |
| 22 | + |
| 23 | +// The Things Network |
| 24 | +function Decoder(bytes, port) { |
| 25 | + return milesightDeviceDecode(bytes); |
| 26 | +} |
| 27 | +/* eslint-enable */ |
| 28 | + |
| 29 | +function milesightDeviceDecode(bytes) { |
| 30 | + var decoded = {}; |
| 31 | + |
| 32 | + for (var i = 0; i < bytes.length; ) { |
| 33 | + var channel_id = bytes[i++]; |
| 34 | + var channel_type = bytes[i++]; |
| 35 | + |
| 36 | + // IPSO VERSION |
| 37 | + if (channel_id === 0xff && channel_type === 0x01) { |
| 38 | + decoded.ipso_version = readProtocolVersion(bytes[i]); |
| 39 | + i += 1; |
| 40 | + } |
| 41 | + // HARDWARE VERSION |
| 42 | + else if (channel_id === 0xff && channel_type === 0x09) { |
| 43 | + decoded.hardware_version = readHardwareVersion(bytes.slice(i, i + 2)); |
| 44 | + i += 2; |
| 45 | + } |
| 46 | + // FIRMWARE VERSION |
| 47 | + else if (channel_id === 0xff && channel_type === 0x0a) { |
| 48 | + decoded.firmware_version = readFirmwareVersion(bytes.slice(i, i + 2)); |
| 49 | + i += 2; |
| 50 | + } |
| 51 | + // TSL VERSION |
| 52 | + else if (channel_id === 0xff && channel_type === 0xff) { |
| 53 | + decoded.tsl_version = readTslVersion(bytes.slice(i, i + 2)); |
| 54 | + i += 2; |
| 55 | + } |
| 56 | + // SERIAL NUMBER |
| 57 | + else if (channel_id === 0xff && channel_type === 0x16) { |
| 58 | + decoded.sn = readSerialNumber(bytes.slice(i, i + 8)); |
| 59 | + i += 8; |
| 60 | + } |
| 61 | + // LORAWAN CLASS TYPE |
| 62 | + else if (channel_id === 0xff && channel_type === 0x0f) { |
| 63 | + decoded.lorawan_class = readLoRaWANClass(bytes[i]); |
| 64 | + i += 1; |
| 65 | + } |
| 66 | + // RESET EVENT |
| 67 | + else if (channel_id === 0xff && channel_type === 0xfe) { |
| 68 | + decoded.reset_event = readResetEvent(1); |
| 69 | + i += 1; |
| 70 | + } |
| 71 | + // DEVICE STATUS |
| 72 | + else if (channel_id === 0xff && channel_type === 0x0b) { |
| 73 | + decoded.device_status = readDeviceStatus(1); |
| 74 | + i += 1; |
| 75 | + } |
| 76 | + |
| 77 | + // BATTERY |
| 78 | + else if (channel_id === 0x01 && channel_type === 0x75) { |
| 79 | + decoded.battery = readUInt8(bytes[i]); |
| 80 | + i += 1; |
| 81 | + } |
| 82 | + // TEMPERATURE |
| 83 | + else if (channel_id === 0x03 && channel_type === 0x67) { |
| 84 | + decoded.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10; |
| 85 | + i += 2; |
| 86 | + } |
| 87 | + // DISTANCE |
| 88 | + else if (channel_id === 0x04 && channel_type === 0x82) { |
| 89 | + decoded.distance = readUInt16LE(bytes.slice(i, i + 2)); |
| 90 | + i += 2; |
| 91 | + } |
| 92 | + // POSITION |
| 93 | + else if (channel_id === 0x05 && channel_type === 0x00) { |
| 94 | + decoded.position = readPositionType(bytes[i]); |
| 95 | + i += 1; |
| 96 | + } |
| 97 | + // TEMPERATURE WITH ABNORMAL |
| 98 | + else if (channel_id === 0x83 && channel_type === 0x67) { |
| 99 | + decoded.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10; |
| 100 | + decoded.temperature_alarm = readAlarmType(bytes[i + 2]); |
| 101 | + i += 3; |
| 102 | + } |
| 103 | + // DISTANCE WITH ALARMING |
| 104 | + else if (channel_id === 0x84 && channel_type === 0x82) { |
| 105 | + decoded.distance = readUInt16LE(bytes.slice(i, i + 2)); |
| 106 | + decoded.distance_alarm = readAlarmType(bytes[i + 2]); |
| 107 | + i += 3; |
| 108 | + } |
| 109 | + // DOWNLINK RESPONSE |
| 110 | + else if (channel_id === 0xfe || channel_id === 0xff) { |
| 111 | + var result = handle_downlink_response(channel_type, bytes, i); |
| 112 | + decoded = Object.assign(decoded, result.data); |
| 113 | + i = result.offset; |
| 114 | + } else { |
| 115 | + break; |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + return decoded; |
| 120 | +} |
| 121 | + |
| 122 | +function handle_downlink_response(channel_type, bytes, offset) { |
| 123 | + var decoded = {}; |
| 124 | + |
| 125 | + switch (channel_type) { |
| 126 | + case 0x02: |
| 127 | + decoded.collection_interval = readUInt16LE(bytes.slice(offset, offset + 2)); |
| 128 | + offset += 2; |
| 129 | + break; |
| 130 | + case 0x03: |
| 131 | + decoded.report_interval = readUInt16LE(bytes.slice(offset, offset + 2)); |
| 132 | + offset += 2; |
| 133 | + break; |
| 134 | + case 0x06: |
| 135 | + var value = readUInt8(bytes[offset]); |
| 136 | + var alarm_type = (value >>> 3) & 0x07; |
| 137 | + var condition_type = value & 0x07; |
| 138 | + var config = {}; |
| 139 | + config.condition = readMathConditionType(condition_type); |
| 140 | + config.alarm_release_enable = readEnableStatus((value >>> 7) & 0x01); |
| 141 | + config.threshold_min = readUInt16LE(bytes.slice(offset + 1, offset + 3)); |
| 142 | + config.threshold_max = readUInt16LE(bytes.slice(offset + 3, offset + 5)); |
| 143 | + // skip 4 bytes |
| 144 | + offset += 9; |
| 145 | + if (alarm_type === 1) { |
| 146 | + decoded.standard_mode_alarm_config = config; |
| 147 | + } else if (alarm_type === 2) { |
| 148 | + decoded.bin_mode_alarm_config = config; |
| 149 | + } |
| 150 | + break; |
| 151 | + case 0x10: |
| 152 | + decoded.reboot = readYesNoStatus(1); |
| 153 | + offset += 1; |
| 154 | + break; |
| 155 | + case 0x13: |
| 156 | + decoded.install_height_enable = readEnableStatus(bytes[offset]); |
| 157 | + offset += 1; |
| 158 | + break; |
| 159 | + case 0x1c: |
| 160 | + decoded.recollection_config = {}; |
| 161 | + decoded.recollection_config.counts = bytes[offset]; |
| 162 | + decoded.recollection_config.interval = bytes[offset + 1]; |
| 163 | + offset += 2; |
| 164 | + break; |
| 165 | + case 0x28: |
| 166 | + decoded.query_device_status = readYesNoStatus(1); |
| 167 | + offset += 1; |
| 168 | + break; |
| 169 | + case 0x3e: |
| 170 | + decoded.tilt_linkage_distance_enable = readEnableStatus(bytes[offset]); |
| 171 | + offset += 1; |
| 172 | + break; |
| 173 | + case 0x4a: |
| 174 | + decoded.sync_time = readYesNoStatus(1); |
| 175 | + offset += 1; |
| 176 | + break; |
| 177 | + case 0x56: |
| 178 | + decoded.tof_detection_enable = readEnableStatus(bytes[offset]); |
| 179 | + offset += 1; |
| 180 | + break; |
| 181 | + case 0x70: |
| 182 | + decoded.people_existing_height = readUInt16LE(bytes.slice(offset, offset + 2)); |
| 183 | + offset += 2; |
| 184 | + break; |
| 185 | + case 0x71: |
| 186 | + decoded.working_mode = readWorkingMode(bytes[offset]); |
| 187 | + offset += 1; |
| 188 | + break; |
| 189 | + case 0x77: |
| 190 | + decoded.install_height = readUInt16LE(bytes.slice(offset, offset + 2)); |
| 191 | + offset += 2; |
| 192 | + break; |
| 193 | + default: |
| 194 | + throw new Error("unknown downlink response"); |
| 195 | + } |
| 196 | + |
| 197 | + return { data: decoded, offset: offset }; |
| 198 | +} |
| 199 | + |
| 200 | +function readProtocolVersion(bytes) { |
| 201 | + var major = (bytes & 0xf0) >> 4; |
| 202 | + var minor = bytes & 0x0f; |
| 203 | + return "v" + major + "." + minor; |
| 204 | +} |
| 205 | + |
| 206 | +function readHardwareVersion(bytes) { |
| 207 | + var major = (bytes[0] & 0xff).toString(16); |
| 208 | + var minor = (bytes[1] & 0xff) >> 4; |
| 209 | + return "v" + major + "." + minor; |
| 210 | +} |
| 211 | + |
| 212 | +function readFirmwareVersion(bytes) { |
| 213 | + var major = (bytes[0] & 0xff).toString(16); |
| 214 | + var minor = (bytes[1] & 0xff).toString(16); |
| 215 | + return "v" + major + "." + minor; |
| 216 | +} |
| 217 | + |
| 218 | +function readTslVersion(bytes) { |
| 219 | + var major = bytes[0] & 0xff; |
| 220 | + var minor = bytes[1] & 0xff; |
| 221 | + return "v" + major + "." + minor; |
| 222 | +} |
| 223 | + |
| 224 | +function readSerialNumber(bytes) { |
| 225 | + var temp = []; |
| 226 | + for (var idx = 0; idx < bytes.length; idx++) { |
| 227 | + temp.push(("0" + (bytes[idx] & 0xff).toString(16)).slice(-2)); |
| 228 | + } |
| 229 | + return temp.join(""); |
| 230 | +} |
| 231 | + |
| 232 | +function readLoRaWANClass(type) { |
| 233 | + var class_map = { |
| 234 | + 0: "Class A", |
| 235 | + 1: "Class B", |
| 236 | + 2: "Class C", |
| 237 | + 3: "Class CtoB", |
| 238 | + }; |
| 239 | + return getValue(class_map, type); |
| 240 | +} |
| 241 | + |
| 242 | +function readResetEvent(status) { |
| 243 | + var status_map = { 0: "normal", 1: "reset" }; |
| 244 | + return getValue(status_map, status); |
| 245 | +} |
| 246 | + |
| 247 | +function readDeviceStatus(status) { |
| 248 | + var status_map = { 0: "off", 1: "on" }; |
| 249 | + return getValue(status_map, status); |
| 250 | +} |
| 251 | + |
| 252 | +function readYesNoStatus(status) { |
| 253 | + var status_map = { 0: "no", 1: "yes" }; |
| 254 | + return getValue(status_map, status); |
| 255 | +} |
| 256 | + |
| 257 | +function readEnableStatus(status) { |
| 258 | + var status_map = { 0: "disable", 1: "enable" }; |
| 259 | + return getValue(status_map, status); |
| 260 | +} |
| 261 | + |
| 262 | +function readPositionType(type) { |
| 263 | + var type_map = { 0: "normal", 1: "tilt" }; |
| 264 | + return getValue(type_map, type); |
| 265 | +} |
| 266 | + |
| 267 | +function readAlarmType(type) { |
| 268 | + var type_map = { 0: "threshold_alarm_release", 1: "threshold_alarm" }; |
| 269 | + return getValue(type_map, type); |
| 270 | +} |
| 271 | + |
| 272 | +function readMathConditionType(type) { |
| 273 | + var type_map = { 0: "disable", 1: "below", 2: "above", 3: "between", 4: "outside" }; |
| 274 | + return getValue(type_map, type); |
| 275 | +} |
| 276 | + |
| 277 | +function readWorkingMode(type) { |
| 278 | + var type_map = { 0: "standard", 1: "bin" }; |
| 279 | + return getValue(type_map, type); |
| 280 | +} |
| 281 | + |
| 282 | +/* eslint-disable */ |
| 283 | +function readUInt8(bytes) { |
| 284 | + return bytes & 0xff; |
| 285 | +} |
| 286 | + |
| 287 | +function readInt8(bytes) { |
| 288 | + var ref = readUInt8(bytes); |
| 289 | + return ref > 0x7f ? ref - 0x100 : ref; |
| 290 | +} |
| 291 | + |
| 292 | +function readUInt16LE(bytes) { |
| 293 | + var value = (bytes[1] << 8) + bytes[0]; |
| 294 | + return value & 0xffff; |
| 295 | +} |
| 296 | + |
| 297 | +function readInt16LE(bytes) { |
| 298 | + var ref = readUInt16LE(bytes); |
| 299 | + return ref > 0x7fff ? ref - 0x10000 : ref; |
| 300 | +} |
| 301 | + |
| 302 | +function readUInt32LE(bytes) { |
| 303 | + var value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0]; |
| 304 | + return (value & 0xffffffff) >>> 0; |
| 305 | +} |
| 306 | + |
| 307 | +function readInt32LE(bytes) { |
| 308 | + var ref = readUInt32LE(bytes); |
| 309 | + return ref > 0x7fffffff ? ref - 0x100000000 : ref; |
| 310 | +} |
| 311 | + |
| 312 | +function getValue(map, key) { |
| 313 | + if (RAW_VALUE) return key; |
| 314 | + |
| 315 | + var value = map[key]; |
| 316 | + if (!value) value = "unknown"; |
| 317 | + return value; |
| 318 | +} |
| 319 | + |
| 320 | +//if (!Object.assign) { |
| 321 | + Object.defineProperty(Object, "assign", { |
| 322 | + enumerable: false, |
| 323 | + configurable: true, |
| 324 | + writable: true, |
| 325 | + value: function (target) { |
| 326 | + "use strict"; |
| 327 | + if (target == null) { |
| 328 | + throw new TypeError("Cannot convert first argument to object"); |
| 329 | + } |
| 330 | + |
| 331 | + var to = Object(target); |
| 332 | + for (var i = 1; i < arguments.length; i++) { |
| 333 | + var nextSource = arguments[i]; |
| 334 | + if (nextSource == null) { |
| 335 | + continue; |
| 336 | + } |
| 337 | + nextSource = Object(nextSource); |
| 338 | + |
| 339 | + var keysArray = Object.keys(Object(nextSource)); |
| 340 | + for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) { |
| 341 | + var nextKey = keysArray[nextIndex]; |
| 342 | + var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); |
| 343 | + if (desc !== undefined && desc.enumerable) { |
| 344 | + // concat array |
| 345 | + if (Array.isArray(to[nextKey]) && Array.isArray(nextSource[nextKey])) { |
| 346 | + to[nextKey] = to[nextKey].concat(nextSource[nextKey]); |
| 347 | + } else { |
| 348 | + to[nextKey] = nextSource[nextKey]; |
| 349 | + } |
| 350 | + } |
| 351 | + } |
| 352 | + } |
| 353 | + return to; |
| 354 | + }, |
| 355 | + }); |
| 356 | +//} |
0 commit comments