diff --git a/VENDORS/Milesight/AM308/ChirpStack/uplink/converter.json b/VENDORS/Milesight/AM308/ChirpStack/uplink/converter.json
index dfb0419..2b210a4 100644
--- a/VENDORS/Milesight/AM308/ChirpStack/uplink/converter.json
+++ b/VENDORS/Milesight/AM308/ChirpStack/uplink/converter.json
@@ -1,39 +1,35 @@
{
- "name": "ChirpStack uplink converter for Milesight AM308",
- "type": "UPLINK",
- "debugMode": true,
- "configuration": {
- "scriptLang": "TBEL",
- "decoder": null,
- "tbelDecoder": "var data = decodeToJson(payload);\nvar deviceName = data.deviceInfo.deviceName;\nvar deviceType = data.deviceInfo.deviceProfileName;\nvar groupName = '8-in-1 IAQ Sensor';\n// var customerName = 'Customer A';\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//\n// In the example - bytes will be saved as HEX string and also parsed as light level, battery level and PIR sensor value.\n//\n\nfunction decodePayload(inputArray) {\n var output = {\n attributes: {},\n telemetry: {}\n };\n // --- Decoding code --- //\n var input = bytesToExecutionArrayList(inputArray);\n output.telemetry.HEX_bytes = bytesToHex(input);\n\n var historyData = {};\n var decoded = {};\n decoded.hexString = bytesToHex(input);\n for (var i = 0; i < input.length - 2;) {\n var channel_id = input[i++] & 0xff;\n var channel_type = input[i++] & 0xff;\n // BATTERY\n if (channel_id === 0x01 && channel_type === 0x75) {\n decoded.battery = input[i];\n i += 1;\n }\n // TEMPERATURE\n if (channel_id === 0x03 && channel_type === 0x67) {\n // ℃\n decoded.temperature = parseBytesToInt(input, i,\n 2, false) / 10;\n i += 2;\n // ℉\n // decoded.temperature = parseBytesToInt(input, i, 2, false) / 10 * 1.8 + 32;\n // i +=2;\n }\n // HUMIDITY\n if (channel_id === 0x04 && channel_type === 0x68) {\n decoded.humidity = input[i] / 2;\n i += 1;\n }\n // PIR\n if (channel_id === 0x05 && channel_type === 0x00) {\n decoded.pir = input[i] === 1 ? \"trigger\" :\n \"idle\";\n i += 1;\n }\n // LIGHT\n if (channel_id === 0x06 && channel_type === 0xCB) {\n decoded.light_level = input[i];\n i += 1;\n }\n // CO2\n if (channel_id === 0x07 && channel_type === 0x7D) {\n decoded.co2 = parseBytesToInt(input, i, 2,\n false);\n i += 2;\n }\n // TVOC\n if (channel_id === 0x08 && channel_type === 0x7D) {\n decoded.tvoc = parseBytesToInt(input, i, 2,\n false);\n i += 2;\n }\n // PRESSURE\n if (channel_id === 0x09 && channel_type === 0x73) {\n decoded.pressure = parseBytesToInt(input, i, 2,\n false) / 10;\n i += 2;\n }\n // HCHO\n if (channel_id === 0x0A && channel_type === 0x7D) {\n decoded.hcho = parseBytesToInt(input, i, 2,\n false) / 100;\n i += 2;\n }\n // PM2.5\n if (channel_id === 0x0B && channel_type === 0x7D) {\n decoded.pm2_5 = parseBytesToInt(input, i, 2,\n false);\n i += 2;\n }\n // PM10\n if (channel_id === 0x0C && channel_type === 0x7D) {\n decoded.pm10 = parseBytesToInt(input, i, 2,\n false);\n i += 2;\n }\n // O3\n if (channel_id === 0x0D && channel_type === 0x7D) {\n decoded.o3 = parseBytesToInt(input, i, 2,\n false) / 100;\n i += 2;\n }\n // BEEP\n if (channel_id === 0x0E && channel_type === 0x01) {\n decoded.beep = input[i] === 1 ? \"yes\" : \"no\";\n i += 1;\n\n }\n // HISTORY DATA (AM308)\n if (channel_id === 0x20 && channel_type === 0xCE) {\n historyData = {};\n historyData.timestamp = parseBytesToInt(input,\n i, 4, false);\n historyData.temperature = parseBytesToInt(input,\n i + 4, 2, false) / 10;\n historyData.humidity = parseBytesToInt(input,\n i + 6, 2, false) / 2;\n historyData.pir = input[i + 8] === 1 ?\n \"trigger\" : \"idle\";\n historyData.light_level = input[i + 9] === 1;\n historyData.co2 = parseBytesToInt(input, i + 10,\n 2, false);\n historyData.tvoc = parseBytesToInt(input, i +\n 12, 2, false);\n historyData.pressure = parseBytesToInt(input,\n i + 14, 2, false) / 10;\n historyData.pm2_5 = parseBytesToInt(input, i +\n 16, 2, false);\n historyData.pm10 = parseBytesToInt(input, i +\n 18, 2, false);\n i += 20;\n if (decoded.history == null) {\n decoded.history = [];\n }\n decoded.history.push(historyData);\n }\n }\n\n output.telemetry = 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;\nvar timestamp = -1;\nif (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf(\n '.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf(\n '+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf(\n 'Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf(\n '-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex +\n 3) {\n dateString = dateString.substring(0,\n secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0,\n secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex,\n dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n}\n// If we cannot parse timestamp - we will use the current timestamp\nif (timestamp == -1) {\n timestamp = Date.now();\n}\n// --- Timestamp parsing\n\n// You can add some keys manually to attributes or telemetry\nattributes.deduplicationId = data.deduplicationId;\n\n// You can exclude some keys from the result\nvar excludeFromAttributesList = [\"deviceName\", \"rxInfo\",\n \"confirmed\", \"data\", \"deduplicationId\", \"time\",\n \"adr\", \"dr\", \"fCnt\"\n];\nvar excludeFromTelemetryList = [\"data\", \"deviceInfo\",\n \"txInfo\", \"devAddr\", \"adr\", \"time\", \"fPort\",\n \"region_common_name\", \"region_config_id\",\n \"deduplicationId\"\n];\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 telemetryData = toFlatMap(data,\n excludeFromTelemetryList, false);\nvar attributesData = toFlatMap(data,\n excludeFromAttributesList, false);\n\nvar uplinkDataList = [];\n\n// Passing incoming bytes to decodePayload function, to get custom decoding\nvar customDecoding = decodePayload(base64ToBytes(data\n.data));\n\n// Collecting data to result\nif (customDecoding.?telemetry.size() > 0) {\n telemetry.putAll(customDecoding.telemetry);\n}\n\nif (customDecoding.?attributes.size() > 0) {\n attributes.putAll(customDecoding.attributes);\n}\n\ntelemetry.putAll(telemetryData);\nattributes.putAll(attributesData);\n\nvar result = {\n deviceName: deviceName,\n deviceType: deviceType,\n // assetName: assetName,\n // assetType: assetType,\n // customerName: customerName,\n groupName: groupName,\n attributes: attributes,\n telemetry: {\n ts: timestamp,\n values: telemetry\n }\n};\n\nreturn result;",
- "encoder": null,
- "tbelEncoder": null,
- "updateOnlyKeys": [
- "tenantId",
- "tenantName",
- "applicationId",
- "applicationName",
- "deviceProfileId",
- "deviceProfileName",
- "devEui",
- "devAddr",
- "fPort",
- "frequency",
- "bandwidth",
- "spreadingFactor",
- "codeRate",
- "battery",
- "pir",
- "daylight",
- "confirmed",
- "gatewayId",
- "channel",
- "rfChain",
- "crcStatus"
- ]
- },
- "additionalInfo": {
- "description": ""
- },
- "edgeTemplate": false
+ "name": "ChirpStack uplink converter for Milesight AM308",
+ "type": "UPLINK",
+ "debugMode": true,
+ "configuration": {
+ "scriptLang": "TBEL",
+ "decoder": null,
+ "tbelDecoder": "var data = decodeToJson(payload);\nvar deviceName = \"AM308 \" + data.deviceInfo.deviceName;\nvar deviceType = \"AM308\";\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 historyData = {};\n for (var i = 0; i < input.length - 2;) {\n var channel_id = input[i++] & 0xff;\n var channel_type = input[i++] & 0xff;\n // BATTERY\n if (channel_id === 0x01 && channel_type === 0x75) {\n decoded.battery = input[i];\n i += 1;\n }\n // TEMPERATURE\n if (channel_id === 0x03 && channel_type === 0x67) {\n // ℃\n decoded.temperature = parseBytesToInt(input, i,\n 2, false) / 10;\n i += 2;\n // ℉\n // decoded.temperature = parseBytesToInt(input, i, 2, false) / 10 * 1.8 + 32;\n // i +=2;\n }\n // HUMIDITY\n if (channel_id === 0x04 && channel_type === 0x68) {\n decoded.humidity = parseBytesToInt(input, i, 1, false) / 2;\n i += 1;\n }\n // PIR\n if (channel_id === 0x05 && channel_type === 0x00) {\n decoded.pir = input[i] === 1 ? \"trigger\" :\n \"idle\";\n i += 1;\n }\n // LIGHT\n if (channel_id === 0x06 && channel_type === 0xCB) {\n decoded.light_level = input[i];\n i += 1;\n }\n // CO2\n if (channel_id === 0x07 && channel_type === 0x7D) {\n decoded.co2 = parseBytesToInt(input, i, 2,\n false);\n i += 2;\n }\n // TVOC\n if (channel_id === 0x08 && channel_type === 0x7D) {\n decoded.tvoc = parseBytesToInt(input, i, 2,\n false);\n i += 2;\n }\n // PRESSURE\n if (channel_id === 0x09 && channel_type === 0x73) {\n decoded.pressure = parseBytesToInt(input, i, 2,\n false) / 10;\n i += 2;\n }\n // PM2.5\n if (channel_id === 0x0B && channel_type === 0x7D) {\n decoded.pm2_5 = parseBytesToInt(input, i, 2,\n false);\n i += 2;\n }\n // PM10\n if (channel_id === 0x0C && channel_type === 0x7D) {\n decoded.pm10 = parseBytesToInt(input, i, 2,\n false);\n i += 2;\n }\n // BEEP\n if (channel_id === 0x0E && channel_type === 0x01) {\n decoded.beep = input[i] === 1 ? \"yes\" : \"no\";\n i += 1;\n }\n // HISTORY DATA\n if (channel_id === 0x20 && channel_type === 0xCE) {\n var historyData = {\n ts: parseBytesToInt(input, i, 4, false),\n values: {\n temperature: parseBytesToInt(input, i + 4, 2, false) / 10,\n humidity: parseBytesToInt(input, i + 6, 2, false) / 2,\n pir: input[i + 8] === 1 ? \"trigger\" : \"idle\",\n light_level: input[i + 9],\n co2: parseBytesToInt(input, i + 10, 2, false),\n tvoc: parseBytesToInt(input, i + 12, 2, false),\n pressure: parseBytesToInt(input, i + 14, 2, false) / 10,\n pm2_5: parseBytesToInt(input, i + 16, 2, false),\n pm10: parseBytesToInt(input, i + 18, 2, false)\n }\n };\n \n i += 20;\n }\n }\n\n output.telemetry = [{\n ts: timestamp,\n values: decoded\n }, historyData];\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\n.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\nvar gatewayInfo = getGatewayInfo();\nvar addDataToTelemetry = {};\naddDataToTelemetry.snr = gatewayInfo.snr;\naddDataToTelemetry.rssi = gatewayInfo.rssi;\naddDataToTelemetry.channel = gatewayInfo.channel;\naddDataToTelemetry.rfChain = gatewayInfo.rfChain;\naddDataToTelemetry.fCnt = data.fCnt;\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\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 timestamp = -1;\n if (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf(\n '.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf(\n '+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf(\n 'Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf(\n '-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex +\n 3) {\n dateString = dateString.substring(0,\n secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0,\n secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex,\n 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 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 = addDataToMultipleTelemetries(telemetry, addDataToTelemetry);\n }\n else if (telemetry.size == 1) {\n telemetry = addDataToSingleTelemetry(telemetry, addDataToTelemetry);\n }\n else {\n telemetry.add(addDataToTelemetry);\n }\n \n return telemetry;\n}\n\nfunction addDataToMultipleTelemetries(telemetry, addDataToTelemetry) {\n foreach(element : addDataToTelemetry.entrySet()) {\n if(!telemetry[0][\"values\"].keys.contains(element.key)) {\n telemetry[0][\"values\"][element.key] = element.value;\n }\n if (!telemetry[1][\"values\"].keys.contains(element.key)) {\n telemetry[1][\"values\"][element.key] = element.value;\n }\n }\n \n return telemetry;\n}\n\nfunction addDataToSingleTelemetry(telemetry, addDataToTelemetry) {\n foreach(element : addDataToTelemetry.entrySet()) {\n if(!telemetry[0][\"values\"].keys.contains(element.key)) {\n telemetry[0][\"values\"][element.key] = element.value;\n }\n }\n \n return telemetry;\n}",
+ "encoder": null,
+ "tbelEncoder": null,
+ "updateOnlyKeys": [
+ "tenantId",
+ "tenantName",
+ "applicationId",
+ "applicationName",
+ "deviceProfileId",
+ "deviceProfileName",
+ "devAddr",
+ "fPort",
+ "frequency",
+ "bandwidth",
+ "spreadingFactor",
+ "codeRate",
+ "battery",
+ "channel",
+ "rfChain",
+ "eui",
+ "beep"
+ ]
+ },
+ "additionalInfo": {
+ "description": ""
+ },
+ "edgeTemplate": false
}
\ No newline at end of file
diff --git a/VENDORS/Milesight/AM308/ChirpStack/uplink/metadata.json b/VENDORS/Milesight/AM308/ChirpStack/uplink/metadata.json
index b6ab81f..1f9359b 100644
--- a/VENDORS/Milesight/AM308/ChirpStack/uplink/metadata.json
+++ b/VENDORS/Milesight/AM308/ChirpStack/uplink/metadata.json
@@ -1,3 +1,4 @@
{
- "integrationName": "ChirpStack integration"
+ "integrationName": "ChirpStack integration",
+ "includeGatewayInfo": false
}
\ No newline at end of file
diff --git a/VENDORS/Milesight/AM308/ChirpStack/uplink/payload.base64 b/VENDORS/Milesight/AM308/ChirpStack/uplink/payload.base64
deleted file mode 100644
index e69de29..0000000
diff --git a/VENDORS/Milesight/AM308/ChirpStack/uplink/payload.txt b/VENDORS/Milesight/AM308/ChirpStack/uplink/payload.txt
deleted file mode 100644
index e69de29..0000000
diff --git a/VENDORS/Milesight/AM308/ChirpStack/uplink/payload.json b/VENDORS/Milesight/AM308/ChirpStack/uplink/payload_1.json
similarity index 100%
rename from VENDORS/Milesight/AM308/ChirpStack/uplink/payload.json
rename to VENDORS/Milesight/AM308/ChirpStack/uplink/payload_1.json
diff --git a/VENDORS/Milesight/AM308/ChirpStack/uplink/payload_2.json b/VENDORS/Milesight/AM308/ChirpStack/uplink/payload_2.json
new file mode 100644
index 0000000..13001da
--- /dev/null
+++ b/VENDORS/Milesight/AM308/ChirpStack/uplink/payload_2.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": 85,
+ "confirmed": false,
+ "data": "AXVVA2fuAARofAUAAQbLAgd9qAMIfSUACXNmJwp9BAALfSAADH0wAA19IAAOAVUgzmU1ygwA4QBuAWQBwgAKJ5cAGQAy",
+ "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/Milesight/AM308/ChirpStack/uplink/result_1.json b/VENDORS/Milesight/AM308/ChirpStack/uplink/result_1.json
new file mode 100644
index 0000000..a21a120
--- /dev/null
+++ b/VENDORS/Milesight/AM308/ChirpStack/uplink/result_1.json
@@ -0,0 +1,34 @@
+{
+ "deviceName": "AM308 Device name",
+ "deviceType": "AM308",
+ "attributes": {
+ "eui": "1000000000000001",
+ "devAddr": "20000001",
+ "fPort": 85,
+ "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": {
+ "battery": 85,
+ "temperature": 23.8,
+ "humidity": 62.0,
+ "pir": "trigger",
+ "light_level": 2,
+ "co2": 936,
+ "tvoc": 37,
+ "pressure": 1008.6,
+ "pm2_5": 32,
+ "pm10": 48
+ }
+ }]
+}
\ No newline at end of file
diff --git a/VENDORS/Milesight/AM308/ChirpStack/uplink/result.json b/VENDORS/Milesight/AM308/ChirpStack/uplink/result_2.json
similarity index 53%
rename from VENDORS/Milesight/AM308/ChirpStack/uplink/result.json
rename to VENDORS/Milesight/AM308/ChirpStack/uplink/result_2.json
index b50402e..af7936d 100644
--- a/VENDORS/Milesight/AM308/ChirpStack/uplink/result.json
+++ b/VENDORS/Milesight/AM308/ChirpStack/uplink/result_2.json
@@ -1,49 +1,48 @@
{
- "deviceName": "Device name",
- "deviceType": "Chirpstack default device profile",
- "groupName": "8-in-1 IAQ Sensor",
+ "deviceName": "AM308 Device name",
+ "deviceType": "AM308",
"attributes": {
- "deduplicationId": "57433366-50a6-4dc2-8145-2df1bbc70d9e",
- "tenantId": "52f14cd4-c6f1-4fbd-8f87-4025e1d49242",
- "tenantName": "ChirpStack",
+ "eui": "1000000000000001",
+ "devAddr": "20000001",
+ "fPort": 85,
"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",
- "devEui": "1000000000000001",
- "devAddr": "20000001",
- "fPort": 85,
"frequency": 868500000,
"bandwidth": 125000,
"spreadingFactor": 7,
"codeRate": "CR_4_5"
},
- "telemetry": {
+ "telemetry": [{
"ts": 1684741625404,
"values": {
- "hexString": "0175550367EE0004687C05000106CB02077DA803087D2500097366270A7D04000B7D20000C7D3000",
"battery": 85,
"temperature": 23.8,
- "humidity": 62,
+ "humidity": 62.0,
"pir": "trigger",
"light_level": 2,
"co2": 936,
"tvoc": 37,
"pressure": 1008.6,
- "hcho": 0.04,
"pm2_5": 32,
"pm10": 48,
- "dr": 5,
- "fCnt": 4,
- "confirmed": false,
- "gatewayId": "6a7e111a10000000",
- "uplinkId": 24022,
- "rssi": -35,
- "snr": 11.5,
- "channel": 2,
- "rfChain": 1,
- "context": "EFwMtA==",
- "crcStatus": "CRC_OK"
+ "beep": "no"
+ }
+ }, {
+ "ts": 214578533,
+ "values": {
+ "temperature": 5760.0,
+ "humidity": 14080.0,
+ "trigger": "idle",
+ "light_level": 100,
+ "co2": 49665,
+ "tvoc": 2560,
+ "pressure": 3869.5,
+ "pm2_5": 6400,
+ "pm10": 12800
}
- }
+ }]
}
\ No newline at end of file
diff --git a/VENDORS/Milesight/AM308/Loriot/uplink/converter.json b/VENDORS/Milesight/AM308/Loriot/uplink/converter.json
new file mode 100644
index 0000000..c1013dc
--- /dev/null
+++ b/VENDORS/Milesight/AM308/Loriot/uplink/converter.json
@@ -0,0 +1,25 @@
+{
+ "name": "Uplink data converter for Loriot integration AM-308",
+ "type": "UPLINK",
+ "debugMode": true,
+ "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 = \"AM308 \" + data.EUI;\nvar deviceType = \"AM308\";\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 // --- Decoding code --- //\n var decoded = {};\n var historyData = {};\n for (var i = 0; i < input.length - 2; ) {\n var channel_id = input[i++] & 0xff;\n var channel_type = input[i++] & 0xff;\n // BATTERY\n if(channel_id === 0x01 && channel_type === 0x75) {\n decoded.battery = input[i]; \n i += 1;\n }\n // TEMPERATURE\n if (channel_id === 0x03 && channel_type === 0x67) {\n // ℃\n decoded.temperature = parseBytesToInt(input, i, 2, false) / 10;\n // ℉\n // decoded.temperature = parseBytesToInt(input, i, 2, false) / 10 * 1.8 + 32;\n i += 2;\n }\n // HUMIDITY\n if (channel_id === 0x04 && channel_type === 0x68) {\n decoded.humidity = parseBytesToInt(input, i, 1, false) / 2;\n i += 1;\n }\n // PIR\n if (channel_id === 0x05 && channel_type === 0x00) {\n decoded.pir = input[i] === 1 ? \"trigger\" : \"idle\";\n i += 1;\n }\n // LIGHT\n if (channel_id === 0x06 && channel_type === 0xcb) {\n decoded.light_level = input[i];\n i += 1;\n }\n // CO2\n if (channel_id === 0x07 && channel_type === 0x7d) {\n decoded.co2 = parseBytesToInt(input, i, 2, false);\n i += 2;\n }\n // TVOC\n if (channel_id === 0x08 && channel_type === 0x7d) {\n decoded.tvoc = parseBytesToInt(input, i, 2, false);\n i += 2;\n }\n // PRESSURE\n if (channel_id === 0x09 && channel_type === 0x73) {\n decoded.pressure = parseBytesToInt(input, i, 2, false) / 10;\n i += 2;\n }\n // PM2_5\n if (channel_id === 0x0b && channel_type === 0x7d) {\n decoded.pm2_5 = parseBytesToInt(input, i, 2, false);\n i += 2;\n }\n // PM10\n else if (channel_id === 0x0c && channel_type === 0x7d) {\n decoded.pm10 = parseBytesToInt(input, i, 2, false);\n i += 2;\n }\n // BEEP\n if (channel_id === 0x0e && channel_type === 0x01) {\n decoded.beep = input[i] === 1 ? \"yes\" : \"no\";\n i += 1;\n }\n // HISTORY DATA\n if (channel_id === 0x20 && channel_type === 0xce) {\n var historyData = {\n ts: parseBytesToInt(input, i, 4, false),\n values: {\n temperature: parseBytesToInt(input, i + 4, 2, false) / 10,\n humidity: parseBytesToInt(input, i + 6, 2, false) / 2,\n pir: input[i + 8] === 1 ? \"trigger\" : \"idle\",\n light_level: input[i + 9],\n co2: parseBytesToInt(input, i + 10, 2, false),\n tvoc: parseBytesToInt(input, i + 12, 2, false),\n pressure: parseBytesToInt(input, i + 14, 2, false) / 10,\n pm2_5: parseBytesToInt(input, i + 16, 2, false),\n pm10: parseBytesToInt(input, i + 18, 2, false)\n }\n };\n \n i += 20;\n }\n }\n \n output.telemetry = [{\n ts: timestamp,\n values: decoded\n }, historyData];\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 addDataToTelemetry = {};\naddDataToTelemetry.rssi = data.rssi;\naddDataToTelemetry.seqno = data.seqno;\naddDataToTelemetry.snr = data.snr;\naddDataToTelemetry.ack = data.ack;\naddDataToTelemetry.toa = data.toa;\naddDataToTelemetry.fCnt = data.fcnt;\n\nvar isIncludeGatewayInfo = metadata[\"includeGatewayInfo\"];\nif(isIncludeGatewayInfo == true) {\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 includeGatewayInfo = [\"ts\", \"gweui\", \"rssi\"];\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 gatewayInfoMsg = {\n deviceName: gatewayDeviceNamePrefix + gatewayInfo.gweui,\n deviceType: gatewayDeviceType,\n telemetry: [{\n \"ts\": parseDateToTimestamp(gatewayInfo.ts),\n \"values\": getDataList(gatewayInfo, includeGatewayInfo)\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 parseDateToTimestamp(dateString) {\n var timestamp = -1;\n if (dateString != null) {\n \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 \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 processTelemetryData(telemetry, addDataToTelemetry) {\n if (telemetry.size > 1) {\n telemetry = addDataToMultipleTelemetries(telemetry, addDataToTelemetry);\n }\n else if (telemetry.size == 1) {\n telemetry = addDataToSingleTelemetry(telemetry, addDataToTelemetry);\n }\n else {\n telemetry.add(addDataToTelemetry);\n }\n \n return telemetry;\n}\n\nfunction addDataToMultipleTelemetries(telemetry, addDataToTelemetry) {\n foreach(element : addDataToTelemetry.entrySet()) {\n if(!telemetry[0][\"values\"].keys.contains(element.key)) {\n telemetry[0][\"values\"][element.key] = element.value;\n }\n if (!telemetry[1][\"values\"].keys.contains(element.key)) {\n telemetry[1][\"values\"][element.key] = element.value;\n }\n }\n \n return telemetry;\n}\n\nfunction addDataToSingleTelemetry(telemetry, addDataToTelemetry) {\n foreach(element : addDataToTelemetry.entrySet()) {\n if(!telemetry[0][\"values\"].keys.contains(element.key)) {\n telemetry[0][\"values\"][element.key] = element.value;\n }\n }\n \n return telemetry;\n}",
+ "encoder": null,
+ "tbelEncoder": null,
+ "updateOnlyKeys": [
+ "fPort",
+ "battery",
+ "ack",
+ "beep",
+ "eui",
+ "frequency",
+ "dr"
+ ]
+ },
+ "additionalInfo": {
+ "description": ""
+ },
+ "edgeTemplate": false
+}
\ No newline at end of file
diff --git a/VENDORS/Milesight/AM308/Loriot/uplink/metadata.json b/VENDORS/Milesight/AM308/Loriot/uplink/metadata.json
new file mode 100644
index 0000000..d991a3c
--- /dev/null
+++ b/VENDORS/Milesight/AM308/Loriot/uplink/metadata.json
@@ -0,0 +1,4 @@
+{
+ "integrationName": "Loriot integration",
+ "includeGatewayInfo": false
+}
\ No newline at end of file
diff --git a/VENDORS/Milesight/AM308/Loriot/uplink/payload_1.json b/VENDORS/Milesight/AM308/Loriot/uplink/payload_1.json
new file mode 100644
index 0000000..518698d
--- /dev/null
+++ b/VENDORS/Milesight/AM308/Loriot/uplink/payload_1.json
@@ -0,0 +1,17 @@
+{
+ "cmd": "rx",
+ "seqno": 3040,
+ "EUI": "1000000000000001",
+ "ts": 1684478801936,
+ "fcnt": 2,
+ "port": 85,
+ "freq": 867500000,
+ "rssi": -21,
+ "snr": 10,
+ "toa": 206,
+ "dr": "SF9 BW125 4/5",
+ "ack": false,
+ "bat": 94,
+ "offline": false,
+ "data": "0175550367ee0004687c05000106cb02077da803087d2500097366270a7d04000b7d20000c7d3000"
+}
\ No newline at end of file
diff --git a/VENDORS/Milesight/AM308/Loriot/uplink/payload_2.json b/VENDORS/Milesight/AM308/Loriot/uplink/payload_2.json
new file mode 100644
index 0000000..0f44476
--- /dev/null
+++ b/VENDORS/Milesight/AM308/Loriot/uplink/payload_2.json
@@ -0,0 +1,17 @@
+{
+ "cmd": "rx",
+ "seqno": 3040,
+ "EUI": "1000000000000001",
+ "ts": 1684478801936,
+ "fcnt": 2,
+ "port": 85,
+ "freq": 867500000,
+ "rssi": -21,
+ "snr": 10,
+ "toa": 206,
+ "dr": "SF9 BW125 4/5",
+ "ack": false,
+ "bat": 94,
+ "offline": false,
+ "data": "0175550367ee0004687c05000106cb02077da803087d2500097366270a7d04000b7d20000c7d30000d7d20000e015520ce6535ca0c00e1006e016401c2000a279700190032"
+}
\ No newline at end of file
diff --git a/VENDORS/Milesight/AM308/Loriot/uplink/result_1.json b/VENDORS/Milesight/AM308/Loriot/uplink/result_1.json
new file mode 100644
index 0000000..b7f6b2f
--- /dev/null
+++ b/VENDORS/Milesight/AM308/Loriot/uplink/result_1.json
@@ -0,0 +1,24 @@
+[{
+ "deviceName": "AM308 1000000000000001",
+ "deviceType": "AM308",
+ "attributes": {
+ "eui": "1000000000000001",
+ "fPort": 85,
+ "frequency": 867500000
+ },
+ "telemetry": [{
+ "ts": 1684478801936,
+ "values": {
+ "battery": 85,
+ "temperature": 23.8,
+ "humidity": 62.0,
+ "pir": "trigger",
+ "light_level": 2,
+ "co2": 936,
+ "tvoc": 37,
+ "pressure": 1008.6,
+ "pm2_5": 32,
+ "pm10": 48
+ }
+ }]
+}]
\ No newline at end of file
diff --git a/VENDORS/Milesight/AM308/Loriot/uplink/result_2.json b/VENDORS/Milesight/AM308/Loriot/uplink/result_2.json
new file mode 100644
index 0000000..04bbf37
--- /dev/null
+++ b/VENDORS/Milesight/AM308/Loriot/uplink/result_2.json
@@ -0,0 +1,38 @@
+[{
+ "deviceName": "AM308 1000000000000001",
+ "deviceType": "AM308",
+ "attributes": {
+ "eui": "1000000000000001",
+ "fPort": 85,
+ "frequency": 867500000
+ },
+ "telemetry": [{
+ "ts": 1684478801936,
+ "values": {
+ "battery": 85,
+ "temperature": 23.8,
+ "humidity": 62.0,
+ "pir": "trigger",
+ "light_level": 2,
+ "co2": 936,
+ "tvoc": 37,
+ "pressure": 1008.6,
+ "pm2_5": 32,
+ "pm10": 48,
+ "beep": "no"
+ }
+ }, {
+ "ts": 214578533,
+ "values": {
+ "temperature": 5760.0,
+ "humidity": 14080.0,
+ "trigger": "idle",
+ "light_level": 100,
+ "co2": 49665,
+ "tvoc": 2560,
+ "pressure": 3869.5,
+ "pm2_5": 6400,
+ "pm10": 12800
+ }
+ }]
+}]
\ No newline at end of file
diff --git a/VENDORS/Milesight/AM308/ThingsStackCommunity/uplink/converter.json b/VENDORS/Milesight/AM308/ThingsStackCommunity/uplink/converter.json
new file mode 100644
index 0000000..19ad5f7
--- /dev/null
+++ b/VENDORS/Milesight/AM308/ThingsStackCommunity/uplink/converter.json
@@ -0,0 +1,35 @@
+{
+ "name": "Uplink data converter for The Things Stack Community integration AM-308",
+ "type": "UPLINK",
+ "debugMode": true,
+ "configuration": {
+ "scriptLang": "TBEL",
+ "decoder": null,
+ "tbelDecoder": "var data = decodeToJson(payload);\n\nvar deviceName = \"AM308 \" + data.end_device_ids.device_id;\nvar deviceType = \"AM308\";\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 historyData = {};\n for (var i = 0; i < input.length - 2; ) {\n var channel_id = input[i++] & 0xff;\n var channel_type = input[i++] & 0xff;\n // BATTERY\n if (channel_id === 0x01 && channel_type === 0x75) {\n decoded.battery = input[i];\n i += 1;\n }\n // TEMPERATURE\n if (channel_id === 0x03 && channel_type === 0x67) {\n // ℃\n decoded.temperature = parseBytesToInt(input, i, 2, false) / 10;\n i += 2;\n // ℉\n // decoded.temperature = parseBytesToInt(input, i, 2, false) / 10 * 1.8 + 32;\n // i +=2;\n }\n // HUMIDITY\n if (channel_id === 0x04 && channel_type === 0x68) {\n decoded.humidity = parseBytesToInt(input, i, 1, false) / 2;\n i += 1;\n }\n // PIR\n if (channel_id === 0x05 && channel_type === 0x00) {\n decoded.pir = input[i] === 1 ? \"trigger\" : \"idle\";\n i += 1;\n }\n // LIGHT\n if (channel_id === 0x06 && channel_type === 0xcb) {\n decoded.light_level = input[i];\n i += 1;\n }\n // CO2\n if (channel_id === 0x07 && channel_type === 0x7d) {\n decoded.co2 = parseBytesToInt(input, i, 2, false);\n i += 2;\n }\n // TVOC\n if (channel_id === 0x08 && channel_type === 0x7d) {\n decoded.tvoc = parseBytesToInt(input, i, 2, false);\n i += 2;\n }\n // PRESSURE\n if (channel_id === 0x09 && channel_type === 0x73) {\n decoded.pressure = parseBytesToInt(input, i, 2, false) / 10;\n i += 2;\n }\n // PM2.5\n if (channel_id === 0x0b && channel_type === 0x7d) {\n decoded.pm2_5 = parseBytesToInt(input, i, 2, false);\n i += 2;\n }\n // PM10\n if (channel_id === 0x0c && channel_type === 0x7d) {\n decoded.pm10 = parseBytesToInt(input, i, 2, false);\n i += 2;\n }\n // BEEP\n if (channel_id === 0x0e && channel_type === 0x01) {\n decoded.beep = input[i] === 1 ? \"yes\" : \"no\";\n i += 1;\n\n }\n \n // HISTORY DATA\n if (channel_id === 0x20 && channel_type === 0xce) {\n var historyData = {\n ts: parseBytesToInt(input, i, 4, false),\n values: {\n temperature: parseBytesToInt(input, i + 4, 2, false) / 10,\n humidity: parseBytesToInt(input, i + 6, 2, false) / 2,\n pir: input[i + 8] === 1 ? \"trigger\" : \"idle\",\n light_level: input[i + 9],\n co2: parseBytesToInt(input, i + 10, 2, false),\n tvoc: parseBytesToInt(input, i + 12, 2, false),\n pressure: parseBytesToInt(input, i + 14, 2, false) / 10,\n pm2_5: parseBytesToInt(input, i + 16, 2, false),\n pm10: parseBytesToInt(input, i + 18, 2, false)\n }\n };\n \n i += 20;\n }\n }\n \n output.telemetry = [{\n ts: timestamp,\n values: decoded\n }, historyData];\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 timestamp = new Date(dateString).getTime();\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 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 = addDataToMultipleTelemetries(telemetry, addDataToTelemetry);\n }\n else if (telemetry.size == 1) {\n telemetry = addDataToSingleTelemetry(telemetry, addDataToTelemetry);\n }\n else {\n telemetry.add(addDataToTelemetry);\n }\n \n return telemetry;\n}\n\nfunction addDataToMultipleTelemetries(telemetry, addDataToTelemetry) {\n foreach(element : addDataToTelemetry.entrySet()) {\n if(!telemetry[0][\"values\"].keys.contains(element.key)) {\n telemetry[0][\"values\"][element.key] = element.value;\n }\n if (!telemetry[1][\"values\"].keys.contains(element.key)) {\n telemetry[1][\"values\"][element.key] = element.value;\n }\n }\n \n return telemetry;\n}\n\nfunction addDataToSingleTelemetry(telemetry, addDataToTelemetry) {\n foreach(element : addDataToTelemetry.entrySet()) {\n if(!telemetry[0][\"values\"].keys.contains(element.key)) {\n telemetry[0][\"values\"][element.key] = element.value;\n }\n }\n \n return telemetry;\n}",
+ "encoder": null,
+ "tbelEncoder": null,
+ "updateOnlyKeys": [
+ "fPort",
+ "bandwidth",
+ "frequency",
+ "net_id",
+ "cluster_id",
+ "cluster_address",
+ "device_id",
+ "join_eui",
+ "battery",
+ "eui",
+ "beep",
+ "channel",
+ "applicationId",
+ "devAddr",
+ "spreadingFactor",
+ "codeRate",
+ "tenantId"
+ ]
+ },
+ "additionalInfo": {
+ "description": ""
+ },
+ "edgeTemplate": false
+}
\ No newline at end of file
diff --git a/VENDORS/Milesight/AM308/ThingsStackCommunity/uplink/metadata.json b/VENDORS/Milesight/AM308/ThingsStackCommunity/uplink/metadata.json
new file mode 100644
index 0000000..ccdda72
--- /dev/null
+++ b/VENDORS/Milesight/AM308/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/Milesight/AM308/ThingsStackCommunity/uplink/payload_1.json b/VENDORS/Milesight/AM308/ThingsStackCommunity/uplink/payload_1.json
new file mode 100644
index 0000000..defbca5
--- /dev/null
+++ b/VENDORS/Milesight/AM308/ThingsStackCommunity/uplink/payload_1.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": 85,
+ "f_cnt": 10335,
+ "frm_payload": "AXVVA2fuAARofAUAAQbLAgd9qAMIfSUACXNmJwp9BAALfSAADH0wAA19IAAOAVU=",
+ "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/Milesight/AM308/ThingsStackCommunity/uplink/payload_2.json b/VENDORS/Milesight/AM308/ThingsStackCommunity/uplink/payload_2.json
new file mode 100644
index 0000000..1ba5857
--- /dev/null
+++ b/VENDORS/Milesight/AM308/ThingsStackCommunity/uplink/payload_2.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": 85,
+ "f_cnt": 10335,
+ "frm_payload": "AXVVA2fuAARofAUAAQbLAgd9qAMIfSUACXNmJwp9BAALfSAADH0wAA19IAAOAVUgzmU1ygwA4QBuAWQBwgAKJ5cAGQAy",
+ "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/Milesight/AM308/ThingsStackCommunity/uplink/result_1.json b/VENDORS/Milesight/AM308/ThingsStackCommunity/uplink/result_1.json
new file mode 100644
index 0000000..b6f2ec0
--- /dev/null
+++ b/VENDORS/Milesight/AM308/ThingsStackCommunity/uplink/result_1.json
@@ -0,0 +1,36 @@
+{
+ "deviceName": "AM308 eui-1000000000000001",
+ "deviceType": "AM308",
+ "attributes": {
+ "eui": "1000000000000001",
+ "fPort": 85,
+ "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": {
+ "battery": 85,
+ "temperature": 23.8,
+ "humidity": 62.0,
+ "pir": "trigger",
+ "light_level": 2,
+ "co2": 936,
+ "tvoc": 37,
+ "pressure": 1008.6,
+ "pm2_5": 32,
+ "pm10": 48,
+ "beep": "no"
+ }
+ }]
+}
\ No newline at end of file
diff --git a/VENDORS/Milesight/AM308/ThingsStackCommunity/uplink/result_2.json b/VENDORS/Milesight/AM308/ThingsStackCommunity/uplink/result_2.json
new file mode 100644
index 0000000..97ca543
--- /dev/null
+++ b/VENDORS/Milesight/AM308/ThingsStackCommunity/uplink/result_2.json
@@ -0,0 +1,49 @@
+{
+ "deviceName": "AM308 eui-1000000000000001",
+ "deviceType": "AM308",
+ "attributes": {
+ "eui": "1000000000000001",
+ "fPort": 85,
+ "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": {
+ "battery": 85,
+ "temperature": 23.8,
+ "humidity": 62.0,
+ "pir": "trigger",
+ "light_level": 2,
+ "co2": 936,
+ "tvoc": 37,
+ "pressure": 1008.6,
+ "pm2_5": 32,
+ "pm10": 48,
+ "beep": "no"
+ }
+ }, {
+ "ts": 214578533,
+ "values": {
+ "temperature": 5760.0,
+ "humidity": 14080.0,
+ "trigger": "idle",
+ "light_level": 100,
+ "co2": 49665,
+ "tvoc": 2560,
+ "pressure": 3869.5,
+ "pm2_5": 6400,
+ "pm10": 12800
+ }
+ }]
+}
\ No newline at end of file
diff --git a/VENDORS/Milesight/AM308/ThingsStackIndustries/uplink/converter.json b/VENDORS/Milesight/AM308/ThingsStackIndustries/uplink/converter.json
new file mode 100644
index 0000000..8766df6
--- /dev/null
+++ b/VENDORS/Milesight/AM308/ThingsStackIndustries/uplink/converter.json
@@ -0,0 +1,36 @@
+{
+ "name": "Uplink data converter for The Things Stack Industries integration AM-308",
+ "type": "UPLINK",
+ "debugMode": true,
+ "configuration": {
+ "scriptLang": "TBEL",
+ "decoder": null,
+ "tbelDecoder": "var data = decodeToJson(payload);\n\nvar deviceName = \"AM308 \" + data.end_device_ids.device_id;\nvar deviceType = \"AM308\";\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 // --- Timestamp parsing\n var decoded = {};\n var historyData = {};\n for (var i = 0; i < input.length - 2; ) {\n var channel_id = input[i++] & 0xff;\n var channel_type = input[i++] & 0xff;\n // BATTERY\n if(channel_id === 0x01 && channel_type === 0x75) {\n decoded.battery = input[i]; \n i += 1;\n }\n // TEMPERATURE\n if (channel_id === 0x03 && channel_type === 0x67) {\n // ℃\n decoded.temperature = parseBytesToInt(input, i, 2, false) / 10;\n // ℉\n // decoded.temperature = parseBytesToInt(input, i, 2, false) / 10 * 1.8 + 32;\n i += 2;\n }\n // HUMIDITY\n if (channel_id === 0x04 && channel_type === 0x68) {\n decoded.humidity = parseBytesToInt(input, i, 1, false) / 2;\n i += 1;\n }\n // PIR\n if (channel_id === 0x05 && channel_type === 0x00) {\n decoded.pir = input[i] === 1 ? \"trigger\" : \"idle\";\n i += 1;\n }\n // LIGHT\n if (channel_id === 0x06 && channel_type === 0xcb) {\n decoded.light_level = input[i];\n i += 1;\n }\n // CO2\n if (channel_id === 0x07 && channel_type === 0x7d) {\n decoded.co2 = parseBytesToInt(input, i, 2, false);\n i += 2;\n }\n // TVOC\n if (channel_id === 0x08 && channel_type === 0x7d) {\n decoded.tvoc = parseBytesToInt(input, i, 2, false);\n i += 2;\n }\n // PRESSURE\n if (channel_id === 0x09 && channel_type === 0x73) {\n decoded.pressure = parseBytesToInt(input, i, 2, false) / 10;\n i += 2;\n }\n // PM2_5\n if (channel_id === 0x0b && channel_type === 0x7d) {\n decoded.pm2_5 = parseBytesToInt(input, i, 2, false);\n i += 2;\n }\n // PM10\n else if (channel_id === 0x0c && channel_type === 0x7d) {\n decoded.pm10 = parseBytesToInt(input, i, 2, false);\n i += 2;\n }\n // BEEP\n if (channel_id === 0x0e && channel_type === 0x01) {\n decoded.beep = input[i] === 1 ? \"yes\" : \"no\";\n i += 1;\n }\n // HISTORY DATA (AM308)\n if (channel_id === 0x20 && channel_type === 0xce) {\n var historyData = {\n ts: parseBytesToInt(input, i, 4, false),\n values: {\n temperature: parseBytesToInt(input, i + 4, 2, false) / 10,\n humidity: parseBytesToInt(input, i + 6, 2, false) / 2,\n pir: input[i + 8] === 1 ? \"trigger\" : \"idle\",\n light_level: input[i + 9],\n co2: parseBytesToInt(input, i + 10, 2, false),\n tvoc: parseBytesToInt(input, i + 12, 2, false),\n pressure: parseBytesToInt(input, i + 14, 2, false) / 10,\n pm2_5: parseBytesToInt(input, i + 16, 2, false),\n pm10: parseBytesToInt(input, i + 18, 2, false)\n }\n };\n \n i += 20;\n }\n }\n \n output.telemetry = [{\n ts: timestamp,\n values: decoded\n }, historyData];\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_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\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\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 timestamp = new Date(dateString).getTime();\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 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 = addDataToMultipleTelemetries(telemetry, addDataToTelemetry);\n }\n else if (telemetry.size == 1) {\n telemetry = addDataToSingleTelemetry(telemetry, addDataToTelemetry);\n }\n else {\n telemetry.add(addDataToTelemetry);\n }\n \n return telemetry;\n}\n\nfunction addDataToMultipleTelemetries(telemetry, addDataToTelemetry) {\n foreach(element : addDataToTelemetry.entrySet()) {\n if(!telemetry[0][\"values\"].keys.contains(element.key)) {\n telemetry[0][\"values\"][element.key] = element.value;\n }\n if (!telemetry[1][\"values\"].keys.contains(element.key)) {\n telemetry[1][\"values\"][element.key] = element.value;\n }\n }\n \n return telemetry;\n}\n\nfunction addDataToSingleTelemetry(telemetry, addDataToTelemetry) {\n foreach(element : addDataToTelemetry.entrySet()) {\n if(!telemetry[0][\"values\"].keys.contains(element.key)) {\n telemetry[0][\"values\"][element.key] = element.value;\n }\n }\n \n return telemetry;\n}",
+ "encoder": null,
+ "tbelEncoder": null,
+ "updateOnlyKeys": [
+ "fPort",
+ "bandwidth",
+ "frequency",
+ "net_id",
+ "cluster_id",
+ "cluster_address",
+ "tenant_address",
+ "device_id",
+ "join_eui",
+ "battery",
+ "eui",
+ "beep",
+ "channel",
+ "devAddr",
+ "spreadingFactor",
+ "codeRate",
+ "tenantId",
+ "applicationId"
+ ]
+ },
+ "additionalInfo": {
+ "description": ""
+ },
+ "edgeTemplate": false
+}
\ No newline at end of file
diff --git a/VENDORS/Milesight/AM308/ThingsStackIndustries/uplink/metadata.json b/VENDORS/Milesight/AM308/ThingsStackIndustries/uplink/metadata.json
new file mode 100644
index 0000000..074bdab
--- /dev/null
+++ b/VENDORS/Milesight/AM308/ThingsStackIndustries/uplink/metadata.json
@@ -0,0 +1,4 @@
+{
+ "integrationName": "The Things Stack Industries integration new",
+ "includeGatewayInfo": false
+}
\ No newline at end of file
diff --git a/VENDORS/Milesight/AM308/ThingsStackIndustries/uplink/payload_1.json b/VENDORS/Milesight/AM308/ThingsStackIndustries/uplink/payload_1.json
new file mode 100644
index 0000000..25906c4
--- /dev/null
+++ b/VENDORS/Milesight/AM308/ThingsStackIndustries/uplink/payload_1.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": 85,
+ "f_cnt": 5017,
+ "frm_payload": "AXVVA2fuAARofAUAAQbLAgd9qAMIfSUACXNmJwp9BAALfSAADH0wAA19IAAOAVU=",
+ "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/Milesight/AM308/ThingsStackIndustries/uplink/payload_2.json b/VENDORS/Milesight/AM308/ThingsStackIndustries/uplink/payload_2.json
new file mode 100644
index 0000000..36851d0
--- /dev/null
+++ b/VENDORS/Milesight/AM308/ThingsStackIndustries/uplink/payload_2.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": 85,
+ "f_cnt": 5017,
+ "frm_payload": "AXVVA2fuAARofAUAAQbLAgd9qAMIfSUACXNmJwp9BAALfSAADH0wAA19IAAOAVUgzmU1ygwA4QBuAWQBwgAKJ5cAGQAy",
+ "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/Milesight/AM308/ThingsStackIndustries/uplink/result_1.json b/VENDORS/Milesight/AM308/ThingsStackIndustries/uplink/result_1.json
new file mode 100644
index 0000000..5ea2582
--- /dev/null
+++ b/VENDORS/Milesight/AM308/ThingsStackIndustries/uplink/result_1.json
@@ -0,0 +1,36 @@
+{
+ "deviceName": "AM308 eui-1000000000000001",
+ "deviceType": "AM308",
+ "attributes": {
+ "eui": "1000000000000001",
+ "fPort": 85,
+ "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_adress": "eu1.cloud.thethings.industries",
+ "bandwidth": 125000,
+ "frequency": "868500000"
+ },
+ "telemetry": [{
+ "ts": 1684398325906,
+ "values": {
+ "battery": 85,
+ "temperature": 23.8,
+ "humidity": 62.0,
+ "pir": "trigger",
+ "light_level": 2,
+ "co2": 936,
+ "tvoc": 37,
+ "pressure": 1008.6,
+ "pm2_5": 32,
+ "pm10": 48,
+ "beep": "no"
+ }
+ }]
+}
\ No newline at end of file
diff --git a/VENDORS/Milesight/AM308/ThingsStackIndustries/uplink/result_2.json b/VENDORS/Milesight/AM308/ThingsStackIndustries/uplink/result_2.json
new file mode 100644
index 0000000..5605f54
--- /dev/null
+++ b/VENDORS/Milesight/AM308/ThingsStackIndustries/uplink/result_2.json
@@ -0,0 +1,49 @@
+{
+ "deviceName": "AM308 eui-1000000000000001",
+ "deviceType": "AM308",
+ "attributes": {
+ "eui": "1000000000000001",
+ "fPort": 85,
+ "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_adress": "eu1.cloud.thethings.industries",
+ "bandwidth": 125000,
+ "frequency": "868500000"
+ },
+ "telemetry": [{
+ "ts": 1684398325906,
+ "values": {
+ "battery": 85,
+ "temperature": 23.8,
+ "humidity": 62.0,
+ "pir": "trigger",
+ "light_level": 2,
+ "co2": 936,
+ "tvoc": 37,
+ "pressure": 1008.6,
+ "pm2_5": 32,
+ "pm10": 48,
+ "beep": "no"
+ }
+ }, {
+ "ts": 214578533,
+ "values": {
+ "temperature": 5760.0,
+ "humidity": 14080.0,
+ "trigger": "idle",
+ "light_level": 100,
+ "co2": 49665,
+ "tvoc": 2560,
+ "pressure": 3869.5,
+ "pm2_5": 6400,
+ "pm10": 12800
+ }
+ }]
+}
\ No newline at end of file
diff --git a/VENDORS/Milesight/AM308/guide.md b/VENDORS/Milesight/AM308/guide.md
index b86b150..e3bce3f 100644
--- a/VENDORS/Milesight/AM308/guide.md
+++ b/VENDORS/Milesight/AM308/guide.md
@@ -11,11 +11,9 @@
| Light Level | 0x06 | 0xCB | 1 | light_level(1B) |
| CO2 | 0x07 | 0x7D | 2 | co2(2B)
co2, unit: ppm |
| tVOC | 0x08 | 0x7D | 2 | tvoc(2B) |
-| Pressure | 0x09 | 0x73 | 2 | pressure(2B)
pressure, unit: hPa |
-| HCHO | 0x0A | 0x7D | 2 | hcho(2B)
hcho, unit: mg/m3 |
+| Pressure | 0x09 | 0x73 | 2 | pressure(2B)
pressure, unit: hPa | |
| PM2.5 | 0x0B | 0x7D | 2 | pm2_5(2B)
pm2_5, unit: ug/m3 |
-| PM10 | 0x0C | 0x7D | 2 | pm10(2B)
pm10, unit: ug/m3 |
-| O3 | 0x0D | 0x7D | 2 | o3(2B)
o3, unit: ppm |
+| PM10 | 0x0C | 0x7D | 2 | pm10(2B)
pm10, unit: ug/m3 | |
| Beep | 0x0E | 0x01 | 1 | beep(1B) |
| Historical Data
(AM308) | 0x20 | 0xCE | 20 | timestamp(4B) + temperature(2B) + humidity(2B) + pir(1B) + light_level(1B) + co2(2B) + tvoc(2B) + pressure(2B) + pm2_5(2B) + pm10(2B) |
diff --git a/VENDORS/Milesight/AM308/info.json b/VENDORS/Milesight/AM308/info.json
index b35e659..40bae59 100644
--- a/VENDORS/Milesight/AM308/info.json
+++ b/VENDORS/Milesight/AM308/info.json
@@ -1,5 +1,5 @@
{
"url": "https://www.milesight.com/iot/product/lorawan-sensor/am319",
- "label": "AM308(L): 8-in-1 IAQ Sensor",
+ "label": "AM308: 8-in-1 IAQ Sensor",
"description": "AM308 is a 8-in-1 ambience monitoring sensor including the\nmeasurement of temperature, \nhumidity, \nCO2 concentration,\nTVOC, \npressure, \nlight, \nmotion, \nPM2.5 & PM10, etc. \nIt can show visually with different modes on the E-ink screen in real-time and transmit to network server using LoRaWAN® technology."
}
diff --git a/data_converters_validator.py b/data_converters_validator.py
index 65fbeca..ba6bf9e 100644
--- a/data_converters_validator.py
+++ b/data_converters_validator.py
@@ -19,12 +19,12 @@ def find_payload_and_result_pairs(directory):
pairs = []
- if 'payload.json' in payloads and 'result.json' in results:
- pairs.append(('payload.json', 'result.json'))
- elif 'payload.json' in payloads and 'result.json' not in results:
- print(f"Validation failed for {directory}: payload.json is present, but result.json is missing.")
- elif 'result.json' in results and 'payload.json' not in payloads:
- print(f"Validation failed for {directory}: result.json is present, but payload.json is missing.")
+ if 'payload_1.json' in payloads and 'result_1.json' in results:
+ pairs.append(('payload_1.json', 'result_1.json'))
+ elif 'payload_1.json' in payloads and 'result_1.json' not in results:
+ print(f"Validation failed for {directory}: payload_1.json is present, but result_1.json is missing.")
+ elif 'result_1.json' in results and 'payload_1.json' not in payloads:
+ print(f"Validation failed for {directory}: result_1.json is present, but payload_1.json is missing.")
for payload_file in payloads:
if re.match(r'payload_\d+\.json', payload_file):