{ "name": "Loriot Uplink Converter for ioThings ioButton", "type": "UPLINK", "integrationType": "LORIOT", "debugMode": false, "debugSettings": { "failuresEnabled": true, "allEnabled": false, "allEnabledUntil": 0 }, "configuration": { "scriptLang": "TBEL", "decoder": "/**\n * Decodes the incoming payload and returns a structured object containing telemetry data and attributes.\n *\n * @param {number[]} input - The raw payload received as an array of bytes.\n * @returns {Object} output - The structured output with decoded telemetry and attributes.\n */\n\nfunction decodePayload(input) {\n // Initialize the output object with empty attributes and telemetry for clarity.\n var result = { attributes: {}, telemetry: {}};\n\n // Decode serial number (SN) from the first 4 bytes of the payload.\n // Press '?' icon in the top right corner to learn more about built in helper functions and capabilities.\n result.attributes.sn = parseBytesToInt(input, 0, 4);\n\n // Extract the timestamp from metadata (represented in milliseconds).\n var timestamp = metadata.ts; // ts from the incoming message.\n\n // Initialize an object to store decoded key/value telemetry data.\n var values = {};\n\n // Decode battery level from the 5th byte of the payload.\n values.battery = parseBytesToInt(input, 4, 1);\n\n // Decode temperature from the 6th and 7th bytes of the payload (divided by 100).\n values.temperature = parseBytesToInt(input, 5, 2) / 100.0;\n\n // Decode saturation from the 8th byte of the payload.\n values.saturation = parseBytesToInt(input, 7, 1);\n\n // Combine the timestamp with values and add it to the telemetry.\n result.telemetry = {\n ts: timestamp,\n values: values\n };\n\n // Return the fully constructed output object.\n return result;\n // Same logic, less code:\n // return {\n // attributes: {\n // sn: parseBytesToInt(input, 0, 4)\n // },\n // telemetry: {\n // ts: metadata.ts,\n // values: {\n // battery: parseBytesToInt(input, 4, 1),\n // temperature: parseBytesToInt(input, 5, 2) / 100.0,\n // saturation: parseBytesToInt(input, 7, 1)\n // }\n // }\n // };\n}\n\nvar result = decodePayload(payload);\n// Uncomment this code block to overwrite values set in the main configuration window. Useful if you extract device/asset/customer/group names from the payload;\n// result.type = 'DEVICE'; // Entity type allows you to choose type of created entity. Can be 'DEVICE' or 'ASSET'.\n// result.name = 'Temperature Sensor'; // Device or asset name (the value must be unique)\n// result.profile = 'IndustrialSensorProfile'; // Device or asset profile name.\n// result.customer = 'MyCustomer'; // If customer is not null - created entity will be assigned to customer with such name.\n// result.group = 'SensorsGroup'; // If group is not null - created entity will be added to the entity group with such name.\n\n// Return the final result object.\nreturn result;\n\n/**\n * Parse a slice of bytes from an array into an integer (big-endian).\n *\n * @param {number[]} input - The array of bytes.\n * @param {number} offset - The starting index.\n * @param {number} length - The number of bytes to convert.\n * @returns {number} - The resulting integer.\n */\nfunction parseBytesToInt(input, offset, length) {\n var result = 0;\n for (var i = offset; i < offset + length; i++) {\n result = (result << 8) | (input[i] & 0xFF);\n }\n return result;\n}", "tbelDecoder": "/**\n * Decodes the incoming payload and returns a structured object containing telemetry data and attributes.\n *\n * @param {byte[]} input - The raw payload received as an array of bytes.\n * @returns {Object} output - The structured output with decoded telemetry and attributes.\n */\n\nfunction decodePayload(input) {\n // Initialize the output object with empty attributes and telemetry for clarity.\n var result = { attributes: {}, telemetry: {}};\n\n\n // Extract the timestamp from metadata (represented in milliseconds).\n var timestamp = metadata.ts; // ts is the timestamp parsed from the incoming message's time, or returns the current time if it cannot be parsed.\n\n // Initialize an object to store decoded key/value telemetry data.\n var values = {};\n var index = 0;\n var headerByte = input[index++];\n result.attributes.uplinkReasonButton = ((headerByte & 1) != 0);\n\n if (result.attributes.uplinkReasonButton) {\n result.attributes.buttonClickReason = 'single';\n }\n \n result.attributes.uplinkReasonMovement = ((headerByte & 2) != 0);\n result.attributes.uplinkReasonGpio = ((headerByte & 4) != 0);\n result.attributes.containsGps = ((headerByte & 8) != 0);\n result.attributes.containsOnboardSensors = ((headerByte & 16) != 0);\n result.attributes.containsSpecial = ((headerByte & 32) != 0);\n result.attributes.crc = Integer.toHexString(input[index++] & 0xFF);\n values.batteryLevel = parseBytesIntToFloat(input, index, 1);\n index += 1;\n \n if (result.attributes.containsOnboardSensors != null && result.attributes.containsOnboardSensors) {\n var sensorContent = input[index++];\n result.attributes.containsTemperature = ((sensorContent & 1) != 0);\n result.attributes.containsLight = ((sensorContent & 2) != 0);\n result.attributes.containsAccelerometerCurrent = ((sensorContent & 4) != 0);\n result.attributes.containsAccelerometerMax = ((sensorContent & 8) != 0);\n result.attributes.containsWifiPositioningData = ((sensorContent & 16) != 0);\n result.attributes.buttonEventInfo = ((sensorContent & 32) != 0);\n result.attributes.containsExternalSensors = ((sensorContent & 64) != 0);\n result.attributes.containsBluetoothData = false;\n \n var buttonHeader = result.attributes.uplinkReasonButton;\n var buttonEvent = result.attributes.buttonEventInfo;\n if (!buttonEvent && !buttonHeader) {\n result.attributes.buttonClickReason = 'none';\n } else if (!buttonEvent && buttonHeader) {\n result.attributes.buttonClickReason = 'single';\n } else if (buttonEvent && !buttonHeader) {\n result.attributes.buttonClickReason = 'long';\n result.attributes.uplinkReasonButton = true;\n } else if (buttonEvent && buttonHeader) {\n result.attributes.buttonClickReason = 'double';\n }\n \n var hasSecondSensorContent = (sensorContent & 128) != 0;\n\n if (hasSecondSensorContent) {\n var sensorContent2 = input[index++];\n result.attributes.containsBluetoothData = (sensorContent2 & 1) != 0;\n result.attributes.containsRelativeHumidity = (sensorContent2 & 2) != 0;\n result.attributes.containsAirPressure = (sensorContent2 & 4) != 0;\n result.attributes.containsManDown = (sensorContent2 & 8) != 0;\n result.attributes.containsTilt = (sensorContent2 & 16) != 0;\n result.attributes.containsRetransmitCnt = (sensorContent2 & 32) != 0;\n }\n \n if (result.attributes.containsTemperature) {\n values.temperature = parseBytesToInt(input, index, 2) / 100;\n index += 2;\n }\n \n if (result.attributes.containsLight != null && result.attributes.containsLight) {\n var value = parseBytesToInt(input, index, 2);\n var exponent = value >> 12 & 0xFF;\n values.lightIntensity = Float.valueOf((value & 0x0FFF) << exponent) / 100;\n index += 2;\n }\n \n if (result.attributes.containsAccelerometerCurrent != null && result.attributes.containsAccelerometerCurrent) {\n values.accelerometer = {\n x: parseBytesIntToFloat(input, index, 2) / 1000,\n y: parseBytesIntToFloat(input, index + 2, 2) / 1000,\n z: parseBytesIntToFloat(input, index + 4, 2) / 1000\n };\n \n index += 6;\n }\n \n if (result.attributes.containsAccelerometerMax != null && result.attributes.containsAccelerometerMax) {\n values.maxAccelerationNew = parseBytesIntToFloat(input, index, 2) / 1000;\n values.maxAccelerationHistory = parseBytesIntToFloat(input, index + 2, 2) / 1000;\n \n index += 4;\n }\n \n if(result.attributes.containsWifiPositioningData != null && result.attributes.containsWifiPositioningData) {\n var wifiInfo = input[index++];\n var numAccessPoints = wifiInfo & 7;\n \n var wifiStatus = ((wifiInfo & 8) >> 2) + ((wifiInfo & 16) >> 3);\n var containsSignalStrength = wifiInfo & 32;\n var wifiStatusDescription = \"\";\n \n switch (wifiStatus) {\n case 0:\n wifiStatusDescription = 'success';\n break;\n case 1:\n wifiStatusDescription = 'failed';\n break;\n case 2:\n wifiStatusDescription = 'no_access_points';\n break;\n default:\n wifiStatusDescription = \"unknown (\" + wifiStatus + \")\";\n }\n \n values.wifiInfo = {\n status: wifiStatusDescription,\n statusCode: wifiStatus,\n accessPoints: []\n };\n \n for (int i = 0; i < numAccessPoints; i++) {\n var macParts = [];\n for (int j = 0; j < 6; j++) {\n macParts[j] = String.format(\"%02x\", input[index++] & 0xFF);\n }\n var macAddress = String.join(\":\", macParts);\n \n var signalStrength = null;\n if (containsSignalStrength != 0) {\n signalStrength = input[index++];\n }\n \n values.wifiInfo.accessPoints.push({\n macAddress: macAddress,\n signalStrength: signalStrength\n });\n }\n }\n \n if (result.attributes.containsExternalSensors != null && result.attributes.containsExternalSensors) {\n var type = input[index++];\n \n if (type === 0x0A) {\n values.externalSensor = {\n type: 'battery',\n batteryA: parseBytesToInt(input, index, 2),\n batteryB: parseBytesToInt(input, index + 2, 2)\n };\n i += 4;\n } else if (type === 0x64) {\n values.externalSensor = {\n type: 'externalTemperature',\n value: parseBytesToInt(input, index, 2) / 100\n };\n i += 2;\n } else if (type === 0x65) {\n values.externalSensor = {\n type: 'detectSwitch',\n value: input[index++]\n };\n } else if (type === 0x66) {\n var iobuttonStateData = input[index++];\n var iobuttonState = (iobuttonStateData & 0xF0) >> 4;\n var iobuttonStateClickCnt = iobuttonStateData & 0x0F;\n \n if (iobuttonState === 0) {\n iobuttonState = 'Idle';\n } else if (iobuttonState === 1) {\n iobuttonState = 'Calling';\n } else if (iobuttonState === 2) {\n iobuttonState = 'Success';\n } else if (iobuttonState === 3) {\n iobuttonState = 'Cleared';\n } else {\n iobuttonState = 'Undefined';\n }\n\n values.externalSensor = {\n type: 'buttonState',\n state: iobuttonState,\n clickCnt: iobuttonStateClickCnt\n };\n }\n }\n \n if(result.attributes.containsBluetoothData != null && result.attributes.containsBluetoothData) {\n var bluetoothInfo = input[index++];\n var numBeacons = bluetoothInfo & 7;\n var bluetoothStatus = bluetoothInfo >> 3 & 0x03;\n var addSlotInfo = bluetoothInfo >> 5 & 0x03;\n var bluetoothStatusDescription = \"\";\n \n if (bluetoothStatus == 0) {\n bluetoothStatusDescription = \"success\";\n }\n else if (bluetoothStatus == 1) {\n bluetoothStatusDescription = \"failed\";\n }\n else if (bluetoothStatus == 2) {\n bluetoothStatusDescription = \"no_access_points\";\n }\n else {\n bluetoothStatusDescription = \"unknown (\" + bluetoothStatus + \")\";\n }\n \n values.bluetoothInfo = {\n status: bluetoothStatusDescription,\n statusCode: bluetoothStatus,\n addSlotInfo: addSlotInfo,\n beacons: []\n };\n \n for (var i = 0; i < numBeacons; i++) {\n var beaconBluetoothBeaconsResult = null;\n if (addSlotInfo == 0x00) {\n beaconBluetoothBeaconsResult = parseBluetoothBeacons00(input, index);\n }\n else if (addSlotInfo == 0x01) {\n beaconBluetoothBeaconsResult = parseBluetoothBeacons01(input, index);\n }\n else if (addSlotInfo == 0x02) {\n beaconBluetoothBeaconsResult = parseBluetoothBeacons02(input, index);\n }\n \n if (beaconBluetoothBeaconsResult != null) {\n values.bluetoothInfo.beacons.push(beaconBluetoothBeaconsResult.beacon);\n index += beaconBluetoothBeaconsResult.index;\n }\n }\n }\n \n if (result.attributes.containsRelativeHumidity != null && result.attributes.containsRelativeHumidity) {\n values.humidity = parseBytesToInt(input, index, 2) / 100;\n i += 2;\n }\n \n if (result.attributes.containsAirPressure != null && result.attributes.containsAirPressure) {\n values.pressure = parseBytesToInt(input, index, 3);\n i += 3;\n }\n \n if (result.attributes.containsManDown != null && result.attributes.containsManDown) {\n var manDownData = (input[index++]);\n var manDownState = (manDownData & 0x0f);\n var manDownStateLabel = \"\";\n \n if (manDownState === 0x00) {\n manDownStateLabel = 'ok';\n } else if (manDownState === 0x01) {\n manDownStateLabel = 'sleeping';\n } else if (manDownState === 0x02) {\n manDownStateLabel = 'preAlarm';\n } else if (manDownState === 0x03) {\n manDownStateLabel = 'alarm';\n } else {\n manDownStateLabel = manDownState + '';\n }\n\n values.manDown = {\n state: manDownStateLabel,\n positionAlarm: (manDownData & 0x10) != 0,\n movementAlarm: (manDownData & 0x20) != 0\n };\n }\n \n if (result.attributes.containsTilt != null && result.attributes.containsTilt) {\n values.tilt = {\n currentTilt: parseBytesToInt(input, index, 2) / 100,\n currentDirection: Math.round(input[index + 2] * (360/255)),\n maximumTiltHistory: parseBytesToInt(input, index + 3, 2) / 100,\n DirectionHistory: Math.round(input[index + 5] * (360/255)),\n };\n index += 6;\n }\n\n if (result.attributes.containsRetransmitCnt != null && result.attributes.containsRetransmitCnt) {\n values.retransmitCnt = input[index++];\n index += 1;\n }\n }\n \n if (result.attributes.containsGps != null && result.attributes.containsGps) {\n values.gps = {};\n values.gps.navStat = input[index++];\n values.gps.latitude = parseBytesIntToFloat(input, index, 4) / 10000000;\n values.gps.longitude = parseBytesIntToFloat(input, index + 4, 4) / 10000000;\n values.gps.altRef = parseBytesIntToFloat(input, index + 8, 2) / 10;\n values.gps.hAcc = input[index + 10];\n values.gps.vAcc = input[index + 11];\n values.gps.sog = parseBytesIntToFloat(input, index + 12, 2) / 10;\n values.gps.cog = parseBytesIntToFloat(input, index + 14, 2) / 10;\n values.gps.hdop = input[index + 16] / 10;\n values.gps.numSvs = input[index + 17];\n index += 18;\n }\n\n // Combine the timestamp with values and add it to the telemetry.\n result.telemetry = {\n ts: timestamp,\n values: values\n };\n\n // Return the fully constructed output object.\n return result;\n}\n\nvar uplinkDataResult = [];\n \nvar result = {};\n\nif (payload != null && payload.length != 0) {\n result = decodePayload(payload);\n}\n\nuplinkDataResult.push(result);\n\n// Return the final result object.\nreturn uplinkDataResult;\n\nfunction substringToHex(source, int offset, int length) {\n var hex = \"\";\n for (int i = 0; i < length; i++) {\n hex += String.format(\"%02x\", source[offset + i] & 0xFF);\n }\n return hex;\n}\n\nfunction parseBluetoothBeacons00(bytes, i) {\n var beaconStatus = bytes[i++];\n var beaconType = beaconStatus & 0x03;\n var rssiRaw = beaconStatus >> 2;\n var rssi = 27 - rssiRaw * 2;\n var beacon = \"\";\n\n if (beaconType === 0x00) {\n var bluetoothResult = {\n beacon : {\n type: 'ibeacon',\n rssi: rssi,\n uuid: substringToHex(bytes, i, 2),\n major: substringToHex(bytes, i + 2, 2),\n minor: substringToHex(bytes, i + 4, 2)\n },\n index: 6\n };\n return bluetoothResult;\n } else if (beaconType === 0x01) {\n var bluetoothResult = {\n beacon : {\n type: 'eddystone',\n rssi: rssi,\n instance: substringToHex(bytes, i, 6)\n },\n index: 6\n };\n return bluetoothResult;\n } else if (beaconType === 0x02) {\n var bluetoothResult = {\n beacon : {\n type: 'altbeacon',\n rssi: rssi,\n id1: substringToHex(bytes, i, 2),\n id2: substringToHex(bytes, i + 2, 2),\n id3: substringToHex(bytes, i + 4, 2)\n },\n index: 6\n };\n return bluetoothResult;\n } else if (beaconType === 0x03) {\n var bluetoothResult = {\n beacon : {\n type: 'fullbeacon',\n rssi: rssi,\n id1: substringToHex(bytes, i, 2),\n id2: substringToHex(bytes, i + 2, 2),\n id3: substringToHex(bytes, i + 4, 2)\n },\n index: 6\n };\n return bluetoothResult;\n } else {\n return null;\n }\n}\n\nfunction parseBluetoothBeacons01(bytes, i) {\n var beaconStatus = bytes[i++];\n var beaconType = beaconStatus & 0x03;\n var rssiRaw = beaconStatus >> 2;\n var rssi = 27 - rssiRaw * 2;\n\n if (beaconType === 0x00) {\n var bluetoothResult = {\n beacon : {\n type: 'ibeacon',\n rssi: rssi,\n uuid: substringToHex(bytes, i, 16),\n major: substringToHex(bytes, i + 16, 2),\n minor: substringToHex(bytes, i + 16, 2)\n },\n index: 20\n };\n return bluetoothResult;\n } else if (beaconType === 0x01) {\n var bluetoothResult = {\n beacon : {\n type: 'eddystone',\n rssi: rssi,\n namespace: substringToHex(bytes, i, 10),\n instance: substringToHex(bytes, i + 10, 6)\n },\n index: 16\n };\n return bluetoothResult;\n } else if (beaconType === 0x02) {\n var bluetoothResult = {\n beacon : {\n type: 'altbeacon',\n rssi: rssi,\n id1: substringToHex(bytes, i, 16),\n id2: substringToHex(bytes, i + 16, 2),\n id3: substringToHex(bytes, i + 18, 2)\n },\n index: 20\n };\n return bluetoothResult;\n } else if (beaconType === 0x03) {\n var bluetoothResult = {\n beacon : {\n type: 'fullbeacon',\n rssi: rssi,\n id1: substringToHex(bytes, i, 16),\n id2: substringToHex(bytes, i + 16, 2),\n id3: substringToHex(bytes, i + 18, 2)\n },\n index: 20\n };\n return bluetoothResult;\n } else {\n return null;\n }\n}\n\nfunction parseBluetoothBeacons02(bytes, i) {\n var beaconStatus = bytes[i++];\n var beaconType = beaconStatus & 0x03;\n var slotMatch = beaconStatus >> 2 & 0x07;\n var rssiRaw = beaconStatus >> 2;\n var rssi = 27 - rssiRaw * 2;\n\n if (beaconType === 0x00) {\n var bluetoothResult = {\n beacon : {\n type: 'ibeacon',\n rssi: rssi,\n slot: slotMatch,\n major: substringToHex(bytes, i, 2),\n minor: substringToHex(bytes, i + 2, 2)\n },\n index: 4\n };\n return bluetoothResult;\n } else if (beaconType === 0x01) {\n var bluetoothResult = {\n beacon : {\n type: 'eddystone',\n rssi: rssi,\n slot: slotMatch,\n instance: substringToHex(bytes, i, 6)\n },\n index: 6\n };\n return bluetoothResult;\n } else if (beaconType === 0x02) {\n var bluetoothResult = {\n beacon : {\n type: 'altbeacon',\n rssi: rssi,\n slot: slotMatch,\n id2: substringToHex(bytes, i, 2),\n id3: substringToHex(bytes, i + 2, 2)\n },\n index: 4\n };\n return bluetoothResult;\n } else if (beaconType === 0x03) {\n var bluetoothResult = {\n beacon : {\n type: 'fullbeacon',\n rssi: rssi,\n slot: slotMatch,\n id2: substringToHex(bytes, i, 2),\n id3: substringToHex(bytes, i + 2, 2)\n },\n index: 4\n };\n return bluetoothResult;\n } else {\n return null;\n }\n}", "encoder": null, "tbelEncoder": null, "updateOnlyKeys": [ "eui", "fPort", "frequency", "dr" ], "type": "DEVICE", "name": "ioButton $eui", "profile": "", "label": "", "customer": "", "group": "", "telemetry": null, "attributes": [ "eui", "fPort", "frequency" ] }, "additionalInfo": { "description": "" }, "edgeTemplate": false, "converterVersion": 2 }