{ "name": "ChirpStack Uplink Converter for ioThings ioButton", "type": "UPLINK", "integrationType": "CHIRPSTACK", "debugMode": false, "debugSettings": { "failuresEnabled": true, "allEnabled": false, "allEnabledUntil": 0 }, "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": "/**\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 // 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 result = {};\n\nif (payload != null && payload.length != 0) {\n result = decodePayload(payload);\n}\n\n\n// Return the final result object.\nreturn result;\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", "devAddr", "fPort", "tenantId", "tenantName", "applicationId", "applicationName", "adr", "dr", "frequency", "bandwidth", "spreadingFactor", "codeRate", "deviceProfileId", "deviceProfileName" ], "type": "DEVICE", "name": "ioButton $eui", "profile": "$deviceProfileName", "label": "$deviceName", "customer": "", "group": "", "telemetry": null, "attributes": [ "eui", "devAddr", "fPort", "tenantId", "tenantName", "applicationId", "applicationName", "adr", "dr", "frequency", "bandwidth", "spreadingFactor", "codeRate", "deviceProfileId", "deviceProfileName" ] }, "additionalInfo": { "description": "" }, "edgeTemplate": false, "converterVersion": 2 }