Sensor
/** *__ _____ ____ ___ ___ ___ *\ \ / / _ \| __ )_ _|_ _/ _ \ * \ V / | | | _ \| | | | | | | * | || |_| | |_) | | | | |_| | * |_| \___/|____/___|___\__\_\ * * * YOBIIQ JS payload decoder compatible with TTN v3/v4 payload formatter and ChirpStack payload codec. * * @author Fostin Kpodar * @version 1.1.0 * @copyright YOBIIQ B.V. | https://www.yobiiq.com * * @release 06/12/2023 * @update 10/30/2024 * * @author Dominic Hakke * // Changes in header of document, naming conventions changed. * * * @product P1002015 iQ SD-1001 (Smoke Detector) * * */ // Version Control var VERSION_CONTROL = { CODEC : {VERSION: "1.1.0", NAME: "codecVersion"}, DEVICE: {MODEL : "SD-1001", NAME: "deviceModel"}, PRODUCT: {CODE : "1002015", NAME: "productCode"}, MANUFACTURER: {COMPANY : "YOBIIQ B.V.", NAME: "manufacturer"}, } // Configuration constants for device basic info var CONFIG_INFO = { FPORT : 50, CHANNEL : parseInt("0xFF", 16), TYPES : { "0x09" : {SIZE : 2, NAME : "hardwareVersion", DIGIT: false}, "0x0A" : {SIZE : 2, NAME : "firmwareVersion", DIGIT: false}, "0x16" : {SIZE : 5, NAME : "deviceSerialNumber", DIGIT: true}, "0x0F" : {SIZE : 1, NAME : "deviceClass", VALUES : { "0x00" : "Class A", "0x01" : "Class B", "0x02" : "Class C", }, }, "0x0B" : {SIZE : 1, NAME : "powerEvent", VALUES : { "0x00" : "AC Power Off", "0x01" : "AC Power On", }, }, }, WARNING_NAME : "warning", ERROR_NAME : "error", INFO_NAME : "info" } // Configuration constants for data registers var CONFIG_DATA = { FPORT : 8, CHANNELS : { "0x01" : {SIZE : 1, NAME : "batteryLevelInPercentage",}, "0x02" : {SIZE : 1, NAME : "powerEvent", VALUES : { "0x00" : "AC Power Off", "0x01" : "AC Power On", }, }, "0x03" : {SIZE : 1, NAME : "lowBatteryAlarm", VALUES : { "0x00" : "Normal", "0x01" : "Alarm", }, }, "0x04" : {SIZE : 1, NAME : "faultAlarm", VALUES : { "0x00" : "Normal", "0x01" : "Alarm", }, }, "0x05" : {SIZE : 1, NAME : "smokeAlarm", VALUES : { "0x00" : "Normal", "0x01" : "Alarm", }, }, "0x06" : {SIZE : 1, NAME : "interconnectAlarm", VALUES : { "0x00" : "Normal", "0x01" : "Alarm", }, }, "0x07" : {SIZE : 1, NAME : "testButtonPressed", VALUES : { "0x00" : "Normal", "0x01" : "Pushed", }, }, }, WARNING_NAME : "warning", ERROR_NAME : "error", INFO_NAME : "info" } function isBasicInformation(bytes, fPort) { if(fPort == CONFIG_INFO.FPORT) { return true; } // Example: ff090100 ff0a0102 ff162404152795 ff0f02 ff0b01 if(bytes[0] == CONFIG_INFO.CHANNEL && bytes[4] == CONFIG_INFO.CHANNEL && bytes[8] == CONFIG_INFO.CHANNEL ) { return true } return false; } function decodeBasicInformation(bytes) { var LENGTH = bytes.length; var decoded = {}; var index = 0; var channel = 0; var type = ""; var size = 0; if(LENGTH == 1) { if(bytes[0] == 0) { decoded[CONFIG_INFO.INFO_NAME] = "Downlink command succeeded"; } else if(bytes[0] == 1) { decoded[CONFIG_INFO.WARNING_NAME] = "Downlink command failed"; } return decoded; } try { while(index < LENGTH) { channel = bytes[index]; index = index + 1; if(channel != CONFIG_INFO.CHANNEL) { continue; // next byte } // Type of basic information type = "0x" + toEvenHEX(bytes[index].toString(16).toUpperCase()); index = index + 1; var info = CONFIG_INFO.TYPES[type] size = info.SIZE; // Decoding var value = 0; if(size != 0) { if("DIGIT" in info) { if(info.DIGIT == false) { // Decode into "V" + DIGIT STRING + "." DIGIT STRING format value = getDigitStringArrayNoFormat(bytes, index, size); value = "V" + value[0] + "." + value[1]; }else { // Decode into DIGIT STRING format value = getDigitStringArrayEvenFormat(bytes, index, size).join(""); value = parseInt(value, 10); } } else if("VALUES" in info) { // Decode into HEX STRING (VALUES specified in CONFIG_INFO) value = "0x" + toEvenHEX(bytes[index].toString(16).toUpperCase()); value = info.VALUES[value]; }else { // Decode into DECIMAL format value = getValueFromBytesBigEndianFormat(bytes, index, size); } decoded[info.NAME] = value; index = index + size; } } }catch(error) { decoded[CONFIG_INFO.ERROR_NAME] = error.message; } return decoded; } function decodeDeviceData(bytes) { var LENGTH = bytes.length; var decoded = {}; var index = 0; var channel = ""; var type = 0; var size = 0; if(LENGTH == 1) { if(bytes[0] == 0) { decoded[CONFIG_DATA.INFO_NAME] = "Downlink command succeeded"; } else if(bytes[0] == 1) { decoded[CONFIG_DATA.WARNING_NAME] = "Downlink command failed"; } return decoded; } try { while(index < LENGTH) { // Channel of device data channel = "0x" + toEvenHEX(bytes[index].toString(16).toUpperCase()); index = index + 1; // Type of device data type = bytes[index]; index = index + 1; // No type checking var config = CONFIG_DATA.CHANNELS[channel] size = config.SIZE; // Decoding var value = 0; if("VALUES" in config) { // Decode into STRING (VALUES specified in CONFIG_DATA) value = "0x" + toEvenHEX(bytes[index].toString(16).toUpperCase()); value = config.VALUES[value]; }else { // Decode into DECIMAL format value = getValueFromBytesBigEndianFormat(bytes, index, size); } decoded[config.NAME] = value; index = index + size; } }catch(error) { decoded[CONFIG_DATA.ERROR_NAME] = error.message; } return decoded; } function getValueFromBytesBigEndianFormat(bytes, index, size) { var value = 0; for(var i=0; i<(size-1); i=i+1) { value = (value | bytes[index+i]) << 8; } value = value | bytes[index+size-1] return (value >>> 0); // to unsigned } function getValueFromBytesLittleEndianFormat(bytes, index, size) { var value = 0; for(var i=(size-1); i>0; i=i-1) { value = (value | bytes[index+i]) << 8; } value = value | bytes[index] return (value >>> 0); // to unsigned } function getDigitStringArrayNoFormat(bytes, index, size) { var hexString = [] for(var i=0; i= config["MIN"] && obj[field[1]] <= config["MAX"]) { encoded[index] = CONFIG_DEVICE.CHANNEL; index = index + 1; encoded[index] = config.TYPE; index = index + 1; if(config.SIZE == 1) { encoded[index] = value; index = index + 1; }else if(config.SIZE == 2) { switch(config.TYPE) { case 3: // reporting interval var lowByte = value % 256; encoded[index] = ((lowByte & parseInt("0x0F", 16)) << 4) + (lowByte >> 4); index = index + 1; encoded[index] = (value >> 8) % 256; index = index + 1; break; default: encoded[index] = (value >> 8) % 256; index = index + 1; encoded[index] = value % 256; index = index + 1; break; } } }else { // Error return []; } }catch(error) { // Error return []; } return encoded; }
This codec is sourced from The Things Network. All rights belong to The Things Network.
This codec is licensed under the GNU General Public License v3 (GPL v3). Modifications, if any, are clearly marked. You are free to use, modify, and distribute the codec under the terms of GPL v3.