diff --git a/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/converter.json b/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/converter.json new file mode 100644 index 00000000..4a94bacb --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/converter.json @@ -0,0 +1,19 @@ +{ + "name": "ChirpStack Uplink Decoder for Decentlab DL-LID", + "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": "const 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-LID',\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/laser-distance-level-sensor-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 11,\n values: [{name: 'distance_average',\n displayName: 'Distance: average',\n convert: function (x) { return x[0]; },\n unit: 'mm'},\n {name: 'distance_minimum',\n displayName: 'Distance: minimum',\n convert: function (x) { return x[1]; },\n unit: 'mm'},\n {name: 'distance_maximum',\n displayName: 'Distance: maximum',\n convert: function (x) { return x[2]; },\n unit: 'mm'},\n {name: 'distance_median',\n displayName: 'Distance: median',\n convert: function (x) { return x[3]; },\n unit: 'mm'},\n {name: 'distance_10th_percentile',\n displayName: 'Distance: 10th percentile',\n convert: function (x) { return x[4]; },\n unit: 'mm'},\n {name: 'distance_25th_percentile',\n displayName: 'Distance: 25th percentile',\n convert: function (x) { return x[5]; },\n unit: 'mm'},\n {name: 'distance_75th_percentile',\n displayName: 'Distance: 75th percentile',\n convert: function (x) { return x[6]; },\n unit: 'mm'},\n {name: 'distance_90th_percentile',\n displayName: 'Distance: 90th percentile',\n convert: function (x) { return x[7]; },\n unit: 'mm'},\n {name: 'distance_most_frequent_value',\n displayName: 'Distance: most frequent value',\n convert: function (x) { return x[8]; },\n unit: 'mm'},\n {name: 'number_of_samples',\n displayName: 'Number of samples',\n convert: function (x) { return x[9]; }},\n {name: 'total_acquisition_time',\n displayName: 'Total acquisition time',\n convert: function (x) { return x[10] / 1.024; },\n unit: 'ms'}]},\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-LID/ChirpStack/uplink/metadata.json b/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/metadata.json new file mode 100644 index 00000000..1f9359b9 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/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-LID/ChirpStack/uplink/payload.json b/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/payload.json new file mode 100644 index 00000000..4c273d0d --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/payload.json @@ -0,0 +1,55 @@ +{ + "adr": true, + "confirmed": false, + "data": "AhHJAAMRmxF2EbwRnhGKEZQRqBGoEZQAZAGZCr0=", + "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-LID/ChirpStack/uplink/payload_01.json b/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/payload_01.json new file mode 100644 index 00000000..a35fb7b0 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/payload_01.json @@ -0,0 +1,55 @@ +{ + "adr": true, + "confirmed": false, + "data": "AhHJAAIKvQ==", + "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-LID/ChirpStack/uplink/payload_02.json b/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/payload_02.json new file mode 100644 index 00000000..27d684d6 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/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": "AhHJAAMRmxF2EbwRnhGKEZQRqBGoEZQAZAGZCr0=", + "objectJSON": "{\"temperatureSensor\":25,\"humiditySensor\":32}", + "tags": { + "key": "value" + } +} diff --git a/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/payload_03.json b/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/payload_03.json new file mode 100644 index 00000000..f071b013 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/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": "AhHJAAIKvQ==", + "objectJSON": "{\"temperatureSensor\":25,\"humiditySensor\":32}", + "tags": { + "key": "value" + } +} diff --git a/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/result.json b/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/result.json new file mode 100644 index 00000000..2c01c1eb --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/result.json @@ -0,0 +1,33 @@ +{ + "deviceName": "4553", + "deviceType": "DL-LID", + "attributes": { + "lora_dev_eui": "70B3D57BA0003BB8", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": "2024-11-10T12:51:50+00:00", + "values": { + "distance_average": 4507, + "distance_minimum": 4470, + "distance_maximum": 4540, + "distance_median": 4510, + "distance_10th_percentile": 4490, + "distance_25th_percentile": 4500, + "distance_75th_percentile": 4520, + "distance_90th_percentile": 4520, + "distance_most_frequent_value": 4500, + "number_of_samples": 100, + "total_acquisition_time": 399.4140625, + "battery_voltage": 2.749, + "lora_frame_counter": 20, + "lora_frame_port": 1, + "lora_frequency": 868300000, + "lora_spreading_factor": 7, + "lora_rssi": -106, + "lora_snr": 3 + } + } + ] +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/result_01.json b/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/result_01.json new file mode 100644 index 00000000..6f658cd9 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/result_01.json @@ -0,0 +1,22 @@ +{ + "deviceName": "4553", + "deviceType": "DL-LID", + "attributes": { + "lora_dev_eui": "70B3D57BA0003BB8", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": "2024-11-10T12:51:50+00:00", + "values": { + "battery_voltage": 2.749, + "lora_frame_counter": 20, + "lora_frame_port": 1, + "lora_frequency": 868300000, + "lora_spreading_factor": 7, + "lora_rssi": -106, + "lora_snr": 3 + } + } + ] +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/result_02.json b/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/result_02.json new file mode 100644 index 00000000..0daa7eea --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/result_02.json @@ -0,0 +1,32 @@ +{ + "deviceName": "4553", + "deviceType": "DL-LID", + "attributes": { + "lora_dev_eui": "0202020202020202", + "protocol_version": 2 + }, + "telemetry": [ + { + "values": { + "distance_average": 4507, + "distance_minimum": 4470, + "distance_maximum": 4540, + "distance_median": 4510, + "distance_10th_percentile": 4490, + "distance_25th_percentile": 4500, + "distance_75th_percentile": 4520, + "distance_90th_percentile": 4520, + "distance_most_frequent_value": 4500, + "number_of_samples": 100, + "total_acquisition_time": 399.4140625, + "battery_voltage": 2.749, + "lora_frame_counter": 10, + "lora_frame_port": 5, + "lora_frequency": 868100000, + "lora_spreading_factor": 11, + "lora_rssi": -48, + "lora_snr": 9 + } + } + ] +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/result_03.json b/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/result_03.json new file mode 100644 index 00000000..d6ef3005 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ChirpStack/uplink/result_03.json @@ -0,0 +1,21 @@ +{ + "deviceName": "4553", + "deviceType": "DL-LID", + "attributes": { + "lora_dev_eui": "0202020202020202", + "protocol_version": 2 + }, + "telemetry": [ + { + "values": { + "battery_voltage": 2.749, + "lora_frame_counter": 10, + "lora_frame_port": 5, + "lora_frequency": 868100000, + "lora_spreading_factor": 11, + "lora_rssi": -48, + "lora_snr": 9 + } + } + ] +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-LID/LORIOT/uplink/converter.json b/VENDORS/Decentlab/DL-LID/LORIOT/uplink/converter.json new file mode 100644 index 00000000..9d255c54 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/LORIOT/uplink/converter.json @@ -0,0 +1,19 @@ +{ + "name": "LORIOT Uplink Decoder for Decentlab DL-LID", + "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": "const 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-LID',\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/laser-distance-level-sensor-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 11,\n values: [{name: 'distance_average',\n displayName: 'Distance: average',\n convert: function (x) { return x[0]; },\n unit: 'mm'},\n {name: 'distance_minimum',\n displayName: 'Distance: minimum',\n convert: function (x) { return x[1]; },\n unit: 'mm'},\n {name: 'distance_maximum',\n displayName: 'Distance: maximum',\n convert: function (x) { return x[2]; },\n unit: 'mm'},\n {name: 'distance_median',\n displayName: 'Distance: median',\n convert: function (x) { return x[3]; },\n unit: 'mm'},\n {name: 'distance_10th_percentile',\n displayName: 'Distance: 10th percentile',\n convert: function (x) { return x[4]; },\n unit: 'mm'},\n {name: 'distance_25th_percentile',\n displayName: 'Distance: 25th percentile',\n convert: function (x) { return x[5]; },\n unit: 'mm'},\n {name: 'distance_75th_percentile',\n displayName: 'Distance: 75th percentile',\n convert: function (x) { return x[6]; },\n unit: 'mm'},\n {name: 'distance_90th_percentile',\n displayName: 'Distance: 90th percentile',\n convert: function (x) { return x[7]; },\n unit: 'mm'},\n {name: 'distance_most_frequent_value',\n displayName: 'Distance: most frequent value',\n convert: function (x) { return x[8]; },\n unit: 'mm'},\n {name: 'number_of_samples',\n displayName: 'Number of samples',\n convert: function (x) { return x[9]; }},\n {name: 'total_acquisition_time',\n displayName: 'Total acquisition time',\n convert: function (x) { return x[10] / 1.024; },\n unit: 'ms'}]},\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-LID/LORIOT/uplink/metadata.json b/VENDORS/Decentlab/DL-LID/LORIOT/uplink/metadata.json new file mode 100644 index 00000000..5c24f9c2 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/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-LID/LORIOT/uplink/payload.json b/VENDORS/Decentlab/DL-LID/LORIOT/uplink/payload.json new file mode 100644 index 00000000..39bef131 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/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": "0211c90003119b117611bc119e118a119411a811a81194006401990abd" +} diff --git a/VENDORS/Decentlab/DL-LID/LORIOT/uplink/payload_01.json b/VENDORS/Decentlab/DL-LID/LORIOT/uplink/payload_01.json new file mode 100644 index 00000000..c6a1ec25 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/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": "0211c900020abd" +} diff --git a/VENDORS/Decentlab/DL-LID/LORIOT/uplink/result.json b/VENDORS/Decentlab/DL-LID/LORIOT/uplink/result.json new file mode 100644 index 00000000..0b70981d --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/LORIOT/uplink/result.json @@ -0,0 +1,33 @@ +{ + "deviceName": "4553", + "deviceType": "DL-LID", + "attributes": { + "lora_dev_eui": "0004A30B001ADCB5", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1501064048945, + "values": { + "distance_average": 4507, + "distance_minimum": 4470, + "distance_maximum": 4540, + "distance_median": 4510, + "distance_10th_percentile": 4490, + "distance_25th_percentile": 4500, + "distance_75th_percentile": 4520, + "distance_90th_percentile": 4520, + "distance_most_frequent_value": 4500, + "number_of_samples": 100, + "total_acquisition_time": 399.4140625, + "battery_voltage": 2.749, + "lora_frame_counter": 121, + "lora_frame_port": 1, + "lora_frequency": 868300000, + "lora_snr": -15.8, + "lora_rssi": -119, + "lora_spreading_factor": 11 + } + } + ] +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-LID/LORIOT/uplink/result_01.json b/VENDORS/Decentlab/DL-LID/LORIOT/uplink/result_01.json new file mode 100644 index 00000000..a1ca0e3b --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/LORIOT/uplink/result_01.json @@ -0,0 +1,22 @@ +{ + "deviceName": "4553", + "deviceType": "DL-LID", + "attributes": { + "lora_dev_eui": "0004A30B001ADCB5", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1501064048945, + "values": { + "battery_voltage": 2.749, + "lora_frame_counter": 121, + "lora_frame_port": 1, + "lora_frequency": 868300000, + "lora_snr": -15.8, + "lora_rssi": -119, + "lora_spreading_factor": 11 + } + } + ] +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-LID/ThingPark/uplink/converter.json b/VENDORS/Decentlab/DL-LID/ThingPark/uplink/converter.json new file mode 100644 index 00000000..bc2b6adc --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ThingPark/uplink/converter.json @@ -0,0 +1,19 @@ +{ + "name": "ThingPark Uplink Decoder for Decentlab DL-LID", + "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": "const 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-LID',\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/laser-distance-level-sensor-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 11,\n values: [{name: 'distance_average',\n displayName: 'Distance: average',\n convert: function (x) { return x[0]; },\n unit: 'mm'},\n {name: 'distance_minimum',\n displayName: 'Distance: minimum',\n convert: function (x) { return x[1]; },\n unit: 'mm'},\n {name: 'distance_maximum',\n displayName: 'Distance: maximum',\n convert: function (x) { return x[2]; },\n unit: 'mm'},\n {name: 'distance_median',\n displayName: 'Distance: median',\n convert: function (x) { return x[3]; },\n unit: 'mm'},\n {name: 'distance_10th_percentile',\n displayName: 'Distance: 10th percentile',\n convert: function (x) { return x[4]; },\n unit: 'mm'},\n {name: 'distance_25th_percentile',\n displayName: 'Distance: 25th percentile',\n convert: function (x) { return x[5]; },\n unit: 'mm'},\n {name: 'distance_75th_percentile',\n displayName: 'Distance: 75th percentile',\n convert: function (x) { return x[6]; },\n unit: 'mm'},\n {name: 'distance_90th_percentile',\n displayName: 'Distance: 90th percentile',\n convert: function (x) { return x[7]; },\n unit: 'mm'},\n {name: 'distance_most_frequent_value',\n displayName: 'Distance: most frequent value',\n convert: function (x) { return x[8]; },\n unit: 'mm'},\n {name: 'number_of_samples',\n displayName: 'Number of samples',\n convert: function (x) { return x[9]; }},\n {name: 'total_acquisition_time',\n displayName: 'Total acquisition time',\n convert: function (x) { return x[10] / 1.024; },\n unit: 'ms'}]},\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-LID/ThingPark/uplink/metadata.json b/VENDORS/Decentlab/DL-LID/ThingPark/uplink/metadata.json new file mode 100644 index 00000000..d024aecf --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/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-LID/ThingPark/uplink/payload.json b/VENDORS/Decentlab/DL-LID/ThingPark/uplink/payload.json new file mode 100644 index 00000000..c555b268 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/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": "0211c90003119b117611bc119e118a119411a811a81194006401990abd", + "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-LID/ThingPark/uplink/payload_01.json b/VENDORS/Decentlab/DL-LID/ThingPark/uplink/payload_01.json new file mode 100644 index 00000000..d6240ef9 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/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": "0211c900020abd", + "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-LID/ThingPark/uplink/result.json b/VENDORS/Decentlab/DL-LID/ThingPark/uplink/result.json new file mode 100644 index 00000000..f8c4c065 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ThingPark/uplink/result.json @@ -0,0 +1,33 @@ +{ + "deviceName": "4553", + "deviceType": "DL-LID", + "attributes": { + "lora_dev_eui": "70B3D57BA000156B", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1732828102138, + "values": { + "distance_average": 4507, + "distance_minimum": 4470, + "distance_maximum": 4540, + "distance_median": 4510, + "distance_10th_percentile": 4490, + "distance_25th_percentile": 4500, + "distance_75th_percentile": 4520, + "distance_90th_percentile": 4520, + "distance_most_frequent_value": 4500, + "number_of_samples": 100, + "total_acquisition_time": 399.4140625, + "battery_voltage": 2.749, + "lora_frame_counter": 26, + "lora_frame_port": 1, + "lora_frequency": 902500000, + "lora_spreading_factor": 9, + "lora_rssi": -114, + "lora_snr": 4.75 + } + } + ] +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-LID/ThingPark/uplink/result_01.json b/VENDORS/Decentlab/DL-LID/ThingPark/uplink/result_01.json new file mode 100644 index 00000000..60f4cd7e --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ThingPark/uplink/result_01.json @@ -0,0 +1,22 @@ +{ + "deviceName": "4553", + "deviceType": "DL-LID", + "attributes": { + "lora_dev_eui": "70B3D57BA000156B", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1732828102138, + "values": { + "battery_voltage": 2.749, + "lora_frame_counter": 26, + "lora_frame_port": 1, + "lora_frequency": 902500000, + "lora_spreading_factor": 9, + "lora_rssi": -114, + "lora_snr": 4.75 + } + } + ] +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-LID/ThingParkEnterprise/uplink/converter.json b/VENDORS/Decentlab/DL-LID/ThingParkEnterprise/uplink/converter.json new file mode 100644 index 00000000..881094b1 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ThingParkEnterprise/uplink/converter.json @@ -0,0 +1,19 @@ +{ + "name": "ThingParkEnterprise Uplink Decoder for Decentlab DL-LID", + "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": "const 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-LID',\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/laser-distance-level-sensor-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 11,\n values: [{name: 'distance_average',\n displayName: 'Distance: average',\n convert: function (x) { return x[0]; },\n unit: 'mm'},\n {name: 'distance_minimum',\n displayName: 'Distance: minimum',\n convert: function (x) { return x[1]; },\n unit: 'mm'},\n {name: 'distance_maximum',\n displayName: 'Distance: maximum',\n convert: function (x) { return x[2]; },\n unit: 'mm'},\n {name: 'distance_median',\n displayName: 'Distance: median',\n convert: function (x) { return x[3]; },\n unit: 'mm'},\n {name: 'distance_10th_percentile',\n displayName: 'Distance: 10th percentile',\n convert: function (x) { return x[4]; },\n unit: 'mm'},\n {name: 'distance_25th_percentile',\n displayName: 'Distance: 25th percentile',\n convert: function (x) { return x[5]; },\n unit: 'mm'},\n {name: 'distance_75th_percentile',\n displayName: 'Distance: 75th percentile',\n convert: function (x) { return x[6]; },\n unit: 'mm'},\n {name: 'distance_90th_percentile',\n displayName: 'Distance: 90th percentile',\n convert: function (x) { return x[7]; },\n unit: 'mm'},\n {name: 'distance_most_frequent_value',\n displayName: 'Distance: most frequent value',\n convert: function (x) { return x[8]; },\n unit: 'mm'},\n {name: 'number_of_samples',\n displayName: 'Number of samples',\n convert: function (x) { return x[9]; }},\n {name: 'total_acquisition_time',\n displayName: 'Total acquisition time',\n convert: function (x) { return x[10] / 1.024; },\n unit: 'ms'}]},\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-LID/ThingParkEnterprise/uplink/metadata.json b/VENDORS/Decentlab/DL-LID/ThingParkEnterprise/uplink/metadata.json new file mode 100644 index 00000000..47c01690 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/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-LID/ThingParkEnterprise/uplink/payload.json b/VENDORS/Decentlab/DL-LID/ThingParkEnterprise/uplink/payload.json new file mode 100644 index 00000000..c555b268 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/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": "0211c90003119b117611bc119e118a119411a811a81194006401990abd", + "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-LID/ThingParkEnterprise/uplink/payload_01.json b/VENDORS/Decentlab/DL-LID/ThingParkEnterprise/uplink/payload_01.json new file mode 100644 index 00000000..d6240ef9 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/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": "0211c900020abd", + "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-LID/ThingParkEnterprise/uplink/result.json b/VENDORS/Decentlab/DL-LID/ThingParkEnterprise/uplink/result.json new file mode 100644 index 00000000..f8c4c065 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ThingParkEnterprise/uplink/result.json @@ -0,0 +1,33 @@ +{ + "deviceName": "4553", + "deviceType": "DL-LID", + "attributes": { + "lora_dev_eui": "70B3D57BA000156B", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1732828102138, + "values": { + "distance_average": 4507, + "distance_minimum": 4470, + "distance_maximum": 4540, + "distance_median": 4510, + "distance_10th_percentile": 4490, + "distance_25th_percentile": 4500, + "distance_75th_percentile": 4520, + "distance_90th_percentile": 4520, + "distance_most_frequent_value": 4500, + "number_of_samples": 100, + "total_acquisition_time": 399.4140625, + "battery_voltage": 2.749, + "lora_frame_counter": 26, + "lora_frame_port": 1, + "lora_frequency": 902500000, + "lora_spreading_factor": 9, + "lora_rssi": -114, + "lora_snr": 4.75 + } + } + ] +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-LID/ThingParkEnterprise/uplink/result_01.json b/VENDORS/Decentlab/DL-LID/ThingParkEnterprise/uplink/result_01.json new file mode 100644 index 00000000..60f4cd7e --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ThingParkEnterprise/uplink/result_01.json @@ -0,0 +1,22 @@ +{ + "deviceName": "4553", + "deviceType": "DL-LID", + "attributes": { + "lora_dev_eui": "70B3D57BA000156B", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1732828102138, + "values": { + "battery_voltage": 2.749, + "lora_frame_counter": 26, + "lora_frame_port": 1, + "lora_frequency": 902500000, + "lora_spreading_factor": 9, + "lora_rssi": -114, + "lora_snr": 4.75 + } + } + ] +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-LID/ThingsStackCommunity/uplink/converter.json b/VENDORS/Decentlab/DL-LID/ThingsStackCommunity/uplink/converter.json new file mode 100644 index 00000000..63626037 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ThingsStackCommunity/uplink/converter.json @@ -0,0 +1,19 @@ +{ + "name": "ThingsStackCommunity Uplink Decoder for Decentlab DL-LID", + "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": "const 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-LID',\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/laser-distance-level-sensor-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 11,\n values: [{name: 'distance_average',\n displayName: 'Distance: average',\n convert: function (x) { return x[0]; },\n unit: 'mm'},\n {name: 'distance_minimum',\n displayName: 'Distance: minimum',\n convert: function (x) { return x[1]; },\n unit: 'mm'},\n {name: 'distance_maximum',\n displayName: 'Distance: maximum',\n convert: function (x) { return x[2]; },\n unit: 'mm'},\n {name: 'distance_median',\n displayName: 'Distance: median',\n convert: function (x) { return x[3]; },\n unit: 'mm'},\n {name: 'distance_10th_percentile',\n displayName: 'Distance: 10th percentile',\n convert: function (x) { return x[4]; },\n unit: 'mm'},\n {name: 'distance_25th_percentile',\n displayName: 'Distance: 25th percentile',\n convert: function (x) { return x[5]; },\n unit: 'mm'},\n {name: 'distance_75th_percentile',\n displayName: 'Distance: 75th percentile',\n convert: function (x) { return x[6]; },\n unit: 'mm'},\n {name: 'distance_90th_percentile',\n displayName: 'Distance: 90th percentile',\n convert: function (x) { return x[7]; },\n unit: 'mm'},\n {name: 'distance_most_frequent_value',\n displayName: 'Distance: most frequent value',\n convert: function (x) { return x[8]; },\n unit: 'mm'},\n {name: 'number_of_samples',\n displayName: 'Number of samples',\n convert: function (x) { return x[9]; }},\n {name: 'total_acquisition_time',\n displayName: 'Total acquisition time',\n convert: function (x) { return x[10] / 1.024; },\n unit: 'ms'}]},\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-LID/ThingsStackCommunity/uplink/metadata.json b/VENDORS/Decentlab/DL-LID/ThingsStackCommunity/uplink/metadata.json new file mode 100644 index 00000000..11a4de83 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/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-LID/ThingsStackCommunity/uplink/payload.json b/VENDORS/Decentlab/DL-LID/ThingsStackCommunity/uplink/payload.json new file mode 100644 index 00000000..346d4bd9 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/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": "AhHJAAMRmxF2EbwRnhGKEZQRqBGoEZQAZAGZCr0=", + "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-LID/ThingsStackCommunity/uplink/payload_01.json b/VENDORS/Decentlab/DL-LID/ThingsStackCommunity/uplink/payload_01.json new file mode 100644 index 00000000..ed661e66 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/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": "AhHJAAIKvQ==", + "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-LID/ThingsStackCommunity/uplink/result.json b/VENDORS/Decentlab/DL-LID/ThingsStackCommunity/uplink/result.json new file mode 100644 index 00000000..32d4a3e1 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ThingsStackCommunity/uplink/result.json @@ -0,0 +1,33 @@ +{ + "deviceName": "4553", + "deviceType": "DL-LID", + "attributes": { + "lora_dev_eui": "70B3D57BA0000BCE", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1580982365447, + "values": { + "distance_average": 4507, + "distance_minimum": 4470, + "distance_maximum": 4540, + "distance_median": 4510, + "distance_10th_percentile": 4490, + "distance_25th_percentile": 4500, + "distance_75th_percentile": 4520, + "distance_90th_percentile": 4520, + "distance_most_frequent_value": 4500, + "number_of_samples": 100, + "total_acquisition_time": 399.4140625, + "battery_voltage": 2.749, + "lora_frame_counter": 101, + "lora_frame_port": 1, + "lora_frequency": 867700000, + "lora_spreading_factor": 11, + "lora_rssi": -16, + "lora_snr": 8.5 + } + } + ] +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-LID/ThingsStackCommunity/uplink/result_01.json b/VENDORS/Decentlab/DL-LID/ThingsStackCommunity/uplink/result_01.json new file mode 100644 index 00000000..4d5c0b0c --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ThingsStackCommunity/uplink/result_01.json @@ -0,0 +1,22 @@ +{ + "deviceName": "4553", + "deviceType": "DL-LID", + "attributes": { + "lora_dev_eui": "70B3D57BA0000BCE", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1580982365447, + "values": { + "battery_voltage": 2.749, + "lora_frame_counter": 101, + "lora_frame_port": 1, + "lora_frequency": 867700000, + "lora_spreading_factor": 11, + "lora_rssi": -16, + "lora_snr": 8.5 + } + } + ] +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-LID/ThingsStackIndustries/uplink/converter.json b/VENDORS/Decentlab/DL-LID/ThingsStackIndustries/uplink/converter.json new file mode 100644 index 00000000..2a38e60e --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ThingsStackIndustries/uplink/converter.json @@ -0,0 +1,19 @@ +{ + "name": "ThingsStackIndustries Uplink Decoder for Decentlab DL-LID", + "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": "const 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-LID',\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/laser-distance-level-sensor-for-lorawan */\n const decentlab_decoder = {\n PROTOCOL_VERSION: 2,\n SENSORS: [\n {length: 11,\n values: [{name: 'distance_average',\n displayName: 'Distance: average',\n convert: function (x) { return x[0]; },\n unit: 'mm'},\n {name: 'distance_minimum',\n displayName: 'Distance: minimum',\n convert: function (x) { return x[1]; },\n unit: 'mm'},\n {name: 'distance_maximum',\n displayName: 'Distance: maximum',\n convert: function (x) { return x[2]; },\n unit: 'mm'},\n {name: 'distance_median',\n displayName: 'Distance: median',\n convert: function (x) { return x[3]; },\n unit: 'mm'},\n {name: 'distance_10th_percentile',\n displayName: 'Distance: 10th percentile',\n convert: function (x) { return x[4]; },\n unit: 'mm'},\n {name: 'distance_25th_percentile',\n displayName: 'Distance: 25th percentile',\n convert: function (x) { return x[5]; },\n unit: 'mm'},\n {name: 'distance_75th_percentile',\n displayName: 'Distance: 75th percentile',\n convert: function (x) { return x[6]; },\n unit: 'mm'},\n {name: 'distance_90th_percentile',\n displayName: 'Distance: 90th percentile',\n convert: function (x) { return x[7]; },\n unit: 'mm'},\n {name: 'distance_most_frequent_value',\n displayName: 'Distance: most frequent value',\n convert: function (x) { return x[8]; },\n unit: 'mm'},\n {name: 'number_of_samples',\n displayName: 'Number of samples',\n convert: function (x) { return x[9]; }},\n {name: 'total_acquisition_time',\n displayName: 'Total acquisition time',\n convert: function (x) { return x[10] / 1.024; },\n unit: 'ms'}]},\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-LID/ThingsStackIndustries/uplink/metadata.json b/VENDORS/Decentlab/DL-LID/ThingsStackIndustries/uplink/metadata.json new file mode 100644 index 00000000..e304ca99 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/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-LID/ThingsStackIndustries/uplink/payload.json b/VENDORS/Decentlab/DL-LID/ThingsStackIndustries/uplink/payload.json new file mode 100644 index 00000000..346d4bd9 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/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": "AhHJAAMRmxF2EbwRnhGKEZQRqBGoEZQAZAGZCr0=", + "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-LID/ThingsStackIndustries/uplink/payload_01.json b/VENDORS/Decentlab/DL-LID/ThingsStackIndustries/uplink/payload_01.json new file mode 100644 index 00000000..ed661e66 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/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": "AhHJAAIKvQ==", + "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-LID/ThingsStackIndustries/uplink/result.json b/VENDORS/Decentlab/DL-LID/ThingsStackIndustries/uplink/result.json new file mode 100644 index 00000000..32d4a3e1 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ThingsStackIndustries/uplink/result.json @@ -0,0 +1,33 @@ +{ + "deviceName": "4553", + "deviceType": "DL-LID", + "attributes": { + "lora_dev_eui": "70B3D57BA0000BCE", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1580982365447, + "values": { + "distance_average": 4507, + "distance_minimum": 4470, + "distance_maximum": 4540, + "distance_median": 4510, + "distance_10th_percentile": 4490, + "distance_25th_percentile": 4500, + "distance_75th_percentile": 4520, + "distance_90th_percentile": 4520, + "distance_most_frequent_value": 4500, + "number_of_samples": 100, + "total_acquisition_time": 399.4140625, + "battery_voltage": 2.749, + "lora_frame_counter": 101, + "lora_frame_port": 1, + "lora_frequency": 867700000, + "lora_spreading_factor": 11, + "lora_rssi": -16, + "lora_snr": 8.5 + } + } + ] +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-LID/ThingsStackIndustries/uplink/result_01.json b/VENDORS/Decentlab/DL-LID/ThingsStackIndustries/uplink/result_01.json new file mode 100644 index 00000000..4d5c0b0c --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/ThingsStackIndustries/uplink/result_01.json @@ -0,0 +1,22 @@ +{ + "deviceName": "4553", + "deviceType": "DL-LID", + "attributes": { + "lora_dev_eui": "70B3D57BA0000BCE", + "protocol_version": 2 + }, + "telemetry": [ + { + "ts": 1580982365447, + "values": { + "battery_voltage": 2.749, + "lora_frame_counter": 101, + "lora_frame_port": 1, + "lora_frequency": 867700000, + "lora_spreading_factor": 11, + "lora_rssi": -16, + "lora_snr": 8.5 + } + } + ] +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-LID/info.json b/VENDORS/Decentlab/DL-LID/info.json new file mode 100644 index 00000000..da116313 --- /dev/null +++ b/VENDORS/Decentlab/DL-LID/info.json @@ -0,0 +1,4 @@ +{ + "url": "https://www.decentlab.com/products/laser-distance-level-sensor-for-lorawan", + "description": "The Decentlab DL-LID is equipped with a distance/level sensor for measuring distance. Suitable for generic ranging and proximity monitoring, snow level monitoring, water level monitoring, and flood monitoring." +} \ No newline at end of file diff --git a/VENDORS/Decentlab/DL-LID/photo.png b/VENDORS/Decentlab/DL-LID/photo.png new file mode 100644 index 00000000..d90c7479 Binary files /dev/null and b/VENDORS/Decentlab/DL-LID/photo.png differ diff --git a/VENDORS/Decentlab/info.json b/VENDORS/Decentlab/info.json new file mode 100644 index 00000000..4c650ee5 --- /dev/null +++ b/VENDORS/Decentlab/info.json @@ -0,0 +1,4 @@ +{ + "url": "https://www.decentlab.com", + "description": "Decentlab is a Swiss company providing wireless sensor devices and services for distributed, cost-effective monitoring solutions. Its sensor devices communicate wirelessly over LoRaWAN® and are designed for ultra low power consumption, capable of operating on batteries for several years. The devices are built for industrial applications and are ready to be deployed in any harsh indoor or outdoor environment. Application areas are environmental and air quality monitoring, hydrological measurements, smart agriculture and smart cities." +} diff --git a/VENDORS/Decentlab/logo.svg b/VENDORS/Decentlab/logo.svg new file mode 100644 index 00000000..effa28e4 --- /dev/null +++ b/VENDORS/Decentlab/logo.svg @@ -0,0 +1,71 @@ + +image/svg+xml \ No newline at end of file