diff --git a/vendor/iothings/index.yaml b/vendor/iothings/index.yaml index 157ab5b963..5323f81f8a 100644 --- a/vendor/iothings/index.yaml +++ b/vendor/iothings/index.yaml @@ -1,2 +1,3 @@ endDevices: - iotracker3 + - iobutton diff --git a/vendor/iothings/iobutton-codec.yaml b/vendor/iothings/iobutton-codec.yaml new file mode 100644 index 0000000000..782cdc2811 --- /dev/null +++ b/vendor/iothings/iobutton-codec.yaml @@ -0,0 +1,2 @@ +uplinkDecoder: + fileName: iobutton.js diff --git a/vendor/iothings/iobutton-profile.yaml b/vendor/iothings/iobutton-profile.yaml new file mode 100644 index 0000000000..46ace7de4a --- /dev/null +++ b/vendor/iothings/iobutton-profile.yaml @@ -0,0 +1,7 @@ +macVersion: 1.0.4 +regionalParametersVersion: RP002-1.0.1 +supportsJoin: true +maxEIRP: 16 +supports32bitFCnt: true +supportsClassB: false +supportsClassC: false diff --git a/vendor/iothings/iobutton.js b/vendor/iothings/iobutton.js new file mode 100644 index 0000000000..c3c8e8eb80 --- /dev/null +++ b/vendor/iothings/iobutton.js @@ -0,0 +1,551 @@ +"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; +} \ No newline at end of file diff --git a/vendor/iothings/iobutton.png b/vendor/iothings/iobutton.png new file mode 100644 index 0000000000..c3a2d49e2d Binary files /dev/null and b/vendor/iothings/iobutton.png differ diff --git a/vendor/iothings/iobutton.yaml b/vendor/iothings/iobutton.yaml new file mode 100644 index 0000000000..989145170e --- /dev/null +++ b/vendor/iothings/iobutton.yaml @@ -0,0 +1,69 @@ +name: ioButton +description: LoRaWAN Panic Button +hardwareVersions: + - version: '1' + numeric: 1 +firmwareVersions: + - version: '1.17' + numeric: 1 + hardwareVersions: + - '1' + features: + - transmission interval + profiles: + EU863-870: + vendorID: iothings + id: iobutton-profile + lorawanCertified: true + codec: iobutton-codec + US902-928: + vendorID: iothings + id: iobutton-profile + lorawanCertified: true + codec: iobutton-codec +sensors: + - button +additionalRadios: + - wifi + - ble +dimensions: + width: 78 + length: 78 + height: 20 +weight: 70 +battery: + replaceable: true + type: ER14500 +operatingConditions: + temperature: + min: -20 + max: 65 +ipCode: IP40 +keyProvisioning: + - custom +keyProgramming: + - bluetooth +keySecurity: read protected +firmwareProgramming: + - fuota other +productURL: https://www.iotracker.eu/products/iobutton +dataSheetURL: https://www.iotracker.nl/uploads/files/7/a/7ab03f5852c845755ec66ae2022ffcad302a1b3f.pdf +resellerURLs: + - name: 'ioThings' + region: + - European Union + - United States + - Canada + url: https://www.iotracker.eu/contact + - name: 'iot-shop' + region: + - European Union + - United States + - Canada + url: https://iot-shop.de +msrp: + EUR: 89 +photos: + main: iobutton.png +videos: + main: https://www.youtube.com/watch?v=8AACbYotXKA diff --git a/vendor/iothings/iotracker3.js b/vendor/iothings/iotracker3.js index 137e53c499..c3c8e8eb80 100644 --- a/vendor/iothings/iotracker3.js +++ b/vendor/iothings/iotracker3.js @@ -1,11 +1,17 @@ "use strict"; /* eslint no-bitwise: ["error", { "allow": ["&", "<<", ">>", "|"] }] */ + /* eslint no-plusplus: "off" */ +/** + * Decode payload + * @param bytes Buffer + * @returns Object + */ function Decoder(bytes) { - - var decoded = {}; + // Decoded result + var decoded = {}; // Pointer/index within the byte stream var index = 0; @@ -18,7 +24,7 @@ function Decoder(bytes) { var x = (byte1 & 0xFF) << 8 | byte2 & 0xFF; if (sign) { - return 0xFFFF0000 | x; + return 0xFFFF0000 | x; // fill in most significant bits with 1's } return x; @@ -29,33 +35,40 @@ function Decoder(bytes) { } function toSignedInteger(byte1, byte2, byte3, byte4) { - return (byte1 & 0x80 ? -1 : 1) * ((byte1 & 0x7F) << 24) + (byte2 << 16) + (byte3 << 8) + byte4; + return byte1 << 24 | byte2 << 16 | byte3 << 8 | byte4; } - function bytesToHexString(bytes){ - if (!bytes){ + 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){ + + 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]; + + 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++]; @@ -108,9 +121,11 @@ function Decoder(bytes) { return beacon; default: - return null; + throw new Error('Invalid beacon type'); } - } + } // parser for slotInfo 0x01 + + function parseBluetoothBeacons01() { var beaconStatus = bytes[index++]; var beaconType = beaconStatus & 0x03; @@ -163,9 +178,11 @@ function Decoder(bytes) { return beacon; default: - return null; + throw new Error('Invalid beacon type'); } - } + } // parser for slotInfo 0x02 + + function parseBluetoothBeacons02() { var beaconStatus = bytes[index++]; var beaconType = beaconStatus & 0x03; @@ -219,18 +236,23 @@ function Decoder(bytes) { return beacon; default: - return null; + 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++]; + decoded.crc = bytes[index++].toString(16); decoded.batteryLevel = bytes[index++]; if (decoded.containsOnboardSensors) { @@ -245,6 +267,20 @@ function Decoder(bytes) { 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) { @@ -253,6 +289,8 @@ function Decoder(bytes) { 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) { @@ -280,7 +318,7 @@ function Decoder(bytes) { if (decoded.sensorContent.containsWifiPositioningData) { var wifiInfo = bytes[index++]; - var numAccessPoints = wifiInfo & 7; + var numAccessPoints = wifiInfo & 7; // const wifiStatus = (wifiInfo >> 3) & 0x03; var wifiStatus = ((wifiInfo & 8) >> 2) + ((wifiInfo & 16) >> 3); var containsSignalStrength = wifiInfo & 32; @@ -314,7 +352,7 @@ function Decoder(bytes) { var signalStrength = void 0; if (containsSignalStrength) { - signalStrength = toSignedChar(bytes[index++]); + signalStrength = toSignedChar(bytes[index++]); // to signed } else { signalStrength = null; } @@ -338,12 +376,52 @@ function Decoder(bytes) { }; 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; } } @@ -378,31 +456,23 @@ function Decoder(bytes) { beacons: [] }; - for (var _i = 0; _i < numBeacons; _i++) { - var beacon; switch (addSlotInfo) { case 0x00: - beacon = parseBluetoothBeacons00(); + decoded.bluetoothInfo.beacons.push(parseBluetoothBeacons00()); break; case 0x01: - beacon = parseBluetoothBeacons01(); + decoded.bluetoothInfo.beacons.push(parseBluetoothBeacons01()); break; case 0x02: - beacon = parseBluetoothBeacons02(); + decoded.bluetoothInfo.beacons.push(parseBluetoothBeacons02()); break; default: - return {errors: ['Invalid addSlotInfo type']}; - - } - if (beacon === null) { - return {errors: ['Invalid beacon type']}; + throw new Error('Invalid addSlotInfo type'); } - - decoded.bluetoothInfo.beacons.push(beacon); } } @@ -411,31 +481,37 @@ function Decoder(bytes) { } 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; - switch(manDownState) { + 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+''; + manDownStateLabel = manDownState + ''; break; } + decoded.manDown = { state: manDownStateLabel, positionAlarm: !!(manDownData & 0x10), @@ -443,23 +519,25 @@ function Decoder(bytes) { }; } + 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.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++]; @@ -470,9 +548,4 @@ function Decoder(bytes) { } return decoded; -} - -// Export function (for implementations and for testing) -if (typeof module !== 'undefined' && module.exports !== null) { - module.exports = Decoder; } \ No newline at end of file diff --git a/vendor/iothings/iotracker3.yaml b/vendor/iothings/iotracker3.yaml index cfe22ca730..14839435fc 100644 --- a/vendor/iothings/iotracker3.yaml +++ b/vendor/iothings/iotracker3.yaml @@ -1,10 +1,10 @@ name: ioTracker 3 -description: multi-sensor LoRaWAN tracker +description: LoRaWAN Panic Button and Multi-Sensor Tracker hardwareVersions: - version: '3' numeric: 1 firmwareVersions: - - version: '1.10' + - version: '1.18' numeric: 1 hardwareVersions: - '3' @@ -57,18 +57,18 @@ firmwareProgramming: productURL: https://www.iotracker.nl/iotracker dataSheetURL: https://www.iotracker.nl/uploads/files/1/a/1ae2a37091d7cf93cabb1e81f4c4949dc1ad29fc.pdf resellerURLs: - - name: 'instaSOLUTION' + - name: 'ioThings' region: - European Union - url: https://instasolution.ch/threat-alerting/ - - name: 'Serinus' + - United States + - Canada + url: https://www.iotracker.eu/contact + - name: 'iot-shop' region: - European Union - url: https://serinus.de/ - - name: 'X-Guard' - region: - - European Union - url: https://x-guard.nl/ + - United States + - Canada + url: https://iot-shop.de msrp: EUR: 89 photos: