Sensor
"use strict"; /* eslint no-bitwise: ["error", { "allow": ["&", "<<", ">>", "|"] }] */ /* eslint no-plusplus: "off" */ /** * Decode payload * @param bytes Buffer * @returns Object */ function Decoder(bytes) { // Decoded result var decoded = {}; // Pointer/index within the byte stream var index = 0; function toSignedChar(byte) { return (byte & 127) - (byte & 128); } function toSignedShort(byte1, byte2) { var sign = byte1 & 1 << 7; var x = (byte1 & 0xFF) << 8 | byte2 & 0xFF; if (sign) { return 0xFFFF0000 | x; // fill in most significant bits with 1's } return x; } function toUnsignedShort(byte1, byte2) { return (byte1 << 8) + byte2; } function toSignedInteger(byte1, byte2, byte3, byte4) { return byte1 << 24 | byte2 << 16 | byte3 << 8 | byte4; } function bytesToHexString(bytes) { if (!bytes) { return null; } bytes = new Uint8Array(bytes); var hexBytes = []; for (var i = 0; i < bytes.length; ++i) { var byteString = bytes[i].toString(16); if (byteString.length < 2) { byteString = "0" + byteString; } hexBytes.push(byteString); } return hexBytes.join(""); } function substring(source, offset, length) { var buffer = new Uint8Array(length); for (var i = 0; i < length; i++) { buffer[i] = source[offset + i]; } return bytesToHexString(buffer); } // parser for slotInfo 0x00 function parseBluetoothBeacons00() { var beaconStatus = bytes[index++]; var beaconType = beaconStatus & 0x03; var rssiRaw = beaconStatus >> 2; var rssi = 27 - rssiRaw * 2; var beacon = void 0; switch (beaconType) { case 0x00: beacon = { type: 'ibeacon', rssi: rssi, uuid: substring(bytes, index, 2), major: substring(bytes, index + 2, 2), minor: substring(bytes, index + 4, 2) }; index += 6; return beacon; case 0x01: beacon = { type: 'eddystone', rssi: rssi, instance: substring(bytes, index, 6) }; index += 6; return beacon; case 0x02: beacon = { type: 'altbeacon', rssi: rssi, id1: substring(bytes, index, 2), id2: substring(bytes, index + 2, 2), id3: substring(bytes, index + 4, 2) }; index += 6; return beacon; case 0x03: beacon = { type: 'fullbeacon', rssi: rssi, id1: substring(bytes, index, 2), id2: substring(bytes, index + 2, 2), id3: substring(bytes, index + 4, 2) }; index += 6; return beacon; default: throw new Error('Invalid beacon type'); } } // parser for slotInfo 0x01 function parseBluetoothBeacons01() { var beaconStatus = bytes[index++]; var beaconType = beaconStatus & 0x03; var rssiRaw = beaconStatus >> 2; var rssi = 27 - rssiRaw * 2; var beacon = void 0; switch (beaconType) { case 0x00: beacon = { type: 'ibeacon', rssi: rssi, uuid: substring(bytes, index, 16), major: substring(bytes, index + 16, 2), minor: substring(bytes, index + 18, 2) }; index += 20; return beacon; case 0x01: beacon = { type: 'eddystone', rssi: rssi, namespace: substring(bytes, index, 10), instance: substring(bytes, index + 10, 6) }; index += 16; return beacon; case 0x02: beacon = { type: 'altbeacon', rssi: rssi, id1: substring(bytes, index, 16), id2: substring(bytes, index + 16, 2), id3: substring(bytes, index + 18, 2) }; index += 20; return beacon; case 0x03: beacon = { type: 'fullbeacon', rssi: rssi, id1: substring(bytes, index, 16), id2: substring(bytes, index + 16, 2), id3: substring(bytes, index + 18, 2) }; index += 20; return beacon; default: throw new Error('Invalid beacon type'); } } // parser for slotInfo 0x02 function parseBluetoothBeacons02() { var beaconStatus = bytes[index++]; var beaconType = beaconStatus & 0x03; var slotMatch = beaconStatus >> 2 & 0x07; var rssiRaw = bytes[index++] & 63; var rssi = 27 - rssiRaw * 2; var beacon = void 0; switch (beaconType) { case 0x00: beacon = { type: 'ibeacon', rssi: rssi, slot: slotMatch, major: substring(bytes, index, 2), minor: substring(bytes, index + 2, 2) }; index += 4; return beacon; case 0x01: beacon = { type: 'eddystone', rssi: rssi, slot: slotMatch, instance: substring(bytes, index, 6) }; index += 6; return beacon; case 0x02: beacon = { type: 'altbeacon', rssi: rssi, slot: slotMatch, id2: substring(bytes, index, 2), id3: substring(bytes, index + 2, 2) }; index += 4; return beacon; case 0x03: beacon = { type: 'fullbeacon', rssi: rssi, slot: slotMatch, id2: substring(bytes, index, 2), id3: substring(bytes, index + 2, 2) }; index += 6; return beacon; default: throw new Error('Invalid beacon type'); } } // Read header byte var headerByte = bytes[index++]; decoded.uplinkReasonButton = !!(headerByte & 1); if (decoded.uplinkReasonButton) { // Also set the reason (this can be overridden below, based on sensor content) decoded.buttonClickReason = 'single'; } decoded.uplinkReasonMovement = !!(headerByte & 2); decoded.uplinkReasonGpio = !!(headerByte & 4); decoded.containsGps = !!(headerByte & 8); decoded.containsOnboardSensors = !!(headerByte & 16); decoded.containsSpecial = !!(headerByte & 32); decoded.crc = bytes[index++].toString(16); decoded.batteryLevel = bytes[index++]; if (decoded.containsOnboardSensors) { var sensorContent = bytes[index++]; decoded.sensorContent = { containsTemperature: !!(sensorContent & 1), containsLight: !!(sensorContent & 2), containsAccelerometerCurrent: !!(sensorContent & 4), containsAccelerometerMax: !!(sensorContent & 8), containsWifiPositioningData: !!(sensorContent & 16), buttonEventInfo: !!(sensorContent & 32), containsExternalSensors: !!(sensorContent & 64), containsBluetoothData: false }; var buttonHeader = decoded.uplinkReasonButton; // b0 var buttonEvent = decoded.sensorContent.buttonEventInfo; // b3 if (!buttonEvent && !buttonHeader) { decoded.buttonClickReason = 'none'; } else if (!buttonEvent && buttonHeader) { decoded.buttonClickReason = 'single'; } else if (buttonEvent && !buttonHeader) { decoded.buttonClickReason = 'long'; decoded.uplinkReasonButton = true; // Set the uplink reason true, because the button was pressed. } else if (buttonEvent && buttonHeader) { decoded.buttonClickReason = 'double'; } var hasSecondSensorContent = !!(sensorContent & 128); if (hasSecondSensorContent) { var sensorContent2 = bytes[index++]; decoded.sensorContent.containsBluetoothData = !!(sensorContent2 & 1); decoded.sensorContent.containsRelativeHumidity = !!(sensorContent2 & 2); decoded.sensorContent.containsAirPressure = !!(sensorContent2 & 4); decoded.sensorContent.containsManDown = !!(sensorContent2 & 8); decoded.sensorContent.containsTilt = !!(sensorContent2 & 16); decoded.sensorContent.containsRetransmitCnt = !!(sensorContent2 & 32); } if (decoded.sensorContent.containsTemperature) { decoded.temperature = toSignedShort(bytes[index++], bytes[index++]) / 100; } if (decoded.sensorContent.containsLight) { var value = (bytes[index++] << 8) + bytes[index++]; var exponent = value >> 12 & 0xFF; decoded.lightIntensity = ((value & 0x0FFF) << exponent) / 100; } if (decoded.sensorContent.containsAccelerometerCurrent) { decoded.accelerometer = { x: toSignedShort(bytes[index++], bytes[index++]) / 1000, y: toSignedShort(bytes[index++], bytes[index++]) / 1000, z: toSignedShort(bytes[index++], bytes[index++]) / 1000 }; } if (decoded.sensorContent.containsAccelerometerMax) { decoded.maxAccelerationNew = toSignedShort(bytes[index++], bytes[index++]) / 1000; decoded.maxAccelerationHistory = toSignedShort(bytes[index++], bytes[index++]) / 1000; } if (decoded.sensorContent.containsWifiPositioningData) { var wifiInfo = bytes[index++]; var numAccessPoints = wifiInfo & 7; // const wifiStatus = (wifiInfo >> 3) & 0x03; var wifiStatus = ((wifiInfo & 8) >> 2) + ((wifiInfo & 16) >> 3); var containsSignalStrength = wifiInfo & 32; var wifiStatusDescription = void 0; switch (wifiStatus) { case 0: wifiStatusDescription = 'success'; break; case 1: wifiStatusDescription = 'failed'; break; case 2: wifiStatusDescription = 'no_access_points'; break; default: wifiStatusDescription = "unknown (" + wifiStatus + ")"; } decoded.wifiInfo = { status: wifiStatusDescription, statusCode: wifiStatus, accessPoints: [] }; for (var i = 0; i < numAccessPoints; i++) { var macAddress = [bytes[index++].toString(16), bytes[index++].toString(16), bytes[index++].toString(16), bytes[index++].toString(16), bytes[index++].toString(16), bytes[index++].toString(16)]; var signalStrength = void 0; if (containsSignalStrength) { signalStrength = toSignedChar(bytes[index++]); // to signed } else { signalStrength = null; } decoded.wifiInfo.accessPoints.push({ macAddress: macAddress.join(':'), signalStrength: signalStrength }); } } if (decoded.sensorContent.containsExternalSensors) { var type = bytes[index++]; switch (type) { case 0x0A: decoded.externalSensor = { type: 'battery', batteryA: toUnsignedShort(bytes[index++], bytes[index++]), batteryB: toUnsignedShort(bytes[index++], bytes[index++]) }; break; case 0x64: decoded.externalSensor = { type: 'externalTemperature', value: toSignedShort(bytes[index++], bytes[index++]) / 100 }; break; case 0x65: decoded.externalSensor = { type: 'detectSwitch', value: bytes[index++] }; break; case 0x66: var iobuttonStateData = bytes[index++]; var iobuttonState = (iobuttonStateData & 0xF0) >> 4; var iobuttonStateClickCnt = iobuttonStateData & 0x0F; switch (iobuttonState) { case 0: iobuttonState = 'Idle'; break; case 1: iobuttonState = 'Calling'; break; case 2: iobuttonState = 'Success'; break; case 3: iobuttonState = 'Cleared'; break; default: iobuttonState = 'Undefined'; } decoded.externalSensor = { type: 'buttonState', state: iobuttonState, clickCnt: iobuttonStateClickCnt }; break; } } if (decoded.sensorContent.containsBluetoothData) { var bluetoothInfo = bytes[index++]; var numBeacons = bluetoothInfo & 7; var bluetoothStatus = bluetoothInfo >> 3 & 0x03; var addSlotInfo = bluetoothInfo >> 5 & 0x03; var bluetoothStatusDescription = void 0; switch (bluetoothStatus) { case 0: bluetoothStatusDescription = 'success'; break; case 1: bluetoothStatusDescription = 'failed'; break; case 2: bluetoothStatusDescription = 'no_access_points'; break; default: bluetoothStatusDescription = "unknown (" + bluetoothStatus + ")"; } decoded.bluetoothInfo = { status: bluetoothStatusDescription, statusCode: bluetoothStatus, addSlotInfo: addSlotInfo, beacons: [] }; for (var _i = 0; _i < numBeacons; _i++) { switch (addSlotInfo) { case 0x00: decoded.bluetoothInfo.beacons.push(parseBluetoothBeacons00()); break; case 0x01: decoded.bluetoothInfo.beacons.push(parseBluetoothBeacons01()); break; case 0x02: decoded.bluetoothInfo.beacons.push(parseBluetoothBeacons02()); break; default: throw new Error('Invalid addSlotInfo type'); } } } if (decoded.sensorContent.containsRelativeHumidity) { decoded.relativeHumidity = toUnsignedShort(bytes[index++], bytes[index++]) / 100; } if (decoded.sensorContent.containsAirPressure) { // uint24 decoded.airPressure = (bytes[index++] << 16) + (bytes[index++] << 8) + bytes[index++]; } if (decoded.sensorContent.containsManDown) { var manDownData = bytes[index++]; var manDownState = manDownData & 0x0f; var manDownStateLabel = void 0; switch (manDownState) { case 0x00: manDownStateLabel = 'ok'; break; case 0x01: manDownStateLabel = 'sleeping'; break; case 0x02: manDownStateLabel = 'preAlarm'; break; case 0x03: manDownStateLabel = 'alarm'; break; default: manDownStateLabel = manDownState + ''; break; } decoded.manDown = { state: manDownStateLabel, positionAlarm: !!(manDownData & 0x10), movementAlarm: !!(manDownData & 0x20) }; } if (decoded.sensorContent.containsTilt) { decoded.tilt = { currentTilt: toUnsignedShort(bytes[index++], bytes[index++]) / 100, currentDirection: Math.round(bytes[index++] * (360 / 255)), maximumTiltHistory: toUnsignedShort(bytes[index++], bytes[index++]) / 100, DirectionHistory: Math.round(bytes[index++] * (360 / 255)) }; } if (decoded.sensorContent.containsRetransmitCnt) { decoded.retransmitCnt = bytes[index++]; } } if (decoded.containsGps) { decoded.gps = {}; decoded.gps.navStat = bytes[index++]; decoded.gps.latitude = toSignedInteger(bytes[index++], bytes[index++], bytes[index++], bytes[index++]) / 10000000; decoded.gps.longitude = toSignedInteger(bytes[index++], bytes[index++], bytes[index++], bytes[index++]) / 10000000; decoded.gps.altRef = toUnsignedShort(bytes[index++], bytes[index++]) / 10; decoded.gps.hAcc = bytes[index++]; decoded.gps.vAcc = bytes[index++]; decoded.gps.sog = toUnsignedShort(bytes[index++], bytes[index++]) / 10; decoded.gps.cog = toUnsignedShort(bytes[index++], bytes[index++]) / 10; decoded.gps.hdop = bytes[index++] / 10; decoded.gps.numSvs = bytes[index++]; } return decoded; }
This codec is sourced from The Things Network. All rights belong to The Things Network.
This codec is licensed under the GNU General Public License v3 (GPL v3). Modifications, if any, are clearly marked. You are free to use, modify, and distribute the codec under the terms of GPL v3.