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 00000000..9c656897 Binary files /dev/null and b/VENDORS/Tektelic/Comfort/photo.png differ