SEEED - Sensecap S2100 Lorawan Data Logger

Sensor

Codec Description

Codec for SEEED - Sensecap-S2100-Lorawan-Data-Logger (v1.0.0).

Codec Preview

/* eslint-disable no-throw-literal */ /* eslint-disable vars-on-top */ /* eslint-disable no-var */ /* eslint-disable radix */ /* eslint-disable no-case-declarations */ /* eslint-disable no-unused-vars */ /* eslint-disable no-plusplus */ /* eslint-disable no-use-before-define */ /** * Entry, decoder.js */ function decodeUplink(input, port) { // init var bytes = bytes2HexString(input).toLocaleUpperCase(); const result = { err: 0, payload: bytes, valid: true, messages: [], }; const splitArray = dataSplit(bytes); // data decoder const decoderArray = []; for (let i = 0; i < splitArray.length; i++) { const item = splitArray[i]; const { dataId } = item; const { dataValue } = item; const messages = dataIdAndDataValueJudge(dataId, dataValue); decoderArray.push(messages); } result.messages = decoderArray; return { data: result }; } /** * data splits * @param bytes * @returns {*[]} */ function dataSplit(bytes) { const frameArray = []; for (let i = 0; i < bytes.length; i++) { const remainingValue = bytes; const dataId = remainingValue.substring(0, 2); let dataValue; let dataObj = {}; switch (dataId) { case "01": case "20": case "21": case "30": case "31": case "33": case "40": case "41": case "42": case "43": case "44": case "45": dataValue = remainingValue.substring(2, 22); bytes = remainingValue.substring(22); dataObj = { dataId, dataValue, }; break; case "02": dataValue = remainingValue.substring(2, 18); bytes = remainingValue.substring(18); dataObj = { dataId: "02", dataValue, }; break; case "03": case "06": dataValue = remainingValue.substring(2, 4); bytes = remainingValue.substring(4); dataObj = { dataId, dataValue, }; break; case "05": case "34": dataValue = bytes.substring(2, 10); bytes = remainingValue.substring(10); dataObj = { dataId, dataValue, }; break; case "04": case "10": case "32": case "35": case "36": case "37": case "38": case "39": dataValue = bytes.substring(2, 20); bytes = remainingValue.substring(20); dataObj = { dataId, dataValue, }; break; default: dataValue = "9"; break; } if (dataValue.length < 2) { break; } frameArray.push(dataObj); } return frameArray; } function dataIdAndDataValueJudge(dataId, dataValue) { let messages = []; switch (dataId) { case "01": const temperature = dataValue.substring(0, 4); const humidity = dataValue.substring(4, 6); const illumination = dataValue.substring(6, 14); const uv = dataValue.substring(14, 16); const windSpeed = dataValue.substring(16, 20); messages = [ { variable: "4097", value: loraWANV2DataFormat(temperature, 10), metadata: { type: "Air Temperature", }, }, { variable: "4098", value: loraWANV2DataFormat(humidity), metadata: { type: "Air Humidity", }, }, { variable: "4099", value: loraWANV2DataFormat(illumination), metadata: { type: "Light Intensity", }, }, { variable: "4190", value: loraWANV2DataFormat(uv, 10), metadata: { type: "UV Index", }, }, { variable: "4105", value: loraWANV2DataFormat(windSpeed, 10), metadata: { type: "Wind Speed", }, }, ]; break; case "02": const windDirection = dataValue.substring(0, 4); const rainfall = dataValue.substring(4, 12); const airPressure = dataValue.substring(12, 16); messages = [ { variable: "4104", value: loraWANV2DataFormat(windDirection), metadata: { type: "Wind Direction Sensor", }, }, { variable: "4113", value: loraWANV2DataFormat(rainfall, 1000), metadata: { type: "Rain Gauge", }, }, { variable: "4101", value: loraWANV2DataFormat(airPressure, 0.1), metadata: { type: "Barometric Pressure", }, }, ]; break; case "03": const Electricity = dataValue; messages = [ { variable: "battery", value: loraWANV2DataFormat(Electricity), unit: "%", }, ]; break; case "04": const electricityWhether = dataValue.substring(0, 2); const hwv = dataValue.substring(2, 6); const bdv = dataValue.substring(6, 10); const sensorAcquisitionInterval = dataValue.substring(10, 14); const gpsAcquisitionInterval = dataValue.substring(14, 18); messages = [ { variable: "battery", value: loraWANV2DataFormat(electricityWhether), unit: "%", }, { variable: "hardware_version", value: `${loraWANV2DataFormat( hwv.substring(0, 2) )}.${loraWANV2DataFormat(hwv.substring(2, 4))}`, }, { variable: "firmware_version", value: `${loraWANV2DataFormat( bdv.substring(0, 2) )}.${loraWANV2DataFormat(bdv.substring(2, 4))}`, }, { variable: "measure_interval", value: parseInt(loraWANV2DataFormat(sensorAcquisitionInterval)) * 60, }, { variable: "gps_interval", value: parseInt(loraWANV2DataFormat(gpsAcquisitionInterval)) * 60, }, ]; break; case "05": const sensorAcquisitionIntervalFive = dataValue.substring(0, 4); const gpsAcquisitionIntervalFive = dataValue.substring(4, 8); messages = [ { variable: "measure_interval", value: parseInt(loraWANV2DataFormat(sensorAcquisitionIntervalFive)) * 60, }, { variable: "gps_interval", value: parseInt(loraWANV2DataFormat(gpsAcquisitionIntervalFive)) * 60, }, ]; break; case "06": const errorCode = dataValue; let descZh; switch (errorCode) { case "00": descZh = "ccl_sensor_error_none"; break; case "01": descZh = "ccl_sensor_not_found"; break; case "02": descZh = "ccl_sensor_wakeup_error"; break; case "03": descZh = "ccl_sensor_not_response"; break; case "04": descZh = "ccl_sensor_data_empty"; break; case "05": descZh = "ccl_sensor_data_head_error"; break; case "06": descZh = "ccl_sensor_data_crc_error"; break; case "07": descZh = "ccl_sensor_data_b1_no_valid"; break; case "08": descZh = "ccl_sensor_data_b2_no_valid"; break; case "09": descZh = "ccl_sensor_random_not_match"; break; case "0A": descZh = "ccl_sensor_pubkey_sign_verify_failed"; break; case "0B": descZh = "ccl_sensor_data_sign_verify_failed"; break; case "0C": descZh = "ccl_sensor_data_value_hi"; break; case "0D": descZh = "ccl_sensor_data_value_low"; break; case "0E": descZh = "ccl_sensor_data_value_missed"; break; case "0F": descZh = "ccl_sensor_arg_invaild"; break; case "10": descZh = "ccl_sensor_rs485_master_busy"; break; case "11": descZh = "ccl_sensor_rs485_rev_data_error"; break; case "12": descZh = "ccl_sensor_rs485_reg_missed"; break; case "13": descZh = "ccl_sensor_rs485_fun_exe_error"; break; case "14": descZh = "ccl_sensor_rs485_write_strategy_error"; break; case "15": descZh = "ccl_sensor_config_error"; break; case "FF": descZh = "ccl_sensor_data_error_unkonw"; break; default: descZh = "cc_other_failed"; break; } messages = [ { variable: "4101", value: descZh, metadata: { type: "sensor_error_event", }, }, ]; break; case "10": const statusValue = dataValue.substring(0, 2); const { status, type } = loraWANV2BitDataFormat(statusValue); const sensecapId = dataValue.substring(2); messages = [ { variable: "case10", value: "case10", metadata: { status, channelType: type, sensorEui: sensecapId, }, }, ]; break; case "20": let initmeasurementId = 4175; const sensor = []; for (let i = 0; i < dataValue.length; i += 4) { const modelId = loraWANV2DataFormat(dataValue.substring(i, i + 2)); const detectionType = loraWANV2DataFormat( dataValue.substring(i + 2, i + 4) ); const aiHeadValues = `${modelId}.${detectionType}`; sensor.push({ variable: String(initmeasurementId), value: aiHeadValues, }); initmeasurementId++; } messages = sensor; break; case "21": // Vision AI: // AI 识别输出帧 const tailValueArray = []; let initTailmeasurementId = 4180; for (let i = 0; i < dataValue.length; i += 4) { const modelId = loraWANV2DataFormat(dataValue.substring(i, i + 2)); const detectionType = loraWANV2DataFormat( dataValue.substring(i + 2, i + 4) ); const aiTailValues = `${modelId}.${detectionType}`; tailValueArray.push({ variable: String(initTailmeasurementId), value: aiTailValues, metadata: { type: `AI Detection ${i}`, }, }); initTailmeasurementId++; } messages = tailValueArray; break; case "30": case "31": // 首帧或者首帧输出帧 const channelInfoOne = loraWANV2ChannelBitFormat( dataValue.substring(0, 2) ); const dataOne = { variable: String(parseInt(channelInfoOne.one)), value: loraWANV2DataFormat(dataValue.substring(4, 12), 1000), metadata: { type: "Measurement", }, }; const dataTwo = { variable: String(parseInt(channelInfoOne.two)), value: loraWANV2DataFormat(dataValue.substring(12, 20), 1000), metadata: { type: "Measurement", }, }; const cacheArrayInfo = []; if (parseInt(channelInfoOne.one)) { cacheArrayInfo.push(dataOne); } if (parseInt(channelInfoOne.two)) { cacheArrayInfo.push(dataTwo); } cacheArrayInfo.forEach((item) => { messages.push(item); }); break; case "32": const channelInfoTwo = loraWANV2ChannelBitFormat( dataValue.substring(0, 2) ); const dataThree = { variable: String(parseInt(channelInfoTwo.one)), value: loraWANV2DataFormat(dataValue.substring(2, 10), 1000), metadata: { type: "Measurement", }, }; const dataFour = { variable: String(parseInt(channelInfoTwo.two)), value: loraWANV2DataFormat(dataValue.substring(10, 18), 1000), metadata: { type: "Measurement", }, }; if (parseInt(channelInfoTwo.one)) { messages.push(dataThree); } if (parseInt(channelInfoTwo.two)) { messages.push(dataFour); } break; case "33": const channelInfoThree = loraWANV2ChannelBitFormat( dataValue.substring(0, 2) ); const dataFive = { variable: String(parseInt(channelInfoThree.one)), value: loraWANV2DataFormat(dataValue.substring(4, 12), 1000), metadata: { type: "Measurement", }, }; const dataSix = { variable: String(parseInt(channelInfoThree.two)), value: loraWANV2DataFormat(dataValue.substring(12, 20), 1000), metadata: { type: "Measurement", }, }; if (parseInt(channelInfoThree.one)) { messages.push(dataFive); } if (parseInt(channelInfoThree.two)) { messages.push(dataSix); } break; case "34": const model = loraWANV2DataFormat(dataValue.substring(0, 2)); const GPIOInput = loraWANV2DataFormat(dataValue.substring(2, 4)); const simulationModel = loraWANV2DataFormat(dataValue.substring(4, 6)); const simulationInterface = loraWANV2DataFormat( dataValue.substring(6, 8) ); messages = [ { variable: "datalogger", value: "datalogger", metadata: { dataloggerProtocol: model, dataloggerGPIOInput: GPIOInput, dataloggerAnalogType: simulationModel, dataloggerAnalogInterface: simulationInterface, }, }, ]; break; case "35": case "36": const channelTDOne = loraWANV2ChannelBitFormat(dataValue.substring(0, 2)); const channelSortTDOne = 3920 + (parseInt(channelTDOne.one) - 1) * 2; const channelSortTDTWO = 3921 + (parseInt(channelTDOne.one) - 1) * 2; messages = [ { variable: String([channelSortTDOne]), value: loraWANV2DataFormat(dataValue.substring(2, 10), 1000), }, { variable: String([channelSortTDTWO]), value: loraWANV2DataFormat(dataValue.substring(10, 18), 1000), }, ]; break; case "37": const channelTDInfoTwo = loraWANV2ChannelBitFormat( dataValue.substring(0, 2) ); const channelSortOne = 3920 + (parseInt(channelTDInfoTwo.one) - 1) * 2; const channelSortTWO = 3921 + (parseInt(channelTDInfoTwo.one) - 1) * 2; messages = [ { variable: String([channelSortOne]), value: loraWANV2DataFormat(dataValue.substring(2, 10), 1000), }, { variable: String([channelSortTWO]), value: loraWANV2DataFormat(dataValue.substring(10, 18), 1000), }, ]; break; case "38": const channelTDInfoThree = loraWANV2ChannelBitFormat( dataValue.substring(0, 2) ); const channelSortThreeOne = 3920 + (parseInt(channelTDInfoThree.one) - 1) * 2; const channelSortThreeTWO = 3921 + (parseInt(channelTDInfoThree.one) - 1) * 2; messages = [ { variable: String([channelSortThreeOne]), value: loraWANV2DataFormat(dataValue.substring(2, 10), 1000), }, { variable: String([channelSortThreeTWO]), value: loraWANV2DataFormat(dataValue.substring(10, 18), 1000), }, ]; break; case "39": const electricityWhetherTD = dataValue.substring(0, 2); const hwvTD = dataValue.substring(2, 6); const bdvTD = dataValue.substring(6, 10); const sensorAcquisitionIntervalTD = dataValue.substring(10, 14); const gpsAcquisitionIntervalTD = dataValue.substring(14, 18); messages = [ { variable: "battery", value: loraWANV2DataFormat(electricityWhetherTD), unit: "%", }, { variable: "hardware_version", value: `${loraWANV2DataFormat( hwvTD.substring(0, 2) )}.${loraWANV2DataFormat(hwvTD.substring(2, 4))}`, }, { variable: "firmware_version", value: `${loraWANV2DataFormat( bdvTD.substring(0, 2) )}.${loraWANV2DataFormat(bdvTD.substring(2, 4))}`, }, { variable: "measure_interval", value: parseInt(loraWANV2DataFormat(sensorAcquisitionIntervalTD)) * 60, }, { variable: "thresholdMeasureInterval", value: parseInt(loraWANV2DataFormat(gpsAcquisitionIntervalTD)), }, ]; break; case "40": case "41": const lightIntensity = dataValue.substring(0, 4); const loudness = dataValue.substring(4, 8); // X const accelerateX = dataValue.substring(8, 12); // Y const accelerateY = dataValue.substring(12, 16); // Z const accelerateZ = dataValue.substring(16, 20); messages = [ { variable: "4193", value: loraWANV2DataFormat(lightIntensity), metadata: { type: "Light Intensity", }, }, { variable: "4192", value: loraWANV2DataFormat(loudness), metadata: { type: "Sound Intensity", }, }, { variable: "4150", value: loraWANV2DataFormat(accelerateX, 100), metadata: { type: "AccelerometerX", }, }, { variable: "4151", value: loraWANV2DataFormat(accelerateY, 100), metadata: { type: "AccelerometerY", }, }, { variable: "4152", value: loraWANV2DataFormat(accelerateZ, 100), metadata: { type: "AccelerometerZ", }, }, ]; break; case "42": const airTemperature = dataValue.substring(0, 4); const AirHumidity = dataValue.substring(4, 8); const tVOC = dataValue.substring(8, 12); const CO2eq = dataValue.substring(12, 16); const soilMoisture = dataValue.substring(16, 20); messages = [ { variable: "4097", value: loraWANV2DataFormat(airTemperature, 100), metadata: { type: "Air Temperature", }, }, { variable: "4098", value: loraWANV2DataFormat(AirHumidity, 100), metadata: { type: "Air Humidity", }, }, { variable: "4195", value: loraWANV2DataFormat(tVOC), metadata: { type: "Total Volatile Organic Compounds", }, }, { variable: "4100", value: loraWANV2DataFormat(CO2eq), metadata: { type: "CO2", }, }, { variable: "4196", value: loraWANV2DataFormat(soilMoisture), metadata: { type: "Soil moisture intensity", }, }, ]; break; case "43": case "44": const headerDevKitValueArray = []; let initDevkitmeasurementId = 4175; for (let i = 0; i < dataValue.length; i += 4) { const modelId = loraWANV2DataFormat(dataValue.substring(i, i + 2)); const detectionType = loraWANV2DataFormat( dataValue.substring(i + 2, i + 4) ); const aiHeadValues = `${modelId}.${detectionType}`; headerDevKitValueArray.push({ variable: String(initDevkitmeasurementId), value: aiHeadValues, metadata: { type: `AI Detection ${i}`, }, }); initDevkitmeasurementId++; } messages = headerDevKitValueArray; break; case "45": let initTailDevKitmeasurementId = 4180; for (let i = 0; i < dataValue.length; i += 4) { const modelId = loraWANV2DataFormat(dataValue.substring(i, i + 2)); const detectionType = loraWANV2DataFormat( dataValue.substring(i + 2, i + 4) ); const aiTailValues = `${modelId}.${detectionType}`; messages.push({ variable: String(initTailDevKitmeasurementId), value: aiTailValues, metadata: { type: `AI Detection ${i}`, }, }); initTailDevKitmeasurementId++; } break; default: break; } return messages; } /** * * data formatting * @param str * @param divisor * @returns {string|number} */ function loraWANV2DataFormat(str, divisor = 1) { const strReverse = bigEndianTransform(str); let str2 = toBinary(strReverse); if (str2.substring(0, 1) === "1") { const arr = str2.split(""); const reverseArr = arr.map((item) => { if (parseInt(item) === 1) { return 0; } return 1; }); str2 = parseInt(reverseArr.join(""), 2) + 1; return `-${str2 / divisor}`; } return parseInt(str2, 2) / divisor; } /** * Handling big-endian data formats * @param data * @returns {*[]} */ function bigEndianTransform(data) { const dataArray = []; for (let i = 0; i < data.length; i += 2) { dataArray.push(data.substring(i, i + 2)); } // array of hex return dataArray; } /** * Convert to an 8-digit binary number with 0s in front of the number * @param arr * @returns {string} */ function toBinary(arr) { const binaryData = arr.map((item) => { let data = parseInt(item, 16).toString(2); const dataLength = data.length; if (data.length !== 8) { for (let i = 0; i < 8 - dataLength; i++) { data = `0${data}`; } } return data; }); const ret = binaryData.toString().replace(/,/g, ""); return ret; } /** * sensor * @param str * @returns {{channel: number, type: number, status: number}} */ function loraWANV2BitDataFormat(str) { const strReverse = bigEndianTransform(str); const str2 = toBinary(strReverse); const channel = parseInt(str2.substring(0, 4), 2); const status = parseInt(str2.substring(4, 5), 2); const type = parseInt(str2.substring(5), 2); return { channel, status, type }; } /** * channel info * @param str * @returns {{channelTwo: number, channelOne: number}} */ function loraWANV2ChannelBitFormat(str) { const strReverse = bigEndianTransform(str); const str2 = toBinary(strReverse); const one = parseInt(str2.substring(0, 4), 2); const two = parseInt(str2.substring(4, 8), 2); const resultInfo = { one, two, }; return resultInfo; } /** * data log status bit * @param str * @returns {{total: number, level: number, isTH: number}} */ function loraWANV2DataLogBitFormat(str) { const strReverse = bigEndianTransform(str); const str2 = toBinary(strReverse); const isTH = parseInt(str2.substring(0, 1), 2); const total = parseInt(str2.substring(1, 5), 2); const left = parseInt(str2.substring(5), 2); const resultInfo = { isTH, total, left, }; return resultInfo; } function bytes2HexString(arrBytes) { var str = ""; for (var i = 0; i < arrBytes.length; i++) { var tmp; var num = arrBytes[i]; if (num < 0) { tmp = (255 + num + 1).toString(16); } else { tmp = num.toString(16); } if (tmp.length === 1) { tmp = `0${tmp}`; } str += tmp; } return str; } function toTagoFormat(result) { if (!result.data.messages.length) { return "Payload is not valid"; } console.log("result", result.data.messages); const arrayToTago = []; result.data.messages.forEach((messageArray) => { messageArray.forEach((item) => { arrayToTago.push(item); }); }); console.log("arrayToTago", arrayToTago); return arrayToTago; } const payload_raw = payload.find((x) => x.variable === "payload"); if (payload_raw) { try { // Convert the data from Hex to Javascript Buffer. const buffer = Buffer.from(payload_raw.value, "hex"); const payload_aux = toTagoFormat(decodeUplink(buffer)); // verify if payload_aux is of type array payload = payload.concat(payload_aux.map((x) => ({ ...x }))); } catch (e) { // Print the error to the Live Inspector. console.error(e); // Return the variable parse_error for debugging. payload = [{ variable: "parse_error", value: e.message }]; } } console.log("payload", 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.

Community Feedback