diff --git a/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/converter.json b/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/converter.json index f4d2c4ed..b5df8e83 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/converter.json +++ b/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/converter.json @@ -14,6 +14,6 @@ "lora_dev_eui", "lora_snr" ], - "decoder": "// WARNING: This script is designed to work with a remote JS executor. Compatibility with a local JS evaluator is not guaranteed and may require rewriting.\n\nconst message = decodeToJson(payload);\nconst values = Object.assign(\n decodeDevicePayload(base64ToBytes(message.data)),\n extractNetworkTelemetry(message)\n);\n\nreturn {\n deviceName: String(values.device_id),\n deviceType: 'DL-SHT35',\n attributes: {\n lora_dev_eui: values.lora_dev_eui,\n protocol_version: values.protocol_version,\n },\n telemetry: [{\n ts: message.time,\n values: keepTelemetry(values)\n }]\n};\n\nfunction decodeDevicePayload(payload) {\n /* https://www.decentlab.com/products/air-temperature-and-humidity-sensor-with-radiation-shield-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 2,\n values: [{name: 'air_temperature',\n displayName: 'Air temperature',\n convert: function (x) { return 175 * x[0] / 65535 - 45; },\n unit: '°C'},\n {name: 'air_humidity',\n displayName: 'Air humidity',\n convert: function (x) { return 100 * x[1] / 65535; },\n unit: '%'}]},\n {length: 1,\n values: [{name: 'battery_voltage',\n displayName: 'Battery voltage',\n convert: function (x) { return x[0] / 1000; },\n unit: 'V'}]}\n ],\n read_int: function (bytes, pos) {\n return (bytes[pos] << 8) + bytes[pos + 1];\n },\n decode: function (msg) {\n var bytes = msg;\n var i, j;\n if (typeof msg === 'string') {\n bytes = [];\n for (i = 0; i < msg.length; i += 2) {\n bytes.push(parseInt(msg.substring(i, i + 2), 16));\n }\n }\n var version = bytes[0];\n if (version != this.PROTOCOL_VERSION) {\n return {error: \"protocol version \" + version + \" doesn't match v2\"};\n }\n var deviceId = this.read_int(bytes, 1);\n var flags = this.read_int(bytes, 3);\n var result = {'protocol_version': version, 'device_id': deviceId};\n // decode payload\n var pos = 5;\n for (i = 0; i < this.SENSORS.length; i++, flags >>= 1) {\n if ((flags & 1) !== 1)\n continue;\n var sensor = this.SENSORS[i];\n var x = [];\n // convert data to 16-bit integer array\n for (j = 0; j < sensor.length; j++) {\n x.push(this.read_int(bytes, pos));\n pos += 2;\n }\n // decode sensor values\n for (j = 0; j < sensor.values.length; j++) {\n var value = sensor.values[j];\n if ('convert' in value) {\n result[value.name] = {displayName: value.displayName,\n value: value.convert.bind(this)(x)};\n if ('unit' in value)\n result[value.name]['unit'] = value.unit;\n }\n }\n }\n return result;\n }\n };\n\n const decoded = decentlab_decoder.decode(payload);\n const result = {};\n for (var k in decoded) {\n if (typeof decoded[k] === \"object\") {\n result[k] = decoded[k].value;\n } else if (typeof decoded[k] === \"number\") {\n result[k] = decoded[k];\n }\n }\n return result;\n}\n\nfunction extractNetworkTelemetry(payload) {\n const result = {\n lora_frame_counter: message.fCnt,\n lora_frame_port: message.fPort,\n lora_frequency: message.txInfo.frequency,\n lora_spreading_factor:\n message?.txInfo?.modulation?.lora?.spreadingFactor ||\n message.txInfo.loRaModulationInfo.spreadingFactor,\n lora_dev_eui: (\n message?.deviceInfo?.devEui ||\n base64ToBytes(message.devEUI).reduce(\n (a, x) => a + (\"0\" + x.toString(16)).slice(-2),\n \"\",\n )\n ).toUpperCase(),\n };\n const gwi = message.rxInfo\n .map((x) => ({ lora_rssi: x.rssi, lora_snr: x.snr || x.loRaSNR }))\n .reduce((a, x) => (x.lora_rssi > a.lora_rssi ? x : a), {\n lora_rssi: -Number.MAX_VALUE,\n });\n return Object.assign(result, gwi);\n}\n\n// Helper functions\n\nfunction decodeToJson(payload) {\n const s = String.fromCharCode.apply(String, payload);\n return JSON.parse(s);\n}\n\nfunction parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n }\n // If we cannot parse timestamp - we will use the current timestamp\n if (timestamp == -1) {\n timestamp = Date.now();\n }\n\n return timestamp;\n}\n\nfunction base64ToBytes(base64String) {\n if (typeof base64String === 'string') {\n return Uint8Array.from(atob(base64String), c => c.charCodeAt(0));\n }\n return [];\n}\n\nfunction keepTelemetry(telemetry) {\n const unwanted = ['protocol_version', 'device_id', 'lora_dev_eui'];\n return Object.fromEntries(\n Object.entries(telemetry).filter(\n (x) => !unwanted.includes(x[0])\n )\n );\n}" + "decoder": "// WARNING: This script is designed to work with a remote JS executor. Compatibility with a local JS evaluator is not guaranteed and may require rewriting.\n\nconst message = decodeToJson(payload);\nconst values = Object.assign(\n decodeDevicePayload(base64ToBytes(message.data)),\n extractNetworkTelemetry(message)\n);\n\nreturn {\n deviceName: String(values.device_id),\n deviceType: 'DL-SHT35-001',\n attributes: {\n lora_dev_eui: values.lora_dev_eui,\n protocol_version: values.protocol_version,\n },\n telemetry: [{\n ts: message.time,\n values: keepTelemetry(values)\n }]\n};\n\nfunction decodeDevicePayload(payload) {\n /* https://www.decentlab.com/products/air-temperature-and-humidity-sensor-with-radiation-shield-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 2,\n values: [{name: 'air_temperature',\n displayName: 'Air temperature',\n convert: function (x) { return 175 * x[0] / 65535 - 45; },\n unit: '°C'},\n {name: 'air_humidity',\n displayName: 'Air humidity',\n convert: function (x) { return 100 * x[1] / 65535; },\n unit: '%'}]},\n {length: 1,\n values: [{name: 'battery_voltage',\n displayName: 'Battery voltage',\n convert: function (x) { return x[0] / 1000; },\n unit: 'V'}]}\n ],\n read_int: function (bytes, pos) {\n return (bytes[pos] << 8) + bytes[pos + 1];\n },\n decode: function (msg) {\n var bytes = msg;\n var i, j;\n if (typeof msg === 'string') {\n bytes = [];\n for (i = 0; i < msg.length; i += 2) {\n bytes.push(parseInt(msg.substring(i, i + 2), 16));\n }\n }\n var version = bytes[0];\n if (version != this.PROTOCOL_VERSION) {\n return {error: \"protocol version \" + version + \" doesn't match v2\"};\n }\n var deviceId = this.read_int(bytes, 1);\n var flags = this.read_int(bytes, 3);\n var result = {'protocol_version': version, 'device_id': deviceId};\n // decode payload\n var pos = 5;\n for (i = 0; i < this.SENSORS.length; i++, flags >>= 1) {\n if ((flags & 1) !== 1)\n continue;\n var sensor = this.SENSORS[i];\n var x = [];\n // convert data to 16-bit integer array\n for (j = 0; j < sensor.length; j++) {\n x.push(this.read_int(bytes, pos));\n pos += 2;\n }\n // decode sensor values\n for (j = 0; j < sensor.values.length; j++) {\n var value = sensor.values[j];\n if ('convert' in value) {\n result[value.name] = {displayName: value.displayName,\n value: value.convert.bind(this)(x)};\n if ('unit' in value)\n result[value.name]['unit'] = value.unit;\n }\n }\n }\n return result;\n }\n };\n\n const decoded = decentlab_decoder.decode(payload);\n const result = {};\n for (var k in decoded) {\n if (typeof decoded[k] === \"object\") {\n result[k] = decoded[k].value;\n } else if (typeof decoded[k] === \"number\") {\n result[k] = decoded[k];\n }\n }\n return result;\n}\n\nfunction extractNetworkTelemetry(payload) {\n const result = {\n lora_frame_counter: message.fCnt,\n lora_frame_port: message.fPort,\n lora_frequency: message.txInfo.frequency,\n lora_spreading_factor:\n message?.txInfo?.modulation?.lora?.spreadingFactor ||\n message.txInfo.loRaModulationInfo.spreadingFactor,\n lora_dev_eui: (\n message?.deviceInfo?.devEui ||\n base64ToBytes(message.devEUI).reduce(\n (a, x) => a + (\"0\" + x.toString(16)).slice(-2),\n \"\",\n )\n ).toUpperCase(),\n };\n const gwi = message.rxInfo\n .map((x) => ({ lora_rssi: x.rssi, lora_snr: x.snr || x.loRaSNR }))\n .reduce((a, x) => (x.lora_rssi > a.lora_rssi ? x : a), {\n lora_rssi: -Number.MAX_VALUE,\n });\n return Object.assign(result, gwi);\n}\n\n// Helper functions\n\nfunction decodeToJson(payload) {\n const s = String.fromCharCode.apply(String, payload);\n return JSON.parse(s);\n}\n\nfunction parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n }\n // If we cannot parse timestamp - we will use the current timestamp\n if (timestamp == -1) {\n timestamp = Date.now();\n }\n\n return timestamp;\n}\n\nfunction base64ToBytes(base64String) {\n if (typeof base64String === 'string') {\n return Uint8Array.from(atob(base64String), c => c.charCodeAt(0));\n }\n return [];\n}\n\nfunction keepTelemetry(telemetry) {\n const unwanted = ['protocol_version', 'device_id', 'lora_dev_eui'];\n return Object.fromEntries(\n Object.entries(telemetry).filter(\n (x) => !unwanted.includes(x[0])\n )\n );\n}" } } \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/result.json b/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/result.json index 4fba4c87..a1f6ddd0 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/result.json +++ b/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/result.json @@ -1,6 +1,6 @@ { "deviceName": "782", - "deviceType": "DL-SHT35", + "deviceType": "DL-SHT35-001", "attributes": { "lora_dev_eui": "70B3D57BA0003BB8", "protocol_version": 2 diff --git a/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/result_01.json b/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/result_01.json index 3fa3482f..911cb1f3 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/result_01.json +++ b/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/result_01.json @@ -1,6 +1,6 @@ { "deviceName": "782", - "deviceType": "DL-SHT35", + "deviceType": "DL-SHT35-001", "attributes": { "lora_dev_eui": "70B3D57BA0003BB8", "protocol_version": 2 diff --git a/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/result_02.json b/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/result_02.json index 32566044..46072cb4 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/result_02.json +++ b/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/result_02.json @@ -1,6 +1,6 @@ { "deviceName": "782", - "deviceType": "DL-SHT35", + "deviceType": "DL-SHT35-001", "attributes": { "lora_dev_eui": "0202020202020202", "protocol_version": 2 diff --git a/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/result_03.json b/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/result_03.json index 1d189d1f..69ad5abe 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/result_03.json +++ b/VENDORS/Decentlab/DL-SHT35-001/ChirpStack/uplink/result_03.json @@ -1,6 +1,6 @@ { "deviceName": "782", - "deviceType": "DL-SHT35", + "deviceType": "DL-SHT35-001", "attributes": { "lora_dev_eui": "0202020202020202", "protocol_version": 2 diff --git a/VENDORS/Decentlab/DL-SHT35-001/LORIOT/uplink/converter.json b/VENDORS/Decentlab/DL-SHT35-001/LORIOT/uplink/converter.json index be781ef1..4b7c01d4 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/LORIOT/uplink/converter.json +++ b/VENDORS/Decentlab/DL-SHT35-001/LORIOT/uplink/converter.json @@ -14,6 +14,6 @@ "lora_dev_eui", "lora_snr" ], - "decoder": "// WARNING: This script is designed to work with a remote JS executor. Compatibility with a local JS evaluator is not guaranteed and may require rewriting.\n\nconst message = decodeToJson(payload);\nconst values = Object.assign(\n decodeDevicePayload(message.data),\n extractNetworkTelemetry(message)\n);\n\nreturn {\n deviceName: String(values.device_id),\n deviceType: 'DL-SHT35',\n attributes: {\n lora_dev_eui: values.lora_dev_eui,\n protocol_version: values.protocol_version,\n },\n telemetry: [{\n ts: message.ts,\n values: keepTelemetry(values)\n }]\n};\n\nfunction decodeDevicePayload(payload) {\n /* https://www.decentlab.com/products/air-temperature-and-humidity-sensor-with-radiation-shield-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 2,\n values: [{name: 'air_temperature',\n displayName: 'Air temperature',\n convert: function (x) { return 175 * x[0] / 65535 - 45; },\n unit: '°C'},\n {name: 'air_humidity',\n displayName: 'Air humidity',\n convert: function (x) { return 100 * x[1] / 65535; },\n unit: '%'}]},\n {length: 1,\n values: [{name: 'battery_voltage',\n displayName: 'Battery voltage',\n convert: function (x) { return x[0] / 1000; },\n unit: 'V'}]}\n ],\n read_int: function (bytes, pos) {\n return (bytes[pos] << 8) + bytes[pos + 1];\n },\n decode: function (msg) {\n var bytes = msg;\n var i, j;\n if (typeof msg === 'string') {\n bytes = [];\n for (i = 0; i < msg.length; i += 2) {\n bytes.push(parseInt(msg.substring(i, i + 2), 16));\n }\n }\n var version = bytes[0];\n if (version != this.PROTOCOL_VERSION) {\n return {error: \"protocol version \" + version + \" doesn't match v2\"};\n }\n var deviceId = this.read_int(bytes, 1);\n var flags = this.read_int(bytes, 3);\n var result = {'protocol_version': version, 'device_id': deviceId};\n // decode payload\n var pos = 5;\n for (i = 0; i < this.SENSORS.length; i++, flags >>= 1) {\n if ((flags & 1) !== 1)\n continue;\n var sensor = this.SENSORS[i];\n var x = [];\n // convert data to 16-bit integer array\n for (j = 0; j < sensor.length; j++) {\n x.push(this.read_int(bytes, pos));\n pos += 2;\n }\n // decode sensor values\n for (j = 0; j < sensor.values.length; j++) {\n var value = sensor.values[j];\n if ('convert' in value) {\n result[value.name] = {displayName: value.displayName,\n value: value.convert.bind(this)(x)};\n if ('unit' in value)\n result[value.name]['unit'] = value.unit;\n }\n }\n }\n return result;\n }\n };\n\n const decoded = decentlab_decoder.decode(payload);\n const result = {};\n for (var k in decoded) {\n if (typeof decoded[k] === \"object\") {\n result[k] = decoded[k].value;\n } else if (typeof decoded[k] === \"number\") {\n result[k] = decoded[k];\n }\n }\n return result;\n}\n\nfunction extractNetworkTelemetry(payload) {\n const result = {\n lora_frame_counter: message.fcnt,\n lora_frame_port: message.port,\n lora_frequency: message.freq,\n lora_snr: message.snr,\n lora_rssi: message.rssi,\n lora_dev_eui: message.EUI,\n lora_spreading_factor: parseInt(message.dr.replace(/.*SF(\\d+).*/, '$1')),\n };\n if (message.cmd === 'gw') {\n const gwi = message.gws\n .map((x) => ({ lora_rssi: x.rssi, lora_snr: x.snr }))\n .reduce((a, x) => (x.lora_rssi > a.lora_rssi ? x : a), {\n lora_rssi: -Number.MAX_VALUE,\n });\n result.rssi = gwi.rssi;\n result.snr = gwi.snr;\n }\n return result;\n}\n\n// Helper functions\n\nfunction decodeToJson(payload) {\n const s = String.fromCharCode.apply(String, payload);\n return JSON.parse(s);\n}\n\nfunction parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n }\n // If we cannot parse timestamp - we will use the current timestamp\n if (timestamp == -1) {\n timestamp = Date.now();\n }\n\n return timestamp;\n}\n\nfunction base64ToBytes(base64String) {\n if (typeof base64String === 'string') {\n return Uint8Array.from(atob(base64String), c => c.charCodeAt(0));\n }\n return [];\n}\n\nfunction keepTelemetry(telemetry) {\n const unwanted = ['protocol_version', 'device_id', 'lora_dev_eui'];\n return Object.fromEntries(\n Object.entries(telemetry).filter(\n (x) => !unwanted.includes(x[0])\n )\n );\n}" + "decoder": "// WARNING: This script is designed to work with a remote JS executor. Compatibility with a local JS evaluator is not guaranteed and may require rewriting.\n\nconst message = decodeToJson(payload);\nconst values = Object.assign(\n decodeDevicePayload(message.data),\n extractNetworkTelemetry(message)\n);\n\nreturn {\n deviceName: String(values.device_id),\n deviceType: 'DL-SHT35-001',\n attributes: {\n lora_dev_eui: values.lora_dev_eui,\n protocol_version: values.protocol_version,\n },\n telemetry: [{\n ts: message.ts,\n values: keepTelemetry(values)\n }]\n};\n\nfunction decodeDevicePayload(payload) {\n /* https://www.decentlab.com/products/air-temperature-and-humidity-sensor-with-radiation-shield-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 2,\n values: [{name: 'air_temperature',\n displayName: 'Air temperature',\n convert: function (x) { return 175 * x[0] / 65535 - 45; },\n unit: '°C'},\n {name: 'air_humidity',\n displayName: 'Air humidity',\n convert: function (x) { return 100 * x[1] / 65535; },\n unit: '%'}]},\n {length: 1,\n values: [{name: 'battery_voltage',\n displayName: 'Battery voltage',\n convert: function (x) { return x[0] / 1000; },\n unit: 'V'}]}\n ],\n read_int: function (bytes, pos) {\n return (bytes[pos] << 8) + bytes[pos + 1];\n },\n decode: function (msg) {\n var bytes = msg;\n var i, j;\n if (typeof msg === 'string') {\n bytes = [];\n for (i = 0; i < msg.length; i += 2) {\n bytes.push(parseInt(msg.substring(i, i + 2), 16));\n }\n }\n var version = bytes[0];\n if (version != this.PROTOCOL_VERSION) {\n return {error: \"protocol version \" + version + \" doesn't match v2\"};\n }\n var deviceId = this.read_int(bytes, 1);\n var flags = this.read_int(bytes, 3);\n var result = {'protocol_version': version, 'device_id': deviceId};\n // decode payload\n var pos = 5;\n for (i = 0; i < this.SENSORS.length; i++, flags >>= 1) {\n if ((flags & 1) !== 1)\n continue;\n var sensor = this.SENSORS[i];\n var x = [];\n // convert data to 16-bit integer array\n for (j = 0; j < sensor.length; j++) {\n x.push(this.read_int(bytes, pos));\n pos += 2;\n }\n // decode sensor values\n for (j = 0; j < sensor.values.length; j++) {\n var value = sensor.values[j];\n if ('convert' in value) {\n result[value.name] = {displayName: value.displayName,\n value: value.convert.bind(this)(x)};\n if ('unit' in value)\n result[value.name]['unit'] = value.unit;\n }\n }\n }\n return result;\n }\n };\n\n const decoded = decentlab_decoder.decode(payload);\n const result = {};\n for (var k in decoded) {\n if (typeof decoded[k] === \"object\") {\n result[k] = decoded[k].value;\n } else if (typeof decoded[k] === \"number\") {\n result[k] = decoded[k];\n }\n }\n return result;\n}\n\nfunction extractNetworkTelemetry(payload) {\n const result = {\n lora_frame_counter: message.fcnt,\n lora_frame_port: message.port,\n lora_frequency: message.freq,\n lora_snr: message.snr,\n lora_rssi: message.rssi,\n lora_dev_eui: message.EUI,\n lora_spreading_factor: parseInt(message.dr.replace(/.*SF(\\d+).*/, '$1')),\n };\n if (message.cmd === 'gw') {\n const gwi = message.gws\n .map((x) => ({ lora_rssi: x.rssi, lora_snr: x.snr }))\n .reduce((a, x) => (x.lora_rssi > a.lora_rssi ? x : a), {\n lora_rssi: -Number.MAX_VALUE,\n });\n result.rssi = gwi.rssi;\n result.snr = gwi.snr;\n }\n return result;\n}\n\n// Helper functions\n\nfunction decodeToJson(payload) {\n const s = String.fromCharCode.apply(String, payload);\n return JSON.parse(s);\n}\n\nfunction parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n }\n // If we cannot parse timestamp - we will use the current timestamp\n if (timestamp == -1) {\n timestamp = Date.now();\n }\n\n return timestamp;\n}\n\nfunction base64ToBytes(base64String) {\n if (typeof base64String === 'string') {\n return Uint8Array.from(atob(base64String), c => c.charCodeAt(0));\n }\n return [];\n}\n\nfunction keepTelemetry(telemetry) {\n const unwanted = ['protocol_version', 'device_id', 'lora_dev_eui'];\n return Object.fromEntries(\n Object.entries(telemetry).filter(\n (x) => !unwanted.includes(x[0])\n )\n );\n}" } } \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-SHT35-001/LORIOT/uplink/result.json b/VENDORS/Decentlab/DL-SHT35-001/LORIOT/uplink/result.json index 178e03a5..cffecb65 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/LORIOT/uplink/result.json +++ b/VENDORS/Decentlab/DL-SHT35-001/LORIOT/uplink/result.json @@ -1,6 +1,6 @@ { "deviceName": "782", - "deviceType": "DL-SHT35", + "deviceType": "DL-SHT35-001", "attributes": { "lora_dev_eui": "0004A30B001ADCB5", "protocol_version": 2 diff --git a/VENDORS/Decentlab/DL-SHT35-001/LORIOT/uplink/result_01.json b/VENDORS/Decentlab/DL-SHT35-001/LORIOT/uplink/result_01.json index f531f446..ba31c58b 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/LORIOT/uplink/result_01.json +++ b/VENDORS/Decentlab/DL-SHT35-001/LORIOT/uplink/result_01.json @@ -1,6 +1,6 @@ { "deviceName": "782", - "deviceType": "DL-SHT35", + "deviceType": "DL-SHT35-001", "attributes": { "lora_dev_eui": "0004A30B001ADCB5", "protocol_version": 2 diff --git a/VENDORS/Decentlab/DL-SHT35-001/ThingPark/uplink/converter.json b/VENDORS/Decentlab/DL-SHT35-001/ThingPark/uplink/converter.json index 2feb45d4..d03547dd 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/ThingPark/uplink/converter.json +++ b/VENDORS/Decentlab/DL-SHT35-001/ThingPark/uplink/converter.json @@ -14,6 +14,6 @@ "lora_dev_eui", "lora_snr" ], - "decoder": "// WARNING: This script is designed to work with a remote JS executor. Compatibility with a local JS evaluator is not guaranteed and may require rewriting.\n\nconst message = decodeToJson(payload);\nconst values = Object.assign(\n decodeDevicePayload(message.DevEUI_uplink.payload_hex),\n extractNetworkTelemetry(message)\n);\n\nreturn {\n deviceName: String(values.device_id),\n deviceType: 'DL-SHT35',\n attributes: {\n lora_dev_eui: values.lora_dev_eui,\n protocol_version: values.protocol_version,\n },\n telemetry: [{\n ts: parseDateToTimestamp(message.DevEUI_uplink.Time),\n values: keepTelemetry(values)\n }]\n};\n\nfunction decodeDevicePayload(payload) {\n /* https://www.decentlab.com/products/air-temperature-and-humidity-sensor-with-radiation-shield-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 2,\n values: [{name: 'air_temperature',\n displayName: 'Air temperature',\n convert: function (x) { return 175 * x[0] / 65535 - 45; },\n unit: '°C'},\n {name: 'air_humidity',\n displayName: 'Air humidity',\n convert: function (x) { return 100 * x[1] / 65535; },\n unit: '%'}]},\n {length: 1,\n values: [{name: 'battery_voltage',\n displayName: 'Battery voltage',\n convert: function (x) { return x[0] / 1000; },\n unit: 'V'}]}\n ],\n read_int: function (bytes, pos) {\n return (bytes[pos] << 8) + bytes[pos + 1];\n },\n decode: function (msg) {\n var bytes = msg;\n var i, j;\n if (typeof msg === 'string') {\n bytes = [];\n for (i = 0; i < msg.length; i += 2) {\n bytes.push(parseInt(msg.substring(i, i + 2), 16));\n }\n }\n var version = bytes[0];\n if (version != this.PROTOCOL_VERSION) {\n return {error: \"protocol version \" + version + \" doesn't match v2\"};\n }\n var deviceId = this.read_int(bytes, 1);\n var flags = this.read_int(bytes, 3);\n var result = {'protocol_version': version, 'device_id': deviceId};\n // decode payload\n var pos = 5;\n for (i = 0; i < this.SENSORS.length; i++, flags >>= 1) {\n if ((flags & 1) !== 1)\n continue;\n var sensor = this.SENSORS[i];\n var x = [];\n // convert data to 16-bit integer array\n for (j = 0; j < sensor.length; j++) {\n x.push(this.read_int(bytes, pos));\n pos += 2;\n }\n // decode sensor values\n for (j = 0; j < sensor.values.length; j++) {\n var value = sensor.values[j];\n if ('convert' in value) {\n result[value.name] = {displayName: value.displayName,\n value: value.convert.bind(this)(x)};\n if ('unit' in value)\n result[value.name]['unit'] = value.unit;\n }\n }\n }\n return result;\n }\n };\n\n const decoded = decentlab_decoder.decode(payload);\n const result = {};\n for (var k in decoded) {\n if (typeof decoded[k] === \"object\") {\n result[k] = decoded[k].value;\n } else if (typeof decoded[k] === \"number\") {\n result[k] = decoded[k];\n }\n }\n return result;\n}\n\nfunction extractNetworkTelemetry(payload) {\n return {\n lora_frame_counter: message.DevEUI_uplink.FCntUp,\n lora_frame_port: message.DevEUI_uplink.FPort,\n lora_frequency: message.DevEUI_uplink?.Frequency * 1000000,\n lora_spreading_factor: message.DevEUI_uplink.SpFact,\n lora_dev_eui: message.DevEUI_uplink.DevEUI,\n lora_rssi: message.DevEUI_uplink.LrrRSSI,\n lora_snr: message.DevEUI_uplink.LrrSNR,\n };\n}\n\n// Helper functions\n\nfunction decodeToJson(payload) {\n const s = String.fromCharCode.apply(String, payload);\n return JSON.parse(s);\n}\n\nfunction parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n }\n // If we cannot parse timestamp - we will use the current timestamp\n if (timestamp == -1) {\n timestamp = Date.now();\n }\n\n return timestamp;\n}\n\nfunction base64ToBytes(base64String) {\n if (typeof base64String === 'string') {\n return Uint8Array.from(atob(base64String), c => c.charCodeAt(0));\n }\n return [];\n}\n\nfunction keepTelemetry(telemetry) {\n const unwanted = ['protocol_version', 'device_id', 'lora_dev_eui'];\n return Object.fromEntries(\n Object.entries(telemetry).filter(\n (x) => !unwanted.includes(x[0])\n )\n );\n}" + "decoder": "// WARNING: This script is designed to work with a remote JS executor. Compatibility with a local JS evaluator is not guaranteed and may require rewriting.\n\nconst message = decodeToJson(payload);\nconst values = Object.assign(\n decodeDevicePayload(message.DevEUI_uplink.payload_hex),\n extractNetworkTelemetry(message)\n);\n\nreturn {\n deviceName: String(values.device_id),\n deviceType: 'DL-SHT35-001',\n attributes: {\n lora_dev_eui: values.lora_dev_eui,\n protocol_version: values.protocol_version,\n },\n telemetry: [{\n ts: parseDateToTimestamp(message.DevEUI_uplink.Time),\n values: keepTelemetry(values)\n }]\n};\n\nfunction decodeDevicePayload(payload) {\n /* https://www.decentlab.com/products/air-temperature-and-humidity-sensor-with-radiation-shield-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 2,\n values: [{name: 'air_temperature',\n displayName: 'Air temperature',\n convert: function (x) { return 175 * x[0] / 65535 - 45; },\n unit: '°C'},\n {name: 'air_humidity',\n displayName: 'Air humidity',\n convert: function (x) { return 100 * x[1] / 65535; },\n unit: '%'}]},\n {length: 1,\n values: [{name: 'battery_voltage',\n displayName: 'Battery voltage',\n convert: function (x) { return x[0] / 1000; },\n unit: 'V'}]}\n ],\n read_int: function (bytes, pos) {\n return (bytes[pos] << 8) + bytes[pos + 1];\n },\n decode: function (msg) {\n var bytes = msg;\n var i, j;\n if (typeof msg === 'string') {\n bytes = [];\n for (i = 0; i < msg.length; i += 2) {\n bytes.push(parseInt(msg.substring(i, i + 2), 16));\n }\n }\n var version = bytes[0];\n if (version != this.PROTOCOL_VERSION) {\n return {error: \"protocol version \" + version + \" doesn't match v2\"};\n }\n var deviceId = this.read_int(bytes, 1);\n var flags = this.read_int(bytes, 3);\n var result = {'protocol_version': version, 'device_id': deviceId};\n // decode payload\n var pos = 5;\n for (i = 0; i < this.SENSORS.length; i++, flags >>= 1) {\n if ((flags & 1) !== 1)\n continue;\n var sensor = this.SENSORS[i];\n var x = [];\n // convert data to 16-bit integer array\n for (j = 0; j < sensor.length; j++) {\n x.push(this.read_int(bytes, pos));\n pos += 2;\n }\n // decode sensor values\n for (j = 0; j < sensor.values.length; j++) {\n var value = sensor.values[j];\n if ('convert' in value) {\n result[value.name] = {displayName: value.displayName,\n value: value.convert.bind(this)(x)};\n if ('unit' in value)\n result[value.name]['unit'] = value.unit;\n }\n }\n }\n return result;\n }\n };\n\n const decoded = decentlab_decoder.decode(payload);\n const result = {};\n for (var k in decoded) {\n if (typeof decoded[k] === \"object\") {\n result[k] = decoded[k].value;\n } else if (typeof decoded[k] === \"number\") {\n result[k] = decoded[k];\n }\n }\n return result;\n}\n\nfunction extractNetworkTelemetry(payload) {\n return {\n lora_frame_counter: message.DevEUI_uplink.FCntUp,\n lora_frame_port: message.DevEUI_uplink.FPort,\n lora_frequency: message.DevEUI_uplink?.Frequency * 1000000,\n lora_spreading_factor: message.DevEUI_uplink.SpFact,\n lora_dev_eui: message.DevEUI_uplink.DevEUI,\n lora_rssi: message.DevEUI_uplink.LrrRSSI,\n lora_snr: message.DevEUI_uplink.LrrSNR,\n };\n}\n\n// Helper functions\n\nfunction decodeToJson(payload) {\n const s = String.fromCharCode.apply(String, payload);\n return JSON.parse(s);\n}\n\nfunction parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n }\n // If we cannot parse timestamp - we will use the current timestamp\n if (timestamp == -1) {\n timestamp = Date.now();\n }\n\n return timestamp;\n}\n\nfunction base64ToBytes(base64String) {\n if (typeof base64String === 'string') {\n return Uint8Array.from(atob(base64String), c => c.charCodeAt(0));\n }\n return [];\n}\n\nfunction keepTelemetry(telemetry) {\n const unwanted = ['protocol_version', 'device_id', 'lora_dev_eui'];\n return Object.fromEntries(\n Object.entries(telemetry).filter(\n (x) => !unwanted.includes(x[0])\n )\n );\n}" } } \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-SHT35-001/ThingPark/uplink/result.json b/VENDORS/Decentlab/DL-SHT35-001/ThingPark/uplink/result.json index 196a5cdf..3e9504d7 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/ThingPark/uplink/result.json +++ b/VENDORS/Decentlab/DL-SHT35-001/ThingPark/uplink/result.json @@ -1,6 +1,6 @@ { "deviceName": "782", - "deviceType": "DL-SHT35", + "deviceType": "DL-SHT35-001", "attributes": { "lora_dev_eui": "70B3D57BA000156B", "protocol_version": 2 diff --git a/VENDORS/Decentlab/DL-SHT35-001/ThingPark/uplink/result_01.json b/VENDORS/Decentlab/DL-SHT35-001/ThingPark/uplink/result_01.json index e83504f1..ea5af45d 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/ThingPark/uplink/result_01.json +++ b/VENDORS/Decentlab/DL-SHT35-001/ThingPark/uplink/result_01.json @@ -1,6 +1,6 @@ { "deviceName": "782", - "deviceType": "DL-SHT35", + "deviceType": "DL-SHT35-001", "attributes": { "lora_dev_eui": "70B3D57BA000156B", "protocol_version": 2 diff --git a/VENDORS/Decentlab/DL-SHT35-001/ThingParkEnterprise/uplink/converter.json b/VENDORS/Decentlab/DL-SHT35-001/ThingParkEnterprise/uplink/converter.json index 533f0957..86f7f62b 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/ThingParkEnterprise/uplink/converter.json +++ b/VENDORS/Decentlab/DL-SHT35-001/ThingParkEnterprise/uplink/converter.json @@ -14,6 +14,6 @@ "lora_dev_eui", "lora_snr" ], - "decoder": "// WARNING: This script is designed to work with a remote JS executor. Compatibility with a local JS evaluator is not guaranteed and may require rewriting.\n\nconst message = decodeToJson(payload);\nconst values = Object.assign(\n decodeDevicePayload(message.DevEUI_uplink.payload_hex),\n extractNetworkTelemetry(message)\n);\n\nreturn {\n deviceName: String(values.device_id),\n deviceType: 'DL-SHT35',\n attributes: {\n lora_dev_eui: values.lora_dev_eui,\n protocol_version: values.protocol_version,\n },\n telemetry: [{\n ts: parseDateToTimestamp(message.DevEUI_uplink.Time),\n values: keepTelemetry(values)\n }]\n};\n\nfunction decodeDevicePayload(payload) {\n /* https://www.decentlab.com/products/air-temperature-and-humidity-sensor-with-radiation-shield-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 2,\n values: [{name: 'air_temperature',\n displayName: 'Air temperature',\n convert: function (x) { return 175 * x[0] / 65535 - 45; },\n unit: '°C'},\n {name: 'air_humidity',\n displayName: 'Air humidity',\n convert: function (x) { return 100 * x[1] / 65535; },\n unit: '%'}]},\n {length: 1,\n values: [{name: 'battery_voltage',\n displayName: 'Battery voltage',\n convert: function (x) { return x[0] / 1000; },\n unit: 'V'}]}\n ],\n read_int: function (bytes, pos) {\n return (bytes[pos] << 8) + bytes[pos + 1];\n },\n decode: function (msg) {\n var bytes = msg;\n var i, j;\n if (typeof msg === 'string') {\n bytes = [];\n for (i = 0; i < msg.length; i += 2) {\n bytes.push(parseInt(msg.substring(i, i + 2), 16));\n }\n }\n var version = bytes[0];\n if (version != this.PROTOCOL_VERSION) {\n return {error: \"protocol version \" + version + \" doesn't match v2\"};\n }\n var deviceId = this.read_int(bytes, 1);\n var flags = this.read_int(bytes, 3);\n var result = {'protocol_version': version, 'device_id': deviceId};\n // decode payload\n var pos = 5;\n for (i = 0; i < this.SENSORS.length; i++, flags >>= 1) {\n if ((flags & 1) !== 1)\n continue;\n var sensor = this.SENSORS[i];\n var x = [];\n // convert data to 16-bit integer array\n for (j = 0; j < sensor.length; j++) {\n x.push(this.read_int(bytes, pos));\n pos += 2;\n }\n // decode sensor values\n for (j = 0; j < sensor.values.length; j++) {\n var value = sensor.values[j];\n if ('convert' in value) {\n result[value.name] = {displayName: value.displayName,\n value: value.convert.bind(this)(x)};\n if ('unit' in value)\n result[value.name]['unit'] = value.unit;\n }\n }\n }\n return result;\n }\n };\n\n const decoded = decentlab_decoder.decode(payload);\n const result = {};\n for (var k in decoded) {\n if (typeof decoded[k] === \"object\") {\n result[k] = decoded[k].value;\n } else if (typeof decoded[k] === \"number\") {\n result[k] = decoded[k];\n }\n }\n return result;\n}\n\nfunction extractNetworkTelemetry(payload) {\n return {\n lora_frame_counter: message.DevEUI_uplink.FCntUp,\n lora_frame_port: message.DevEUI_uplink.FPort,\n lora_frequency: message.DevEUI_uplink?.Frequency * 1000000,\n lora_spreading_factor: message.DevEUI_uplink.SpFact,\n lora_dev_eui: message.DevEUI_uplink.DevEUI,\n lora_rssi: message.DevEUI_uplink.LrrRSSI,\n lora_snr: message.DevEUI_uplink.LrrSNR,\n };\n}\n\n// Helper functions\n\nfunction decodeToJson(payload) {\n const s = String.fromCharCode.apply(String, payload);\n return JSON.parse(s);\n}\n\nfunction parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n }\n // If we cannot parse timestamp - we will use the current timestamp\n if (timestamp == -1) {\n timestamp = Date.now();\n }\n\n return timestamp;\n}\n\nfunction base64ToBytes(base64String) {\n if (typeof base64String === 'string') {\n return Uint8Array.from(atob(base64String), c => c.charCodeAt(0));\n }\n return [];\n}\n\nfunction keepTelemetry(telemetry) {\n const unwanted = ['protocol_version', 'device_id', 'lora_dev_eui'];\n return Object.fromEntries(\n Object.entries(telemetry).filter(\n (x) => !unwanted.includes(x[0])\n )\n );\n}" + "decoder": "// WARNING: This script is designed to work with a remote JS executor. Compatibility with a local JS evaluator is not guaranteed and may require rewriting.\n\nconst message = decodeToJson(payload);\nconst values = Object.assign(\n decodeDevicePayload(message.DevEUI_uplink.payload_hex),\n extractNetworkTelemetry(message)\n);\n\nreturn {\n deviceName: String(values.device_id),\n deviceType: 'DL-SHT35-001',\n attributes: {\n lora_dev_eui: values.lora_dev_eui,\n protocol_version: values.protocol_version,\n },\n telemetry: [{\n ts: parseDateToTimestamp(message.DevEUI_uplink.Time),\n values: keepTelemetry(values)\n }]\n};\n\nfunction decodeDevicePayload(payload) {\n /* https://www.decentlab.com/products/air-temperature-and-humidity-sensor-with-radiation-shield-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 2,\n values: [{name: 'air_temperature',\n displayName: 'Air temperature',\n convert: function (x) { return 175 * x[0] / 65535 - 45; },\n unit: '°C'},\n {name: 'air_humidity',\n displayName: 'Air humidity',\n convert: function (x) { return 100 * x[1] / 65535; },\n unit: '%'}]},\n {length: 1,\n values: [{name: 'battery_voltage',\n displayName: 'Battery voltage',\n convert: function (x) { return x[0] / 1000; },\n unit: 'V'}]}\n ],\n read_int: function (bytes, pos) {\n return (bytes[pos] << 8) + bytes[pos + 1];\n },\n decode: function (msg) {\n var bytes = msg;\n var i, j;\n if (typeof msg === 'string') {\n bytes = [];\n for (i = 0; i < msg.length; i += 2) {\n bytes.push(parseInt(msg.substring(i, i + 2), 16));\n }\n }\n var version = bytes[0];\n if (version != this.PROTOCOL_VERSION) {\n return {error: \"protocol version \" + version + \" doesn't match v2\"};\n }\n var deviceId = this.read_int(bytes, 1);\n var flags = this.read_int(bytes, 3);\n var result = {'protocol_version': version, 'device_id': deviceId};\n // decode payload\n var pos = 5;\n for (i = 0; i < this.SENSORS.length; i++, flags >>= 1) {\n if ((flags & 1) !== 1)\n continue;\n var sensor = this.SENSORS[i];\n var x = [];\n // convert data to 16-bit integer array\n for (j = 0; j < sensor.length; j++) {\n x.push(this.read_int(bytes, pos));\n pos += 2;\n }\n // decode sensor values\n for (j = 0; j < sensor.values.length; j++) {\n var value = sensor.values[j];\n if ('convert' in value) {\n result[value.name] = {displayName: value.displayName,\n value: value.convert.bind(this)(x)};\n if ('unit' in value)\n result[value.name]['unit'] = value.unit;\n }\n }\n }\n return result;\n }\n };\n\n const decoded = decentlab_decoder.decode(payload);\n const result = {};\n for (var k in decoded) {\n if (typeof decoded[k] === \"object\") {\n result[k] = decoded[k].value;\n } else if (typeof decoded[k] === \"number\") {\n result[k] = decoded[k];\n }\n }\n return result;\n}\n\nfunction extractNetworkTelemetry(payload) {\n return {\n lora_frame_counter: message.DevEUI_uplink.FCntUp,\n lora_frame_port: message.DevEUI_uplink.FPort,\n lora_frequency: message.DevEUI_uplink?.Frequency * 1000000,\n lora_spreading_factor: message.DevEUI_uplink.SpFact,\n lora_dev_eui: message.DevEUI_uplink.DevEUI,\n lora_rssi: message.DevEUI_uplink.LrrRSSI,\n lora_snr: message.DevEUI_uplink.LrrSNR,\n };\n}\n\n// Helper functions\n\nfunction decodeToJson(payload) {\n const s = String.fromCharCode.apply(String, payload);\n return JSON.parse(s);\n}\n\nfunction parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n }\n // If we cannot parse timestamp - we will use the current timestamp\n if (timestamp == -1) {\n timestamp = Date.now();\n }\n\n return timestamp;\n}\n\nfunction base64ToBytes(base64String) {\n if (typeof base64String === 'string') {\n return Uint8Array.from(atob(base64String), c => c.charCodeAt(0));\n }\n return [];\n}\n\nfunction keepTelemetry(telemetry) {\n const unwanted = ['protocol_version', 'device_id', 'lora_dev_eui'];\n return Object.fromEntries(\n Object.entries(telemetry).filter(\n (x) => !unwanted.includes(x[0])\n )\n );\n}" } } \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-SHT35-001/ThingParkEnterprise/uplink/result.json b/VENDORS/Decentlab/DL-SHT35-001/ThingParkEnterprise/uplink/result.json index 196a5cdf..3e9504d7 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/ThingParkEnterprise/uplink/result.json +++ b/VENDORS/Decentlab/DL-SHT35-001/ThingParkEnterprise/uplink/result.json @@ -1,6 +1,6 @@ { "deviceName": "782", - "deviceType": "DL-SHT35", + "deviceType": "DL-SHT35-001", "attributes": { "lora_dev_eui": "70B3D57BA000156B", "protocol_version": 2 diff --git a/VENDORS/Decentlab/DL-SHT35-001/ThingParkEnterprise/uplink/result_01.json b/VENDORS/Decentlab/DL-SHT35-001/ThingParkEnterprise/uplink/result_01.json index e83504f1..ea5af45d 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/ThingParkEnterprise/uplink/result_01.json +++ b/VENDORS/Decentlab/DL-SHT35-001/ThingParkEnterprise/uplink/result_01.json @@ -1,6 +1,6 @@ { "deviceName": "782", - "deviceType": "DL-SHT35", + "deviceType": "DL-SHT35-001", "attributes": { "lora_dev_eui": "70B3D57BA000156B", "protocol_version": 2 diff --git a/VENDORS/Decentlab/DL-SHT35-001/ThingsStackCommunity/uplink/converter.json b/VENDORS/Decentlab/DL-SHT35-001/ThingsStackCommunity/uplink/converter.json index 5a1a4894..8871ef4b 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/ThingsStackCommunity/uplink/converter.json +++ b/VENDORS/Decentlab/DL-SHT35-001/ThingsStackCommunity/uplink/converter.json @@ -14,6 +14,6 @@ "lora_dev_eui", "lora_snr" ], - "decoder": "// WARNING: This script is designed to work with a remote JS executor. Compatibility with a local JS evaluator is not guaranteed and may require rewriting.\n\nconst message = decodeToJson(payload);\nconst values = Object.assign(\n decodeDevicePayload(base64ToBytes(message.uplink_message.frm_payload)),\n extractNetworkTelemetry(message)\n);\n\nreturn {\n deviceName: String(values.device_id),\n deviceType: 'DL-SHT35',\n attributes: {\n lora_dev_eui: values.lora_dev_eui,\n protocol_version: values.protocol_version,\n },\n telemetry: [{\n ts: parseDateToTimestamp(message.received_at),\n values: keepTelemetry(values)\n }]\n};\n\nfunction decodeDevicePayload(payload) {\n /* https://www.decentlab.com/products/air-temperature-and-humidity-sensor-with-radiation-shield-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 2,\n values: [{name: 'air_temperature',\n displayName: 'Air temperature',\n convert: function (x) { return 175 * x[0] / 65535 - 45; },\n unit: '°C'},\n {name: 'air_humidity',\n displayName: 'Air humidity',\n convert: function (x) { return 100 * x[1] / 65535; },\n unit: '%'}]},\n {length: 1,\n values: [{name: 'battery_voltage',\n displayName: 'Battery voltage',\n convert: function (x) { return x[0] / 1000; },\n unit: 'V'}]}\n ],\n read_int: function (bytes, pos) {\n return (bytes[pos] << 8) + bytes[pos + 1];\n },\n decode: function (msg) {\n var bytes = msg;\n var i, j;\n if (typeof msg === 'string') {\n bytes = [];\n for (i = 0; i < msg.length; i += 2) {\n bytes.push(parseInt(msg.substring(i, i + 2), 16));\n }\n }\n var version = bytes[0];\n if (version != this.PROTOCOL_VERSION) {\n return {error: \"protocol version \" + version + \" doesn't match v2\"};\n }\n var deviceId = this.read_int(bytes, 1);\n var flags = this.read_int(bytes, 3);\n var result = {'protocol_version': version, 'device_id': deviceId};\n // decode payload\n var pos = 5;\n for (i = 0; i < this.SENSORS.length; i++, flags >>= 1) {\n if ((flags & 1) !== 1)\n continue;\n var sensor = this.SENSORS[i];\n var x = [];\n // convert data to 16-bit integer array\n for (j = 0; j < sensor.length; j++) {\n x.push(this.read_int(bytes, pos));\n pos += 2;\n }\n // decode sensor values\n for (j = 0; j < sensor.values.length; j++) {\n var value = sensor.values[j];\n if ('convert' in value) {\n result[value.name] = {displayName: value.displayName,\n value: value.convert.bind(this)(x)};\n if ('unit' in value)\n result[value.name]['unit'] = value.unit;\n }\n }\n }\n return result;\n }\n };\n\n const decoded = decentlab_decoder.decode(payload);\n const result = {};\n for (var k in decoded) {\n if (typeof decoded[k] === \"object\") {\n result[k] = decoded[k].value;\n } else if (typeof decoded[k] === \"number\") {\n result[k] = decoded[k];\n }\n }\n return result;\n}\n\nfunction extractNetworkTelemetry(payload) {\n const result = {\n lora_frame_counter: message.uplink_message.f_cnt,\n lora_frame_port: message.uplink_message.f_port,\n lora_frequency: parseInt(message.uplink_message.settings.frequency),\n lora_dev_eui: message.end_device_ids.dev_eui,\n lora_spreading_factor:\n message.uplink_message.settings.data_rate.lora.spreading_factor,\n };\n const gwi = message.uplink_message.rx_metadata\n .map((x) => ({ lora_rssi: x.rssi, lora_snr: x.snr }))\n .reduce((a, x) => (x.lora_rssi > a.lora_rssi ? x : a), {\n lora_rssi: -Number.MAX_VALUE,\n });\n return Object.assign(result, gwi);\n}\n\n// Helper functions\n\nfunction decodeToJson(payload) {\n const s = String.fromCharCode.apply(String, payload);\n return JSON.parse(s);\n}\n\nfunction parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n }\n // If we cannot parse timestamp - we will use the current timestamp\n if (timestamp == -1) {\n timestamp = Date.now();\n }\n\n return timestamp;\n}\n\nfunction base64ToBytes(base64String) {\n if (typeof base64String === 'string') {\n return Uint8Array.from(atob(base64String), c => c.charCodeAt(0));\n }\n return [];\n}\n\nfunction keepTelemetry(telemetry) {\n const unwanted = ['protocol_version', 'device_id', 'lora_dev_eui'];\n return Object.fromEntries(\n Object.entries(telemetry).filter(\n (x) => !unwanted.includes(x[0])\n )\n );\n}" + "decoder": "// WARNING: This script is designed to work with a remote JS executor. Compatibility with a local JS evaluator is not guaranteed and may require rewriting.\n\nconst message = decodeToJson(payload);\nconst values = Object.assign(\n decodeDevicePayload(base64ToBytes(message.uplink_message.frm_payload)),\n extractNetworkTelemetry(message)\n);\n\nreturn {\n deviceName: String(values.device_id),\n deviceType: 'DL-SHT35-001',\n attributes: {\n lora_dev_eui: values.lora_dev_eui,\n protocol_version: values.protocol_version,\n },\n telemetry: [{\n ts: parseDateToTimestamp(message.received_at),\n values: keepTelemetry(values)\n }]\n};\n\nfunction decodeDevicePayload(payload) {\n /* https://www.decentlab.com/products/air-temperature-and-humidity-sensor-with-radiation-shield-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 2,\n values: [{name: 'air_temperature',\n displayName: 'Air temperature',\n convert: function (x) { return 175 * x[0] / 65535 - 45; },\n unit: '°C'},\n {name: 'air_humidity',\n displayName: 'Air humidity',\n convert: function (x) { return 100 * x[1] / 65535; },\n unit: '%'}]},\n {length: 1,\n values: [{name: 'battery_voltage',\n displayName: 'Battery voltage',\n convert: function (x) { return x[0] / 1000; },\n unit: 'V'}]}\n ],\n read_int: function (bytes, pos) {\n return (bytes[pos] << 8) + bytes[pos + 1];\n },\n decode: function (msg) {\n var bytes = msg;\n var i, j;\n if (typeof msg === 'string') {\n bytes = [];\n for (i = 0; i < msg.length; i += 2) {\n bytes.push(parseInt(msg.substring(i, i + 2), 16));\n }\n }\n var version = bytes[0];\n if (version != this.PROTOCOL_VERSION) {\n return {error: \"protocol version \" + version + \" doesn't match v2\"};\n }\n var deviceId = this.read_int(bytes, 1);\n var flags = this.read_int(bytes, 3);\n var result = {'protocol_version': version, 'device_id': deviceId};\n // decode payload\n var pos = 5;\n for (i = 0; i < this.SENSORS.length; i++, flags >>= 1) {\n if ((flags & 1) !== 1)\n continue;\n var sensor = this.SENSORS[i];\n var x = [];\n // convert data to 16-bit integer array\n for (j = 0; j < sensor.length; j++) {\n x.push(this.read_int(bytes, pos));\n pos += 2;\n }\n // decode sensor values\n for (j = 0; j < sensor.values.length; j++) {\n var value = sensor.values[j];\n if ('convert' in value) {\n result[value.name] = {displayName: value.displayName,\n value: value.convert.bind(this)(x)};\n if ('unit' in value)\n result[value.name]['unit'] = value.unit;\n }\n }\n }\n return result;\n }\n };\n\n const decoded = decentlab_decoder.decode(payload);\n const result = {};\n for (var k in decoded) {\n if (typeof decoded[k] === \"object\") {\n result[k] = decoded[k].value;\n } else if (typeof decoded[k] === \"number\") {\n result[k] = decoded[k];\n }\n }\n return result;\n}\n\nfunction extractNetworkTelemetry(payload) {\n const result = {\n lora_frame_counter: message.uplink_message.f_cnt,\n lora_frame_port: message.uplink_message.f_port,\n lora_frequency: parseInt(message.uplink_message.settings.frequency),\n lora_dev_eui: message.end_device_ids.dev_eui,\n lora_spreading_factor:\n message.uplink_message.settings.data_rate.lora.spreading_factor,\n };\n const gwi = message.uplink_message.rx_metadata\n .map((x) => ({ lora_rssi: x.rssi, lora_snr: x.snr }))\n .reduce((a, x) => (x.lora_rssi > a.lora_rssi ? x : a), {\n lora_rssi: -Number.MAX_VALUE,\n });\n return Object.assign(result, gwi);\n}\n\n// Helper functions\n\nfunction decodeToJson(payload) {\n const s = String.fromCharCode.apply(String, payload);\n return JSON.parse(s);\n}\n\nfunction parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n }\n // If we cannot parse timestamp - we will use the current timestamp\n if (timestamp == -1) {\n timestamp = Date.now();\n }\n\n return timestamp;\n}\n\nfunction base64ToBytes(base64String) {\n if (typeof base64String === 'string') {\n return Uint8Array.from(atob(base64String), c => c.charCodeAt(0));\n }\n return [];\n}\n\nfunction keepTelemetry(telemetry) {\n const unwanted = ['protocol_version', 'device_id', 'lora_dev_eui'];\n return Object.fromEntries(\n Object.entries(telemetry).filter(\n (x) => !unwanted.includes(x[0])\n )\n );\n}" } } \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-SHT35-001/ThingsStackCommunity/uplink/result.json b/VENDORS/Decentlab/DL-SHT35-001/ThingsStackCommunity/uplink/result.json index 2c54fb05..556d71b8 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/ThingsStackCommunity/uplink/result.json +++ b/VENDORS/Decentlab/DL-SHT35-001/ThingsStackCommunity/uplink/result.json @@ -1,6 +1,6 @@ { "deviceName": "782", - "deviceType": "DL-SHT35", + "deviceType": "DL-SHT35-001", "attributes": { "lora_dev_eui": "70B3D57BA0000BCE", "protocol_version": 2 diff --git a/VENDORS/Decentlab/DL-SHT35-001/ThingsStackCommunity/uplink/result_01.json b/VENDORS/Decentlab/DL-SHT35-001/ThingsStackCommunity/uplink/result_01.json index 17298040..c7da9207 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/ThingsStackCommunity/uplink/result_01.json +++ b/VENDORS/Decentlab/DL-SHT35-001/ThingsStackCommunity/uplink/result_01.json @@ -1,6 +1,6 @@ { "deviceName": "782", - "deviceType": "DL-SHT35", + "deviceType": "DL-SHT35-001", "attributes": { "lora_dev_eui": "70B3D57BA0000BCE", "protocol_version": 2 diff --git a/VENDORS/Decentlab/DL-SHT35-001/ThingsStackIndustries/uplink/converter.json b/VENDORS/Decentlab/DL-SHT35-001/ThingsStackIndustries/uplink/converter.json index 0f49c3d4..02223686 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/ThingsStackIndustries/uplink/converter.json +++ b/VENDORS/Decentlab/DL-SHT35-001/ThingsStackIndustries/uplink/converter.json @@ -14,6 +14,6 @@ "lora_dev_eui", "lora_snr" ], - "decoder": "// WARNING: This script is designed to work with a remote JS executor. Compatibility with a local JS evaluator is not guaranteed and may require rewriting.\n\nconst message = decodeToJson(payload);\nconst values = Object.assign(\n decodeDevicePayload(base64ToBytes(message.uplink_message.frm_payload)),\n extractNetworkTelemetry(message)\n);\n\nreturn {\n deviceName: String(values.device_id),\n deviceType: 'DL-SHT35',\n attributes: {\n lora_dev_eui: values.lora_dev_eui,\n protocol_version: values.protocol_version,\n },\n telemetry: [{\n ts: parseDateToTimestamp(message.received_at),\n values: keepTelemetry(values)\n }]\n};\n\nfunction decodeDevicePayload(payload) {\n /* https://www.decentlab.com/products/air-temperature-and-humidity-sensor-with-radiation-shield-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 2,\n values: [{name: 'air_temperature',\n displayName: 'Air temperature',\n convert: function (x) { return 175 * x[0] / 65535 - 45; },\n unit: '°C'},\n {name: 'air_humidity',\n displayName: 'Air humidity',\n convert: function (x) { return 100 * x[1] / 65535; },\n unit: '%'}]},\n {length: 1,\n values: [{name: 'battery_voltage',\n displayName: 'Battery voltage',\n convert: function (x) { return x[0] / 1000; },\n unit: 'V'}]}\n ],\n read_int: function (bytes, pos) {\n return (bytes[pos] << 8) + bytes[pos + 1];\n },\n decode: function (msg) {\n var bytes = msg;\n var i, j;\n if (typeof msg === 'string') {\n bytes = [];\n for (i = 0; i < msg.length; i += 2) {\n bytes.push(parseInt(msg.substring(i, i + 2), 16));\n }\n }\n var version = bytes[0];\n if (version != this.PROTOCOL_VERSION) {\n return {error: \"protocol version \" + version + \" doesn't match v2\"};\n }\n var deviceId = this.read_int(bytes, 1);\n var flags = this.read_int(bytes, 3);\n var result = {'protocol_version': version, 'device_id': deviceId};\n // decode payload\n var pos = 5;\n for (i = 0; i < this.SENSORS.length; i++, flags >>= 1) {\n if ((flags & 1) !== 1)\n continue;\n var sensor = this.SENSORS[i];\n var x = [];\n // convert data to 16-bit integer array\n for (j = 0; j < sensor.length; j++) {\n x.push(this.read_int(bytes, pos));\n pos += 2;\n }\n // decode sensor values\n for (j = 0; j < sensor.values.length; j++) {\n var value = sensor.values[j];\n if ('convert' in value) {\n result[value.name] = {displayName: value.displayName,\n value: value.convert.bind(this)(x)};\n if ('unit' in value)\n result[value.name]['unit'] = value.unit;\n }\n }\n }\n return result;\n }\n };\n\n const decoded = decentlab_decoder.decode(payload);\n const result = {};\n for (var k in decoded) {\n if (typeof decoded[k] === \"object\") {\n result[k] = decoded[k].value;\n } else if (typeof decoded[k] === \"number\") {\n result[k] = decoded[k];\n }\n }\n return result;\n}\n\nfunction extractNetworkTelemetry(payload) {\n const result = {\n lora_frame_counter: message.uplink_message.f_cnt,\n lora_frame_port: message.uplink_message.f_port,\n lora_frequency: parseInt(message.uplink_message.settings.frequency),\n lora_dev_eui: message.end_device_ids.dev_eui,\n lora_spreading_factor:\n message.uplink_message.settings.data_rate.lora.spreading_factor,\n };\n const gwi = message.uplink_message.rx_metadata\n .map((x) => ({ lora_rssi: x.rssi, lora_snr: x.snr }))\n .reduce((a, x) => (x.lora_rssi > a.lora_rssi ? x : a), {\n lora_rssi: -Number.MAX_VALUE,\n });\n return Object.assign(result, gwi);\n}\n\n// Helper functions\n\nfunction decodeToJson(payload) {\n const s = String.fromCharCode.apply(String, payload);\n return JSON.parse(s);\n}\n\nfunction parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n }\n // If we cannot parse timestamp - we will use the current timestamp\n if (timestamp == -1) {\n timestamp = Date.now();\n }\n\n return timestamp;\n}\n\nfunction base64ToBytes(base64String) {\n if (typeof base64String === 'string') {\n return Uint8Array.from(atob(base64String), c => c.charCodeAt(0));\n }\n return [];\n}\n\nfunction keepTelemetry(telemetry) {\n const unwanted = ['protocol_version', 'device_id', 'lora_dev_eui'];\n return Object.fromEntries(\n Object.entries(telemetry).filter(\n (x) => !unwanted.includes(x[0])\n )\n );\n}" + "decoder": "// WARNING: This script is designed to work with a remote JS executor. Compatibility with a local JS evaluator is not guaranteed and may require rewriting.\n\nconst message = decodeToJson(payload);\nconst values = Object.assign(\n decodeDevicePayload(base64ToBytes(message.uplink_message.frm_payload)),\n extractNetworkTelemetry(message)\n);\n\nreturn {\n deviceName: String(values.device_id),\n deviceType: 'DL-SHT35-001',\n attributes: {\n lora_dev_eui: values.lora_dev_eui,\n protocol_version: values.protocol_version,\n },\n telemetry: [{\n ts: parseDateToTimestamp(message.received_at),\n values: keepTelemetry(values)\n }]\n};\n\nfunction decodeDevicePayload(payload) {\n /* https://www.decentlab.com/products/air-temperature-and-humidity-sensor-with-radiation-shield-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 2,\n values: [{name: 'air_temperature',\n displayName: 'Air temperature',\n convert: function (x) { return 175 * x[0] / 65535 - 45; },\n unit: '°C'},\n {name: 'air_humidity',\n displayName: 'Air humidity',\n convert: function (x) { return 100 * x[1] / 65535; },\n unit: '%'}]},\n {length: 1,\n values: [{name: 'battery_voltage',\n displayName: 'Battery voltage',\n convert: function (x) { return x[0] / 1000; },\n unit: 'V'}]}\n ],\n read_int: function (bytes, pos) {\n return (bytes[pos] << 8) + bytes[pos + 1];\n },\n decode: function (msg) {\n var bytes = msg;\n var i, j;\n if (typeof msg === 'string') {\n bytes = [];\n for (i = 0; i < msg.length; i += 2) {\n bytes.push(parseInt(msg.substring(i, i + 2), 16));\n }\n }\n var version = bytes[0];\n if (version != this.PROTOCOL_VERSION) {\n return {error: \"protocol version \" + version + \" doesn't match v2\"};\n }\n var deviceId = this.read_int(bytes, 1);\n var flags = this.read_int(bytes, 3);\n var result = {'protocol_version': version, 'device_id': deviceId};\n // decode payload\n var pos = 5;\n for (i = 0; i < this.SENSORS.length; i++, flags >>= 1) {\n if ((flags & 1) !== 1)\n continue;\n var sensor = this.SENSORS[i];\n var x = [];\n // convert data to 16-bit integer array\n for (j = 0; j < sensor.length; j++) {\n x.push(this.read_int(bytes, pos));\n pos += 2;\n }\n // decode sensor values\n for (j = 0; j < sensor.values.length; j++) {\n var value = sensor.values[j];\n if ('convert' in value) {\n result[value.name] = {displayName: value.displayName,\n value: value.convert.bind(this)(x)};\n if ('unit' in value)\n result[value.name]['unit'] = value.unit;\n }\n }\n }\n return result;\n }\n };\n\n const decoded = decentlab_decoder.decode(payload);\n const result = {};\n for (var k in decoded) {\n if (typeof decoded[k] === \"object\") {\n result[k] = decoded[k].value;\n } else if (typeof decoded[k] === \"number\") {\n result[k] = decoded[k];\n }\n }\n return result;\n}\n\nfunction extractNetworkTelemetry(payload) {\n const result = {\n lora_frame_counter: message.uplink_message.f_cnt,\n lora_frame_port: message.uplink_message.f_port,\n lora_frequency: parseInt(message.uplink_message.settings.frequency),\n lora_dev_eui: message.end_device_ids.dev_eui,\n lora_spreading_factor:\n message.uplink_message.settings.data_rate.lora.spreading_factor,\n };\n const gwi = message.uplink_message.rx_metadata\n .map((x) => ({ lora_rssi: x.rssi, lora_snr: x.snr }))\n .reduce((a, x) => (x.lora_rssi > a.lora_rssi ? x : a), {\n lora_rssi: -Number.MAX_VALUE,\n });\n return Object.assign(result, gwi);\n}\n\n// Helper functions\n\nfunction decodeToJson(payload) {\n const s = String.fromCharCode.apply(String, payload);\n return JSON.parse(s);\n}\n\nfunction parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n }\n // If we cannot parse timestamp - we will use the current timestamp\n if (timestamp == -1) {\n timestamp = Date.now();\n }\n\n return timestamp;\n}\n\nfunction base64ToBytes(base64String) {\n if (typeof base64String === 'string') {\n return Uint8Array.from(atob(base64String), c => c.charCodeAt(0));\n }\n return [];\n}\n\nfunction keepTelemetry(telemetry) {\n const unwanted = ['protocol_version', 'device_id', 'lora_dev_eui'];\n return Object.fromEntries(\n Object.entries(telemetry).filter(\n (x) => !unwanted.includes(x[0])\n )\n );\n}" } } \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-SHT35-001/ThingsStackIndustries/uplink/result.json b/VENDORS/Decentlab/DL-SHT35-001/ThingsStackIndustries/uplink/result.json index 2c54fb05..556d71b8 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/ThingsStackIndustries/uplink/result.json +++ b/VENDORS/Decentlab/DL-SHT35-001/ThingsStackIndustries/uplink/result.json @@ -1,6 +1,6 @@ { "deviceName": "782", - "deviceType": "DL-SHT35", + "deviceType": "DL-SHT35-001", "attributes": { "lora_dev_eui": "70B3D57BA0000BCE", "protocol_version": 2 diff --git a/VENDORS/Decentlab/DL-SHT35-001/ThingsStackIndustries/uplink/result_01.json b/VENDORS/Decentlab/DL-SHT35-001/ThingsStackIndustries/uplink/result_01.json index 17298040..c7da9207 100644 --- a/VENDORS/Decentlab/DL-SHT35-001/ThingsStackIndustries/uplink/result_01.json +++ b/VENDORS/Decentlab/DL-SHT35-001/ThingsStackIndustries/uplink/result_01.json @@ -1,6 +1,6 @@ { "deviceName": "782", - "deviceType": "DL-SHT35", + "deviceType": "DL-SHT35-001", "attributes": { "lora_dev_eui": "70B3D57BA0000BCE", "protocol_version": 2 diff --git a/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/converter.json b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/converter.json new file mode 100644 index 00000000..29d5f734 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/converter.json @@ -0,0 +1,19 @@ +{ + "name": "ChirpStack Uplink Decoder for Decentlab DL-SHT35-002", + "type": "UPLINK", + "debugMode": true, + "edgeTemplate": false, + "configuration": { + "scriptLang": "JS", + "updateOnlyKeys": [ + "lora_frame_counter", + "lora_frame_port", + "lora_frequency", + "lora_spreading_factor", + "lora_rssi", + "lora_dev_eui", + "lora_snr" + ], + "decoder": "// WARNING: This script is designed to work with a remote JS executor. Compatibility with a local JS evaluator is not guaranteed and may require rewriting.\n\nconst message = decodeToJson(payload);\nconst values = Object.assign(\n decodeDevicePayload(base64ToBytes(message.data)),\n extractNetworkTelemetry(message)\n);\n\nreturn {\n deviceName: String(values.device_id),\n deviceType: 'DL-SHT35-002',\n attributes: {\n lora_dev_eui: values.lora_dev_eui,\n protocol_version: values.protocol_version,\n },\n telemetry: [{\n ts: message.time,\n values: keepTelemetry(values)\n }]\n};\n\nfunction decodeDevicePayload(payload) {\n /* https://www.decentlab.com/products/air-temperature-and-humidity-sensor-with-radiation-shield-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 2,\n values: [{name: 'air_temperature',\n displayName: 'Air temperature',\n convert: function (x) { return 175 * x[0] / 65535 - 45; },\n unit: '°C'},\n {name: 'air_humidity',\n displayName: 'Air humidity',\n convert: function (x) { return 100 * x[1] / 65535; },\n unit: '%'}]},\n {length: 1,\n values: [{name: 'battery_voltage',\n displayName: 'Battery voltage',\n convert: function (x) { return x[0] / 1000; },\n unit: 'V'}]}\n ],\n read_int: function (bytes, pos) {\n return (bytes[pos] << 8) + bytes[pos + 1];\n },\n decode: function (msg) {\n var bytes = msg;\n var i, j;\n if (typeof msg === 'string') {\n bytes = [];\n for (i = 0; i < msg.length; i += 2) {\n bytes.push(parseInt(msg.substring(i, i + 2), 16));\n }\n }\n var version = bytes[0];\n if (version != this.PROTOCOL_VERSION) {\n return {error: \"protocol version \" + version + \" doesn't match v2\"};\n }\n var deviceId = this.read_int(bytes, 1);\n var flags = this.read_int(bytes, 3);\n var result = {'protocol_version': version, 'device_id': deviceId};\n // decode payload\n var pos = 5;\n for (i = 0; i < this.SENSORS.length; i++, flags >>= 1) {\n if ((flags & 1) !== 1)\n continue;\n var sensor = this.SENSORS[i];\n var x = [];\n // convert data to 16-bit integer array\n for (j = 0; j < sensor.length; j++) {\n x.push(this.read_int(bytes, pos));\n pos += 2;\n }\n // decode sensor values\n for (j = 0; j < sensor.values.length; j++) {\n var value = sensor.values[j];\n if ('convert' in value) {\n result[value.name] = {displayName: value.displayName,\n value: value.convert.bind(this)(x)};\n if ('unit' in value)\n result[value.name]['unit'] = value.unit;\n }\n }\n }\n return result;\n }\n };\n\n const decoded = decentlab_decoder.decode(payload);\n const result = {};\n for (var k in decoded) {\n if (typeof decoded[k] === \"object\") {\n result[k] = decoded[k].value;\n } else if (typeof decoded[k] === \"number\") {\n result[k] = decoded[k];\n }\n }\n return result;\n}\n\nfunction extractNetworkTelemetry(payload) {\n const result = {\n lora_frame_counter: message.fCnt,\n lora_frame_port: message.fPort,\n lora_frequency: message.txInfo.frequency,\n lora_spreading_factor:\n message?.txInfo?.modulation?.lora?.spreadingFactor ||\n message.txInfo.loRaModulationInfo.spreadingFactor,\n lora_dev_eui: (\n message?.deviceInfo?.devEui ||\n base64ToBytes(message.devEUI).reduce(\n (a, x) => a + (\"0\" + x.toString(16)).slice(-2),\n \"\",\n )\n ).toUpperCase(),\n };\n const gwi = message.rxInfo\n .map((x) => ({ lora_rssi: x.rssi, lora_snr: x.snr || x.loRaSNR }))\n .reduce((a, x) => (x.lora_rssi > a.lora_rssi ? x : a), {\n lora_rssi: -Number.MAX_VALUE,\n });\n return Object.assign(result, gwi);\n}\n\n// Helper functions\n\nfunction decodeToJson(payload) {\n const s = String.fromCharCode.apply(String, payload);\n return JSON.parse(s);\n}\n\nfunction parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n }\n // If we cannot parse timestamp - we will use the current timestamp\n if (timestamp == -1) {\n timestamp = Date.now();\n }\n\n return timestamp;\n}\n\nfunction base64ToBytes(base64String) {\n if (typeof base64String === 'string') {\n return Uint8Array.from(atob(base64String), c => c.charCodeAt(0));\n }\n return [];\n}\n\nfunction keepTelemetry(telemetry) {\n const unwanted = ['protocol_version', 'device_id', 'lora_dev_eui'];\n return Object.fromEntries(\n Object.entries(telemetry).filter(\n (x) => !unwanted.includes(x[0])\n )\n );\n}" + } +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/metadata.json b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/metadata.json new file mode 100644 index 00000000..1f9359b9 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/metadata.json @@ -0,0 +1,4 @@ +{ + "integrationName": "ChirpStack integration", + "includeGatewayInfo": false +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/payload.json b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/payload.json new file mode 100644 index 00000000..e524f89c --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/payload.json @@ -0,0 +1,55 @@ +{ + "adr": true, + "confirmed": false, + "data": "AgMOAANkoHmxDGA=", + "deduplicationId": "ede7dfc1-a1f5-418c-b009-dc57ebcf0895", + "devAddr": "017d1f70", + "deviceInfo": { + "applicationId": "32dafeb5-0865-4edd-bf1c-3f8d9b07af52", + "applicationName": "MoDus-Sain", + "devEui": "70b3d57ba0003bb8", + "deviceClassEnabled": "CLASS_A", + "deviceName": "PM-15288", + "deviceProfileId": "7ec3de3f-7b87-49ae-8f32-330249122a50", + "deviceProfileName": "dev-prof-Decentlab", + "tags": { + "lts": "true" + }, + "tenantId": "663cc43f-0bf2-48d4-afc6-ff27ba969f10", + "tenantName": "Friloranet" + }, + "dr": 5, + "fCnt": 20, + "fPort": 1, + "rxInfo": [ + { + "context": "AAAAAAAAAAAA/gADaoXb7A==", + "crcStatus": "CRC_OK", + "gatewayId": "fcc23dfffe2e72ed", + "gwTime": "2024-11-12T12:51:50+00:00", + "location": { + "latitude": 46.80179269386121, + "longitude": 7.1381521224975595 + }, + "metadata": { + "region_common_name": "EU868", + "region_config_id": "eu868" + }, + "nsTime": "2024-11-12T12:51:50.819853185+00:00", + "rssi": -106, + "snr": 3.0, + "uplinkId": 3257702911 + } + ], + "time": "2024-11-10T12:51:50+00:00", + "txInfo": { + "frequency": 868300000, + "modulation": { + "lora": { + "bandwidth": 125000, + "codeRate": "CR_4_5", + "spreadingFactor": 7 + } + } + } +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/payload_01.json b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/payload_01.json new file mode 100644 index 00000000..f4b6b8b6 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/payload_01.json @@ -0,0 +1,55 @@ +{ + "adr": true, + "confirmed": false, + "data": "AgMOAAIMYA==", + "deduplicationId": "ede7dfc1-a1f5-418c-b009-dc57ebcf0895", + "devAddr": "017d1f70", + "deviceInfo": { + "applicationId": "32dafeb5-0865-4edd-bf1c-3f8d9b07af52", + "applicationName": "MoDus-Sain", + "devEui": "70b3d57ba0003bb8", + "deviceClassEnabled": "CLASS_A", + "deviceName": "PM-15288", + "deviceProfileId": "7ec3de3f-7b87-49ae-8f32-330249122a50", + "deviceProfileName": "dev-prof-Decentlab", + "tags": { + "lts": "true" + }, + "tenantId": "663cc43f-0bf2-48d4-afc6-ff27ba969f10", + "tenantName": "Friloranet" + }, + "dr": 5, + "fCnt": 20, + "fPort": 1, + "rxInfo": [ + { + "context": "AAAAAAAAAAAA/gADaoXb7A==", + "crcStatus": "CRC_OK", + "gatewayId": "fcc23dfffe2e72ed", + "gwTime": "2024-11-12T12:51:50+00:00", + "location": { + "latitude": 46.80179269386121, + "longitude": 7.1381521224975595 + }, + "metadata": { + "region_common_name": "EU868", + "region_config_id": "eu868" + }, + "nsTime": "2024-11-12T12:51:50.819853185+00:00", + "rssi": -106, + "snr": 3.0, + "uplinkId": 3257702911 + } + ], + "time": "2024-11-10T12:51:50+00:00", + "txInfo": { + "frequency": 868300000, + "modulation": { + "lora": { + "bandwidth": 125000, + "codeRate": "CR_4_5", + "spreadingFactor": 7 + } + } + } +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/payload_02.json b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/payload_02.json new file mode 100644 index 00000000..b02a35ee --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/payload_02.json @@ -0,0 +1,46 @@ +{ + "applicationID": "123", + "applicationName": "temperature-sensor", + "deviceName": "garden-sensor", + "devEUI": "AgICAgICAgI=", + "rxInfo": [ + { + "gatewayID": "AwMDAwMDAwM=", + "time": "2019-11-08T13:59:25.048445Z", + "timeSinceGPSEpoch": null, + "rssi": -48, + "loRaSNR": 9, + "channel": 5, + "rfChain": 0, + "board": 0, + "antenna": 0, + "location": { + "latitude": 52.3740364, + "longitude": 4.9144401, + "altitude": 10.5 + }, + "fineTimestampType": "NONE", + "context": "9u/uvA==", + "uplinkID": "jhMh8Gq6RAOChSKbi83RHQ==" + } + ], + "txInfo": { + "frequency": 868100000, + "modulation": "LORA", + "loRaModulationInfo": { + "bandwidth": 125, + "spreadingFactor": 11, + "codeRate": "4/5", + "polarizationInversion": false + } + }, + "adr": true, + "dr": 1, + "fCnt": 10, + "fPort": 5, + "data": "AgMOAANkoHmxDGA=", + "objectJSON": "{\"temperatureSensor\":25,\"humiditySensor\":32}", + "tags": { + "key": "value" + } +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/payload_03.json b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/payload_03.json new file mode 100644 index 00000000..75524c99 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/payload_03.json @@ -0,0 +1,46 @@ +{ + "applicationID": "123", + "applicationName": "temperature-sensor", + "deviceName": "garden-sensor", + "devEUI": "AgICAgICAgI=", + "rxInfo": [ + { + "gatewayID": "AwMDAwMDAwM=", + "time": "2019-11-08T13:59:25.048445Z", + "timeSinceGPSEpoch": null, + "rssi": -48, + "loRaSNR": 9, + "channel": 5, + "rfChain": 0, + "board": 0, + "antenna": 0, + "location": { + "latitude": 52.3740364, + "longitude": 4.9144401, + "altitude": 10.5 + }, + "fineTimestampType": "NONE", + "context": "9u/uvA==", + "uplinkID": "jhMh8Gq6RAOChSKbi83RHQ==" + } + ], + "txInfo": { + "frequency": 868100000, + "modulation": "LORA", + "loRaModulationInfo": { + "bandwidth": 125, + "spreadingFactor": 11, + "codeRate": "4/5", + "polarizationInversion": false + } + }, + "adr": true, + "dr": 1, + "fCnt": 10, + "fPort": 5, + "data": "AgMOAAIMYA==", + "objectJSON": "{\"temperatureSensor\":25,\"humiditySensor\":32}", + "tags": { + "key": "value" + } +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/result.json b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/result.json new file mode 100644 index 00000000..becc32c5 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/result.json @@ -0,0 +1,24 @@ +{ + "deviceName": "782", + "deviceType": "DL-SHT35-002", + "attributes": { + "lora_dev_eui": "70B3D57BA0003BB8", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": "2024-11-10T12:51:50+00:00", + "values": { + "air_temperature": 23.787670710307466, + "air_humidity": 47.536430914778364, + "battery_voltage": 3.168, + "lora_frame_counter": 20, + "lora_frame_port": 1, + "lora_frequency": 868300000, + "lora_spreading_factor": 7, + "lora_rssi": -106, + "lora_snr": 3 + } + } + ] +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/result_01.json b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/result_01.json new file mode 100644 index 00000000..ef238e28 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/result_01.json @@ -0,0 +1,22 @@ +{ + "deviceName": "782", + "deviceType": "DL-SHT35-002", + "attributes": { + "lora_dev_eui": "70B3D57BA0003BB8", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": "2024-11-10T12:51:50+00:00", + "values": { + "battery_voltage": 3.168, + "lora_frame_counter": 20, + "lora_frame_port": 1, + "lora_frequency": 868300000, + "lora_spreading_factor": 7, + "lora_rssi": -106, + "lora_snr": 3 + } + } + ] +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/result_02.json b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/result_02.json new file mode 100644 index 00000000..5289a4a3 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/result_02.json @@ -0,0 +1,23 @@ +{ + "deviceName": "782", + "deviceType": "DL-SHT35-002", + "attributes": { + "lora_dev_eui": "0202020202020202", + "protocol_version": 2 + }, + "telemetry": [ + { + "values": { + "air_temperature": 23.787670710307466, + "air_humidity": 47.536430914778364, + "battery_voltage": 3.168, + "lora_frame_counter": 10, + "lora_frame_port": 5, + "lora_frequency": 868100000, + "lora_spreading_factor": 11, + "lora_rssi": -48, + "lora_snr": 9 + } + } + ] +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/result_03.json b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/result_03.json new file mode 100644 index 00000000..ada74806 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ChirpStack/uplink/result_03.json @@ -0,0 +1,21 @@ +{ + "deviceName": "782", + "deviceType": "DL-SHT35-002", + "attributes": { + "lora_dev_eui": "0202020202020202", + "protocol_version": 2 + }, + "telemetry": [ + { + "values": { + "battery_voltage": 3.168, + "lora_frame_counter": 10, + "lora_frame_port": 5, + "lora_frequency": 868100000, + "lora_spreading_factor": 11, + "lora_rssi": -48, + "lora_snr": 9 + } + } + ] +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/LORIOT/uplink/converter.json b/VENDORS/Decentlab/DL-SHT35-002/LORIOT/uplink/converter.json new file mode 100644 index 00000000..9207d027 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/LORIOT/uplink/converter.json @@ -0,0 +1,19 @@ +{ + "name": "LORIOT Uplink Decoder for Decentlab DL-SHT35-002", + "type": "UPLINK", + "debugMode": true, + "edgeTemplate": false, + "configuration": { + "scriptLang": "JS", + "updateOnlyKeys": [ + "lora_frame_counter", + "lora_frame_port", + "lora_frequency", + "lora_spreading_factor", + "lora_rssi", + "lora_dev_eui", + "lora_snr" + ], + "decoder": "// WARNING: This script is designed to work with a remote JS executor. Compatibility with a local JS evaluator is not guaranteed and may require rewriting.\n\nconst message = decodeToJson(payload);\nconst values = Object.assign(\n decodeDevicePayload(message.data),\n extractNetworkTelemetry(message)\n);\n\nreturn {\n deviceName: String(values.device_id),\n deviceType: 'DL-SHT35-002',\n attributes: {\n lora_dev_eui: values.lora_dev_eui,\n protocol_version: values.protocol_version,\n },\n telemetry: [{\n ts: message.ts,\n values: keepTelemetry(values)\n }]\n};\n\nfunction decodeDevicePayload(payload) {\n /* https://www.decentlab.com/products/air-temperature-and-humidity-sensor-with-radiation-shield-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 2,\n values: [{name: 'air_temperature',\n displayName: 'Air temperature',\n convert: function (x) { return 175 * x[0] / 65535 - 45; },\n unit: '°C'},\n {name: 'air_humidity',\n displayName: 'Air humidity',\n convert: function (x) { return 100 * x[1] / 65535; },\n unit: '%'}]},\n {length: 1,\n values: [{name: 'battery_voltage',\n displayName: 'Battery voltage',\n convert: function (x) { return x[0] / 1000; },\n unit: 'V'}]}\n ],\n read_int: function (bytes, pos) {\n return (bytes[pos] << 8) + bytes[pos + 1];\n },\n decode: function (msg) {\n var bytes = msg;\n var i, j;\n if (typeof msg === 'string') {\n bytes = [];\n for (i = 0; i < msg.length; i += 2) {\n bytes.push(parseInt(msg.substring(i, i + 2), 16));\n }\n }\n var version = bytes[0];\n if (version != this.PROTOCOL_VERSION) {\n return {error: \"protocol version \" + version + \" doesn't match v2\"};\n }\n var deviceId = this.read_int(bytes, 1);\n var flags = this.read_int(bytes, 3);\n var result = {'protocol_version': version, 'device_id': deviceId};\n // decode payload\n var pos = 5;\n for (i = 0; i < this.SENSORS.length; i++, flags >>= 1) {\n if ((flags & 1) !== 1)\n continue;\n var sensor = this.SENSORS[i];\n var x = [];\n // convert data to 16-bit integer array\n for (j = 0; j < sensor.length; j++) {\n x.push(this.read_int(bytes, pos));\n pos += 2;\n }\n // decode sensor values\n for (j = 0; j < sensor.values.length; j++) {\n var value = sensor.values[j];\n if ('convert' in value) {\n result[value.name] = {displayName: value.displayName,\n value: value.convert.bind(this)(x)};\n if ('unit' in value)\n result[value.name]['unit'] = value.unit;\n }\n }\n }\n return result;\n }\n };\n\n const decoded = decentlab_decoder.decode(payload);\n const result = {};\n for (var k in decoded) {\n if (typeof decoded[k] === \"object\") {\n result[k] = decoded[k].value;\n } else if (typeof decoded[k] === \"number\") {\n result[k] = decoded[k];\n }\n }\n return result;\n}\n\nfunction extractNetworkTelemetry(payload) {\n const result = {\n lora_frame_counter: message.fcnt,\n lora_frame_port: message.port,\n lora_frequency: message.freq,\n lora_snr: message.snr,\n lora_rssi: message.rssi,\n lora_dev_eui: message.EUI,\n lora_spreading_factor: parseInt(message.dr.replace(/.*SF(\\d+).*/, '$1')),\n };\n if (message.cmd === 'gw') {\n const gwi = message.gws\n .map((x) => ({ lora_rssi: x.rssi, lora_snr: x.snr }))\n .reduce((a, x) => (x.lora_rssi > a.lora_rssi ? x : a), {\n lora_rssi: -Number.MAX_VALUE,\n });\n result.rssi = gwi.rssi;\n result.snr = gwi.snr;\n }\n return result;\n}\n\n// Helper functions\n\nfunction decodeToJson(payload) {\n const s = String.fromCharCode.apply(String, payload);\n return JSON.parse(s);\n}\n\nfunction parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n }\n // If we cannot parse timestamp - we will use the current timestamp\n if (timestamp == -1) {\n timestamp = Date.now();\n }\n\n return timestamp;\n}\n\nfunction base64ToBytes(base64String) {\n if (typeof base64String === 'string') {\n return Uint8Array.from(atob(base64String), c => c.charCodeAt(0));\n }\n return [];\n}\n\nfunction keepTelemetry(telemetry) {\n const unwanted = ['protocol_version', 'device_id', 'lora_dev_eui'];\n return Object.fromEntries(\n Object.entries(telemetry).filter(\n (x) => !unwanted.includes(x[0])\n )\n );\n}" + } +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/LORIOT/uplink/metadata.json b/VENDORS/Decentlab/DL-SHT35-002/LORIOT/uplink/metadata.json new file mode 100644 index 00000000..5c24f9c2 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/LORIOT/uplink/metadata.json @@ -0,0 +1,4 @@ +{ + "integrationName": "LORIOT integration", + "includeGatewayInfo": false +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-SHT35-002/LORIOT/uplink/payload.json b/VENDORS/Decentlab/DL-SHT35-002/LORIOT/uplink/payload.json new file mode 100644 index 00000000..db896a43 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/LORIOT/uplink/payload.json @@ -0,0 +1,13 @@ +{ + "cmd": "rx", + "EUI": "0004A30B001ADCB5", + "ts": 1501064048945, + "fcnt": 121, + "port": 1, + "freq": 868300000, + "rssi": -119, + "snr": -15.8, + "dr": "SF11 BW125 4/5", + "ack": false, + "data": "02030e000364a079b10c60" +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/LORIOT/uplink/payload_01.json b/VENDORS/Decentlab/DL-SHT35-002/LORIOT/uplink/payload_01.json new file mode 100644 index 00000000..5a049244 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/LORIOT/uplink/payload_01.json @@ -0,0 +1,13 @@ +{ + "cmd": "rx", + "EUI": "0004A30B001ADCB5", + "ts": 1501064048945, + "fcnt": 121, + "port": 1, + "freq": 868300000, + "rssi": -119, + "snr": -15.8, + "dr": "SF11 BW125 4/5", + "ack": false, + "data": "02030e00020c60" +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/LORIOT/uplink/result.json b/VENDORS/Decentlab/DL-SHT35-002/LORIOT/uplink/result.json new file mode 100644 index 00000000..fd184c33 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/LORIOT/uplink/result.json @@ -0,0 +1,24 @@ +{ + "deviceName": "782", + "deviceType": "DL-SHT35-002", + "attributes": { + "lora_dev_eui": "0004A30B001ADCB5", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1501064048945, + "values": { + "air_temperature": 23.787670710307466, + "air_humidity": 47.536430914778364, + "battery_voltage": 3.168, + "lora_frame_counter": 121, + "lora_frame_port": 1, + "lora_frequency": 868300000, + "lora_snr": -15.8, + "lora_rssi": -119, + "lora_spreading_factor": 11 + } + } + ] +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/LORIOT/uplink/result_01.json b/VENDORS/Decentlab/DL-SHT35-002/LORIOT/uplink/result_01.json new file mode 100644 index 00000000..7d9dabcc --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/LORIOT/uplink/result_01.json @@ -0,0 +1,22 @@ +{ + "deviceName": "782", + "deviceType": "DL-SHT35-002", + "attributes": { + "lora_dev_eui": "0004A30B001ADCB5", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1501064048945, + "values": { + "battery_voltage": 3.168, + "lora_frame_counter": 121, + "lora_frame_port": 1, + "lora_frequency": 868300000, + "lora_snr": -15.8, + "lora_rssi": -119, + "lora_spreading_factor": 11 + } + } + ] +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingPark/uplink/converter.json b/VENDORS/Decentlab/DL-SHT35-002/ThingPark/uplink/converter.json new file mode 100644 index 00000000..a2e25858 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingPark/uplink/converter.json @@ -0,0 +1,19 @@ +{ + "name": "ThingPark Uplink Decoder for Decentlab DL-SHT35-002", + "type": "UPLINK", + "debugMode": true, + "edgeTemplate": false, + "configuration": { + "scriptLang": "JS", + "updateOnlyKeys": [ + "lora_frame_counter", + "lora_frame_port", + "lora_frequency", + "lora_spreading_factor", + "lora_rssi", + "lora_dev_eui", + "lora_snr" + ], + "decoder": "// WARNING: This script is designed to work with a remote JS executor. Compatibility with a local JS evaluator is not guaranteed and may require rewriting.\n\nconst message = decodeToJson(payload);\nconst values = Object.assign(\n decodeDevicePayload(message.DevEUI_uplink.payload_hex),\n extractNetworkTelemetry(message)\n);\n\nreturn {\n deviceName: String(values.device_id),\n deviceType: 'DL-SHT35-002',\n attributes: {\n lora_dev_eui: values.lora_dev_eui,\n protocol_version: values.protocol_version,\n },\n telemetry: [{\n ts: parseDateToTimestamp(message.DevEUI_uplink.Time),\n values: keepTelemetry(values)\n }]\n};\n\nfunction decodeDevicePayload(payload) {\n /* https://www.decentlab.com/products/air-temperature-and-humidity-sensor-with-radiation-shield-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 2,\n values: [{name: 'air_temperature',\n displayName: 'Air temperature',\n convert: function (x) { return 175 * x[0] / 65535 - 45; },\n unit: '°C'},\n {name: 'air_humidity',\n displayName: 'Air humidity',\n convert: function (x) { return 100 * x[1] / 65535; },\n unit: '%'}]},\n {length: 1,\n values: [{name: 'battery_voltage',\n displayName: 'Battery voltage',\n convert: function (x) { return x[0] / 1000; },\n unit: 'V'}]}\n ],\n read_int: function (bytes, pos) {\n return (bytes[pos] << 8) + bytes[pos + 1];\n },\n decode: function (msg) {\n var bytes = msg;\n var i, j;\n if (typeof msg === 'string') {\n bytes = [];\n for (i = 0; i < msg.length; i += 2) {\n bytes.push(parseInt(msg.substring(i, i + 2), 16));\n }\n }\n var version = bytes[0];\n if (version != this.PROTOCOL_VERSION) {\n return {error: \"protocol version \" + version + \" doesn't match v2\"};\n }\n var deviceId = this.read_int(bytes, 1);\n var flags = this.read_int(bytes, 3);\n var result = {'protocol_version': version, 'device_id': deviceId};\n // decode payload\n var pos = 5;\n for (i = 0; i < this.SENSORS.length; i++, flags >>= 1) {\n if ((flags & 1) !== 1)\n continue;\n var sensor = this.SENSORS[i];\n var x = [];\n // convert data to 16-bit integer array\n for (j = 0; j < sensor.length; j++) {\n x.push(this.read_int(bytes, pos));\n pos += 2;\n }\n // decode sensor values\n for (j = 0; j < sensor.values.length; j++) {\n var value = sensor.values[j];\n if ('convert' in value) {\n result[value.name] = {displayName: value.displayName,\n value: value.convert.bind(this)(x)};\n if ('unit' in value)\n result[value.name]['unit'] = value.unit;\n }\n }\n }\n return result;\n }\n };\n\n const decoded = decentlab_decoder.decode(payload);\n const result = {};\n for (var k in decoded) {\n if (typeof decoded[k] === \"object\") {\n result[k] = decoded[k].value;\n } else if (typeof decoded[k] === \"number\") {\n result[k] = decoded[k];\n }\n }\n return result;\n}\n\nfunction extractNetworkTelemetry(payload) {\n return {\n lora_frame_counter: message.DevEUI_uplink.FCntUp,\n lora_frame_port: message.DevEUI_uplink.FPort,\n lora_frequency: message.DevEUI_uplink?.Frequency * 1000000,\n lora_spreading_factor: message.DevEUI_uplink.SpFact,\n lora_dev_eui: message.DevEUI_uplink.DevEUI,\n lora_rssi: message.DevEUI_uplink.LrrRSSI,\n lora_snr: message.DevEUI_uplink.LrrSNR,\n };\n}\n\n// Helper functions\n\nfunction decodeToJson(payload) {\n const s = String.fromCharCode.apply(String, payload);\n return JSON.parse(s);\n}\n\nfunction parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n }\n // If we cannot parse timestamp - we will use the current timestamp\n if (timestamp == -1) {\n timestamp = Date.now();\n }\n\n return timestamp;\n}\n\nfunction base64ToBytes(base64String) {\n if (typeof base64String === 'string') {\n return Uint8Array.from(atob(base64String), c => c.charCodeAt(0));\n }\n return [];\n}\n\nfunction keepTelemetry(telemetry) {\n const unwanted = ['protocol_version', 'device_id', 'lora_dev_eui'];\n return Object.fromEntries(\n Object.entries(telemetry).filter(\n (x) => !unwanted.includes(x[0])\n )\n );\n}" + } +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingPark/uplink/metadata.json b/VENDORS/Decentlab/DL-SHT35-002/ThingPark/uplink/metadata.json new file mode 100644 index 00000000..d024aecf --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingPark/uplink/metadata.json @@ -0,0 +1,4 @@ +{ + "integrationName": "ThingPark integration", + "includeGatewayInfo": false +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingPark/uplink/payload.json b/VENDORS/Decentlab/DL-SHT35-002/ThingPark/uplink/payload.json new file mode 100644 index 00000000..5adfd4bc --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingPark/uplink/payload.json @@ -0,0 +1,75 @@ +{ + "DevEUI_uplink": { + "Time": "2024-11-28T21:08:22.138+00:00", + "DevEUI": "70B3D57BA000156B", + "FPort": 1, + "FCntUp": 26, + "LostUplinksAS": 0, + "ADRbit": 1, + "MType": 2, + "FCntDn": 2, + "payload_hex": "02030e000364a079b10c60", + "mic_hex": "e7214986", + "Lrcid": "00000211", + "LrrRSSI": -114.0, + "LrrSNR": 4.75, + "LrrESP": -115.2547, + "SpFact": 9, + "SubBand": "G0", + "Channel": "LC1", + "Lrrid": "100019D4", + "Late": 0, + "LrrLAT": 32.516357, + "LrrLON": -106.824348, + "Lrrs": { + "Lrr": [ + { + "Lrrid": "100019D4", + "Chain": 0, + "LrrRSSI": -114.0, + "LrrSNR": 4.75, + "LrrESP": -115.2547 + } + ] + }, + "DevLrrCnt": 1, + "CustomerID": "100045194", + "CustomerData": { + "loc": { + "lat": "32.592782", + "lon": "-106.927742" + }, + "alr": { + "pro": "DL/TBRG", + "ver": "1" + }, + "tags": ["EnvironSens", "RainGauge", "CDRRC"], + "doms": [], + "name": "RainGauge 5483_Test" + }, + "BaseStationData": { + "doms": [], + "name": "iStation US #6_CDRRC_Summerford" + }, + "ModelCfg": "0", + "DriverCfg": { + "mod": { + "pId": "dl", + "mId": "dl-tbrg", + "ver": "1" + }, + "app": { + "pId": "dl", + "mId": "dl-tbrg", + "ver": "1" + } + }, + "InstantPER": 0.0, + "MeanPER": 0.037037, + "DevAddr": "00FDA112", + "TxPower": 18.0, + "NbTrans": 2, + "Frequency": 902.5, + "DynamicClass": "A" + } +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingPark/uplink/payload_01.json b/VENDORS/Decentlab/DL-SHT35-002/ThingPark/uplink/payload_01.json new file mode 100644 index 00000000..7d0a43d9 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingPark/uplink/payload_01.json @@ -0,0 +1,75 @@ +{ + "DevEUI_uplink": { + "Time": "2024-11-28T21:08:22.138+00:00", + "DevEUI": "70B3D57BA000156B", + "FPort": 1, + "FCntUp": 26, + "LostUplinksAS": 0, + "ADRbit": 1, + "MType": 2, + "FCntDn": 2, + "payload_hex": "02030e00020c60", + "mic_hex": "e7214986", + "Lrcid": "00000211", + "LrrRSSI": -114.0, + "LrrSNR": 4.75, + "LrrESP": -115.2547, + "SpFact": 9, + "SubBand": "G0", + "Channel": "LC1", + "Lrrid": "100019D4", + "Late": 0, + "LrrLAT": 32.516357, + "LrrLON": -106.824348, + "Lrrs": { + "Lrr": [ + { + "Lrrid": "100019D4", + "Chain": 0, + "LrrRSSI": -114.0, + "LrrSNR": 4.75, + "LrrESP": -115.2547 + } + ] + }, + "DevLrrCnt": 1, + "CustomerID": "100045194", + "CustomerData": { + "loc": { + "lat": "32.592782", + "lon": "-106.927742" + }, + "alr": { + "pro": "DL/TBRG", + "ver": "1" + }, + "tags": ["EnvironSens", "RainGauge", "CDRRC"], + "doms": [], + "name": "RainGauge 5483_Test" + }, + "BaseStationData": { + "doms": [], + "name": "iStation US #6_CDRRC_Summerford" + }, + "ModelCfg": "0", + "DriverCfg": { + "mod": { + "pId": "dl", + "mId": "dl-tbrg", + "ver": "1" + }, + "app": { + "pId": "dl", + "mId": "dl-tbrg", + "ver": "1" + } + }, + "InstantPER": 0.0, + "MeanPER": 0.037037, + "DevAddr": "00FDA112", + "TxPower": 18.0, + "NbTrans": 2, + "Frequency": 902.5, + "DynamicClass": "A" + } +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingPark/uplink/result.json b/VENDORS/Decentlab/DL-SHT35-002/ThingPark/uplink/result.json new file mode 100644 index 00000000..872843f3 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingPark/uplink/result.json @@ -0,0 +1,24 @@ +{ + "deviceName": "782", + "deviceType": "DL-SHT35-002", + "attributes": { + "lora_dev_eui": "70B3D57BA000156B", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1732828102138, + "values": { + "air_temperature": 23.787670710307466, + "air_humidity": 47.536430914778364, + "battery_voltage": 3.168, + "lora_frame_counter": 26, + "lora_frame_port": 1, + "lora_frequency": 902500000, + "lora_spreading_factor": 9, + "lora_rssi": -114, + "lora_snr": 4.75 + } + } + ] +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingPark/uplink/result_01.json b/VENDORS/Decentlab/DL-SHT35-002/ThingPark/uplink/result_01.json new file mode 100644 index 00000000..bcdf7769 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingPark/uplink/result_01.json @@ -0,0 +1,22 @@ +{ + "deviceName": "782", + "deviceType": "DL-SHT35-002", + "attributes": { + "lora_dev_eui": "70B3D57BA000156B", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1732828102138, + "values": { + "battery_voltage": 3.168, + "lora_frame_counter": 26, + "lora_frame_port": 1, + "lora_frequency": 902500000, + "lora_spreading_factor": 9, + "lora_rssi": -114, + "lora_snr": 4.75 + } + } + ] +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingParkEnterprise/uplink/converter.json b/VENDORS/Decentlab/DL-SHT35-002/ThingParkEnterprise/uplink/converter.json new file mode 100644 index 00000000..534bf7f8 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingParkEnterprise/uplink/converter.json @@ -0,0 +1,19 @@ +{ + "name": "ThingParkEnterprise Uplink Decoder for Decentlab DL-SHT35-002", + "type": "UPLINK", + "debugMode": true, + "edgeTemplate": false, + "configuration": { + "scriptLang": "JS", + "updateOnlyKeys": [ + "lora_frame_counter", + "lora_frame_port", + "lora_frequency", + "lora_spreading_factor", + "lora_rssi", + "lora_dev_eui", + "lora_snr" + ], + "decoder": "// WARNING: This script is designed to work with a remote JS executor. Compatibility with a local JS evaluator is not guaranteed and may require rewriting.\n\nconst message = decodeToJson(payload);\nconst values = Object.assign(\n decodeDevicePayload(message.DevEUI_uplink.payload_hex),\n extractNetworkTelemetry(message)\n);\n\nreturn {\n deviceName: String(values.device_id),\n deviceType: 'DL-SHT35-002',\n attributes: {\n lora_dev_eui: values.lora_dev_eui,\n protocol_version: values.protocol_version,\n },\n telemetry: [{\n ts: parseDateToTimestamp(message.DevEUI_uplink.Time),\n values: keepTelemetry(values)\n }]\n};\n\nfunction decodeDevicePayload(payload) {\n /* https://www.decentlab.com/products/air-temperature-and-humidity-sensor-with-radiation-shield-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 2,\n values: [{name: 'air_temperature',\n displayName: 'Air temperature',\n convert: function (x) { return 175 * x[0] / 65535 - 45; },\n unit: '°C'},\n {name: 'air_humidity',\n displayName: 'Air humidity',\n convert: function (x) { return 100 * x[1] / 65535; },\n unit: '%'}]},\n {length: 1,\n values: [{name: 'battery_voltage',\n displayName: 'Battery voltage',\n convert: function (x) { return x[0] / 1000; },\n unit: 'V'}]}\n ],\n read_int: function (bytes, pos) {\n return (bytes[pos] << 8) + bytes[pos + 1];\n },\n decode: function (msg) {\n var bytes = msg;\n var i, j;\n if (typeof msg === 'string') {\n bytes = [];\n for (i = 0; i < msg.length; i += 2) {\n bytes.push(parseInt(msg.substring(i, i + 2), 16));\n }\n }\n var version = bytes[0];\n if (version != this.PROTOCOL_VERSION) {\n return {error: \"protocol version \" + version + \" doesn't match v2\"};\n }\n var deviceId = this.read_int(bytes, 1);\n var flags = this.read_int(bytes, 3);\n var result = {'protocol_version': version, 'device_id': deviceId};\n // decode payload\n var pos = 5;\n for (i = 0; i < this.SENSORS.length; i++, flags >>= 1) {\n if ((flags & 1) !== 1)\n continue;\n var sensor = this.SENSORS[i];\n var x = [];\n // convert data to 16-bit integer array\n for (j = 0; j < sensor.length; j++) {\n x.push(this.read_int(bytes, pos));\n pos += 2;\n }\n // decode sensor values\n for (j = 0; j < sensor.values.length; j++) {\n var value = sensor.values[j];\n if ('convert' in value) {\n result[value.name] = {displayName: value.displayName,\n value: value.convert.bind(this)(x)};\n if ('unit' in value)\n result[value.name]['unit'] = value.unit;\n }\n }\n }\n return result;\n }\n };\n\n const decoded = decentlab_decoder.decode(payload);\n const result = {};\n for (var k in decoded) {\n if (typeof decoded[k] === \"object\") {\n result[k] = decoded[k].value;\n } else if (typeof decoded[k] === \"number\") {\n result[k] = decoded[k];\n }\n }\n return result;\n}\n\nfunction extractNetworkTelemetry(payload) {\n return {\n lora_frame_counter: message.DevEUI_uplink.FCntUp,\n lora_frame_port: message.DevEUI_uplink.FPort,\n lora_frequency: message.DevEUI_uplink?.Frequency * 1000000,\n lora_spreading_factor: message.DevEUI_uplink.SpFact,\n lora_dev_eui: message.DevEUI_uplink.DevEUI,\n lora_rssi: message.DevEUI_uplink.LrrRSSI,\n lora_snr: message.DevEUI_uplink.LrrSNR,\n };\n}\n\n// Helper functions\n\nfunction decodeToJson(payload) {\n const s = String.fromCharCode.apply(String, payload);\n return JSON.parse(s);\n}\n\nfunction parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n }\n // If we cannot parse timestamp - we will use the current timestamp\n if (timestamp == -1) {\n timestamp = Date.now();\n }\n\n return timestamp;\n}\n\nfunction base64ToBytes(base64String) {\n if (typeof base64String === 'string') {\n return Uint8Array.from(atob(base64String), c => c.charCodeAt(0));\n }\n return [];\n}\n\nfunction keepTelemetry(telemetry) {\n const unwanted = ['protocol_version', 'device_id', 'lora_dev_eui'];\n return Object.fromEntries(\n Object.entries(telemetry).filter(\n (x) => !unwanted.includes(x[0])\n )\n );\n}" + } +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingParkEnterprise/uplink/metadata.json b/VENDORS/Decentlab/DL-SHT35-002/ThingParkEnterprise/uplink/metadata.json new file mode 100644 index 00000000..47c01690 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingParkEnterprise/uplink/metadata.json @@ -0,0 +1,4 @@ +{ + "integrationName": "ThingParkEnterprise integration", + "includeGatewayInfo": false +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingParkEnterprise/uplink/payload.json b/VENDORS/Decentlab/DL-SHT35-002/ThingParkEnterprise/uplink/payload.json new file mode 100644 index 00000000..5adfd4bc --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingParkEnterprise/uplink/payload.json @@ -0,0 +1,75 @@ +{ + "DevEUI_uplink": { + "Time": "2024-11-28T21:08:22.138+00:00", + "DevEUI": "70B3D57BA000156B", + "FPort": 1, + "FCntUp": 26, + "LostUplinksAS": 0, + "ADRbit": 1, + "MType": 2, + "FCntDn": 2, + "payload_hex": "02030e000364a079b10c60", + "mic_hex": "e7214986", + "Lrcid": "00000211", + "LrrRSSI": -114.0, + "LrrSNR": 4.75, + "LrrESP": -115.2547, + "SpFact": 9, + "SubBand": "G0", + "Channel": "LC1", + "Lrrid": "100019D4", + "Late": 0, + "LrrLAT": 32.516357, + "LrrLON": -106.824348, + "Lrrs": { + "Lrr": [ + { + "Lrrid": "100019D4", + "Chain": 0, + "LrrRSSI": -114.0, + "LrrSNR": 4.75, + "LrrESP": -115.2547 + } + ] + }, + "DevLrrCnt": 1, + "CustomerID": "100045194", + "CustomerData": { + "loc": { + "lat": "32.592782", + "lon": "-106.927742" + }, + "alr": { + "pro": "DL/TBRG", + "ver": "1" + }, + "tags": ["EnvironSens", "RainGauge", "CDRRC"], + "doms": [], + "name": "RainGauge 5483_Test" + }, + "BaseStationData": { + "doms": [], + "name": "iStation US #6_CDRRC_Summerford" + }, + "ModelCfg": "0", + "DriverCfg": { + "mod": { + "pId": "dl", + "mId": "dl-tbrg", + "ver": "1" + }, + "app": { + "pId": "dl", + "mId": "dl-tbrg", + "ver": "1" + } + }, + "InstantPER": 0.0, + "MeanPER": 0.037037, + "DevAddr": "00FDA112", + "TxPower": 18.0, + "NbTrans": 2, + "Frequency": 902.5, + "DynamicClass": "A" + } +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingParkEnterprise/uplink/payload_01.json b/VENDORS/Decentlab/DL-SHT35-002/ThingParkEnterprise/uplink/payload_01.json new file mode 100644 index 00000000..7d0a43d9 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingParkEnterprise/uplink/payload_01.json @@ -0,0 +1,75 @@ +{ + "DevEUI_uplink": { + "Time": "2024-11-28T21:08:22.138+00:00", + "DevEUI": "70B3D57BA000156B", + "FPort": 1, + "FCntUp": 26, + "LostUplinksAS": 0, + "ADRbit": 1, + "MType": 2, + "FCntDn": 2, + "payload_hex": "02030e00020c60", + "mic_hex": "e7214986", + "Lrcid": "00000211", + "LrrRSSI": -114.0, + "LrrSNR": 4.75, + "LrrESP": -115.2547, + "SpFact": 9, + "SubBand": "G0", + "Channel": "LC1", + "Lrrid": "100019D4", + "Late": 0, + "LrrLAT": 32.516357, + "LrrLON": -106.824348, + "Lrrs": { + "Lrr": [ + { + "Lrrid": "100019D4", + "Chain": 0, + "LrrRSSI": -114.0, + "LrrSNR": 4.75, + "LrrESP": -115.2547 + } + ] + }, + "DevLrrCnt": 1, + "CustomerID": "100045194", + "CustomerData": { + "loc": { + "lat": "32.592782", + "lon": "-106.927742" + }, + "alr": { + "pro": "DL/TBRG", + "ver": "1" + }, + "tags": ["EnvironSens", "RainGauge", "CDRRC"], + "doms": [], + "name": "RainGauge 5483_Test" + }, + "BaseStationData": { + "doms": [], + "name": "iStation US #6_CDRRC_Summerford" + }, + "ModelCfg": "0", + "DriverCfg": { + "mod": { + "pId": "dl", + "mId": "dl-tbrg", + "ver": "1" + }, + "app": { + "pId": "dl", + "mId": "dl-tbrg", + "ver": "1" + } + }, + "InstantPER": 0.0, + "MeanPER": 0.037037, + "DevAddr": "00FDA112", + "TxPower": 18.0, + "NbTrans": 2, + "Frequency": 902.5, + "DynamicClass": "A" + } +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingParkEnterprise/uplink/result.json b/VENDORS/Decentlab/DL-SHT35-002/ThingParkEnterprise/uplink/result.json new file mode 100644 index 00000000..872843f3 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingParkEnterprise/uplink/result.json @@ -0,0 +1,24 @@ +{ + "deviceName": "782", + "deviceType": "DL-SHT35-002", + "attributes": { + "lora_dev_eui": "70B3D57BA000156B", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1732828102138, + "values": { + "air_temperature": 23.787670710307466, + "air_humidity": 47.536430914778364, + "battery_voltage": 3.168, + "lora_frame_counter": 26, + "lora_frame_port": 1, + "lora_frequency": 902500000, + "lora_spreading_factor": 9, + "lora_rssi": -114, + "lora_snr": 4.75 + } + } + ] +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingParkEnterprise/uplink/result_01.json b/VENDORS/Decentlab/DL-SHT35-002/ThingParkEnterprise/uplink/result_01.json new file mode 100644 index 00000000..bcdf7769 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingParkEnterprise/uplink/result_01.json @@ -0,0 +1,22 @@ +{ + "deviceName": "782", + "deviceType": "DL-SHT35-002", + "attributes": { + "lora_dev_eui": "70B3D57BA000156B", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1732828102138, + "values": { + "battery_voltage": 3.168, + "lora_frame_counter": 26, + "lora_frame_port": 1, + "lora_frequency": 902500000, + "lora_spreading_factor": 9, + "lora_rssi": -114, + "lora_snr": 4.75 + } + } + ] +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingsStackCommunity/uplink/converter.json b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackCommunity/uplink/converter.json new file mode 100644 index 00000000..443fb80d --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackCommunity/uplink/converter.json @@ -0,0 +1,19 @@ +{ + "name": "ThingsStackCommunity Uplink Decoder for Decentlab DL-SHT35-002", + "type": "UPLINK", + "debugMode": true, + "edgeTemplate": false, + "configuration": { + "scriptLang": "JS", + "updateOnlyKeys": [ + "lora_frame_counter", + "lora_frame_port", + "lora_frequency", + "lora_spreading_factor", + "lora_rssi", + "lora_dev_eui", + "lora_snr" + ], + "decoder": "// WARNING: This script is designed to work with a remote JS executor. Compatibility with a local JS evaluator is not guaranteed and may require rewriting.\n\nconst message = decodeToJson(payload);\nconst values = Object.assign(\n decodeDevicePayload(base64ToBytes(message.uplink_message.frm_payload)),\n extractNetworkTelemetry(message)\n);\n\nreturn {\n deviceName: String(values.device_id),\n deviceType: 'DL-SHT35-002',\n attributes: {\n lora_dev_eui: values.lora_dev_eui,\n protocol_version: values.protocol_version,\n },\n telemetry: [{\n ts: parseDateToTimestamp(message.received_at),\n values: keepTelemetry(values)\n }]\n};\n\nfunction decodeDevicePayload(payload) {\n /* https://www.decentlab.com/products/air-temperature-and-humidity-sensor-with-radiation-shield-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 2,\n values: [{name: 'air_temperature',\n displayName: 'Air temperature',\n convert: function (x) { return 175 * x[0] / 65535 - 45; },\n unit: '°C'},\n {name: 'air_humidity',\n displayName: 'Air humidity',\n convert: function (x) { return 100 * x[1] / 65535; },\n unit: '%'}]},\n {length: 1,\n values: [{name: 'battery_voltage',\n displayName: 'Battery voltage',\n convert: function (x) { return x[0] / 1000; },\n unit: 'V'}]}\n ],\n read_int: function (bytes, pos) {\n return (bytes[pos] << 8) + bytes[pos + 1];\n },\n decode: function (msg) {\n var bytes = msg;\n var i, j;\n if (typeof msg === 'string') {\n bytes = [];\n for (i = 0; i < msg.length; i += 2) {\n bytes.push(parseInt(msg.substring(i, i + 2), 16));\n }\n }\n var version = bytes[0];\n if (version != this.PROTOCOL_VERSION) {\n return {error: \"protocol version \" + version + \" doesn't match v2\"};\n }\n var deviceId = this.read_int(bytes, 1);\n var flags = this.read_int(bytes, 3);\n var result = {'protocol_version': version, 'device_id': deviceId};\n // decode payload\n var pos = 5;\n for (i = 0; i < this.SENSORS.length; i++, flags >>= 1) {\n if ((flags & 1) !== 1)\n continue;\n var sensor = this.SENSORS[i];\n var x = [];\n // convert data to 16-bit integer array\n for (j = 0; j < sensor.length; j++) {\n x.push(this.read_int(bytes, pos));\n pos += 2;\n }\n // decode sensor values\n for (j = 0; j < sensor.values.length; j++) {\n var value = sensor.values[j];\n if ('convert' in value) {\n result[value.name] = {displayName: value.displayName,\n value: value.convert.bind(this)(x)};\n if ('unit' in value)\n result[value.name]['unit'] = value.unit;\n }\n }\n }\n return result;\n }\n };\n\n const decoded = decentlab_decoder.decode(payload);\n const result = {};\n for (var k in decoded) {\n if (typeof decoded[k] === \"object\") {\n result[k] = decoded[k].value;\n } else if (typeof decoded[k] === \"number\") {\n result[k] = decoded[k];\n }\n }\n return result;\n}\n\nfunction extractNetworkTelemetry(payload) {\n const result = {\n lora_frame_counter: message.uplink_message.f_cnt,\n lora_frame_port: message.uplink_message.f_port,\n lora_frequency: parseInt(message.uplink_message.settings.frequency),\n lora_dev_eui: message.end_device_ids.dev_eui,\n lora_spreading_factor:\n message.uplink_message.settings.data_rate.lora.spreading_factor,\n };\n const gwi = message.uplink_message.rx_metadata\n .map((x) => ({ lora_rssi: x.rssi, lora_snr: x.snr }))\n .reduce((a, x) => (x.lora_rssi > a.lora_rssi ? x : a), {\n lora_rssi: -Number.MAX_VALUE,\n });\n return Object.assign(result, gwi);\n}\n\n// Helper functions\n\nfunction decodeToJson(payload) {\n const s = String.fromCharCode.apply(String, payload);\n return JSON.parse(s);\n}\n\nfunction parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n }\n // If we cannot parse timestamp - we will use the current timestamp\n if (timestamp == -1) {\n timestamp = Date.now();\n }\n\n return timestamp;\n}\n\nfunction base64ToBytes(base64String) {\n if (typeof base64String === 'string') {\n return Uint8Array.from(atob(base64String), c => c.charCodeAt(0));\n }\n return [];\n}\n\nfunction keepTelemetry(telemetry) {\n const unwanted = ['protocol_version', 'device_id', 'lora_dev_eui'];\n return Object.fromEntries(\n Object.entries(telemetry).filter(\n (x) => !unwanted.includes(x[0])\n )\n );\n}" + } +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingsStackCommunity/uplink/metadata.json b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackCommunity/uplink/metadata.json new file mode 100644 index 00000000..11a4de83 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackCommunity/uplink/metadata.json @@ -0,0 +1,4 @@ +{ + "integrationName": "ThingsStackCommunity integration", + "includeGatewayInfo": false +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingsStackCommunity/uplink/payload.json b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackCommunity/uplink/payload.json new file mode 100644 index 00000000..54f8c83f --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackCommunity/uplink/payload.json @@ -0,0 +1,60 @@ +{ + "end_device_ids": { + "device_id": "03022", + "application_ids": { + "application_id": "test-decentlab" + }, + "dev_eui": "70B3D57BA0000BCE", + "join_eui": "70B3D57ED00006B2", + "dev_addr": "27000020" + }, + "correlation_ids": [ + "as:up:01E0CY8V864TP36Q130RSQQJBY", + "gs:conn:01E0B4GJHDRFKNEQ9GG3ZDG1JX", + "gs:uplink:01E0CY8V1FFJ0WCKXTKT0CRPDY", + "ns:uplink:01E0CY8V1JGN0R5YZTBH48YXH3", + "rpc:/ttn.lorawan.v3.GsNs/HandleUplink:01E0CY8V1JHJA8PE0P98VSDE6K" + ], + "received_at": "2020-02-06T09:46:05.447941836Z", + "uplink_message": { + "session_key_id": "AXAWYbtxgUllLtJWdZrW0Q==", + "f_port": 1, + "f_cnt": 101, + "frm_payload": "AgMOAANkoHmxDGA=", + "rx_metadata": [ + { + "gateway_ids": { + "gateway_id": "eui-b827ebfffe9f2da9", + "eui": "B827EBFFFE9F2DA9" + }, + "time": "2020-02-06T09:46:05Z", + "timestamp": 436812492, + "rssi": -16, + "channel_rssi": -16, + "snr": 8.5, + "uplink_token": "CiIKIAoUZXVpLWI4MjdlYmZmZmU5ZjJkYTkSCLgn6//+ny2pEMz1pNAB", + "location": { + "latitude": 52.71863806169478, + "longitude": -4.052632749080659, + "altitude": 5, + "source": "SOURCE_REGISTRY" + }, + "channel_index": 6 + } + ], + "settings": { + "data_rate": { + "lora": { + "bandwidth": 125000, + "spreading_factor": 11 + } + }, + "data_rate_index": 1, + "coding_rate": "4/5", + "frequency": "867700000", + "timestamp": 436812492, + "time": "2020-02-06T09:46:05Z" + }, + "received_at": "2020-02-06T09:46:05.234172599Z" + } +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingsStackCommunity/uplink/payload_01.json b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackCommunity/uplink/payload_01.json new file mode 100644 index 00000000..09bf7a63 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackCommunity/uplink/payload_01.json @@ -0,0 +1,60 @@ +{ + "end_device_ids": { + "device_id": "03022", + "application_ids": { + "application_id": "test-decentlab" + }, + "dev_eui": "70B3D57BA0000BCE", + "join_eui": "70B3D57ED00006B2", + "dev_addr": "27000020" + }, + "correlation_ids": [ + "as:up:01E0CY8V864TP36Q130RSQQJBY", + "gs:conn:01E0B4GJHDRFKNEQ9GG3ZDG1JX", + "gs:uplink:01E0CY8V1FFJ0WCKXTKT0CRPDY", + "ns:uplink:01E0CY8V1JGN0R5YZTBH48YXH3", + "rpc:/ttn.lorawan.v3.GsNs/HandleUplink:01E0CY8V1JHJA8PE0P98VSDE6K" + ], + "received_at": "2020-02-06T09:46:05.447941836Z", + "uplink_message": { + "session_key_id": "AXAWYbtxgUllLtJWdZrW0Q==", + "f_port": 1, + "f_cnt": 101, + "frm_payload": "AgMOAAIMYA==", + "rx_metadata": [ + { + "gateway_ids": { + "gateway_id": "eui-b827ebfffe9f2da9", + "eui": "B827EBFFFE9F2DA9" + }, + "time": "2020-02-06T09:46:05Z", + "timestamp": 436812492, + "rssi": -16, + "channel_rssi": -16, + "snr": 8.5, + "uplink_token": "CiIKIAoUZXVpLWI4MjdlYmZmZmU5ZjJkYTkSCLgn6//+ny2pEMz1pNAB", + "location": { + "latitude": 52.71863806169478, + "longitude": -4.052632749080659, + "altitude": 5, + "source": "SOURCE_REGISTRY" + }, + "channel_index": 6 + } + ], + "settings": { + "data_rate": { + "lora": { + "bandwidth": 125000, + "spreading_factor": 11 + } + }, + "data_rate_index": 1, + "coding_rate": "4/5", + "frequency": "867700000", + "timestamp": 436812492, + "time": "2020-02-06T09:46:05Z" + }, + "received_at": "2020-02-06T09:46:05.234172599Z" + } +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingsStackCommunity/uplink/result.json b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackCommunity/uplink/result.json new file mode 100644 index 00000000..925b1f23 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackCommunity/uplink/result.json @@ -0,0 +1,24 @@ +{ + "deviceName": "782", + "deviceType": "DL-SHT35-002", + "attributes": { + "lora_dev_eui": "70B3D57BA0000BCE", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1580982365447, + "values": { + "air_temperature": 23.787670710307466, + "air_humidity": 47.536430914778364, + "battery_voltage": 3.168, + "lora_frame_counter": 101, + "lora_frame_port": 1, + "lora_frequency": 867700000, + "lora_spreading_factor": 11, + "lora_rssi": -16, + "lora_snr": 8.5 + } + } + ] +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingsStackCommunity/uplink/result_01.json b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackCommunity/uplink/result_01.json new file mode 100644 index 00000000..7f2fb8ca --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackCommunity/uplink/result_01.json @@ -0,0 +1,22 @@ +{ + "deviceName": "782", + "deviceType": "DL-SHT35-002", + "attributes": { + "lora_dev_eui": "70B3D57BA0000BCE", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1580982365447, + "values": { + "battery_voltage": 3.168, + "lora_frame_counter": 101, + "lora_frame_port": 1, + "lora_frequency": 867700000, + "lora_spreading_factor": 11, + "lora_rssi": -16, + "lora_snr": 8.5 + } + } + ] +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingsStackIndustries/uplink/converter.json b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackIndustries/uplink/converter.json new file mode 100644 index 00000000..0bcd4110 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackIndustries/uplink/converter.json @@ -0,0 +1,19 @@ +{ + "name": "ThingsStackIndustries Uplink Decoder for Decentlab DL-SHT35-002", + "type": "UPLINK", + "debugMode": true, + "edgeTemplate": false, + "configuration": { + "scriptLang": "JS", + "updateOnlyKeys": [ + "lora_frame_counter", + "lora_frame_port", + "lora_frequency", + "lora_spreading_factor", + "lora_rssi", + "lora_dev_eui", + "lora_snr" + ], + "decoder": "// WARNING: This script is designed to work with a remote JS executor. Compatibility with a local JS evaluator is not guaranteed and may require rewriting.\n\nconst message = decodeToJson(payload);\nconst values = Object.assign(\n decodeDevicePayload(base64ToBytes(message.uplink_message.frm_payload)),\n extractNetworkTelemetry(message)\n);\n\nreturn {\n deviceName: String(values.device_id),\n deviceType: 'DL-SHT35-002',\n attributes: {\n lora_dev_eui: values.lora_dev_eui,\n protocol_version: values.protocol_version,\n },\n telemetry: [{\n ts: parseDateToTimestamp(message.received_at),\n values: keepTelemetry(values)\n }]\n};\n\nfunction decodeDevicePayload(payload) {\n /* https://www.decentlab.com/products/air-temperature-and-humidity-sensor-with-radiation-shield-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 2,\n values: [{name: 'air_temperature',\n displayName: 'Air temperature',\n convert: function (x) { return 175 * x[0] / 65535 - 45; },\n unit: '°C'},\n {name: 'air_humidity',\n displayName: 'Air humidity',\n convert: function (x) { return 100 * x[1] / 65535; },\n unit: '%'}]},\n {length: 1,\n values: [{name: 'battery_voltage',\n displayName: 'Battery voltage',\n convert: function (x) { return x[0] / 1000; },\n unit: 'V'}]}\n ],\n read_int: function (bytes, pos) {\n return (bytes[pos] << 8) + bytes[pos + 1];\n },\n decode: function (msg) {\n var bytes = msg;\n var i, j;\n if (typeof msg === 'string') {\n bytes = [];\n for (i = 0; i < msg.length; i += 2) {\n bytes.push(parseInt(msg.substring(i, i + 2), 16));\n }\n }\n var version = bytes[0];\n if (version != this.PROTOCOL_VERSION) {\n return {error: \"protocol version \" + version + \" doesn't match v2\"};\n }\n var deviceId = this.read_int(bytes, 1);\n var flags = this.read_int(bytes, 3);\n var result = {'protocol_version': version, 'device_id': deviceId};\n // decode payload\n var pos = 5;\n for (i = 0; i < this.SENSORS.length; i++, flags >>= 1) {\n if ((flags & 1) !== 1)\n continue;\n var sensor = this.SENSORS[i];\n var x = [];\n // convert data to 16-bit integer array\n for (j = 0; j < sensor.length; j++) {\n x.push(this.read_int(bytes, pos));\n pos += 2;\n }\n // decode sensor values\n for (j = 0; j < sensor.values.length; j++) {\n var value = sensor.values[j];\n if ('convert' in value) {\n result[value.name] = {displayName: value.displayName,\n value: value.convert.bind(this)(x)};\n if ('unit' in value)\n result[value.name]['unit'] = value.unit;\n }\n }\n }\n return result;\n }\n };\n\n const decoded = decentlab_decoder.decode(payload);\n const result = {};\n for (var k in decoded) {\n if (typeof decoded[k] === \"object\") {\n result[k] = decoded[k].value;\n } else if (typeof decoded[k] === \"number\") {\n result[k] = decoded[k];\n }\n }\n return result;\n}\n\nfunction extractNetworkTelemetry(payload) {\n const result = {\n lora_frame_counter: message.uplink_message.f_cnt,\n lora_frame_port: message.uplink_message.f_port,\n lora_frequency: parseInt(message.uplink_message.settings.frequency),\n lora_dev_eui: message.end_device_ids.dev_eui,\n lora_spreading_factor:\n message.uplink_message.settings.data_rate.lora.spreading_factor,\n };\n const gwi = message.uplink_message.rx_metadata\n .map((x) => ({ lora_rssi: x.rssi, lora_snr: x.snr }))\n .reduce((a, x) => (x.lora_rssi > a.lora_rssi ? x : a), {\n lora_rssi: -Number.MAX_VALUE,\n });\n return Object.assign(result, gwi);\n}\n\n// Helper functions\n\nfunction decodeToJson(payload) {\n const s = String.fromCharCode.apply(String, payload);\n return JSON.parse(s);\n}\n\nfunction parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n }\n // If we cannot parse timestamp - we will use the current timestamp\n if (timestamp == -1) {\n timestamp = Date.now();\n }\n\n return timestamp;\n}\n\nfunction base64ToBytes(base64String) {\n if (typeof base64String === 'string') {\n return Uint8Array.from(atob(base64String), c => c.charCodeAt(0));\n }\n return [];\n}\n\nfunction keepTelemetry(telemetry) {\n const unwanted = ['protocol_version', 'device_id', 'lora_dev_eui'];\n return Object.fromEntries(\n Object.entries(telemetry).filter(\n (x) => !unwanted.includes(x[0])\n )\n );\n}" + } +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingsStackIndustries/uplink/metadata.json b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackIndustries/uplink/metadata.json new file mode 100644 index 00000000..e304ca99 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackIndustries/uplink/metadata.json @@ -0,0 +1,4 @@ +{ + "integrationName": "ThingsStackIndustries integration", + "includeGatewayInfo": false +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingsStackIndustries/uplink/payload.json b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackIndustries/uplink/payload.json new file mode 100644 index 00000000..54f8c83f --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackIndustries/uplink/payload.json @@ -0,0 +1,60 @@ +{ + "end_device_ids": { + "device_id": "03022", + "application_ids": { + "application_id": "test-decentlab" + }, + "dev_eui": "70B3D57BA0000BCE", + "join_eui": "70B3D57ED00006B2", + "dev_addr": "27000020" + }, + "correlation_ids": [ + "as:up:01E0CY8V864TP36Q130RSQQJBY", + "gs:conn:01E0B4GJHDRFKNEQ9GG3ZDG1JX", + "gs:uplink:01E0CY8V1FFJ0WCKXTKT0CRPDY", + "ns:uplink:01E0CY8V1JGN0R5YZTBH48YXH3", + "rpc:/ttn.lorawan.v3.GsNs/HandleUplink:01E0CY8V1JHJA8PE0P98VSDE6K" + ], + "received_at": "2020-02-06T09:46:05.447941836Z", + "uplink_message": { + "session_key_id": "AXAWYbtxgUllLtJWdZrW0Q==", + "f_port": 1, + "f_cnt": 101, + "frm_payload": "AgMOAANkoHmxDGA=", + "rx_metadata": [ + { + "gateway_ids": { + "gateway_id": "eui-b827ebfffe9f2da9", + "eui": "B827EBFFFE9F2DA9" + }, + "time": "2020-02-06T09:46:05Z", + "timestamp": 436812492, + "rssi": -16, + "channel_rssi": -16, + "snr": 8.5, + "uplink_token": "CiIKIAoUZXVpLWI4MjdlYmZmZmU5ZjJkYTkSCLgn6//+ny2pEMz1pNAB", + "location": { + "latitude": 52.71863806169478, + "longitude": -4.052632749080659, + "altitude": 5, + "source": "SOURCE_REGISTRY" + }, + "channel_index": 6 + } + ], + "settings": { + "data_rate": { + "lora": { + "bandwidth": 125000, + "spreading_factor": 11 + } + }, + "data_rate_index": 1, + "coding_rate": "4/5", + "frequency": "867700000", + "timestamp": 436812492, + "time": "2020-02-06T09:46:05Z" + }, + "received_at": "2020-02-06T09:46:05.234172599Z" + } +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingsStackIndustries/uplink/payload_01.json b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackIndustries/uplink/payload_01.json new file mode 100644 index 00000000..09bf7a63 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackIndustries/uplink/payload_01.json @@ -0,0 +1,60 @@ +{ + "end_device_ids": { + "device_id": "03022", + "application_ids": { + "application_id": "test-decentlab" + }, + "dev_eui": "70B3D57BA0000BCE", + "join_eui": "70B3D57ED00006B2", + "dev_addr": "27000020" + }, + "correlation_ids": [ + "as:up:01E0CY8V864TP36Q130RSQQJBY", + "gs:conn:01E0B4GJHDRFKNEQ9GG3ZDG1JX", + "gs:uplink:01E0CY8V1FFJ0WCKXTKT0CRPDY", + "ns:uplink:01E0CY8V1JGN0R5YZTBH48YXH3", + "rpc:/ttn.lorawan.v3.GsNs/HandleUplink:01E0CY8V1JHJA8PE0P98VSDE6K" + ], + "received_at": "2020-02-06T09:46:05.447941836Z", + "uplink_message": { + "session_key_id": "AXAWYbtxgUllLtJWdZrW0Q==", + "f_port": 1, + "f_cnt": 101, + "frm_payload": "AgMOAAIMYA==", + "rx_metadata": [ + { + "gateway_ids": { + "gateway_id": "eui-b827ebfffe9f2da9", + "eui": "B827EBFFFE9F2DA9" + }, + "time": "2020-02-06T09:46:05Z", + "timestamp": 436812492, + "rssi": -16, + "channel_rssi": -16, + "snr": 8.5, + "uplink_token": "CiIKIAoUZXVpLWI4MjdlYmZmZmU5ZjJkYTkSCLgn6//+ny2pEMz1pNAB", + "location": { + "latitude": 52.71863806169478, + "longitude": -4.052632749080659, + "altitude": 5, + "source": "SOURCE_REGISTRY" + }, + "channel_index": 6 + } + ], + "settings": { + "data_rate": { + "lora": { + "bandwidth": 125000, + "spreading_factor": 11 + } + }, + "data_rate_index": 1, + "coding_rate": "4/5", + "frequency": "867700000", + "timestamp": 436812492, + "time": "2020-02-06T09:46:05Z" + }, + "received_at": "2020-02-06T09:46:05.234172599Z" + } +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingsStackIndustries/uplink/result.json b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackIndustries/uplink/result.json new file mode 100644 index 00000000..925b1f23 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackIndustries/uplink/result.json @@ -0,0 +1,24 @@ +{ + "deviceName": "782", + "deviceType": "DL-SHT35-002", + "attributes": { + "lora_dev_eui": "70B3D57BA0000BCE", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1580982365447, + "values": { + "air_temperature": 23.787670710307466, + "air_humidity": 47.536430914778364, + "battery_voltage": 3.168, + "lora_frame_counter": 101, + "lora_frame_port": 1, + "lora_frequency": 867700000, + "lora_spreading_factor": 11, + "lora_rssi": -16, + "lora_snr": 8.5 + } + } + ] +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/ThingsStackIndustries/uplink/result_01.json b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackIndustries/uplink/result_01.json new file mode 100644 index 00000000..7f2fb8ca --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/ThingsStackIndustries/uplink/result_01.json @@ -0,0 +1,22 @@ +{ + "deviceName": "782", + "deviceType": "DL-SHT35-002", + "attributes": { + "lora_dev_eui": "70B3D57BA0000BCE", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1580982365447, + "values": { + "battery_voltage": 3.168, + "lora_frame_counter": 101, + "lora_frame_port": 1, + "lora_frequency": 867700000, + "lora_spreading_factor": 11, + "lora_rssi": -16, + "lora_snr": 8.5 + } + } + ] +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/info.json b/VENDORS/Decentlab/DL-SHT35-002/info.json new file mode 100644 index 00000000..609ed0b2 --- /dev/null +++ b/VENDORS/Decentlab/DL-SHT35-002/info.json @@ -0,0 +1,5 @@ +{ + "url": "https://www.decentlab.com/products/air-temperature-and-humidity-sensor-for-lorawan", + "label": "DL-SHT35-002: Air temperature and humidity sensor", + "description": "The Decentlab DL-SHT35-002 is equipped with a temperature and humidity sensors for environmental monitoring. Suitable for cold chain, storage, building automation, smart agriculture applications." +} diff --git a/VENDORS/Decentlab/DL-SHT35-002/photo.png b/VENDORS/Decentlab/DL-SHT35-002/photo.png new file mode 100644 index 00000000..47e5f1eb Binary files /dev/null and b/VENDORS/Decentlab/DL-SHT35-002/photo.png differ