// Decoder for Mutelcor LoRaButton payload var keywords = false; // For machine parsing set keywords to true, this will give keywords instead of text descriptions var fahrenheit = false; // Set to true for temperature in Farenheit, otherwise Celcius var uid_type_prefix = true; // Add the UID type byte value before the UID to make the type part of the UID // Chirpstack v4 and TTN v3 function decodeUplink(input) { // Decode an uplink message from a buffer (array) of bytes to an object of fields. var decoded = MutelcorLoRaButtonDecode(input.bytes, input.fPort); var errors = []; if (decoded.error !== undefined) { errors.push(decoded.error); // Also add error description errors.push(translation.error_val[decoded.error]); } if (!keywords) decoded = Descriptions(decoded); return { data: decoded, warnings: [], errors: errors, }; } var translation = { port: "[01] Port", version: "[02] Payload Version", voltage: "[03] Voltage Battery/Input [V]", opcode: "[04] OpCode", opcode_val: { 0: "Heartbeat", 1: "Alarm", 2: "Votes", 3: "Measurements", 4: "Location", 5: "Thresholds", 6: "Switch", 7: "Reminder", 80: "Feedback", 112: "Info", 113: "Show", 114: "Update", 128: "SCD30", }, opcode_noval: "Unknown OpCode (@)", buttons: "[05] Buttons", totals: "[06] Button Totals", counts: "[07] Button Counts", qcrc: "[08] Questions CRC", meas: "[09] Measurements", temp: "[01] Temperature [°C]", temp_f: "[01] Temperature [°F]", rh: "[02] Relative Humidity [%]", press: "[03] Pressure [hPa]", light: "[04] Light [lx]", co2: "[05] CO₂ [ppm]", tvoc: "[06] Total Volatile Organic Compound [ppb]", dist: "[07] Distance [mm]", dinputs: "[08] Digital Inputs", pm1: "[09] PM1.0 [µg/m³]", pm2: "[10] PM2.5 [µg/m³]", pm10: "[11] PM10 [µg/m³]", trigger: "[20] Thresholds Triggered", stop: "[21] Thresholds Stopped", state: "[22] Switch State", state_val: { 0: "Off", 1: "On", }, state_noval: "Unknown switch state (@)", lat: "[23] Latitude", lon: "[24] Longitude", info: "[25] Firmware Info", ccrc: "[26] Config CRC", cfrom: "[27] Config from position", cto: "[28] Config to position", config: "[29] Configuration", confver: "[01] Config layout version", conflen: "[02] Config in use length", deveui: "[03] LoRaWAN DevEUI", appeui: "[04] LoRaWAN AppEUI", region: "[05] LoRaWAN Region", region_val: { 0: "EU868 (Europe 863-870 MHz ISM)", 1: "US915 (USA, Canada and South America 902-928 MHz ISM)", 2: "AU915 (Australia 915-928 MHz ISM)", 3: "AS923-1 (Asia and South America 923 MHz ISM)", 4: "IN865 (India 865-867 MHz ISM)", 5: "AS923jp (Japan 923 MHz ISM with listen-before-talk (LBT) rules)", 6: "KR920 (Korea 920-923 MHz ISM)", 7: "AS923-2 (Indonesia and Vietnam 921 MHz ISM)", 8: "AS923-3 (Africa, Middle East, Europe, Russia, Cuba and Philippines 916 MHz ISM)", 9: "AS923-4 (Israel 917-920 MHz ISM)", 255: "No LoRa (no LoRaWAN communication)", }, region_noval: "Unknown region (@)", adr: "[06] LoRaWAN ADR", adr_val: { 0: "Off", 1: "On", }, adr_noval: "Unknown ADR value (@)", uport: "[07] LoRaWAN Upload Port", hbport: "[08] LoRaWAN Upload Port for heartbeat/reminder", confrm: "[09] LoRaWAN Confirm", confrm_val: { 0: "Off", 1: "On", }, confrm_noval: "Unknown confirm value (@)", hbconfrm: "[10] LoRaWAN Confirm heartbeat/reminder", hbconfrm_val: { 0: "Off", 1: "On", }, hbconfrm_noval: "Unknown confirm value (@)", dutycycl: "[11] LoRaWAN Duty cycle", dutycycl_val: { 0: "Always", 1: "Not for new alarm/switch", 2: "Not for alarm/switch and retries", }, dutycycl_noval: "Unknown duty cycle value (@)", unittype: "[12] LoRaButton Unit type", unittype_val: { 0: "Alarm Unit", 1: "Vote Unit", 2: "Switch Unit", }, unittype_noval: "Unknown unit type value (@)", numbut: "[13] LoRaButton Number of buttons", butitv: "[14] LoRaButton Button press and switch interval [sec]", buztog: "[15] LoRaButton Button / switch on buzzer feedback toggle interval [sec]", buzdur: "[16] LoRaButton Button / switch on buzzer feedback duration [sec]", ledtog: "[17] LoRaButton Button / switch on LED feedback toggle interval [sec]", leddur: "[18] LoRaButton Button / switch on LED feedback duration [sec]", alretr: "[19] LoRaButton Alarm / switch on retries", alretitv: "[20] LoRaButton Alarm / switch retry interval [sec]", butpin: "[21] LoRaButton Button $ pin", butpin_val: { 255: "No pin", }, ledpin: "[22] LoRaButton Button $ LED pin", ledpin_val: { 255: "No pin", 128: "Mix LED 1", 129: "Mix LED 2", 130: "Mix LED 3", 131: "Mix LED 4", }, voteaitv: "[23] LoRaButton Vote accumulation interval [sec]", voteathr: "[24] LoRaButton Vote accumulation threshold per button", mtitv: "[25] LoRaButton Heartbeat/measurement/reminder interval base [sec]", stdel: "[26] LoRaButton Transmit first heartbeat/measurement/reminder (+/- 25%) [sec]", amtitv: "[27] LoRaButton Heartbeat/measurement/reminder interval extra [sec]", senspow: "[28] LoRaButton Sensors power pin", senspow_val: { 255: "No pin", }, tcitv: "[29] LoRaButton Sensor Threshold check interval [sec]", tmeas: "[30] LoRaButton Sensor Threshold $ measurement", tmeas_val: { 0: "Temperature [0.1 K]", 1: "Relative Humidity [%]", 4: "CO₂ [ppm]", 6: "Distance [mm]", 8: "Digital input $", 254: "Voltage [10 mV]", 255: "None/disabled", }, tmeas_noval: "Unknown Thresholds Measurement value (@)", ttval: "[31] LoRaButton Sensor Threshold $ trigger value", tsval: "[32] LoRaButton Sensor Threshold $ stop value", tbuztog: "[33] LoRaButton Feedback $ buzzer toggle interval [sec]", tbuzdur: "[34] LoRaButton Feedback $ buzzer duration [sec]", tbuzrem: "[35] LoRaButton Feedback $ buzzer reminder interval [sec]", tledpin: "[36] LoRaButton Feedback $ LED pin", tledpin_val: { 255: "No pin", 128: "Mix LED 1", 129: "Mix LED 2", 130: "Mix LED 3", 131: "Mix LED 4", }, tledtog: "[37] LoRaButton Feedback $ LED toggle interval [sec]", tleddur: "[38] LoRaButton Feedback $ LED duration [sec]", tledrem: "[39] LoRaButton Feedback $ LED reminder interval [sec]", subband: "[40] LoRaWAN Sub Band for US915 and AU915 (1-8)", subband_val: { 0: "Try all subbands", }, scd30tmp: "[41] LoRaButton Sensor SCD30 Temperature Offset [°C]", scd30alt: "[42] LoRaButton Sensor SCD30 Altitude [m]", power: "[43] LoRaButton Power", power_val: { 0: "External Power", 1: "2 x AA Alkaline", 2: "2 x AA Lithium 1.5 Volt", }, ofbuztog: "[44] LoRaButton Switch off buzzer feedback toggle interval [sec]", ofbuzdur: "[45] LoRaButton Switch off buzzer feedback duration [sec]", onledpin: "[46] LoRaButton Switch on LED pin", onledpin_val: { 255: "No pin", 128: "Mix LED 1", 129: "Mix LED 2", 130: "Mix LED 3", 131: "Mix LED 4", }, ofledpin: "[47] LoRaButton Switch off LED pin", ofledpin_val: { 255: "No pin", 128: "Mix LED 1", 129: "Mix LED 2", 130: "Mix LED 3", 131: "Mix LED 4", }, ofledtog: "[48] LoRaButton Switch off LED feedback toggle interval [sec]", ofleddur: "[49] LoRaButton Switch off LED feedback duration [sec]", ofretr: "[50] LoRaButton Switch off retries", ncbuztog: "[51] LoRaButton Alarm No Confirmation buzzer feedback toggle interval [sec]", ncbuzdur: "[52] LoRaButton Alarm No Confirmation buzzer feedback duration [sec]", ncledpin: "[53] LoRaButton Alarm No Confirmation LED pin", ncledpin_val: { 255: "No pin", 128: "Mix LED 1", 129: "Mix LED 2", 130: "Mix LED 3", 131: "Mix LED 4", }, ncledtog: "[54] LoRaButton Alarm No Confirmation LED feedback toggle interval [sec]", ncleddur: "[55] LoRaButton Alarm No Confirmation LED feedback duration [sec]", cbuztog: "[56] LoRaButton Alarm Confirmation buzzer feedback toggle interval [sec]", cbuzdur: "[57] LoRaButton Alarm Confirmation buzzer feedback duration [sec]", cledpin: "[58] LoRaButton Alarm Confirmation LED pin", cledpin_val: { 255: "No pin", 128: "Mix LED 1", 129: "Mix LED 2", 130: "Mix LED 3", 131: "Mix LED 4", }, cledtog: "[59] LoRaButton Alarm Confirmation LED feedback toggle interval [sec]", cleddur: "[60] LoRaButton Alarm Confirmation LED feedback duration [sec]", choldoff: "[61] LoRaButton Alarm Confirmation feedback holdoff [sec]", mixledpin: "[62] LoRaButton Mix LED $ LED $ pin", mixledpin_val: { 255: "No pin", }, mixledlev: "[63] LoRaButton Mix LED $ LED $ level [%]", diginp: "[64] Digital Input $ pin", ttameas: "[65] LoRaButton Sensor Threshold $ trigger additional measurements", tsameas: "[66] LoRaButton Sensor Threshold $ stop additional measurements", uidtime: "[67] LoRaButton UID read timeout [sec]", uidtime_val: { 0: "UID reader disabled", }, uidbcycl: "[68] LoRaButton UID read success beep [cycles]", uidbcycl_val: { 0: "No beep", }, uidsucc: "[69] LoRaButton UID read success action", uidsucc_val: { 0: "Send Alarm and immediately give confirm feedback (when configured)", 1: "Send Alarm and (when configured) wait for confirm downlink", }, uidsucc_noval: "Unknown UID read success action value (@)", uidfail: "[70] LoRaButton UID read failure action", uidfail_val: { 0: "Don't send Alarm and immediately give non confirm feedback (when configured)", 1: "Send Alarm and (when configured) wait for confirm downlink", 2: "Send Alarm and immediately give non confirm feedback (when configured)", }, uidfail_noval: "Unknown UID read failure action value (@)", uidbmask: "[71] LoRaButton UID read button mask (disable UID read for indicated buttons)", buzbmask: "[72] LoRaButton Buzzer button mask (disable buzzer for indicated buttons, both button feedback and alarm (non) confirm)", fmtitv: "[73] LoRaButton Feedback/threshold $ Max. heartbeat/measurement/reminder interval when feedback or threshold active [sec]", jndrmin: "[74] LoRaWAN OTAA Join Minimum Datarate for EU686, IN865, AS923 and KR920", jndrmax: "[75] LoRaWAN OTAA Join Maximum Datarate for EU686, IN865, AS923 and KR920", result: "[30] Update Result", result_val: { 0: "Success", 1: "Incomplete (update length exceeds payload)", 2: "Failed (not writable or position error)", 3: "No length field", 4: "Failed (max heartbeat/measurement/reminder interval increase more than double)", }, result_noval: "Unknown update result (@)", succcnt: "[31] Update Success count", scd30result: "[32] SCD30 Result", scd30result_val: { 0: "Success", 1: "Start Timeout", 2: "Address Timeout", 3: "Data Timeout", 7: "Stop Timeout", 32: "Address NACK", 48: "Data NACK", 56: "Arbitration lost", 241: "Recalibration pending", 242: "Recalibration canceled", 243: "Recalibration missing argument", 245: "No Auto Calibration on batteries", 255: "Invalid command/argument length", }, alarmid: "[33] Alarm ID", feedbacktime: "[34] Feedback Time [min]", feedbacks: "[35] Feedbacks", uiderror: "[36] UID Error", uiderror_val: { 32: "RFID/NFC Reader not detected at startup", 48: "No tag/card found", 64: "Lost communication with RFID/NFC Reader", 96: "Reading card/tag failed, position it closer", 112: "Multiple cards/tags, present only one", 144: "Card/tag not yet supported", 160: "Reading card/tag interrupted", 176: "UID too long, maximal 10 bytes", 192: "Tag/card with Random UID, not supported", }, uidtype: "[37] UID Type", uidtype_val: { 0: "Generic passive 106 kbps (ISO/IEC14443-4A, Mifare and DEP)", 1: "Generic passive 212 kbps (FeliCa and DEP)", 2: "Generic passive 424 kbps (FeliCa and DEP)", 3: "Passive 106 kbps ISO/IEC14443-4B", 4: "Innovision Jewel/Topaz tag", 16: "Mifare card", 17: "FeliCa 212 kbps card", 18: "FeliCa 424 kbps card", 32: "Passive 106 kbps ISO/IEC14443-4A", 35: "Passive 106 kbps ISO/IEC14443-4B", 64: "DEP passive 106 kbps", 65: "DEP passive 212 kbps", 66: "DEP passive 424 kbps", 128: "DEP active 106 kbps", 129: "DEP active 212 kbps", 130: "DEP active 424 kbps", }, uid: "[38] UID", left: "[88] Undecoded Payload Left [hex]", error: "[89] Decode error", error_val: { 0: "Empty", 10: "Unexpected end, no (complete) voltage", 20: "Unexpected end, no OpCode", 30: "Unknown OpCode", 35: "Unexpected end, UID Error requires Error value", 40: "Unexpected end, OpCode Votes requires #Buttons", 50: "Too many buttons, maximum is 12", 60: "Unexpected end, incomplete Button Totals", 70: "Unexpected end, incomplete Button Counts", 80: "Unexpected end, OpCode Measurements/Thresholds requires Measurements", 90: "Unexpected end, measurements without (complete) Temperature value", 100: "Unexpected end, measurements without Relative Humidity value", 110: "Unexpected end, measurements without (complete) Pressure value", 120: "Unexpected end, measurements without (complete) Light value", 130: "Unexpected end, measurements without (complete) CO₂ value", 140: "Unexpected end, measurements without (complete) TVOC value", 143: "Unexpected end, measurements without (complete) Distance value", 144: "Unexpected end, measurements without more Measurements", 145: "Unexpected end, measurements without Digital Inputs", 146: "Unexpected end, measurements without (complete) Particulate Matter", 150: "Unexpected end, OpCode Thresholds requires Threshold info", 160: "Unexpected end, OpCode Location requires (complete) Latitude", 170: "Unexpected end, OpCode Location requires (complete) Longitude", 175: "Unexpected end, OpCode Switch/Reminder requires Switch State", 177: "Unexpected end, OpCode Feedback requires Feedback Time", 180: "Unexpected end, OpCode Show/Update require (complete) CRC", 190: "Unexpected end, OpCode Show requires Config Start", 200: "Unexpected end, OpCode Show requires Config Length", 210: "Unexpected end, OpCode Show without (complete) Config Content", 220: "Unexpected end, OpCode Update requires Update Result", 230: "Unknown Update Result", 240: "Unexpected end, OpCode SCD30 requires SCD30 Result", }, hex: "[99] Complete payload [hex]", }; function Descriptions(decode) { if (decode === null || typeof decode !== "object" || Array.isArray(decode)) return decode; var descriptions = {}; for (var key in decode) { var value = Descriptions(decode[key]); var labels = key.split("_"); var description = translation[labels[0]]; if (fahrenheit && labels[0] == "temp" && translation["temp_f"]) description = translation["temp_f"]; if (typeof description === "string") { for (var label_d = 1; label_d < labels.length; label_d += 1) { description = description.replace("$", labels[label_d]); } key = description; } var value_descriptions = translation[labels[0] + "_val"]; if (value_descriptions !== undefined && value_descriptions !== null) { var value_description = value_descriptions[value]; if (value_description === undefined) { var value_nodescription = translation[labels[0] + "_noval"]; if (typeof value_nodescription === "string") value = value_nodescription.replace("@", value); } else { value = value_description; } } if (typeof value === "string") { for (var label_v = 1; label_v < labels.length; label_v += 1) { value = value.replace("$", labels[label_v]); } } descriptions[key] = value; } return descriptions; } function MutelcorLoRaButtonDecode(bytes, port) { var decoded = {}; var pos = 0; decoded.hex = toHexMSBF(bytes); decoded.port = port; if (bytes.length === 0) { decoded.error = 0; return addPayloadLeft(decoded, bytes, pos); } decoded.version = bytes[pos++]; if (bytes.length < pos + 2) { decoded.error = 10; return addPayloadLeft(decoded, bytes, pos); } decoded.voltage = (bytes[pos++] * 256 + bytes[pos++]) / 100; if (bytes.length < pos + 1) { decoded.error = 20; return addPayloadLeft(decoded, bytes, pos); } var opcode = bytes[pos++]; decoded.opcode = opcode; if (!(opcode in translation.opcode_val)) decoded.error = 30; if (decoded.opcode === 1) { if (pos < bytes.length && pos + 2 != bytes.length) { decoded.buttons = ["1", "2", "3", "4", "5", "6", "7", "8"].filter(bitmaskFilter, bytes[pos++]); } if (pos + 1 < bytes.length) { decoded.alarmid = bytes[pos++] * 256 + bytes[pos++]; } if (pos < bytes.length) { var uidtype = bytes[pos++]; if (uidtype === 255) { if (pos < bytes.length) { decoded.uiderror = bytes[pos++]; } else { decoded.error = 35; return addPayloadLeft(decoded, bytes, pos); } } else { decoded.uidtype = uidtype; decoded.uid = (uid_type_prefix ? toHex(uidtype) : "") + toHexMSBF(bytes.slice(pos)); pos = bytes.length; } } } if (decoded.opcode === 2 && bytes.length < pos + 1) { decoded.error = 40; return addPayloadLeft(decoded, bytes, pos); } if (decoded.opcode === 2 || decoded.opcode === 0 && pos + 1 <= bytes.length) { var buttons = bytes[pos++]; decoded.buttons = buttons; if (12 < buttons) { decoded.error = 50; return addPayloadLeft(decoded, bytes, pos); } decoded.totals = {}; for (var button_t = 1; button_t <= buttons; button_t += 1) { if (bytes.length < pos + 2) { decoded.error = 60; return addPayloadLeft(decoded, bytes, pos); } if (pos + 2 <= bytes.length) { decoded.totals[button_t] = bytes[pos++] * 256 + bytes[pos++]; } } if (decoded.opcode === 2) { decoded.counts = {}; for (var button_c = 1; button_c <= buttons; button_c += 1) { if (bytes.length < pos + 1) { decoded.error = 70; return addPayloadLeft(decoded, bytes, pos); } decoded.counts[button_c] = bytes[pos++]; } if (pos + 4 <= bytes.length) { decoded.qcrc = ((bytes[pos++] * 256 + bytes[pos++]) * 256 + bytes[pos++]) * 256 + bytes[pos++]; } } } if (decoded.opcode === 3 || decoded.opcode === 5) { if (bytes.length < pos + 1) { decoded.error = 80; return addPayloadLeft(decoded, bytes, pos); } decoded.meas = {}; var measurements = bytes[pos++]; if (measurements & 1) { if (bytes.length < pos + 2) { decoded.error = 90; return addPayloadLeft(decoded, bytes, pos); } decoded.meas.temp = bytes[pos++] * 256 + bytes[pos++]; decoded.meas.temp <<= 16; // Temperature is 16 bit signed, javascript 32 bit signed, move the sign bit to the right position decoded.meas.temp /= 655360; // Get the signed temperature back to Celcius value if (fahrenheit) decoded.meas.temp = Math.round((decoded.meas.temp * 1.8 + 32) * 100) / 100; } if (measurements & 2) { if (bytes.length < pos + 1) { decoded.error = 100; return addPayloadLeft(decoded, bytes, pos); } decoded.meas.rh = bytes[pos++]; } if (measurements & 4) { if (bytes.length < pos + 2) { decoded.error = 110; return addPayloadLeft(decoded, bytes, pos); } decoded.meas.press = (bytes[pos++] * 256 + bytes[pos++]) / 10; } if (measurements & 8) { if (bytes.length < pos + 2) { decoded.error = 120; return addPayloadLeft(decoded, bytes, pos); } decoded.meas.light = bytes[pos++] * 256 + bytes[pos++]; } if (measurements & 16) { if (bytes.length < pos + 2) { decoded.error = 130; return addPayloadLeft(decoded, bytes, pos); } decoded.meas.co2 = bytes[pos++] * 256 + bytes[pos++]; } if (measurements & 32) { if (bytes.length < pos + 2) { decoded.error = 140; return addPayloadLeft(decoded, bytes, pos); } decoded.meas.tvoc = bytes[pos++] * 256 + bytes[pos++]; } if (measurements & 64) { if (bytes.length < pos + 2) { decoded.error = 143; return addPayloadLeft(decoded, bytes, pos); } decoded.meas.dist = bytes[pos++] * 256 + bytes[pos++]; } if (measurements & 128) { if (bytes.length < pos + 2) { decoded.error = 144; return addPayloadLeft(decoded, bytes, pos); } measurements = bytes[pos++]; if (measurements & 1) { if (bytes.length < pos + 1) { decoded.error = 145; return addPayloadLeft(decoded, bytes, pos); } var digital_inputs = bytes[pos++]; var dinputs = {}; for (var diginp = 0; diginp < 4; diginp += 1) { if (digital_inputs & (1 << diginp)) dinputs[diginp + 1] = (digital_inputs & (1 << (diginp + 4))) != 0; } decoded.meas.dinputs = dinputs; } if (measurements & 2) { if (bytes.length < pos + 6) { decoded.error = 146; return addPayloadLeft(decoded, bytes, pos); } decoded.meas.pm1 = bytes[pos++] * 256 + bytes[pos++]; decoded.meas.pm2 = bytes[pos++] * 256 + bytes[pos++]; decoded.meas.pm10 = bytes[pos++] * 256 + bytes[pos++]; } } if (decoded.opcode === 5) { if (bytes.length < pos + 1) { decoded.error = 150; return addPayloadLeft(decoded, bytes, pos); } var threshold_info = bytes[pos++]; var triggered = ["1", "2", "3", "4"].filter(bitmaskFilter, threshold_info & 0x0F); if (triggered && 0 < triggered.length) decoded.trigger = triggered; var stopped = ["1", "2", "3", "4"].filter(bitmaskFilter, threshold_info >> 4); if (stopped && 0 < stopped.length) decoded.stop = stopped; } if (pos + 1 <= bytes.length) { decoded.state = bytes[pos++]; } } if (decoded.opcode === 4) { if (bytes.length < pos + 4) { decoded.error = 160; return addPayloadLeft(decoded, bytes, pos); } decoded.lat = ((bytes[pos++] * 256 + bytes[pos++]) * 256 + bytes[pos++]) * 256 + bytes[pos++]; if (decoded.lat > 2147483647) decoded.lat -= 4294967296; decoded.lat /= 10000000; if (bytes.length < pos + 4) { decoded.error = 170; return addPayloadLeft(decoded, bytes, pos); } decoded.lon = ((bytes[pos++] * 256 + bytes[pos++]) * 256 + bytes[pos++]) * 256 + bytes[pos++]; if (decoded.lon > 2147483647) decoded.lon -= 4294967296; decoded.lon /= 10000000; } if (decoded.opcode === 6 || decoded.opcode === 7) { if (bytes.length < pos + 1) { decoded.error = 175; return addPayloadLeft(decoded, bytes, pos); } decoded.state = bytes[pos++]; } if (decoded.opcode === 80) { if (bytes.length < pos + 1) { decoded.error = 177; return addPayloadLeft(decoded, bytes, pos); } decoded.feedbacktime = bytes[pos++]; if (pos + 1 <= bytes.length) { var feedbacks_bitmask = bytes[pos++]; var feedbacks = ["1", "2", "3", "4"].filter(bitmaskFilter, feedbacks_bitmask); if (feedbacks && 0 < feedbacks.length) decoded.feedbacks = feedbacks; } } if (decoded.opcode === 112) { var info = ""; while (pos < bytes.length) { info += String.fromCharCode(bytes[pos++]); } decoded.info = info } if (decoded.opcode === 113 || decoded.opcode === 114) { if (bytes.length < pos + 2) { decoded.error = 180; return addPayloadLeft(decoded, bytes, pos); } decoded.ccrc = bytes[pos++] * 256 + bytes[pos++]; if (decoded.opcode === 113) { if (bytes.length < pos + 1) { decoded.error = 190; return addPayloadLeft(decoded, bytes, pos); } var config_start = bytes[pos++]; decoded.cfrom = config_start; if (bytes.length < pos + 1) { decoded.error = 200; return addPayloadLeft(decoded, bytes, pos); } var config_length = bytes[pos++]; var config_index = config_start; var config_end = config_start + config_length if (bytes.length < pos + config_length) config_end = config_start + bytes.length - pos; decoded.cto = config_end - 1; decoded.config = {}; while (config_index < config_end) { var field = null; var value = bytes[pos]; var shift = 1; var config_pos = config_index; if (51 <= config_index && config_index <= 62) config_pos = 51; if (63 <= config_index && config_index <= 74) config_pos = 63; if (82 <= config_index && config_index <= 85) config_pos = 82; if (86 <= config_index && config_index <= 93) config_pos = 86; if (94 <= config_index && config_index <= 101) config_pos = 94; if (102 <= config_index && config_index <= 105) config_pos = 102; if (106 <= config_index && config_index <= 109) config_pos = 106; if (110 <= config_index && config_index <= 113) config_pos = 110; if (114 <= config_index && config_index <= 117) config_pos = 114; if (118 <= config_index && config_index <= 121) config_pos = 118; if (122 <= config_index && config_index <= 125) config_pos = 122; if (126 <= config_index && config_index <= 129) config_pos = 126; if (152 <= config_index && config_index <= 163) config_pos = 152; if (164 <= config_index && config_index <= 175) config_pos = 164; if (176 <= config_index && config_index <= 179) config_pos = 176; if (180 <= config_index && config_index <= 183) config_pos = 180; if (184 <= config_index && config_index <= 187) config_pos = 184; if (194 <= config_index && config_index <= 197) config_pos = 194; var index = config_index - config_pos; switch (config_pos) { case 0: field = "confver"; break; case 1: if (config_index + 2 <= config_end) { field = "conflen"; value += bytes[pos + 1] * 256; shift = 2; } break; case 3: if (config_index + 8 <= config_end) { field = "deveui"; value = toHexLSBF(bytes.slice(pos), 8); shift = 8; } break; case 11: if (config_index + 8 <= config_end) { field = "appeui"; value = toHexLSBF(bytes.slice(pos), 8); shift = 8; } break; case 35: field = "region"; break; case 36: field = "adr"; break; case 37: field = "uport"; break; case 38: field = "hbport"; break; case 39: field = "confrm"; break; case 40: field = "hbconfrm"; break; case 41: field = "dutycycl"; break; case 42: field = "unittype"; break; case 43: field = "numbut"; break; case 44: field = "butitv" value /= 4; break; case 45: field = "buztog"; value /= 4; break; case 46: field = "buzdur"; value /= 4; break; case 47: field = "ledtog" value /= 4; break; case 48: field = "leddur"; value /= 4; break; case 49: field = "alretr"; break; case 50: field = "alretitv"; break; case 51: field = "butpin_" + ("0" + (index + 1)).slice(-2); break; case 63: field = "ledpin_" + ("0" + (index + 1)).slice(-2); break; case 75: field = "voteaitv"; value *= 15; break; case 76: field = "voteathr"; break; case 77: field = "mtitv"; value *= 360; break; case 78: field = "stdel"; value *= 15; break; case 79: field = "amtitv"; value *= 2; break; case 80: field = "senspow"; break; case 81: field = "tcitv"; value /= 4; break; case 82: field = "tmeas_" + (index + 1); break; case 86: if (config_index + 2 <= config_end) { field = "ttval_" + ((index >> 1) + 1); value += bytes[pos + 1] * 256; shift = 2; } break; case 94: if (config_index + 2 <= config_end) { field = "tsval_" + ((index >> 1) + 1); value += bytes[pos + 1] * 256; shift = 2; } break; case 102: field = "tbuztog_" + (index + 1); value /= 4; break; case 106: field = "tbuzdur_" + (index + 1); value /= 4; break; case 110: field = "tbuzrem_" + (index + 1); value *= 60; break; case 114: field = "tledpin_" + (index + 1); break; case 118: field = "tledtog_" + (index + 1); value /= 4; break; case 122: field = "tleddur_" + (index + 1); value /= 4; break; case 126: field = "tledrem_" + (index + 1); value *= 60; break; case 130: field = "subband"; break; case 131: field = "scd30tmp"; value /= 10; break; case 132: field = "scd30alt"; value *= 10; break; case 133: field = "power"; break; case 134: field = "ofbuztog"; value /= 4; break; case 135: field = "ofbuzdur"; value /= 4; break; case 136: field = "onledpin"; break; case 137: field = "ofledpin"; break; case 138: field = "ofledtog"; value /= 4; break; case 139: field = "ofleddur"; value /= 4; break; case 140: field = "ofretr"; break; case 141: field = "ncbuztog"; value /= 16; break; case 142: field = "ncbuzdur"; value /= 4; break; case 143: field = "ncledpin"; break; case 144: field = "ncledtog"; value /= 16; break; case 145: field = "ncleddur"; value /= 4; break; case 146: field = "cbuztog"; value /= 16; break; case 147: field = "cbuzdur"; value /= 4; break; case 148: field = "cledpin"; break; case 149: field = "cledtog"; value /= 16; break; case 150: field = "cleddur"; value /= 4; break; case 151: field = "choldoff"; value /= 4; break; case 152: field = "mixledpin_" + (index % 4 + 1) + "_" + (Math.floor(index / 4) + 1); break; case 164: field = "mixledlev_" + (index % 4 + 1) + "_" + (Math.floor(index / 4) + 1); value = Math.round((value * 1000) / 255) / 10; break; case 176: field = "diginp_" + (index + 1); break; case 180: field = "ttameas_" + (index + 1); break; case 184: field = "tsameas_" + (index + 1); break; case 188: field = "uidtime"; value /= 4; break; case 189: field = "uidbcycl"; break; case 190: field = "uidsucc"; break; case 191: field = "uidfail"; break; case 192: field = "uidbmask"; value = ["1", "2", "3", "4", "5", "6", "7", "8"].filter(bitmaskFilter, value); break; case 193: field = "buzbmask"; value = ["1", "2", "3", "4", "5", "6", "7", "8"].filter(bitmaskFilter, value); break; case 194: field = "fmtitv_" + (index + 1); value *= 15; break; case 198: field = "jndrmin"; break; case 199: field = "jndrmax"; break; } if (field !== null) decoded.config[field] = value; pos += shift; config_index += shift; } if (config_index < config_start + config_length) { decoded.error = 210; return addPayloadLeft(decoded, bytes, pos); } } if (decoded.opcode === 114) { if (bytes.length < pos + 1) { decoded.error = 220; return addPayloadLeft(decoded, bytes, pos); } var result = bytes[pos++]; decoded.result = result; if (!(result in translation.result_val)) decoded.error = 230; // Success count was added in version 1.4.1, to be backwards compatible we don't give an error when omitted if (pos < bytes.length) { decoded.succcnt = bytes[pos++]; } } } if (decoded.opcode === 128) { if (bytes.length < pos + 1) { decoded.error = 240; return addPayloadLeft(decoded, bytes, pos); } decoded.scd30result = bytes[pos++]; } return addPayloadLeft(decoded, bytes, pos); } function addPayloadLeft(decoded, bytes, pos) { if (pos < bytes.length) decoded.left = toHexMSBF(bytes.slice(pos)); return decoded; } function bitmaskFilter(value, pos) { return this & (1 << pos); } function toHex(byte) { return ("0" + byte.toString(16).toUpperCase()).slice(-2); } function toHexMSBF(buf, len) { if (len === undefined) len = buf.length; var output = ""; for (var i = 0; i < len; i += 1) output += toHex(buf[i]); return output; } function toHexLSBF(buf, len) { if (len === undefined) len = buf.length; var output = ""; for (var i = len - 1; 0 <= i; i -= 1) output += toHex(buf[i]); return output; }