Sensor
/* * JavaScript implementation of brUncompress. */ // {{{ Constants const ST_UNDEF = 0; const ST_BL = 1; const ST_U4 = 2; const ST_I4 = 3; const ST_U8 = 4; const ST_I8 = 5; const ST_U16 = 6; const ST_I16 = 7; const ST_U24 = 8; const ST_I24 = 9; const ST_U32 = 10; const ST_I32 = 11; const ST_FL = 12; const ST = {}; ST[ST_UNDEF] = 0; ST[ST_BL] = 1; ST[ST_U4] = 4; ST[ST_I4] = 4; ST[ST_U8] = 8; ST[ST_I8] = 8; ST[ST_U16] = 16; ST[ST_I16] = 16; ST[ST_U24] = 24; ST[ST_I24] = 24; ST[ST_U32] = 32; ST[ST_I32] = 32; ST[ST_FL] = 32; const BR_HUFF_MAX_INDEX_TABLE = 14; const NUMBER_OF_SERIES = 16; const HUFF = [ [ { sz: 2, lbl: 0x000 }, { sz: 2, lbl: 0x001 }, { sz: 2, lbl: 0x003 }, { sz: 3, lbl: 0x005 }, { sz: 4, lbl: 0x009 }, { sz: 5, lbl: 0x011 }, { sz: 6, lbl: 0x021 }, { sz: 7, lbl: 0x041 }, { sz: 8, lbl: 0x081 }, { sz: 10, lbl: 0x200 }, { sz: 11, lbl: 0x402 }, { sz: 11, lbl: 0x403 }, { sz: 11, lbl: 0x404 }, { sz: 11, lbl: 0x405 }, { sz: 11, lbl: 0x406 }, { sz: 11, lbl: 0x407 }, ], [ { sz: 7, lbl: 0x06f }, { sz: 5, lbl: 0x01a }, { sz: 4, lbl: 0x00c }, { sz: 3, lbl: 0x003 }, { sz: 3, lbl: 0x007 }, { sz: 2, lbl: 0x002 }, { sz: 2, lbl: 0x000 }, { sz: 3, lbl: 0x002 }, { sz: 6, lbl: 0x036 }, { sz: 9, lbl: 0x1bb }, { sz: 9, lbl: 0x1b9 }, { sz: 10, lbl: 0x375 }, { sz: 10, lbl: 0x374 }, { sz: 10, lbl: 0x370 }, { sz: 11, lbl: 0x6e3 }, { sz: 11, lbl: 0x6e2 }, ], [ { sz: 4, lbl: 0x009 }, { sz: 3, lbl: 0x005 }, { sz: 2, lbl: 0x000 }, { sz: 2, lbl: 0x001 }, { sz: 2, lbl: 0x003 }, { sz: 5, lbl: 0x011 }, { sz: 6, lbl: 0x021 }, { sz: 7, lbl: 0x041 }, { sz: 8, lbl: 0x081 }, { sz: 10, lbl: 0x200 }, { sz: 11, lbl: 0x402 }, { sz: 11, lbl: 0x403 }, { sz: 11, lbl: 0x404 }, { sz: 11, lbl: 0x405 }, { sz: 11, lbl: 0x406 }, { sz: 11, lbl: 0x407 }, ], ]; // }}} // {{{ Polyfills Math.trunc = Math.trunc || function (x) { if (isNaN(x)) { return NaN; } if (x > 0) { return Math.floor(x); } return Math.ceil(x); }; // }}} /** * brUncompress main function */ function brUncompress(tagsz, argList, hexString, batch_absolute_timestamp) { const out = initResult(); const buffer = createBuffer(parseHexString(hexString)); const flag = generateFlag(buffer.getNextSample(ST_U8)); out.batch_counter = buffer.getNextSample(ST_U8, 3); buffer.getNextSample(ST_U8, 1); const temp = prePopulateOutput(out, buffer, argList, flag, tagsz); let { last_timestamp } = temp; const { index_of_the_first_sample } = temp; if (flag.hasSample) { last_timestamp = uncompressSamplesData(out, buffer, index_of_the_first_sample, argList, last_timestamp, flag, tagsz); } out.batch_relative_timestamp = extractTimestampFromBuffer(buffer, last_timestamp); return adaptToExpectedFormat(out, argList, batch_absolute_timestamp); } /// //////////// Sub functions /////////////// /** * Init br_uncompress result data structure */ function initResult() { const series = []; let i = 0; while (i < NUMBER_OF_SERIES) { series.push({ codingType: 0, codingTable: 0, resolution: null, uncompressSamples: [], }); i += 1; } return { batch_counter: 0, batch_relative_timestamp: 0, series, }; } /** * Function to create a buffer from a byteArray. Allow to read sample from the * byteArray to extract data. */ function createBuffer(byteArray) { /** * Retrieve the pattern for HUFF table lookup */ function bitsBuf2HuffPattern(byteArray, index, nb_bits) { let sourceBitStart = index; const sz = nb_bits - 1; if (byteArray.length * 8 < sourceBitStart + nb_bits) { throw "Verify that dest buf is large enough"; } let bittoread = 0; let pattern = 0; while (nb_bits > 0) { if (byteArray[sourceBitStart >> 3] & (1 << (sourceBitStart & 0x07))) { pattern |= 1 << (sz - bittoread); } nb_bits--; bittoread++; sourceBitStart++; } return pattern; } return { index: 0, byteArray, getNextSample(sampleType, nbBitsInput) { let nbBits = nbBitsInput || ST[sampleType]; let sourceBitStart = this.index; this.index += nbBits; if (sampleType === ST_FL && nbBits !== 32) { throw "Mauvais sampletype"; } let u32 = 0; let nbytes = Math.trunc((nbBits - 1) / 8) + 1; let nbitsfrombyte = nbBits % 8; if (nbitsfrombyte === 0 && nbytes > 0) { nbitsfrombyte = 8; } while (nbytes > 0) { let bittoread = 0; while (nbitsfrombyte > 0) { const idx = sourceBitStart >> 3; if (this.byteArray[idx] & (1 << (sourceBitStart & 0x07))) { u32 |= 1 << ((nbytes - 1) * 8 + bittoread); } nbitsfrombyte--; bittoread++; sourceBitStart += 1; } nbytes--; nbitsfrombyte = 8; } // Propagate the sign bit if 1 if ((sampleType == ST_I4 || sampleType == ST_I8 || sampleType == ST_I16 || sampleType == ST_I24) && u32 & (1 << (nbBits - 1))) { for (let i = nbBits; i < 32; i++) { u32 |= 1 << i; nbBits++; } } return u32; }, /** * Extract sz and bi from Huff table */ getNextBifromHi(huff_coding) { for (let i = 2; i < 12; i++) { const lhuff = bitsBuf2HuffPattern(this.byteArray, this.index, i); for (let j = 0; j < HUFF[huff_coding].length; j++) { if (HUFF[huff_coding][j].sz == i && lhuff == HUFF[huff_coding][j].lbl) { this.index += i; return j; } } } throw "Bi not found in HUFF table"; }, }; } /** * Convert the hex string given as parameter to a ByteArray */ function parseHexString(str) { str = str .split("") .filter(function (x) { return !isNaN(parseInt(x, 16)); }) .join(""); const result = []; while (str.length >= 2) { result.push(parseInt(str.substring(0, 2), 16)); str = str.substring(2, str.length); } return result; } /** * Generate a flag object from an integer value. */ function generateFlag(flagAsInt) { let binbase = flagAsInt.toString(2); // leftpad while (binbase.length < 8) { binbase = `0${binbase}`; } return { isCommonTimestamp: parseInt(binbase[binbase.length - 2], 2), hasSample: !parseInt(binbase[binbase.length - 3], 2), batch_req: parseInt(binbase[binbase.length - 4], 2), nb_of_type_measure: parseInt(binbase.substring(0, 4), 2), }; } /** * Prepopulate output with relative timestamp and measure of the first sample * for each series. */ function prePopulateOutput(out, buffer, argList, flag, tagsz) { let currentTimestamp = 0; let index_of_the_first_sample = 0; for (let i = 0; i < flag.nb_of_type_measure; i++) { const tag = { size: tagsz, lbl: buffer.getNextSample(ST_U8, tagsz), }; const sampleIndex = findIndexFromArgList(argList, tag); if (i == 0) { index_of_the_first_sample = sampleIndex; } currentTimestamp = extractTimestampFromBuffer(buffer, currentTimestamp); out.series[sampleIndex] = computeSeries(buffer, argList[sampleIndex].sampletype, tag.lbl, currentTimestamp); if (flag.hasSample) { out.series[sampleIndex].codingType = buffer.getNextSample(ST_U8, 2); out.series[sampleIndex].codingTable = buffer.getNextSample(ST_U8, 2); } } return { last_timestamp: currentTimestamp, index_of_the_first_sample, }; } /** * Initialize next series from buffer */ function computeSeries(buffer, sampletype, label, currentTimestamp) { return { uncompressSamples: [ { data_relative_timestamp: currentTimestamp, data: { value: getMeasure(buffer, sampletype), label, }, }, ], codingType: 0, codingTable: 0, resolution: null, }; } /** * Return the index of tag lbl in the argument list */ function findIndexFromArgList(argList, tag) { for (let i = 0; i < argList.length; i++) { if (argList[i].taglbl === tag.lbl) { return i; } } throw "Cannot find index in argList"; } /** * Extract a new time stamp using Huff table, optionnaly from a baseTimestamp */ function extractTimestampFromBuffer(buffer, baseTimestamp) { if (baseTimestamp) { const bi = buffer.getNextBifromHi(1); return computeTimestampFromBi(buffer, baseTimestamp, bi); } return buffer.getNextSample(ST_U32); } /** * Compute a new timestamp from a previous one, regarding bi value */ function computeTimestampFromBi(buffer, baseTimestamp, bi) { if (bi > BR_HUFF_MAX_INDEX_TABLE) { return buffer.getNextSample(ST_U32); } if (bi > 0) { return computeTimestampFromPositiveBi(buffer, baseTimestamp, bi); } return baseTimestamp; } /** * Compute a new timestamp from a previous one, regarding posotive bi value */ function computeTimestampFromPositiveBi(buffer, baseTimestamp, bi) { return buffer.getNextSample(ST_U32, bi) + baseTimestamp + Math.pow(2, bi) - 1; } /** * Extract the measure from the buffer, handling float case */ function getMeasure(buffer, sampletype) { const v = buffer.getNextSample(sampletype); return sampletype === ST_FL ? Bytes2Float32(v) : v; } /** * Uncompress samples data presenting common timestamp or separate timestamp */ function uncompressSamplesData(out, buffer, index_of_the_first_sample, argList, last_timestamp, flag, tagsz) { if (flag.isCommonTimestamp) { return handleCommonTimestamp(out, buffer, index_of_the_first_sample, argList, flag, tagsz); } return handleSeparateTimestamp(out, buffer, argList, last_timestamp, flag, tagsz); } /** * Uncompress data in case of common timestamp */ function handleCommonTimestamp(out, buffer, index_of_the_first_sample, argList, flag, tagsz) { // number of sample const nb_sample_to_parse = buffer.getNextSample(ST_U8, 8); const tag = {}; const temp = initTimestampCommonTable(out, buffer, nb_sample_to_parse, index_of_the_first_sample); const { timestampCommon } = temp; const { lastTimestamp } = temp; for (let j = 0; j < flag.nb_of_type_measure; j++) { let first_null_delta_value = 1; tag.lbl = buffer.getNextSample(ST_U8, tagsz); const sampleIndex = findIndexFromArgList(argList, tag); for (let i = 0; i < nb_sample_to_parse; i++) { // Available bit const available = buffer.getNextSample(ST_U8, 1); if (available) { // Delta value const bi = buffer.getNextBifromHi(out.series[sampleIndex].codingTable); const currentMeasure = { data_relative_timestamp: 0, data: {}, }; if (bi <= BR_HUFF_MAX_INDEX_TABLE) { const precedingValue = out.series[sampleIndex].uncompressSamples[out.series[sampleIndex].uncompressSamples.length - 1].data.value; if (bi > 0) { currentMeasure.data.value = completeCurrentMeasure(buffer, precedingValue, out.series[sampleIndex].codingType, argList[sampleIndex].resol, bi); } else { // (bi <= 0) if (first_null_delta_value) { // First value is yet recorded starting from the header first_null_delta_value = 0; continue; } else { currentMeasure.data.value = precedingValue; } } } else { // bi > BR_HUFF_MAX_INDEX_TABLE currentMeasure.data.value = buffer.getNextSample(argList[sampleIndex].sampletype); } currentMeasure.data_relative_timestamp = timestampCommon[i]; out.series[sampleIndex].uncompressSamples.push(currentMeasure); } } } return lastTimestamp; } /** * Initialize common timestamp table. Returns the table and last calculated timestamp */ function initTimestampCommonTable(out, buffer, nbSampleToParse, firstSampleIndex) { const timestampCommon = []; let lastTimestamp = 0; const timestampCoding = buffer.getNextSample(ST_U8, 2); for (let i = 0; i < nbSampleToParse; i++) { // delta timestamp const bi = buffer.getNextBifromHi(timestampCoding); if (bi <= BR_HUFF_MAX_INDEX_TABLE) { if (i == 0) { timestampCommon.push(out.series[firstSampleIndex].uncompressSamples[0].data_relative_timestamp); } else if (bi > 0) { var precedingTimestamp = timestampCommon[i - 1]; timestampCommon.push(buffer.getNextSample(ST_U32, bi) + precedingTimestamp + Math.pow(2, bi) - 1); } else { timestampCommon.push(precedingTimestamp); } } else { timestampCommon.push(buffer.getNextSample(ST_U32)); } lastTimestamp = timestampCommon[i]; } return { timestampCommon, lastTimestamp, }; } /** * Complete current measure from the preceding one */ function completeCurrentMeasure(buffer, precedingValue, codingType, resol, bi) { const currentValue = buffer.getNextSample(ST_U16, bi); if (codingType === 0) { // ADLC return computeAdlcValue(currentValue, resol, precedingValue, bi); } if (codingType === 1) { // Positive return (currentValue + Math.pow(2, bi) - 1) * resol + precedingValue; } // Negative return precedingValue - (currentValue + (Math.pow(2, bi) - 1)) * resol; } /** * Return current value in ADLC case */ function computeAdlcValue(currentValue, resol, precedingValue, bi) { if (currentValue >= Math.pow(2, bi - 1)) { return currentValue * resol + precedingValue; } return (currentValue + 1 - Math.pow(2, bi)) * resol + precedingValue; } /** * Uncompress data in case of separate timestamp */ function handleSeparateTimestamp(out, buffer, argList, last_timestamp, flag, tagsz) { const tag = {}; for (let i = 0; i < flag.nb_of_type_measure; i++) { tag.lbl = buffer.getNextSample(ST_U8, tagsz); const sampleIndex = findIndexFromArgList(argList, tag); const compressSampleNb = buffer.getNextSample(ST_U8, 8); if (compressSampleNb) { const timestampCoding = buffer.getNextSample(ST_U8, 2); for (let j = 0; j < compressSampleNb; j++) { const precedingRelativeTimestamp = out.series[sampleIndex].uncompressSamples[out.series[sampleIndex].uncompressSamples.length - 1].data_relative_timestamp; const currentMeasure = { data_relative_timestamp: 0, data: {}, }; let bi = buffer.getNextBifromHi(timestampCoding); currentMeasure.data_relative_timestamp = computeTimestampFromBi(buffer, precedingRelativeTimestamp, bi); if (currentMeasure.data_relative_timestamp > last_timestamp) { last_timestamp = currentMeasure.data_relative_timestamp; } bi = buffer.getNextBifromHi(out.series[sampleIndex].codingTable); if (bi <= BR_HUFF_MAX_INDEX_TABLE) { const precedingValue = out.series[sampleIndex].uncompressSamples[out.series[sampleIndex].uncompressSamples.length - 1].data.value; if (bi > 0) { currentMeasure.data.value = completeCurrentMeasure(buffer, precedingValue, out.series[sampleIndex].codingType, argList[sampleIndex].resol, bi); } else { // bi <= 0 currentMeasure.data.value = precedingValue; } } else { // bi > BR_HUFF_MAX_INDEX_TABLE currentMeasure.data.value = buffer.getNextSample(argList[sampleIndex].sampletype); } out.series[sampleIndex].uncompressSamples.push(currentMeasure); } } } return last_timestamp; } /** * Translate brUncompress output data to expected structure */ function adaptToExpectedFormat(out, argList, batchAbsoluteTimestamp) { const returnedGlobalObject = { // batch_counter: out.batch_counter, // batch_relative_timestamp: out.batch_relative_timestamp }; if (batchAbsoluteTimestamp) { returnedGlobalObject.b_ts = batchAbsoluteTimestamp; } returnedGlobalObject.datas = out.series.reduce(function (acc, current, index) { return acc.concat( current.uncompressSamples.map(function (item) { const returned = { // data_relative_timestamp: item.data_relative_timestamp, data: { value: argList[index].divide ? item.data.value / argList[index].divide : item.data.value, }, }; if (argList[index].lblname) { returned.data.label = argList[index].lblname; } if (batchAbsoluteTimestamp) { returned.date = computeDataAbsoluteTimestamp(batchAbsoluteTimestamp, out.batch_relative_timestamp, item.data_relative_timestamp); } return returned; }) ); }, []); return returnedGlobalObject; } /** * Compute data absolute timestamp from batch absolute timestamp (bat), batch * relative timestamp (brt) and data relative timestamp (drt) */ function computeDataAbsoluteTimestamp(bat, brt, drt) { return new Date(new Date(bat) - (brt - drt) * 1000).toISOString(); } try { module.exports = brUncompress; } catch (e) { // when called from nashorn, module.exports is unavailable… } function UintToInt(Uint, Size) { if (Size === 2) { if ((Uint & 0x8000) > 0) { Uint -= 0x10000; } } if (Size === 3) { if ((Uint & 0x800000) > 0) { Uint -= 0x1000000; } } if (Size === 4) { if ((Uint & 0x80000000) > 0) { Uint -= 0x100000000; } } return Uint; } function decimalToHex(d, padding) { let hex = Number(d).toString(16).toUpperCase(); padding = typeof padding === "undefined" || padding === null ? (padding = 2) : padding; while (hex.length < padding) { hex = `0${hex}`; } return `0x${hex}`; } function Bytes2Float32(bytes) { const sign = bytes & 0x80000000 ? -1 : 1; let exponent = ((bytes >> 23) & 0xff) - 127; let significand = bytes & ~(-1 << 23); if (exponent == 128) return sign * (significand ? Number.NaN : Number.POSITIVE_INFINITY); if (exponent == -127) { if (significand == 0) return sign * 0.0; exponent = -126; significand /= 1 << 23; } else significand = (significand | (1 << 23)) / (1 << 23); return sign * significand * Math.pow(2, exponent); } function Decoder(bytes, port) { // Decode an uplink message from a buffer // (array) of bytes to an object of fields. var decoded = {}; let decodedBatch = {}; const lora = {}; // decoded.lora.port = port; // Get raw payload const bytes_len_ = bytes.length; let temp_hex_str = ""; lora.payload = ""; for (let j = 0; j < bytes_len_; j++) { temp_hex_str = bytes[j].toString(16).toUpperCase(); if (temp_hex_str.length == 1) { temp_hex_str = `0${temp_hex_str}`; } lora.payload += temp_hex_str; } const date = new Date(); const lDate = date.toISOString(); if (port === 125) { // batch decodedBatch = !(bytes[0] & 0x01); // trame standard if (decodedBatch === false) { decoded.zclheader = {}; decoded.zclheader.report = "standard"; attributID = -1; cmdID = -1; clusterdID = -1; // endpoint decoded.zclheader.endpoint = ((bytes[0] & 0xe0) >> 5) | ((bytes[0] & 0x06) << 2); // command ID cmdID = bytes[1]; decoded.zclheader.cmdID = decimalToHex(cmdID, 2); // Cluster ID clusterdID = bytes[2] * 256 + bytes[3]; decoded.zclheader.clusterdID = decimalToHex(clusterdID, 4); // decode report and read atrtribut response if ((cmdID === 0x0a) | (cmdID === 0x8a) | (cmdID === 0x01)) { stdData = {}; var tab = []; // Attribut ID attributID = bytes[4] * 256 + bytes[5]; decoded.zclheader.attributID = decimalToHex(attributID, 4); if (cmdID === 0x8a) { decoded.zclheader.alarm = 1; } else { decoded.zclheader.alarm = 0; } // data index start if ((cmdID === 0x0a) | (cmdID === 0x8a)) index = 7; if (cmdID === 0x01) { index = 8; decoded.zclheader.status = bytes[6]; } //console.log(clusterdID); //console.log(attributID); //console.log(bytes[index]); // temperature if ((clusterdID === 0x0402) & (attributID === 0x0000)) { tab.push({ label: "Temperature", value: UintToInt(bytes[index] * 256 + bytes[index + 1], 2) / 100, date: lDate }); tab.push({ label: "Temperature_std", value: UintToInt(bytes[index] * 256 + bytes[index + 1], 2) / 100, date: lDate }); } // humidity if ((clusterdID === 0x0405) & (attributID === 0x0000)) { tab.push({ label: "Humidity", value: (bytes[index] * 256 + bytes[index + 1]) / 100, date: lDate }); } // Open Close if ((clusterdID === 0x000F) & (attributID === 0x0055)) { tab.push({ label: "Violation", value:bytes[index], date: lDate }); } // lorawan message type if ( (clusterdID === 0x8004 ) & (attributID === 0x0000)) { if (bytes[index] === 1) stdData.message_type = "confirmed"; if (bytes[index] === 0) stdData.message_type = "unconfirmed"; } // lorawan retry if ((clusterdID === 0x8004) & (attributID === 0x0001)) { stdData.nb_retry = bytes[index]; } // lorawan reassociation if ((clusterdID === 0x8004) & (attributID === 0x0002)) { stdData.period_in_minutes = bytes[index + 1] * 256 + bytes[index + 2]; stdData.nb_err_frames = bytes[index + 3] * 256 + bytes[index + 4]; } // configuration node power desc if ((clusterdID === 0x0050) & (attributID === 0x0006)) { index2 = index + 3; if ((bytes[index + 2] & 0x01) === 0x01) { tab.push({ label: "ExternalPowerVoltage", value: (bytes[index2] * 256 + bytes[index2 + 1]) / 1000, date: lDate }); index2 += 2; } if ((bytes[index + 2] & 0x04) === 0x04) { tab.push({ label: "BatteryVoltage", value: (bytes[index2] * 256 + bytes[index2 + 1]) / 1000, date: lDate }); index2 += 2; } if ((bytes[index + 2] & 0x02) === 0x02) { decoded.data.rechargeable_battery_voltage = (bytes[index2] * 256 + bytes[index2 + 1]) / 1000; index2 += 2; } if ((bytes[index + 2] & 0x08) === 0x08) { decoded.data.solar_harvesting_voltage = (bytes[index2] * 256 + bytes[index2 + 1]) / 1000; index2 += 2; } if ((bytes[index + 2] & 0x10) === 0x10) { decoded.data.tic_harvesting_voltage = (bytes[index2] * 256 + bytes[index2 + 1]) / 1000; index2 += 2; } // tab.push(stdData); } decoded.data = tab; } // decode configuration response if (cmdID === 0x07) { // AttributID attributID = bytes[6] * 256 + bytes[7]; decoded.zclheader.attributID = decimalToHex(attributID, 4); // status decoded.zclheader.status = bytes[4]; // batch decoded.zclheader.decodedBatch = bytes[5]; } // decode read configuration response if (cmdID === 0x09) { // AttributID attributID = bytes[6] * 256 + bytes[7]; decoded.zclheader.attributID = decimalToHex(attributID, 4); // status decoded.zclheader.status = bytes[4]; // batch decoded.zclheader.decodedBatch = bytes[5]; // AttributType decoded.zclheader.attribut_type = bytes[8]; // min decoded.zclheader.min = {}; if ((bytes[9] & 0x80) === 0x80) { decoded.zclheader.min.value = (bytes[9] - 0x80) * 256 + bytes[10]; decoded.zclheader.min.unity = "minutes"; } else { decoded.zclheader.min.value = bytes[9] * 256 + bytes[10]; decoded.zclheader.min.unity = "seconds"; } // max decoded.zclheader.max = {}; if ((bytes[9] & 0x80) === 0x80) { decoded.zclheader.max.value = (bytes[9] - 0x80) * 256 + bytes[10]; decoded.zclheader.max.unity = "minutes"; } else { decoded.zclheader.max.value = bytes[9] * 256 + bytes[10]; decoded.zclheader.max.unity = "seconds"; } } } else { var decoded = {}; brData = brUncompress( 2, [ { taglbl: 0, resol: 10, sampletype: 7, lblname: "Temperature", divide: 100 }, { taglbl: 1, resol: 100, sampletype: 6, lblname: "Humidity", divide: 100 }, { taglbl: 2, resol: 1, sampletype: 6, lblname: "BatteryVoltage", divide: 1000 }, ], lora.payload, lDate ); const data_length = brData.datas.length; var tab = []; for (let i = 0; i < data_length; i++) { tab.push({ label: brData.datas[i].data.label, value: brData.datas[i].data.value, date: brData.datas[i].date }); } decoded.data = tab; decoded.zclheader = {}; decoded.zclheader.report = "batch"; } } return decoded; } //const ignore_vars = ['timestamp','rssi','snr','lora_bandwidth','lora_spreading_factor','data_rate_index','coding_rate','frequency','time','gateway_eui']; const ignore_vars = ['protocol_data_rssi','protocol_data_signal','protocol_data_snr','location','geolocation_precision','city_name','lora_coding_rate','gateway_location','city_code','geolocation_type','protocol_data_requested_nbrep','protocol_data_noise','protocol_data_lora_version','protocol_data_gateways','protocol_data_best_gateway_id','protocol_data_netid','protocol_data_devnonce','protocol_data_devaddr','protocol_data_appnonce','device_properties_external_id','device_properties_deveui','device_properties_appeui','payload_encrypted','type','profile','profile_id','group','group_id','device_id','timestamp','rssi','snr','lora_bandwidth','data_rate_index','coding_rate','frequency','time','gateway_eui','id']; //timestamp,fport,fcnt,payload,rssi,snr,application_id,device_id,downlink_key payload = payload.filter(x => !ignore_vars.includes(x.variable)); // I'm adding this extra code to run the TTN parser. const payload_raw = payload.find((x) => x.variable === "payload_raw" || x.variable === "payload" || x.variable === "data"); if (payload_raw) { // Convert the data from Hex to Buffer (same as bytes from TTN). const buffer = Buffer.from(payload_raw.value, "hex"); // Get the port variable. const port = payload.find((x) => x.variable === "port" || x.variable === "fport"|| x.variable === "protocol_data_port"); // Run the TTN decoder. const decoded_payload = Decoder(buffer, port ? port.value : null); // Copy or generate a serie for this batch of data. const serie = payload[0].serie || String(new Date().getTime()); // Normalize the decoded_payload.data if (decoded_payload && decoded_payload.data) { // transform/normalize the structure to TagoIO structure. const normalized_payload = decoded_payload.data.map((data) => { return { variable: data.label.toLowerCase(), value: data.value, time: data.date }; }); // concat both array. payload = payload.concat(normalized_payload); // console.log(normalized_payload); //payload = normalized_payload; } // Parse of zclheader if (decoded_payload.zclheader) { // Check if alarm was provided, and then concatenate. if ("alarm" in decoded_payload.zclheader) { payload.push({ variable: "alarm", value: decoded_payload.zclheader.alarm, serie }); } if ("report" in decoded_payload.zclheader) { // payload.push({ variable: "report", value: decoded_payload.zclheader.report, serie }); } } } // console.log(payload);
This codec is sourced from TagoIO Github. All rights belong to TagoIO Github.
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.