diff --git a/VENDORS/Tektelic/Aura/ChirpStack/uplink/converter.json b/VENDORS/Tektelic/Aura/ChirpStack/uplink/converter.json new file mode 100644 index 00000000..3601cc1d --- /dev/null +++ b/VENDORS/Tektelic/Aura/ChirpStack/uplink/converter.json @@ -0,0 +1,39 @@ +{ + "name": "ChirpStack Uplink Decoder for Aura", + "type": "UPLINK", + "debugMode": false, + "debugSettings": { + "failuresEnabled": true, + "allEnabled": false, + "allEnabledUntil": 1733331880270 + }, + "configuration": { + "scriptLang": "TBEL", + "decoder": null, + "tbelDecoder": "var data = decodeToJson(payload);\nvar deviceName = data.deviceInfo.deviceName + \" \" + data.deviceInfo.devEui;\nvar deviceType = \"Aura\";\nvar groupName = null; // If groupName is not null - created device will be added to the entity group with such name.\nvar customerName = null; // If customerName is not null - created devices will be assigned to customer with such name. \n\n// use assetName and assetType instead of deviceName and deviceType\n// to automatically create assets instead of devices.\n// var assetName = 'Asset A';\n// var assetType = 'building';\n\n// If you want to parse incoming data somehow, you can add your code to this function.\n// input: bytes\n// expected output:\n// {\n// \"attributes\": {\"attributeKey\": \"attributeValue\"},\n// \"telemetry\": [{\"ts\": 1...1, \"values\": {\"telemetryKey\":\"telemetryValue\"}, {\"ts\": 1...2, \"values\": {\"telemetryKey\":\"telemetryValue\"}}]\n// }\n\nfunction decodePayload(input) {\n var output = {\n attributes: {},\n telemetry: []\n };\n \n // --- Decoding code --- //\n var decoded = {};\n var fPort = data.fPort;\n if(fPort == 10) {\n for(var i = 0; i < input.length - 2; ) {\n var key_1 = input[i++] & 0xff;\n var key_2 = input[i++] & 0xff;\n \n if(key_1 == 0x00 && key_2 == 0xFE) {\n decoded.energy_consumption_meter_elapsed = parseBytesToInt(input, i, 4);\n decoded.energy_consumption_meter_consumed = parseBytesToInt(input, i + 4, 4);\n i += 8;\n }\n else if(key_1 == 0x00 && key_2 == 0x00) {\n decoded.energy_consumption_meter_status = parseBytesToInt(input, i, 1);\n if(decoded.energy_consumption_meter_status != 0) {\n decoded.energy_consumption_meter_status != 1;\n }\n }\n else if(key_1 == 0x00 && key_2 == 0x74) {\n decoded.voltmeter = (input[i] << 8) | (input[i + 1] & 0xFF);\n if (decoded.voltmeter != 65535) {\n decoded.voltmeter = decoded.voltmeter * 10;\n }\n \n i += 2;\n }\n else if(key_1 == 0x00 && key_2 == 0x75) {\n decoded.voltmeter = (input[i] << 8) | (input[i + 1] & 0xFF);\n if (decoded.ammeter != 65535) {\n decoded.ammeter = decoded.ammeter * 10;\n }\n \n i += 2;\n }\n else if(key_1 == 0x00 && key_2 == 0x80) {\n decoded.real_power = (input[i] << 8) | (input[i + 1] & 0xFF);\n if (decoded.real_power != 65535) {\n decoded.real_power = decoded.real_power * 10;\n }\n \n i += 2;\n }\n else if(key_1 == 0x01 && key_2 == 0x80) {\n decoded.apparent_power = (input[i] << 8) | (input[i + 1] & 0xFF);\n if (decoded.apparent_power != 65535) {\n decoded.apparent_power = decoded.apparent_power * 10;\n }\n \n i += 2;\n }\n else if(key_1 == 0x00 && key_2 == 0x81) {\n decoded.power_factor_meter = input[i] & 0xFF;\n if (decoded.power_factor_meter != 65535) {\n decoded.power_factor_meter = decoded.power_factor_meter * 100;\n }\n \n i += 1;\n }\n else if(key_1 == 0x00 && key_2 == 0x01) {\n decoded.relay_status = input[i] & 0xFF;\n \n if (decoded.relay_status != 0) {\n decoded.relay_status = 1;\n }\n \n i += 1;\n }\n }\n }\n else if (fPort == 100) {\n for(var i = 0; i < input.length -1; ) {\n var key = input[i++] & 0xff;\n \n if(key == 0x00) {\n output.attributes.eui = bytesToHex(java.util.Arrays.copyRange(input, i, i + 8));\n i += 8;\n }\n else if(key == 0x01) {\n output.attributes.app_eui = bytesToHex(java.util.Arrays.copyRange(input, i, i + 8));\n\t\t\t\ti += 8;\n }\n else if (key == 0x02) {\n output.attributes.app_key = bytesToHex(java.util.Arrays.copyRange(input, i, i + 16));\n i += 16;\n }\n else if (key == 0x03) {\n output.attributes.devAddr = bytesToHex(java.util.Arrays.copyRange(input, i, i + 4));\n i += 4;\n }\n else if(key == 0x04) {\n output.attributes.network_session_key = bytesToHex(java.util.Arrays.copyRange(input, i, i + 16));\n i += 16;\n }\n else if(key == 0x05) {\n output.attributes.app_session_key = bytesToHex(java.util.Arrays.copyRange(input, i, i + 16));\n i += 16;\n }\n else if(key == 0x10) {\n output.attributes.loramac_join_mode = (input[i] >> 7) & 1;\n i += 2;\n }\n else if (key == 0x11) {\n var val = (input[i] >> 4) & 0x0F;\n \n if(val == 0x0C) {\n output.attributes.lora_class = \"Class C\";\n }\n else if(val == 0x0B) {\n output.attributes.lora_class = \"Class B\";\n }\n else if(val == 0x0A) {\n output.attributes.lora_class = \"Class F\";\n }\n else {\n output.attributes.lora_class = \"Invalid\";\n }\n\t\t\t\t\n\t\t\t\toutput.attributes.confirm_mode = (input[i] >> 8) & 1; \n\t\t\t\toutput.attributes.sync_word = (input[i] >> 9) & 1;\n\t\t\t\toutput.attributes.duty_cycle = (input[i] >> 10) & 1;\n\t\t\t\toutput.attributes.adr = (input[i] >> 11) & 1; \n\t\t\t\t\n\t\t\t\ti += 2;\n }\n else if (key == 0x12) {\n output.attributes.dr_number = (((input[i] << 8) | input[i + 1]) >> 8) & 0xF;\n output.attributes.tx_power_number = ((input[i] << 8) | input[i + 1]) & 0xF;\n \n i +=2;\n }\n else if (key == 0x13) {\n output.attributes.frequency = (((input[i] << 32) | (input[i + 1] << 24) | (input[i + 2] << 16) | (input[i + 3] << 8) | input[i + 4]) >> 8) & 0xFFFFFFFF;\n output.attributes.dr_number_rx2 = input[i + 4] & 0xFF;\n \n i += 5;\n }\n else if(key == 0x19) {\n output.attributes.netid_msb = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x20) {\n output.attributes.seconds_per_core_tick = parseBytesToInt(input, i, 4);\n i += 4;\n }\n else if(key == 0x21) {\n output.attributes.tick_per_battery = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x71) {\n output.attributes.app_major_version = input[i];\n\t\t\t\toutput.attributes.app_minor_version = input[i + 1];\n\t\t\t\toutput.attributes.app_revision = input[i + 2];\n\t\t\t\toutput.attributes.loramac_major_version = input[i + 3];\n\t\t\t\toutput.attributes.loramac_minor_version = input[i + 4];\n\t\t\t\toutput.attributes.loramac_revision = input[i + 5];\n\t\t\t\toutput.attributes.region = input[i + 7];\n\t\t\t\ti += 7;\n }\n }\n }\n\n output.telemetry = [{\n ts: timestamp,\n values: decoded\n }];\n // --- Decoding code --- //\n return output;\n}\n\n// --- attributes and telemetry objects ---\nvar telemetry = [];\nvar attributes = {};\n// --- attributes and telemetry objects ---\n\n// --- Timestamp parsing\nvar dateString = data.time;\ntimestamp = parseDateToTimestamp(dateString);\n// --- Timestamp parsing\n\n// Passing incoming bytes to decodePayload function, to get custom decoding\nvar customDecoding = decodePayload(base64ToBytes(data.data));\n\n\nattributes.eui = data.deviceInfo.devEui;\n\n// Collecting data to result\nif (customDecoding.?telemetry.size() > 0) {\n if (customDecoding.telemetry instanceof java.util.ArrayList) {\n foreach(telemetryObj: customDecoding.telemetry) {\n if (telemetryObj.ts != null && telemetryObj.values != null) {\n telemetry.add(telemetryObj);\n }\n }\n } else {\n telemetry.putAll(customDecoding.telemetry);\n }\n}\n\nif (customDecoding.?attributes.size() > 0) {\n attributes.putAll(customDecoding.attributes);\n}\n\n// You can add some keys manually to attributes or telemetry\nattributes.eui = data.deviceInfo.?devEui;\nattributes.devAddr = data.devAddr;\nattributes.fPort = data.fPort;\nattributes.applicationId = data.deviceInfo.?applicationId;\nattributes.applicationName = data.deviceInfo.?applicationName;\nattributes.tenantId = data.deviceInfo.?tenantId;\nattributes.tenantName = data.deviceInfo.?tenantName;\nattributes.deviceProfileId = data.deviceInfo.?deviceProfileId;\nattributes.deviceProfileName = data.deviceInfo.?deviceProfileName;\nattributes.frequency = data.txInfo.?frequency;\nattributes.bandwidth = data.txInfo.?modulation.?lora.?bandwidth;\nattributes.spreadingFactor = data.txInfo.?modulation.?lora.?spreadingFactor;\nattributes.codeRate = data.txInfo.?modulation.?lora.?codeRate;\n\nif(Boolean.parseBoolean(metadata[\"includeGatewayInfo\"])) {\n var gatewayInfo = getGatewayInfo();\n var addDataToTelemetry = {};\n addDataToTelemetry.snr = gatewayInfo.snr;\n addDataToTelemetry.rssi = gatewayInfo.rssi;\n addDataToTelemetry.channel = gatewayInfo.channel;\n addDataToTelemetry.rfChain = gatewayInfo.rfChain;\n addDataToTelemetry.fCnt = data.fCnt;\n \n telemetry = processTelemetryData(telemetry, addDataToTelemetry);\n}\n\nvar result = {\n deviceName: deviceName,\n deviceType: deviceType,\n // assetName: assetName,\n // assetType: assetType,\n attributes: attributes,\n telemetry: telemetry\n};\n\naddAdditionalInfoForDeviceMsg(result, customerName, groupName);\n\nreturn result;\n\nfunction addAdditionalInfoForDeviceMsg(deviceInfo, customerName, groupName) {\n if (customerName != null) {\n deviceInfo.customerName = customerName;\n }\n if (groupName != null) {\n deviceInfo.groupName = groupName;\n }\n}\n\nfunction parseDateToTimestamp(dateString) {\n var date = new Date(dateString);\n var timestamp = date.getTime();\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 getGatewayInfo() {\n var gatewayList = data.rxInfo;\n var maxRssi = Integer.MIN_VALUE;\n var gatewayInfo = {};\n \n foreach (gateway : gatewayList) {\n if(gateway.rssi > maxRssi) {\n maxRssi = gateway.rssi;\n gatewayInfo = gateway;\n }\n }\n \n return gatewayInfo;\n}\n\nfunction processTelemetryData(telemetry, addDataToTelemetry) {\n if (telemetry.size >= 1) {\n telemetry = addDataToTelemetries(telemetry, addDataToTelemetry);\n }\n else {\n telemetry.add(addDataToTelemetry);\n }\n \n return telemetry;\n}\n\nfunction addDataToTelemetries(telemetries, addDataToTelemetry) {\n foreach(telemetry : telemetries) {\n foreach(element : addDataToTelemetry.entrySet()) {\n if(!telemetry[\"values\"].keys.contains(element.key)) {\n telemetry[\"values\"][element.key] = element.value;\n }\n } \n }\n \n return telemetries;\n}", + "encoder": null, + "tbelEncoder": null, + "updateOnlyKeys": [ + "tenantId", + "tenantName", + "applicationId", + "applicationName", + "deviceProfileId", + "deviceProfileName", + "devAddr", + "fPort", + "frequency", + "bandwidth", + "spreadingFactor", + "codeRate", + "channel", + "rfChain", + "eui", + "battery" + ] + }, + "additionalInfo": { + "description": "" + }, + "edgeTemplate": false +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Aura/ChirpStack/uplink/metadata.json b/VENDORS/Tektelic/Aura/ChirpStack/uplink/metadata.json new file mode 100644 index 00000000..23f54b34 --- /dev/null +++ b/VENDORS/Tektelic/Aura/ChirpStack/uplink/metadata.json @@ -0,0 +1,4 @@ +{ + "integrationName": "ChirpStack integration", + "includeGatewayInfo": "false" +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Aura/ChirpStack/uplink/payload.json b/VENDORS/Tektelic/Aura/ChirpStack/uplink/payload.json new file mode 100644 index 00000000..1ac72155 --- /dev/null +++ b/VENDORS/Tektelic/Aura/ChirpStack/uplink/payload.json @@ -0,0 +1,48 @@ +{ + "deduplicationId": "57433366-50a6-4dc2-8145-2df1bbc70d9e", + "time": "2023-05-22T07:47:05.404859+00:00", + "deviceInfo": { + "tenantId": "52f14cd4-c6f1-4fbd-8f87-4025e1d49242", + "tenantName": "ChirpStack", + "applicationId": "ca739e26-7b67-4f14-b69e-d568c22a5a75", + "applicationName": "Chirpstack application", + "deviceProfileId": "605d08d4-65f5-4d2c-8a5a-3d2457662f79", + "deviceProfileName": "Chirpstack default device profile", + "deviceName": "Device name", + "devEui": "1000000000000001", + "tags": {} + }, + "devAddr": "20000001", + "adr": true, + "dr": 5, + "fCnt": 4, + "fPort": 10, + "confirmed": false, + "data": "AP4AAVGAAABqUAAAAA==", + "rxInfo": [{ + "gatewayId": "6a7e111a10000000", + "uplinkId": 24022, + "time": "2023-05-22T07:47:05.404859+00:00", + "rssi": -35, + "snr": 11.5, + "channel": 2, + "rfChain": 1, + "location": {}, + "context": "EFwMtA==", + "metadata": { + "region_common_name": "EU868", + "region_config_id": "eu868" + }, + "crcStatus": "CRC_OK" + }], + "txInfo": { + "frequency": 868500000, + "modulation": { + "lora": { + "bandwidth": 125000, + "spreadingFactor": 7, + "codeRate": "CR_4_5" + } + } + } +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Aura/ChirpStack/uplink/result.json b/VENDORS/Tektelic/Aura/ChirpStack/uplink/result.json new file mode 100644 index 00000000..d723b7d5 --- /dev/null +++ b/VENDORS/Tektelic/Aura/ChirpStack/uplink/result.json @@ -0,0 +1,27 @@ +{ + "deviceName": "Device name 1000000000000001", + "deviceType": "Aura", + "attributes": { + "eui": "1000000000000001", + "devAddr": "20000001", + "fPort": 10, + "applicationId": "ca739e26-7b67-4f14-b69e-d568c22a5a75", + "applicationName": "Chirpstack application", + "tenantId": "52f14cd4-c6f1-4fbd-8f87-4025e1d49242", + "tenantName": "ChirpStack", + "deviceProfileId": "605d08d4-65f5-4d2c-8a5a-3d2457662f79", + "deviceProfileName": "Chirpstack default device profile", + "frequency": 868500000, + "bandwidth": 125000, + "spreadingFactor": 7, + "codeRate": "CR_4_5" + }, + "telemetry": [{ + "ts": 1684741625404, + "values": { + "energy_consumption_meter_elapsed": 86400, + "energy_consumption_meter_consumed": 27216, + "energy_consumption_meter_status": 0 + } + }] +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Aura/LORIOT/uplink/converter.json b/VENDORS/Tektelic/Aura/LORIOT/uplink/converter.json new file mode 100644 index 00000000..7de9b7ad --- /dev/null +++ b/VENDORS/Tektelic/Aura/LORIOT/uplink/converter.json @@ -0,0 +1,29 @@ +{ + "name": "Loriot Uplink Decoder for Aura", + "type": "UPLINK", + "debugMode": false, + "debugSettings": { + "failuresEnabled": true, + "allEnabled": false, + "allEnabledUntil": 1733331880270 + }, + "configuration": { + "scriptLang": "TBEL", + "decoder": "// Decode an uplink message from a buffer\n// payload - array of bytes\n// metadata - key/value object\n\n/** Decoder **/\n\n// decode payload to string\nvar payloadStr = decodeToString(payload);\n\n// decode payload to JSON\n// var data = decodeToJson(payload);\n\nvar deviceName = 'Device A';\nvar deviceType = 'thermostat';\nvar customerName = 'Customer C';\nvar groupName = 'thermostat devices';\nvar manufacturer = 'Example corporation';\n// use assetName and assetType instead of deviceName and deviceType\n// to automatically create assets instead of devices.\n// var assetName = 'Asset A';\n// var assetType = 'building';\n\n// Result object with device/asset attributes/telemetry data\nvar result = {\n// Use deviceName and deviceType or assetName and assetType, but not both.\n deviceName: deviceName,\n deviceType: deviceType,\n// assetName: assetName,\n// assetType: assetType,\n// customerName: customerName,\n groupName: groupName,\n attributes: {\n model: 'Model A',\n serialNumber: 'SN111',\n integrationName: metadata['integrationName'],\n manufacturer: manufacturer\n },\n telemetry: {\n temperature: 42,\n humidity: 80,\n rawData: payloadStr\n }\n};\n\n/** Helper functions **/\n\nfunction decodeToString(payload) {\n return String.fromCharCode.apply(String, payload);\n}\n\nfunction decodeToJson(payload) {\n // covert payload to string.\n var str = decodeToString(payload);\n\n // parse string to JSON\n var data = JSON.parse(str);\n return data;\n}\n\nreturn result;", + "tbelDecoder": "var data = decodeToJson(payload);\nvar deviceName = data.EUI;\nvar deviceType = \"Aura\";\nvar groupName = null; // If groupName is not null - created device will be added to the entity group with such name.\nvar customerName = null; // If customerName is not null - created devices will be assigned to customer with such name. \n\n// use assetName and assetType instead of deviceName and deviceType\n// to automatically create assets instead of devices.\n// var assetName = 'Asset A';\n// var assetType = 'building';\n\n// If you want to parse incoming data somehow, you can add your code to this function.\n// input: bytes\n// expected output:\n// {\n// \"attributes\": {\"attributeKey\": \"attributeValue\"},\n// \"telemetry\": {\"telemetryKey\": \"telemetryValue\"}\n// }\n\nfunction decodePayload(input) {\n var output = { attributes: {}, telemetry: []};\n \n var decoded = {};\n var fPort = data.port;\n if(fPort == 10) {\n for(var i = 0; i < input.length - 2; ) {\n var key_1 = input[i++] & 0xff;\n var key_2 = input[i++] & 0xff;\n \n if(key_1 == 0x00 && key_2 == 0xFE) {\n decoded.energy_consumption_meter_elapsed = parseBytesToInt(input, i, 4);\n decoded.energy_consumption_meter_consumed = parseBytesToInt(input, i + 4, 4);\n i += 8;\n }\n else if(key_1 == 0x00 && key_2 == 0x00) {\n decoded.energy_consumption_meter_status = parseBytesToInt(input, i, 1);\n if(decoded.energy_consumption_meter_status != 0) {\n decoded.energy_consumption_meter_status != 1;\n }\n }\n else if(key_1 == 0x00 && key_2 == 0x74) {\n decoded.voltmeter = (input[i] << 8) | (input[i + 1] & 0xFF);\n if (decoded.voltmeter != 65535) {\n decoded.voltmeter = decoded.voltmeter * 10;\n }\n \n i += 2;\n }\n else if(key_1 == 0x00 && key_2 == 0x75) {\n decoded.voltmeter = (input[i] << 8) | (input[i + 1] & 0xFF);\n if (decoded.ammeter != 65535) {\n decoded.ammeter = decoded.ammeter * 10;\n }\n \n i += 2;\n }\n else if(key_1 == 0x00 && key_2 == 0x80) {\n decoded.real_power = (input[i] << 8) | (input[i + 1] & 0xFF);\n if (decoded.real_power != 65535) {\n decoded.real_power = decoded.real_power * 10;\n }\n \n i += 2;\n }\n else if(key_1 == 0x01 && key_2 == 0x80) {\n decoded.apparent_power = (input[i] << 8) | (input[i + 1] & 0xFF);\n if (decoded.apparent_power != 65535) {\n decoded.apparent_power = decoded.apparent_power * 10;\n }\n \n i += 2;\n }\n else if(key_1 == 0x00 && key_2 == 0x81) {\n decoded.power_factor_meter = input[i] & 0xFF;\n if (decoded.power_factor_meter != 65535) {\n decoded.power_factor_meter = decoded.power_factor_meter * 100;\n }\n \n i += 1;\n }\n else if(key_1 == 0x00 && key_2 == 0x01) {\n decoded.relay_status = input[i] & 0xFF;\n \n if (decoded.relay_status != 0) {\n decoded.relay_status = 1;\n }\n \n i += 1;\n }\n }\n }\n else if (fPort == 100) {\n for(var i = 0; i < input.length -1; ) {\n var key = input[i++] & 0xff;\n \n if(key == 0x00) {\n output.attributes.eui = bytesToHex(java.util.Arrays.copyRange(input, i, i + 8));\n i += 8;\n }\n else if(key == 0x01) {\n output.attributes.app_eui = bytesToHex(java.util.Arrays.copyRange(input, i, i + 8));\n\t\t\t\ti += 8;\n }\n else if (key == 0x02) {\n output.attributes.app_key = bytesToHex(java.util.Arrays.copyRange(input, i, i + 16));\n i += 16;\n }\n else if (key == 0x03) {\n output.attributes.devAddr = bytesToHex(java.util.Arrays.copyRange(input, i, i + 4));\n i += 4;\n }\n else if(key == 0x04) {\n output.attributes.network_session_key = bytesToHex(java.util.Arrays.copyRange(input, i, i + 16));\n i += 16;\n }\n else if(key == 0x05) {\n output.attributes.app_session_key = bytesToHex(java.util.Arrays.copyRange(input, i, i + 16));\n i += 16;\n }\n else if(key == 0x10) {\n output.attributes.loramac_join_mode = (input[i] >> 7) & 1;\n i += 2;\n }\n else if (key == 0x11) {\n var val = (input[i] >> 4) & 0x0F;\n \n if(val == 0x0C) {\n output.attributes.lora_class = \"Class C\";\n }\n else if(val == 0x0B) {\n output.attributes.lora_class = \"Class B\";\n }\n else if(val == 0x0A) {\n output.attributes.lora_class = \"Class F\";\n }\n else {\n output.attributes.lora_class = \"Invalid\";\n }\n\t\t\t\t\n\t\t\t\toutput.attributes.confirm_mode = (input[i] >> 8) & 1; \n\t\t\t\toutput.attributes.sync_word = (input[i] >> 9) & 1;\n\t\t\t\toutput.attributes.duty_cycle = (input[i] >> 10) & 1;\n\t\t\t\toutput.attributes.adr = (input[i] >> 11) & 1; \n\t\t\t\t\n\t\t\t\ti += 2;\n }\n else if (key == 0x12) {\n output.attributes.dr_number = (((input[i] << 8) | input[i + 1]) >> 8) & 0xF;\n output.attributes.tx_power_number = ((input[i] << 8) | input[i + 1]) & 0xF;\n \n i +=2;\n }\n else if (key == 0x13) {\n output.attributes.frequency = (((input[i] << 32) | (input[i + 1] << 24) | (input[i + 2] << 16) | (input[i + 3] << 8) | input[i + 4]) >> 8) & 0xFFFFFFFF;\n output.attributes.dr_number_rx2 = input[i + 4] & 0xFF;\n \n i += 5;\n }\n else if(key == 0x19) {\n output.attributes.netid_msb = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x20) {\n output.attributes.seconds_per_core_tick = parseBytesToInt(input, i, 4);\n i += 4;\n }\n else if(key == 0x21) {\n output.attributes.tick_per_battery = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x71) {\n output.attributes.app_major_version = input[i];\n\t\t\t\toutput.attributes.app_minor_version = input[i + 1];\n\t\t\t\toutput.attributes.app_revision = input[i + 2];\n\t\t\t\toutput.attributes.loramac_major_version = input[i + 3];\n\t\t\t\toutput.attributes.loramac_minor_version = input[i + 4];\n\t\t\t\toutput.attributes.loramac_revision = input[i + 5];\n\t\t\t\toutput.attributes.region = input[i + 7];\n\t\t\t\ti += 7;\n }\n }\n }\n \n output.telemetry = [{\n ts: timestamp,\n values: decoded\n }];\n\n // --- Decoding code --- //\n return output;\n}\n\n// --- attributes and telemetry objects ---\nvar telemetry = [];\nvar attributes = {};\n// --- attributes and telemetry objects ---\n\n// --- Timestamp parsing\ntimestamp = data.ts;\n// --- Timestamp parsing\n\n// Message parsing\n// To avoid paths in the decoded objects we passing false value to function as \"pathInKey\" argument.\n// Warning: pathInKey can cause already found fields to be overwritten with the last value found.\n\nvar uplinkDataList = [];\n\n// Passing incoming bytes to decodePayload function, to get custom decoding\nvar customDecoding = decodePayload(hexToBytes(data.data));\n\n// Collecting data to result\nif (customDecoding.?telemetry.size() > 0) {\n if (customDecoding.telemetry instanceof java.util.ArrayList) {\n foreach(telemetryObj: customDecoding.telemetry) {\n if (telemetryObj.ts != null && telemetryObj.values != null) {\n telemetry.add(telemetryObj);\n }\n }\n } else {\n telemetry.putAll(customDecoding.telemetry);\n }\n}\n\nif (customDecoding.?attributes.size() > 0) {\n attributes.putAll(customDecoding.attributes);\n}\n\n// You can add some keys manually to attributes or telemetry\nattributes.eui = data.EUI;\nattributes.fPort = data.port;\nattributes.frequency = data.freq;\n\nvar isIncludeGatewayInfo = metadata[\"includeGatewayInfo\"];\nif(isIncludeGatewayInfo == true) {\n var addDataToTelemetry = {};\n addDataToTelemetry.rssi = data.rssi;\n addDataToTelemetry.seqno = data.seqno;\n addDataToTelemetry.snr = data.snr;\n addDataToTelemetry.ack = data.ack;\n addDataToTelemetry.toa = data.toa;\n addDataToTelemetry.fCnt = data.fcnt;\n \n telemetry = processTelemetryData(telemetry, addDataToTelemetry);\n}\n\nvar deviceInfo = {\n deviceName: deviceName,\n deviceType: deviceType,\n// assetName: assetName,\n// assetType: assetType,\n attributes: attributes,\n telemetry: telemetry, \n};\n\naddAdditionalInfoForDeviceMsg(deviceInfo, customerName, groupName);\n\nuplinkDataList.add(deviceInfo);\n\nvar gatewayDeviceNamePrefix = \"Gateway \";\nvar gatewayDeviceType = \"Lora gateway\";\nvar gatewayGroupName = null; // If gatewayGroupName is not null - created device will be added to the entity group with such name.\n\nif (data.cmd == \"gw\") {\n foreach( gatewayInfo : data.gws ) {\n var addGatewayInfo = {};\n\n // You can add some keys manually telemetry\n addGatewayInfo.rssi = gatewayInfo.rssi;\n addGatewayInfo.snr = gatewayInfo.snr;\n // You can add some keys manually telemetry\n \n var gatewayInfoMsg = {\n deviceName: gatewayDeviceNamePrefix + gatewayInfo.gweui,\n deviceType: gatewayDeviceType,\n telemetry: [{\n \"ts\": gatewayInfo.ts,\n \"values\": addGatewayInfo\n }],\n attributes: {\n eui: gatewayInfo.gweui\n }\n };\n addAdditionalInfoForDeviceMsg(gatewayInfoMsg, customerName, gatewayGroupName);\n uplinkDataList.add(gatewayInfoMsg);\n }\n}\n\nreturn uplinkDataList;\n\nfunction addAdditionalInfoForDeviceMsg(deviceInfo, customerName, groupName) {\n if (customerName != null) {\n deviceInfo.customerName = customerName;\n }\n if (groupName != null) {\n deviceInfo.groupName = groupName;\n }\n}\n\nfunction processTelemetryData(telemetry, addDataToTelemetry) {\n if (telemetry.size >= 1) {\n telemetry = addDataToTelemetries(telemetry, addDataToTelemetry);\n }\n else {\n telemetry.add(addDataToTelemetry);\n }\n \n return telemetry;\n}\n\nfunction addDataToTelemetries(telemetries, addDataToTelemetry) {\n foreach(telemetry : telemetries) {\n foreach(element : addDataToTelemetry.entrySet()) {\n if(!telemetry[\"values\"].keys.contains(element.key)) {\n telemetry[\"values\"][element.key] = element.value;\n }\n } \n }\n \n return telemetries;\n}", + "encoder": null, + "tbelEncoder": null, + "updateOnlyKeys": [ + "fPort", + "ack", + "eui", + "frequency", + "dr", + "battery" + ] + }, + "additionalInfo": { + "description": "" + }, + "edgeTemplate": false +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Aura/LORIOT/uplink/metadata.json b/VENDORS/Tektelic/Aura/LORIOT/uplink/metadata.json new file mode 100644 index 00000000..ae2ee743 --- /dev/null +++ b/VENDORS/Tektelic/Aura/LORIOT/uplink/metadata.json @@ -0,0 +1,4 @@ +{ + "integrationName": "Loriot integration", + "includeGatewayInfo": "false" +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Aura/LORIOT/uplink/payload.json b/VENDORS/Tektelic/Aura/LORIOT/uplink/payload.json new file mode 100644 index 00000000..0b10cef0 --- /dev/null +++ b/VENDORS/Tektelic/Aura/LORIOT/uplink/payload.json @@ -0,0 +1,17 @@ +{ + "cmd": "rx", + "seqno": 3040, + "EUI": "1000000000000001", + "ts": 1684478801936, + "fcnt": 2, + "port": 10, + "freq": 867500000, + "rssi": -21, + "snr": 10, + "toa": 206, + "dr": "SF9 BW125 4/5", + "ack": false, + "bat": 94, + "offline": false, + "data": "00FE0001518000006A50000000" +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Aura/LORIOT/uplink/result.json b/VENDORS/Tektelic/Aura/LORIOT/uplink/result.json new file mode 100644 index 00000000..c0fbf70e --- /dev/null +++ b/VENDORS/Tektelic/Aura/LORIOT/uplink/result.json @@ -0,0 +1,17 @@ +[{ + "deviceName": "1000000000000001", + "deviceType": "Aura", + "attributes": { + "eui": "1000000000000001", + "fPort": 10, + "frequency": 867500000 + }, + "telemetry": [{ + "ts": 1684478801936, + "values": { + "energy_consumption_meter_elapsed": 86400, + "energy_consumption_meter_consumed": 27216, + "energy_consumption_meter_status": 0 + } + }] +}] \ No newline at end of file diff --git a/VENDORS/Tektelic/Aura/ThingsStackCommunity/uplink/converter.json b/VENDORS/Tektelic/Aura/ThingsStackCommunity/uplink/converter.json new file mode 100644 index 00000000..c5ea9ae3 --- /dev/null +++ b/VENDORS/Tektelic/Aura/ThingsStackCommunity/uplink/converter.json @@ -0,0 +1,39 @@ +{ + "name": "The Things Stack Community Uplink Decoder for Aura", + "type": "UPLINK", + "debugMode": false, + "debugSettings": { + "failuresEnabled": true, + "allEnabled": false, + "allEnabledUntil": 1733331880270 + }, + "configuration": { + "scriptLang": "TBEL", + "decoder": null, + "tbelDecoder": "var data = decodeToJson(payload);\n\nvar deviceName = data.end_device_ids.device_id;\nvar deviceType = \"Aura\";\nvar groupName = null; // If groupName is not null - created device will be added to the entity group with such name.\nvar customerName = null; // If customerName is not null - created devices will be assigned to customer with such name. \n\n// use assetName and assetType instead of deviceName and deviceType\n// to automatically create assets instead of devices.\n// var assetName = 'Asset A';\n// var assetType = 'building';\n\n// If you want to parse incoming data somehow, you can add your code to this function.\n// input: bytes\n// expected output:\n// {\n// \"attributes\": {\"attributeKey\": \"attributeValue\"},\n// \"telemetry\": [{\"ts\": 1...1, \"values\": {\"telemetryKey\":\"telemetryValue\"}, {\"ts\": 1...2, \"values\": {\"telemetryKey\":\"telemetryValue\"}}]\n// }\n\nfunction decodeFrmPayload(input) {\n var output = {\n attributes: {}, telemetry: {}\n };\n \n // --- Decoding code --- //\n var decoded = {};\n var fPort = data.uplink_message.f_port;\n if(fPort == 10) {\n for(var i = 0; i < input.length - 2; ) {\n var key_1 = input[i++] & 0xff;\n var key_2 = input[i++] & 0xff;\n \n if(key_1 == 0x00 && key_2 == 0xFE) {\n decoded.energy_consumption_meter_elapsed = parseBytesToInt(input, i, 4);\n decoded.energy_consumption_meter_consumed = parseBytesToInt(input, i + 4, 4);\n i += 8;\n }\n else if(key_1 == 0x00 && key_2 == 0x00) {\n decoded.energy_consumption_meter_status = parseBytesToInt(input, i, 1);\n if(decoded.energy_consumption_meter_status != 0) {\n decoded.energy_consumption_meter_status != 1;\n }\n }\n else if(key_1 == 0x00 && key_2 == 0x74) {\n decoded.voltmeter = (input[i] << 8) | (input[i + 1] & 0xFF);\n if (decoded.voltmeter != 65535) {\n decoded.voltmeter = decoded.voltmeter * 10;\n }\n \n i += 2;\n }\n else if(key_1 == 0x00 && key_2 == 0x75) {\n decoded.voltmeter = (input[i] << 8) | (input[i + 1] & 0xFF);\n if (decoded.ammeter != 65535) {\n decoded.ammeter = decoded.ammeter * 10;\n }\n \n i += 2;\n }\n else if(key_1 == 0x00 && key_2 == 0x80) {\n decoded.real_power = (input[i] << 8) | (input[i + 1] & 0xFF);\n if (decoded.real_power != 65535) {\n decoded.real_power = decoded.real_power * 10;\n }\n \n i += 2;\n }\n else if(key_1 == 0x01 && key_2 == 0x80) {\n decoded.apparent_power = (input[i] << 8) | (input[i + 1] & 0xFF);\n if (decoded.apparent_power != 65535) {\n decoded.apparent_power = decoded.apparent_power * 10;\n }\n \n i += 2;\n }\n else if(key_1 == 0x00 && key_2 == 0x81) {\n decoded.power_factor_meter = input[i] & 0xFF;\n if (decoded.power_factor_meter != 65535) {\n decoded.power_factor_meter = decoded.power_factor_meter * 100;\n }\n \n i += 1;\n }\n else if(key_1 == 0x00 && key_2 == 0x01) {\n decoded.relay_status = input[i] & 0xFF;\n \n if (decoded.relay_status != 0) {\n decoded.relay_status = 1;\n }\n \n i += 1;\n }\n }\n }\n else if (fPort == 100) {\n for(var i = 0; i < input.length -1; ) {\n var key = input[i++] & 0xff;\n \n if(key == 0x00) {\n output.attributes.eui = bytesToHex(java.util.Arrays.copyRange(input, i, i + 8));\n i += 8;\n }\n else if(key == 0x01) {\n output.attributes.app_eui = bytesToHex(java.util.Arrays.copyRange(input, i, i + 8));\n\t\t\t\ti += 8;\n }\n else if (key == 0x02) {\n output.attributes.app_key = bytesToHex(java.util.Arrays.copyRange(input, i, i + 16));\n i += 16;\n }\n else if (key == 0x03) {\n output.attributes.devAddr = bytesToHex(java.util.Arrays.copyRange(input, i, i + 4));\n i += 4;\n }\n else if(key == 0x04) {\n output.attributes.network_session_key = bytesToHex(java.util.Arrays.copyRange(input, i, i + 16));\n i += 16;\n }\n else if(key == 0x05) {\n output.attributes.app_session_key = bytesToHex(java.util.Arrays.copyRange(input, i, i + 16));\n i += 16;\n }\n else if(key == 0x10) {\n output.attributes.loramac_join_mode = (input[i] >> 7) & 1;\n i += 2;\n }\n else if (key == 0x11) {\n var val = (input[i] >> 4) & 0x0F;\n \n if(val == 0x0C) {\n output.attributes.lora_class = \"Class C\";\n }\n else if(val == 0x0B) {\n output.attributes.lora_class = \"Class B\";\n }\n else if(val == 0x0A) {\n output.attributes.lora_class = \"Class F\";\n }\n else {\n output.attributes.lora_class = \"Invalid\";\n }\n\t\t\t\t\n\t\t\t\toutput.attributes.confirm_mode = (input[i] >> 8) & 1; \n\t\t\t\toutput.attributes.sync_word = (input[i] >> 9) & 1;\n\t\t\t\toutput.attributes.duty_cycle = (input[i] >> 10) & 1;\n\t\t\t\toutput.attributes.adr = (input[i] >> 11) & 1; \n\t\t\t\t\n\t\t\t\ti += 2;\n }\n else if (key == 0x12) {\n output.attributes.dr_number = (((input[i] << 8) | input[i + 1]) >> 8) & 0xF;\n output.attributes.tx_power_number = ((input[i] << 8) | input[i + 1]) & 0xF;\n \n i +=2;\n }\n else if (key == 0x13) {\n output.attributes.frequency = (((input[i] << 32) | (input[i + 1] << 24) | (input[i + 2] << 16) | (input[i + 3] << 8) | input[i + 4]) >> 8) & 0xFFFFFFFF;\n output.attributes.dr_number_rx2 = input[i + 4] & 0xFF;\n \n i += 5;\n }\n else if(key == 0x19) {\n output.attributes.netid_msb = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x20) {\n output.attributes.seconds_per_core_tick = parseBytesToInt(input, i, 4);\n i += 4;\n }\n else if(key == 0x21) {\n output.attributes.tick_per_battery = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x71) {\n output.attributes.app_major_version = input[i];\n\t\t\t\toutput.attributes.app_minor_version = input[i + 1];\n\t\t\t\toutput.attributes.app_revision = input[i + 2];\n\t\t\t\toutput.attributes.loramac_major_version = input[i + 3];\n\t\t\t\toutput.attributes.loramac_minor_version = input[i + 4];\n\t\t\t\toutput.attributes.loramac_revision = input[i + 5];\n\t\t\t\toutput.attributes.region = input[i + 7];\n\t\t\t\ti += 7;\n }\n }\n }\n \n output.telemetry = [{\n ts: timestamp,\n values: decoded\n }];\n \n // --- Decoding code --- //\n return output;\n}\n\n\n// --- attributes and telemetry objects ---\nvar telemetry = [];\nvar attributes = {};\n// --- attributes and telemetry objects ---\n\n// --- Timestamp parsing\nvar dateString = data.uplink_message.received_at;\n// If data is simulated or device doesn't send his own date string - we will use date from upcoming message, set by network server\nif ((data.simulated != null && data.simulated) || dateString == null) {\n dateString = data.received_at;\n}\ntimestamp = parseDateToTimestamp(dateString);\n// --- Timestamp parsing\n\n// Message parsing\n// To avoid paths in the decoded objects we passing false value to function as \"pathInKey\" argument.\n// Warning: pathInKey can cause already found fields to be overwritten with the last value found, e.g. receive_at from uplink_message will be written receive_at in the root.\n\n// Passing incoming bytes to decodeFrmPayload function, to get custom decoding\nvar customDecoding = {};\nif (data.uplink_message.get(\"frm_payload\") != null) {\n customDecoding = decodeFrmPayload(base64ToBytes(data.uplink_message.frm_payload));\n}\n\n// Collecting data to result\nif (customDecoding.?telemetry.size() > 0) {\n if (customDecoding.telemetry instanceof java.util.ArrayList) {\n foreach(telemetryObj: customDecoding.telemetry) {\n if (telemetryObj.ts != null && telemetryObj.values != null) {\n telemetry.add(telemetryObj);\n }\n }\n } else {\n telemetry.putAll(customDecoding.telemetry);\n }\n}\n\nif (customDecoding.?attributes.size() > 0) {\n attributes.putAll(customDecoding.attributes);\n}\n\n// You can add some keys manually to attributes or telemetry\nvar applicationId = data.end_device_ids.?application_ids.?application_id;\nvar devAddr = data.end_device_ids.?dev_addr;\nvar spreadingFactor = data.uplink_message.?settings.?data_rate.?lora.?spreading_factor;\nvar codeRate = data.uplink_message.?settings.?data_rate.?lora.?coding_rate;\nvar tenantId = data.uplink_message.?network_ids.?tenant_id;\nattributes.eui = data.end_device_ids.dev_eui;\nattributes.fPort = data.uplink_message.f_port;\nattributes.applicationId = applicationId;\nattributes.devAddr = devAddr;\nattributes.spreadingFactor = spreadingFactor;\nattributes.codeRate = codeRate;\nattributes.tenantId = tenantId;\nattributes.device_id = data.end_device_ids.?device_id;\nattributes.join_eui = data.end_device_ids.?join_eui;\nattributes.net_id = data.uplink_message.?network_ids.?net_id;\nattributes.cluster_id = data.uplink_message.?network_ids.?cluster_id;\nattributes.cluster_adress = data.uplink_message.?network_ids.?cluster_address;\nattributes.bandwidth = data.uplink_message.?settings.?data_rate.?lora.?bandwidth;\nattributes.frequency = data.uplink_message.?settings.?frequency;\n\n\nvar gatewayInfo = getGatewayInfo();\nvar addDataToTelemetry = {};\naddDataToTelemetry.snr = gatewayInfo.snr;\naddDataToTelemetry.rssi = gatewayInfo.rssi;\naddDataToTelemetry.channel = gatewayInfo.channel_index;\naddDataToTelemetry.consumed_airtime = data.uplink_message.?consumed_airtime;\naddDataToTelemetry.fCnt = data.uplink_message.?f_cnt;\n\nvar isIncludeGatewayInfo = metadata[\"includeGatewayInfo\"];\nif(isIncludeGatewayInfo == true) {\n telemetry = processTelemetryData(telemetry, addDataToTelemetry);\n}\n\nvar result = {\n deviceName: deviceName,\n deviceType: deviceType,\n// assetName: assetName,\n// assetType: assetType,\n attributes: attributes,\n telemetry: telemetry\n};\n\naddAdditionalInfoForDeviceMsg(result, customerName, groupName);\n\nreturn result;\n\n\nfunction addAdditionalInfoForDeviceMsg(deviceInfo, customerName, groupName) {\n if (customerName != null) {\n deviceInfo.customerName = customerName;\n }\n if (groupName != null) {\n deviceInfo.groupName = groupName;\n }\n}\n\nfunction parseDateToTimestamp(dateString) {\n var date = new Date(dateString);\n var timestamp = date.getTime();\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 getGatewayInfo() {\n var gatewayList = data.uplink_message.?rx_metadata;\n var maxRssi = Integer.MIN_VALUE;\n var gatewayInfo = {};\n \n foreach (gateway : gatewayList) {\n if(gateway.rssi > maxRssi) {\n maxRssi = gateway.rssi;\n gatewayInfo = gateway;\n }\n }\n \n return gatewayInfo;\n}\n\nfunction processTelemetryData(telemetry, addDataToTelemetry) {\n if (telemetry.size >= 1) {\n telemetry = addDataToTelemetries(telemetry, addDataToTelemetry);\n }\n else {\n telemetry.add(addDataToTelemetry);\n }\n \n return telemetry;\n}\n\nfunction addDataToTelemetries(telemetries, addDataToTelemetry) {\n foreach(telemetry : telemetries) {\n foreach(element : addDataToTelemetry.entrySet()) {\n if(!telemetry[\"values\"].keys.contains(element.key)) {\n telemetry[\"values\"][element.key] = element.value;\n }\n } \n }\n \n return telemetries;\n}", + "encoder": null, + "tbelEncoder": null, + "updateOnlyKeys": [ + "fPort", + "bandwidth", + "frequency", + "net_id", + "cluster_id", + "cluster_address", + "device_id", + "join_eui", + "battery", + "eui", + "channel", + "applicationId", + "devAddr", + "spreadingFactor", + "codeRate", + "tenantId" + ] + }, + "additionalInfo": { + "description": "" + }, + "edgeTemplate": false +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Aura/ThingsStackCommunity/uplink/metadata.json b/VENDORS/Tektelic/Aura/ThingsStackCommunity/uplink/metadata.json new file mode 100644 index 00000000..0d75c374 --- /dev/null +++ b/VENDORS/Tektelic/Aura/ThingsStackCommunity/uplink/metadata.json @@ -0,0 +1,4 @@ +{ + "integrationName": "The Things Stack Community integration", + "includeGatewayInfo": "false" +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Aura/ThingsStackCommunity/uplink/payload.json b/VENDORS/Tektelic/Aura/ThingsStackCommunity/uplink/payload.json new file mode 100644 index 00000000..9d86cc7d --- /dev/null +++ b/VENDORS/Tektelic/Aura/ThingsStackCommunity/uplink/payload.json @@ -0,0 +1,54 @@ +{ + "end_device_ids": { + "device_id": "eui-1000000000000001", + "application_ids": { + "application_id": "application-tts-name" + }, + "dev_eui": "1000000000000001", + "join_eui": "2000000000000001", + "dev_addr": "20000001" + }, + "correlation_ids": ["as:up:01H0S7ZJQ9MQPMVY49FT3SE07M", "gs:conn:01H03BQZ9342X3Y86DJ2P704E5", "gs:up:host:01H03BQZ99EGAM52KK1300GFKN", "gs:uplink:01H0S7ZJGS6D9TJSKJN8XNTMAV", "ns:uplink:01H0S7ZJGS9KKD4HTTPKFEMWCV", "rpc:/ttn.lorawan.v3.GsNs/HandleUplink:01H0S7ZJGSF3M38ZRZVTM38DEC", "rpc:/ttn.lorawan.v3.NsAs/HandleUplink:01H0S7ZJQ8R2EH5AA269AKM8DX"], + "received_at": "2023-05-19T05:33:35.848446463Z", + "uplink_message": { + "session_key_id": "AYfqmb0pc/1uRZv9xUydgQ==", + "f_port": 10, + "f_cnt": 10335, + "frm_payload": "AP4AAVGAAABqUAAAAA==", + "rx_metadata": [{ + "gateway_ids": { + "gateway_id": "eui-6a7e111a10000000", + "eui": "6A7E111A10000000" + }, + "time": "2023-05-19T05:33:35.608982Z", + "timestamp": 3893546133, + "rssi": -35, + "channel_rssi": -35, + "snr": 13.2, + "frequency_offset": "69", + "uplink_token": "CiIKIAoUZXVpLTZhN2UxMTFhMTAwMDAwMDASCCThJP/+9k6eEJWZy8AOGgwIr5ScowYQvNbUsQIgiMy8y6jwpwE=", + "channel_index": 3, + "received_at": "2023-05-19T05:33:35.607383681Z" + }], + "settings": { + "data_rate": { + "lora": { + "bandwidth": 125000, + "spreading_factor": 7, + "coding_rate": "4/5" + } + }, + "frequency": "867100000", + "timestamp": 3893546133, + "time": "2023-05-19T05:33:35.608982Z" + }, + "received_at": "2023-05-19T05:33:35.641841782Z", + "consumed_airtime": "0.056576s", + "network_ids": { + "net_id": "000013", + "tenant_id": "ttn", + "cluster_id": "eu1", + "cluster_address": "eu1.cloud.thethings.network" + } + } +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Aura/ThingsStackCommunity/uplink/result.json b/VENDORS/Tektelic/Aura/ThingsStackCommunity/uplink/result.json new file mode 100644 index 00000000..c03cf49a --- /dev/null +++ b/VENDORS/Tektelic/Aura/ThingsStackCommunity/uplink/result.json @@ -0,0 +1,28 @@ +{ + "deviceName": "eui-1000000000000001", + "deviceType": "Aura", + "attributes": { + "eui": "1000000000000001", + "fPort": 10, + "applicationId": "application-tts-name", + "devAddr": "20000001", + "spreadingFactor": 7, + "codeRate": "4/5", + "tenantId": "ttn", + "device_id": "eui-1000000000000001", + "join_eui": "2000000000000001", + "net_id": "000013", + "cluster_id": "eu1", + "cluster_adress": "eu1.cloud.thethings.network", + "bandwidth": 125000, + "frequency": "867100000" + }, + "telemetry": [{ + "ts": 1684474415641, + "values": { + "energy_consumption_meter_elapsed": 86400, + "energy_consumption_meter_consumed": 27216, + "energy_consumption_meter_status": 0 + } + }] +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Aura/ThingsStackIndustries/uplink/converter.json b/VENDORS/Tektelic/Aura/ThingsStackIndustries/uplink/converter.json new file mode 100644 index 00000000..48f16f1e --- /dev/null +++ b/VENDORS/Tektelic/Aura/ThingsStackIndustries/uplink/converter.json @@ -0,0 +1,40 @@ +{ + "name": "The Things Stack Industries Uplink Decoder for Aura", + "type": "UPLINK", + "debugMode": false, + "debugSettings": { + "failuresEnabled": true, + "allEnabled": false, + "allEnabledUntil": 1733331880270 + }, + "configuration": { + "scriptLang": "TBEL", + "decoder": null, + "tbelDecoder": "var data = decodeToJson(payload);\n\nvar deviceName = data.end_device_ids.device_id;\nvar deviceType = \"Aura\";\nvar groupName = null; // If groupName is not null - created device will be added to the entity group with such name.\nvar customerName = null; // If customerName is not null - created devices will be assigned to customer with such name. \n\n// use assetName and assetType instead of deviceName and deviceType\n// to automatically create assets instead of devices.\n// var assetName = 'Asset A';\n// var assetType = 'building';\n\n// If you want to parse incoming data somehow, you can add your code to this function.\n// input: bytes\n// expected output:\n// {\n// \"attributes\": {\"attributeKey\": \"attributeValue\"},\n// \"telemetry\": [{\"ts\": 1...1, \"values\": {\"telemetryKey\":\"telemetryValue\"}, {\"ts\": 1...2, \"values\": {\"telemetryKey\":\"telemetryValue\"}}]\n// }\n\nfunction decodeFrmPayload(input) {\n var output = { attributes: {}, telemetry: []};\n \n // --- Decoding code --- //\n var decoded = {};\n var fPort = data.uplink_message.f_port;\n if(fPort == 10) {\n for(var i = 0; i < input.length - 2; ) {\n var key_1 = input[i++] & 0xff;\n var key_2 = input[i++] & 0xff;\n \n if(key_1 == 0x00 && key_2 == 0xFE) {\n decoded.energy_consumption_meter_elapsed = parseBytesToInt(input, i, 4);\n decoded.energy_consumption_meter_consumed = parseBytesToInt(input, i + 4, 4);\n i += 8;\n }\n else if(key_1 == 0x00 && key_2 == 0x00) {\n decoded.energy_consumption_meter_status = parseBytesToInt(input, i, 1);\n if(decoded.energy_consumption_meter_status != 0) {\n decoded.energy_consumption_meter_status != 1;\n }\n }\n else if(key_1 == 0x00 && key_2 == 0x74) {\n decoded.voltmeter = (input[i] << 8) | (input[i + 1] & 0xFF);\n if (decoded.voltmeter != 65535) {\n decoded.voltmeter = decoded.voltmeter * 10;\n }\n \n i += 2;\n }\n else if(key_1 == 0x00 && key_2 == 0x75) {\n decoded.voltmeter = (input[i] << 8) | (input[i + 1] & 0xFF);\n if (decoded.ammeter != 65535) {\n decoded.ammeter = decoded.ammeter * 10;\n }\n \n i += 2;\n }\n else if(key_1 == 0x00 && key_2 == 0x80) {\n decoded.real_power = (input[i] << 8) | (input[i + 1] & 0xFF);\n if (decoded.real_power != 65535) {\n decoded.real_power = decoded.real_power * 10;\n }\n \n i += 2;\n }\n else if(key_1 == 0x01 && key_2 == 0x80) {\n decoded.apparent_power = (input[i] << 8) | (input[i + 1] & 0xFF);\n if (decoded.apparent_power != 65535) {\n decoded.apparent_power = decoded.apparent_power * 10;\n }\n \n i += 2;\n }\n else if(key_1 == 0x00 && key_2 == 0x81) {\n decoded.power_factor_meter = input[i] & 0xFF;\n if (decoded.power_factor_meter != 65535) {\n decoded.power_factor_meter = decoded.power_factor_meter * 100;\n }\n \n i += 1;\n }\n else if(key_1 == 0x00 && key_2 == 0x01) {\n decoded.relay_status = input[i] & 0xFF;\n \n if (decoded.relay_status != 0) {\n decoded.relay_status = 1;\n }\n \n i += 1;\n }\n }\n }\n else if (fPort == 100) {\n for(var i = 0; i < input.length -1; ) {\n var key = input[i++] & 0xff;\n \n if(key == 0x00) {\n output.attributes.eui = bytesToHex(java.util.Arrays.copyRange(input, i, i + 8));\n i += 8;\n }\n else if(key == 0x01) {\n output.attributes.app_eui = bytesToHex(java.util.Arrays.copyRange(input, i, i + 8));\n\t\t\t\ti += 8;\n }\n else if (key == 0x02) {\n output.attributes.app_key = bytesToHex(java.util.Arrays.copyRange(input, i, i + 16));\n i += 16;\n }\n else if (key == 0x03) {\n output.attributes.devAddr = bytesToHex(java.util.Arrays.copyRange(input, i, i + 4));\n i += 4;\n }\n else if(key == 0x04) {\n output.attributes.network_session_key = bytesToHex(java.util.Arrays.copyRange(input, i, i + 16));\n i += 16;\n }\n else if(key == 0x05) {\n output.attributes.app_session_key = bytesToHex(java.util.Arrays.copyRange(input, i, i + 16));\n i += 16;\n }\n else if(key == 0x10) {\n output.attributes.loramac_join_mode = (input[i] >> 7) & 1;\n i += 2;\n }\n else if (key == 0x11) {\n var val = (input[i] >> 4) & 0x0F;\n \n if(val == 0x0C) {\n output.attributes.lora_class = \"Class C\";\n }\n else if(val == 0x0B) {\n output.attributes.lora_class = \"Class B\";\n }\n else if(val == 0x0A) {\n output.attributes.lora_class = \"Class F\";\n }\n else {\n output.attributes.lora_class = \"Invalid\";\n }\n\t\t\t\t\n\t\t\t\toutput.attributes.confirm_mode = (input[i] >> 8) & 1; \n\t\t\t\toutput.attributes.sync_word = (input[i] >> 9) & 1;\n\t\t\t\toutput.attributes.duty_cycle = (input[i] >> 10) & 1;\n\t\t\t\toutput.attributes.adr = (input[i] >> 11) & 1; \n\t\t\t\t\n\t\t\t\ti += 2;\n }\n else if (key == 0x12) {\n output.attributes.dr_number = (((input[i] << 8) | input[i + 1]) >> 8) & 0xF;\n output.attributes.tx_power_number = ((input[i] << 8) | input[i + 1]) & 0xF;\n \n i +=2;\n }\n else if (key == 0x13) {\n output.attributes.frequency = (((input[i] << 32) | (input[i + 1] << 24) | (input[i + 2] << 16) | (input[i + 3] << 8) | input[i + 4]) >> 8) & 0xFFFFFFFF;\n output.attributes.dr_number_rx2 = input[i + 4] & 0xFF;\n \n i += 5;\n }\n else if(key == 0x19) {\n output.attributes.netid_msb = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x20) {\n output.attributes.seconds_per_core_tick = parseBytesToInt(input, i, 4);\n i += 4;\n }\n else if(key == 0x21) {\n output.attributes.tick_per_battery = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x71) {\n output.attributes.app_major_version = input[i];\n\t\t\t\toutput.attributes.app_minor_version = input[i + 1];\n\t\t\t\toutput.attributes.app_revision = input[i + 2];\n\t\t\t\toutput.attributes.loramac_major_version = input[i + 3];\n\t\t\t\toutput.attributes.loramac_minor_version = input[i + 4];\n\t\t\t\toutput.attributes.loramac_revision = input[i + 5];\n\t\t\t\toutput.attributes.region = input[i + 7];\n\t\t\t\ti += 7;\n }\n }\n }\n \n output.telemetry = [{\n ts: timestamp,\n values: decoded\n }];\n\n // --- Decoding code --- //\n return output;\n}\n\n// --- attributes and telemetry objects ---\nvar telemetry = [];\nvar attributes = {};\n// --- attributes and telemetry objects ---\n\n// --- Timestamp parsing\nvar dateString = data.uplink_message.received_at;\n\nif ((data.simulated != null && data.simulated) || dateString == null) {\n dateString = data.received_at;\n}\n\ntimestamp = parseDateToTimestamp(dateString);\n// --- Timestamp parsing\n\n// Message parsing\n// To avoid paths in the decoded objects we passing false value to function as \"pathInKey\" argument.\n// Warning: pathInKey can cause already found fields to be overwritten with the last value found, e.g. receive_at from uplink_message will be written receive_at in the root.\n\n// Passing incoming bytes to decodeFrmPayload function, to get custom decoding\nvar customDecoding = {};\nif (data.uplink_message.get(\"frm_payload\") != null) {\n customDecoding = decodeFrmPayload(base64ToBytes(data.uplink_message.frm_payload));\n}\n\n// Collecting data to result\nif (customDecoding.?telemetry.size() > 0) {\n if (customDecoding.telemetry instanceof java.util.ArrayList) {\n foreach(telemetryObj: customDecoding.telemetry) {\n if (telemetryObj.ts != null && telemetryObj.values != null) {\n telemetry.add(telemetryObj);\n }\n }\n } else {\n telemetry.putAll(customDecoding.telemetry);\n }\n}\n\nif (customDecoding.?attributes.size() > 0) {\n attributes.putAll(customDecoding.attributes);\n}\n\n// You can add some keys manually to attributes or telemetry\nvar applicationId = data.end_device_ids.?application_ids.?application_id;\nvar devAddr = data.end_device_ids.?dev_addr;\nvar spreadingFactor = data.uplink_message.?settings.?data_rate.?lora.?spreading_factor;\nvar codeRate = data.uplink_message.?settings.?data_rate.?lora.?coding_rate;\nvar tenantId = data.uplink_message.?network_ids.?tenant_id;\nattributes.eui = data.end_device_ids.dev_eui;\nattributes.fPort = data.uplink_message.f_port;\nattributes.applicationId = applicationId;\nattributes.devAddr = devAddr;\nattributes.spreadingFactor = spreadingFactor;\nattributes.codeRate = codeRate;\nattributes.tenantId = tenantId;\nattributes.device_id = data.end_device_ids.?device_id;\nattributes.join_eui = data.end_device_ids.?join_eui;\nattributes.net_id = data.uplink_message.?network_ids.?net_id;\nattributes.cluster_id = data.uplink_message.?network_ids.?cluster_id;\nattributes.cluster_address = data.uplink_message.?network_ids.?cluster_address;\nattributes.bandwidth = data.uplink_message.?settings.?data_rate.?lora.?bandwidth;\nattributes.frequency = data.uplink_message.?settings.?frequency;\n\nvar isIncludeGatewayInfo = metadata[\"includeGatewayInfo\"];\nif(isIncludeGatewayInfo == true) {\n var gatewayInfo = getGatewayInfo();\n var addDataToTelemetry = {};\n addDataToTelemetry.snr = gatewayInfo.snr;\n addDataToTelemetry.rssi = gatewayInfo.rssi;\n addDataToTelemetry.channel = gatewayInfo.channel_index;\n addDataToTelemetry.consumed_airtime = data.uplink_message.?consumed_airtime;\n addDataToTelemetry.fCnt = data.uplink_message.?f_cnt;\n\n telemetry = processTelemetryData(telemetry, addDataToTelemetry);\n}\n\nvar result = {\n deviceName: deviceName,\n deviceType: deviceType,\n // assetName: assetName,\n // assetType: assetType,\n attributes: attributes,\n telemetry: telemetry\n};\n\naddAdditionalInfoForDeviceMsg(result, customerName, groupName);\n\nreturn result;\n\nfunction addAdditionalInfoForDeviceMsg(deviceInfo, customerName, groupName) {\n if (customerName != null) {\n deviceInfo.customerName = customerName;\n }\n if (groupName != null) {\n deviceInfo.groupName = groupName;\n }\n}\n\nfunction parseDateToTimestamp(dateString) {\n var date = new Date(dateString);\n var timestamp = date.getTime();\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 getGatewayInfo() {\n var gatewayList = data.uplink_message.?rx_metadata;\n var maxRssi = Integer. MIN_VALUE;\n var gatewayInfo = {};\n \n foreach (gateway : gatewayList) {\n if(gateway.rssi > maxRssi) {\n maxRssi = gateway.rssi;\n gatewayInfo = gateway;\n }\n }\n \n return gatewayInfo;\n}\n\nfunction processTelemetryData(telemetry, addDataToTelemetry) {\n if (telemetry.size >= 1) {\n telemetry = addDataToTelemetries(telemetry, addDataToTelemetry);\n }\n else {\n telemetry.add(addDataToTelemetry);\n }\n \n return telemetry;\n}\n\nfunction addDataToTelemetries(telemetries, addDataToTelemetry) {\n foreach(telemetry : telemetries) {\n foreach(element : addDataToTelemetry.entrySet()) {\n if(!telemetry[\"values\"].keys.contains(element.key)) {\n telemetry[\"values\"][element.key] = element.value;\n }\n } \n }\n \n return telemetries;\n}", + "encoder": null, + "tbelEncoder": null, + "updateOnlyKeys": [ + "fPort", + "bandwidth", + "frequency", + "net_id", + "cluster_id", + "cluster_address", + "tenant_address", + "device_id", + "join_eui", + "eui", + "channel", + "devAddr", + "spreadingFactor", + "codeRate", + "tenantId", + "applicationId", + "battery" + ] + }, + "additionalInfo": { + "description": "" + }, + "edgeTemplate": false +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Aura/ThingsStackIndustries/uplink/metadata.json b/VENDORS/Tektelic/Aura/ThingsStackIndustries/uplink/metadata.json new file mode 100644 index 00000000..23f54b34 --- /dev/null +++ b/VENDORS/Tektelic/Aura/ThingsStackIndustries/uplink/metadata.json @@ -0,0 +1,4 @@ +{ + "integrationName": "ChirpStack integration", + "includeGatewayInfo": "false" +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Aura/ThingsStackIndustries/uplink/payload.json b/VENDORS/Tektelic/Aura/ThingsStackIndustries/uplink/payload.json new file mode 100644 index 00000000..31db41d3 --- /dev/null +++ b/VENDORS/Tektelic/Aura/ThingsStackIndustries/uplink/payload.json @@ -0,0 +1,77 @@ +{ + "end_device_ids": { + "device_id": "eui-1000000000000001", + "application_ids": { + "application_id": "application-tti-name" + }, + "dev_eui": "1000000000000001", + "join_eui": "2000000000000001", + "dev_addr": "20000001" + }, + "correlation_ids": ["as:up:01H0PZDGB1NW6NAPD815NGHPF6", "gs:conn:01H0FJRSXSYT7VKNYXJ89F95XT", "gs:up:host:01H0FJRSY3MZMGPPFBQ4FZV4T8", "gs:uplink:01H0PZDG4HHGFRTXRTXD4PFTH7", "ns:uplink:01H0PZDG4JZ3BM0K6J89EQK1J7", "rpc:/ttn.lorawan.v3.GsNs/HandleUplink:01H0PZDG4J02F85RYFPCNSNXCR", "rpc:/ttn.lorawan.v3.NsAs/HandleUplink:01H0PZDGB081PMP806BJHNHX1A"], + "received_at": "2023-05-18T08:25:26.112483370Z", + "uplink_message": { + "session_key_id": "AYfg8rhha5n+FWx0ZaAprA==", + "f_port": 10, + "f_cnt": 5017, + "frm_payload": "AP4AAVGAAABqUAAAAA==", + "rx_metadata": [{ + "gateway_ids": { + "gateway_id": "eui-6A7E111A10000000", + "eui": "6A7E111A10000000" + }, + "time": "2023-05-18T08:25:25.885310Z", + "timestamp": 818273765, + "rssi": -24, + "channel_rssi": -24, + "snr": 12, + "frequency_offset": "671", + "uplink_token": "CiIKIAoUZXVpLTZBN0UxMTFBMTAwMDAwMDASCCThJP/+9k6eEOW7l4YDGgwI9cGXowYQ5KPhrwMgiI2rp+jpOA=", + "channel_index": 2, + "received_at": "2023-05-18T08:25:25.869324983Z" + }, { + "gateway_ids": { + "gateway_id": "packetbroker" + }, + "packet_broker": { + "message_id": "01H0PZDG4MF9AYSMNY44MAVTDH", + "forwarder_net_id": "000013", + "forwarder_tenant_id": "ttn", + "forwarder_cluster_id": "eu1.cloud.thethings.network", + "forwarder_gateway_eui": "6A7E111A10000000", + "forwarder_gateway_id": "eui-6a7e111a10000000", + "home_network_net_id": "000013", + "home_network_tenant_id": "tenant", + "home_network_cluster_id": "eu1.cloud.thethings.industries" + }, + "time": "2023-05-18T08:25:25.885310Z", + "rssi": -24, + "channel_rssi": -24, + "snr": 12, + "frequency_offset": "671", + "uplink_token": "eyJnIjoiWlhsS2FHSkhZMmxQYVVwQ1RWUkpORkl3VGs1VE1XTnBURU5LYkdKdFRXbFBhVXBDVFZSSk5GSXdUazVKYVhkcFlWaFphVTlwU201a01uaGhWVlJvZDFSWFVuRmlSM1JtVFcxT2RVbHBkMmxrUjBadVNXcHZhV05ZY0RKT1IyeExaREpSZVZwR1pIUmpNRXBLVlVoR2RFNVZkR3BWVTBvNUxua3paVVJTWVRaM1lXOU1kbTQwVm5sdmIyWmlPWGN1ZUhCZmVrcElaa3hIWlZadGRVUlFVeTVuYlRaVlZXRXdkakpHV0VKMGJUUjZaMjVXUkVoeGVHRjRaMlJKTlVkS1VsbERhemc1VDNCbk5rVk1iM1JDUkVZM1VWbHdZbEJDTkdOblNqWjBlbkphYUV4MFRVMHhZMVZFTTFac01XdExURUo0YURaMFExTnhhMVJsWWw4eE5FdHlVVXcyZUhsRWFFbEhlakJITXpoTE0xaFdlRzR5VUVjMk4wNUViME5WTkhoTmRrazFZVk5oWkUwd2FXVnFjR294VGtoMFduZHlZMDFxVlVGNmRsbERUazlNY2s5eFdVeFpWMk5XTG1WVFFYVkpNVkptT1U5NWRqUTNhSEoxTUZoalYxRT0iLCJhIjp7ImZuaWQiOiIwMDAwMTMiLCJmdGlkIjoidHRuIiwiZmNpZCI6ImV1MS5jbG91ZC50aGV0aGluZ3MubmV0d29yayJ9fQ==", + "received_at": "2023-05-18T08:25:25.906038642Z" + }], + "settings": { + "data_rate": { + "lora": { + "bandwidth": 125000, + "spreading_factor": 7, + "coding_rate": "4/5" + } + }, + "frequency": "868500000", + "timestamp": 818273765, + "time": "2023-05-18T08:25:25.885310Z" + }, + "received_at": "2023-05-18T08:25:25.906399073Z", + "consumed_airtime": "0.097536s", + "network_ids": { + "net_id": "000013", + "tenant_id": "tenant", + "cluster_id": "eu1", + "cluster_address": "eu1.cloud.thethings.industries", + "tenant_address": "tenant.eu1.cloud.thethings.industries" + } + } +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Aura/ThingsStackIndustries/uplink/result.json b/VENDORS/Tektelic/Aura/ThingsStackIndustries/uplink/result.json new file mode 100644 index 00000000..6c5224e2 --- /dev/null +++ b/VENDORS/Tektelic/Aura/ThingsStackIndustries/uplink/result.json @@ -0,0 +1,28 @@ +{ + "deviceName": "eui-1000000000000001", + "deviceType": "Aura", + "attributes": { + "eui": "1000000000000001", + "fPort": 10, + "applicationId": "application-tti-name", + "devAddr": "20000001", + "spreadingFactor": 7, + "codeRate": "4/5", + "tenantId": "tenant", + "device_id": "eui-1000000000000001", + "join_eui": "2000000000000001", + "net_id": "000013", + "cluster_id": "eu1", + "cluster_address": "eu1.cloud.thethings.industries", + "bandwidth": 125000, + "frequency": "868500000" + }, + "telemetry": [{ + "ts": 1684398325906, + "values": { + "energy_consumption_meter_elapsed": 86400, + "energy_consumption_meter_consumed": 27216, + "energy_consumption_meter_status": 0 + } + }] +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Aura/info.json b/VENDORS/Tektelic/Aura/info.json new file mode 100644 index 00000000..94656046 --- /dev/null +++ b/VENDORS/Tektelic/Aura/info.json @@ -0,0 +1,5 @@ +{ + "url": "https://tektelic.com/products/sensors/aura-smart-ac-switch/", + "label": "Aura: Enhanced energy monitoring and remote control for smart homes, offices and facilities", + "description": "AURA is a 120V Smart Switch with the form factor of a standard AC Decorator-style rocker switch, incorporating a LoRa radio with power control and monitoring circuitry. It provides features such as power on-off control and status, as well as precise measurement of consumed energy (kWh), line voltage (Vrms), load current (Arms), load power (real, reactive, and apparent, W), and load power factor. AURA is a Class C device that enables real-time control and telemetry inquiry." +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Aura/photo.png b/VENDORS/Tektelic/Aura/photo.png new file mode 100644 index 00000000..e43d3a0c Binary files /dev/null and b/VENDORS/Tektelic/Aura/photo.png differ