diff --git a/pepperl-fuchs-wilsen-sonic-distance/README.md b/pepperl-fuchs-wilsen-sonic-distance/README.md new file mode 100644 index 00000000..1b6f882d --- /dev/null +++ b/pepperl-fuchs-wilsen-sonic-distance/README.md @@ -0,0 +1,60 @@ +# WILSEN.sonic.distance + +The WILSEN.sonic.distance is a wireless ultrasonic sensor with LoRaWAN interface developed by Pepperl+Fuchs. This device provides accurate distance measurements using ultrasonic technology and transmits data via LoRaWAN networks. Designed for industrial applications, it features IP67-rated enclosure for harsh environments and operates as a LoRaWAN Class A device on 868 MHz frequency. + +## Features + +- **Ultrasonic Distance Measurement**: Precision distance sensing with 1 mm resolution +- **LoRaWAN Connectivity**: LoRaWAN specification V1.0.3, Class A device +- **Multiple Sensors**: Distance, temperature, GPS positioning, and battery monitoring +- **Rugged Design**: IP67 enclosure for industrial environments +- **Wide Operating Temperature**: -25°C to 70°C +- **Compact Form Factor**: 81 x 182 x 71 mm dimensions +- **868 MHz Frequency**: +8 dBm transmitter radiated power +- **Downlink Support**: Bidirectional communication capability + +## Use Cases + +- Tank level monitoring +- Bin and silo fill level detection +- Distance measurement in industrial automation +- Waste management systems +- Environmental monitoring applications +- Asset tracking and positioning + +## Thinger.io Integration + +The WILSEN.sonic.distance integrates seamlessly with Thinger.io through LoRaWAN network servers, enabling real-time distance monitoring, data logging, and remote configuration capabilities. + +## Requirements + +A LoRaWAN server is required to communicate the WILSEN.sonic.distance into Thinger.io, some options are: + +- [The Things Stack](https://www.thethingsindustries.com/stack/) +- [LORIOT](https://loriot.io/) +- [ChirpStack](https://www.chirpstack.io/) + +Alongside, the corresponding plugin for the selected LoRaWAN server needs to be installed in your Thinger.io instance. + +## Get Started + +### Installation + +Look for the plugin in the [Thinger.io Plugin Store](https://marketplace.thinger.io/) and install it in your Thinger.io instance. Once the plugin is installed a new Product will be created for this device. + +### Configuration + +The Product is already preconfigured, check that the auto provision prefix matches the one selected in your LoRaWAN server plugin in Thinger.io, or change it to your desire. + +### Usage + +Start sending uplinks for autoprovisioning devices and buckets. +This product also provides a predefined dashboard and downlinks. + +## Additional Resources + +Pepperl+Fuchs resources can be found at: + +- [Product Datasheet](http://files.pepperl-fuchs.com/webcat/navi/productInfo/pds/70150489_eng.pdf) +- [TTN Device Repository](https://www.thethingsnetwork.org/device-repository/devices/pepperl-fuchs/wilsen-sonic-distance/) +- [Thinger docs](https://docs.thinger.io) \ No newline at end of file diff --git a/pepperl-fuchs-wilsen-sonic-level/CHANGELOG.md b/pepperl-fuchs-wilsen-sonic-level/CHANGELOG.md new file mode 100644 index 00000000..581d5bad --- /dev/null +++ b/pepperl-fuchs-wilsen-sonic-level/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 1.0.0 - 2026-01-28 + +- First version of plugin \ No newline at end of file diff --git a/pepperl-fuchs-wilsen-sonic-level/LICENSE.md b/pepperl-fuchs-wilsen-sonic-level/LICENSE.md new file mode 100644 index 00000000..bffeef34 --- /dev/null +++ b/pepperl-fuchs-wilsen-sonic-level/LICENSE.md @@ -0,0 +1,7 @@ +Copyright 2026 Thinger.io + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/pepperl-fuchs-wilsen-sonic-level/README.md b/pepperl-fuchs-wilsen-sonic-level/README.md new file mode 100644 index 00000000..f1e8cb5d --- /dev/null +++ b/pepperl-fuchs-wilsen-sonic-level/README.md @@ -0,0 +1,63 @@ +# WILSEN.sonic.level + +The WILSEN.sonic.level is a wireless ultrasonic sensor manufactured by Pepperl+Fuchs, designed to remotely monitor fill levels in containers, tanks, silos, and other industrial applications. This LoRaWAN-enabled device provides reliable distance and level measurements with a long battery life, making it ideal for remote monitoring applications. + +## Key Features + +- **Ultrasonic measurement technology** for accurate distance and level detection +- **Multiple sensing ranges**: 2.5 m, 4 m, or 7 m depending on model variant +- **High resolution**: 1 mm measurement precision +- **Dead band**: 0 to 500 mm +- **Configurable measurement interval**: from 10 minutes to 24 hours +- **Long battery life**: approximately 10 years under typical operating conditions +- **High capacity battery**: 3.6V, 13000 mAh lithium battery +- **LoRaWAN connectivity** for long-range wireless communication +- **Rugged design**: IP67 enclosure rating +- **Wide operating temperature range**: -25°C to 70°C +- **Compact dimensions**: 81 x 182 x 71 mm +- **Integrated sensors**: proximity, distance, GPS, temperature, and battery monitoring + +## Typical Applications + +- Fill level monitoring in tanks and silos +- Distance measurement in industrial environments +- Level monitoring in rivers and water bodies +- Container and bin monitoring +- Remote asset monitoring + +## Thinger.io Integration + +The WILSEN.sonic.level integrates seamlessly with Thinger.io through LoRaWAN network servers, enabling real-time data visualization, monitoring, and management of ultrasonic sensor data. + +## Requirements + +A LoRaWAN server is required to communicate the WILSEN.sonic.level into Thinger.io, some options are: + +- [The Things Stack](https://www.thethingsindustries.com/stack/) +- [LORIOT](https://loriot.io/) +- [ChirpStack](https://www.chirpstack.io/) + +Alongside, the corresponding plugin for the selected LoRaWAN server needs to be installed in your Thinger.io instance. + +## Get Started + +### Installation + +Look for the plugin in the [Thinger.io Plugin Store](https://marketplace.thinger.io/) and install it in your Thinger.io instance. Once the plugin is installed a new Product will be created for this device. + +### Configuration + +The Product is already preconfigured, check that the auto provision prefix matches the one selected in your LoRaWAN server plugin in Thinger.io, or change it to your desire. + +### Usage + +Start sending uplinks for autoprovisioning devices and buckets. +This product also provides a predefined dashboard and downlinks. + +## Additional Resources + +Pepperl+Fuchs resources can be found at: + +- [Product Information](https://www.pepperl-fuchs.com/en/products/industrial-sensors/wireless-sensors/wireless-ultrasonic-sensors-gp65414) +- [Device Repository](https://www.thethingsnetwork.org/device-repository/devices/pepperl-fuchs/wilsen-sonic-level/) +- [Thinger docs](https://docs.thinger.io) \ No newline at end of file diff --git a/pepperl-fuchs-wilsen-sonic-level/assets/wilsen-sonic-distance.jpg b/pepperl-fuchs-wilsen-sonic-level/assets/wilsen-sonic-distance.jpg new file mode 100644 index 00000000..2cf2efdf Binary files /dev/null and b/pepperl-fuchs-wilsen-sonic-level/assets/wilsen-sonic-distance.jpg differ diff --git a/pepperl-fuchs-wilsen-sonic-level/assets/wilsen-sonic-level.jpg b/pepperl-fuchs-wilsen-sonic-level/assets/wilsen-sonic-level.jpg new file mode 100644 index 00000000..2cf2efdf Binary files /dev/null and b/pepperl-fuchs-wilsen-sonic-level/assets/wilsen-sonic-level.jpg differ diff --git a/pepperl-fuchs-wilsen-sonic-level/plugin.json b/pepperl-fuchs-wilsen-sonic-level/plugin.json new file mode 100644 index 00000000..39a866d9 --- /dev/null +++ b/pepperl-fuchs-wilsen-sonic-level/plugin.json @@ -0,0 +1,420 @@ +{ + "name": "pepperl_fuchs_wilsen_sonic_level", + "version": "1.0.0", + "description": "Wireless ultrasonic sensor with LoRaWAN interface", + "author": "Thinger.io", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/thinger-io/plugins.git", + "directory": "pepperl-fuchs-wilsen-sonic-level" + }, + "metadata": { + "name": "Pepperl-Fuchs WILSEN-SONIC-LEVEL", + "description": "Wireless ultrasonic sensor with LoRaWAN interface", + "image": "assets/wilsen-sonic-level.jpg", + "category": "devices", + "vendor": "pepperl-fuchs" + }, + "resources": { + "products": [ + { + "description": "Wireless ultrasonic sensor with LoRaWAN interface", + "enabled": true, + "name": "Pepperl-Fuchs WILSEN-SONIC-LEVEL", + "product": "pepperl_fuchs_wilsen_sonic_level", + "profile": { + "code": { + "code": "function decodeThingerUplink(thingerData) {\n // 0. If data has already been decoded, we will return it\n if (thingerData.decodedPayload) return thingerData.decodedPayload;\n \n // 1. Extract and Validate Input\n // We need 'payload' (hex string) and 'fPort' (integer)\n const hexPayload = thingerData.payload || \"\";\n const port = thingerData.fPort || 1;\n\n // 2. Convert Hex String to Byte Array\n const bytes = [];\n for (let i = 0; i < hexPayload.length; i += 2) {\n bytes.push(parseInt(hexPayload.substr(i, 2), 16));\n }\n\n // 3. Dynamic Function Detection and Execution\n \n // CASE A: (The Things Stack v3)\n if (typeof decodeUplink === 'function') {\n try {\n const input = {\n bytes: bytes,\n fPort: port\n };\n var result = decodeUplink(input);\n \n if (result.data) return result.data;\n\n return result; \n } catch (e) {\n console.error(\"Error inside decodeUplink:\", e);\n throw e;\n }\n }\n\n // CASE B: Legacy TTN (v2)\n else if (typeof Decoder === 'function') {\n try {\n return Decoder(bytes, port);\n } catch (e) {\n console.error(\"Error inside Decoder:\", e);\n throw e;\n }\n }\n\n // CASE C: No decoder found\n else {\n throw new Error(\"No compatible TTN decoder function (decodeUplink or Decoder) found in scope.\");\n }\n}\n\n\n// TTN decoder\n'use strict';\r\n\r\nfunction decodeUplink(input) {\r\n var hexStr = byte2HexString(input.bytes);\r\n var obj = payloadParser(hexStr);\r\n\r\n return {\r\n data: obj,\r\n warnings: [],\r\n errors: []\r\n };\r\n}\r\n\r\n/**\r\n * This is the function to create a payload object by decoding hex string\r\n * @param {String} hexStr\r\n * @return {Object}\r\n */\r\nfunction payloadParser(hexStr) {\r\n const LoRaMessgeType = {\r\n 0: 'Unconfirmed',\r\n 1: 'Confirmed'\r\n };\r\n const SonicBeamWidth = {\r\n 254: 'Small',\r\n 253: 'Medium',\r\n 252: 'Wide',\r\n 55: 'User-defined',\r\n };\r\n const SonicBurstLength = {\r\n 5: 'Normal',\r\n 3: 'Short',\r\n 16: 'Very short',\r\n };\r\n const SonicTransmittingPower = {\r\n 63: 'High',\r\n 40: 'Medium',\r\n 12: 'Low'\r\n };\r\n const SonicSensitivity = {\r\n 63: 'Maximum',\r\n 51: 'Very high',\r\n 48: 'High',\r\n 38: 'Medium',\r\n 27: 'Low',\r\n 15: 'Minimum',\r\n };\r\n const SonicEvaluationMethod = {\r\n 1: 'Average value'\r\n };\r\n const SonicApplicationFilter = {\r\n 1: 'Container filling'\r\n };\r\n const ValveStatus = {\r\n 0: 'Closed',\r\n 1: 'Open',\r\n 2: 'Undefined',\r\n 3: 'Not connected',\r\n 7: 'Not inquired'\r\n }\r\n const SensorDetails = {\r\n 0: 'Low',\r\n 1: 'High',\r\n 7: 'Not inquired',\r\n 8: 'Short circuit',\r\n 9: 'Not connected',\r\n 10: 'Invalid current level',\r\n }\r\n const ValveOpenSignal = {\r\n 1: 'Sensor 1',\r\n 2: 'Sensor 2'\r\n };\r\n const ValveTriggerEventType = {\r\n 1: 'State change',\r\n 2: 'Valve open',\r\n 3: 'Valve closed',\r\n };\r\n const NodeOutputLogic = {\r\n 1: 'Normally open',\r\n 2: 'Normally closed'\r\n };\r\n const SensorStatus = {\r\n 0: 'No target detected',\r\n 1: 'Target detected',\r\n 7: 'Not inquired',\r\n 8: 'Short circuit',\r\n 9: 'Not connected',\r\n 10: 'Invalid current level',\r\n }\r\n const NodeTriggerEventType = {\r\n 1: 'State change',\r\n 2: 'Target detected',\r\n 3: 'No target detected',\r\n };\r\n const GPSAccuracyMode = {\r\n 1: 'Eco mode',\r\n 2: 'Precision mode',\r\n };\r\n var len;\r\n var sID;\r\n var obj = {};\r\n\r\n obj.payload = hexStr;\r\n for (var i = 0; i < hexStr.length; i = i + 2) {\r\n len = parseInt(hexStr.substr(i, 2), 16);\r\n sID = hexStr.substr(i + 2, 4);\r\n\r\n if (sID == '0201') { // 'Temperature'\r\n obj.temp = parseFloat(hex2float(hexStr.substr(i + 6, 8)).toFixed(1)); // float\r\n }\r\n else if (sID == '0B01') { // 'Proximity'\r\n obj.proxx = parseInt(hexStr.substr(i + 6, 4), 16); // uint16\r\n }\r\n else if (sID == '0B02') { // 'Proximity in mm'\r\n obj.proxx_mm = parseInt(hexStr.substr(i + 6, 4), 16); // uint16\r\n }\r\n else if (sID == '0B06') { // 'Fillinglevel'\r\n obj.fillinglvl = parseInt(hexStr.substr(i + 6, 2), 16); // uint8\r\n }\r\n else if (sID == '0B07') { // 'Amplitude'\r\n obj.amplitude = parseInt(hexStr.substr(i + 6, 2), 16); // uint8\r\n }\r\n else if (sID == '0B08') { // 'Water Body Level'\r\n obj.water_body_level_mm = parseInt(hexStr.substr(i + 6, 4), 16); // uint16\r\n }\r\n else if (sID == '0C01') { // 'Valve'\r\n obj.valve = parseInt(hexStr.substr(i + 6, 2), 16); // uint8\r\n obj.valveChecksum = parseInt(hexStr.substr(i + 8, 2), 16); // uint8\r\n }\r\n else if (sID == '0C02') { // 'Valve Status'\r\n obj.valve_status = parseInt(hexStr.substr(i + 6, 2), 16); // uint8\r\n const valve1Status = obj.valve_status & 0x0F;\r\n const valve2Status = (obj.valve_status >> 4) & 0x0F;\r\n obj.valve_1_status = (ValveStatus[valve1Status] ? ValveStatus[valve1Status] : 'Invalid');\r\n obj.valve_2_status = (ValveStatus[valve2Status] ? ValveStatus[valve2Status] : 'Invalid');\r\n }\r\n else if (sID == '0C03') { // 'Sensor Details'\r\n obj.sensor_details = parseInt(hexStr.substr(i + 6, 4), 16); // uint16\r\n const sensor1details = obj.sensor_details & 0x000F;\r\n const sensor2details = (obj.sensor_details >> 4) & 0x000F;\r\n const sensor3details = (obj.sensor_details >> 8) & 0x000F;\r\n const sensor4details = (obj.sensor_details >> 12) & 0x000F;\r\n obj.sensor_1_details = (SensorDetails[sensor1details] ? SensorDetails[sensor1details] : 'Invalid');\r\n obj.sensor_2_details = (SensorDetails[sensor2details] ? SensorDetails[sensor2details] : 'Invalid');\r\n obj.sensor_3_details = (SensorDetails[sensor3details] ? SensorDetails[sensor3details] : 'Invalid');\r\n obj.sensor_4_details = (SensorDetails[sensor4details] ? SensorDetails[sensor4details] : 'Invalid');\r\n }\r\n else if (sID == '0C04') { // 'Sensor Status'\r\n obj.sensor_status = parseInt(hexStr.substr(i + 6, 2), 16); // uint8\r\n const sensor1Status = obj.sensor_status & 0x0F;\r\n const sensor2Status = (obj.sensor_status >> 4) & 0x0F;\r\n obj.sensor_1_status = (SensorStatus[sensor1Status] ? SensorStatus[sensor1Status] : 'Invalid');\r\n obj.sensor_2_status = (SensorStatus[sensor2Status] ? SensorStatus[sensor2Status] : 'Invalid');\r\n }\r\n else if (sID == '2A25') { // 'Serial Number'\r\n obj.serial_nr = hex2string(hexStr.substr(i + 6, 28));\r\n }\r\n else if (sID == '2A26') { // 'Serial Number - 6 bytes uint'\r\n obj.serial_nr_uint = parseInt(hexStr.substr(i + 6, 12), 16); // uint24\r\n }\r\n else if (sID == '3101') { // 'LoRa Transmission Counter'\r\n obj.lora_count = parseInt(hexStr.substr(i + 6, 4), 16); // uint16\r\n }\r\n else if (sID == '3102') { // 'GPS Acquisition Counter'\r\n obj.gps_count = parseInt(hexStr.substr(i + 6, 4), 16); // uint16\r\n }\r\n else if (sID == '3103') { // 'US Measurement Counter'\r\n obj.us_sensor_count = parseInt(hexStr.substr(i + 6, 8), 16); // uint32\r\n }\r\n else if (sID == '3104') { // 'Sensor Measurement Counter'\r\n obj.sensing_count = parseInt(hexStr.substr(i + 6, 8), 16); // uint32\r\n }\r\n else if (sID == '5001') { // 'GPS Latitude'\r\n obj.latitude = parseFloat((number2Int32(parseInt(hexStr.substr(i + 6, 8), 16)) / 1000000).toFixed(6));\r\n }\r\n else if (sID == '5002') { // 'GPS Longitude'\r\n obj.longitude = parseFloat((number2Int32(parseInt(hexStr.substr(i + 6, 8), 16)) / 1000000).toFixed(6));\r\n }\r\n else if (sID == '5101') { // 'Battery'\r\n if (parseInt(hexStr.substr(i + 4, 2), 16) == 1) {\r\n obj.battery_vol = parseInt(hexStr.substr(i + 6, 2), 16) / 10; // uint8\r\n }\r\n }\r\n\r\n // Downlink ACK: Device Config\r\n else if (sID == 'F101') {\r\n obj.ble_enable = (parseInt(hexStr.substr(i + 6, 2), 16) > 0);\r\n }\r\n else if (sID == 'F102') {\r\n obj.device_name = hex2string(hexStr.substr(i + 6, 32));\r\n }\r\n else if (sID == 'F103') {\r\n obj.reset_counter = 'done';\r\n }\r\n else if (sID == 'F104') {\r\n obj.factory_reset = 'done';\r\n }\r\n else if (sID == 'F105') {\r\n obj.change_password = 'done';\r\n }\r\n else if (sID == 'F108') {\r\n obj.device_name_1 = hex2string(hexStr.substr(i + 6, 16));\r\n }\r\n else if (sID == 'F109') {\r\n obj.device_name_2 = hex2string(hexStr.substr(i + 6, 16));\r\n } \r\n \r\n // Downlink ACK: GPS Config\r\n else if (sID == 'F201') {\r\n obj.gps_acquisition_active = (parseInt(hexStr.substr(i + 6, 2), 16) > 0);\r\n }\r\n else if (sID == 'F202') {\r\n obj.gps_acquisition_interval = parseInt(hexStr.substr(i + 6, 8), 16);\r\n }\r\n else if (sID == 'F203') {\r\n obj.gps_next_acquisition = parseInt(hexStr.substr(i + 6, 8), 16);\r\n }\r\n else if (sID == 'F204') {\r\n const gpsAccuracyModeIndex = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.gps_accuracy_mode = (GPSAccuracyMode[gpsAccuracyModeIndex] ? GPSAccuracyMode[gpsAccuracyModeIndex] : 'Invalid');\r\n }\r\n else if (sID == 'F205') {\r\n const messageTypeIndex = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.gps_message_type = (LoRaMessgeType[messageTypeIndex] ? LoRaMessgeType[messageTypeIndex] : 'Invalid');\r\n }\r\n else if (sID == 'F206') {\r\n obj.gps_number_of_transmission = parseInt(hexStr.substr(i + 6, 2), 16);\r\n }\r\n else if (sID == 'F207') {\r\n obj.gps_localization = 'triggered';\r\n }\r\n\r\n // Downlink ACK: LoRa Config\r\n else if (sID == 'F301') {\r\n const messageTypeIndex = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.lora_message_type = (LoRaMessgeType[messageTypeIndex] ? LoRaMessgeType[messageTypeIndex] : 'Invalid');\r\n }\r\n else if (sID == 'F302') {\r\n obj.lora_number_of_transmission = parseInt(hexStr.substr(i + 6, 2), 16);\r\n }\r\n else if (sID == 'F303') {\r\n const spreadingFactor = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.lora_spreading_factor = (spreadingFactor == 255 ? 'ADR' : spreadingFactor);\r\n }\r\n else if (sID == 'F304') {\r\n obj.lora_data_transmission_active = (parseInt(hexStr.substr(i + 6, 2), 16) > 0);\r\n }\r\n else if (sID == 'F305') {\r\n obj.lora_transmission_interval = parseInt(hexStr.substr(i + 6, 8), 16);\r\n }\r\n else if (sID == 'F306') {\r\n obj.lora_next_transmission = parseInt(hexStr.substr(i + 6, 8), 16);\r\n }\r\n else if (sID == 'F307') {\r\n obj.lora_downlink_config = (parseInt(hexStr.substr(i + 6, 2), 16) > 0);\r\n }\r\n else if (sID == 'F308') {\r\n obj.lora_downlink_config_ack = (parseInt(hexStr.substr(i + 6, 2), 16) > 0);\r\n }\r\n else if (sID == 'F309') {\r\n obj.lora_sub_band = parseInt(hexStr.substr(i + 6, 2), 16);\r\n }\r\n\r\n // Downlink ACK: UltraSonic Config\r\n else if (sID == 'F401') {\r\n const SonicBeamWidthVal = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.us_beam_width = (SonicBeamWidth[SonicBeamWidthVal] ? SonicBeamWidth[SonicBeamWidthVal] : 'Invalid');\r\n }\r\n else if (sID == 'F402') {\r\n const SonicBurstLengthVal = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.us_user_defined_burst_length = (SonicBurstLength[SonicBurstLengthVal] ? SonicBurstLength[SonicBurstLengthVal] : SonicBurstLengthVal);\r\n }\r\n else if (sID == 'F403') {\r\n const SonicTransmittingPowerVal = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.us_user_defined_transmitting_power = (SonicTransmittingPower[SonicTransmittingPowerVal] ? SonicTransmittingPower[SonicTransmittingPowerVal] : SonicTransmittingPowerVal);\r\n }\r\n else if (sID == 'F404') {\r\n const SonicSensitivityVal = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.us_user_defined_sensitivity = (SonicSensitivity[SonicSensitivityVal] ? SonicSensitivity[SonicSensitivityVal] : SonicSensitivityVal);\r\n }\r\n else if (sID == 'F405') {\r\n obj.us_full_distance = parseInt(hexStr.substr(i + 6, 4), 16);\r\n }\r\n else if (sID == 'F406') {\r\n obj.us_empty_distance = parseInt(hexStr.substr(i + 6, 4), 16);\r\n }\r\n else if (sID == 'F407') {\r\n obj.us_measurement_sequence_active = (parseInt(hexStr.substr(i + 6, 2), 16) > 0);\r\n }\r\n else if (sID == 'F408') {\r\n const evaluationMethodVal = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.us_evaluation_method = (SonicEvaluationMethod[evaluationMethodVal] ? SonicEvaluationMethod[evaluationMethodVal] : 'Invalid');\r\n }\r\n else if (sID == 'F409') {\r\n obj.us_measurements_per_sequence = parseInt(hexStr.substr(i + 6, 2), 16);\r\n }\r\n else if (sID == 'F40A') {\r\n obj.us_at_intervals_of = parseInt(hexStr.substr(i + 6, 4), 16);\r\n }\r\n else if (sID == 'F40B') {\r\n obj.us_application_filter_active = (parseInt(hexStr.substr(i + 6, 2), 16) > 0);\r\n }\r\n else if (sID == 'F40C') {\r\n const filterVal = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.us_post_processing_filter = (SonicApplicationFilter[filterVal] ? SonicApplicationFilter[filterVal] : 'Invalid');\r\n }\r\n else if (sID == 'F40D') {\r\n obj.us_additional_measurement = parseInt(hexStr.substr(i + 6, 2), 16);\r\n }\r\n else if (sID == 'F40E') {\r\n obj.water_body_level_active = parseInt(hexStr.substr(i + 6, 2), 16);\r\n }\r\n else if (sID == 'F40F') {\r\n obj.distance_to_water_body_ground = parseInt(hexStr.substr(i + 6, 4), 16);\r\n }\r\n\r\n // Downlink ACK: Device Information\r\n else if (sID == 'F501') {\r\n obj.part_number = parseInt(hexStr.substr(i + 6, 8), 16);\r\n }\r\n else if (sID == 'F502') {\r\n const major = parseInt(hexStr.substr(i + 6, 4), 16);\r\n const minor = parseInt(hexStr.substr(i + 10, 4), 16);\r\n const patch = parseInt(hexStr.substr(i + 14, 4), 16);\r\n obj.hardware_revision = major + '.' + minor + '.' + patch;\r\n }\r\n else if (sID == 'F503') {\r\n const major = parseInt(hexStr.substr(i + 6, 4), 16);\r\n const minor = parseInt(hexStr.substr(i + 10, 4), 16);\r\n const patch = parseInt(hexStr.substr(i + 14, 4), 16);\r\n obj.firmware_revision = major + '.' + minor + '.' + patch;\r\n }\r\n \r\n // Downlink ACK: Valve(UCC) Config\r\n \telse if (sID == 'F601') {\r\n const messageTypeIndex = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.valve_monitoring_message_type = (LoRaMessgeType[messageTypeIndex] ? LoRaMessgeType[messageTypeIndex] : 'Invalid');\r\n }\r\n else if (sID == 'F602') {\r\n obj.valve_monitoring_interval = parseInt(hexStr.substr(i + 6, 8), 16);\r\n }\r\n\r\n // Downlink ACK: Valve Config\r\n \telse if (sID == 'F701') {\r\n const openSignalIndex = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.valve_1_open_signal = (ValveOpenSignal[openSignalIndex] ? ValveOpenSignal[openSignalIndex] : 'Invalid');\r\n }\r\n else if (sID == 'F702') {\r\n obj.valve_1_event_driven_transmission = (parseInt(hexStr.substr(i + 6, 2), 16) > 0);\r\n }\r\n else if (sID == 'F703') {\r\n const triggerEventIndex = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.valve_1_trigger_event = (ValveTriggerEventType[triggerEventIndex] ? ValveTriggerEventType[triggerEventIndex] : 'Invalid');\r\n }\r\n else if (sID == 'F704') {\r\n const messageTypeIndex = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.valve_1_message_type = (LoRaMessgeType[messageTypeIndex] ? LoRaMessgeType[messageTypeIndex] : 'Invalid');\r\n }\r\n else if (sID == 'F705') {\r\n obj.valve_1_num_of_transmission = parseInt(hexStr.substr(i + 6, 2), 16);\r\n }\r\n else if (sID == 'F706') {\r\n obj.valve_1_monitoring_interval = parseInt(hexStr.substr(i + 6, 8), 16);\r\n }\r\n else if (sID == 'F707') {\r\n const openSignalIndex = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.valve_2_open_signal = (ValveOpenSignal[openSignalIndex] ? ValveOpenSignal[openSignalIndex] : 'Invalid');\r\n }\r\n else if (sID == 'F708') {\r\n obj.valve_2_event_driven_transmission = (parseInt(hexStr.substr(i + 6, 2), 16) > 0);\r\n }\r\n else if (sID == 'F709') {\r\n const triggerEventIndex = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.valve_2_trigger_event = (ValveTriggerEventType[triggerEventIndex] ? ValveTriggerEventType[triggerEventIndex] : 'Invalid');\r\n }\r\n else if (sID == 'F70A') {\r\n const messageTypeIndex = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.valve_2_message_type = (LoRaMessgeType[messageTypeIndex] ? LoRaMessgeType[messageTypeIndex] : 'Invalid');\r\n }\r\n else if (sID == 'F70B') {\r\n obj.valve_2_num_of_transmission = parseInt(hexStr.substr(i + 6, 2), 16);\r\n }\r\n else if (sID == 'F70C') {\r\n obj.valve_2_monitoring_interval = parseInt(hexStr.substr(i + 6, 8), 16);\r\n }\r\n\r\n // Downlink ACK: Node Config\r\n \telse if (sID == 'F801') {\r\n const outputLogicIndex = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.sensor_1_output_logic = (NodeOutputLogic[outputLogicIndex] ? NodeOutputLogic[outputLogicIndex] : 'Invalid');\r\n }\r\n else if (sID == 'F802') {\r\n obj.sensor_1_event_driven_transmission = (parseInt(hexStr.substr(i + 6, 2), 16) > 0);\r\n }\r\n else if (sID == 'F803') {\r\n const triggerEventIndex = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.sensor_1_trigger_event = (NodeTriggerEventType[triggerEventIndex] ? NodeTriggerEventType[triggerEventIndex] : 'Invalid');\r\n }\r\n else if (sID == 'F804') {\r\n const messageTypeIndex = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.sensor_1_message_type = (LoRaMessgeType[messageTypeIndex] ? LoRaMessgeType[messageTypeIndex] : 'Invalid');\r\n }\r\n else if (sID == 'F805') {\r\n obj.sensor_1_num_of_transmission = parseInt(hexStr.substr(i + 6, 2), 16);\r\n }\r\n else if (sID == 'F806') {\r\n obj.sensor_1_monitoring_interval = parseInt(hexStr.substr(i + 6, 8), 16);\r\n }\r\n else if (sID == 'F807') {\r\n const outputLogicIndex = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.sensor_2_output_logic = (NodeOutputLogic[outputLogicIndex] ? NodeOutputLogic[outputLogicIndex] : 'Invalid');\r\n }\r\n else if (sID == 'F808') {\r\n obj.sensor_2_event_driven_transmission = (parseInt(hexStr.substr(i + 6, 2), 16) > 0);\r\n }\r\n else if (sID == 'F809') {\r\n const triggerEventIndex = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.sensor_2_trigger_event = (NodeTriggerEventType[triggerEventIndex] ? NodeTriggerEventType[triggerEventIndex] : 'Invalid');\r\n }\r\n else if (sID == 'F80A') {\r\n const messageTypeIndex = parseInt(hexStr.substr(i + 6, 2), 16);\r\n obj.sensor_2_message_type = (LoRaMessgeType[messageTypeIndex] ? LoRaMessgeType[messageTypeIndex] : 'Invalid');\r\n }\r\n else if (sID == 'F80B') {\r\n obj.sensor_2_num_of_transmission = parseInt(hexStr.substr(i + 6, 2), 16);\r\n }\r\n else if (sID == 'F80C') {\r\n obj.sensor_2_monitoring_interval = parseInt(hexStr.substr(i + 6, 8), 16);\r\n }\r\n\r\n i = i + (len * 2);\r\n }\r\n return obj;\r\n}\r\n\r\nfunction byte2HexString(bytes) {\r\n var retHexString = '';\r\n for (var i_b = 0; i_b < bytes.length; i_b++) {\r\n retHexString = retHexString.concat(('0' + (Number(bytes[i_b]).toString(16))).slice(-2).toUpperCase());\r\n }\r\n return retHexString;\r\n}\r\n\r\nfunction number2Int32(value) {\r\n if (value > 0x7FFFFFFF) {\r\n return (value - 0x100000000);\r\n }\r\n return value;\r\n}\r\n\r\nfunction hex2string(hexx) {\r\n var hex = hexx.toString();\r\n var str = '';\r\n for (var j = 0;\r\n (j < hex.length && hex.substr(j, 2) !== '00'); j += 2)\r\n str += String.fromCharCode(parseInt(hex.substr(j, 2), 16));\r\n return str;\r\n}\r\n\r\nfunction hex2float(hexstring) {\r\n var bytes = [];\r\n bytes[0] = parseInt(hexstring.substr(0, 2), 16);\r\n bytes[1] = parseInt(hexstring.substr(2, 2), 16);\r\n bytes[2] = parseInt(hexstring.substr(4, 2), 16);\r\n bytes[3] = parseInt(hexstring.substr(6, 2), 16);\r\n return decodeFloat(bytes, 1, 8, 23, -126, 127, false);\r\n}\r\n\r\nfunction decodeFloat(bytes, signBits, exponentBits, fractionBits, eMin, eMax, littleEndian) {\r\n var binary = '';\r\n for (var z = 0, l = bytes.length; z < l; z += 1) {\r\n var bits = bytes[z].toString(2);\r\n while (bits.length < 8) {\r\n bits = '0' + bits;\r\n }\r\n if (littleEndian) {\r\n binary = bits + binary;\r\n } else {\r\n binary += bits;\r\n }\r\n }\r\n var sign = (binary.charAt(0) === '1') ? -1 : 1;\r\n var exponent = parseInt(binary.substr(signBits, exponentBits), 2) - eMax;\r\n var significandBase = binary.substr(signBits + exponentBits, fractionBits);\r\n var significandBin = '1' + significandBase;\r\n var cnt = 0;\r\n var val = 1;\r\n var significand = 0;\r\n if (exponent == -eMax) {\r\n if (significandBase.indexOf('1') === -1)\r\n return 0;\r\n else {\r\n exponent = eMin;\r\n significandBin = '0' + significandBase;\r\n }\r\n }\r\n while (cnt < significandBin.length) {\r\n significand += val * parseInt(significandBin.charAt(cnt));\r\n val = val / 2;\r\n cnt += 1;\r\n }\r\n return sign * significand * Math.pow(2, exponent);\r\n}\r\n", + "environment": "javascript", + "storage": "", + "version": "1.0" + }, + "properties": { + "uplink": { + "data": { + "payload": "{{payload}}", + "payload_function": "", + "payload_type": "source_payload", + "resource": "uplink", + "source": "resource", + "update": "events" + }, + "default": { + "source": "value" + }, + "enabled": true + } + } + }, + "_resources": { + "properties": [ + { + "property": "dashboard", + "value": { + "tabs": [ + { + "name": "Overview", + "widgets": [ + { + "api": {}, + "layout": { + "col": 0, + "row": 0, + "sizeX": 12, + "sizeY": 10 + }, + "panel": { + "color": "#1565C0", + "currentColor": "#1565C0", + "showFullscreen": true, + "showOffline": { + "type": "last_sample" + }, + "subtitle": "Ultrasonic distance measurement", + "title": "Distance / Proximity (mm)" + }, + "properties": { + "options": "var options = {\n series: series,\n chart: {\n background: '#1565C0',\n toolbar: { show: true, autoSelected: 'zoom' },\n zoom: { enabled: true, type: 'x', autoScaleYaxis: true }\n },\n stroke: { curve: 'smooth', width: 2 },\n xaxis: { type: 'datetime', labels: { datetimeUTC: false, style: { colors: '#FFFFFF' } } },\n yaxis: {\n labels: { formatter: function(val) { return val.toFixed(0) + ' mm'; }, style: { colors: '#FFFFFF' } },\n title: { text: 'Distance (mm)', style: { color: '#FFFFFF' } }\n },\n tooltip: {\n x: { format: 'dd/MM/yyyy HH:mm' },\n shared: true,\n theme: 'dark'\n },\n legend: { position: 'bottom', labels: { colors: '#FFFFFF' } },\n grid: { borderColor: '#3a7bd5' }\n};" + }, + "sources": [ + { + "$timespan": { + "magnitude": "day", + "mode": "configurable", + "period": "latest", + "value": 7 + }, + "bucket": { + "backend": "mongodb", + "id": "pepperl_fuchs_wilsen_sonic_level_data", + "mapping": "proxx_mm", + "tags": { + "device": [], + "group": [] + }, + "user": "{{user}}" + }, + "color": "#4FC3F7", + "name": "Proximity (mm)", + "source": "bucket", + "timespan": { + "magnitude": "day", + "mode": "relative", + "period": "latest", + "value": 7 + } + }, + { + "$timespan": { + "magnitude": "day", + "mode": "configurable", + "period": "latest", + "value": 7 + }, + "bucket": { + "backend": "mongodb", + "id": "pepperl_fuchs_wilsen_sonic_level_data", + "mapping": "water_body_level_mm", + "tags": { + "device": [], + "group": [] + }, + "user": "{{user}}" + }, + "color": "#81C784", + "name": "Water Level (mm)", + "source": "bucket", + "timespan": { + "magnitude": "day", + "mode": "relative", + "period": "latest", + "value": 7 + } + } + ], + "type": "apex_charts" + }, + { + "api": {}, + "layout": { + "col": 0, + "row": 10, + "sizeX": 6, + "sizeY": 8 + }, + "panel": { + "color": "#00695C", + "currentColor": "#00695C", + "showFullscreen": true, + "showOffline": { + "type": "last_sample" + }, + "subtitle": "Container fill percentage", + "title": "Filling Level (%)" + }, + "properties": { + "options": "var options = {\n series: series,\n chart: {\n type: 'area',\n background: '#00695C',\n toolbar: { show: true, autoSelected: 'zoom' },\n zoom: { enabled: true, type: 'x', autoScaleYaxis: true }\n },\n stroke: { curve: 'smooth', width: 2 },\n fill: { type: 'gradient', gradient: { shadeIntensity: 1, opacityFrom: 0.7, opacityTo: 0.2 } },\n xaxis: { type: 'datetime', labels: { datetimeUTC: false, style: { colors: '#FFFFFF' } } },\n yaxis: {\n min: 0,\n max: 100,\n labels: { formatter: function(val) { return val.toFixed(0) + ' %'; }, style: { colors: '#FFFFFF' } },\n title: { text: 'Fill Level', style: { color: '#FFFFFF' } }\n },\n tooltip: {\n x: { format: 'dd/MM/yyyy HH:mm' },\n theme: 'dark'\n },\n legend: { position: 'bottom', labels: { colors: '#FFFFFF' } },\n grid: { borderColor: '#26a69a' }\n};" + }, + "sources": [ + { + "$timespan": { + "magnitude": "day", + "mode": "configurable", + "period": "latest", + "value": 7 + }, + "bucket": { + "backend": "mongodb", + "id": "pepperl_fuchs_wilsen_sonic_level_data", + "mapping": "fillinglvl", + "tags": { + "device": [], + "group": [] + }, + "user": "{{user}}" + }, + "color": "#80CBC4", + "name": "Fill Level", + "source": "bucket", + "timespan": { + "magnitude": "day", + "mode": "relative", + "period": "latest", + "value": 7 + } + } + ], + "type": "apex_charts" + }, + { + "api": {}, + "layout": { + "col": 6, + "row": 10, + "sizeX": 6, + "sizeY": 8 + }, + "panel": { + "color": "#E65100", + "currentColor": "#E65100", + "showFullscreen": true, + "showOffline": { + "type": "last_sample" + }, + "subtitle": "Sensor temperature", + "title": "Temperature (°C)" + }, + "properties": { + "options": "var options = {\n series: series,\n chart: {\n background: '#E65100',\n toolbar: { show: true, autoSelected: 'zoom' },\n zoom: { enabled: true, type: 'x', autoScaleYaxis: true }\n },\n stroke: { curve: 'smooth', width: 2 },\n xaxis: { type: 'datetime', labels: { datetimeUTC: false, style: { colors: '#FFFFFF' } } },\n yaxis: {\n labels: { formatter: function(val) { return val.toFixed(1) + ' °C'; }, style: { colors: '#FFFFFF' } },\n title: { text: 'Temperature', style: { color: '#FFFFFF' } }\n },\n tooltip: {\n x: { format: 'dd/MM/yyyy HH:mm' },\n theme: 'dark'\n },\n legend: { position: 'bottom', labels: { colors: '#FFFFFF' } },\n grid: { borderColor: '#ff8a65' }\n};" + }, + "sources": [ + { + "$timespan": { + "magnitude": "day", + "mode": "configurable", + "period": "latest", + "value": 7 + }, + "bucket": { + "backend": "mongodb", + "id": "pepperl_fuchs_wilsen_sonic_level_data", + "mapping": "temp", + "tags": { + "device": [], + "group": [] + }, + "user": "{{user}}" + }, + "color": "#FFCC80", + "name": "Temperature", + "source": "bucket", + "timespan": { + "magnitude": "day", + "mode": "relative", + "period": "latest", + "value": 7 + } + } + ], + "type": "apex_charts" + }, + { + "api": {}, + "layout": { + "col": 0, + "row": 18, + "sizeX": 6, + "sizeY": 8 + }, + "panel": { + "color": "#5E35B1", + "currentColor": "#5E35B1", + "showFullscreen": true, + "showOffline": { + "type": "last_sample" + }, + "subtitle": "Sensor battery status", + "title": "Battery Voltage (V)" + }, + "properties": { + "options": "var options = {\n series: series,\n chart: {\n background: '#5E35B1',\n toolbar: { show: true, autoSelected: 'zoom' },\n zoom: { enabled: true, type: 'x', autoScaleYaxis: true }\n },\n stroke: { curve: 'stepline', width: 2 },\n xaxis: { type: 'datetime', labels: { datetimeUTC: false, style: { colors: '#FFFFFF' } } },\n yaxis: {\n labels: { formatter: function(val) { return val.toFixed(1) + ' V'; }, style: { colors: '#FFFFFF' } },\n title: { text: 'Battery', style: { color: '#FFFFFF' } }\n },\n tooltip: {\n x: { format: 'dd/MM/yyyy HH:mm' },\n theme: 'dark'\n },\n legend: { position: 'bottom', labels: { colors: '#FFFFFF' } },\n grid: { borderColor: '#9575cd' }\n};" + }, + "sources": [ + { + "$timespan": { + "magnitude": "day", + "mode": "configurable", + "period": "latest", + "value": 30 + }, + "bucket": { + "backend": "mongodb", + "id": "pepperl_fuchs_wilsen_sonic_level_data", + "mapping": "battery_vol", + "tags": { + "device": [], + "group": [] + }, + "user": "{{user}}" + }, + "color": "#B39DDB", + "name": "Battery", + "source": "bucket", + "timespan": { + "magnitude": "day", + "mode": "relative", + "period": "latest", + "value": 30 + } + } + ], + "type": "apex_charts" + }, + { + "api": {}, + "layout": { + "col": 6, + "row": 18, + "sizeX": 6, + "sizeY": 8 + }, + "panel": { + "color": "#37474F", + "currentColor": "#37474F", + "showFullscreen": true, + "showOffline": { + "type": "last_sample" + }, + "subtitle": "Echo signal strength", + "title": "Signal Amplitude" + }, + "properties": { + "options": "var options = {\n series: series,\n chart: {\n background: '#37474F',\n toolbar: { show: true, autoSelected: 'zoom' },\n zoom: { enabled: true, type: 'x', autoScaleYaxis: true }\n },\n stroke: { curve: 'smooth', width: 2 },\n xaxis: { type: 'datetime', labels: { datetimeUTC: false, style: { colors: '#FFFFFF' } } },\n yaxis: {\n min: 0,\n labels: { formatter: function(val) { return val.toFixed(0); }, style: { colors: '#FFFFFF' } },\n title: { text: 'Amplitude', style: { color: '#FFFFFF' } }\n },\n tooltip: {\n x: { format: 'dd/MM/yyyy HH:mm' },\n theme: 'dark'\n },\n legend: { position: 'bottom', labels: { colors: '#FFFFFF' } },\n grid: { borderColor: '#607d8b' }\n};" + }, + "sources": [ + { + "$timespan": { + "magnitude": "day", + "mode": "configurable", + "period": "latest", + "value": 7 + }, + "bucket": { + "backend": "mongodb", + "id": "pepperl_fuchs_wilsen_sonic_level_data", + "mapping": "amplitude", + "tags": { + "device": [], + "group": [] + }, + "user": "{{user}}" + }, + "color": "#90A4AE", + "name": "Amplitude", + "source": "bucket", + "timespan": { + "magnitude": "day", + "mode": "relative", + "period": "latest", + "value": 7 + } + } + ], + "type": "apex_charts" + } + ] + }, + { + "name": "Location", + "widgets": [ + { + "api": {}, + "layout": { + "col": 0, + "row": 0, + "sizeX": 12, + "sizeY": 14 + }, + "panel": { + "color": "#263238", + "currentColor": "#263238", + "showFullscreen": true, + "showOffline": { + "type": "last_sample" + }, + "subtitle": "GPS position from sensor", + "title": "Device Location" + }, + "properties": { + "center": { + "lat": 48.8566, + "lng": 2.3522 + }, + "clusterMarkers": false, + "fitBounds": true, + "layer": "CartoDB.DarkMatter", + "zoom": 12 + }, + "sources": [ + { + "bucket": { + "backend": "mongodb", + "id": "pepperl_fuchs_wilsen_sonic_level_data", + "mapping": "", + "tags": { + "device": [], + "group": [] + }, + "user": "{{user}}" + }, + "latitude": { + "source": "bucket", + "value": "latitude" + }, + "longitude": { + "source": "bucket", + "value": "longitude" + }, + "source": "bucket", + "timespan": { + "magnitude": "day", + "mode": "relative", + "period": "latest", + "value": 1 + } + } + ], + "type": "map" + } + ] + } + ] + } + } + ] + } + } + ] + } +} \ No newline at end of file