TTN Smart Sensor (Comtac)

Sensor

Codec Description

Codec for Comtac

Codec Preview

/* comtac AG LPN TD-1 payload decoder (TTN). www.comtac.ch [email protected] */ //DEFINES var PAYLOAD_VERSION = 0x01; //PL version 1 var DATA_PORT = 3; //DATA port var CONFIG_PORT = 100; //CONFIG port var REJOIN_PORT = 102; //REJOIN port var TYPE_GPS_DATA = 0x01; //9 bytes, 4 bytes Latitude, 4 bytes Longitude, 1 byte extra var TYPE_WIFI_DATA = 0x02; //8 to 29 bytes -> 1 byte nr WIFI detected, 6 bytes MAC1, 1 byte RSSI1, 6 bytes MACX, 1 byte RSSIX var ERROR_BAT_VOLTAGE = 255; //255 for ERROR var ERROR_TEMP = 127; //127 for ERROR var POS_STATUS = ['NONE', 'GPS OK', 'GPS NOK', 'WIFI OK', 'WIFI NOK', 'GPS AND WIFI OK', 'GPS AND WIFI NOK']; var PING_TYPE = ['NORMAL', 'MIDRANGE', 'LONGRANGE', 'EVENT']; var POS_REQUESTS = ['INVALID', 'GPS ONLY', 'WIFI ONLY', 'GPS OR WIFI', 'WIFI OR GPS', 'GPS AND WIFI']; //HELPING FUNCTIONS function bin32dec(bin) { var num = bin & 0xFFFFFFFF; if (0x80000000 & num) num = -(0x0100000000 - num); return num; } function bin16dec(bin) { var num = bin & 0xFFFF; if (0x8000 & num) num = - (0x010000 - num); return num; } function bin8dec(bin) { var num = bin & 0xFF; if (0x80 & num) num = -(0x0100 - num); return num; } function bin8string(bin) { var num = bin & 0xFF; var str = ''; str = num.toString(16); str = (str.length === 1 ? ('0' + str) : str); return str; } //DECODING FUNCTIONS function DecodeTD1DataPayload(data) { var obj = {}; if (data[0] == PAYLOAD_VERSION) { //POS STATUS var posStatus = (data[1] >> 5) & 0x07; obj.posStatus = POS_STATUS[posStatus]; //PING TYPE var pingType = (data[1] >> 2) & 0x07; obj.pingType = PING_TYPE[pingType]; //BAT FULL var batFull = data[1] & 0x02; if (batFull) { obj.batFull = true; } //CONNECTION TEST var connTest = data[1] & 0x01; if (connTest) { obj.connectionTest = true; } //BATTERY if (data[2] == ERROR_BAT_VOLTAGE) { obj.batVoltage = 'ERROR'; } else { obj.batVoltage = (data[2] * 5 + 3000) / 1000; } //TEMP if (bin8dec(data[3]) == ERROR_TEMP) { obj.temp = 'ERROR'; } else { obj.temp = bin8dec(data[3]); } //IDs for (i = 4; i < data.length; i++) { switch (data[i]) { case TYPE_GPS_DATA: //GPS DATA var gpsLat = (data[i + 1] << 24) | (data[i + 2] << 16) | (data[i + 3] << 8) | (data[i + 4]); gpsLat = bin32dec(gpsLat) / 1000000; var gpsLong = (data[i + 5] << 24) | (data[i + 6] << 16) | (data[i + 7] << 8) | (data[i + 8]); gpsLong = bin32dec(gpsLong) / 1000000; var gpsEPE = (data[i + 9] >> 4) & 0x0F; var gpsSAT = (data[i + 9]) & 0x0F; obj.gpsLong = gpsLong; obj.gpsLat = gpsLat; if (gpsEPE < 0x0F) { obj.gpsEPE = ((gpsEPE) * 10).toString(10) + '-' + ((gpsEPE + 1) * 10).toString(10) + ' meters'; } else { obj.gpsEPE = '> 150 meters'; } obj.gpsSAT = gpsSAT; i += 9; break; case TYPE_WIFI_DATA: //WIFI DATA var nrAPs = (data[i + 1]) & 0x0F; switch (nrAPs) { case 4: var MAC1 = bin8string(data[i + 2]) + ':' + bin8string(data[i + 3]) + ':' + bin8string(data[i + 4]) + ':' + bin8string(data[i + 5]) + ':' + bin8string(data[i + 6]) + ':' + bin8string(data[i + 7]); var RSSI1 = data[i + 8] * -1; var MAC2 = bin8string(data[i + 9]) + ':' + bin8string(data[i + 10]) + ':' + bin8string(data[i + 11]) + ':' + bin8string(data[i + 12]) + ':' + bin8string(data[i + 13]) + ':' + bin8string(data[i + 14]); var RSSI2 = data[i + 15] * -1; var MAC3 = bin8string(data[i + 16]) + ':' + bin8string(data[i + 17]) + ':' + bin8string(data[i + 18]) + ':' + bin8string(data[i + 19]) + ':' + bin8string(data[i + 20]) + ':' + bin8string(data[i + 21]); var RSSI3 = data[i + 22] * -1; var MAC4 = bin8string(data[i + 23]) + ':' + bin8string(data[i + 24]) + ':' + bin8string(data[i + 25]) + ':' + bin8string(data[i + 26]) + ':' + bin8string(data[i + 27]) + ':' + bin8string(data[i + 28]); var RSSI4 = data[i + 29] * -1; obj.MAC1 = MAC1; obj.RSSI1 = RSSI1; obj.MAC2 = MAC2; obj.RSSI2 = RSSI2; obj.MAC3 = MAC3; obj.RSSI3 = RSSI3; obj.MAC4 = MAC4; obj.RSSI4 = RSSI4; i += 29; break; case 3: var MAC1 = bin8string(data[i + 2]) + ':' + bin8string(data[i + 3]) + ':' + bin8string(data[i + 4]) + ':' + bin8string(data[i + 5]) + ':' + bin8string(data[i + 6]) + ':' + bin8string(data[i + 7]); var RSSI1 = data[i + 8] * -1; var MAC2 = bin8string(data[i + 9]) + ':' + bin8string(data[i + 10]) + ':' + bin8string(data[i + 11]) + ':' + bin8string(data[i + 12]) + ':' + bin8string(data[i + 13]) + ':' + bin8string(data[i + 14]); var RSSI2 = data[i + 15] * -1; var MAC3 = bin8string(data[i + 16]) + ':' + bin8string(data[i + 17]) + ':' + bin8string(data[i + 18]) + ':' + bin8string(data[i + 19]) + ':' + bin8string(data[i + 20]) + ':' + bin8string(data[i + 21]); var RSSI3 = data[i + 22] * -1; obj.MAC1 = MAC1; obj.RSSI1 = RSSI1; obj.MAC2 = MAC2; obj.RSSI2 = RSSI2; obj.MAC3 = MAC3; obj.RSSI3 = RSSI3; i += 22; break; case 2: var MAC1 = bin8string(data[i + 2]) + ':' + bin8string(data[i + 3]) + ':' + bin8string(data[i + 4]) + ':' + bin8string(data[i + 5]) + ':' + bin8string(data[i + 6]) + ':' + bin8string(data[i + 7]); var RSSI1 = data[i + 8] * -1; var MAC2 = bin8string(data[i + 9]) + ':' + bin8string(data[i + 10]) + ':' + bin8string(data[i + 11]) + ':' + bin8string(data[i + 12]) + ':' + bin8string(data[i + 13]) + ':' + bin8string(data[i + 14]); var RSSI2 = data[i + 15] * -1; obj.MAC1 = MAC1; obj.RSSI1 = RSSI1; obj.MAC2 = MAC2; obj.RSSI2 = RSSI2; i += 15; break; case 1: var MAC1 = bin8string(data[i + 2]) + ':' + bin8string(data[i + 3]) + ':' + bin8string(data[i + 4]) + ':' + bin8string(data[i + 5]) + ':' + bin8string(data[i + 6]) + ':' + bin8string(data[i + 7]); var RSSI1 = data[i + 8] * -1; obj.MAC1 = MAC1; obj.RSSI1 = RSSI1; i += 8; break; default: //something is wrong with data, return error i = data.length; return { errors: ['Received data corrupted'] }; } break; default: //something is wrong with data, return error i = data.length; return { errors: ['Received data corrupted'] }; } } } else { return { errors: ['Invalid payload version'] }; } return obj; } function DecodeTD1ConfigPayload(data) { var obj = {}; if (data[0] == PAYLOAD_VERSION) { //STATUS obj.cfgStatus = ''; if (data[1] & 0x01) { obj.cfgStatus += '/CFG INIT'; } if (data[1] & 0x02) { obj.cfgStatus += '/CFG GET'; } if (data[1] & 0x04) { obj.cfgStatus += '/CFG SET'; } //BATTERY if (data[2] == ERROR_BAT_VOLTAGE) { obj.batVoltage = 'ERROR'; } else { obj.batVoltage = (data[2] * 5 + 3000) / 1000; } //TEMP if (bin8dec(data[3]) == ERROR_TEMP) { obj.temp = 'ERROR'; } else { obj.temp = bin8dec(data[3]); } //APP MAIN obj.appMainVers = data[4]; //APP MINOR obj.appMinorVers = data[5]; //PING INTERVAL obj.pingInterval = (data[6] << 8) | (data[7]); //LONG RANGE TRIGGER obj.longRangeTrigger = data[8]; //MID RANGE TRIGGER obj.midRangeTrigger = data[9]; //REJOIN TRIGGER obj.rejoinTrigger = (data[10] << 8) | (data[11]); //GPS FIXES obj.gpsFixes = data[12]; //MIN WIFI DETECTS obj.minWIFIDetects = data[13]; } else { return { errors: ['Invalid payload version'] }; } return obj; } //MAIN UPLINK DECODER CALL function decodeUplink(input) { if (input.fPort == DATA_PORT) { var ret = DecodeTD1DataPayload(input.bytes); if (ret.errors) { return { errors: [ret.errors] }; } return { data: ret }; } else if (input.fPort == CONFIG_PORT) { var ret = DecodeTD1ConfigPayload(input.bytes); if (ret.errors) { return { errors: [ret.errors] }; } return { data: ret }; } else { return { errors: ['Invalid FPort'] }; } } //DOWNLINK DECODER CALL function decodeDownlink(input) { if (input.fPort == DATA_PORT) { if (input.bytes.length == 1) { if (POS_REQUESTS[input.bytes[0]] == 'INVALID') { return { errors: ['Invalid positioning request value'] }; } else { return { data: { fPort: input.fPort, posRequest: POS_REQUESTS[input.bytes[0]], } }; } } else { return { errors: ['Invalid payload size'] }; } } else if (input.fPort == CONFIG_PORT) { if (input.bytes.length == 8) { var pingInterval_m = (input.bytes[0] << 8) | (input.bytes[1]); var rejoinTrigger_count = (input.bytes[4] << 8) | (input.bytes[5]); var gpsFixes_count = input.bytes[6]; var minWIFIDetects_count = input.bytes[7]; if ((pingInterval_m < 15) || (pingInterval_m > 50000)) { return { errors: ['Invalid ping interval value'] }; } if (rejoinTrigger_count > 50000) { return { errors: ['Invalid rejoin trigger value'] }; } if ((gpsFixes_count < 5) || (gpsFixes_count > 20)) { return { errors: ['Invalid GPS fixes value'] }; } if ((minWIFIDetects_count < 1) || (minWIFIDetects_count > 4)) { return { errors: ['Invalid minimum WIFI detections value'] }; } return { data: { fPort: input.fPort, pingInterval: pingInterval_m, longRangeTrigger: input.bytes[2], midRangeTrigger: input.bytes[3], rejoinTrigger: rejoinTrigger_count, gpsFixes: gpsFixes_count, minWIFIDetects: minWIFIDetects_count, } }; } else if (input.bytes.length == 1) { if (input.bytes[0]){ return { data: { fPort: input.fPort, configGet: true, } }; } else { return { data: { fPort: input.fPort, configGet: false, } }; } } else { return { errors: ['Invalid payload size'] }; } } else if (input.fPort == REJOIN_PORT) { if (input.bytes.length == 1) { if (input.bytes[0]){ return { data: { fPort: input.fPort, rejoinSet: true, } }; } else { return { data: { fPort: input.fPort, rejoinSet: false, } }; } } else { return { errors: ['Invalid payload size'] }; } } else { return { errors: ['Invalid fport'] }; } } //DOWNLINK ENCODER CALL function encodeDownlink(input) { if (input.data.fPort == DATA_PORT) { var posReqByte = POS_REQUESTS.indexOf(input.data.posRequest); if ((posReqByte < 1) || (posReqByte > 5)) { return { errors: ['Invalid positioning request value'] }; } return { fPort: DATA_PORT, bytes: [posReqByte], }; } else if (input.data.fPort == CONFIG_PORT) { if (typeof input.data.configGet !== 'undefined'){ var cfgGetByte = 0; if (input.data.configGet){ cfgGetByte = 1; } return { fPort: CONFIG_PORT, bytes: [cfgGetByte], }; } else if ((typeof input.data.pingInterval !== 'undefined') && (typeof input.data.longRangeTrigger !== 'undefined') && (typeof input.data.midRangeTrigger !== 'undefined') && (typeof input.data.rejoinTrigger !== 'undefined') && (typeof input.data.gpsFixes !== 'undefined') && (typeof input.data.minWIFIDetects !== 'undefined')){ if ((input.data.pingInterval < 15) || (input.data.pingInterval > 50000)) { return { errors: ['Invalid ping interval value'] }; } if ((input.data.longRangeTrigger < 0) || (input.data.longRangeTrigger > 255)) { return { errors: ['Invalid long range trigger value'] }; } if ((input.data.midRangeTrigger < 0) || (input.data.midRangeTrigger > 255)) { return { errors: ['Invalid mid range trigger value'] }; } if ((input.data.rejoinTrigger < 0) || (input.data.rejoinTrigger > 50000)) { return { errors: ['Invalid rejoin trigger value'] }; } if ((input.data.gpsFixes < 5) || (input.data.gpsFixes > 20)) { return { errors: ['Invalid GPS fixes value'] }; } if ((input.data.minWIFIDetects < 1) || (input.data.minWIFIDetects > 4)) { return { errors: ['Invalid minimum WIFI detections value'] }; } return { fPort: CONFIG_PORT, bytes: [((input.data.pingInterval >> 8) & 0xFF), (input.data.pingInterval & 0xFF), input.data.longRangeTrigger, input.data.midRangeTrigger, ((input.data.rejoinTrigger >> 8) & 0xFF), (input.data.rejoinTrigger & 0xFF), input.data.gpsFixes, input.data.minWIFIDetects], }; } else { return { errors: ['Invalid input params defined'] }; } } else if (input.data.fPort == REJOIN_PORT) { if (typeof input.data.rejoinSet !== 'undefined'){ var rejoinSetByte = 0; if (input.data.rejoinSet){ rejoinSetByte = 1; } return { fPort: REJOIN_PORT, bytes: [rejoinSetByte], }; } else { return { errors: ['Invalid input params defined'] }; } } else { return { errors: ['Invalid fport'] }; } } 

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.

Community Feedback