diff --git a/VENDORS/Milesight/AM307/ChirpStack/uplink/result.json b/VENDORS/Milesight/AM307/ChirpStack/uplink/result.json index d5a8ecee..9ab9046b 100644 --- a/VENDORS/Milesight/AM307/ChirpStack/uplink/result.json +++ b/VENDORS/Milesight/AM307/ChirpStack/uplink/result.json @@ -1,49 +1,49 @@ { - "deviceName": "Device name", - "deviceType": "Chirpstack default device profile", - "groupName": "9-in-1 IAQ Sensor", - "attributes": { - "deduplicationId": "57433366-50a6-4dc2-8145-2df1bbc70d9e", - "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", - "devEui": "1000000000000001", - "devAddr": "20000001", - "fPort": 85, - "frequency": 868500000, - "bandwidth": 125000, - "spreadingFactor": 7, - "codeRate": "CR_4_5" - }, - "telemetry": { - "ts": 1684741625404, - "values": { - "hexString": "0175550367EE0004687C05000106CB02077DA803087D2500097366270A7D04000B7D20000C7D3000", - "battery": 85, - "temperature": 23.8, - "humidity": 62, - "pir": "trigger", - "light_level": 2, - "co2": 936, - "tvoc": 37, - "pressure": 1008.6, - "hcho": 0.04, - "pm2_5": 32, - "pm10": 48, - "dr": 5, - "fCnt": 4, - "confirmed": false, - "gatewayId": "6a7e111a10000000", - "uplinkId": 24022, - "rssi": -35, - "snr": 11.5, - "channel": 2, - "rfChain": 1, - "context": "EFwMtA==", - "crcStatus": "CRC_OK" + "deviceName": "Device name", + "deviceType": "Chirpstack default device profile", + "groupName": "7-in-1 IAQ Sensor", + "attributes": { + "deduplicationId": "57433366-50a6-4dc2-8145-2df1bbc70d9e", + "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", + "devEui": "1000000000000001", + "devAddr": "20000001", + "fPort": 85, + "frequency": 868500000, + "bandwidth": 125000, + "spreadingFactor": 7, + "codeRate": "CR_4_5" + }, + "telemetry": { + "ts": 1684741625404, + "values": { + "hexString": "0175550367EE0004687C05000106CB02077DA803087D2500097366270A7D04000B7D20000C7D3000", + "battery": 85, + "temperature": 23.8, + "humidity": 62, + "pir": "trigger", + "light_level": 2, + "co2": 936, + "tvoc": 37, + "pressure": 1008.6, + "hcho": 0.04, + "pm2_5": 32, + "pm10": 48, + "dr": 5, + "fCnt": 4, + "confirmed": false, + "gatewayId": "6a7e111a10000000", + "uplinkId": 24022, + "rssi": -35, + "snr": 11.5, + "channel": 2, + "rfChain": 1, + "context": "EFwMtA==", + "crcStatus": "CRC_OK" + } } - } } \ No newline at end of file diff --git a/VENDORS/Milesight/AM308/ChirpStack/uplink/result.json b/VENDORS/Milesight/AM308/ChirpStack/uplink/result.json index d5a8ecee..b50402ea 100644 --- a/VENDORS/Milesight/AM308/ChirpStack/uplink/result.json +++ b/VENDORS/Milesight/AM308/ChirpStack/uplink/result.json @@ -1,7 +1,7 @@ { "deviceName": "Device name", "deviceType": "Chirpstack default device profile", - "groupName": "9-in-1 IAQ Sensor", + "groupName": "8-in-1 IAQ Sensor", "attributes": { "deduplicationId": "57433366-50a6-4dc2-8145-2df1bbc70d9e", "tenantId": "52f14cd4-c6f1-4fbd-8f87-4025e1d49242", diff --git a/VENDORS/Milesight/WT201/ChirpStack/downlink/payload_20.json b/VENDORS/Milesight/WT201/ChirpStack/downlink/payload_20.json index 60d4a751..84e8bbd4 100644 --- a/VENDORS/Milesight/WT201/ChirpStack/downlink/payload_20.json +++ b/VENDORS/Milesight/WT201/ChirpStack/downlink/payload_20.json @@ -1,3 +1,3 @@ { - "report_status": 1 + "system_on_off": 1 } \ No newline at end of file diff --git a/VENDORS/Milesight/WT201/ChirpStack/uplink/converter.json b/VENDORS/Milesight/WT201/ChirpStack/uplink/converter.json index c89b720c..439a9a72 100644 --- a/VENDORS/Milesight/WT201/ChirpStack/uplink/converter.json +++ b/VENDORS/Milesight/WT201/ChirpStack/uplink/converter.json @@ -5,7 +5,7 @@ "configuration": { "scriptLang": "TBEL", "decoder": null, - "tbelDecoder": "var data = decodeToJson(payload);\nvar deviceName = data.deviceInfo.deviceName;\nvar deviceType = data.deviceInfo.deviceProfileName;\nvar groupName = '9-in-1 IAQ Sensor';\n// var customerName = 'Customer A';\n// use assetName and assetType instead of deviceName and deviceType\n// to automatically create assets instead of devices.\n// var assetName = 'Asset A';\n// var assetType = 'building';\n\n// If you want to parse incoming data somehow, you can add your code to this function.\n// input: bytes\n// expected output:\n// {\n// \"attributes\": {\"attributeKey\": \"attributeValue\"},\n// \"telemetry\": {\"telemetryKey\": \"telemetryValue\"}\n// }\n//\n// In the example - bytes will be saved as HEX string and also parsed as light level, battery level and PIR sensor value.\n//\n\n// \"G\" Uplink; \"G/GH\" Downlink === \"g\"\n// \"CL&CN\" Downlink === \"cl_cn\"\nvar bitMaskWiringSettingss =[\n {\"y1\": 0, \"g\": 2, \"ob\": 4, \"w1\": 6},\n {\"e\": 0, \"cl_cn\": 2, \"pek\": 4, \"w2_aux\": 6},\n {\"y2_gl\": 0, \"ob_mode\": 2}\n];\n\nfunction decodePayload(inputArray) {\n var output = { attributes:{}, telemetry: {} };\n // --- Decoding code --- //\n var input = bytesToExecutionArrayList(inputArray);\n output.telemetry.HEX_bytes = bytesToHex(input);\n\n var decoded = {};\n decoded.hexString = bytesToHex(input);\n\n for (var i = 0; i < input.length - 2; ) {\n var channel_id = input[i++] & 0xff;\n var channel_type = input[i++] & 0xff;\n\n // // IPSO VERSION\n if (channel_id === 0xFF && channel_type === 0x01) {\n decoded.ipso_version = readProtocolVersion(input[i]);\n i += 1;\n }\n // HARDWARE VERSION\n else if (channel_id === 0xFF && channel_type === 0x09) {\n decoded.hardware_version = readHardwareVersion(input.slice(i, i + 2));\n i += 2;\n }\n // FIRMWARE VERSION\n else if (channel_id === 0xFF && channel_type === 0x0A) {\n decoded.firmware_version = readFirmwareVersion(input.slice(i, i + 2));\n i += 2;\n }\n // DEVICE STATUS\n else if (channel_id === 0xFF && channel_type === 0x0B) {\n decoded.device_status = \"on\";\n i += 1;\n }\n // LORAWAN CLASS TYPE\n else if (channel_id === 0xFF && channel_type === 0x0F) {\n decoded.lorawan_class = readLoRaWANClass(input[i] & 0xff);\n i += 1;\n }\n // PRODUCT SERIAL NUMBER\n else if (channel_id === 0xFF && channel_type === 0x16) {\n decoded.sn = readSerialNumber(input.slice(i, i+ 8));\n i += 8;\n }\n // TSL VERSION\n else if (channel_id === 0xFF && channel_type === 0xFF) {\n decoded.tsl_version = readFirmwareVersion(input.slice(i, i + 2));\n i += 2;\n }\n // TEMPERATURE\n else if (channel_id === 0x03 && channel_type === 0x67) {\n decoded.temperature = parseBytesIntToFloat(input, i, 2, false) / 10;\n i += 2;\n }\n // TEMPERATURE TARGET\n else if (channel_id === 0x04 && channel_type === 0x67) {\n decoded.temperature_target = parseBytesIntToFloat(input, i, 2, false) / 10;\n i += 2;\n }\n // TEMPERATURE CONTROL\n else if (channel_id === 0x05 && channel_type === 0xE7) {\n var temperature_control = input[i];\n decoded.temperature_control_mode = readTemperatureCtlMode(temperature_control & 0x03);\n decoded.temperature_control_status = readTemperatureCtlStatus((temperature_control >>> 4) & 0x0f);\n i += 1;\n }\n // FAN CONTROL\n else if (channel_id === 0x06 && channel_type === 0xE8) {\n var valueFanControl = input[i];\n decoded.fan_mode = readFanMode(valueFanControl & 0x03);\n decoded.fan_status = readFanStatus((valueFanControl >>> 2) & 0x03);\n i += 1;\n }\n // PLAN EVENT\n else if (channel_id === 0x07 && channel_type === 0xBC) {\n decoded.plan_event = readPlanEvent(input[i] & 0x0f);\n i += 1;\n }\n // SYSTEM STATUS\n else if (channel_id === 0x08 && channel_type === 0x8E) {\n decoded.system_status = readSystemStatus(input[i] & 0xff);\n i += 1;\n }\n // HUMIDITY\n else if (channel_id === 0x09 && channel_type === 0x68) {\n decoded.humidity = parseBytesIntToFloat(input, i, 1) / 2;\n i += 1;\n }\n // RELAY STATUS\n else if (channel_id === 0x0A && channel_type === 0x6E) {\n decoded.wires_relay = readWiresRelay(input[i] & 0xff);\n i += 1;\n }\n // PLAN\n else if (channel_id === 0xFF && channel_type === 0xC9) {\n var schedule = {};\n schedule.type = readPlanType(input[i]);\n schedule.index = (input[i + 1] & 0xff) + 1;\n schedule.plan_enable = input[i + 2] === 0 ? \"disable\" : \"enable\";\n schedule.week_recycle = readWeekRecycleSettings(input[i + 3] & 0xff);\n var time_mins = parseBytesToInt(input, i + 4, 2, false);\n schedule.time = String.format(\"%02d:%02d\", (int) (time_mins / 60), time_mins % 60);\n i += 6;\n\n if (decoded.plan_schedule === null) {\n decoded.plan_schedule = [];\n }\n schedule.i = i ;\n decoded.plan_schedule.push(schedule);\n }\n // PLAN SETTINGS\n else if (channel_id === 0xFF && channel_type === 0xC8) {\n var plan_setting = {};\n plan_setting.type = readPlanType(input[i] & 0xff);\n plan_setting.temperature_ctl_mode = readTemperatureCtlMode(input[i + 1] & 0xff);\n plan_setting.fan_mode = readFanMode(input[i + 2] & 0xff);\n plan_setting.temperature_target = input[i + 3] & 0x7f;\n plan_setting.temperature_unit = readTemperatureUnit(input[i + 3] >>> 7);\n plan_setting.temperature_error = parseBytesIntToFloat(input, i + 4, 1, false) / 10;\n i += 5;\n\n if (decoded.plan_settings === null) {\n decoded.plan_settings = [];\n }\n decoded.plan_settings.push(plan_setting);\n }\n // WIRES\n else if (channel_id === 0xFF && channel_type === 0xCA) {\n decoded.wires = readWires([input[i] & 0xff, input[i + 1] & 0xff, input[i + 2] & 0xff]);\n decoded.ob_mode = readObMode((input[i + 2] >>> 2) & 0x03);\n i += 3;\n }\n // CONTROL PERMISSIONS\n else if (channel_id === 0xFF && channel_type === 0xF6) {\n decoded.control_permissions = input[i] === 1 ? \"remote control\" : \"thermostat\";\n i += 1;\n }\n // TEMPERATURE ALARM\n else if (channel_id === 0x83 && channel_type === 0x67) {\n decoded.temperature = parseBytesToInt(input, i, 2, false) / 10;\n decoded.temperature_alarm = readTemperatureAlarm(input[i + 2] & 0xff);\n i += 3;\n }\n // TEMPERATURE EXCEPTION\n else if (channel_id === 0xB3 && channel_type === 0x67) {\n decoded.temperature_exception = readException(input[i]);\n i += 1;\n }\n // HUMIDITY EXCEPTION\n else if (channel_id === 0xB9 && channel_type === 0x68) {\n decoded.humidity_exception = readException(input[i] & 0xff);\n i += 1;\n }\n // HISTORICAL DATA\n else if (channel_id === 0x20 && channel_type === 0xCE) {\n var timestamp = parseBytesToInt(input, i, 4, false);\n var value1 = parseBytesToInt(input, i+4, 2, false);\n var value2 = parseBytesToInt(input, i+6, 2, false);\n var data = {};\n data.timestamp = timestamp;\n\n // fan_mode\n data.fan_mode = readFanMode(value1 & 0x03);\n data.fan_status = readFanStatus((value1 >>> 2) & 0x03);\n data.system_status = readSystemStatus((value1 >>> 4) & 0x01);\n\n // temperature_ctl_mode\n var temperature = 1.0 * ((value1 >>> 5) & 0x7ff) / 10 - 100;\n data.temperature = toFixed(temperature, 1);\n data.temperature_ctl_mode = readTemperatureCtlMode(value2 & 0x03);\n data.temperature_ctl_status = readTemperatureCtlStatus((value2 >>> 2) & 0x07);\n var temperature_target = 1.0 * ((value2 >>> 5) & 0x7ff) / 10 - 100;\n data.temperature_target = toFixed(temperature_target, 1);\n i += 8;\n\n decoded.history = [];\n decoded.history.push(data);\n }\n // DOWNLINK RESPONSE\n // TEMPERATURE MODE SUPPORT: The device will send a reply including wirings, supported mode and levels when it receives a wiring setting command.\n else if (channel_id === 0xFF && channel_type === 0xCB) {\n decoded.temperature_control_mode_enable = readTemperatureCtlModeEnable(input[i] & 0xff);\n decoded.temperature_control_status_enable = readTemperatureCtlStatusEnable(input[i + 1] & 0xff, input[i + 2] & 0xff);\n i += 3;\n }\n else if (channel_id === 0xFE) {\n result = handle_downlink_response(channel_type, input, i);\n decoded = Object.assign(decoded, result.data);\n i = result.offset;\n } else if (channel_id === 0xF8) {\n result = handle_downlink_response_ext(channel_type, input, i);\n decoded = Object.assign(decoded, result.data);\n i = result.offset;\n } else {\n break;\n }\n }\n output.telemetry = decoded;\n return output;\n}\n\nfunction readProtocolVersion(bytes) {\n var minor = (bytes & 0xf0) >> 4;\n var major = bytes & 0x0f;\n return \"v\" + major + \".\" + minor;\n}\n\nfunction readHardwareVersion(bytes) {\n var major = bytes[0] & 0xff;\n var minor = (bytes[1] & 0xff) >> 4;\n return \"v\" + major + \".\" + minor;\n}\n\nfunction readFirmwareVersion(bytes) {\n var major = bytes[0] & 0xff;\n var minor = bytes[1] & 0xff;\n return \"v\" + major + \".\" + minor;\n}\n\nfunction readSerialNumber(bytes) {\n return bytesToHex(bytes);\n}\n\nfunction readLoRaWANClass(type) {\n switch (type) {\n case 0x00:\n return \"ClassA\";\n case 0x01:\n return \"ClassB\";\n case 0x02:\n return \"ClassC\";\n case 0x03:\n return \"ClassCtoB\";\n default:\n return \"unknown\";\n }\n}\n\nfunction readTemperatureUnit(type) {\n switch (type) {\n case 0x00:\n return \"℃\";\n case 0x01:\n return \"℉\";\n default:\n return \"unknown\";\n }\n}\n\nfunction readTemperatureAlarm(type) {\n switch (type) {\n case 0x01:\n return \"emergency heating timeout alarm\";\n case 0x02:\n return \"auxiliary heating timeout alarm\";\n case 0x03:\n return \"persistent low temperature alarm\";\n case 0x04:\n return \"persistent low temperature alarm release\";\n case 0x05:\n return \"persistent high temperature alarm\";\n case 0x06:\n return \"persistent high temperature alarm release\";\n case 0x07:\n return \"freeze protection alarm\";\n case 0x08:\n return \"freeze protection alarm release\";\n case 0x09:\n return \"temperature threshold alarm\";\n case 0x0a:\n return \"temperature threshold alarm release\";\n default:\n }\n}\n\nfunction readException(type) {\n switch (type) {\n case 0x01:\n return \"collection failure\";\n case 0x02:\n return \"out of measuring range\";\n default:\n }\n}\n\nfunction readPlanEvent(type) {\n switch (type) {\n case 0x00:\n return \"not executed\";\n case 0x01:\n return \"wake\";\n case 0x02:\n return \"away\";\n case 0x03:\n return \"home\";\n case 0x04:\n return \"sleep\";\n default:\n }\n}\n\nfunction readPlanType(type) {\n switch (type) {\n case 0x00:\n return \"wake\";\n case 0x01:\n return \"away\";\n case 0x02:\n return \"home\";\n case 0x03:\n return \"sleep\";\n default:\n }\n}\n\nfunction readFanMode(type) {\n switch (type) {\n case 0x00:\n return \"auto\";\n case 0x01:\n return \"on\";\n case 0x02:\n return \"circulate\";\n default:\n }\n}\n\nfunction readFanStatus(type) {\n switch (type) {\n case 0x00:\n return \"off\";\n case 0x01:\n return \"high speed\";\n case 0x02:\n return \"low speed\";\n case 0x03:\n return \"on\";\n default:\n }\n}\n\nfunction readSystemStatus(type) {\n switch (type) {\n case 0x00:\n return \"off\";\n case 0x01:\n return \"on\";\n default:\n }\n}\n\nfunction readTemperatureCtlMode(type) {\n switch (type) {\n case 0x00:\n return \"heat\";\n case 0x01:\n return \"em heat\";\n case 0x02:\n return \"cool\";\n case 0x03:\n return \"auto\";\n default:\n }\n}\n\nfunction readTemperatureCtlStatus(type) {\n switch (type) {\n case 0x00:\n return \"standby\";\n case 0x01:\n return \"stage-1 heat\";\n case 0x02:\n return \"stage-2 heat\";\n case 0x03:\n return \"stage-3 heat\";\n case 0x04:\n return \"stage-4 heat\";\n case 0x05:\n return \"em heat\";\n case 0x06:\n return \"stage-1 cool\";\n case 0x07:\n return \"stage-2 cool\";\n default:\n }\n}\n\nfunction readWires(wireArrays) {\n var wire = [];\n for (var i = 0; i < 3; i++) {\n var wireArray = wireArrays[i];\n var bitMaskWiringSettings = bitMaskWiringSettingss[i];\n foreach(bitMaskWiringSetting : bitMaskWiringSettings.entrySet()) {\n var wireArrayValue = ((wireArray >>> bitMaskWiringSetting.value) & 0x03);\n if (bitMaskWiringSetting.key != \"ob_mode\" && wireArrayValue != 0) {\n if (i === 1 && bitMaskWiringSetting.key === \"w2_aux\") {\n switch (wireArrayValue) {\n case 1:\n wire.push(\"w2\");\n break;\n case 2:\n wire.push(\"aux\");\n break;\n default:\n }\n } else if (i === 2 && bitMaskWiringSetting.key === \"y2_gl\") {\n switch (wireArrayValue) {\n case 1:\n wire.push(\"y2\");\n break;\n case 2:\n wire.push(\"gl\");\n break;\n default:\n }\n } else {\n wire.push(bitMaskWiringSetting.key);\n }\n }\n }\n }\n return wire;\n}\n\nfunction readWiresRelay(status) {\n var relay = {};\n var statusBinaryArray = parseByteToBinaryArray(status, 7, false);\n relay.y1 = statusBinaryArray[0];\n relay.y2_gl = statusBinaryArray[1];\n relay.w1 = statusBinaryArray[2];\n relay.w2_aux = statusBinaryArray[3];\n relay.e = statusBinaryArray[4];\n relay.g = statusBinaryArray[5];\n relay.ob = statusBinaryArray[6];\n return relay;\n}\n\nfunction readObMode(type) {\n switch (type) {\n case 0x00: // 00\n return \"cool\";\n case 0x01: // 01\n return \"heat\";\n case 0x03: // 11\n return \"Keep Original Setting\";\n default:\n }\n}\n\nfunction readTemperatureCtlModeEnable(type) {\n var enable = [];\n for (var ind = 3; ind > -1; ind --) {\n if (((type >>> ind) & 0x01) === 1) {\n enable.push(readTemperatureCtlMode(ind));\n }\n }\n return enable;\n}\n\nfunction readTemperatureCtlStatusEnable(heat_mode, cool_mode) {\n var enable = [];\n for (var ind = 3; ind > -1; ind --) {\n if (((heat_mode >>> ind) & 0x01) === 1) {\n enable.push(readTemperatureCtlStatus(ind+1));\n }\n }\n if (((heat_mode >>> 4) & 0x01) === 1) {\n enable.push(\"aux heat\");\n }\n\n // bit0: stage-1 cool, bit1: stage-2 cool\n if (((cool_mode >>> 0) & 0x03) === 1) {\n enable.push(\"stage-1 cool\");\n }\n if (((cool_mode >>> 1) & 0x03) === 1) {\n enable.push(\"stage-2 cool\");\n }\n return enable;\n}\n\nfunction readWeekRecycleSettings(type) {\n // bit1: \"mon\", bit2: \"tues\", bit3: \"wed\", bit4: \"thur\", bit5: \"fri\", bit6: \"sat\", bit7: \"sun\"\n var week_enable = [];\n if (((type >>> 1) & 0x01) === 1){\n week_enable.push(\"Mon.\");\n }\n if (((type >>> 2) & 0x01) === 1){\n week_enable.push(\"Tues.\");\n }\n if (((type >>> 3) & 0x01) === 1){\n week_enable.push(\"Wed.\");\n }\n if (((type >>> 4) & 0x01) === 1){\n week_enable.push(\"Thur.\");\n }\n if (((type >>> 5) & 0x01) === 1){\n week_enable.push(\"Fri.\");\n }\n if (((type >>> 6) & 0x01) === 1){\n week_enable.push(\"Sat.\");\n }\n if (((type >>> 7) & 0x01) === 1){\n week_enable.push(\"Sun.\");\n }\n return week_enable;\n}\n\nfunction handle_downlink_response(channel_type, bytes, offset) {\n var decoded = {};\n\n switch (channel_type) {\n case 0x02: // collection_interval\n decoded.collection_interval = parseBytesToInt(bytes, offset, 2, false);\n offset += 2;\n break;\n case 0x03:\n decoded.outside_temperature = parseBytesIntToFloat(bytes, offset, 2, false) / 10;\n offset += 2;\n break;\n case 0x06: // temperature_threshold_config\n var ctl = bytes[offset] & 0xff;\n var condition = ctl & 0x07;\n var alarm_type = (ctl >>> 3) & 0x07;\n var data = { condition: condition, alarm_type: alarm_type };\n if (condition === 1 || condition === 3 || condition === 4) {\n data.min = parseBytesIntToFloat(bytes, offset + 1, 2, false) / 10;\n }\n if (condition === 2 || condition === 3 || condition === 4) {\n data.max = parseBytesIntToFloat(bytes, offset + 3, 2, false) / 10;\n }\n data.lock_time = parseBytesToInt(bytes, offset + 5, 2, false);\n data.continue_time = parseBytesToInt(bytes, offset + 7, 2, false);\n offset += 9;\n\n if (decoded.temperature_threshold_config === null) {\n decoded.temperature_threshold_config = [];\n }\n decoded.temperature_threshold_config.push(data);\n break;\n case 0x10:\n // TODO reboot\n break;\n case 0x25:\n var masked = bytes[offset] & 0xff;\n var status = bytes[offset + 1] & 0xff;\n\n if (decoded.child_lock_config === null){\n decoded.child_lock_config = {};\n }\n if (((masked >> 0) & 0x01) === 1) {\n decoded.child_lock_config.power_button = (status >> 0) & 0x01;\n }\n if (((masked >> 1) & 0x01) === 1) {\n decoded.child_lock_config.up_button = (status >> 1) & 0x01;\n }\n if (((masked >> 2) & 0x01) === 1) {\n decoded.child_lock_config.down_button = (status >> 2) & 0x01;\n }\n if (((masked >> 3) & 0x01) === 1) {\n decoded.child_lock_config.fan_button = (status >> 3) & 0x01;\n }\n if (((masked >> 4) & 0x01) === 1) {\n decoded.child_lock_config.mode_button = (status >> 4) & 0x01;\n }\n if(((masked >> 5) & 0x01) === 1) {\n decoded.child_lock_config.reset_button = (status >> 5) & 0x01;\n }\n offset += 2;\n break;\n case 0x28:\n // TODO\n /**\n * 00, the device will reply schedule contents and times of all plans with the same format as downlink commands\n * 01, the device will return a periodic packet\n */\n break;\n case 0x68: // Data Storage\n // TODO\n /**\n * 00: disable, 01: enable\n */\n break;\n case 0x69: // Data Retransmission\n // TODO\n /**\n * 00: disable, 01: enable\n */\n break;\n case 0x6A: // Data Retransmission Interval\n // TODO\n /**\n * 00: disable, 01: enable\n */\n break;\n case 0x82:\n decoded.multicast_group_config = {};\n var value = bytes[offset] & 0xff;\n var mask = value >>> 4;\n var enabled = value & 0x0f;\n if (((mask >> 0) & 0x01) === 1) {\n decoded.multicast_group_config.group1_enable = enabled & 0x01;\n }\n if (((mask >> 1) & 0x01) === 1) {\n decoded.multicast_group_config.group2_enable = (enabled >> 1) & 0x01;\n }\n if (((mask >> 2) & 0x01) === 1) {\n decoded.multicast_group_config.group3_enable = (enabled >> 2) & 0x01;\n }\n if (((mask >> 3) & 0x01) === 1) {\n decoded.multicast_group_config.group4_enable = (enabled >> 3) & 0x01;\n }\n offset += 1;\n break;\n case 0x83:\n var config = {};\n config.id = (bytes[offset] & 0xff) + 1;\n config.enable = bytes[offset + 1] & 0xff;\n if (config.enable === 1) {\n config.d2d_cmd = readD2DCommand(bytes.slice(offset + 2, offset + 4));\n config.action_type = (bytes[offset + 4] >>> 4) & 0x0f;\n config.action = bytes[offset + 4] & 0x0f;\n }\n offset += 5;\n if (ecoded.d2d_slave_config == null) {\n decoded.d2d_slave_config = [];\n }\n decoded.d2d_slave_config.push(config);\n break;\n case 0x8E: // report_interval, ignore the first byte\n decoded.report_interval = parseBytesToInt(bytes, offset + 1, 2, false);\n offset += 3;\n break;\n case 0x96:\n var config = {};\n config.mode = bytes[offset] & 0xff;\n config.enable = bytes[offset + 1] & 0xff;\n if (config.enable === 1) {\n config.uplink_enable = bytes[offset + 2] & 0xff;\n config.d2d_cmd = readD2DCommand(bytes.slice(offset + 3, offset + 5));\n config.time_enable = bytes[offset + 7] & 0xff;\n if (config.time_enable === 1) {\n config.time = parseBytesToInt(bytes, offset + 5, 2, false);\n }\n }\n offset += 8;\n if (decoded.d2d_master_config === null) {\n decoded.d2d_master_config = [];\n }\n decoded.d2d_master_config.push(config);\n break;\n case 0x4a: // sync_time\n decoded.sync_time = 1;\n offset += 1;\n break;\n case 0xAB:\n decoded.temperature_calibration = {};\n decoded.temperature_calibration.enable = bytes[offset] & 0xff;\n if (decoded.temperature_calibration.enable === 1) {\n decoded.temperature_calibration.temperature = parseBytesIntToFloat(bytes, offset + 1, 2, false) / 10;\n }\n offset += 3;\n break;\n case 0xB0:\n if (decoded.freeze_protection_config === null) {\n decoded.freeze_protection_config = {};\n }\n decoded.freeze_protection_config.enable = bytes[offset] & 0xff;\n if (decoded.freeze_protection_config.enable === 1) {\n decoded.freeze_protection_config.temperature = parseBytesIntToFloat(bytes, offset + 1, 2, false) / 10;\n }\n offset += 3;\n break;\n case 0xB5: // ob_mode\n decoded.ob_mode = bytes[offset] & 0xff;\n offset += 1;\n break;\n case 0xB6:\n decoded.fan_mode = bytes[offset] & 0xff;\n offset += 1;\n break;\n case 0xB7:\n decoded.temperature_control_mode = bytes[offset] & 0xff;\n var t = bytes[offset + 1] & 0xff;\n decoded.temperature_target = t & 0x7f;\n decoded.temperature_unit = (t >>> 7) & 0x01;\n offset += 2;\n break;\n case 0xB8: // temperature_tolerance\n decoded.temperature_tolerance = {};\n decoded.temperature_tolerance.temperature_error = parseBytesIntToFloat(bytes, offset, 1) / 10;\n decoded.temperature_tolerance.auto_control_temperature_error = parseBytesIntToFloat(bytes, offset + 1, 1) / 10;\n offset += 2;\n break;\n case 0xB9:\n decoded.temperature_level_up_condition = {};\n decoded.temperature_level_up_condition.type = bytes[offset] & 0xff;\n decoded.temperature_level_up_condition.time = bytes[offset + 1] & 0xff;\n decoded.temperature_level_up_condition.temperature_error = parseBytesIntToFloat(bytes, offset + 2, 2, false) / 10;\n offset += 4;\n break;\n case 0xBA:\n decoded.dst_config = {};\n decoded.dst_config.enable = bytes[offset] & 0xff;\n if (decoded.dst_config.enable === 1) {\n decoded.dst_config.offset = bytes[offset + 1] & 0xff;\n decoded.dst_config.start_time = {};\n decoded.dst_config.start_time.month = bytes[offset + 3] & 0xff;\n var start_day = bytes[offset + 3] & 0xff;\n decoded.dst_config.start_time.week = (start_day >>> 4) & 0x0f;\n decoded.dst_config.start_time.weekday = start_day & 0x0f;\n var start_time = parseBytesToInt(bytes, offset + 4, 2, false);\n decoded.dst_config.start_time.time = Math.floor(start_time / 60) + \":\" + (\"0\" + (start_time % 60)).slice(-2);\n decoded.dst_config.end_time = {};\n decoded.dst_config.end_time.month = bytes[offset + 6] & 0xff;\n var end_day = bytes[offset + 7] & 0xff;\n decoded.dst_config.end_time.week = (end_day >>> 4) & 0x0f;\n decoded.dst_config.end_time.weekday = end_day & 0x0f;\n var end_time = parseBytesToInt(bytes, offset + 8, 2, false);\n decoded.dst_config.end_time.time = Math.floor(end_time / 60) + \":\" + (\"0\" + (end_time % 60)).slice(-2);\n }\n offset += 10;\n break;\n case 0xBD: // timezone\n decoded.timezone = readInt16LE(bytes.slice(offset, offset + 2)) / 60;\n offset += 2;\n break;\n case 0xC1:\n decoded.card_config = {};\n decoded.card_config.enable = bytes[offset] & 0xff;\n if (decoded.card_config.enable === 1) {\n decoded.card_config.action_type = bytes[offset + 1] & 0xff;\n if (decoded.card_config.action_type === 1) {\n var action = bytes[offset + 3] & 0xff;\n decoded.card_config.in_plan_type = (action >>> 4) & 0x0f;\n decoded.card_config.out_plan_type = action & 0x0f;\n }\n decoded.card_config.invert = bytes[offset + 3] & 0xff;\n }\n offset += 4;\n break;\n case 0xC2:\n decoded.plan_mode = bytes[offset] & 0xff;\n offset += 1;\n break;\n case 0xC4:\n if (decoded.outside_temperature_control_config === null) {\n decoded.outside_temperature_control_config = {};\n }\n decoded.outside_temperature_control_config.enable = bytes[offset] & 0xff;\n if (decoded.outside_temperature_control_config.enable === 1) {\n decoded.outside_temperature_control_config.timeout = bytes[offset + 1] & 0xff;\n }\n offset += 2;\n break;\n case 0xC5:\n decoded.temperature_control_enable = bytes[offset] & 0xff;\n offset += 1;\n break;\n case 0xC7:\n var data = bytes[offset] & 0xff;\n offset += 1;\n\n var mask = data >>> 4;\n var status = data & 0x0f;\n\n if (((mask >> 0) & 0x01) === 1) {\n decoded.d2d_master_enable = status & 0x01;\n }\n if (((mask >> 1) & 0x01) === 1) {\n decoded.d2d_slave_enable = (status >> 1) & 0x01;\n }\n break;\n case 0xC8:\n if (decoded.plan_config === null) {\n decoded.plan_config = {};\n }\n decoded.plan_config.type = bytes[offset] & 0xff;\n decoded.plan_config.temperature_control_mode = bytes[offset + 1] & 0xff;\n decoded.plan_config.fan_mode = bytes[offset + 3] & 0xff;\n var t = bytes[offset + 3] & 0xff;\n decoded.plan_config.temperature_target = t & 0x7f;\n decoded.temperature_unit = (t >>> 7) & 0x01;\n decoded.plan_config.temperature_error = (bytes[offset + 4] & 0xff) / 10;\n offset += 5;\n break;\n case 0xC9:\n var schedule = {};\n schedule.type = bytes[offset];\n schedule.id = bytes[offset + 1] + 1;\n schedule.enable = bytes[offset + 2];\n schedule.week_recycle = readWeekRecycleSettings(bytes[offset + 3]);\n var time_mins = parseBytesToInt(bytes, offset + 4, 2, false);\n schedule.time = Math.floor(time_mins / 60) + \":\" + (\"0\" + (time_mins % 60)).slice(-2);\n offset += 6;\n if (decoded.plan_schedule === null) {\n decoded.plan_schedule = [];\n }\n decoded.plan_schedule.push(schedule);\n break;\n case 0xf6:\n decoded.control_permissions = bytes[offset] & 0xff;\n offset += 1;\n break;\n case 0xf7:\n var wire_relay_bit_offset = { y1: 0, y2_gl: 1, w1: 2, w2_aux: 3, e: 4, g: 5, ob: 6 };\n var mask = parseBytesToInt(bytes, offset, 2, false);\n var status = parseBytesToInt(bytes, offset + 2, 2, false);\n offset += 4;\n\n decoded.wires_relay_config = {};\n foreach (key : wire_relay_bit_offset) {\n if (((mask >>> wire_relay_bit_offset[key]) & 0x01) === 1) {\n decoded.wires_relay_config[key] = (status >>> wire_relay_bit_offset[key]) & 0x01;\n }\n }\n break;\n case 0xf8: // offline_control_mode\n decoded.offline_control_mode = bytes[offset] & 0xff;\n break;\n case 0xf9: // humidity_calibration\n decoded.humidity_calibration = {};\n decoded.humidity_calibration.enable = bytes[offset] & 0xff;\n if (decoded.humidity_calibration.enable === 1) {\n decoded.humidity_calibration.humidity = readInt16LE(bytes.slice(offset + 1, offset + 3)) / 10;\n }\n offset += 3;\n break;\n case 0xfa:\n decoded.temperature_control_mode = bytes[offset] & 0xff;\n decoded.temperature_target = readInt16LE(bytes.slice(offset + 1, offset + 3)) / 10;\n offset += 3;\n break;\n case 0xfb:\n decoded.temperature_control_mode = bytes[offset] & 0xff;\n offset += 1;\n break;\n default:\n raiseError(\"unknown downlink response\");\n }\n\n return { data: decoded, offset: offset };\n}\n\nfunction handle_downlink_response_ext(channel_type, bytes, offset) {\n var decoded = {};\n\n switch (channel_type) {\n case 0x05:\n var fan_delay_control_result = bytes[offset + 3] & 0xff;\n if (fan_delay_control_result === 0) {\n decoded.fan_delay_enable = bytes[offset] & 0xff;\n decoded.fan_delay_time = bytes[offset + 1] & 0xff;\n } else {\n raiseError(\"fan delay control failed\");\n }\n offset += 3;\n break;\n case 0x06:\n var fan_execute_result = bytes[offset + 1] & 0xff;\n if (fan_execute_result === 0) {\n decoded.fan_execute_time = bytes[offset] & 0xff;\n } else {\n raiseError(\"fan execute control failed\");\n }\n\n offset += 2;\n break;\n case 0x07:\n var dehumidify_control_result = bytes[offset + 3] & 0xff;\n if (dehumidify_control_result === 0) {\n decoded.fan_dehumidify = {};\n decoded.fan_dehumidify.enable = bytes[offset] & 0xff;\n if (decoded.fan_dehumidify.enable === 1) {\n decoded.fan_dehumidify.execute_time = bytes[offset + 1] & 0xff;\n }\n }\n offset += 3;\n break;\n case 0x09:\n var humidity_range_result = bytes[offset + 3] & 0xff;\n if (humidity_range_result === 0) {\n decoded.humidity_range = {};\n decoded.humidity_range.min = bytes[offset] & 0xff;\n decoded.humidity_range.max = bytes[offset + 1] & 0xff;\n } else {\n raiseError(\"humidity range control failed\");\n }\n offset += 3;\n break;\n case 0x0a:\n var dehumidify_result = bytes[offset + 3] & 0xff;\n if (dehumidify_result === 0) {\n decoded.temperature_dehumidify = {};\n decoded.temperature_dehumidify.enable = bytes[offset] & 0xff;\n if (decoded.temperature_dehumidify.enable === 1) {\n var value = bytes[offset + 1] & 0xff;\n if (value != 0xff) {\n decoded.temperature_dehumidify.temperature_tolerance = readUInt8(bytes[offset + 1]) / 10;\n }\n }\n } else {\n raiseError(\"dehumidify control failed\");\n }\n offset += 3;\n break;\n default:\n raiseError(\"unknown downlink response\");\n }\n\n return { data: decoded, offset: offset };\n}\n\nfunction readD2DCommand(bytes) {\n var bytesInt = parseBytesToInt(bytes, 0 , 2);\n return intToHex(bytesInt, false, false, 2)\n}\n\n// --- attributes and telemetry objects ---\nvar telemetry = {};\nvar attributes = {};\n// --- attributes and telemetry objects ---\n\n// --- Timestamp parsing\nvar dateString = data.time;\nvar timestamp = -1;\nif (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n}\n// If we cannot parse timestamp - we will use the current timestamp\nif (timestamp == -1) {\n timestamp = Date.now();\n}\n// --- Timestamp parsing\n\n// You can add some keys manually to attributes or telemetry\nattributes.deduplicationId = data.deduplicationId;\n\n// You can exclude some keys from the result\nvar excludeFromAttributesList = [\"deviceName\", \"rxInfo\", \"confirmed\", \"data\", \"deduplicationId\",\"time\", \"adr\", \"dr\", \"fCnt\"];\nvar excludeFromTelemetryList = [\"data\", \"deviceInfo\", \"txInfo\", \"devAddr\", \"adr\", \"time\", \"fPort\", \"region_common_name\", \"region_config_id\", \"deduplicationId\"];\n\n// Message parsing\n// To avoid paths in the decoded objects we passing false value to function as \"pathInKey\" argument.\n// Warning: pathInKey can cause already found fields to be overwritten with the last value found.\n\nvar telemetryData = toFlatMap(data, excludeFromTelemetryList, false);\nvar attributesData = toFlatMap(data, excludeFromAttributesList, false);\n\nvar uplinkDataList = [];\n\n// Passing incoming bytes to decodePayload function, to get custom decoding\nvar customDecoding = decodePayload(base64ToBytes(data.data));\n\n// Collecting data to result\nif (customDecoding.?telemetry.size() > 0) {\n telemetry.putAll(customDecoding.telemetry);\n}\n\nif (customDecoding.?attributes.size() > 0) {\n attributes.putAll(customDecoding.attributes);\n}\n\ntelemetry.putAll(telemetryData);\nattributes.putAll(attributesData);\n\nvar result = {\n deviceName: deviceName,\n deviceType: deviceType,\n groupName: groupName,\n attributes: attributes,\n telemetry: {\n ts: timestamp,\n values: telemetry\n }\n};\n\nreturn result;", + "tbelDecoder": "var data = decodeToJson(payload);\nvar deviceName = data.deviceInfo.deviceName;\nvar deviceType = data.deviceInfo.deviceProfileName;\nvar groupName = '9-in-1 IAQ Sensor';\n// var customerName = 'Customer A';\n// use assetName and assetType instead of deviceName and deviceType\n// to automatically create assets instead of devices.\n// var assetName = 'Asset A';\n// var assetType = 'building';\n\n// If you want to parse incoming data somehow, you can add your code to this function.\n// input: bytes\n// expected output:\n// {\n// \"attributes\": {\"attributeKey\": \"attributeValue\"},\n// \"telemetry\": {\"telemetryKey\": \"telemetryValue\"}\n// }\n//\n// In the example - bytes will be saved as HEX string and also parsed as light level, battery level and PIR sensor value.\n//\n\n// \"G\" Uplink; \"G/GH\" Downlink === \"g\"\n// \"CL&CN\" Downlink === \"cl_cn\"\nvar bitMaskWiringSettingss =[\n {\"y1\": 0, \"g\": 2, \"ob\": 4, \"w1\": 6},\n {\"e\": 0, \"cl_cn\": 2, \"pek\": 4, \"w2_aux\": 6},\n {\"y2_gl\": 0, \"ob_mode\": 2}\n];\n\nfunction decodePayload(inputArray) {\n var output = { attributes:{}, telemetry: {} };\n // --- Decoding code --- //\n var input = bytesToExecutionArrayList(inputArray);\n output.telemetry.HEX_bytes = bytesToHex(input);\n\n var decoded = {};\n decoded.hexString = bytesToHex(input);\n\n for (var i = 0; i < input.length - 2; ) {\n var channel_id = input[i++] & 0xff;\n var channel_type = input[i++] & 0xff;\n\n // // IPSO VERSION\n if (channel_id === 0xFF && channel_type === 0x01) {\n decoded.ipso_version = readProtocolVersion(input[i]);\n i += 1;\n }\n // HARDWARE VERSION\n else if (channel_id === 0xFF && channel_type === 0x09) {\n decoded.hardware_version = readHardwareVersion(input.slice(i, i + 2));\n i += 2;\n }\n // FIRMWARE VERSION\n else if (channel_id === 0xFF && channel_type === 0x0A) {\n decoded.firmware_version = readFirmwareVersion(input.slice(i, i + 2));\n i += 2;\n }\n // DEVICE STATUS\n else if (channel_id === 0xFF && channel_type === 0x0B) {\n decoded.device_status = \"on\";\n i += 1;\n }\n // LORAWAN CLASS TYPE\n else if (channel_id === 0xFF && channel_type === 0x0F) {\n decoded.lorawan_class = readLoRaWANClass(input[i] & 0xff);\n i += 1;\n }\n // PRODUCT SERIAL NUMBER\n else if (channel_id === 0xFF && channel_type === 0x16) {\n decoded.sn = readSerialNumber(input.slice(i, i+ 8));\n i += 8;\n }\n // TSL VERSION\n else if (channel_id === 0xFF && channel_type === 0xFF) {\n decoded.tsl_version = readFirmwareVersion(input.slice(i, i + 2));\n i += 2;\n }\n // TEMPERATURE\n else if (channel_id === 0x03 && channel_type === 0x67) {\n decoded.temperature = parseBytesIntToFloat(input, i, 2, false) / 10;\n i += 2;\n }\n // TEMPERATURE TARGET\n else if (channel_id === 0x04 && channel_type === 0x67) {\n decoded.temperature_target = parseBytesIntToFloat(input, i, 2, false) / 10;\n i += 2;\n }\n // TEMPERATURE CONTROL\n else if (channel_id === 0x05 && channel_type === 0xE7) {\n var temperature_control = input[i];\n decoded.temperature_control_mode = readTemperatureCtlMode(temperature_control & 0x03);\n decoded.temperature_control_status = readTemperatureCtlStatus((temperature_control >>> 4) & 0x0f);\n i += 1;\n }\n // FAN CONTROL\n else if (channel_id === 0x06 && channel_type === 0xE8) {\n var valueFanControl = input[i];\n decoded.fan_mode = readFanMode(valueFanControl & 0x03);\n decoded.fan_status = readFanStatus((valueFanControl >>> 2) & 0x03);\n i += 1;\n }\n // PLAN EVENT\n else if (channel_id === 0x07 && channel_type === 0xBC) {\n decoded.plan_event = readPlanEvent(input[i] & 0x0f);\n i += 1;\n }\n // SYSTEM STATUS\n else if (channel_id === 0x08 && channel_type === 0x8E) {\n decoded.system_status = readSystemStatus(input[i] & 0xff);\n i += 1;\n }\n // HUMIDITY\n else if (channel_id === 0x09 && channel_type === 0x68) {\n decoded.humidity = parseBytesIntToFloat(input, i, 1) / 2;\n i += 1;\n }\n // RELAY STATUS\n else if (channel_id === 0x0A && channel_type === 0x6E) {\n decoded.wires_relay = readWiresRelay(input[i] & 0xff);\n i += 1;\n }\n // PLAN\n else if (channel_id === 0xFF && channel_type === 0xC9) {\n var schedule = {};\n schedule.type = readPlanType(input[i]);\n schedule.index = (input[i + 1] & 0xff) + 1;\n schedule.plan_enable = input[i + 2] === 0 ? \"disable\" : \"enable\";\n schedule.week_recycle = readWeekRecycleSettings(input[i + 3] & 0xff);\n var time_mins = parseBytesToInt(input, i + 4, 2, false);\n schedule.time = String.format(\"%02d:%02d\", (int) (time_mins / 60), time_mins % 60);\n i += 6;\n\n if (decoded.plan_schedule === null) {\n decoded.plan_schedule = [];\n }\n schedule.i = i ;\n decoded.plan_schedule.push(schedule);\n }\n // PLAN SETTINGS\n else if (channel_id === 0xFF && channel_type === 0xC8) {\n var plan_setting = {};\n plan_setting.type = readPlanType(input[i] & 0xff);\n plan_setting.temperature_ctl_mode = readTemperatureCtlMode(input[i + 1] & 0xff);\n plan_setting.fan_mode = readFanMode(input[i + 2] & 0xff);\n plan_setting.temperature_target = input[i + 3] & 0x7f;\n plan_setting.temperature_unit = readTemperatureUnit(input[i + 3] >>> 7);\n plan_setting.temperature_error = parseBytesIntToFloat(input, i + 4, 1, false) / 10;\n i += 5;\n\n if (decoded.plan_settings === null) {\n decoded.plan_settings = [];\n }\n decoded.plan_settings.push(plan_setting);\n }\n // WIRES\n else if (channel_id === 0xFF && channel_type === 0xCA) {\n decoded.wires = readWires([input[i] & 0xff, input[i + 1] & 0xff, input[i + 2] & 0xff]);\n decoded.ob_mode = readObMode((input[i + 2] >>> 2) & 0x03);\n i += 3;\n }\n // CONTROL PERMISSIONS\n else if (channel_id === 0xFF && channel_type === 0xF6) {\n decoded.control_permissions = input[i] === 1 ? \"remote control\" : \"thermostat\";\n i += 1;\n }\n // TEMPERATURE ALARM\n else if (channel_id === 0x83 && channel_type === 0x67) {\n decoded.temperature = parseBytesToInt(input, i, 2, false) / 10;\n decoded.temperature_alarm = readTemperatureAlarm(input[i + 2] & 0xff);\n i += 3;\n }\n // TEMPERATURE EXCEPTION\n else if (channel_id === 0xB3 && channel_type === 0x67) {\n decoded.temperature_exception = readException(input[i]);\n i += 1;\n }\n // HUMIDITY EXCEPTION\n else if (channel_id === 0xB9 && channel_type === 0x68) {\n decoded.humidity_exception = readException(input[i] & 0xff);\n i += 1;\n }\n // HISTORICAL DATA\n else if (channel_id === 0x20 && channel_type === 0xCE) {\n var timestamp = parseBytesToInt(input, i, 4, false);\n var value1 = parseBytesToInt(input, i+4, 2, false);\n var value2 = parseBytesToInt(input, i+6, 2, false);\n var data = {};\n data.timestamp = timestamp;\n\n // fan_mode\n data.fan_mode = readFanMode(value1 & 0x03);\n data.fan_status = readFanStatus((value1 >>> 2) & 0x03);\n data.system_status = readSystemStatus((value1 >>> 4) & 0x01);\n\n // temperature_ctl_mode\n var temperature = 1.0 * ((value1 >>> 5) & 0x7ff) / 10 - 100;\n data.temperature = toFixed(temperature, 1);\n data.temperature_ctl_mode = readTemperatureCtlMode(value2 & 0x03);\n data.temperature_ctl_status = readTemperatureCtlStatus((value2 >>> 2) & 0x07);\n var temperature_target = 1.0 * ((value2 >>> 5) & 0x7ff) / 10 - 100;\n data.temperature_target = toFixed(temperature_target, 1);\n i += 8;\n\n decoded.history = [];\n decoded.history.push(data);\n }\n // DOWNLINK RESPONSE\n // TEMPERATURE MODE SUPPORT: The device will send a reply including wirings, supported mode and levels when it receives a wiring setting command.\n else if (channel_id === 0xFF && channel_type === 0xCB) {\n decoded.temperature_control_mode_enable = readTemperatureCtlModeEnable(input[i] & 0xff);\n decoded.temperature_control_status_enable = readTemperatureCtlStatusEnable(input[i + 1] & 0xff, input[i + 2] & 0xff);\n i += 3;\n }\n else if (channel_id === 0xFE) {\n result = handle_downlink_response(channel_type, input, i);\n decoded = Object.assign(decoded, result.data);\n i = result.offset;\n } else if (channel_id === 0xF8) {\n result = handle_downlink_response_ext(channel_type, input, i);\n decoded = Object.assign(decoded, result.data);\n i = result.offset;\n } else {\n break;\n }\n }\n output.telemetry = decoded;\n return output;\n}\n\nfunction readProtocolVersion(bytes) {\n var minor = (bytes & 0xf0) >> 4;\n var major = bytes & 0x0f;\n return \"v\" + major + \".\" + minor;\n}\n\nfunction readHardwareVersion(bytes) {\n var major = bytes[0] & 0xff;\n var minor = (bytes[1] & 0xff) >> 4;\n return \"v\" + major + \".\" + minor;\n}\n\nfunction readFirmwareVersion(bytes) {\n var major = bytes[0] & 0xff;\n var minor = bytes[1] & 0xff;\n return \"v\" + major + \".\" + minor;\n}\n\nfunction readSerialNumber(bytes) {\n return bytesToHex(bytes);\n}\n\nfunction readLoRaWANClass(type) {\n switch (type) {\n case 0x00:\n return \"ClassA\";\n case 0x01:\n return \"ClassB\";\n case 0x02:\n return \"ClassC\";\n case 0x03:\n return \"ClassCtoB\";\n default:\n return \"unknown\";\n }\n}\n\nfunction readTemperatureUnit(type) {\n switch (type) {\n case 0x00:\n return \"℃\";\n case 0x01:\n return \"℉\";\n default:\n return \"unknown\";\n }\n}\n\nfunction readTemperatureAlarm(type) {\n switch (type) {\n case 0x01:\n return \"emergency heating timeout alarm\";\n case 0x02:\n return \"auxiliary heating timeout alarm\";\n case 0x03:\n return \"persistent low temperature alarm\";\n case 0x04:\n return \"persistent low temperature alarm release\";\n case 0x05:\n return \"persistent high temperature alarm\";\n case 0x06:\n return \"persistent high temperature alarm release\";\n case 0x07:\n return \"freeze protection alarm\";\n case 0x08:\n return \"freeze protection alarm release\";\n case 0x09:\n return \"temperature threshold alarm\";\n case 0x0a:\n return \"temperature threshold alarm release\";\n default:\n }\n}\n\nfunction readException(type) {\n switch (type) {\n case 0x01:\n return \"collection failure\";\n case 0x02:\n return \"out of measuring range\";\n default:\n }\n}\n\nfunction readPlanEvent(type) {\n switch (type) {\n case 0x00:\n return \"not executed\";\n case 0x01:\n return \"wake\";\n case 0x02:\n return \"away\";\n case 0x03:\n return \"home\";\n case 0x04:\n return \"sleep\";\n default:\n }\n}\n\nfunction readPlanType(type) {\n switch (type) {\n case 0x00:\n return \"wake\";\n case 0x01:\n return \"away\";\n case 0x02:\n return \"home\";\n case 0x03:\n return \"sleep\";\n default:\n }\n}\n\nfunction readFanMode(type) {\n switch (type) {\n case 0x00:\n return \"auto\";\n case 0x01:\n return \"on\";\n case 0x02:\n return \"circulate\";\n default:\n }\n}\n\nfunction readFanStatus(type) {\n switch (type) {\n case 0x00:\n return \"standby\";\n case 0x01:\n return \"high speed\";\n case 0x02:\n return \"low speed\";\n case 0x03:\n return \"on\";\n default:\n }\n}\n\nfunction readSystemStatus(type) {\n switch (type) {\n case 0x00:\n return \"off\";\n case 0x01:\n return \"on\";\n default:\n }\n}\n\nfunction readTemperatureCtlMode(type) {\n switch (type) {\n case 0x00:\n return \"heat\";\n case 0x01:\n return \"em heat\";\n case 0x02:\n return \"cool\";\n case 0x03:\n return \"auto\";\n default:\n }\n}\n\nfunction readTemperatureCtlStatus(type) {\n switch (type) {\n case 0x00:\n return \"standby\";\n case 0x01:\n return \"stage-1 heat\";\n case 0x02:\n return \"stage-2 heat\";\n case 0x03:\n return \"stage-3 heat\";\n case 0x04:\n return \"stage-4 heat\";\n case 0x05:\n return \"em heat\";\n case 0x06:\n return \"stage-1 cool\";\n case 0x07:\n return \"stage-2 cool\";\n default:\n }\n}\n\nfunction readWires(wireArrays) {\n var wire = [];\n for (var i = 0; i < 3; i++) {\n var wireArray = wireArrays[i];\n var bitMaskWiringSettings = bitMaskWiringSettingss[i];\n foreach(bitMaskWiringSetting : bitMaskWiringSettings.entrySet()) {\n var wireArrayValue = ((wireArray >>> bitMaskWiringSetting.value) & 0x03);\n if (bitMaskWiringSetting.key != \"ob_mode\" && wireArrayValue != 0) {\n if (i === 1 && bitMaskWiringSetting.key === \"w2_aux\") {\n switch (wireArrayValue) {\n case 1:\n wire.push(\"w2\");\n break;\n case 2:\n wire.push(\"aux\");\n break;\n default:\n }\n } else if (i === 2 && bitMaskWiringSetting.key === \"y2_gl\") {\n switch (wireArrayValue) {\n case 1:\n wire.push(\"y2\");\n break;\n case 2:\n wire.push(\"gl\");\n break;\n default:\n }\n } else {\n wire.push(bitMaskWiringSetting.key);\n }\n }\n }\n }\n return wire;\n}\n\nfunction readWiresRelay(status) {\n var relay = {};\n var statusBinaryArray = parseByteToBinaryArray(status, 7, false);\n relay.y1 = statusBinaryArray[0];\n relay.y2_gl = statusBinaryArray[1];\n relay.w1 = statusBinaryArray[2];\n relay.w2_aux = statusBinaryArray[3];\n relay.e = statusBinaryArray[4];\n relay.g = statusBinaryArray[5];\n relay.ob = statusBinaryArray[6];\n return relay;\n}\n\nfunction readObMode(type) {\n switch (type) {\n case 0x00: // 00\n return \"cool\";\n case 0x01: // 01\n return \"heat\";\n case 0x03: // 11\n return \"Keep Original Setting\";\n default:\n }\n}\n\nfunction readTemperatureCtlModeEnable(type) {\n var enable = [];\n for (var ind = 3; ind > -1; ind --) {\n if (((type >>> ind) & 0x01) === 1) {\n enable.push(readTemperatureCtlMode(ind));\n }\n }\n return enable;\n}\n\nfunction readTemperatureCtlStatusEnable(heat_mode, cool_mode) {\n var enable = [];\n for (var ind = 3; ind > -1; ind --) {\n if (((heat_mode >>> ind) & 0x01) === 1) {\n enable.push(readTemperatureCtlStatus(ind+1));\n }\n }\n if (((heat_mode >>> 4) & 0x01) === 1) {\n enable.push(\"aux heat\");\n }\n\n // bit0: stage-1 cool, bit1: stage-2 cool\n if (((cool_mode >>> 0) & 0x03) === 1) {\n enable.push(\"stage-1 cool\");\n }\n if (((cool_mode >>> 1) & 0x03) === 1) {\n enable.push(\"stage-2 cool\");\n }\n return enable;\n}\n\nfunction readWeekRecycleSettings(type) {\n // bit1: \"mon\", bit2: \"tues\", bit3: \"wed\", bit4: \"thur\", bit5: \"fri\", bit6: \"sat\", bit7: \"sun\"\n var week_enable = [];\n if (((type >>> 1) & 0x01) === 1){\n week_enable.push(\"Mon.\");\n }\n if (((type >>> 2) & 0x01) === 1){\n week_enable.push(\"Tues.\");\n }\n if (((type >>> 3) & 0x01) === 1){\n week_enable.push(\"Wed.\");\n }\n if (((type >>> 4) & 0x01) === 1){\n week_enable.push(\"Thur.\");\n }\n if (((type >>> 5) & 0x01) === 1){\n week_enable.push(\"Fri.\");\n }\n if (((type >>> 6) & 0x01) === 1){\n week_enable.push(\"Sat.\");\n }\n if (((type >>> 7) & 0x01) === 1){\n week_enable.push(\"Sun.\");\n }\n return week_enable;\n}\n\nfunction handle_downlink_response(channel_type, bytes, offset) {\n var decoded = {};\n\n switch (channel_type) {\n case 0x02: // collection_interval\n decoded.collection_interval = parseBytesToInt(bytes, offset, 2, false);\n offset += 2;\n break;\n case 0x03:\n decoded.outside_temperature = parseBytesIntToFloat(bytes, offset, 2, false) / 10;\n offset += 2;\n break;\n case 0x06: // temperature_threshold_config\n var ctl = bytes[offset] & 0xff;\n var condition = ctl & 0x07;\n var alarm_type = (ctl >>> 3) & 0x07;\n var data = { condition: condition, alarm_type: alarm_type };\n if (condition === 1 || condition === 3 || condition === 4) {\n data.min = parseBytesIntToFloat(bytes, offset + 1, 2, false) / 10;\n }\n if (condition === 2 || condition === 3 || condition === 4) {\n data.max = parseBytesIntToFloat(bytes, offset + 3, 2, false) / 10;\n }\n data.lock_time = parseBytesToInt(bytes, offset + 5, 2, false);\n data.continue_time = parseBytesToInt(bytes, offset + 7, 2, false);\n offset += 9;\n\n if (decoded.temperature_threshold_config === null) {\n decoded.temperature_threshold_config = [];\n }\n decoded.temperature_threshold_config.push(data);\n break;\n case 0x10:\n // TODO reboot\n break;\n case 0x25:\n var masked = bytes[offset] & 0xff;\n var status = bytes[offset + 1] & 0xff;\n\n if (decoded.child_lock_config === null){\n decoded.child_lock_config = {};\n }\n if (((masked >> 0) & 0x01) === 1) {\n decoded.child_lock_config.power_button = (status >> 0) & 0x01;\n }\n if (((masked >> 1) & 0x01) === 1) {\n decoded.child_lock_config.up_button = (status >> 1) & 0x01;\n }\n if (((masked >> 2) & 0x01) === 1) {\n decoded.child_lock_config.down_button = (status >> 2) & 0x01;\n }\n if (((masked >> 3) & 0x01) === 1) {\n decoded.child_lock_config.fan_button = (status >> 3) & 0x01;\n }\n if (((masked >> 4) & 0x01) === 1) {\n decoded.child_lock_config.mode_button = (status >> 4) & 0x01;\n }\n if(((masked >> 5) & 0x01) === 1) {\n decoded.child_lock_config.reset_button = (status >> 5) & 0x01;\n }\n offset += 2;\n break;\n case 0x28:\n // TODO\n /**\n * 00, the device will reply schedule contents and times of all plans with the same format as downlink commands\n * 01, the device will return a periodic packet\n */\n break;\n case 0x68: // Data Storage\n // TODO\n /**\n * 00: disable, 01: enable\n */\n break;\n case 0x69: // Data Retransmission\n // TODO\n /**\n * 00: disable, 01: enable\n */\n break;\n case 0x6A: // Data Retransmission Interval\n // TODO\n /**\n * 00: disable, 01: enable\n */\n break;\n case 0x82:\n decoded.multicast_group_config = {};\n var value = bytes[offset] & 0xff;\n var mask = value >>> 4;\n var enabled = value & 0x0f;\n if (((mask >> 0) & 0x01) === 1) {\n decoded.multicast_group_config.group1_enable = enabled & 0x01;\n }\n if (((mask >> 1) & 0x01) === 1) {\n decoded.multicast_group_config.group2_enable = (enabled >> 1) & 0x01;\n }\n if (((mask >> 2) & 0x01) === 1) {\n decoded.multicast_group_config.group3_enable = (enabled >> 2) & 0x01;\n }\n if (((mask >> 3) & 0x01) === 1) {\n decoded.multicast_group_config.group4_enable = (enabled >> 3) & 0x01;\n }\n offset += 1;\n break;\n case 0x83:\n var config = {};\n config.id = (bytes[offset] & 0xff) + 1;\n config.enable = bytes[offset + 1] & 0xff;\n if (config.enable === 1) {\n config.d2d_cmd = readD2DCommand(bytes.slice(offset + 2, offset + 4));\n config.action_type = (bytes[offset + 4] >>> 4) & 0x0f;\n config.action = bytes[offset + 4] & 0x0f;\n }\n offset += 5;\n if (ecoded.d2d_slave_config == null) {\n decoded.d2d_slave_config = [];\n }\n decoded.d2d_slave_config.push(config);\n break;\n case 0x8E: // report_interval, ignore the first byte\n decoded.report_interval = parseBytesToInt(bytes, offset + 1, 2, false);\n offset += 3;\n break;\n case 0x96:\n var config = {};\n config.mode = bytes[offset] & 0xff;\n config.enable = bytes[offset + 1] & 0xff;\n if (config.enable === 1) {\n config.uplink_enable = bytes[offset + 2] & 0xff;\n config.d2d_cmd = readD2DCommand(bytes.slice(offset + 3, offset + 5));\n config.time_enable = bytes[offset + 7] & 0xff;\n if (config.time_enable === 1) {\n config.time = parseBytesToInt(bytes, offset + 5, 2, false);\n }\n }\n offset += 8;\n if (decoded.d2d_master_config === null) {\n decoded.d2d_master_config = [];\n }\n decoded.d2d_master_config.push(config);\n break;\n case 0x4a: // sync_time\n decoded.sync_time = 1;\n offset += 1;\n break;\n case 0xAB:\n decoded.temperature_calibration = {};\n decoded.temperature_calibration.enable = bytes[offset] & 0xff;\n if (decoded.temperature_calibration.enable === 1) {\n decoded.temperature_calibration.temperature = parseBytesIntToFloat(bytes, offset + 1, 2, false) / 10;\n }\n offset += 3;\n break;\n case 0xB0:\n if (decoded.freeze_protection_config === null) {\n decoded.freeze_protection_config = {};\n }\n decoded.freeze_protection_config.enable = bytes[offset] & 0xff;\n if (decoded.freeze_protection_config.enable === 1) {\n decoded.freeze_protection_config.temperature = parseBytesIntToFloat(bytes, offset + 1, 2, false) / 10;\n }\n offset += 3;\n break;\n case 0xB5: // ob_mode\n decoded.ob_mode = bytes[offset] & 0xff;\n offset += 1;\n break;\n case 0xB6:\n decoded.fan_mode = bytes[offset] & 0xff;\n offset += 1;\n break;\n case 0xB7:\n decoded.temperature_control_mode = bytes[offset] & 0xff;\n var t = bytes[offset + 1] & 0xff;\n decoded.temperature_target = t & 0x7f;\n decoded.temperature_unit = (t >>> 7) & 0x01;\n offset += 2;\n break;\n case 0xB8: // temperature_tolerance\n decoded.temperature_tolerance = {};\n decoded.temperature_tolerance.temperature_error = parseBytesIntToFloat(bytes, offset, 1) / 10;\n decoded.temperature_tolerance.auto_control_temperature_error = parseBytesIntToFloat(bytes, offset + 1, 1) / 10;\n offset += 2;\n break;\n case 0xB9:\n decoded.temperature_level_up_condition = {};\n decoded.temperature_level_up_condition.type = bytes[offset] & 0xff;\n decoded.temperature_level_up_condition.time = bytes[offset + 1] & 0xff;\n decoded.temperature_level_up_condition.temperature_error = parseBytesIntToFloat(bytes, offset + 2, 2, false) / 10;\n offset += 4;\n break;\n case 0xBA:\n decoded.dst_config = {};\n decoded.dst_config.enable = bytes[offset] & 0xff;\n if (decoded.dst_config.enable === 1) {\n decoded.dst_config.offset = bytes[offset + 1] & 0xff;\n decoded.dst_config.start_time = {};\n decoded.dst_config.start_time.month = bytes[offset + 3] & 0xff;\n var start_day = bytes[offset + 3] & 0xff;\n decoded.dst_config.start_time.week = (start_day >>> 4) & 0x0f;\n decoded.dst_config.start_time.weekday = start_day & 0x0f;\n var start_time = parseBytesToInt(bytes, offset + 4, 2, false);\n decoded.dst_config.start_time.time = Math.floor(start_time / 60) + \":\" + (\"0\" + (start_time % 60)).slice(-2);\n decoded.dst_config.end_time = {};\n decoded.dst_config.end_time.month = bytes[offset + 6] & 0xff;\n var end_day = bytes[offset + 7] & 0xff;\n decoded.dst_config.end_time.week = (end_day >>> 4) & 0x0f;\n decoded.dst_config.end_time.weekday = end_day & 0x0f;\n var end_time = parseBytesToInt(bytes, offset + 8, 2, false);\n decoded.dst_config.end_time.time = Math.floor(end_time / 60) + \":\" + (\"0\" + (end_time % 60)).slice(-2);\n }\n offset += 10;\n break;\n case 0xBD: // timezone\n decoded.timezone = readInt16LE(bytes.slice(offset, offset + 2)) / 60;\n offset += 2;\n break;\n case 0xC1:\n decoded.card_config = {};\n decoded.card_config.enable = bytes[offset] & 0xff;\n if (decoded.card_config.enable === 1) {\n decoded.card_config.action_type = bytes[offset + 1] & 0xff;\n if (decoded.card_config.action_type === 1) {\n var action = bytes[offset + 3] & 0xff;\n decoded.card_config.in_plan_type = (action >>> 4) & 0x0f;\n decoded.card_config.out_plan_type = action & 0x0f;\n }\n decoded.card_config.invert = bytes[offset + 3] & 0xff;\n }\n offset += 4;\n break;\n case 0xC2:\n decoded.plan_mode = bytes[offset] & 0xff;\n offset += 1;\n break;\n case 0xC4:\n if (decoded.outside_temperature_control_config === null) {\n decoded.outside_temperature_control_config = {};\n }\n decoded.outside_temperature_control_config.enable = bytes[offset] & 0xff;\n if (decoded.outside_temperature_control_config.enable === 1) {\n decoded.outside_temperature_control_config.timeout = bytes[offset + 1] & 0xff;\n }\n offset += 2;\n break;\n case 0xC5:\n decoded.temperature_control_enable = bytes[offset] & 0xff;\n offset += 1;\n break;\n case 0xC7:\n var data = bytes[offset] & 0xff;\n offset += 1;\n\n var mask = data >>> 4;\n var status = data & 0x0f;\n\n if (((mask >> 0) & 0x01) === 1) {\n decoded.d2d_master_enable = status & 0x01;\n }\n if (((mask >> 1) & 0x01) === 1) {\n decoded.d2d_slave_enable = (status >> 1) & 0x01;\n }\n break;\n case 0xC8:\n if (decoded.plan_config === null) {\n decoded.plan_config = {};\n }\n decoded.plan_config.type = bytes[offset] & 0xff;\n decoded.plan_config.temperature_control_mode = bytes[offset + 1] & 0xff;\n decoded.plan_config.fan_mode = bytes[offset + 3] & 0xff;\n var t = bytes[offset + 3] & 0xff;\n decoded.plan_config.temperature_target = t & 0x7f;\n decoded.temperature_unit = (t >>> 7) & 0x01;\n decoded.plan_config.temperature_error = (bytes[offset + 4] & 0xff) / 10;\n offset += 5;\n break;\n case 0xC9:\n var schedule = {};\n schedule.type = bytes[offset];\n schedule.id = bytes[offset + 1] + 1;\n schedule.enable = bytes[offset + 2];\n schedule.week_recycle = readWeekRecycleSettings(bytes[offset + 3]);\n var time_mins = parseBytesToInt(bytes, offset + 4, 2, false);\n schedule.time = Math.floor(time_mins / 60) + \":\" + (\"0\" + (time_mins % 60)).slice(-2);\n offset += 6;\n if (decoded.plan_schedule === null) {\n decoded.plan_schedule = [];\n }\n decoded.plan_schedule.push(schedule);\n break;\n case 0xf6:\n decoded.control_permissions = bytes[offset] & 0xff;\n offset += 1;\n break;\n case 0xf7:\n var wire_relay_bit_offset = { y1: 0, y2_gl: 1, w1: 2, w2_aux: 3, e: 4, g: 5, ob: 6 };\n var mask = parseBytesToInt(bytes, offset, 2, false);\n var status = parseBytesToInt(bytes, offset + 2, 2, false);\n offset += 4;\n\n decoded.wires_relay_config = {};\n foreach (key : wire_relay_bit_offset) {\n if (((mask >>> wire_relay_bit_offset[key]) & 0x01) === 1) {\n decoded.wires_relay_config[key] = (status >>> wire_relay_bit_offset[key]) & 0x01;\n }\n }\n break;\n case 0xf8: // offline_control_mode\n decoded.offline_control_mode = bytes[offset] & 0xff;\n break;\n case 0xf9: // humidity_calibration\n decoded.humidity_calibration = {};\n decoded.humidity_calibration.enable = bytes[offset] & 0xff;\n if (decoded.humidity_calibration.enable === 1) {\n decoded.humidity_calibration.humidity = readInt16LE(bytes.slice(offset + 1, offset + 3)) / 10;\n }\n offset += 3;\n break;\n case 0xfa:\n decoded.temperature_control_mode = bytes[offset] & 0xff;\n decoded.temperature_target = readInt16LE(bytes.slice(offset + 1, offset + 3)) / 10;\n offset += 3;\n break;\n case 0xfb:\n decoded.temperature_control_mode = bytes[offset] & 0xff;\n offset += 1;\n break;\n default:\n raiseError(\"unknown downlink response\");\n }\n\n return { data: decoded, offset: offset };\n}\n\nfunction handle_downlink_response_ext(channel_type, bytes, offset) {\n var decoded = {};\n\n switch (channel_type) {\n case 0x05:\n var fan_delay_control_result = bytes[offset + 3] & 0xff;\n if (fan_delay_control_result === 0) {\n decoded.fan_delay_enable = bytes[offset] & 0xff;\n decoded.fan_delay_time = bytes[offset + 1] & 0xff;\n } else {\n raiseError(\"fan delay control failed\");\n }\n offset += 3;\n break;\n case 0x06:\n var fan_execute_result = bytes[offset + 1] & 0xff;\n if (fan_execute_result === 0) {\n decoded.fan_execute_time = bytes[offset] & 0xff;\n } else {\n raiseError(\"fan execute control failed\");\n }\n\n offset += 2;\n break;\n case 0x07:\n var dehumidify_control_result = bytes[offset + 3] & 0xff;\n if (dehumidify_control_result === 0) {\n decoded.fan_dehumidify = {};\n decoded.fan_dehumidify.enable = bytes[offset] & 0xff;\n if (decoded.fan_dehumidify.enable === 1) {\n decoded.fan_dehumidify.execute_time = bytes[offset + 1] & 0xff;\n }\n }\n offset += 3;\n break;\n case 0x09:\n var humidity_range_result = bytes[offset + 3] & 0xff;\n if (humidity_range_result === 0) {\n decoded.humidity_range = {};\n decoded.humidity_range.min = bytes[offset] & 0xff;\n decoded.humidity_range.max = bytes[offset + 1] & 0xff;\n } else {\n raiseError(\"humidity range control failed\");\n }\n offset += 3;\n break;\n case 0x0a:\n var dehumidify_result = bytes[offset + 3] & 0xff;\n if (dehumidify_result === 0) {\n decoded.temperature_dehumidify = {};\n decoded.temperature_dehumidify.enable = bytes[offset] & 0xff;\n if (decoded.temperature_dehumidify.enable === 1) {\n var value = bytes[offset + 1] & 0xff;\n if (value != 0xff) {\n decoded.temperature_dehumidify.temperature_tolerance = readUInt8(bytes[offset + 1]) / 10;\n }\n }\n } else {\n raiseError(\"dehumidify control failed\");\n }\n offset += 3;\n break;\n default:\n raiseError(\"unknown downlink response\");\n }\n\n return { data: decoded, offset: offset };\n}\n\nfunction readD2DCommand(bytes) {\n var bytesInt = parseBytesToInt(bytes, 0 , 2);\n return intToHex(bytesInt, false, false, 2)\n}\n\n// --- attributes and telemetry objects ---\nvar telemetry = {};\nvar attributes = {};\n// --- attributes and telemetry objects ---\n\n// --- Timestamp parsing\nvar dateString = data.time;\nvar timestamp = -1;\nif (dateString != null) {\n timestamp = new Date(dateString).getTime();\n if (timestamp == -1) {\n var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;\n var millisecondsEndIndex = dateString.lastIndexOf('+');\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('Z');\n }\n if (millisecondsEndIndex == -1) {\n millisecondsEndIndex = dateString.lastIndexOf('-');\n }\n if (millisecondsEndIndex == -1) {\n if (dateString.length >= secondsSeparatorIndex + 3) {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3);\n }\n } else {\n dateString = dateString.substring(0, secondsSeparatorIndex + 3) +\n dateString.substring(millisecondsEndIndex, dateString.length);\n }\n timestamp = new Date(dateString).getTime();\n }\n}\n// If we cannot parse timestamp - we will use the current timestamp\nif (timestamp == -1) {\n timestamp = Date.now();\n}\n// --- Timestamp parsing\n\n// You can add some keys manually to attributes or telemetry\nattributes.deduplicationId = data.deduplicationId;\n\n// You can exclude some keys from the result\nvar excludeFromAttributesList = [\"deviceName\", \"rxInfo\", \"confirmed\", \"data\", \"deduplicationId\",\"time\", \"adr\", \"dr\", \"fCnt\"];\nvar excludeFromTelemetryList = [\"data\", \"deviceInfo\", \"txInfo\", \"devAddr\", \"adr\", \"time\", \"fPort\", \"region_common_name\", \"region_config_id\", \"deduplicationId\"];\n\n// Message parsing\n// To avoid paths in the decoded objects we passing false value to function as \"pathInKey\" argument.\n// Warning: pathInKey can cause already found fields to be overwritten with the last value found.\n\nvar telemetryData = toFlatMap(data, excludeFromTelemetryList, false);\nvar attributesData = toFlatMap(data, excludeFromAttributesList, false);\n\nvar uplinkDataList = [];\n\n// Passing incoming bytes to decodePayload function, to get custom decoding\nvar customDecoding = decodePayload(base64ToBytes(data.data));\n\n// Collecting data to result\nif (customDecoding.?telemetry.size() > 0) {\n telemetry.putAll(customDecoding.telemetry);\n}\n\nif (customDecoding.?attributes.size() > 0) {\n attributes.putAll(customDecoding.attributes);\n}\n\ntelemetry.putAll(telemetryData);\nattributes.putAll(attributesData);\n\nvar result = {\n deviceName: deviceName,\n deviceType: deviceType,\n groupName: groupName,\n attributes: attributes,\n telemetry: {\n ts: timestamp,\n values: telemetry\n }\n};\n\nreturn result;", "encoder": null, "tbelEncoder": null, "updateOnlyKeys": [ @@ -36,4 +36,4 @@ "description": "" }, "edgeTemplate": false -} +} \ No newline at end of file diff --git a/VENDORS/Milesight/WT201/ChirpStack/uplink/payload_08.json b/VENDORS/Milesight/WT201/ChirpStack/uplink/payload_08.json index ace3f3c9..24967067 100644 --- a/VENDORS/Milesight/WT201/ChirpStack/uplink/payload_08.json +++ b/VENDORS/Milesight/WT201/ChirpStack/uplink/payload_08.json @@ -18,7 +18,7 @@ "fCnt": 4, "fPort": 85, "confirmed": false, - "data": "A2cRAQiOAQloSgpuAA==", + "data": "A2cRAQiOAQloSgpucg==", "rxInfo": [{ "gatewayId": "6a7e111a10000000", "uplinkId": 24022, diff --git a/VENDORS/Milesight/WT201/ChirpStack/uplink/result.json b/VENDORS/Milesight/WT201/ChirpStack/uplink/result.json index dc122bb0..1f166043 100644 --- a/VENDORS/Milesight/WT201/ChirpStack/uplink/result.json +++ b/VENDORS/Milesight/WT201/ChirpStack/uplink/result.json @@ -30,6 +30,18 @@ "fan_status": "high speed", "plan_event": "not executed", "temperature_alarm": "temperature threshold alarm", + "history": [{ + "timestamp": 1695172444, + "fan_mode": "auto", + "fan_status": "standby", + "system_status": "on", + "temperature": 27.0, + "temperature_ctl_mode": "heat", + "temperature_ctl_status": "standby", + "temperature_target": 16.6 + }], + "temperature_control_mode_enable": ["auto", "cool", "heat"], + "temperature_control_status_enable": ["stage-1 heat", "aux heat", "stage-1 cool"], "wires": ["y1", "g", "ob", "aux"], "ob_mode": "heat", "plan_schedule": [{ diff --git a/VENDORS/Milesight/WT201/ChirpStack/uplink/result_04.json b/VENDORS/Milesight/WT201/ChirpStack/uplink/result_04.json index 494850aa..1b42f70c 100644 --- a/VENDORS/Milesight/WT201/ChirpStack/uplink/result_04.json +++ b/VENDORS/Milesight/WT201/ChirpStack/uplink/result_04.json @@ -22,7 +22,7 @@ "ts": 1684741625404, "values": { "hexString": "FFCB0D1101FFCA158004", - "temperature_control_mode_enable": ["heat", "cool", "auto"], + "temperature_control_mode_enable": ["auto", "cool", "heat"], "temperature_control_status_enable": ["stage-1 heat", "aux heat", "stage-1 cool"], "wires": ["y1", "g", "ob", "aux"], "ob_mode": "heat",