From cf37fa2d81d29afe0c96262da0732b58f4fa9039 Mon Sep 17 00:00:00 2001 From: thingsboard017 Date: Mon, 23 Dec 2024 19:03:47 +0200 Subject: [PATCH] Added 4 LNS integrations for Comfort --- .../Comfort/ChirpStack/uplink/converter.json | 39 +++++++++ .../Comfort/ChirpStack/uplink/metadata.json | 4 + .../Comfort/ChirpStack/uplink/payload.json | 48 +++++++++++ .../Comfort/ChirpStack/uplink/result.json | 27 ++++++ .../Comfort/LORIOT/uplink/converter.json | 29 +++++++ .../Comfort/LORIOT/uplink/metadata.json | 4 + .../Comfort/LORIOT/uplink/payload.json | 17 ++++ .../Comfort/LORIOT/uplink/result.json | 17 ++++ .../uplink/converter.json | 39 +++++++++ .../ThingsStackCommunity/uplink/metadata.json | 4 + .../ThingsStackCommunity/uplink/payload.json | 54 ++++++++++++ .../ThingsStackCommunity/uplink/result.json | 28 +++++++ .../uplink/converter.json | 40 +++++++++ .../uplink/metadata.json | 4 + .../ThingsStackIndustries/uplink/payload.json | 77 ++++++++++++++++++ .../ThingsStackIndustries/uplink/result.json | 28 +++++++ VENDORS/Tektelic/Comfort/info.json | 5 ++ VENDORS/Tektelic/Comfort/photo.png | Bin 0 -> 81758 bytes 18 files changed, 464 insertions(+) create mode 100644 VENDORS/Tektelic/Comfort/ChirpStack/uplink/converter.json create mode 100644 VENDORS/Tektelic/Comfort/ChirpStack/uplink/metadata.json create mode 100644 VENDORS/Tektelic/Comfort/ChirpStack/uplink/payload.json create mode 100644 VENDORS/Tektelic/Comfort/ChirpStack/uplink/result.json create mode 100644 VENDORS/Tektelic/Comfort/LORIOT/uplink/converter.json create mode 100644 VENDORS/Tektelic/Comfort/LORIOT/uplink/metadata.json create mode 100644 VENDORS/Tektelic/Comfort/LORIOT/uplink/payload.json create mode 100644 VENDORS/Tektelic/Comfort/LORIOT/uplink/result.json create mode 100644 VENDORS/Tektelic/Comfort/ThingsStackCommunity/uplink/converter.json create mode 100644 VENDORS/Tektelic/Comfort/ThingsStackCommunity/uplink/metadata.json create mode 100644 VENDORS/Tektelic/Comfort/ThingsStackCommunity/uplink/payload.json create mode 100644 VENDORS/Tektelic/Comfort/ThingsStackCommunity/uplink/result.json create mode 100644 VENDORS/Tektelic/Comfort/ThingsStackIndustries/uplink/converter.json create mode 100644 VENDORS/Tektelic/Comfort/ThingsStackIndustries/uplink/metadata.json create mode 100644 VENDORS/Tektelic/Comfort/ThingsStackIndustries/uplink/payload.json create mode 100644 VENDORS/Tektelic/Comfort/ThingsStackIndustries/uplink/result.json create mode 100644 VENDORS/Tektelic/Comfort/info.json create mode 100644 VENDORS/Tektelic/Comfort/photo.png diff --git a/VENDORS/Tektelic/Comfort/ChirpStack/uplink/converter.json b/VENDORS/Tektelic/Comfort/ChirpStack/uplink/converter.json new file mode 100644 index 00000000..d0432d74 --- /dev/null +++ b/VENDORS/Tektelic/Comfort/ChirpStack/uplink/converter.json @@ -0,0 +1,39 @@ +{ + "name": "ChirpStack Uplink Decoder for Comfort", + "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 = \"Comfort\";\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 == 0xBA) {\n decoded.battery_voltage = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 2;\n }\n else if(key_1 == 0x01 && key_2 == 0x00) {\n val = parseBytesToInt(input, i, 1);\n switch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.hall_effect_state = \"Magnet Present\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.hall_effect_state = \"Magnet Absent\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.hall_effect_state = \"Invalid\";\n\t\t\t\t}\n \n i += 1;\n }\n else if(key_1 == 0x08 && key_2 == 0x04) {\n decoded.hall_effect_count = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key_1 == 0x0C && key_2 == 0x00) {\n var val = parseBytesToInt(input, i, 1);\n \n switch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.impact_alarm = \"Impact Alarm Inactive\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.impact_alarm = \"Impact Alarm Active\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.impact_alarm = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n i += 1;\n }\n else if(key_1 == 0x05 && key_2 == 0x02) {\n decoded.impact_magnitude = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 2;\n }\n else if(key_1 == 0x07 && key_2 == 0x71) {\n decoded.accelerationX = toFixed(parseBytesToInt(input, i + 4, 2) * 0.001, 3);\n decoded.accelerationY = toFixed(parseBytesToInt(input, i + 2, 2) * 0.001, 3);\n decoded.accelerationZ = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 6;\n }\n else if(key_1 == 0x0E && key_2 == 0x00) {\n var val = parseBytesToInt(input, i, 1);\n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.extconnector_state = \"Low(short-circuit)\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.extconnector_state = \"High(open-circuit)\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.extconnector_state = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if(key_1 == 0x0F && key_2 == 0x04) {\n decoded.extconnector_count = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key_1 == 0x12 && key_2 == 0x04) {\n decoded.extconnector_total_count = parseBytesToInt(input, i, 4);\n i += 4;\n }\n else if(key_1 == 0x11 && key_2 == 0x02) {\n decoded.extconnector_analog = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 2;\n }\n else if(key_1 == 0x0B && key_2 == 0x67) {\n decoded.mcu_temperature = toFixed(parseBytesToInt(input, i, 2) * 0.1, 1);\n i += 2;\n }\n else if(key_1 == 0x03 && key_2 == 0x67) {\n decoded.ambient_temperature = toFixed(parseBytesToInt(input, i, 2) * 0.1, 1);\n i += 2;\n }\n else if(key_1 == 0x04 && key_2 == 0x68) {\n decoded.relative_humidity = toFixed(parseBytesToInt(input, i, 1) * 0.5, 1);\n i += 1;\n }\n else if(key_1 == 0x02 && key_2 == 0x00) {\n var val = parseBytesToInt(input, i, 1);\n \n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.light_detected = \"Dark\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.light_detected = \"Bright\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.light_detected = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if(key_1 == 0x10 && key_2 == 0x02) {\n decoded.light_intensity = parseBytesToInt(input, i, 1);\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if(key_1 == 0x0A && key_2 == 0x00) {\n var val = parseBytesToInt(input, i, 1);\n\t\t\t\t\n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.motion_event_state = \"None\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.motion_event_state = \"Detected\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.motion_event_state = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if(key_1 == 0x0D && key_2 == 0x04) {\n decoded.motion_event_count = parseBytesToInt(input, i, 2);\n\t\t\t\t\n\t\t\t\ti += 2;\n }\n }\n }\n else if(fPort == 0x05) {\n var key_1 = input[i++] & 0xff;\n var key_2 = input[i++] & 0xff;\n \n if(key_1 == 0x40 && key_2 == 0x06) {\n var val = input[i + 5];\n\t\t\tswitch (val){\n\t\t\t\tcase 1:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"Push-button reset\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"DL command rest\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase 4:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"Independent watchdog reset\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase 8:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"Power loss reset\";\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"Invalid\";\n\t\t\t}\n\t\t\tdecoded_data.reset_diagnostics_power_loss_reset_count = input[i + 3];\n\t\t\tdecoded_data.reset_diagnostics_watchdog_reset_count = input[i + 2];\n\t\t\tdecoded_data.reset_diagnostics_dl_reset_count = input[i + 1];\n\t\t\tdecoded_data.reset_diagnostics_button_reset_count = input[i];\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 var val = (((input[i] << 8) | input[i + 1]) >> 15) & 1;\n switch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.loramac_join_mode = \"ABP\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.loramac_join_mode = \"OTAA\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.loramac_join_mode = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if (key == 0x11) {\n\t\t\t\tvar val = (((input[i] << 8) | input[i + 1]) >> 3) & 1;\n\t\t\t\toutput.attributes.adr = getEnableStatus(val);\n\t\t\t\t\n\t\t\t\tvar val = (((input[i] << 8) | input[i + 1]) >> 2) & 1;\n\t\t\t\toutput.attributes.duty_cycle = getEnableStatus(val);\n\t\t\t\t\n\t\t\t\tvar val = (((input[i] << 8) | input[i + 1]) >> 1) & 1;\n\t\t\t\t\n\t\t\t\tswitch (val) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.sync_word = \"Private\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.sync_word = \"Public\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.sync_word = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tvar val = ((input[i] << 8) | input[i + 1]) & 1;\n\t\t\t\t\n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.confirm_mode = \"Unconfirmed\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.confirm_mode = \"Confirmed\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t output.attributes.confirm_mode = \"Invalid\";\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 == 0x1A) {\n output.attributes.loramac_net_id_lsb = parseBytesToInt(input, i, 2);\n\t\t\t\ti += 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 == 0x22) {\n output.attributes.tick_per_ambient_temperature = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x23) {\n output.attributes.tick_per_relative_humidity = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x24) {\n output.attributes.tick_per_reed_switch = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x25) {\n output.attributes.tick_per_light = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x26) {\n output.attributes.tick_per_accelerometer = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x27) {\n output.attributes.tick_per_mcu_temperature = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x28) {\n output.attributes.tick_per_pir = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x29) {\n output.attributes.tick_per_external_connector = parseBytesToInt(input, i, 2);\n \n i += 2;\n }\n else if(key == 0x2A) {\n var bit0 = (input[i] >> 0) & 1;\n var bit1 = (input[i] >> 1) & 1;\n \n output.attributes.mode.rising_edge_enabled = getEnableStatus(bit0);\n output.attributes.mode.falling_edge_enabled = getEnableStatus(bit1);\n \n i += 1;\n }\n else if(key == 0x2B) {\n\t\t\t\toutput.attributes.reed_switch_count_threshold = parseBytesToInt(input, i, 2);\n\t\t\t\ti += 2;\n }\n else if(key == 0x2C) {\n var bit0 = (input[i] >> 0) & 1;\n var bit1 = (input[i] >> 1) & 1;\n \n output.attributes.reed_values_to_transmit_report_state_enabled = getOnOffStatus(bit0);\n output.attributes.reed_values_to_transmit_report_count_enabled = getOnOffStatus(bit1);\n \n i += 1;\n }\n else if(key == 0x2D) {\n var bit0 = (input[i] >> 0) & 1;\n var bit1 = (input[i] >> 1) & 1;\n var bit7 = (input[i] >> 7) & 1;\n \n output.attributes.external_mode_rising_edge_enabled_ex = getOnOffStatus(bit0);\n output.attributes.external_mode_falling_edge_enabled_ex = getOnOffStatus(bit1);\n \n switch (bit7){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.mode = \"Digital\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t output.attributes.mode = \"Analog\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.mode = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n i += 1;\n }\n else if(key == 0x2E) {\n output.attributes.external_connector_count_threshold = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x2F) {\n var bit0 = (input[i] >> 0) & 1;\n var bit1 = (input[i] >> 1) & 1;\n var bit4 = (input[i] >> 4) & 1;\n \n output.attributes.external_values_to_transmit.report_state_enabled_ex = getOnOffStatus(bit0);\n output.attributes.external_values_to_transmit.report_count_enabled_ex = getOnOffStatus(bit1);\n output.attributes.external_values_to_transmit.count_type = bit4;\n i += 1;\n }\n else if(key == 0x30) {\n output.attributes.impact_event_threshold = toFixed(parseBytesToInt(input, i, 2) * 0x001, 3);\n i += 2;\n }\n else if(key == 0x31) {\n output.attributes.acceleration_event_threshold = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 2;\n }\n else if(key == 0x32) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.values_to_transmit.report_periodic_alarm_enabled = getOnOffStatus(bit0);\n \n var bit1 = (input[i] >> 1) & 1;\n output.attributes.values_to_transmit.report_periodic_magnitude_enabled = getOnOffStatus(bit1);\n \n var bit2 = (input[i] >> 2) & 1;\n output.attributes.values_to_transmit.report_periodic_vector_enabled = getOnOffStatus(bit2);\n \n var bit4 = (input[i] >> 4) & 1;\n output.attributes.values_to_transmit.report_event_magnitude_enabled = getOnOffStatus(bit4);\n \n var bit5 = (input[i] >> 5) & 1;\n output.attributes.values_to_transmit.report_event_vector_enabled = getOnOffStatus(bit5);\n \n i += 1;\n }\n else if(key == 0x33) {\n output.attributes.acceleration_impact_grace_period = parseBytesToInt(input, i, 2);\n\t\t\t\t\n\t\t\t\ti += 2;\n }\n else if (key == 0x34) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.acceleration_mode.impact_threshold_enabled = getEnableStatus(bit0);\n \n var bit1 = (input[i] >> 1) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.acceleration_threshold_enabled = getEnableStatus(bit1);\n\t\t\t\t\n\t\t\t\tvar bit4 = (input[i] >> 4) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.xaxis_enabled = getEnableStatus(bit4);\n\t\t\t\t\n\t\t\t\tvar bit5 = (input[i] >> 5) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.yaxis_enabled = getEnableStatus(bit5);\n\t\t\t\t\n\t\t\t\tvar bit6 = (input[i] >> 6) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.zaxis_enabled = getEnableStatus(bit6);\n\t\t\t\t\n\t\t\t\tvar bit7 = (input[i] >> 7) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.poweron = getOnOffStatus(bit7);\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if (key == 0x35) {\n var val = (input[i] >> 1) & 0x03;\n\t\t\t\t\n\t\t\t\tswitch (val) {\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"1 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"10 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"25 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 4:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"50 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 5:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"100 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 6:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"200 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 7:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"400 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tvar val = (arg >> 4) & 0x03;\n\t\t\t\tswitch (val) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"±2 g\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"±4 g\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"±8 g\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"±16 g\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if (key == 0x36) {\n output.attributes.impact_alarm_grace_period = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x37) {\n output.attributes.impact_alarm_threshold_count = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x38) {\n output.attributes.impact_alarm_threshold_period = parseBytesToInt(input, i, 2);\n \n i += 2;\n }\n else if (key == 0x39) {\n output.attributes.temperature_relative_humidity_sample_period_idle = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if (key == 0x3A) {\n output.attributes.temperature_relative_humidity_sample_period_active = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if (key == 0x3B) {\n output.attributes.temperature_threshold_low_temp_threshold = parseBytesToInt(input, i, 1);\n output.attributes.temperature_threshold_high_temp_threshold = parseBytesToInt(input, i + 1, 1);\n \n i += 2;\n }\n else if (key == 0x3C) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.ambient_temperature_threshold_enabled = getEnableStatus(bit0);\n \n i += 1;\n }\n else if (key == 0x3D) {\n output.attributes.rh_threshold.low_rh_threshold = parseBytesToInt(input, i, 1);\n output.attributes.rh_threshold.high_rh_threshold = parseBytesToInt(input, i + 1, 1);\n \n i += 2;\n }\n else if (key == 0x3E) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.relative_humidity_threshold_enabled =getEnableStatus(bit0);\n \n i += 1;\n }\n else if (key == 0x40) {\n output.attributes.mcu_temperature_sample_period_idle = parseBytesToInt(input, i, 4);\n i += 4;\n }\n else if (key == 0x41) {\n output.attributes.mcu_temperature_sample_period_active = parseBytesToInt(input, i, 4);\n i += 4;\n }\n else if (key == 0x42) {\n output.attributes.mcu_temp_threshold_high_mcu_temp_threshold = input[i + 1];\n output.attributes.mcu_temp_threshold_low_mcu_temp_threshold = input[i];\n i += 4;\n }\n else if (key == 0x43) {\n var val = input[i] & 1;\n output.attributes.mcu_temperature_threshold_enabled = getEnableStatus(val);\n\n\t\t\t\ti += 2;\n }\n else if (key == 0x44) {\n output.attributes.analog_sample_period_idle = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if (key == 0x45) {\n output.attributes.analog_sample_period_active = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if(key == 0x46) {\n output.attributes.analog_threshold_high_analog_threshold = toFixed(parseBytesToInt(input, i + 2, 2) * 0.001, 3);\n\t\t\t\toutput.attributes.analog_threshold_low_analog_threshold = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n\t\t\t\ti += 4;\n }\n else if (key == 0x4A) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.analog_input_threshold_enabled = getEnableStatus(bit0);\n \n i +=1;\n }\n else if (key == 0x47) {\n output.attributes.light_sample_period = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if (key == 0x48 ) {\n var val = (input[i] >> 7) & 1;\n output.attributes.light_thresholds.threshold_enabled = getEnableStatus(val);\n output.attributes.light_thresholds.threshold = input[i] & 0x3F;\n \n i += 1;\n }\n else if (key == 0x49) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.light_values_to_transmit.state_reported = getEnableStatus(bit0);\n \n var bit1 = (input[i] >> 1) & 1;\n output.attributes.light_values_to_transmit.intensity_reported = getEnableStatus(bit1);\n \n i +=1;\n }\n else if (key == 0x50) {\n output.attributes.pir_grace_period = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x51) {\n output.attributes.pir_threshold = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x52) {\n output.attributes.pir_threshold_period = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x53) {\n var bit7 = (input[i] >> 7) & 1;\n output.attributes.pir_mode.motion_count_reported = getEnableStatus(bit7);\n \n var bit6 = (input[i] >> pir_mode) & 1;\n output.attributes.pir_mode.motion_state_reported = getEnableStatus(bit6);\n \n var bit1 = (input[i] >> 1) & 1;\n output.attributes.pir_mode.event_transmission_enabled = getEnableStatus(bit1);\n \n var bit0 = (input[i] >> 0) & 1;\n output.attributes.pir_mode.transducer_enabled = getEnableStatus(bit0);\n \n i += 1;\n }\n else if (key == 0x54) {\n output.attributes.pir_mode.motion_count_reported = input[i + 1];\n output.attributes.pir_mode.motion_state_reported = input[i];\n \n i += 1;\n }\n else if (key == 0x6F) {\n var val = (input[i] >> 0) & 1;\n \n switch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes_resp_to_dl_command_format = \"Invalid-write response format\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes_resp_to_dl_command_format = \"4-byte CRC\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes_resp_to_dl_command_format = \"Invalid\";\n\t\t\t\t}\n \n i += 1;\n }\n else if (key == 0x71) {\n output.attributes.app_major_version = input[i + 6];\n\t\t\t\toutput.attributes.app_minor_version = input[i + 5];\n\t\t\t\toutput.attributes.app_revision = input[i + 4];\n\t\t\t\toutput.attributes.loramac_major_version = input[i + 3];\n\t\t\t\toutput.attributes.loramac_minor_version = input[i + 2];\n\t\t\t\toutput.attributes.loramac_revision = input[i + 1];\n\t\t\t\tvar val = input[i];\n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.region = \"EU868\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.region = \"US915\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\toutput.attributes.region = \"AS923\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\toutput.attributes.region = \"AU915\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 4:\n\t\t\t\t\t\toutput.attributes.region = \"IN865\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 6:\n\t\t\t\t\t\toutput.attributes.region = \"KR920\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 7:\n\t\t\t\t\t\toutput.attributes.region = \"RU864\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.region = \"Invalid\";\n\t\t\t\t}\n\t\t\t\ti += 7;\n }\n }\n }\n else if (fPort == 101) {\n var size = input.length;\n var responses = [];\n \n var index = 0;\n while (index < size) {\n var downlinkFcnt = input[index++] & 0xFF; \n var numInvalidWrites = input[index++] & 0xFF; \n \n if (numInvalidWrites > 0) {\n var invalidRegisters = [];\n for (var i = 0; i < numInvalidWrites; i++) {\n invalidRegisters.add(String.format(\"0x%02X\", input[index + i]));\n }\n index += numInvalidWrites;\n \n responses.add(String.format(\n \"%d Invalid write command(s) from DL:%d for register(s): %s\",\n numInvalidWrites, downlinkFcnt, String.join(\", \", invalidRegisters)\n ));\n } else {\n responses.add(String.format(\"All write commands from DL:%d were successful\", downlinkFcnt));\n }\n }\n \n decoded.response = responses;\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}\n\nfunction getEnableStatus(bit) {\n var enableResult = \"Invalid\";\n \n switch (bit) {\n\t\tcase 0:\n\t\t enableResult = \"Disable\";\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\tenableResult = \"Enable\";\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tenableResult = \"Invalid\";\n\t}\n \n return enableResult;\n}\n\nfunction getOnOffStatus(bit) {\n var reportResult = \"Invalid\";\n \n switch (bit) {\n case 0:\n reportResult = \"Off\";\n break;\n case 1:\n reportResult = \"On\";\n break;\n default:\n reportResult = \"Invalid\";\n }\n \n return reportResult;\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/Comfort/ChirpStack/uplink/metadata.json b/VENDORS/Tektelic/Comfort/ChirpStack/uplink/metadata.json new file mode 100644 index 00000000..23f54b34 --- /dev/null +++ b/VENDORS/Tektelic/Comfort/ChirpStack/uplink/metadata.json @@ -0,0 +1,4 @@ +{ + "integrationName": "ChirpStack integration", + "includeGatewayInfo": "false" +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Comfort/ChirpStack/uplink/payload.json b/VENDORS/Tektelic/Comfort/ChirpStack/uplink/payload.json new file mode 100644 index 00000000..8b17d0f9 --- /dev/null +++ b/VENDORS/Tektelic/Comfort/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": "A2cA4QRoTQC6C54=", + "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/Comfort/ChirpStack/uplink/result.json b/VENDORS/Tektelic/Comfort/ChirpStack/uplink/result.json new file mode 100644 index 00000000..90b77baa --- /dev/null +++ b/VENDORS/Tektelic/Comfort/ChirpStack/uplink/result.json @@ -0,0 +1,27 @@ +{ + "deviceName": "Device name 1000000000000001", + "deviceType": "Comfort", + "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": { + "ambient_temperature": 22.5, + "relative_humidity": 38.5, + "battery_voltage": 2.974 + } + }] +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Comfort/LORIOT/uplink/converter.json b/VENDORS/Tektelic/Comfort/LORIOT/uplink/converter.json new file mode 100644 index 00000000..ae63ead2 --- /dev/null +++ b/VENDORS/Tektelic/Comfort/LORIOT/uplink/converter.json @@ -0,0 +1,29 @@ +{ + "name": "Loriot Uplink Decoder for Comfort", + "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 = \"Comfort\";\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 == 0xBA) {\n decoded.battery_voltage = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 2;\n }\n else if(key_1 == 0x01 && key_2 == 0x00) {\n val = parseBytesToInt(input, i, 1);\n switch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.hall_effect_state = \"Magnet Present\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.hall_effect_state = \"Magnet Absent\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.hall_effect_state = \"Invalid\";\n\t\t\t\t}\n \n i += 1;\n }\n else if(key_1 == 0x08 && key_2 == 0x04) {\n decoded.hall_effect_count = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key_1 == 0x0C && key_2 == 0x00) {\n var val = parseBytesToInt(input, i, 1);\n \n switch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.impact_alarm = \"Impact Alarm Inactive\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.impact_alarm = \"Impact Alarm Active\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.impact_alarm = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n i += 1;\n }\n else if(key_1 == 0x05 && key_2 == 0x02) {\n decoded.impact_magnitude = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 2;\n }\n else if(key_1 == 0x07 && key_2 == 0x71) {\n decoded.accelerationX = toFixed(parseBytesToInt(input, i + 4, 2) * 0.001, 3);\n decoded.accelerationY = toFixed(parseBytesToInt(input, i + 2, 2) * 0.001, 3);\n decoded.accelerationZ = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 6;\n }\n else if(key_1 == 0x0E && key_2 == 0x00) {\n var val = parseBytesToInt(input, i, 1);\n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.extconnector_state = \"Low(short-circuit)\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.extconnector_state = \"High(open-circuit)\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.extconnector_state = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if(key_1 == 0x0F && key_2 == 0x04) {\n decoded.extconnector_count = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key_1 == 0x12 && key_2 == 0x04) {\n decoded.extconnector_total_count = parseBytesToInt(input, i, 4);\n i += 4;\n }\n else if(key_1 == 0x11 && key_2 == 0x02) {\n decoded.extconnector_analog = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 2;\n }\n else if(key_1 == 0x0B && key_2 == 0x67) {\n decoded.mcu_temperature = toFixed(parseBytesToInt(input, i, 2) * 0.1, 1);\n i += 2;\n }\n else if(key_1 == 0x03 && key_2 == 0x67) {\n decoded.ambient_temperature = toFixed(parseBytesToInt(input, i, 2) * 0.1, 1);\n i += 2;\n }\n else if(key_1 == 0x04 && key_2 == 0x68) {\n decoded.relative_humidity = toFixed(parseBytesToInt(input, i, 1) * 0.5, 1);\n i += 1;\n }\n else if(key_1 == 0x02 && key_2 == 0x00) {\n var val = parseBytesToInt(input, i, 1);\n \n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.light_detected = \"Dark\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.light_detected = \"Bright\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.light_detected = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if(key_1 == 0x10 && key_2 == 0x02) {\n decoded.light_intensity = parseBytesToInt(input, i, 1);\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if(key_1 == 0x0A && key_2 == 0x00) {\n var val = parseBytesToInt(input, i, 1);\n\t\t\t\t\n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.motion_event_state = \"None\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.motion_event_state = \"Detected\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.motion_event_state = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if(key_1 == 0x0D && key_2 == 0x04) {\n decoded.motion_event_count = parseBytesToInt(input, i, 2);\n\t\t\t\t\n\t\t\t\ti += 2;\n }\n }\n }\n else if(fPort == 0x05) {\n var key_1 = input[i++] & 0xff;\n var key_2 = input[i++] & 0xff;\n \n if(key_1 == 0x40 && key_2 == 0x06) {\n var val = input[i + 5];\n\t\t\tswitch (val){\n\t\t\t\tcase 1:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"Push-button reset\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"DL command rest\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase 4:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"Independent watchdog reset\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase 8:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"Power loss reset\";\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"Invalid\";\n\t\t\t}\n\t\t\tdecoded_data.reset_diagnostics_power_loss_reset_count = input[i + 3];\n\t\t\tdecoded_data.reset_diagnostics_watchdog_reset_count = input[i + 2];\n\t\t\tdecoded_data.reset_diagnostics_dl_reset_count = input[i + 1];\n\t\t\tdecoded_data.reset_diagnostics_button_reset_count = input[i];\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 var val = (((input[i] << 8) | input[i + 1]) >> 15) & 1;\n switch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.loramac_join_mode = \"ABP\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.loramac_join_mode = \"OTAA\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.loramac_join_mode = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if (key == 0x11) {\n\t\t\t\tvar val = (((input[i] << 8) | input[i + 1]) >> 3) & 1;\n\t\t\t\toutput.attributes.adr = getEnableStatus(val);\n\t\t\t\t\n\t\t\t\tvar val = (((input[i] << 8) | input[i + 1]) >> 2) & 1;\n\t\t\t\toutput.attributes.duty_cycle = getEnableStatus(val);\n\t\t\t\t\n\t\t\t\tvar val = (((input[i] << 8) | input[i + 1]) >> 1) & 1;\n\t\t\t\t\n\t\t\t\tswitch (val) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.sync_word = \"Private\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.sync_word = \"Public\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.sync_word = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tvar val = ((input[i] << 8) | input[i + 1]) & 1;\n\t\t\t\t\n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.confirm_mode = \"Unconfirmed\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.confirm_mode = \"Confirmed\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t output.attributes.confirm_mode = \"Invalid\";\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 == 0x1A) {\n output.attributes.loramac_net_id_lsb = parseBytesToInt(input, i, 2);\n\t\t\t\ti += 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 == 0x22) {\n output.attributes.tick_per_ambient_temperature = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x23) {\n output.attributes.tick_per_relative_humidity = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x24) {\n output.attributes.tick_per_reed_switch = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x25) {\n output.attributes.tick_per_light = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x26) {\n output.attributes.tick_per_accelerometer = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x27) {\n output.attributes.tick_per_mcu_temperature = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x28) {\n output.attributes.tick_per_pir = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x29) {\n output.attributes.tick_per_external_connector = parseBytesToInt(input, i, 2);\n \n i += 2;\n }\n else if(key == 0x2A) {\n var bit0 = (input[i] >> 0) & 1;\n var bit1 = (input[i] >> 1) & 1;\n \n output.attributes.mode.rising_edge_enabled = getEnableStatus(bit0);\n output.attributes.mode.falling_edge_enabled = getEnableStatus(bit1);\n \n i += 1;\n }\n else if(key == 0x2B) {\n\t\t\t\toutput.attributes.reed_switch_count_threshold = parseBytesToInt(input, i, 2);\n\t\t\t\ti += 2;\n }\n else if(key == 0x2C) {\n var bit0 = (input[i] >> 0) & 1;\n var bit1 = (input[i] >> 1) & 1;\n \n output.attributes.reed_values_to_transmit_report_state_enabled = getOnOffStatus(bit0);\n output.attributes.reed_values_to_transmit_report_count_enabled = getOnOffStatus(bit1);\n \n i += 1;\n }\n else if(key == 0x2D) {\n var bit0 = (input[i] >> 0) & 1;\n var bit1 = (input[i] >> 1) & 1;\n var bit7 = (input[i] >> 7) & 1;\n \n output.attributes.external_mode_rising_edge_enabled_ex = getOnOffStatus(bit0);\n output.attributes.external_mode_falling_edge_enabled_ex = getOnOffStatus(bit1);\n \n switch (bit7){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.mode = \"Digital\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t output.attributes.mode = \"Analog\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.mode = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n i += 1;\n }\n else if(key == 0x2E) {\n output.attributes.external_connector_count_threshold = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x2F) {\n var bit0 = (input[i] >> 0) & 1;\n var bit1 = (input[i] >> 1) & 1;\n var bit4 = (input[i] >> 4) & 1;\n \n output.attributes.external_values_to_transmit.report_state_enabled_ex = getOnOffStatus(bit0);\n output.attributes.external_values_to_transmit.report_count_enabled_ex = getOnOffStatus(bit1);\n output.attributes.external_values_to_transmit.count_type = bit4;\n i += 1;\n }\n else if(key == 0x30) {\n output.attributes.impact_event_threshold = toFixed(parseBytesToInt(input, i, 2) * 0x001, 3);\n i += 2;\n }\n else if(key == 0x31) {\n output.attributes.acceleration_event_threshold = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 2;\n }\n else if(key == 0x32) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.values_to_transmit.report_periodic_alarm_enabled = getOnOffStatus(bit0);\n \n var bit1 = (input[i] >> 1) & 1;\n output.attributes.values_to_transmit.report_periodic_magnitude_enabled = getOnOffStatus(bit1);\n \n var bit2 = (input[i] >> 2) & 1;\n output.attributes.values_to_transmit.report_periodic_vector_enabled = getOnOffStatus(bit2);\n \n var bit4 = (input[i] >> 4) & 1;\n output.attributes.values_to_transmit.report_event_magnitude_enabled = getOnOffStatus(bit4);\n \n var bit5 = (input[i] >> 5) & 1;\n output.attributes.values_to_transmit.report_event_vector_enabled = getOnOffStatus(bit5);\n \n i += 1;\n }\n else if(key == 0x33) {\n output.attributes.acceleration_impact_grace_period = parseBytesToInt(input, i, 2);\n\t\t\t\t\n\t\t\t\ti += 2;\n }\n else if (key == 0x34) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.acceleration_mode.impact_threshold_enabled = getEnableStatus(bit0);\n \n var bit1 = (input[i] >> 1) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.acceleration_threshold_enabled = getEnableStatus(bit1);\n\t\t\t\t\n\t\t\t\tvar bit4 = (input[i] >> 4) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.xaxis_enabled = getEnableStatus(bit4);\n\t\t\t\t\n\t\t\t\tvar bit5 = (input[i] >> 5) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.yaxis_enabled = getEnableStatus(bit5);\n\t\t\t\t\n\t\t\t\tvar bit6 = (input[i] >> 6) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.zaxis_enabled = getEnableStatus(bit6);\n\t\t\t\t\n\t\t\t\tvar bit7 = (input[i] >> 7) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.poweron = getOnOffStatus(bit7);\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if (key == 0x35) {\n var val = (input[i] >> 1) & 0x03;\n\t\t\t\t\n\t\t\t\tswitch (val) {\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"1 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"10 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"25 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 4:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"50 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 5:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"100 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 6:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"200 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 7:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"400 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tvar val = (arg >> 4) & 0x03;\n\t\t\t\tswitch (val) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"±2 g\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"±4 g\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"±8 g\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"±16 g\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if (key == 0x36) {\n output.attributes.impact_alarm_grace_period = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x37) {\n output.attributes.impact_alarm_threshold_count = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x38) {\n output.attributes.impact_alarm_threshold_period = parseBytesToInt(input, i, 2);\n \n i += 2;\n }\n else if (key == 0x39) {\n output.attributes.temperature_relative_humidity_sample_period_idle = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if (key == 0x3A) {\n output.attributes.temperature_relative_humidity_sample_period_active = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if (key == 0x3B) {\n output.attributes.temperature_threshold_low_temp_threshold = parseBytesToInt(input, i, 1);\n output.attributes.temperature_threshold_high_temp_threshold = parseBytesToInt(input, i + 1, 1);\n \n i += 2;\n }\n else if (key == 0x3C) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.ambient_temperature_threshold_enabled = getEnableStatus(bit0);\n \n i += 1;\n }\n else if (key == 0x3D) {\n output.attributes.rh_threshold.low_rh_threshold = parseBytesToInt(input, i, 1);\n output.attributes.rh_threshold.high_rh_threshold = parseBytesToInt(input, i + 1, 1);\n \n i += 2;\n }\n else if (key == 0x3E) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.relative_humidity_threshold_enabled =getEnableStatus(bit0);\n \n i += 1;\n }\n else if (key == 0x40) {\n output.attributes.mcu_temperature_sample_period_idle = parseBytesToInt(input, i, 4);\n i += 4;\n }\n else if (key == 0x41) {\n output.attributes.mcu_temperature_sample_period_active = parseBytesToInt(input, i, 4);\n i += 4;\n }\n else if (key == 0x42) {\n output.attributes.mcu_temp_threshold_high_mcu_temp_threshold = input[i + 1];\n output.attributes.mcu_temp_threshold_low_mcu_temp_threshold = input[i];\n i += 4;\n }\n else if (key == 0x43) {\n var val = input[i] & 1;\n output.attributes.mcu_temperature_threshold_enabled = getEnableStatus(val);\n\n\t\t\t\ti += 2;\n }\n else if (key == 0x44) {\n output.attributes.analog_sample_period_idle = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if (key == 0x45) {\n output.attributes.analog_sample_period_active = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if(key == 0x46) {\n output.attributes.analog_threshold_high_analog_threshold = toFixed(parseBytesToInt(input, i + 2, 2) * 0.001, 3);\n\t\t\t\toutput.attributes.analog_threshold_low_analog_threshold = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n\t\t\t\ti += 4;\n }\n else if (key == 0x4A) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.analog_input_threshold_enabled = getEnableStatus(bit0);\n \n i +=1;\n }\n else if (key == 0x47) {\n output.attributes.light_sample_period = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if (key == 0x48 ) {\n var val = (input[i] >> 7) & 1;\n output.attributes.light_thresholds.threshold_enabled = getEnableStatus(val);\n output.attributes.light_thresholds.threshold = input[i] & 0x3F;\n \n i += 1;\n }\n else if (key == 0x49) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.light_values_to_transmit.state_reported = getEnableStatus(bit0);\n \n var bit1 = (input[i] >> 1) & 1;\n output.attributes.light_values_to_transmit.intensity_reported = getEnableStatus(bit1);\n \n i +=1;\n }\n else if (key == 0x50) {\n output.attributes.pir_grace_period = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x51) {\n output.attributes.pir_threshold = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x52) {\n output.attributes.pir_threshold_period = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x53) {\n var bit7 = (input[i] >> 7) & 1;\n output.attributes.pir_mode.motion_count_reported = getEnableStatus(bit7);\n \n var bit6 = (input[i] >> pir_mode) & 1;\n output.attributes.pir_mode.motion_state_reported = getEnableStatus(bit6);\n \n var bit1 = (input[i] >> 1) & 1;\n output.attributes.pir_mode.event_transmission_enabled = getEnableStatus(bit1);\n \n var bit0 = (input[i] >> 0) & 1;\n output.attributes.pir_mode.transducer_enabled = getEnableStatus(bit0);\n \n i += 1;\n }\n else if (key == 0x54) {\n output.attributes.pir_mode.motion_count_reported = input[i + 1];\n output.attributes.pir_mode.motion_state_reported = input[i];\n \n i += 1;\n }\n else if (key == 0x6F) {\n var val = (input[i] >> 0) & 1;\n \n switch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes_resp_to_dl_command_format = \"Invalid-write response format\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes_resp_to_dl_command_format = \"4-byte CRC\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes_resp_to_dl_command_format = \"Invalid\";\n\t\t\t\t}\n \n i += 1;\n }\n else if (key == 0x71) {\n output.attributes.app_major_version = input[i + 6];\n\t\t\t\toutput.attributes.app_minor_version = input[i + 5];\n\t\t\t\toutput.attributes.app_revision = input[i + 4];\n\t\t\t\toutput.attributes.loramac_major_version = input[i + 3];\n\t\t\t\toutput.attributes.loramac_minor_version = input[i + 2];\n\t\t\t\toutput.attributes.loramac_revision = input[i + 1];\n\t\t\t\tvar val = input[i];\n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.region = \"EU868\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.region = \"US915\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\toutput.attributes.region = \"AS923\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\toutput.attributes.region = \"AU915\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 4:\n\t\t\t\t\t\toutput.attributes.region = \"IN865\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 6:\n\t\t\t\t\t\toutput.attributes.region = \"KR920\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 7:\n\t\t\t\t\t\toutput.attributes.region = \"RU864\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.region = \"Invalid\";\n\t\t\t\t}\n\t\t\t\ti += 7;\n }\n }\n }\n else if (fPort == 101) {\n var size = input.length;\n var responses = [];\n \n var index = 0;\n while (index < size) {\n var downlinkFcnt = input[index++] & 0xFF; \n var numInvalidWrites = input[index++] & 0xFF; \n \n if (numInvalidWrites > 0) {\n var invalidRegisters = [];\n for (var i = 0; i < numInvalidWrites; i++) {\n invalidRegisters.add(String.format(\"0x%02X\", input[index + i]));\n }\n index += numInvalidWrites;\n \n responses.add(String.format(\n \"%d Invalid write command(s) from DL:%d for register(s): %s\",\n numInvalidWrites, downlinkFcnt, String.join(\", \", invalidRegisters)\n ));\n } else {\n responses.add(String.format(\"All write commands from DL:%d were successful\", downlinkFcnt));\n }\n }\n \n decoded.response = responses;\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}\n\nfunction getEnableStatus(bit) {\n var enableResult = \"Invalid\";\n \n switch (bit) {\n\t\tcase 0:\n\t\t enableResult = \"Disable\";\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\tenableResult = \"Enable\";\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tenableResult = \"Invalid\";\n\t}\n \n return enableResult;\n}\n\nfunction getOnOffStatus(bit) {\n var reportResult = \"Invalid\";\n \n switch (bit) {\n case 0:\n reportResult = \"Off\";\n break;\n case 1:\n reportResult = \"On\";\n break;\n default:\n reportResult = \"Invalid\";\n }\n \n return reportResult;\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/Comfort/LORIOT/uplink/metadata.json b/VENDORS/Tektelic/Comfort/LORIOT/uplink/metadata.json new file mode 100644 index 00000000..ae2ee743 --- /dev/null +++ b/VENDORS/Tektelic/Comfort/LORIOT/uplink/metadata.json @@ -0,0 +1,4 @@ +{ + "integrationName": "Loriot integration", + "includeGatewayInfo": "false" +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Comfort/LORIOT/uplink/payload.json b/VENDORS/Tektelic/Comfort/LORIOT/uplink/payload.json new file mode 100644 index 00000000..b930d85b --- /dev/null +++ b/VENDORS/Tektelic/Comfort/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": "036700E104684D00BA0B9E" +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Comfort/LORIOT/uplink/result.json b/VENDORS/Tektelic/Comfort/LORIOT/uplink/result.json new file mode 100644 index 00000000..584a1873 --- /dev/null +++ b/VENDORS/Tektelic/Comfort/LORIOT/uplink/result.json @@ -0,0 +1,17 @@ +[{ + "deviceName": "1000000000000001", + "deviceType": "Comfort", + "attributes": { + "eui": "1000000000000001", + "fPort": 10, + "frequency": 867500000 + }, + "telemetry": [{ + "ts": 1684478801936, + "values": { + "ambient_temperature": 22.5, + "relative_humidity": 38.5, + "battery_voltage": 2.974 + } + }] +}] \ No newline at end of file diff --git a/VENDORS/Tektelic/Comfort/ThingsStackCommunity/uplink/converter.json b/VENDORS/Tektelic/Comfort/ThingsStackCommunity/uplink/converter.json new file mode 100644 index 00000000..3b3cc8fe --- /dev/null +++ b/VENDORS/Tektelic/Comfort/ThingsStackCommunity/uplink/converter.json @@ -0,0 +1,39 @@ +{ + "name": "The Things Stack Community Uplink Decoder for Comfort", + "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 = \"Comfort\";\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 == 0xBA) {\n decoded.battery_voltage = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 2;\n }\n else if(key_1 == 0x01 && key_2 == 0x00) {\n val = parseBytesToInt(input, i, 1);\n switch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.hall_effect_state = \"Magnet Present\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.hall_effect_state = \"Magnet Absent\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.hall_effect_state = \"Invalid\";\n\t\t\t\t}\n \n i += 1;\n }\n else if(key_1 == 0x08 && key_2 == 0x04) {\n decoded.hall_effect_count = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key_1 == 0x0C && key_2 == 0x00) {\n var val = parseBytesToInt(input, i, 1);\n \n switch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.impact_alarm = \"Impact Alarm Inactive\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.impact_alarm = \"Impact Alarm Active\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.impact_alarm = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n i += 1;\n }\n else if(key_1 == 0x05 && key_2 == 0x02) {\n decoded.impact_magnitude = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 2;\n }\n else if(key_1 == 0x07 && key_2 == 0x71) {\n decoded.accelerationX = toFixed(parseBytesToInt(input, i + 4, 2) * 0.001, 3);\n decoded.accelerationY = toFixed(parseBytesToInt(input, i + 2, 2) * 0.001, 3);\n decoded.accelerationZ = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 6;\n }\n else if(key_1 == 0x0E && key_2 == 0x00) {\n var val = parseBytesToInt(input, i, 1);\n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.extconnector_state = \"Low(short-circuit)\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.extconnector_state = \"High(open-circuit)\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.extconnector_state = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if(key_1 == 0x0F && key_2 == 0x04) {\n decoded.extconnector_count = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key_1 == 0x12 && key_2 == 0x04) {\n decoded.extconnector_total_count = parseBytesToInt(input, i, 4);\n i += 4;\n }\n else if(key_1 == 0x11 && key_2 == 0x02) {\n decoded.extconnector_analog = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 2;\n }\n else if(key_1 == 0x0B && key_2 == 0x67) {\n decoded.mcu_temperature = toFixed(parseBytesToInt(input, i, 2) * 0.1, 1);\n i += 2;\n }\n else if(key_1 == 0x03 && key_2 == 0x67) {\n decoded.ambient_temperature = toFixed(parseBytesToInt(input, i, 2) * 0.1, 1);\n i += 2;\n }\n else if(key_1 == 0x04 && key_2 == 0x68) {\n decoded.relative_humidity = toFixed(parseBytesToInt(input, i, 1) * 0.5, 1);\n i += 1;\n }\n else if(key_1 == 0x02 && key_2 == 0x00) {\n var val = parseBytesToInt(input, i, 1);\n \n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.light_detected = \"Dark\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.light_detected = \"Bright\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.light_detected = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if(key_1 == 0x10 && key_2 == 0x02) {\n decoded.light_intensity = parseBytesToInt(input, i, 1);\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if(key_1 == 0x0A && key_2 == 0x00) {\n var val = parseBytesToInt(input, i, 1);\n\t\t\t\t\n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.motion_event_state = \"None\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.motion_event_state = \"Detected\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.motion_event_state = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if(key_1 == 0x0D && key_2 == 0x04) {\n decoded.motion_event_count = parseBytesToInt(input, i, 2);\n\t\t\t\t\n\t\t\t\ti += 2;\n }\n }\n }\n else if(fPort == 0x05) {\n var key_1 = input[i++] & 0xff;\n var key_2 = input[i++] & 0xff;\n \n if(key_1 == 0x40 && key_2 == 0x06) {\n var val = input[i + 5];\n\t\t\tswitch (val){\n\t\t\t\tcase 1:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"Push-button reset\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"DL command rest\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase 4:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"Independent watchdog reset\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase 8:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"Power loss reset\";\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"Invalid\";\n\t\t\t}\n\t\t\tdecoded_data.reset_diagnostics_power_loss_reset_count = input[i + 3];\n\t\t\tdecoded_data.reset_diagnostics_watchdog_reset_count = input[i + 2];\n\t\t\tdecoded_data.reset_diagnostics_dl_reset_count = input[i + 1];\n\t\t\tdecoded_data.reset_diagnostics_button_reset_count = input[i];\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 var val = (((input[i] << 8) | input[i + 1]) >> 15) & 1;\n switch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.loramac_join_mode = \"ABP\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.loramac_join_mode = \"OTAA\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.loramac_join_mode = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if (key == 0x11) {\n\t\t\t\tvar val = (((input[i] << 8) | input[i + 1]) >> 3) & 1;\n\t\t\t\toutput.attributes.adr = getEnableStatus(val);\n\t\t\t\t\n\t\t\t\tvar val = (((input[i] << 8) | input[i + 1]) >> 2) & 1;\n\t\t\t\toutput.attributes.duty_cycle = getEnableStatus(val);\n\t\t\t\t\n\t\t\t\tvar val = (((input[i] << 8) | input[i + 1]) >> 1) & 1;\n\t\t\t\t\n\t\t\t\tswitch (val) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.sync_word = \"Private\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.sync_word = \"Public\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.sync_word = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tvar val = ((input[i] << 8) | input[i + 1]) & 1;\n\t\t\t\t\n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.confirm_mode = \"Unconfirmed\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.confirm_mode = \"Confirmed\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t output.attributes.confirm_mode = \"Invalid\";\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 == 0x1A) {\n output.attributes.loramac_net_id_lsb = parseBytesToInt(input, i, 2);\n\t\t\t\ti += 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 == 0x22) {\n output.attributes.tick_per_ambient_temperature = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x23) {\n output.attributes.tick_per_relative_humidity = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x24) {\n output.attributes.tick_per_reed_switch = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x25) {\n output.attributes.tick_per_light = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x26) {\n output.attributes.tick_per_accelerometer = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x27) {\n output.attributes.tick_per_mcu_temperature = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x28) {\n output.attributes.tick_per_pir = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x29) {\n output.attributes.tick_per_external_connector = parseBytesToInt(input, i, 2);\n \n i += 2;\n }\n else if(key == 0x2A) {\n var bit0 = (input[i] >> 0) & 1;\n var bit1 = (input[i] >> 1) & 1;\n \n output.attributes.mode.rising_edge_enabled = getEnableStatus(bit0);\n output.attributes.mode.falling_edge_enabled = getEnableStatus(bit1);\n \n i += 1;\n }\n else if(key == 0x2B) {\n\t\t\t\toutput.attributes.reed_switch_count_threshold = parseBytesToInt(input, i, 2);\n\t\t\t\ti += 2;\n }\n else if(key == 0x2C) {\n var bit0 = (input[i] >> 0) & 1;\n var bit1 = (input[i] >> 1) & 1;\n \n output.attributes.reed_values_to_transmit_report_state_enabled = getOnOffStatus(bit0);\n output.attributes.reed_values_to_transmit_report_count_enabled = getOnOffStatus(bit1);\n \n i += 1;\n }\n else if(key == 0x2D) {\n var bit0 = (input[i] >> 0) & 1;\n var bit1 = (input[i] >> 1) & 1;\n var bit7 = (input[i] >> 7) & 1;\n \n output.attributes.external_mode_rising_edge_enabled_ex = getOnOffStatus(bit0);\n output.attributes.external_mode_falling_edge_enabled_ex = getOnOffStatus(bit1);\n \n switch (bit7){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.mode = \"Digital\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t output.attributes.mode = \"Analog\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.mode = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n i += 1;\n }\n else if(key == 0x2E) {\n output.attributes.external_connector_count_threshold = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x2F) {\n var bit0 = (input[i] >> 0) & 1;\n var bit1 = (input[i] >> 1) & 1;\n var bit4 = (input[i] >> 4) & 1;\n \n output.attributes.external_values_to_transmit.report_state_enabled_ex = getOnOffStatus(bit0);\n output.attributes.external_values_to_transmit.report_count_enabled_ex = getOnOffStatus(bit1);\n output.attributes.external_values_to_transmit.count_type = bit4;\n i += 1;\n }\n else if(key == 0x30) {\n output.attributes.impact_event_threshold = toFixed(parseBytesToInt(input, i, 2) * 0x001, 3);\n i += 2;\n }\n else if(key == 0x31) {\n output.attributes.acceleration_event_threshold = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 2;\n }\n else if(key == 0x32) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.values_to_transmit.report_periodic_alarm_enabled = getOnOffStatus(bit0);\n \n var bit1 = (input[i] >> 1) & 1;\n output.attributes.values_to_transmit.report_periodic_magnitude_enabled = getOnOffStatus(bit1);\n \n var bit2 = (input[i] >> 2) & 1;\n output.attributes.values_to_transmit.report_periodic_vector_enabled = getOnOffStatus(bit2);\n \n var bit4 = (input[i] >> 4) & 1;\n output.attributes.values_to_transmit.report_event_magnitude_enabled = getOnOffStatus(bit4);\n \n var bit5 = (input[i] >> 5) & 1;\n output.attributes.values_to_transmit.report_event_vector_enabled = getOnOffStatus(bit5);\n \n i += 1;\n }\n else if(key == 0x33) {\n output.attributes.acceleration_impact_grace_period = parseBytesToInt(input, i, 2);\n\t\t\t\t\n\t\t\t\ti += 2;\n }\n else if (key == 0x34) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.acceleration_mode.impact_threshold_enabled = getEnableStatus(bit0);\n \n var bit1 = (input[i] >> 1) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.acceleration_threshold_enabled = getEnableStatus(bit1);\n\t\t\t\t\n\t\t\t\tvar bit4 = (input[i] >> 4) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.xaxis_enabled = getEnableStatus(bit4);\n\t\t\t\t\n\t\t\t\tvar bit5 = (input[i] >> 5) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.yaxis_enabled = getEnableStatus(bit5);\n\t\t\t\t\n\t\t\t\tvar bit6 = (input[i] >> 6) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.zaxis_enabled = getEnableStatus(bit6);\n\t\t\t\t\n\t\t\t\tvar bit7 = (input[i] >> 7) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.poweron = getOnOffStatus(bit7);\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if (key == 0x35) {\n var val = (input[i] >> 1) & 0x03;\n\t\t\t\t\n\t\t\t\tswitch (val) {\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"1 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"10 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"25 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 4:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"50 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 5:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"100 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 6:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"200 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 7:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"400 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tvar val = (arg >> 4) & 0x03;\n\t\t\t\tswitch (val) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"±2 g\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"±4 g\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"±8 g\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"±16 g\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if (key == 0x36) {\n output.attributes.impact_alarm_grace_period = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x37) {\n output.attributes.impact_alarm_threshold_count = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x38) {\n output.attributes.impact_alarm_threshold_period = parseBytesToInt(input, i, 2);\n \n i += 2;\n }\n else if (key == 0x39) {\n output.attributes.temperature_relative_humidity_sample_period_idle = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if (key == 0x3A) {\n output.attributes.temperature_relative_humidity_sample_period_active = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if (key == 0x3B) {\n output.attributes.temperature_threshold_low_temp_threshold = parseBytesToInt(input, i, 1);\n output.attributes.temperature_threshold_high_temp_threshold = parseBytesToInt(input, i + 1, 1);\n \n i += 2;\n }\n else if (key == 0x3C) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.ambient_temperature_threshold_enabled = getEnableStatus(bit0);\n \n i += 1;\n }\n else if (key == 0x3D) {\n output.attributes.rh_threshold.low_rh_threshold = parseBytesToInt(input, i, 1);\n output.attributes.rh_threshold.high_rh_threshold = parseBytesToInt(input, i + 1, 1);\n \n i += 2;\n }\n else if (key == 0x3E) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.relative_humidity_threshold_enabled =getEnableStatus(bit0);\n \n i += 1;\n }\n else if (key == 0x40) {\n output.attributes.mcu_temperature_sample_period_idle = parseBytesToInt(input, i, 4);\n i += 4;\n }\n else if (key == 0x41) {\n output.attributes.mcu_temperature_sample_period_active = parseBytesToInt(input, i, 4);\n i += 4;\n }\n else if (key == 0x42) {\n output.attributes.mcu_temp_threshold_high_mcu_temp_threshold = input[i + 1];\n output.attributes.mcu_temp_threshold_low_mcu_temp_threshold = input[i];\n i += 4;\n }\n else if (key == 0x43) {\n var val = input[i] & 1;\n output.attributes.mcu_temperature_threshold_enabled = getEnableStatus(val);\n\n\t\t\t\ti += 2;\n }\n else if (key == 0x44) {\n output.attributes.analog_sample_period_idle = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if (key == 0x45) {\n output.attributes.analog_sample_period_active = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if(key == 0x46) {\n output.attributes.analog_threshold_high_analog_threshold = toFixed(parseBytesToInt(input, i + 2, 2) * 0.001, 3);\n\t\t\t\toutput.attributes.analog_threshold_low_analog_threshold = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n\t\t\t\ti += 4;\n }\n else if (key == 0x4A) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.analog_input_threshold_enabled = getEnableStatus(bit0);\n \n i +=1;\n }\n else if (key == 0x47) {\n output.attributes.light_sample_period = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if (key == 0x48 ) {\n var val = (input[i] >> 7) & 1;\n output.attributes.light_thresholds.threshold_enabled = getEnableStatus(val);\n output.attributes.light_thresholds.threshold = input[i] & 0x3F;\n \n i += 1;\n }\n else if (key == 0x49) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.light_values_to_transmit.state_reported = getEnableStatus(bit0);\n \n var bit1 = (input[i] >> 1) & 1;\n output.attributes.light_values_to_transmit.intensity_reported = getEnableStatus(bit1);\n \n i +=1;\n }\n else if (key == 0x50) {\n output.attributes.pir_grace_period = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x51) {\n output.attributes.pir_threshold = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x52) {\n output.attributes.pir_threshold_period = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x53) {\n var bit7 = (input[i] >> 7) & 1;\n output.attributes.pir_mode.motion_count_reported = getEnableStatus(bit7);\n \n var bit6 = (input[i] >> pir_mode) & 1;\n output.attributes.pir_mode.motion_state_reported = getEnableStatus(bit6);\n \n var bit1 = (input[i] >> 1) & 1;\n output.attributes.pir_mode.event_transmission_enabled = getEnableStatus(bit1);\n \n var bit0 = (input[i] >> 0) & 1;\n output.attributes.pir_mode.transducer_enabled = getEnableStatus(bit0);\n \n i += 1;\n }\n else if (key == 0x54) {\n output.attributes.pir_mode.motion_count_reported = input[i + 1];\n output.attributes.pir_mode.motion_state_reported = input[i];\n \n i += 1;\n }\n else if (key == 0x6F) {\n var val = (input[i] >> 0) & 1;\n \n switch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes_resp_to_dl_command_format = \"Invalid-write response format\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes_resp_to_dl_command_format = \"4-byte CRC\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes_resp_to_dl_command_format = \"Invalid\";\n\t\t\t\t}\n \n i += 1;\n }\n else if (key == 0x71) {\n output.attributes.app_major_version = input[i + 6];\n\t\t\t\toutput.attributes.app_minor_version = input[i + 5];\n\t\t\t\toutput.attributes.app_revision = input[i + 4];\n\t\t\t\toutput.attributes.loramac_major_version = input[i + 3];\n\t\t\t\toutput.attributes.loramac_minor_version = input[i + 2];\n\t\t\t\toutput.attributes.loramac_revision = input[i + 1];\n\t\t\t\tvar val = input[i];\n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.region = \"EU868\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.region = \"US915\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\toutput.attributes.region = \"AS923\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\toutput.attributes.region = \"AU915\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 4:\n\t\t\t\t\t\toutput.attributes.region = \"IN865\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 6:\n\t\t\t\t\t\toutput.attributes.region = \"KR920\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 7:\n\t\t\t\t\t\toutput.attributes.region = \"RU864\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.region = \"Invalid\";\n\t\t\t\t}\n\t\t\t\ti += 7;\n }\n }\n }\n else if (fPort == 101) {\n var size = input.length;\n var responses = [];\n \n var index = 0;\n while (index < size) {\n var downlinkFcnt = input[index++] & 0xFF; \n var numInvalidWrites = input[index++] & 0xFF; \n \n if (numInvalidWrites > 0) {\n var invalidRegisters = [];\n for (var i = 0; i < numInvalidWrites; i++) {\n invalidRegisters.add(String.format(\"0x%02X\", input[index + i]));\n }\n index += numInvalidWrites;\n \n responses.add(String.format(\n \"%d Invalid write command(s) from DL:%d for register(s): %s\",\n numInvalidWrites, downlinkFcnt, String.join(\", \", invalidRegisters)\n ));\n } else {\n responses.add(String.format(\"All write commands from DL:%d were successful\", downlinkFcnt));\n }\n }\n \n decoded.response = responses;\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}\n\nfunction getEnableStatus(bit) {\n var enableResult = \"Invalid\";\n \n switch (bit) {\n\t\tcase 0:\n\t\t enableResult = \"Disable\";\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\tenableResult = \"Enable\";\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tenableResult = \"Invalid\";\n\t}\n \n return enableResult;\n}\n\nfunction getOnOffStatus(bit) {\n var reportResult = \"Invalid\";\n \n switch (bit) {\n case 0:\n reportResult = \"Off\";\n break;\n case 1:\n reportResult = \"On\";\n break;\n default:\n reportResult = \"Invalid\";\n }\n \n return reportResult;\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/Comfort/ThingsStackCommunity/uplink/metadata.json b/VENDORS/Tektelic/Comfort/ThingsStackCommunity/uplink/metadata.json new file mode 100644 index 00000000..0d75c374 --- /dev/null +++ b/VENDORS/Tektelic/Comfort/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/Comfort/ThingsStackCommunity/uplink/payload.json b/VENDORS/Tektelic/Comfort/ThingsStackCommunity/uplink/payload.json new file mode 100644 index 00000000..3ab1656f --- /dev/null +++ b/VENDORS/Tektelic/Comfort/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": "A2cA4QRoTQC6C54=", + "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/Comfort/ThingsStackCommunity/uplink/result.json b/VENDORS/Tektelic/Comfort/ThingsStackCommunity/uplink/result.json new file mode 100644 index 00000000..389179af --- /dev/null +++ b/VENDORS/Tektelic/Comfort/ThingsStackCommunity/uplink/result.json @@ -0,0 +1,28 @@ +{ + "deviceName": "eui-1000000000000001", + "deviceType": "Comfort", + "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": { + "ambient_temperature": 22.5, + "relative_humidity": 38.5, + "battery_voltage": 2.974 + } + }] +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Comfort/ThingsStackIndustries/uplink/converter.json b/VENDORS/Tektelic/Comfort/ThingsStackIndustries/uplink/converter.json new file mode 100644 index 00000000..5dff1a9c --- /dev/null +++ b/VENDORS/Tektelic/Comfort/ThingsStackIndustries/uplink/converter.json @@ -0,0 +1,40 @@ +{ + "name": "The Things Stack Industries Uplink Decoder for Comfort", + "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 = \"Comfort\";\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 == 0xBA) {\n decoded.battery_voltage = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 2;\n }\n else if(key_1 == 0x01 && key_2 == 0x00) {\n val = parseBytesToInt(input, i, 1);\n switch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.hall_effect_state = \"Magnet Present\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.hall_effect_state = \"Magnet Absent\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.hall_effect_state = \"Invalid\";\n\t\t\t\t}\n \n i += 1;\n }\n else if(key_1 == 0x08 && key_2 == 0x04) {\n decoded.hall_effect_count = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key_1 == 0x0C && key_2 == 0x00) {\n var val = parseBytesToInt(input, i, 1);\n \n switch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.impact_alarm = \"Impact Alarm Inactive\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.impact_alarm = \"Impact Alarm Active\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.impact_alarm = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n i += 1;\n }\n else if(key_1 == 0x05 && key_2 == 0x02) {\n decoded.impact_magnitude = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 2;\n }\n else if(key_1 == 0x07 && key_2 == 0x71) {\n decoded.accelerationX = toFixed(parseBytesToInt(input, i + 4, 2) * 0.001, 3);\n decoded.accelerationY = toFixed(parseBytesToInt(input, i + 2, 2) * 0.001, 3);\n decoded.accelerationZ = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 6;\n }\n else if(key_1 == 0x0E && key_2 == 0x00) {\n var val = parseBytesToInt(input, i, 1);\n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.extconnector_state = \"Low(short-circuit)\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.extconnector_state = \"High(open-circuit)\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.extconnector_state = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if(key_1 == 0x0F && key_2 == 0x04) {\n decoded.extconnector_count = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key_1 == 0x12 && key_2 == 0x04) {\n decoded.extconnector_total_count = parseBytesToInt(input, i, 4);\n i += 4;\n }\n else if(key_1 == 0x11 && key_2 == 0x02) {\n decoded.extconnector_analog = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 2;\n }\n else if(key_1 == 0x0B && key_2 == 0x67) {\n decoded.mcu_temperature = toFixed(parseBytesToInt(input, i, 2) * 0.1, 1);\n i += 2;\n }\n else if(key_1 == 0x03 && key_2 == 0x67) {\n decoded.ambient_temperature = toFixed(parseBytesToInt(input, i, 2) * 0.1, 1);\n i += 2;\n }\n else if(key_1 == 0x04 && key_2 == 0x68) {\n decoded.relative_humidity = toFixed(parseBytesToInt(input, i, 1) * 0.5, 1);\n i += 1;\n }\n else if(key_1 == 0x02 && key_2 == 0x00) {\n var val = parseBytesToInt(input, i, 1);\n \n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.light_detected = \"Dark\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.light_detected = \"Bright\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.light_detected = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if(key_1 == 0x10 && key_2 == 0x02) {\n decoded.light_intensity = parseBytesToInt(input, i, 1);\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if(key_1 == 0x0A && key_2 == 0x00) {\n var val = parseBytesToInt(input, i, 1);\n\t\t\t\t\n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tdecoded.motion_event_state = \"None\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 255:\n\t\t\t\t\t\tdecoded.motion_event_state = \"Detected\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdecoded.motion_event_state = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if(key_1 == 0x0D && key_2 == 0x04) {\n decoded.motion_event_count = parseBytesToInt(input, i, 2);\n\t\t\t\t\n\t\t\t\ti += 2;\n }\n }\n }\n else if(fPort == 0x05) {\n var key_1 = input[i++] & 0xff;\n var key_2 = input[i++] & 0xff;\n \n if(key_1 == 0x40 && key_2 == 0x06) {\n var val = input[i + 5];\n\t\t\tswitch (val){\n\t\t\t\tcase 1:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"Push-button reset\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"DL command rest\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase 4:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"Independent watchdog reset\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase 8:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"Power loss reset\";\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tdecoded_data.reset_diagnostics_reset_reason = \"Invalid\";\n\t\t\t}\n\t\t\tdecoded_data.reset_diagnostics_power_loss_reset_count = input[i + 3];\n\t\t\tdecoded_data.reset_diagnostics_watchdog_reset_count = input[i + 2];\n\t\t\tdecoded_data.reset_diagnostics_dl_reset_count = input[i + 1];\n\t\t\tdecoded_data.reset_diagnostics_button_reset_count = input[i];\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 var val = (((input[i] << 8) | input[i + 1]) >> 15) & 1;\n switch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.loramac_join_mode = \"ABP\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.loramac_join_mode = \"OTAA\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.loramac_join_mode = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if (key == 0x11) {\n\t\t\t\tvar val = (((input[i] << 8) | input[i + 1]) >> 3) & 1;\n\t\t\t\toutput.attributes.adr = getEnableStatus(val);\n\t\t\t\t\n\t\t\t\tvar val = (((input[i] << 8) | input[i + 1]) >> 2) & 1;\n\t\t\t\toutput.attributes.duty_cycle = getEnableStatus(val);\n\t\t\t\t\n\t\t\t\tvar val = (((input[i] << 8) | input[i + 1]) >> 1) & 1;\n\t\t\t\t\n\t\t\t\tswitch (val) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.sync_word = \"Private\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.sync_word = \"Public\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.sync_word = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tvar val = ((input[i] << 8) | input[i + 1]) & 1;\n\t\t\t\t\n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.confirm_mode = \"Unconfirmed\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.confirm_mode = \"Confirmed\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t output.attributes.confirm_mode = \"Invalid\";\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 == 0x1A) {\n output.attributes.loramac_net_id_lsb = parseBytesToInt(input, i, 2);\n\t\t\t\ti += 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 == 0x22) {\n output.attributes.tick_per_ambient_temperature = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x23) {\n output.attributes.tick_per_relative_humidity = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x24) {\n output.attributes.tick_per_reed_switch = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x25) {\n output.attributes.tick_per_light = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x26) {\n output.attributes.tick_per_accelerometer = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x27) {\n output.attributes.tick_per_mcu_temperature = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x28) {\n output.attributes.tick_per_pir = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x29) {\n output.attributes.tick_per_external_connector = parseBytesToInt(input, i, 2);\n \n i += 2;\n }\n else if(key == 0x2A) {\n var bit0 = (input[i] >> 0) & 1;\n var bit1 = (input[i] >> 1) & 1;\n \n output.attributes.mode.rising_edge_enabled = getEnableStatus(bit0);\n output.attributes.mode.falling_edge_enabled = getEnableStatus(bit1);\n \n i += 1;\n }\n else if(key == 0x2B) {\n\t\t\t\toutput.attributes.reed_switch_count_threshold = parseBytesToInt(input, i, 2);\n\t\t\t\ti += 2;\n }\n else if(key == 0x2C) {\n var bit0 = (input[i] >> 0) & 1;\n var bit1 = (input[i] >> 1) & 1;\n \n output.attributes.reed_values_to_transmit_report_state_enabled = getOnOffStatus(bit0);\n output.attributes.reed_values_to_transmit_report_count_enabled = getOnOffStatus(bit1);\n \n i += 1;\n }\n else if(key == 0x2D) {\n var bit0 = (input[i] >> 0) & 1;\n var bit1 = (input[i] >> 1) & 1;\n var bit7 = (input[i] >> 7) & 1;\n \n output.attributes.external_mode_rising_edge_enabled_ex = getOnOffStatus(bit0);\n output.attributes.external_mode_falling_edge_enabled_ex = getOnOffStatus(bit1);\n \n switch (bit7){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.mode = \"Digital\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t output.attributes.mode = \"Analog\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.mode = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n i += 1;\n }\n else if(key == 0x2E) {\n output.attributes.external_connector_count_threshold = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if(key == 0x2F) {\n var bit0 = (input[i] >> 0) & 1;\n var bit1 = (input[i] >> 1) & 1;\n var bit4 = (input[i] >> 4) & 1;\n \n output.attributes.external_values_to_transmit.report_state_enabled_ex = getOnOffStatus(bit0);\n output.attributes.external_values_to_transmit.report_count_enabled_ex = getOnOffStatus(bit1);\n output.attributes.external_values_to_transmit.count_type = bit4;\n i += 1;\n }\n else if(key == 0x30) {\n output.attributes.impact_event_threshold = toFixed(parseBytesToInt(input, i, 2) * 0x001, 3);\n i += 2;\n }\n else if(key == 0x31) {\n output.attributes.acceleration_event_threshold = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n i += 2;\n }\n else if(key == 0x32) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.values_to_transmit.report_periodic_alarm_enabled = getOnOffStatus(bit0);\n \n var bit1 = (input[i] >> 1) & 1;\n output.attributes.values_to_transmit.report_periodic_magnitude_enabled = getOnOffStatus(bit1);\n \n var bit2 = (input[i] >> 2) & 1;\n output.attributes.values_to_transmit.report_periodic_vector_enabled = getOnOffStatus(bit2);\n \n var bit4 = (input[i] >> 4) & 1;\n output.attributes.values_to_transmit.report_event_magnitude_enabled = getOnOffStatus(bit4);\n \n var bit5 = (input[i] >> 5) & 1;\n output.attributes.values_to_transmit.report_event_vector_enabled = getOnOffStatus(bit5);\n \n i += 1;\n }\n else if(key == 0x33) {\n output.attributes.acceleration_impact_grace_period = parseBytesToInt(input, i, 2);\n\t\t\t\t\n\t\t\t\ti += 2;\n }\n else if (key == 0x34) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.acceleration_mode.impact_threshold_enabled = getEnableStatus(bit0);\n \n var bit1 = (input[i] >> 1) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.acceleration_threshold_enabled = getEnableStatus(bit1);\n\t\t\t\t\n\t\t\t\tvar bit4 = (input[i] >> 4) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.xaxis_enabled = getEnableStatus(bit4);\n\t\t\t\t\n\t\t\t\tvar bit5 = (input[i] >> 5) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.yaxis_enabled = getEnableStatus(bit5);\n\t\t\t\t\n\t\t\t\tvar bit6 = (input[i] >> 6) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.zaxis_enabled = getEnableStatus(bit6);\n\t\t\t\t\n\t\t\t\tvar bit7 = (input[i] >> 7) & 1;\n\t\t\t\toutput.attributes.acceleration_mode.poweron = getOnOffStatus(bit7);\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if (key == 0x35) {\n var val = (input[i] >> 1) & 0x03;\n\t\t\t\t\n\t\t\t\tswitch (val) {\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"1 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"10 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"25 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 4:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"50 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 5:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"100 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 6:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"200 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 7:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"400 Hz\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_sample_rate = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tvar val = (arg >> 4) & 0x03;\n\t\t\t\tswitch (val) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"±2 g\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"±4 g\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"±8 g\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"±16 g\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.sensitivity_accelerometer_measurement_range = \"Invalid\";\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti += 1;\n }\n else if (key == 0x36) {\n output.attributes.impact_alarm_grace_period = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x37) {\n output.attributes.impact_alarm_threshold_count = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x38) {\n output.attributes.impact_alarm_threshold_period = parseBytesToInt(input, i, 2);\n \n i += 2;\n }\n else if (key == 0x39) {\n output.attributes.temperature_relative_humidity_sample_period_idle = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if (key == 0x3A) {\n output.attributes.temperature_relative_humidity_sample_period_active = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if (key == 0x3B) {\n output.attributes.temperature_threshold_low_temp_threshold = parseBytesToInt(input, i, 1);\n output.attributes.temperature_threshold_high_temp_threshold = parseBytesToInt(input, i + 1, 1);\n \n i += 2;\n }\n else if (key == 0x3C) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.ambient_temperature_threshold_enabled = getEnableStatus(bit0);\n \n i += 1;\n }\n else if (key == 0x3D) {\n output.attributes.rh_threshold.low_rh_threshold = parseBytesToInt(input, i, 1);\n output.attributes.rh_threshold.high_rh_threshold = parseBytesToInt(input, i + 1, 1);\n \n i += 2;\n }\n else if (key == 0x3E) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.relative_humidity_threshold_enabled =getEnableStatus(bit0);\n \n i += 1;\n }\n else if (key == 0x40) {\n output.attributes.mcu_temperature_sample_period_idle = parseBytesToInt(input, i, 4);\n i += 4;\n }\n else if (key == 0x41) {\n output.attributes.mcu_temperature_sample_period_active = parseBytesToInt(input, i, 4);\n i += 4;\n }\n else if (key == 0x42) {\n output.attributes.mcu_temp_threshold_high_mcu_temp_threshold = input[i + 1];\n output.attributes.mcu_temp_threshold_low_mcu_temp_threshold = input[i];\n i += 4;\n }\n else if (key == 0x43) {\n var val = input[i] & 1;\n output.attributes.mcu_temperature_threshold_enabled = getEnableStatus(val);\n\n\t\t\t\ti += 2;\n }\n else if (key == 0x44) {\n output.attributes.analog_sample_period_idle = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if (key == 0x45) {\n output.attributes.analog_sample_period_active = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if(key == 0x46) {\n output.attributes.analog_threshold_high_analog_threshold = toFixed(parseBytesToInt(input, i + 2, 2) * 0.001, 3);\n\t\t\t\toutput.attributes.analog_threshold_low_analog_threshold = toFixed(parseBytesToInt(input, i, 2) * 0.001, 3);\n\t\t\t\ti += 4;\n }\n else if (key == 0x4A) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.analog_input_threshold_enabled = getEnableStatus(bit0);\n \n i +=1;\n }\n else if (key == 0x47) {\n output.attributes.light_sample_period = parseBytesToInt(input, i, 4);\n \n i += 4;\n }\n else if (key == 0x48 ) {\n var val = (input[i] >> 7) & 1;\n output.attributes.light_thresholds.threshold_enabled = getEnableStatus(val);\n output.attributes.light_thresholds.threshold = input[i] & 0x3F;\n \n i += 1;\n }\n else if (key == 0x49) {\n var bit0 = (input[i] >> 0) & 1;\n output.attributes.light_values_to_transmit.state_reported = getEnableStatus(bit0);\n \n var bit1 = (input[i] >> 1) & 1;\n output.attributes.light_values_to_transmit.intensity_reported = getEnableStatus(bit1);\n \n i +=1;\n }\n else if (key == 0x50) {\n output.attributes.pir_grace_period = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x51) {\n output.attributes.pir_threshold = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x52) {\n output.attributes.pir_threshold_period = parseBytesToInt(input, i, 2);\n i += 2;\n }\n else if (key == 0x53) {\n var bit7 = (input[i] >> 7) & 1;\n output.attributes.pir_mode.motion_count_reported = getEnableStatus(bit7);\n \n var bit6 = (input[i] >> pir_mode) & 1;\n output.attributes.pir_mode.motion_state_reported = getEnableStatus(bit6);\n \n var bit1 = (input[i] >> 1) & 1;\n output.attributes.pir_mode.event_transmission_enabled = getEnableStatus(bit1);\n \n var bit0 = (input[i] >> 0) & 1;\n output.attributes.pir_mode.transducer_enabled = getEnableStatus(bit0);\n \n i += 1;\n }\n else if (key == 0x54) {\n output.attributes.pir_mode.motion_count_reported = input[i + 1];\n output.attributes.pir_mode.motion_state_reported = input[i];\n \n i += 1;\n }\n else if (key == 0x6F) {\n var val = (input[i] >> 0) & 1;\n \n switch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes_resp_to_dl_command_format = \"Invalid-write response format\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes_resp_to_dl_command_format = \"4-byte CRC\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes_resp_to_dl_command_format = \"Invalid\";\n\t\t\t\t}\n \n i += 1;\n }\n else if (key == 0x71) {\n output.attributes.app_major_version = input[i + 6];\n\t\t\t\toutput.attributes.app_minor_version = input[i + 5];\n\t\t\t\toutput.attributes.app_revision = input[i + 4];\n\t\t\t\toutput.attributes.loramac_major_version = input[i + 3];\n\t\t\t\toutput.attributes.loramac_minor_version = input[i + 2];\n\t\t\t\toutput.attributes.loramac_revision = input[i + 1];\n\t\t\t\tvar val = input[i];\n\t\t\t\tswitch (val){\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\toutput.attributes.region = \"EU868\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toutput.attributes.region = \"US915\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\toutput.attributes.region = \"AS923\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\toutput.attributes.region = \"AU915\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 4:\n\t\t\t\t\t\toutput.attributes.region = \"IN865\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 6:\n\t\t\t\t\t\toutput.attributes.region = \"KR920\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 7:\n\t\t\t\t\t\toutput.attributes.region = \"RU864\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\toutput.attributes.region = \"Invalid\";\n\t\t\t\t}\n\t\t\t\ti += 7;\n }\n }\n }\n else if (fPort == 101) {\n var size = input.length;\n var responses = [];\n \n var index = 0;\n while (index < size) {\n var downlinkFcnt = input[index++] & 0xFF; \n var numInvalidWrites = input[index++] & 0xFF; \n \n if (numInvalidWrites > 0) {\n var invalidRegisters = [];\n for (var i = 0; i < numInvalidWrites; i++) {\n invalidRegisters.add(String.format(\"0x%02X\", input[index + i]));\n }\n index += numInvalidWrites;\n \n responses.add(String.format(\n \"%d Invalid write command(s) from DL:%d for register(s): %s\",\n numInvalidWrites, downlinkFcnt, String.join(\", \", invalidRegisters)\n ));\n } else {\n responses.add(String.format(\"All write commands from DL:%d were successful\", downlinkFcnt));\n }\n }\n \n decoded.response = responses;\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}\n\nfunction getEnableStatus(bit) {\n var enableResult = \"Invalid\";\n \n switch (bit) {\n\t\tcase 0:\n\t\t enableResult = \"Disable\";\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\tenableResult = \"Enable\";\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tenableResult = \"Invalid\";\n\t}\n \n return enableResult;\n}\n\nfunction getOnOffStatus(bit) {\n var reportResult = \"Invalid\";\n \n switch (bit) {\n case 0:\n reportResult = \"Off\";\n break;\n case 1:\n reportResult = \"On\";\n break;\n default:\n reportResult = \"Invalid\";\n }\n \n return reportResult;\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/Comfort/ThingsStackIndustries/uplink/metadata.json b/VENDORS/Tektelic/Comfort/ThingsStackIndustries/uplink/metadata.json new file mode 100644 index 00000000..23f54b34 --- /dev/null +++ b/VENDORS/Tektelic/Comfort/ThingsStackIndustries/uplink/metadata.json @@ -0,0 +1,4 @@ +{ + "integrationName": "ChirpStack integration", + "includeGatewayInfo": "false" +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Comfort/ThingsStackIndustries/uplink/payload.json b/VENDORS/Tektelic/Comfort/ThingsStackIndustries/uplink/payload.json new file mode 100644 index 00000000..6ccf662e --- /dev/null +++ b/VENDORS/Tektelic/Comfort/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": "A2cA4QRoTQC6C54=", + "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/Comfort/ThingsStackIndustries/uplink/result.json b/VENDORS/Tektelic/Comfort/ThingsStackIndustries/uplink/result.json new file mode 100644 index 00000000..ea78fb66 --- /dev/null +++ b/VENDORS/Tektelic/Comfort/ThingsStackIndustries/uplink/result.json @@ -0,0 +1,28 @@ +{ + "deviceName": "eui-1000000000000001", + "deviceType": "Comfort", + "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": { + "ambient_temperature": 22.5, + "relative_humidity": 38.5, + "battery_voltage": 2.974 + } + }] +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Comfort/info.json b/VENDORS/Tektelic/Comfort/info.json new file mode 100644 index 00000000..6c4489f1 --- /dev/null +++ b/VENDORS/Tektelic/Comfort/info.json @@ -0,0 +1,5 @@ +{ + "url": "https://tektelic.com/products/sensors/comfort-base-smart-room-sensor/", + "label": "Comfort: Compact and versatile smart room sensor for holistic indoor environment monitoring", + "description": "The Comfort is a versatile LoRaWAN IoT sensor in a compact form factor, ideal for monitoring and reporting temperature, humidity, light, shock and open/closed doors and windows in an indoor environment. Additional sensing features such as leak and motion detection, as well as counting pulses from an external device, are also supported with the appropriate model. " +} \ No newline at end of file diff --git a/VENDORS/Tektelic/Comfort/photo.png b/VENDORS/Tektelic/Comfort/photo.png new file mode 100644 index 0000000000000000000000000000000000000000..9c65689739d3b61f4f71d894c5ddc3cc736f6d77 GIT binary patch literal 81758 zcmeGEX*`sF*anPWc9B$)RF*eE^~X01jwARWd(ZS zb0SW{%1XRwyv(}7New@VT^kx1Nj(q=Nv7Alvu1xK_TK_rF76+&N`Ic7S>U3aGraRV zCc~?abhI%7l>*K^{1{a*$CuLA#9f&UK`m@m?O z8hl4qmhgFODW>pPT2`x}gHPh-yKH{WfPg*0fPjGTpEc>XDk>_1ZyK#Hc|GCz!}H&t z88$d`!2aOD1Mw^H2R!1GOyVU#l+ z)a@`MY)~i^i-bUG4vvmIHcevCXy`RaVBol~&*5^udCALP5nH9DrCa^|{VObMmstq3yV}yF3q;#0d!C^$TP^9GT z?(Sl)^YBsi1qGL=%`uoO*RP+n&v$mB_^xoD9Dew)0F%9QX9rJYWDM?mYKJK!9I~CJ zOk>KxD<@-YY#LD0Lqq$pLWdJsH@9?Z?yp}fb_EXPiOpR*nVK4EBysf>54FCuV1t_P z=6ae}U0qEr&dptgm6euKM8zn(aiaPZ0~eDqs;Q;L2|J&1@F}~NmKJ3~J%*P|UR+#6 znHd`&`0*(P9iuK?zD!AHV&^@LNTJk>9iU7sEs3(Do-iAAC#My=qWt`0r|LeGq0z{J z+0|1U6wDzUK7F)wPV2?TdVb&dU4G@@N#~-(uV1X}bX=!|B4099S#dau4 zOapPVNMsbt2U&?X3%>uQxXhWXF+PE!2x5-gWewZ1ZqdV7}Y!Sa!0f2rSHmYC?k$uS}O#0_OeV|(fRmnw>|LS95lM|eRnCsd?e)aM{tKWW`yz{eZ=9HE)Wmu~9) zRX6GN#Lquduhg)|Inf)JdE(+m`Zmd%uZN`{NQh-T(9=~cN>2(j&6~5UjW!sg;|D;8cW&$ZO>xG{aMDA1WysJd}~K&1Ct-BwHGV6&rV>e#;KX}-`=N%$hjuL?iIS#BYNicm zap5F6G$G-S$Df)iDpT?g*zoM^so5BbbnnfKvgBO6ICuweX^ZkOFfb?^*kLGAfg})Q z*kUHm#pvjsJEzy#)Rc5^7^CGu{c>=`rmc@J_a>YT7{WH3%VSGf-{3Lq*<4Z2>3|)) zoDAempL1?WSIRna>&QP$x^(23Jxul12@5{R!WR&f4uW-_CRDe~jlY%VMfl3x5MYO^>vJ%rM>5B2lC*x+>tO&&W-1ZF1z-KV5`2yu>X(zl(15&Y&1D@##XfN+U~{H`Jq7GOG@9D zXKe;7+a;2+MzZ!}pNPiU3%n8irRpuL!+vq!|3mzZQ=b-xv#ViUe~^etnKb6urGEYm zwpgd47YB2gFNA4ZkxBh>o^)gEQu!QY@qLoZ+)?kJk^zDEc_jAXcQM8-moq(Kq=Oh( z?n)nopv++cc>A8yfT*Kx0V!62IX{0Jy}?fBY-&~-8{gNka4Go_Kc?lT8z=THqkDOI z`B(+hR8qK?LiJy2k3!^HoCg4ZNn$TGE~OoV#q(|43rAu4?P@gn?zpw&8S8Y<5B>;L zyrmy{TRvL6dhwV@9vleO=p!T^3RwHe3}j?$>ACx95Q=gxhll?=C*55BYAs z{;NeoD2}CjIiQYzhvkE+3)WvuqLBIZo9zT5@mzo*T41FRxG5Pdz`m!wLP4u<{(f30 zR%AjmBq%7D9g1^H&nxUMq^#g(YHBU6fcHU$yVi3{OR`5`;OA=xx{a!y5A$P>RpXDb zPf?f@ULhCNVNMY{v02CdR-G<~hsp`w#05PuftwJlmXqzXcPvP;MX zVzhxdrKL7vMJX%0OY-r@iCt0kB+rdgwv(+EpIRSryOdNbCx3rqPC6)#jZGKp<5y1k zd;Jo4-}5V`-=0NLclfGm2nqDz)$WvgcV#4=2#&1!OfHKqoGtW{dGNY1j#6CIkef@BCZ@PeYzb{~N?i{JZ(Bd6BymtD%+qZQ+8 z-%{43-X49Q&VaZs1phnfZw$dHd_U^`sO9cM!+mR1XA&D`_Gt^}G+TrGf z$WkIO#|Zv4jDJPdqsI%*Mk^>cO35BLqem3PaOrfp%=sm5aS;*EZixj7)Oc`qIU~>s z+3Nj0zn?fSZ^ow@+SrJr6``Bd*^!O@(m7F|2W%dkAd3_rFoAQmO49o8PGwXd4`PGf zK$O2OIc8fN=I<>Z|1Br>iLCvA@s%72V7C0v6yEJ##*hBO=8uQes(c<6DAtdgiU`ty!wW!KiVDu=E{!J_ohn&x8+7Giw z-;bgttMyQl^dd9zbu5L2g|IdIxqpboH=rK*(s}XZXUJzK>Mx|TzpuM@u-?fBYcb77B7rE1sRla(Y$#xlT%!8%fe^edH9xndAUoxg7h!6!u{~F1)@?@k5}0#0-n2SYQpTT z9V13>EPZEia`MlbL31`LS6#)Iil+nMc%wW$o6|_PC>+6iSo-0kTO39Mj;@QzE~(ek zIHN@WRqCE*heN#s`~n|em>1FGZqZv@?6USh-QajEWpQ@kfH5Yy{~T)F|2-)ZYWvDE zsCIcLZl23OsO!Sk2)c}Ltr}J4FMS1Vddx#wkV>t>?NH%_#y$`6FOz#4f|Jko@T1ew zcKqvWL@^a@E7a&%jkemaiL>kDeFAcj)w(SV#MrY!wyTAZsMQ~Fz4my@>DH}VCT))! z?$=alI-A9~TppbpGBGwFE??u4v_956w96<0U8*4E5od--mL6(;jfbNSg3Onil%Qz> zoQ=}?b@AJsli#t)=64a2S!WtjmqnjFUi={`5N`YUY5#N`{s!k6&E$Xq)s|PH?V2;4 zRk)Kc$KZ+2Gx3wQJ zW_3O|sCrCa1h>)?GF1b^IygA#R7VdrHWc*R{?Qr6TC+T&uh-RHt)N4*vu%a%-sVwQ z>5hMN>*mcLdl6a%C2zkajQehEZI897c$lai*7YPxVIg!kLBKc;_BlNcvwNM7_s<`o zU~`p?Mg|E6iw`jcitdT?^`(uq>&e2qe?I5Ux|?@TCn zVLZsGGtuIm=xQMS^4u70=6hxR8H%KiSriBb^N=(pR)755KRev|^(#YNoyVo2`uiCJs|`&1 z&Xu>Nz5G$?9PfNLHZjqD;!s)j$43JKQHS`L9JXL5%Ba`PKoj;@ zDGSMbJJ>ws7t_OG@=xp^Dsc<7y^qmVi4_Q>E4O0)SZ0LnHD{jG^w0fxDi;gOW7e2{ zMI`rKLO7$j_`2|Zk_*9nQW%J?-DE@W0{L=7=V?I79DJJiE4S1}tLIuyW~OziO<&@O zs8mm5!)GV*bV9UT^5PVO&57uezh-a^>gK4>Sm&5a9n6nmErox-}LlZOs zs;a6q&HM)ZpFA1aXn}-LZ%Rqw)n`%c|J8XgSGN{c|MMp~#9g$6L;@voQ)iOjepxC! z=dtJ^Wq-L9qeQ|hGGd*!r#ke-N*opEf-#Y6dX&(2e^%V7PTKC_iHaFi!rjy?H7bm? zj;@4_1weBQ9x{0z^_s}}&;NBrR~aQpbS3!B1zF7nFX>wR@ef}vwiHg4b&s{-+cxeA z5^&brkftluRHe|}5*Ig9Q$g}7%v)GlsXx)woT_o3{kuD24Jz+>$ZWiqEUdPnp-eAE zr4T|M>VtXH%=?hJ^`8nXi0*H&0!8QFO+2s{-bV-))G#@FGMRfOMtwx$6YBy0ss48; zosK<|sHxyQUz%@qw9oTd;Mc-Dw`YCien7N*-7(@tl@nUyNWRXQGSm$`iu~txZ+EPd zc4Fb<7uC1um8KiiqpvGvMSR-##BDyf1+|v!>vJ0(8Y{_i&dJTCG=(DBgVkaLPU#LV z(F09{T#5>EvLPy6Z~bmcIB|DXgbV z=VGzi-nNxT*NjK~4SaqFVJ@KK60LZGWSr02@y?@?8Ar?<+4FehNg?xJ$zM*KAmv_W z;~yW9MGsG#T3VZ*T8ZkV9dm4BSOH*%Gf(O;QK|5hPZ9brE*BW2gXz+vVeN6>a=u{b zzHRiP5XX5Jp?K+p);!Hs+@RtmhVL$cw>R|TPCGcLiIsr=T<4Eb=M?q&vkPpD<74++ z-D$@|Kg1`fvBCvE4|HarnH>Ms^;HCQ-R%k_*cj0V5&C3TkcH<5bwlPLBp!<&)hed9 ztwi2>jdv#WSqTwFtgE9^4Xv#O9y~Z4o}T_034pN2>%W)tYAs$|7G{AFU8dl`iEC%@ zpGOn?x1z$`!h@>uW~XePah_i@T2ydXLz4D%O;3PBgi5hE|r@xDb5Wlym|9i~|^z6G#SKu#O(;+&|jP+F$C0Z!LdSRq3&?Xsr+rY32O3Q*HrD z)BEAB)8!_nCFTVe$+u1D>+@nMH7ht-%w4r6t1?Jf6KOR9ZpQ37Rizod%o|%TZYHEm z{a|cttm)to>Fs^A{`f`V@87qSsqU0A+*||c?KTE%!yV2aadD(2PR?qQPwdYDBDNXj zO`>K3Nk$j1-#`4F%g}|=++r$ewNjo4Cw|i!9&0?HWE6fx3+=p_9{G9e@S)Rc@{S!- z&$>D!C7^We#Oxg7=X$pkgRh~q{0Wng1z}h-#CuJ&eqb%)WNt3;8V1Gik87W&T^aL! zV`E|oIIHR4Rr9F8{d>o+YB77*ZL_ku#7gI~?(a|XR4cVc2fS>3yZ7O8ikcfiwo9}vI}18cIB!VS23cU#ji2Lg8)nye!M@egOl zuW_f}r;AKZN=fL_R6g)h&K(W*+5JgSmOkm7jXdAFK3Vm((BT?SYOFNJ$X%IjwL2cI ztvQ?z%C$kW+pe!0E-D9o@5*a?nNBxeuXN6}jw8VVA&<2B~u zk)EpuoN@;nnp2fnhpBq|qUC(r7gJen75>eSIf!pmo-?mQXuU7&5Nl8+ye1cRwvnrh z3vQ`3ogENDSHY6#%9}ZpTR}U|3k%=mSom>H|0-iV&>J5cv#a%5>PPkV^^v6s9%sNo z5iuwT)Kqz!1lqv$l9v_MX>L9Q5B1}uwr#!7!PK#4b)QtrM=>$WX<|-^L(_e}VZp0R zW|wCj&T@$RO}nmMz+j|ns2r6Nz*dg(E(=`j&5S~Z7WBzyW77v++3I7SYukdgK_}5^ z@&TIXf<1M7p?~#d$}qR(#7)&$;BBmzlLTHYF!6OV)maSsVPyI~%$@dQ3;F%6z|NC= zYgU#Zkt&mF8**-i_ipHCbOXe$yGOjQ$=(fTQ`ofqef-bS_~K__jH1ux=m7Ko48F}; z|J`Yfn;X`BBD_&4+ue`ms)r1RzUAz@nMv#`Ibts=mzth5=)5QD=xA)meMNSlqumGp zBu?xD!mzF7N3GVkh>h1ed5g&bK}TE3EM>spOi`&@V&SG~K@tAP_>q!Sc*Jbdb>&wt zbCb}12)Wo`^=)jKRxd7T%@PojQs(8R%Y4iUsI}=YAEgU`7n6|oFj!UTdsDgDku$RY0Cdw+YRbw~*C! z7xCfB97wxI)$Fo*v?Zyii#RR(a^-49tc&uKi5S1V9!E^`eg$;mf(2?raO!9wZ?Eg; zzUSVoPZ!cch`=JYY_D1J2~)5x6RAYmB(B( z(@pdFm$)W6qf*;EDV0di(^jWj0mc%#Z{*P4Sx7(gK+>rI{PBxVn!*1|RJ%0C8_kT*o5)8pt!>;X`-8)^`>?|Q)|0u zjUi@J2$ioVy#KF29Ki8FJx}(aQz-mLSG4bQ8Fz|?uw+e6RdI7wsyqIdv62-y}~nC5nn8zfl3W>L}Z`^i~U^v?bcWk5|?6ixTsNIPbOwyHQwm4(l=yDv4& zbD_mY&i5dVh+gYHazi?jQ`lk~Tx;!sbw`X8aPSst!tdkMBu;x7`=RBsgsEB+?Y^~C zO;DGiMT6%rF_H0}Xtx>T_q)HjRgYH>Qw@y|G~T?$8p$%(`1!J#{4fg&+U-q7Nmy8T zbFlzTJ*CI?E3u7~-_FEbyAcKu4wV_#ENPzX+&0m`*4&?eQ3&zjwI(uc2@b1EAU5ehSGVZ4urp1$DJ&7L1;X}d!`7BIm z*T)#X!70VpDTeDuqjk(*MD6#vcj;xlXpir<1&L5q|IT>X!IF0cV%jLm7xmaOvF2R;xRc*0m$Xv>SU@VB1Jo!nETA=RlrD^DyEEA>qC>6IRilsOb=ZTiGz3hU8i zK0%z=HTs9M*wC-PdTfzTFV9}K77}4Qj-w;GXGPm=nL?&5LJqp4ZPqJ4 zeQNI@xML_$4G;n)=bZ=0Af)n&i;MXROWfur2;RoRWLuhVqYSTem^D&wp7dCOH7v}G z3||=1biu7B=8h|!Yz^B?{ataeD1T8>zl&apO|P`^#mfnF=-b9CoNyXikMqYA8CTe$ z#Z0PkhBK4GhhiHpueW!zewkghBI@iTu0AL$gFXH9$%iYK!_ab$pUG>W=hGq5$$tE* zMWbuObwy8u?K$!ILQ#p~;_)%v4+qJk8I9k|<(_Xf)c;L7xxLeKarfh)T6-cjpuUrk zVh!XVR{IICKsgnaWO2r+r^zSaT;bvoKXyU^VzcF63`%30hB8X$~J4u;+!Jqk$!o?5{U<=YU+l2 zTm9e<9kVPI?~s*tQaZ~<3tW>!xk^L0nI3GB3D0JYT^YqANm7Mx!ruX~?n$hy10H&7 z9c|5)#U7NzR^C-lm|{>Fq<{Tg47j{O3-)!0h^p-#s`nBg-Wv}WUbO|F!Y_VG(t30J z6xMb$InuJIc~#^vGYFq(OoDyJi3g&mdWyAV^g%+uhhE!_-(D6 zobomW4T7d!);;GvN|1w5hhTbn%vW6qLzDk=z+W?BhW@4r{qY$c!>sJJfb88(npzQ) z#VjVN%?G+6b;i=qX9k63-8p-{XRoagqaN21G7UFV>l|M{6@DD*u&Sl{?HDVms8IJ= zr;-Pn0CRoJepbeXQEk)oOprcGE806GC#&JD`DRLBPJWf1UPhhRXLdv!wkD!C{==tS zcNY`2r+Hdg2T)*v6Mo{(xC|9pNDe04V@c3Vp|m~AU4Bqca9DL(yE=fBfGF172^Sgk zZLPV7kVXnLDT+|3<6k?t`zKIFPV#FX>GFjnm_U3&bOmEwWOa;@-KVaBPp<~bu@un%iiKR2TvkqbP>>Yzg zRX9($%XzMsTc2*l$|nTfl8}%M{S4C2O%c`M;o<23YCl?y$@luP*Z)XM;rArpX1lle zt+!)}M_)yJXs)LH@@9osg{eI3Yor~V9rx(p!mg?i86DD#{SLn-RT9|>?+otyBqFAF zI{!Wr^Z2njeeRwOVt0a$e?zq?L{j;)$&E}1?HhNT?99*%>$7v?g6w{qslp~S1~wCt z@pamR?UF`1>-X|OrvS>{6j@7qUT9F2W?sa9$fZ(S05$n8MszMqMu9Q_rdWr&LS%ZbOX^`H&FC4xg-2bzq#KsoVsZ)clgV zGa$nF=>V`BSuLh?u75lm4eF26vF*cT3Xk1 z6sSsyh4{%T9lZ^QG#LkAw$4h7Gd`*?f9Khq@dmaHw@ne9Rk2T)ubN!6HVyWRWdzQ& z6PSTB_1`-VdD+<&oX_lrolY;#^=S5Gt{XDEffO)>?O+9{e$EM`EG1=#ax%&aZ~kv7 z@*|ZhT+tYuUdOH;Lv!b4J8(x4h@a>Q{mkjPZ~Bv)erq{A^40lr9xK0Y#u0|scNpPb z>?+TJV;t3I9l~HLVdpMPj_l)xDWV}f4PxawT<4k_Xh1eu7;y&r;01ZQx&n%oIazKJ z1x+K1TA%$tKYlz)NJ)|R3N7cor@~1mTU2z7(kMo|E_MlkONDOJtnV%r$_5&H7IXNF zFyw3Y=3TrfE#91>mZaLmc<>sva0M`WLy~aQ@8c#n+v8muB!L4c4RPq;QBH>JpQE0C zvGOBi=O;!nnnB(pywF#6dx3eU$werdcc$&8%;M@9@ zW+x}N4NH6QVS-^jj8zs4D(qLl@Oc$(`{zI(zW@HlZ>CeNbh6pcRK|@WI@`od*1n3O z1PN`(xE+T7`YpiweP$VdK5i4F;3Rxk7K24@US^sJ@lDgSsRpk+&WMnuoS#mK+hLgu|V$daUdFV`IBTHu7sVeH`pezga+ULVZ zo;AdDY2q7cY=A1?M-Lc68>KP}sweIfq&rG3Qj8JQdqPzCVd3QF z`pLD}2pvtk;k%U?P;9sMj{?j(>}`%DB|7N7e?;JLUjv$?;psy2bevO%NRbbMh&P z0j^2(p_%Vk42yJHsaUD-ERY03gp_q}yoiMz=8uoqM*v=-J4_Yx4Fp~3%reI1V(VL} z<7J8I3cj7qbWF=FwFArB>7>2&A%`;L;8DM`{+nz1!9m0WeIzUnq&lpo*4C$Shlg%! z-iE~k#2H0ESlE1n1&BK!$)2WM*?|CtGs*XHM-8+pvUFl)t_i0hEOHP^^wu22dK)7J z8qmHj6MaFW6voeaPOr@o4G5V5AJrGJRB7mu9p?oOTl&5)x#TFUx}y9W1B2?>dXw9= zpQXB!y=0BQ4>?7gMN=>b#Cwq-vr1)#jGQ`3*w00Nog{pV@E%!`MlMB(PJ3b6_dgP9 z83(fQ;z90wW;>)UzIS=TP}^;pI021r^u|IX+NDc+BzVis0+n}`0UoS;bOo^G%B=RZ z^P@@e)9+8KeOU*PUlW3-D&9vH+r$g@Bu?7EEggWQBdWG>*0$Pn0JH`{52L>VxMN0J z|M*2g zzoZ`;r472e4AV%$`hd)RKc&I&FYZtHqG|425D_*VUKF`iK-;88=}U$wA9S|F1s|0i z9GnB!3@e^{qdrqrha48^l~4N98r^fpMtwER2<5j#4yMTSujUq{#ZPKj03MFGxpmZZ zYwM3$sr(;CdG4K*82uitYFx<-d%g>2ZquNwzHhR?i`8o|oa0@W6a^`{*&MIc);l)6 z^=0^#FEICQ!g)4=SByu8jVQ$iDt$rjocNT!89?s<7#Gsc8)!yt&>9l}Z{nzM7y01h z)?BT=jK}2dx2C09htqM5z)mT!J|V74g2kUoonZmQ6IvtT<-iUrEj4%H#VkCNxzo1s z@%U=7v~CDUD23I}@qE|jlG0QS{_rEwOv@!et$9N`ks?aKUj(B7A0#sxk0(-7kefag z_I1D*1AHT0oiNKGq;6Uf`|T9Mpi;$rW%Oq(^+tTBzV2rR70}YN?uYR6@jV1R_3a-{ z+A=AQW@1IYoxa{s>eZck~R6cXejYkQ_=2X-HWDqq53O$y4 zj4IW$wfh>9c6AbYdbU4i^`m=A*+EMsRY(-#RLJ_g24KDjLmPLzJy)e+S~92zNc<@U z%)3xL1p!A>FeyVb=7V6efJzKcCZ5k6)X~Lum8O5(ro@2M(e#-=F!B%o2>U> zXvpoL=#Aw{Z|zLQTf%#bs@HM1O&OX>6+4!;{E(+{>qH7R1s~gDvW>2h`cfmC5hfo-2EthrI5I%d4xDdQuH&b=LDwl%h}A)6t+q z7BJBV3z1Z5pyk)dCq@tpd zL>h09`$nR6#A^YM&kec+Kkm}R1GF&A&dXvoKybE{z#TZ_*Jy?Wo}Lkv4;-29K*Q{j2Sf&zBjY0uS>zFr3A%z;-bWyUn$2I)JcXt$P?ZUQ=c z9=vE;71X#QTY6LcHj z7$@P;P;D;bF((>e#-~xBm9%-S{ySU&S#SF}qYThsQTUMBw`cdPm1DlPBgjKA0J5-l zS$`6@rWqbkd?t#D7IGVyi&ju{LF`}Heh;}ohFxYg0@kI0Pr6*0W7Q>vnQq^h!Z<;d zvcFc^^;>Ywk=kfI)p+>CA`c1J{F&vW@5M8`{%JyQ&H(47@ei}~J&bS#cj}uuu)Wt! zzBKdC-Z9*`Sl9HNWL-B;XOR;DXTUc)1hNdPb)=*{sDSpEqO}JrLz&nc#k@i2ZHmn6 z0P+i5W@l%Vvo7LZoCT!M_zC?c#O0f};6HgWsBi&G91FJsp~5OL2SxZZgEoG8`5`Y{ zt^GLU3ldWA%kFL#(FZn0zpx;`UQAA0NB-P+^^BO?8Ok)!Lp(&TplcGlNcfU(ZZqdP z4^!20*M3%kIPwuI=g=6}b`o{=%&TeUi{7&K`dYIix_BOgf?#f5-dK%C`=GzExBG_R5% zNolVgFh>SP6%RT9IV|w7T)Ai{N4XrSY^`X}jP>AVQ!ZDgxfM0%izu;x1cZpwf&*2G z_Vn?KT_(PKfqC?(ZcC9_X>3WVY)_*N@|^3;ADlWGWdHy#G74ztMa9J4L~jT^iD|rc zmiARU@P!jNa|XrHtO)pW@FM~L@7~hy_s0dNkc6CK^G1U?e;m}J;-UIcu=i7U4D@!q zzGcqO0;}n1L`nwyz~s}5`}0#@S?{E3Xc^v9f3%TGk{MPDn(<2IljKs4%=l4WR~9Fz z<2&LeDi<5OTn-GPl+$-+P-f)m6ojRRFDdeK19j(QKgiCV=FXBd+egqWyzld?9q5$0 z8DC_E@0rSE9aKbgj_b}i^@$Y$gPJvZr-BS{%-K!^N?yUrRMP75W#IQm44Xoi-6j2F zatQx*@uqVgJ9nUV@a?ivnv8X=9;kn(>G|j_yIt>`d&Xz#RkA4yE zuafr&hp4a29Ai4tPcCBBb8)>+wn-{XsG;@yUXSSJ9O{82^zT>E?-08I_d2Y{$2vR$ zfz9jRi0yg>h753Sx_OF2R}U)^Kv!zV(3G2u7mqpsx2sv;IU*{y>nDMOrJULg%gR0s z{J+l+b~RW5e@E)wRr}alRM%TEmUH6t-j)15Iu;hW{N|`YA9CH+7HaYTUlV-4-5WQ2 z$;g=>6AbxDzDKK!FBDD?J~Zcfm|nbl{aBa~Clc=Bz7e-sr&r*y8Q(sxQ~+)@GZvJ^ zCwAqjE5`}<$aJTcN&ToUlIi)?NdXR&SI%N15RQ^WwB6NvlN8155JmdWMQ6AV^ApJnTIty0- z--aTt$AN)_2M)lpW7*ws&|q87!E7_!T&EZ*Z@zpHB?fr6@VU_bFbDp&seC&XMd{7l zzR9d;$0b_G+}YLdZi#%^CWVA$gCgOPg3souva1^wH`GN0O_JK*rWjYO@PWhXkdVQd zuEs>~lkd;zb5dy7bd#Or>ptaVL(DDZ8FO+Aw6%;E?RNf}?rER;8w;Majd_;kTe@?Z zcJpu&Ycqlg3mmoN%*XDW#)aw+Y#REd#(RpM_smXU^8@ODzrKXu*X5E$Q)RZLaaob0 zGDRjof6nM(kEnIrVGz2VwOXbZN1E0_JPIz*3|x?bbmCG^m1}N5vY^g7x>aG1(!IF0qgO@Xfs)h5k(+bQl%)R7f z-PKRF&~~&C1q25@nitQuP0kz}#uum3z5oU1ZE_le%Khe=IO?QsOK==NT5Y}1spqPh zlnv4Bf6<0Fypvri$oRwqS;we;n2!QE$;&{1Wgy=&{0G7@`DwQV{qQBAS)(biZaY97 z5io)EEmTkcD*NX^18P+BpIKb3<(iS7Z`c)h68Gv>k!$ST_;q9B8Vx6(BIANV+;QK5 zmPcS%ifL(XJa)xc)u14+pksLNS(h2ZzkljuAfNrzF6rWqi;Si@FGNLPS(>b0%7m|2 zCqQajv|ruhBkgkm^09w#RHYJ()0-3y`$fK9_8Jab9_AWu7;nt$=I<_)ef6E%O-%9j zjiTIsJZ)8e_cgltN-NLBV}=ud>J8Ao{8W9dm#rbUqycoqw;41z0r?Z#m)o43LyEvj z7tEZV-ky`rB{Jl77T;w?_X-c23p@Yb3V?K1($*TaemLPm3#LjUb6DXmEyL9DnY@`v zCL1_W5%?7w;~#>!bwXf)e-wD}ZrD{@RN7WwHBN+PA`Gr1h$+0d|H|ZiRlYh;pBr>I zGcxoQ54zy>4jgz@{jZtqT&q1U!6Y(L_K(K60_$x4Zt z4~N>k;)po2i9eba0S%Wfn|791v9BzKrFw3y%T3h~GF@jXo{X1SNQn#Dz&|ml9I(L6 zfOTHDsT61yQJbNF?}Zm?w@>uFjxdsELmV+@%7C3{r?wV`>M|6uTdEe8_g|kGGBmMs za>kc#!sNw5|Jk#vJP|Ys-n*cG?5su6rFjtn6mfqWyQJ~CQQ-WnLng;Nbd{>8Y`S7y z_%|*bI&^rvz=2=d`)(O&-@}E#H&Z_t_T)T~B@|ptdZdkp!<=jU7+Qp=xxh{=RVo)q zatmsFy4Mrs9sX{(YN}la8?djtcl}i95I_FZ9C#WdVIzdQ=h7&v7Hl56nvWsvXM z(l#1%0!3zj8C0Ir|23m6URL%L=D)E~(@zxT0&Iu*E&;^%T}=2Nr{f;b&>=VtIE`6Y zSc0o^b5FkMJ^GX;se*15i8mG&I(7GFfOFY*qjgv%#Qhfk7{Q=f~b8;6xt&_7#8htr=nI0;k4WIf`*LOOYCNFPdPMRN1+Cl{eUU<^nhOtThI=FfsOjSW1$rodNq*p? z&b`?yJ&DwUY2x@bd2Vjr^ayX7O%a6!?p}Y8xY+*%BIpV@2KesGi7==`yos`m_~ zs`Kt#{cDnktn~ikiu{Ovuke-tf6&?BRNt38^?x{gWJ7RuBe;{J?1pGXkJT@y!HDS& zZr&oKo+GrO7e2=D8jMqbC&fF_Nd_a0hn9_Lim9TXJwZ82s+UwTE0gXD1V_U)SKP9) z36L*jg8|aw8VoDQm6W;cYe(x%3F_mDhOk3KEy1@eBk#PrktVp@%&(G#Sl!|uBuIIE&w~v@N zpO5m7RS#-R!VlafA7zDC>J@hsuPQa)ry`Fy-qtWP!|1jtuUH8FizBnN!oE7=joIM5 zvmJiV09t49rGKJ>3yM4vyG3;ktqtAkfIeiP9q610)FOBj(F%m_)hH{+zQXJA&!ryd zLhD78O=;$3sk=)H0e9Z34}(J%MdVdOE{a$T35gp0bta)`{=_F57GU-T>^L1G2RsO58OqGKd5#y zwB`1t2|O82e|HxCKih|>3QOUGTqvv%4Ljh4m){zE88?U(2jqXoT&CG=#YoVGtSo7w zuBjUFK`YBln3DvWAdTJ&A-^XmJ7ab})Mx+QXE_5^ieh5aGR1~}9CZM*JD|r2Q21Yn zxI_!z-__8vysMU*tJ<|7f%mzw8HViR{&(H^5qBy?NYtlw|JiXsdz`(W1%zjf;9Rzb z3BpQpk+);>Af&T-Q{e#zVj;-jI*eHmY8z5T_%o8-SP_Q;j}(vLME}#?2x%VM3=68gPLpR#aI@{;0G+1;c-Zoj*fh$YH~asSHP< z>iEqYTQ33Wm{T-{4B5uq0)RU)-c~b(g~YbNv+$oE|HHQ_U2ns#uXoU0fL&@=Qv$uMXc9%C(0_T;)E$mCZ?uvsL^5<#izNp7dBeYfxl%&Ji5 z^qpoB*qz8JKA_n5jGLV~Lw&iQA4w|}A`VRTB&5CvVp#w$yQX1p1~}{pCdmNjXs+kf zPVB5;u!#4lCvMuk`(Gfaf)KXL;^W7*aS(a2e}Zv$<#30AwfS4!Yxf zL@uJ;u0M&G_va8KHs0-UO}Js}I0;g!lbB%HBLa_IKU+f@lFbjYAQStCLv!A!X9{aE z5Gw9kuwkDwPJkf;F?FoZ6&c~%v|tk0lm&bwXSzBF%wwlqR}6Y~1U8GzhBPpql4FZ- zVNa#5zXw9@Q6qJe#OcKjzBC68mgn3f~!hoM+ZmmZZQD#d_q6A>OGzPD4OOjMGz1iLp!%Tk~Qo}0)6V( zp{E|X;Da`^E(6yp(Q5>=r*TKeK~gOPeR_rWpt%L~R#ZuWoNPu&M?wflgpYDs(Ws%s20V54h+x6dF zytu=~>2(VTY30Ed(c9_>7@>oEB8o=Eh3uNqH;d^x$mAt3ubtKp3#Q64iP*= z(L+mJ-maR6Y`@#v!5mbWc~34|<<`erz`>Vs@NVxe6bUdklpufuD8O%JD_PugeI%~J zc};1mUe%ALZdzz1H;>NN{DUHbAfcurtVOWk53us$?n0qJY3nqV2ZSr*nuzQzCmt5Y~g82E1t zWbZzBc>hKmfT_~h@p4#mW$_@Yb6bNaaf`y9XN zk7`@UjV(*Q9HisAX5#i+Kg&n|4_99u*VMoNe>S?LL+P9d5~89K1Az}9rC^|v0s@M( zG}|aCX=Ng<2%-oGNHbCg(jtP?h|%3+EPls(@8f>GzyEmbAI{l%pV#YIuh-smHB;E$ zAr&_&x{eJ9*2I*1Q^Y12K7d$fgJ=StjpG-8cf_zp^$ zAk;xSXQ~_*t59#xZt9H+Ie!J+v@7F+wDK;iaEoAM!yU0QfN_(bF%`+}SK*OLU1DrI zxYAuwQIFaE(rI8V5^Lfc?ua#XJlElt zp7B_Umv`I2H|;|3z4M=axN9@eM{gcfW4RQ#&8(a&DI~oRY9-s@rO6SSQRjW))wj;_i(lZ|~%^9O_Y9a&1jr>cN3mbM-&Z-;iu87BVdWRGv8@n#*p_Zv!-h->&uzT;PYG&L^VwzfA9iC`stT)w=?AE<@Pmz zUwIP|ApV_=3o|TcNZb0kfc% znsm6kCP&IiEo$iQ_XD}|POx#DFE&TvVS|--tzl^Ldxr5|Qt%H>5sx3|p468Cb=Y)rpvR zO7bjGGJAQKVVY%^DWxE4TcS&$;l*-m*F2S`C^c zgKD>%bS)o-r`TFR5w`(q=T5YC^Z#0WRT}r$`&R)opdnyc#mHDjztjQ$WqdlHk&y-U z4kUa8lXf|@;>#La>cQ9@o4|Qc-~CrNo5%G~=lc?E{)9#eV2H;c36yPSo^q%DeC!`& z9dn374|JN7pBD{gL;cW>=4;6DBsWteKglTX)Xc486EeX)DXM|sK9qf?1M6<#R%$)oDK&(33 zH>I&8*h`3@7@bxCs7V378zoT@4cOaiRH?1wBR7l?nian6q*NbHPTw`|2)IX?z-H3YGH8-bMDdd7KWF*a2-JuZ25o7cs07=>G5;OtH}i(4hBn&BpV;_`d)6$a}*r2Q)p!Mc1bk z$1iRs??3*PSxu@BHVek%@+O;IgVN%pEAD|B+wJ#}aIn#HP61h9Vk4F8lDcV96AUmj zKzG|g==o5~$Z;@uGv|m1ZUegTf6ozF{rR(sElf$$!D2xKn50R@h>96porvs82=I1< z-7p+#+kZ38lxDa)rn$NaGd~Ck&c=iC5}44bf=eFm5@*|vt>$7M?Wt|QuIp%yS1Rrm ziXBgs9ym~r>K;30P3j_tMy}Q!Czu-9d<$)!p}ZM4IGcIqwsYcA@L{6(4rSQE4D1&E zCk#Rzif-Pjxz;szr49)fpqY*X8}ngbXvNc^i(tU)m()7wR-K3Jj7>)8vxH~y1_9;M z^mb(&p#F^5@i+vlD?~e!>T42eeh1)k@G1d1gBE+)mm!RQ3g<+fT=T@}kGmha`6ua? zbO-3(i_o-~IZilsCMX>-alIy-tBmcyE5_Kpt_)SPe@(_LR;-}~-5)IXQ6IFK-k#JQ zj?Xh-N!{C0%Q&i?JQO$PTvJ`UYuQKk5d%V@angpYWSR8+027<{; z!A!RL@FCBp>BF)H;0d@jVxKMB{HWmTbBO0*4yYl3DNXz|O#hQI;BB0`!A9hL0_Bkd z(&f{HhDgqL9~@Ihe^Wa?0tXpXvBo!Iw*+*#s7m3>zm3r9!Q}g8nE#l2B?Sc}QU4VM zty`&_pl-#(gbSn@y&-!ohbkBF!%;RJG@o8X^pij8LY3NyR>RT)T zx^pMjN8rb~g@9!#7wN)pxlhoRYn1G;mT*uL)@uRNhALETT0QMGW|T(gXGcXF_5EM| zm|emDP$T!r?^I-cJ%fJEiA4Pdz{EiJTy*xHl(eZ2n_|XuDPKyXUFP-^n6yL2gVOHi z_Xhq#Q+tGCU$kKjWicV6dKE_cyu~$Hu-TVr+?@$}(%{YuECiG+wpPs4AABAy zYmfO;Fu;?!t&4k1xPnPk>kTdovhm+^xkRsZD@|PZ#<0HUMh@m)Relsl`yGwtnfscZI9(rPn) zscqJM3BTocopg7+^l%Nwmu1R!K}iox+1z_7g~T5Cr6O5C9{AhEOJGe-9+)U}nAtfv z7z*v%eu7+3+N(Q9SCxOEcyN^OKL#0s z2h@>^N&xJ%*|k2VuYx{JM<%JZjO`W=o@dJ`1}3Mm4Bb;OU+iL=8OKtbgSxJ@DL@d)_g2)=V> zsABVN1eh3!row~4o4k{19sQM6&+C$mYvy@biz*Wpso%b*-XS-+by2{Y%-nq%4$q1Wro=1B2xWiDKKMyk<&j-{P_$NPb>pk?k*4r{%@BDmk z<`IGnMfAVv6!dyS=|>0dKE&LC_XR5AOj4ldgMWK#Z_On%muLXzm%s6&&@m+HX{h{< zO?j#^-j*525cxSK+M+`wE8%hK$^=@?piPEF+o0ulwzJI^t>JVmeDap%tYhzXc5Ih7 zgk|;LaBf04m{7}AbS-Tuy3cahvjWIcT3B7}?zENe%`8TQ=I(*I82%*}H4s~zl(C8N z>Q#NHGr=Y_>n7~qyn5L*aaj1ix3QsSDz>%%Q(|dHnl2Vf))hutXv_DjjI0emhqPT|nvpa^aP1w< zVh0yP@_f>Sa+3sJJ1EQ;-=gZXn8aM>L}Npp{O;SNpNK)~^;mPZEE)+{(#DDZuKKjB zgShB8u{TFt-2P2DojTPd&zWp#1wJ`c)BkP-p5PtOQ9>wLZe8b>L-(ItH0b( zytNE1KjiE1&tibb>r|LQ%k~EO-09BVcqUC4S-H#kYD+RQ_LADbgKTjFxbSsstx`EH z%7B6Rn;jRpsAHAhi7fihQ?WZ)HH1|pCF$t+%P1{UC+3R0m0Yq3Zc zzX++r1ZKqJ58!vV`q`-N=->Dh;>;7oUU{rSc&hic2P!P{s`-jO8eA?r{j?~-+{r}Z z$YLv<2K<8vx6HRB*TuwmLcGHThm6`|F;h^8A7i-iW&I4@lF6mmU|?Ft?!I5^*zl^v z)m+7mfyWTLvZE12>NPw1Jl?=vMj(l?ysOy^aH%Lubf~+sz=--??^Na!Gdn-G=kBiTR^-YEaJ}yiaZKjE zh9JRcHtOage?LiM8XdXUsH?y4ym05`jh(WU3_`$jKN7-o$0MZUX)Ul-^#z+Y0N;){ ziBh6_-w6^Zmf;JZz*SFdXoX1>H&(H2N<-hidqKGT;ryx2{yxr*2|gfh?_)FgNIVWI zN(_|n_xukXwt8D$0ZRUaF9OrSG#n5u-SD%3(uwDrZWg-a~H80m)-DGG6P<_D937T8C&U()M!)sORDV!**#vbe^ z`m9&5ru9SHaB_|MeGYehz*8Q4!7H7}?~Dq7JT=JU0!d)j+k{ldp)>%ERDj&8cNyFo z$B{QSw{XS(`R*<0&vF5_C8e(4>s9wT9*U~R+5+Er^7>`K#TH5Q2#Ni-I^NVs-w#2J z3RH}eXZX=vSniqx;jkyXubAc98#eKz-0M;?fDN3iJV5-(pwYMLBf9Wv(uN5YdvYmB z1=!bea^imR;^upvJn-CWk5(aAO5{)T$ZACaFcXA2Z&Uo@8-!PoJTPO5mFF4 z57vKBtJIUrNoZQx4l{9GbVUKd)On=U%uRr zpA2pP`pOC4sv`?@u(YU{@reN>UD-;h!y=kutTE%HI(`#&@}gmNS9LtzZ&@Py;)}*z z#QERpP%Y#FqWJp2ddPliOMj6D17>uA8>nyMQ}Ex=h#cH@U0N25xcCyXbg)i;S^>ib zMl*SX0kDkngA)CY>kXO-LIW+7%S62oMI`?N-4Va0_o%yT$-_z5Z+8jK_<(Yf5|XFMOzllSEM?b{4`dh-%=r#B!dS*?+hd$9Dq&VA}y zDLfSkZ9HyvV{(V4OWFd}wBR}tz`Mgg39u{Iy&4WEnL%#~?uUDs?j1(>Pf=uKWTyYR z^s#qFdPwz)nomW2DDcEis!$;iO5nN9L141bsyEO(LDhl6unQ_8{!E)~i(mJ(Lyx|n zu41QtaXYSNgo|%|6XmC)2)N|#D8j2b3`D_>B)qgD@@yep^Zf^R@Mu`oSuw*BZBe1Y zNo@-zYIghIza*ox<(M!w3u#yX6!@d=?I?RHON#*|_YNSyiJIq12RJZjbpqva8CjW{ z!2AavfW^dV^wAl?<_pS~cL|i9xd3)l2E#eJ_u^`Z9bOL9wA3hNji08`~`trvCG9Fk{zx&Ly7jEb)PZ&th~M=VvX5ojK|eB#%i5CIuG`K+N(c zi-dH%M#F}cr$=5JO$A=|FypXoP8v0rvK88?%YM{#KO#I)S+Tm(uIfhAj!Uqa2e_~ zz)}L`#w8Q0ez|aphC_{70RxEhwl@k_6p!(5`)X_hr3(PIe}BcAX7RKw=#@vWYwY|| zjw{WkyM_z^^*CD8HOLA~7m^W~fm?*d!8Kj3y0tW~1}QIeDP5p`!G?~nUy}7-?QVYd z-iKN-p>CKb(K0b?##=-_k)Ufn`QjB=R0;rLfjhx~ua2G`w^hS!$YLK~`;1PJ{bFKh zx!2@BmQ4?wbjK=Z-@(&?7(G;oc)&6&R}0}+%E1Z_C?OVcd_X~>%#rfZq7mvRH*{aK zMPD@J`a$+)$l@KsU#0K%?b`=N+1U~3a#_JyQ`a7^$#7unAU{yT^u(PJ;kTF9_YMh z>+v5-Dl=2d`T1m`zQO}x-LQRx=IDiqJqKd2oBIx=IwkU3K;UHNvnh;n9h0RF+5mFz z=1qEVa3!34oYY=@w!x14djI;XJ7S%FZvc+uWvy*t6u8AaIVv)%30RvT>qGKvM!#^d z0Q2o!Kq?-47b#FNyuJ3$Nb{Hw;m>lFKJVXMf6b>loY~=n`YQrGsy(?8m9hC&OHkt_^=~~QFlsg^qUUVoqXaR@k6vK56C*>}G zG-Ut=yeSRPpH72$G?KDMj{Xlpw*ML(-Dw5^WbYPF9_CB8f?)m*%w`DEj*BeefUpJv^^(C$7X05^Q& z7==E|ssPdTY$i3Z?+$wk=!B0cCEPwrQnx(VDkg!n_$62H_C#`B= zdf+j6-TN~wdweTW(YO&Yqw^;)R`L)y1S_S0;@F85us(IcLPOmjoPHJjpj-$Nym z(?ZF2sJPfaXbe19b-F?#nMnuG8{w7 zY?$Z;@GxrJmQO`4vlZ~(AJU=5VAMMlAuX!#i2CbJGm-tT3y*jP7CrwH?g_L&kERv` zxC%@pb)fVt1%BrjeC6O1T~JG338-JU7(Q~+an?!e*KZ3mTH^ z=68=SVnaKoB&f!;F&7g7G0msJH*g8Pynvq-0lGDcm)~PE28-2ePj-~dzc5+9Rg`3O ze@m(GG1wi>mVeMH0_Hn#ZkU&Pk$~pe&fW?Ya)A`p3fS5V3QUp)jO)$L0<=dkO*CS! z^%K8s$Ts|MY72s3%*5}@p7dRv5&C9M%-6*MM|v^QuI&i!bYErLabKdrQ+eTnjR@`w zqCbk$G)Z1kbAf$dudX&sJD5q-F1M?syM|OuA1=!c&ASWj-7CZ%3B3-oqLpgpWC#&a zpC?wv-aY)3lyR8@YWILX==hr+^FWX><+FB+awu z#i`vq1;BuWn4%hkcg5+M#Rn)xz=TlDF%3nv*RUEivE-pS=XEXjrqp2kO@vwW-vHT< zLOQZtT^IhO`{0cQSeFK5wL)J}=f6gYz0AFdCJETatc=Rl?Lg>%^3~c1waCx#BJyw6>wA{xCa7ADX z2-K%rD0AhvKmi4E8_&x)yUq=COWLCp$={%2S2Z;u;AOv*rbSU!?=$l(l16gB09rR% z^nl_dyzM4&l^NA}nEG{V=CGNn@&Bm~VDq3HEsZv*z1l@?bXk060GI)iDN~G&x6tgY zPCY(}OBLZZ<2e_kiQ|h6J^{~Q$WWC+!>dP^j#Tg$12N{pb6AVvG|ztag!WJR)Kg23 z#}cj6VOCVbtphkVCu?~Qv+V`m8C}bCc@N0j$Jjy9;d)Rdy@E}nf;nnd1h{{3lLOQr zN-+ktPr#myEy8gE_h_#}@;aLlqGAVM0=D1Q z*ajcrc5v_i%YfPZ$4g4wh{$iSjehYN>>pVFew4OH;zFF1D>MT6)fxRAo}LxtelQ#H^N56fXHctGy^gqGm}F134N&?TNw?h{ zMVEx-lIu}S2cHt;CFFhGm;M4k!xoB7%#H?8Eyx{e8Bn7?yL0MB!v<&|dL@rr-*F$2l}%)g*F9ZR})yFR$)dA-ck zxDPc-Yy1w#z9p;0wk+;QAIrj(>S|h;!}em&F?{bg`v>E&&c#rN@clz0Wb;u$Z|(#YA4GQc%`;+6EHAO#3@^! zE(C_VgU-96jlz2>#Ebw>4oF_JAS4eX$7xpqah(w(KRio$PwiE;Jt3O1RHScrmF5kJl;O1se<3UIppfZKQ0OTBLTrhK zM^Bbe;ek_5X0%|G8hrVSSa)^^pFZULn>gb5-h^4@1UrPo(;-SNy6@@B&@f zo6ieM$tU}x6e=Bz5(n9JDDy|)4ZWRH85mNfUl*ns82u@{3GpQE@uon1M?m0jKl#sR z1}*nr-j!?73zQmK;2~4nKDBP;1e_ZnZZsp5N5!d{kd~yb{#iYHGA+>6O#B$820`w$ z9xo!Ohe+z;-&MUj^!UJbd09|FhwEs1rYQC#iZ$hRlCg#$-?hYrXrl=}kp?w)?jmsM zO7(JkRgu?@<@z}j^-l!G8NYEw;E*RE@ZNQ0DNgeFZrQHFxjAyLS&2n2-LkFDJ$97?cIT250oYnurcKY~X=2P~&0+8HXtg>Ntd337w}dml`6?nI9Fwt!TJ55yV0#9rSuS zV{W-)u|?OL_e3 z8{MXlrV_4iTR*NU`1>4XNeI?qGEk^p^C*X^YXOGuX>?D_OjkXQ)n*0UqHu}x_GcT~3(y5(DA&&b4JtBIgZkd? zpQ!u=<2Lb0cu}SRdjv*;%(Fp6vK^mjmf69;0A1SmnxRm!`+J}Fmccc>J9kK`|1GZe zak-&#>q=!qw~Q5ZD)@}UMb|XBV)w$e=KLKN zr1~+HMvLvq>g0@Hhhc{=*Cey6nYL1pbVcshmR9vbWu$)B7n*A)DIUz{`oMoSD!DX| z*{a)ylVC?hPoD%}b&^)AXJ1_rt;!*;iNaiqRzOvq(rPFU=0lJ3s!N5xe}6Qy^G?un zb~JFb;aJISs!I4*$vqmn%MaOsY_fi;yU(I6j@;k%{TXjv-Sb8jNZv1M15_;ZIz$X3 zXB#y1x#X*8KW3lUjqE_a{jZ}?{rdI72yC+l8+DWm?LAfc3}K^@3IHbqRCS1NuA@!Roe^mmj~DVrogScL0YOkc@ZM37evOC5eFAp@Y#h z(pNQ4<~1-3`Lik=U*Fg<077;K2jXqXE1=~4SHnL9me!UOIZmP;=7JKP1l{{F8dMnf zr@gSqZ)bg0o%q~ybJGza-`T7RHGWpduE%%OSg*;|l77tiBR8EfnS8kb z-SLtU=Vc=k^3f}1I|DPPNo$-DmL;dG`k_=~lj~vM#Fg8jwjoRRBSwk_p11}tba%9t zGF;gB42t9HqS1+c;wjLod+QL-4J&0K&5%=m8>7zefG>X31U*zJOr*Qq-NvsPy>uUn z5>kPp6a}w+rtK__-W2TA1g9U53GL7BfNWso?=qW?Mj!4zdD6s?Yz691mOwdA-ivb2 zSu`uo%QTg+-6Qd2=Qu0bk!{f#11>XVlI@ra%3!?ejI>`r?~0Ec(w8uCQ$N9+|^tBsoHFqH#guAf4n7Zb|!vK zWsxmu?&zd&Gkeo3Xq6AgDNsVMs5n__{Sl?&T-uheQ)GudXezfqTL_$`ofz$wp@N51 z0od{Jjs�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

*P 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 z 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

LPSM&ubK%ca3`lhwu^N%K^ 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 MX&Y%(XxK&nA3I&c%m4rY literal 0 HcmV?d00001