diff --git a/decoders/connector/browan/ambient-light/assets/logo.png b/decoders/connector/browan/ambient-light/assets/logo.png new file mode 100644 index 00000000..1da97d7e Binary files /dev/null and b/decoders/connector/browan/ambient-light/assets/logo.png differ diff --git a/decoders/connector/browan/ambient-light/connector.jsonc b/decoders/connector/browan/ambient-light/connector.jsonc new file mode 100644 index 00000000..17627772 --- /dev/null +++ b/decoders/connector/browan/ambient-light/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "Browan Ambient Light", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/browan/ambient-light/description.md b/decoders/connector/browan/ambient-light/description.md new file mode 100644 index 00000000..79ce1373 --- /dev/null +++ b/decoders/connector/browan/ambient-light/description.md @@ -0,0 +1 @@ +Ambient Light Intensity sensor over LoRaWAN™ \ No newline at end of file diff --git a/decoders/connector/browan/ambient-light/v1.0.0/payload-config.jsonc b/decoders/connector/browan/ambient-light/v1.0.0/payload-config.jsonc new file mode 100644 index 00000000..2cab6e0f --- /dev/null +++ b/decoders/connector/browan/ambient-light/v1.0.0/payload-config.jsonc @@ -0,0 +1,25 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "The Tabs Ambient Light Sensor utilizes LoRaWAN connectivity to provide measurements of ambient light intensity which matches the human eye's response to light under a variety of lighting conditions.\n\n**Specifications**\n* Size: 50 x 20 x 50 mm\n* Weight: 30g without battery, 40g with battery\n* Sensors: Temperature, Humidity and Water leak detection\n* Temperature: 0°C to +50°C\n* IP Rating: IP 40 equivalent\n* Power Source: 3.6V 1/2 AA Li-SOCl2 1200mAh battery\n* Frequency: 863-870MHz for EU, 902-928MHz for North America\n* Tx Power: US: +19dBm, EU: +17dBm\n* Rx Sensitivity: -135dBm\n* Antenna Gain: -2dBi Peak, -5dBi Avg", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-loriot-/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js", + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-brdot-/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/browan/ambient-light/v1.0.0/payload.js b/decoders/connector/browan/ambient-light/v1.0.0/payload.js new file mode 100644 index 00000000..b3b06264 --- /dev/null +++ b/decoders/connector/browan/ambient-light/v1.0.0/payload.js @@ -0,0 +1,26 @@ +function Decoder(bytes, port) { + const decoded = []; + if (port === 104) { + // STATUS + decoded.push({ variable: "darker", value: bytes[0] & 0x01 }); + decoded.push({ variable: "lighter", value: (bytes[0] >> 1) & 0x01 }); + decoded.push({ variable: "status_change", value: (bytes[0] >> 4) & 0x01 }); + decoded.push({ variable: "keep_alive", value: (bytes[0] >> 5) & 0x01 }); + // BATTERY + decoded.push({ variable: "battery", value: (25 + (bytes.readUInt8(1) & 0x0f)) / 10, unit: "V" }); + // TEMPERATURE + decoded.push({ variable: "temperature", value: (bytes.readUInt8(2) & 0x7f) - 32, unit: "°C" }); + // LUX + decoded.push({ variable: "lux", value: bytes.readUIntLE(3, 3) / 100, unit: "lux" }); + } + + return decoded; +} + +const data = payload.find((x) => x.variable === "payload" || x.variable === "payload_raw" || x.variable === "data"); +const port = payload.find((x) => x.variable === "port" || x.variable === "fport"); +if (data && port) { + const serie = data.serie || new Date().getTime(); + const bytes = Buffer.from(data.value, "hex"); + payload = payload.concat(Decoder(bytes, Number(port.value))).map((x) => ({ ...x, serie })); +} diff --git a/decoders/connector/browan/door-and-window-sensor/assets/logo.png b/decoders/connector/browan/door-and-window-sensor/assets/logo.png new file mode 100644 index 00000000..bde59fcf Binary files /dev/null and b/decoders/connector/browan/door-and-window-sensor/assets/logo.png differ diff --git a/decoders/connector/browan/door-and-window-sensor/connector.jsonc b/decoders/connector/browan/door-and-window-sensor/connector.jsonc new file mode 100644 index 00000000..660895e7 --- /dev/null +++ b/decoders/connector/browan/door-and-window-sensor/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "Browan Door and Window Sensor", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/browan/door-and-window-sensor/description.md b/decoders/connector/browan/door-and-window-sensor/description.md new file mode 100644 index 00000000..fd0cc6c8 --- /dev/null +++ b/decoders/connector/browan/door-and-window-sensor/description.md @@ -0,0 +1 @@ +Tabs Door & Window sensor to communicate the proximity or not of a magnet over LoRaWAN™ \ No newline at end of file diff --git a/decoders/connector/browan/door-and-window-sensor/v1.0.0/payload-config.jsonc b/decoders/connector/browan/door-and-window-sensor/v1.0.0/payload-config.jsonc new file mode 100644 index 00000000..07d54e02 --- /dev/null +++ b/decoders/connector/browan/door-and-window-sensor/v1.0.0/payload-config.jsonc @@ -0,0 +1,25 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "**Door & Window Sensor**\n\n\n• EU, US, and Asia frequency supported\n\n\n• LoRaWAN 1.0.3\n\n\n• 0 to 50°C operation temperature range\n\n\n• Hall-Effect sensor\n\n\n• 1 cm detection range\n\n\n• IP50 equivalent", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-loriot-/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js", + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-brdot-/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/browan/door-and-window-sensor/v1.0.0/payload.js b/decoders/connector/browan/door-and-window-sensor/v1.0.0/payload.js new file mode 100644 index 00000000..74bd9d8c --- /dev/null +++ b/decoders/connector/browan/door-and-window-sensor/v1.0.0/payload.js @@ -0,0 +1,113 @@ +/* eslint-disable camelcase */ +/* This is an example code for Everynet Parser. +** Everynet send several parameters to TagoIO. The job of this parse is to convert all these parameters into a TagoIO format. +** One of these parameters is the payload of your device. We find it too and apply the appropriate sensor parse. +** +** IMPORTANT: In most case, you will only need to edit the parsePayload function. +** +** Testing: +** You can do manual tests to this parse by using the Device Emulator. Copy and Paste the following code: +** [{ "variable": "everynet_payload", "value": "{ \"params\": { \"payload\": \"0109611395\" } }" }] +** +** The ignore_vars variable in this code should be used to ignore variables +** from the device that you don't want. +*/ +// Add ignorable variables in this array. +const ignore_vars = []; + + +/** + * Convert an object to TagoIO object format. + * Can be used in two ways: + * toTagoFormat({ myvariable: myvalue , anothervariable: anothervalue... }) + * toTagoFormat({ myvariable: { value: myvalue, unit: 'C', metadata: { color: 'green' }} , anothervariable: anothervalue... }) + * + * @param {Object} object_item Object containing key and value. + * @param {String} serie Serie for the variables + * @param {String} prefix Add a prefix to the variables name + */ +function toTagoFormat(object_item, serie, prefix = '') { + const result = []; + for (const key in object_item) { + if (ignore_vars.includes(key)) continue; + + if (typeof object_item[key] === 'object') { + result.push({ + variable: object_item[key].variable || `${prefix}${key}`, + value: object_item[key].value, + serie: object_item[key].serie || serie, + metadata: object_item[key].metadata, + location: object_item[key].location, + unit: object_item[key].unit, + }); + } else { + result.push({ + variable: `${prefix}${key}`, + value: object_item[key], + serie, + }); + } + } + + return result; +} + +// Function to convert decimal numbers to binary +function dec2bin(dec) { + const binary = (dec >>> 0).toString(2); + return '00000000'.substr(binary.length) + binary; +} + +// Decode an uplink message from an array of bytes to an object of fields +function Decoder(bytes, port) { + // Decode an uplink message from a buffer + // (array) of bytes to an object of fields. + const decoded = {}; + + if (bytes == null) { return null; } + + if (port === 100) { + // parse status + decoded.status = { + value: parseInt(dec2bin(bytes[0]).substr(7, 1), 2), + }; + + // parse battery voltage + decoded.battery_voltage = { + value: Number((25 + parseInt(dec2bin(bytes[1]).substr(0, 4), 2)) / 10).toFixed(1), + unit: 'V', + }; + + // parse battery capacity + decoded.battery_capacity = { + value: Number(100 * parseInt(dec2bin(bytes[1]).substr(4), 2) / 15).toFixed(1), + unit: '%', + }; + + // parse temperature + decoded.temperature = { + value: bytes.readUInt8(2) - 32, + unit: '°C', + }; + + decoded.time_elapsed = bytes.readUInt16LE(3); + + decoded.count = bytes.readUIntLE(5, 3); + } else { + return null; + } + + return decoded; +} + +let data = payload.find(x => x.variable === 'data' || x.variable === 'payload'); +let port = payload.find(x => x.variable === 'port'); +if (data && port) { + port = Number(port.value); + const serie = data.serie || new Date().getTime(); + data = data.value; + const vars_to_tago = Decoder(Buffer.from(data, 'hex'), port); + + payload = [...payload, ...toTagoFormat(vars_to_tago, serie)]; + payload = payload.filter(x => !ignore_vars.includes(x.variable)); +} diff --git a/decoders/connector/browan/healthy-home-sensor-iaq/assets/logo.png b/decoders/connector/browan/healthy-home-sensor-iaq/assets/logo.png new file mode 100644 index 00000000..dc01d89e Binary files /dev/null and b/decoders/connector/browan/healthy-home-sensor-iaq/assets/logo.png differ diff --git a/decoders/connector/browan/healthy-home-sensor-iaq/connector.jsonc b/decoders/connector/browan/healthy-home-sensor-iaq/connector.jsonc new file mode 100644 index 00000000..a90e8f24 --- /dev/null +++ b/decoders/connector/browan/healthy-home-sensor-iaq/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "Browan Healthy Home Sensor IAQ", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/browan/healthy-home-sensor-iaq/description.md b/decoders/connector/browan/healthy-home-sensor-iaq/description.md new file mode 100644 index 00000000..87300f7b --- /dev/null +++ b/decoders/connector/browan/healthy-home-sensor-iaq/description.md @@ -0,0 +1 @@ +Temperature, Relative Humidity and Volatile Organic Compound level over LoRaWAN™ \ No newline at end of file diff --git a/decoders/connector/browan/healthy-home-sensor-iaq/v1.0.0/payload-config.jsonc b/decoders/connector/browan/healthy-home-sensor-iaq/v1.0.0/payload-config.jsonc new file mode 100644 index 00000000..46b20ccd --- /dev/null +++ b/decoders/connector/browan/healthy-home-sensor-iaq/v1.0.0/payload-config.jsonc @@ -0,0 +1,25 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "The Healthy Home Indoor Air Quality sensor utilizes LoRaWAN connectivity to communicate the Temperature, Relative Humidity and Volatile Organic Compound levels of the surrounding environment. The intended use is to place the sensor within a room to determine if the air quality, temperature, and humidity are ideal.\n\n**Specifications**\n* Dimensions: 50 x 20 x 50 mm\n* Weight: 30g without battery / 40g with battery\n* Sensors: Temperature & Relative Humidity, Indoor Air Quality\n* Operating Temperature: 0°C to +50°C\n* IP Rating: IP 40 equivalent\n* Frequency: 863-870MHz for EU / 902-928MHz for North America\n* Tx Power: US: +19dBm / EU: +17dBm\n* Rx Sensitivity: -135dBm\n* Antenna Gain: -2dBi Peak, -5dBi Avg\n* Power Source: 3.6V 1/2 AA Li-SOCl2 1200mAh battery\n", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-loriot-/v1.0.0/payload.js", + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-brdot-/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/browan/healthy-home-sensor-iaq/v1.0.0/payload.js b/decoders/connector/browan/healthy-home-sensor-iaq/v1.0.0/payload.js new file mode 100644 index 00000000..1a4eef73 --- /dev/null +++ b/decoders/connector/browan/healthy-home-sensor-iaq/v1.0.0/payload.js @@ -0,0 +1,46 @@ +function Decoder(bytes, port) { + const decoded = []; + // status message + if (port !== 103) return [{ variable: "parse_error", value: "Parser error: Port must be 103" }]; + if (bytes.length !== 11) return [{ variable: "parse_error", value: "Parser error: Bytes length doesn't match" }]; + // status -- byte 0 + decoded.push({ variable: "status_event", value: bytes[0] & 0x01 }); + decoded.push({ variable: "status_type", value: ((bytes[0] >> 3) & 0x01) ? "Temperature and Humidity sensor" : "IAQ Sensor" }); + decoded.push({ variable: "status_temperature_change", value: (bytes[0] >> 4) & 0x01 }); + decoded.push({ variable: "status_humidity_change", value: (bytes[0] >> 5) & 0x01 }); + decoded.push({ variable: "status_iaq_change", value: (bytes[0] >> 6) & 0x01 }); + // battery -- byte 1 (1 to 14) + if ([0, 15].includes(bytes[1] & 0x0F)) return [{ variable: "parse_error", value: "Parser error: Battery must be between 2.6 and 3.9 V" }]; + decoded.push({ variable: "battery", value: (25 + (bytes[1] & 0x0F)) / 10, unit: "V" }); + // temp (pcb) -- byte 2 + decoded.push({ variable: "board_temperature", value: (bytes[2] & 0x7F) - 32, unit: "°C" }); + // rh -- byte 3 (max 100) + if ((bytes[3] & 0x7F) > 100) return [{ variable: "parse_error", value: "Parser error: Max value for humidity is 100%" }]; + decoded.push({ variable: "humidity", value: bytes[3] & 0x7F, unit: "%" }); + // co2 -- bytes 4, 5 + decoded.push({ variable: "eco2", value: bytes.readUInt16BE(4), unit: "ppm" }); + // voc -- bytes 6, 7 + decoded.push({ variable: "voc", value: bytes.readUInt16BE(6), unit: "ppm" }); + // iaq -- bytes 8, 9 (0 to 500) + if (bytes.readUInt16BE(8) > 500) return [{ variable: "parse_error", value: "Parser error: Max value for IAQ is 500" }]; + decoded.push({ variable: "iaq", value: bytes.readUInt16BE(8) }); + // environment temp -- byte 10 + decoded.push({ variable: "environment_temperature", value: (bytes[10] & 0x7F) - 32, unit: "°C" }); + + + return decoded; +} + +// let payload = [{ variable: "payload", value: "790a2a5f2744274427442a" }, { variable: "port", value: "103" }]; +// let payload = [{ variable: "payload", value: "79ffffffffffffffffffff" }, { variable: "port", value: "103" }]; + +const data = payload.find((item) => item.variable === "data" || item.variable === "payload" || item.variable === "payload_raw" ); +const port = payload.find((item) => item.variable === "port" || item.variable === "fport" ); + +if (data) { + const bytes = Buffer.from(data.value, "hex"); + const serie = new Date().getTime(); + payload = payload.concat(Decoder(bytes, Number(port.value))).map((x) => ({ ...x, serie })); +} + +// console.log(payload); \ No newline at end of file diff --git a/decoders/connector/browan/industrial-tracker/assets/logo.png b/decoders/connector/browan/industrial-tracker/assets/logo.png new file mode 100644 index 00000000..f0e6bcf2 Binary files /dev/null and b/decoders/connector/browan/industrial-tracker/assets/logo.png differ diff --git a/decoders/connector/browan/industrial-tracker/connector.jsonc b/decoders/connector/browan/industrial-tracker/connector.jsonc new file mode 100644 index 00000000..8a347327 --- /dev/null +++ b/decoders/connector/browan/industrial-tracker/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "Browan Industrial Tracker", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/browan/industrial-tracker/description.md b/decoders/connector/browan/industrial-tracker/description.md new file mode 100644 index 00000000..8604c3f4 --- /dev/null +++ b/decoders/connector/browan/industrial-tracker/description.md @@ -0,0 +1 @@ +Tracking with GPS and 3-axis accelerometer over LoRaWAN™ \ No newline at end of file diff --git a/decoders/connector/browan/industrial-tracker/v1.0.0/payload-config.jsonc b/decoders/connector/browan/industrial-tracker/v1.0.0/payload-config.jsonc new file mode 100644 index 00000000..231fdbe7 --- /dev/null +++ b/decoders/connector/browan/industrial-tracker/v1.0.0/payload-config.jsonc @@ -0,0 +1,25 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "The Industrial Tracker is a general-purpose tracker, designed for GPS tracking on various applications: bicycles, cars, or pets. It is equipped with GPS and 3-axis accelerometer, that provides a much more cost-effective way for service providers to deploy this for tracking applications than to use the GPRS network. \n\n**Specifications**\n* Size: 107 x 52 x 27 mm\n* Weight: 35g without battery, 45g with battery\n* Sensors: GNSS module, 3D MEMs accelerometer, Hall-effect\n* Temperature: -20°C to +70°C\n* IP Rating: IP 66 equivalent\n* Power Source: 3.6V 1/2 AA Li/SOCl2 1200 mAh battery (2)\n* Frequency: 863-870MHz for EU, 902-928MHz for North America\n* Tx Power: +19dBm conducted\n* Rx Sensitivity: -138dBm conducted\n* Antenna Gain: 0dBi Peak, -3dBi Avg", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-loriot-/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js", + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-brdot-/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/browan/industrial-tracker/v1.0.0/payload.js b/decoders/connector/browan/industrial-tracker/v1.0.0/payload.js new file mode 100644 index 00000000..3e277ffc --- /dev/null +++ b/decoders/connector/browan/industrial-tracker/v1.0.0/payload.js @@ -0,0 +1,80 @@ +/* eslint-disable no-bitwise */ +function signed_convert(val, bitwidth) { + const isnegative = val & (1 << (bitwidth - 1)); + const boundary = 1 << bitwidth; + const minval = -boundary; + const mask = boundary - 1; + return isnegative ? minval + (val & mask) : val; +} +function Decoder(bytes, port) { + const decoded = []; + + if (port === 136) { + decoded.push({ variable: "moving_mode", value: bytes[0] & 1 }); + decoded.push({ variable: "no_gnss_fix", value: !!((bytes[0] & 0x8) >> 3) }); + decoded.push({ + variable: "temperature", + value: (bytes.readUInt8(2) & 0x7f) - 32, + unit: "°C", + }); + + const battery = bytes[1]; + // const capacity = battery >> 4; + const voltage = battery & 0x0f; + decoded.push({ + variable: "battery", + value: (25 + voltage) / 10, + unit: "V", + }); + + let accuracy = bytes[10] >> 5; + accuracy = Math.pow(2, parseInt(accuracy) + 2); + + decoded.push({ variable: "accuracy", value: accuracy, unit: "m" }); + + // Mask off end of accuracy byte, so lon doesn't get affected + bytes[10] &= 0x1f; + + if ((bytes[10] & (1 << 4)) !== 0) { + bytes[10] |= 0xe0; + } + + // Mask off end of lat byte, RFU + //bytes[6] &= 0x0f; + + let lat = (bytes[6] << 24) | (bytes[5] << 16) | (bytes[4] << 8) | bytes[3]; + let lon = (bytes[10] << 24) | (bytes[9] << 16) | (bytes[8] << 8) | bytes[7]; + + if (lat > 134217727) { + lat = lat - 268435456; + } + + lat /= 1000000; + lon /= 1000000; + + decoded.push({ + variable: "location", + value: `${lat},${lon}`, + location: { lat, lng: lon }, + }); + + return decoded; + } +} + +const data = payload.find( + (x) => + x.variable === "payload" || + x.variable === "payload_raw" || + x.variable === "data" +); +const port = payload.find( + (x) => x.variable === "port" || x.variable === "fport" +); +if (data && port) { + const serie = data.serie || new Date().getTime(); + const bytes = Buffer.from(data.value, "hex"); + payload = payload + .concat(Decoder(bytes, Number(port.value))) + .map((x) => ({ ...x, serie })); +} diff --git a/decoders/connector/browan/motion-sensor/assets/logo.png b/decoders/connector/browan/motion-sensor/assets/logo.png new file mode 100644 index 00000000..e2ffa98f Binary files /dev/null and b/decoders/connector/browan/motion-sensor/assets/logo.png differ diff --git a/decoders/connector/browan/motion-sensor/connector.jsonc b/decoders/connector/browan/motion-sensor/connector.jsonc new file mode 100644 index 00000000..15b102bb --- /dev/null +++ b/decoders/connector/browan/motion-sensor/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "Browan Motion Sensor", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/browan/motion-sensor/description.md b/decoders/connector/browan/motion-sensor/description.md new file mode 100644 index 00000000..cbc908d7 --- /dev/null +++ b/decoders/connector/browan/motion-sensor/description.md @@ -0,0 +1 @@ +Tabs Motion Sensor to communicate the presence or not of a person over LoRaWAN™ \ No newline at end of file diff --git a/decoders/connector/browan/motion-sensor/v1.0.0/payload-config.jsonc b/decoders/connector/browan/motion-sensor/v1.0.0/payload-config.jsonc new file mode 100644 index 00000000..71cb278b --- /dev/null +++ b/decoders/connector/browan/motion-sensor/v1.0.0/payload-config.jsonc @@ -0,0 +1,25 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "**PIR & Occupancy Sensor**\n\n\n• EU, US, and Asia frequency supported\n\n\n• LoRaWAN 1.0.3\n\n\n• Dual Passive Infrared detectors and Fresnel Lens\n\n\n• 7m detection range\n\n\n• IP50 equivalent\n\n\n• 123°H x 93°V detection angle", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-loriot-/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js", + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-brdot-/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/browan/motion-sensor/v1.0.0/payload.js b/decoders/connector/browan/motion-sensor/v1.0.0/payload.js new file mode 100644 index 00000000..54fdc67c --- /dev/null +++ b/decoders/connector/browan/motion-sensor/v1.0.0/payload.js @@ -0,0 +1,113 @@ +/* eslint-disable camelcase */ +/* This is an example code for Everynet Parser. +** Everynet send several parameters to TagoIO. The job of this parse is to convert all these parameters into a TagoIO format. +** One of these parameters is the payload of your device. We find it too and apply the appropriate sensor parse. +** +** IMPORTANT: In most case, you will only need to edit the parsePayload function. +** +** Testing: +** You can do manual tests to this parse by using the Device Emulator. Copy and Paste the following code: +** [{ "variable": "everynet_payload", "value": "{ \"params\": { \"payload\": \"0109611395\" } }" }] +** +** The ignore_vars variable in this code should be used to ignore variables +** from the device that you don't want. +*/ +// Add ignorable variables in this array. +const ignore_vars = []; + + +/** + * Convert an object to TagoIO object format. + * Can be used in two ways: + * toTagoFormat({ myvariable: myvalue , anothervariable: anothervalue... }) + * toTagoFormat({ myvariable: { value: myvalue, unit: 'C', metadata: { color: 'green' }} , anothervariable: anothervalue... }) + * + * @param {Object} object_item Object containing key and value. + * @param {String} serie Serie for the variables + * @param {String} prefix Add a prefix to the variables name + */ +function toTagoFormat(object_item, serie, prefix = '') { + const result = []; + for (const key in object_item) { + if (ignore_vars.includes(key)) continue; + + if (typeof object_item[key] === 'object') { + result.push({ + variable: object_item[key].variable || `${prefix}${key}`, + value: object_item[key].value, + serie: object_item[key].serie || serie, + metadata: object_item[key].metadata, + location: object_item[key].location, + unit: object_item[key].unit, + }); + } else { + result.push({ + variable: `${prefix}${key}`, + value: object_item[key], + serie, + }); + } + } + + return result; +} + +// Function to convert decimal numbers to binary +function dec2bin(dec) { + const binary = (dec >>> 0).toString(2); + return '00000000'.substr(binary.length) + binary; +} + +// Decode an uplink message from an array of bytes to an object of fields +function Decoder(bytes, port) { + // Decode an uplink message from a buffer + // (array) of bytes to an object of fields. + const decoded = {}; + + if (bytes == null) { return null; } + + if (port === 102) { + // parse status + decoded.status = { + value: parseInt(dec2bin(bytes[0]).substr(7, 1), 2), + }; + + // parse battery voltage + decoded.battery_voltage = { + value: Number((25 + parseInt(dec2bin(bytes[1]).substr(0, 4), 2)) / 10).toFixed(1), + unit: 'V', + }; + + // parse battery capacity + decoded.battery_capacity = { + value: Number(100 * parseInt(dec2bin(bytes[1]).substr(4), 2) / 15).toFixed(1), + unit: '%', + }; + + // parse temperature + decoded.temperature = { + value: bytes.readUInt8(2) - 32, + unit: '°C', + }; + + decoded.time_elapsed = bytes.readUInt16LE(3); + + decoded.count = bytes.readUIntLE(5, 3); + } else { + return null; + } + + return decoded; +} + +let data = payload.find(x => x.variable === 'data' || x.variable === 'payload'); +let port = payload.find(x => x.variable === 'port'); +if (data && port) { + port = Number(port.value); + const serie = data.serie || new Date().getTime(); + data = data.value; + const vars_to_tago = Decoder(Buffer.from(data, 'hex'), port); + + payload = [...payload, ...toTagoFormat(vars_to_tago, serie)]; + payload = payload.filter(x => !ignore_vars.includes(x.variable)); +} diff --git a/decoders/connector/browan/object-locator/assets/logo.png b/decoders/connector/browan/object-locator/assets/logo.png new file mode 100644 index 00000000..01c25cc2 Binary files /dev/null and b/decoders/connector/browan/object-locator/assets/logo.png differ diff --git a/decoders/connector/browan/object-locator/connector.jsonc b/decoders/connector/browan/object-locator/connector.jsonc new file mode 100644 index 00000000..d2aaee0d --- /dev/null +++ b/decoders/connector/browan/object-locator/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "Browan Object Locator", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/browan/object-locator/description.md b/decoders/connector/browan/object-locator/description.md new file mode 100644 index 00000000..78d73f2e --- /dev/null +++ b/decoders/connector/browan/object-locator/description.md @@ -0,0 +1 @@ +Object Locator over LoRaWAN™ \ No newline at end of file diff --git a/decoders/connector/browan/object-locator/v1.0.0/payload-config.jsonc b/decoders/connector/browan/object-locator/v1.0.0/payload-config.jsonc new file mode 100644 index 00000000..1b0a3275 --- /dev/null +++ b/decoders/connector/browan/object-locator/v1.0.0/payload-config.jsonc @@ -0,0 +1,25 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "The Tabs Object Locator utilizes LoRaWAN connectivity to communicate the location of the device. The intended use is to attach the sensor to an object like a backpack or a purse to be able to remotely know its location. The sensor is composed of a GNSS receive, a push button, an LED indicator, and a USB-C connector. The device contains a LiPo battery that can be recharged through the USB-C connector. \n\n**Specifications**\n* Size: 50 x 13 x 50mm\n* Weight: 28g\n* Sensors: GNSS, 3D MEMs accelerometer, Push Button\n* Temperature: 0°C to +50°C\n* IP Rating: IP 64 equivalent\n* Power Source: 4.2V LiPo 540mAh battery\n* Frequency: 863-870MHz for EU, 902-928MHz for North America\n* Tx Power: +19dBm conducted\n* Rx Sensitivity: -140dBm conducted\n* Antenna Gain: -5dBi Peak, -8dBi Avg", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-loriot-/v1.0.0/payload.js", + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-brdot-/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/browan/object-locator/v1.0.0/payload.js b/decoders/connector/browan/object-locator/v1.0.0/payload.js new file mode 100644 index 00000000..157b00e2 --- /dev/null +++ b/decoders/connector/browan/object-locator/v1.0.0/payload.js @@ -0,0 +1,56 @@ +/* eslint-disable no-bitwise */ +/* eslint-disable no-restricted-properties */ +function Decoder(bytes, port) { + const decoded = []; + if (port === 136) { + // STATUS + decoded.push({ variable: "button_trigger_event", value: bytes[0] & 1 }); + decoded.push({ variable: "no_gnss_fix", value: (bytes[0] & 0x0f) >> 3 }); + // BATTERY + const battery_level = bytes.readUInt8(1); + decoded.push({ variable: "battery", value: (25 + (battery_level & 0x0f)) / 10, unit: "V" }); + decoded.push({ variable: "remaining_battery_capacity", value: Number((100 * ((battery_level >> 4) / 15)).toPrecision(4)), unit: "%" }); + // TEMPERATURE + decoded.push({ variable: "temperature", value: (bytes.readUInt8(2) & 0x7f) - 32, unit: "°C" }); + + // LOCATION + // GNSS Fix? + let positionGnssFix = false; + if ((bytes[0] & 0x8) === 0) { + positionGnssFix = true; + } + + // Accuracy Measurement + let positionAccuracy = bytes[10] >> 5; + positionAccuracy = Math.pow(2, parseInt(positionAccuracy) + 2); + + // Mask off end of accuracy byte, so longitude doesn't get affected + bytes[10] &= 0x1f; + + if ((bytes[10] & (1 << 4)) !== 0) { + bytes[10] |= 0xe0; + } + + // Mask off end of latitude byte, RFU + bytes[6] &= 0x0f; + + // Latitude and Longitude Measurement + let positionLatitude = (bytes[6] << 24) | (bytes[5] << 16) | (bytes[4] << 8) | bytes[3]; + let positionLongitude = (bytes[10] << 24) | (bytes[9] << 16) | (bytes[8] << 8) | bytes[7]; + positionLatitude /= 1000000; + positionLongitude /= 1000000; + + decoded.push({ variable: "location", value: `${positionLatitude},${positionLongitude}`, location: { lat: positionLatitude, lng: positionLongitude } }); + decoded.push({ variable: "accuracy", value: positionAccuracy, unit: "m" }); + } + + return decoded; +} + +const data = payload.find((x) => x.variable === "payload" || x.variable === "payload_raw" || x.variable === "data"); +const port = payload.find((x) => x.variable === "port" || x.variable === "fport"); +if (data && port) { + const serie = data.serie || new Date().getTime(); + const bytes = Buffer.from(data.value, "hex"); + payload = payload.concat(Decoder(bytes, Number(port.value))).map((x) => ({ ...x, serie })); +} diff --git a/decoders/connector/browan/sound-level/assets/logo.png b/decoders/connector/browan/sound-level/assets/logo.png new file mode 100644 index 00000000..a6ae3915 Binary files /dev/null and b/decoders/connector/browan/sound-level/assets/logo.png differ diff --git a/decoders/connector/browan/sound-level/connector.jsonc b/decoders/connector/browan/sound-level/connector.jsonc new file mode 100644 index 00000000..fc3726f4 --- /dev/null +++ b/decoders/connector/browan/sound-level/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "Browan Sound Level", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/browan/sound-level/description.md b/decoders/connector/browan/sound-level/description.md new file mode 100644 index 00000000..a46cbacb --- /dev/null +++ b/decoders/connector/browan/sound-level/description.md @@ -0,0 +1 @@ +Sound Level in decibels over LoRaWAN™ \ No newline at end of file diff --git a/decoders/connector/browan/sound-level/v1.0.0/payload-config.jsonc b/decoders/connector/browan/sound-level/v1.0.0/payload-config.jsonc new file mode 100644 index 00000000..32854e11 --- /dev/null +++ b/decoders/connector/browan/sound-level/v1.0.0/payload-config.jsonc @@ -0,0 +1,25 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "The Sound Level Sensor utilizes LoRaWAN connectivity to provide to easily measure and investigate sound levels in decibels (dBA) in a variety of building environments.\n\n**Specifications**\n* Size: 50 x 20 x 50mm\n* Weight: 30g without battery, 40g with battery\n* Temperature: 0°C to +50°C\n* IP Rating: IP 40 equivalent\n* Power Source: 3.6V 1/2 AA Li/SOCl2 1200 mAh battery\n* Frequency: 863-870MHz for EU, 902-928MHz for North America\n* Tx Power: US: +19dBm, EU: +17dBm\n* Rx Sensitivity: -135dBm\n* Antenna Gain: -2dBi Peak, -5dBi Avg", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-loriot-/v1.0.0/payload.js", + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-brdot-/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/browan/sound-level/v1.0.0/payload.js b/decoders/connector/browan/sound-level/v1.0.0/payload.js new file mode 100644 index 00000000..eca97618 --- /dev/null +++ b/decoders/connector/browan/sound-level/v1.0.0/payload.js @@ -0,0 +1,24 @@ +function Decoder(bytes, port) { + const decoded = []; + if (port === 105) { + // STATUS + decoded.push({ variable: "threshold_event", value: bytes[0] & 1 }); + // BATTERY + const battery_level = bytes.readUInt8(1); + decoded.push({ variable: "battery", value: (25 + (battery_level & 0x0f)) / 10, unit: "V" }); + // TEMPERATURE + decoded.push({ variable: "temperature", value: (bytes.readUInt8(2) & 0x7f) - 32, unit: "°C" }); + // DECIBEL + decoded.push({ variable: "decibel", value: bytes.readUInt8(3), unit: "dBA" }); + } + + return decoded; +} + +const data = payload.find((x) => x.variable === "payload" || x.variable === "payload_raw" || x.variable === "data"); +const port = payload.find((x) => x.variable === "port" || x.variable === "fport"); +if (data && port) { + const serie = data.serie || new Date().getTime(); + const bytes = Buffer.from(data.value, "hex"); + payload = payload.concat(Decoder(bytes, Number(port.value))).map((x) => ({ ...x, serie })); +} diff --git a/decoders/connector/browan/temperature-humidity-sensor/assets/logo.png b/decoders/connector/browan/temperature-humidity-sensor/assets/logo.png new file mode 100644 index 00000000..b646a020 Binary files /dev/null and b/decoders/connector/browan/temperature-humidity-sensor/assets/logo.png differ diff --git a/decoders/connector/browan/temperature-humidity-sensor/connector.jsonc b/decoders/connector/browan/temperature-humidity-sensor/connector.jsonc new file mode 100644 index 00000000..f3974792 --- /dev/null +++ b/decoders/connector/browan/temperature-humidity-sensor/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "Browan Temperature & Humidity Sensor", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/browan/temperature-humidity-sensor/description.md b/decoders/connector/browan/temperature-humidity-sensor/description.md new file mode 100644 index 00000000..74d29316 --- /dev/null +++ b/decoders/connector/browan/temperature-humidity-sensor/description.md @@ -0,0 +1 @@ +Tabs Temperature & humidity sensor designed for in-home and in-building over LoRaWAN™ \ No newline at end of file diff --git a/decoders/connector/browan/temperature-humidity-sensor/v1.0.0/payload-config.jsonc b/decoders/connector/browan/temperature-humidity-sensor/v1.0.0/payload-config.jsonc new file mode 100644 index 00000000..2e93dd87 --- /dev/null +++ b/decoders/connector/browan/temperature-humidity-sensor/v1.0.0/payload-config.jsonc @@ -0,0 +1,25 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "**Temperature & Humidity Sensor**\n\n• EU, US, and Asia frequency supported\n\n• LoRaWAN 1.0.3\n\n• Temperature Accuracy: +/- 1°C\n\n• Humidity Accuracy: +/- 5%\n\n• IP40 equivalent\n\n• 0 to 50°C operation temperature range", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-loriot-/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js", + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-brdot-/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/browan/temperature-humidity-sensor/v1.0.0/payload.js b/decoders/connector/browan/temperature-humidity-sensor/v1.0.0/payload.js new file mode 100644 index 00000000..f3a4e572 --- /dev/null +++ b/decoders/connector/browan/temperature-humidity-sensor/v1.0.0/payload.js @@ -0,0 +1,102 @@ +/* eslint-disable camelcase */ +/* This is an example code for Everynet Parser. +** Everynet send several parameters to TagoIO. The job of this parse is to convert all these parameters into a TagoIO format. +** One of these parameters is the payload of your device. We find it too and apply the appropriate sensor parse. +** +** IMPORTANT: In most case, you will only need to edit the parsePayload function. +** +** Testing: +** You can do manual tests to this parse by using the Device Emulator. Copy and Paste the following code: +** [{ "variable": "everynet_payload", "value": "{ \"params\": { \"payload\": \"0109611395\" } }" }] +** +** The ignore_vars variable in this code should be used to ignore variables +** from the device that you don't want. +*/ +// Add ignorable variables in this array. +const ignore_vars = []; + + +/** + * Convert an object to TagoIO object format. + * Can be used in two ways: + * toTagoFormat({ myvariable: myvalue , anothervariable: anothervalue... }) + * toTagoFormat({ myvariable: { value: myvalue, unit: 'C', metadata: { color: 'green' }} , anothervariable: anothervalue... }) + * + * @param {Object} object_item Object containing key and value. + * @param {String} serie Serie for the variables + * @param {String} prefix Add a prefix to the variables name + */ +function toTagoFormat(object_item, serie, prefix = '') { + const result = []; + for (const key in object_item) { + if (ignore_vars.includes(key)) continue; + + if (typeof object_item[key] === 'object') { + result.push({ + variable: object_item[key].variable || `${prefix}${key}`, + value: object_item[key].value, + serie: object_item[key].serie || serie, + metadata: object_item[key].metadata, + location: object_item[key].location, + unit: object_item[key].unit, + }); + } else { + result.push({ + variable: `${prefix}${key}`, + value: object_item[key], + serie, + }); + } + } + + return result; +} + +// Function to convert decimal numbers to binary +// Decode an uplink message from an array of bytes to an object of fields +function Decoder(bytes, port) { + // Decode an uplink message from a buffer + // (array) of bytes to an object of fields. + const decoded = {}; + + if (bytes == null) { return null; } + + // if (port === 103) { + // parse status + decoded.status = { + value: bytes.readUInt8(0), + }; + + // parse battery voltage + decoded.battery_voltage = { + value: Number( ((25 + ((bytes[1] & 0x0F) >>> 0)) / 10).toFixed(1)), + unit: 'V', + }; + + // parse temperature °C + decoded.temperature = { + value: ((bytes[2] & 0x7F) >>> 0) - 32, + unit: '°C', + }; + + // parse humidity + decoded.relative_humidity = { + value: ((bytes[3] & 0x7F) >>> 0), + unit: '%', + }; + + // decoded.co2 = bytes.readUInt16LE(4); + + // decoded.voc = bytes.readUInt16LE(6); + + return decoded; +} + +const data = payload.find(x => x.variable === 'data' || x.variable === 'payload'); +if (data) { + const serie = data.serie || new Date().getTime(); + const vars_to_tago = Decoder(Buffer.from(data.value, 'hex')); + + payload = [...payload, ...toTagoFormat(vars_to_tago, serie)]; + payload = payload.filter(x => !ignore_vars.includes(x.variable)); +} diff --git a/decoders/connector/browan/water-leak/assets/logo.png b/decoders/connector/browan/water-leak/assets/logo.png new file mode 100644 index 00000000..1da97d7e Binary files /dev/null and b/decoders/connector/browan/water-leak/assets/logo.png differ diff --git a/decoders/connector/browan/water-leak/connector.jsonc b/decoders/connector/browan/water-leak/connector.jsonc new file mode 100644 index 00000000..73128e5a --- /dev/null +++ b/decoders/connector/browan/water-leak/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "Browan Water Leak", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/browan/water-leak/description.md b/decoders/connector/browan/water-leak/description.md new file mode 100644 index 00000000..a628d169 --- /dev/null +++ b/decoders/connector/browan/water-leak/description.md @@ -0,0 +1 @@ +Water Leakage sensor over LoRaWAN™ \ No newline at end of file diff --git a/decoders/connector/browan/water-leak/v1.0.0/payload-config.jsonc b/decoders/connector/browan/water-leak/v1.0.0/payload-config.jsonc new file mode 100644 index 00000000..4078592e --- /dev/null +++ b/decoders/connector/browan/water-leak/v1.0.0/payload-config.jsonc @@ -0,0 +1,25 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "The Water Leakage Sensor utilizes LoRaWAN connectivity to sends an uplink notification when a water leak is detected.\n\n**Specifications**\n* Size: 50 x 20 x 50 mm\n* Weight: 30g without battery, 40g with battery\n* Sensors: Temperature, Humidity and Water leak detection\n* Temperature: 0°C to +50°C\n* IP Rating: IP 50 equivalent\n* Power Source: 3.6V 1/2 AA Li-SOCl2 1200mAh battery\n* Frequency: 863-870MHz for EU, 902-928MHz for North America\n* Tx Power: US: +19dBm, EU: +17dBm\n* Rx Sensitivity: -135dBm\n* Antenna Gain: -2dBi Peak, -5dBi Avg", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-loriot-/v1.0.0/payload.js", + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-brdot-/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/browan/water-leak/v1.0.0/payload.js b/decoders/connector/browan/water-leak/v1.0.0/payload.js new file mode 100644 index 00000000..dde6c586 --- /dev/null +++ b/decoders/connector/browan/water-leak/v1.0.0/payload.js @@ -0,0 +1,29 @@ +function Decoder(bytes, port) { + const decoded = []; + if (port === 106) { + // STATUS + decoded.push({ variable: "water_leakage_detected", value: bytes[0] & 1 }); + decoded.push({ variable: "water_leakage_interrupt", value: (bytes[0] >> 4) & 1 }); + decoded.push({ variable: "temperature_changed", value: (bytes[0] >> 5) & 1 }); + decoded.push({ variable: "humidity_changed", value: (bytes[0] >> 6) & 1 }); + // BATTERY + const battery_level = bytes.readUInt8(1); + decoded.push({ variable: "battery", value: (25 + (battery_level & 0x0f)) / 10, unit: "V" }); + // TEMPERATURE (PCB) + decoded.push({ variable: "temperature_pcb", value: (bytes.readUInt8(2) & 0x7f) - 32, unit: "°C" }); + // RH + decoded.push({ variable: "humidity", value: bytes.readUInt8(3) & 0x7f, unit: "%" }); + // TEMPERATURE (ENVIRONMENT) + decoded.push({ variable: "temperature_environment", value: (bytes.readUInt8(4) & 0x7f) - 32, unit: "°C" }); + } + + return decoded; +} + +const data = payload.find((x) => x.variable === "payload" || x.variable === "payload_raw" || x.variable === "data"); +const port = payload.find((x) => x.variable === "port" || x.variable === "fport"); +if (data && port) { + const serie = data.serie || new Date().getTime(); + const bytes = Buffer.from(data.value, "hex"); + payload = payload.concat(Decoder(bytes, Number(port.value))).map((x) => ({ ...x, serie })); +} diff --git a/decoders/connector/digital-matter/matter-falcon/assets/logo.png b/decoders/connector/digital-matter/matter-falcon/assets/logo.png new file mode 100644 index 00000000..4a807ae2 Binary files /dev/null and b/decoders/connector/digital-matter/matter-falcon/assets/logo.png differ diff --git a/decoders/connector/digital-matter/matter-falcon/connector.jsonc b/decoders/connector/digital-matter/matter-falcon/connector.jsonc new file mode 100644 index 00000000..aff3ee2e --- /dev/null +++ b/decoders/connector/digital-matter/matter-falcon/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "Digital Matter Falcon", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/digital-matter/matter-falcon/description.md b/decoders/connector/digital-matter/matter-falcon/description.md new file mode 100644 index 00000000..1bc4a4cf --- /dev/null +++ b/decoders/connector/digital-matter/matter-falcon/description.md @@ -0,0 +1 @@ +Battery-powered or wired GPS tracking device with inputs/outputs over HTTP. \ No newline at end of file diff --git a/decoders/connector/digital-matter/matter-falcon/v1.0.0/payload-config.jsonc b/decoders/connector/digital-matter/matter-falcon/v1.0.0/payload-config.jsonc new file mode 100644 index 00000000..c201753d --- /dev/null +++ b/decoders/connector/digital-matter/matter-falcon/v1.0.0/payload-config.jsonc @@ -0,0 +1,11 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "Description:\n* Robust battery-powered or wired GPS tracking device with inputs/outputs, I²C Sensor Interface, and WiFi Positioning for indoor and outdoor asset tracking and sensor monitoring.\n\nFeatures:\n* High-precision GPS/GLONASS tracking device with WiFi Access Point Scanning.\n* Flexible Power Options: 3 x AA Batteries with up to 7 years battery life or wired to permanent power.\n* 1 x Analog Input, 2 x Digital Inputs, 1 x Switched Ground Digital Output, 1 x Ignition Digital Input, Switched Power Out.\n* I²C Sensor Interface.\n* Weatherproof and ultra-rugged IP67 Housing.\n* Uses Cellular 2G or LTE-M / NB-IoT.\n* Built-in Battery Meter for monitoring battery use and remaining life predictions.\n* Configure impact-detection alerts when g-forces are exceeded by a user-defined threshold.\n* Stationary devices enter sleep mode until movement occurs to conserve battery life and optimize data usage.\n\nTech-specs:\n* https://www.digitalmatter.com/devices/falcon/tech-specs/", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/https/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/digital-matter/matter-falcon/v1.0.0/payload.js b/decoders/connector/digital-matter/matter-falcon/v1.0.0/payload.js new file mode 100644 index 00000000..b1aea44f --- /dev/null +++ b/decoders/connector/digital-matter/matter-falcon/v1.0.0/payload.js @@ -0,0 +1,199 @@ +/* + * TagoIO Decoders - (https://tago.io/) + * ------------------- + * Generated by :: frederico + * Generated at :: Wed Feb 01 2023 15:37:24 GMT+0000 (Coordinated Universal Time) + * Machine :: frederico-desktop - Node.js v16.18.0 + * ------------------- +*/ + +function JsonDecoder(payload1) { + const result = [ + { + variable: "serial_number", + value: payload1.SerNo + }, + { + variable: "imei", + value: payload1.IMEI + }, + { + variable: "iccid", + value: payload1.ICCID + }, + { + variable: "prodid", + value: payload1.ProdId + }, + { + variable: "fw", + value: payload1.FW + } + ]; + for (const record of payload1.Records || []){ + const time = new Date(record.DateUTC); + const group = String(record.SeqNo); + result.push({ + variable: "sequence_number", + value: record.SeqNo, + group, + time + }, { + variable: "reason", + value: record.Reason, + group, + time + }, { + variable: "date_utc", + value: record.DateUTC, + group, + time + }, { + variable: "gps_utc", + value: record?.Fields?.[0].GpsUTC, + group, + time, + unit: "GPS datetime" + }, { + variable: "location", + value: `${String(record.Fields?.[0].Lat)},${String(record.Fields?.[0].Long)}`, + unit: "Degrees * 1E7", + location: { + lat: record?.Fields?.[0].Lat, + lng: record.Fields?.[0].Long + }, + group, + time + }, { + variable: "alt", + value: record?.Fields?.[0].Alt, + group, + time, + unit: "m" + }, { + variable: "speed", + value: record.Fields?.[0].Spd, + group, + time, + unit: "Cm/s" + }); + if (record.Fields?.[0]?.SpdAcc != undefined) { + result.push({ + variable: "spdacc", + value: record.Fields?.[0]?.SpdAcc * 10, + group, + time, + unit: "Cm/s" + }); + } + if (record.Fields?.[0].Head != undefined) { + result.push({ + variable: "head", + value: record.Fields?.[0].Head * 2, + group, + time, + unit: "Deg" + }); + } + result.push({ + variable: "pdop", + value: record.Fields?.[0].PDOP, + group, + time, + unit: "x10" + }, { + variable: "posacc", + value: record.Fields?.[0].PosAcc, + group, + time, + unit: "m" + }, { + variable: "gpsstat", + value: record.Fields?.[0].GpsStat, + group, + time + }, { + variable: "din", + value: record.Fields?.[1].DIn, + group, + time + }, { + variable: "dout", + value: record.Fields?.[1].DOut, + group, + time + }, { + variable: "devstat", + value: record.Fields?.[1].DevStat, + group, + time + }); + if (record.Fields?.[2].AnalogueData?.["2"] != undefined) { + result.push({ + variable: "external_voltage", + value: record.Fields?.[2].AnalogueData?.["2"] / 100, + group, + time, + unit: "V" + }); + } + result.push({ + variable: "internal_voltage", + value: record.Fields?.[2].AnalogueData?.["1"], + group, + time, + unit: "mV" + }); + if (record.Fields?.[2].AnalogueData?.["3"] != undefined) { + result.push({ + variable: "temperature", + value: record.Fields?.[2].AnalogueData?.["3"] / 100, + group, + time, + unit: "Deg C" + }); + } + result.push({ + variable: "last_known_gsm", + value: record.Fields?.[2].AnalogueData?.["4"], + group, + time + }, { + variable: "loaded_voltage", + value: record.Fields?.[2].AnalogueData?.["5"], + group, + time, + unit: "mV" + }); + if (record.Fields?.[2].AnalogueData?.["6"] != undefined) { + result.push({ + variable: "battery", + value: record.Fields?.[2].AnalogueData?.["6"] / 10, + group, + time, + unit: "%" + }); + } + } + const filtered = result.filter((x)=>x.value !== undefined); + return filtered; +} + +console.log(`Payload received: ${JSON.stringify(payload)}`); + +try { + payload = JsonDecoder(payload[0]); +} catch (error) { + // Print the error to the Live Inspector. + console.error(error); + // Return the variable parse_error for debugging. + payload = [ + { + variable: "parse_error", + value: error.message + } + ]; +} + + +//#sourceMappingURL=data:application/json;charset=utf-8;base64,IntcInZlcnNpb25cIjozLFwic291cmNlc1wiOltdLFwibmFtZXNcIjpbXSxcIm1hcHBpbmdzXCI6XCJcIixcImZpbGVcIjpcInN0ZG91dFwifSI= \ No newline at end of file diff --git a/decoders/connector/digital-matter/matter-yabby-edge/assets/logo.png b/decoders/connector/digital-matter/matter-yabby-edge/assets/logo.png new file mode 100644 index 00000000..d892cd2e Binary files /dev/null and b/decoders/connector/digital-matter/matter-yabby-edge/assets/logo.png differ diff --git a/decoders/connector/digital-matter/matter-yabby-edge/connector.jsonc b/decoders/connector/digital-matter/matter-yabby-edge/connector.jsonc new file mode 100644 index 00000000..31f039c4 --- /dev/null +++ b/decoders/connector/digital-matter/matter-yabby-edge/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "Digital Matter Yabby Edge", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/digital-matter/matter-yabby-edge/description.md b/decoders/connector/digital-matter/matter-yabby-edge/description.md new file mode 100644 index 00000000..b53eec53 --- /dev/null +++ b/decoders/connector/digital-matter/matter-yabby-edge/description.md @@ -0,0 +1 @@ +Asset Tracker with LR1110 over LoRaWAN™ \ No newline at end of file diff --git a/decoders/connector/digital-matter/matter-yabby-edge/v1.0.0/payload-config.jsonc b/decoders/connector/digital-matter/matter-yabby-edge/v1.0.0/payload-config.jsonc new file mode 100644 index 00000000..88d7dcd6 --- /dev/null +++ b/decoders/connector/digital-matter/matter-yabby-edge/v1.0.0/payload-config.jsonc @@ -0,0 +1,25 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "Low cost, Indoor and Outdoor asset tracker with industry-leading battery life in a small rugged IP67 housing. Features Semtech’s latest geolocation solution the LR1110.\n\n**Specifications**\n* LoRaWAN Regions: AU915, AS923-1, AS923-2, AS923-3, EU868, IN865, KR920, RU864, US915\n* Battery: 2 x AAA\n* Battery Life: Up to 12 years of battery life at once-daily position updates. 3 years battery life at once-hourly position updates.\n* Dimensions: Standard - 85 x 63 x 24 mm. Livestock Collar - 109 x 60 x 30 mm. Snap Housing - 75 x 45 x 25 mm\n* Weight: Standard - 82g\n* Housing: Ultra-Rugged IP67 Housing\n* Operating Temperature: -20°C to +60°C", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-loriot-/v1.0.0/payload.js", + "../../../../network/lorawan-brdot-/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/digital-matter/matter-yabby-edge/v1.0.0/payload.js b/decoders/connector/digital-matter/matter-yabby-edge/v1.0.0/payload.js new file mode 100644 index 00000000..6619733c --- /dev/null +++ b/decoders/connector/digital-matter/matter-yabby-edge/v1.0.0/payload.js @@ -0,0 +1,233 @@ +/* + Port 1: Hello Message + Firmware major version + Firmware minor version + Product ID + Hardware revision + Power on reset + Watchdog reset + External reset + Software reset + RFU + Watchdog reset code + Battery voltage + LR1110 hardware revision + LR1110 firmware major version + LR1110 firmware minor version + Port 2: Downlink ACK + Sequence Number + Accept + Firmware major version + Firmware minor version + Product ID + Hardware revision + Downlink port number + LR1110 hardware revision + LR1110 firmware major version + LR1110 firmware minor version + Port 3: Stats message + Initial battery voltage + Current battery voltage + wakeups per trip + trip count + uptime in weeks + battery used + percentage used on LoRaWAN + percentage used on GNSS + percentage used on WiFi + percentage used sleeping + percentage used on battery self discharge + Port 5: Location message + Number of WiFi access points N (0-31) + In-trip + Inactivity Indicator + RFU + Timestamp in NAV message (if present) + position assistance sequence number + Access point 1, RSSI, in dBm (signed) + Access point 1, MAC address + Access point 2, RSSI, in dBm (signed) + Access point 2, MAC address + Further access points + Semtech format GNSS NAV message (optional) +*/ + +function Decoder(bytes, port) { + const decoded = []; + if (port === 1) { + // FIRMWARE VERSION + // Firmware major version + const major = bytes.readUInt8(0); + // Firmware minor version + const minor = bytes.readUInt8(1); + decoded.push({ variable: "firmware_version", value: `${major}.${minor}` }); + // Product ID + const prod_id = bytes.readUInt8(2); + decoded.push({ variable: "product_id", value: prod_id }); + // Hardware revision + const hw_rev = bytes.readUInt8(3); + decoded.push({ variable: "hardware_revision", value: hw_rev }); + // RESETS + const resets = bytes.readUInt8(4); + // Power on reset + const power_on_reset = resets & 1; + decoded.push({ variable: "power_on_reset", value: power_on_reset }); + // Watchdog reset + const watchdog_reset = (resets >> 1) & 1; + decoded.push({ variable: "watchdog_reset", value: watchdog_reset }); + // External reset + const external_reset = (resets >> 2) & 1; + decoded.push({ variable: "external_reset", value: external_reset }); + // Software reset + const sw_reset = (resets >> 3) & 1; + decoded.push({ variable: "software_reset", value: sw_reset }); + // RFU (resets -- bit 4 to bit 7. Beginning at 0) + // Watchdog reset code + const watchdog_reset_code = bytes.readUInt16LE(5); + decoded.push({ variable: "watchdog_reset_code", value: watchdog_reset_code }); + // Battery voltage + const battery = bytes.readUInt8(7); + decoded.push({ variable: "battery", value: 2000 + 7 * battery, unit: "mV" }); + // LR1110 hardware revision + const lr1110_hw_rev = bytes.readUInt8(8); + decoded.push({ variable: "lr1110_hardware_revision", value: lr1110_hw_rev.toString(16) }); + // LR1110 FIRMWARE VERSION + // LR1110 firmware major + const lr1110_fw_major_version = bytes.readUInt8(9); + // LR1110 firmware minor + const lr1110_fw_minor_version = bytes.readUInt8(10); + decoded.push({ variable: "lr1110_firmware", value: `${lr1110_fw_major_version}.${lr1110_fw_minor_version}` }); + } else if (port === 2) { + // DOWNLINK + const downlink = bytes.readUInt8(0); + // Sequence Number + const sequence_number = downlink & 0x7f; + decoded.push({ variable: "downlink_sequence_number", value: sequence_number }); + // Accept + const accept = downlink >> 7; + decoded.push({ variable: "downlink_accepted", value: accept }); + // FIRMWARE VERSION + // Firmware major version + const major = bytes.readUInt8(1); + // Firmware minor version + const minor = bytes.readUInt8(2); + decoded.push({ variable: "firmware_version", value: `${major}.${minor}` }); + // Product ID + const prod_id = bytes.readUInt8(3); + decoded.push({ variable: "product_id", value: prod_id }); + // Hardware revision + const hw_rev = bytes.readUInt8(4); + decoded.push({ variable: "hardware_revision", value: hw_rev }); + // Downlink port number + const downlink_port = bytes.readUInt8(5); + decoded.push({ variable: "downlink_port_number", value: downlink_port }); + // LR1110 hardware revision + const lr1110_hw_rev = bytes.readUInt8(6); + decoded.push({ variable: "lr1110_hardware_revision", value: lr1110_hw_rev.toString(16) }); + // LR1110 FIRMWARE VERSION + // LR1110 firmware major + const lr1110_fw_major_version = bytes.readUInt8(7); + // LR1110 firmware minor + const lr1110_fw_minor_version = bytes.readUInt8(8); + decoded.push({ variable: "lr1110_firmware", value: `${lr1110_fw_major_version}.${lr1110_fw_minor_version}` }); + } else if (port === 3) { + // Initial battery voltage + const initial_battery = bytes.readUInt8(0); + decoded.push({ variable: "initial_battery", value: 2000 + 7 * initial_battery, unit: "mV" }); + // Current battery voltage + const curr_battery = bytes.readUInt8(1); + decoded.push({ variable: "current_battery", value: 2000 + 7 * curr_battery, unit: "mV" }); + // wakeups per trip + const wakeups_trip = bytes.readUInt8(2); + decoded.push({ variable: "wakeups_per_trip", value: wakeups_trip }); + // trip count + const trip_count = bytes.readUInt16LE(3) & 0x3fff; + decoded.push({ variable: "trip_count", value: 32 * trip_count }); + // uptime in weeks + const uptime = bytes.readUInt16LE(4) >> 6; + decoded.push({ variable: "uptime", value: uptime, unit: "weeks" }); + // battery used + const battery = bytes.readUInt16LE(6) & 0x03ff; + decoded.push({ variable: "battery_used", value: battery * 2, unit: "mAh" }); + // percentage used on LoRaWAN + const p_lorawan = bytes.readUInt8(7) >> 2; + decoded.push({ variable: "percentage_used_lorawan", value: p_lorawan * 1.5625, unit: "%" }); + // percentage used on GNSS + const p_gnss = bytes.readUInt8(8) & 0x3f; + decoded.push({ variable: "percentage_used_gnss", value: p_gnss * 1.5625, unit: "%" }); + // percentage used on WiFi + const p_wifi = (bytes.readUInt16LE(8) >> 6) & 0x3f; + decoded.push({ variable: "percentage_used_wifi", value: p_wifi * 1.5625, unit: "%" }); + // percentage used sleeping + const p_sleep = (bytes.readUInt16LE(9) >> 4) & 0x3f; + decoded.push({ variable: "percentage_used_sleeping", value: p_sleep * 1.5625, unit: "%" }); + // percentage used on battery self discharge + const p_discharge = bytes.readUInt8(10) >> 2; + decoded.push({ variable: "percentage_used_battery_self_discharge", value: p_discharge * 1.5625, unit: "%" }); + } else if (port === 5) { + // Number of WiFi access points N (0-31) + const number_wifi_points = bytes.readUInt8(0) & 0x1f; + decoded.push({ variable: "number_of_wifi_access_points", value: number_wifi_points }); + // In-trip + const intrip = (bytes.readUInt8(0) >> 5) & 0x01; + decoded.push({ variable: "in_trip", value: intrip }); + // Inactivity Indicator + const inactivity = (bytes.readUInt8(0) >> 6) & 0x01; + decoded.push({ variable: "inactivity_indicator", value: inactivity }); + // RFU + // Timestamp in NAV message (if present) + const timestamp_nav_message = (bytes.readUInt8(1) >> 2) & 0x01; + decoded.push({ variable: "timestamp_in_nav_message", value: timestamp_nav_message }); + // position assistance sequence number + const sequence_number = bytes.readUInt8(1) >> 3; + decoded.push({ variable: "sequence_number", value: sequence_number }); + // ACCESS POINTS + for (let i = 1, j = 0; i <= number_wifi_points; ++i, ++j) { + // Access point X, RSSI, in dBm (signed) + const rssi = bytes.readInt8(2 + 7 * j); + decoded.push({ variable: `rssi${i}`, value: rssi, unit: "dBm" }); + // Access point X, MAC address + const mac = bytes + .readUIntBE(3 + 7 * j, 6) + .toString(16) + .toUpperCase(); + decoded.push({ variable: `mac${i}`, value: mac }); + } + // Semtech format GNSS NAV message (optional) + if (bytes.length > 7 * number_wifi_points + 2) { + const start = 7 * number_wifi_points + 2; + const semtech_nav_message = bytes + .slice(start, start + 8) + .toString("hex") + .toUpperCase(); + decoded.push({ variable: "semtech_nav_message", value: semtech_nav_message }); + } + } + return decoded; +} + +// let payload = [ +// { variable: "payload", value: "22E09DAC86749221DB0A48C09342A0CA01961B2230217E03" }, +// { variable: "port", value: 5 }, +// ]; +// let payload = [ +// { variable: "payload", value: "7A4A031234013B05030A00" }, +// { variable: "port", value: 3 }, +// ]; +// let payload = [ +// { variable: "payload", value: "D30102560107220305" }, +// { variable: "port", value: 2 }, +// ]; +// let payload = [ +// { variable: "payload", value: "010A56010203017A220305" }, +// { variable: "port", value: 1 }, +// ]; + +const data = payload.find((x) => x.variable === "payload" || x.variable === "payload_raw" || x.variable === "data"); +const port = payload.find((x) => x.variable === "fport" || x.variable === "port"); + +if (data && port) { + const serie = data.serie || new Date().getTime(); + const bytes = Buffer.from(data.value, "hex"); + payload = payload.concat(Decoder(bytes, Number(port.value))).map((x) => ({ ...x, serie })); +} \ No newline at end of file