Plugin file
Plugin configuration file
{
"name": "wika-pgw23-100",
"version": "1.0.0",
"description": "Bourdon tube pressure gauge with wireless transmission",
"author": "Thinger.io",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/thinger-io/plugins.git",
"directory": "wika-pgw23-100"
},
"metadata": {
"name": "Wika PGW23-100 Pressure Sensor",
"description": "Bourdon tube pressure gauge with wireless transmission",
"image": "https://www.wika.com/media/Images/Product_700x700/Pressure/pgw23.100_en_co_rs_w410_h410_image.jpg"
},
"resources": {
"products": [
{
"description": "Bourdon tube pressure gauge with wireless LoRaWAN transmission",
"enabled": true,
"name": "Wika PGW23-100 Pressure Sensor",
"product": "wika_pgw23_100",
"profile": {
"api": {
"downlink": {
"enabled": true,
"handle_connectivity": false,
"request": {
"data": {
"path": "/downlink",
"payload": "{\n \"data\" : \"{{payload.data=\"\"}}\",\n \"port\" : {{payload.port=85}},\n \"priority\": {{payload.priority=3}},\n \"confirmed\" : {{payload.confirmed=false}},\n \"uplink\" : {{property.uplink}} \n}",
"payload_function": "",
"payload_type": "",
"plugin": "{{property.source}}",
"target": "plugin_endpoint"
}
},
"response": {
"data": {
"payload": "{{payload}}",
"payload_function": "",
"payload_type": "source_payload",
"source": "request_response"
}
}
},
"uplink": {
"device_id_resolver": "get_id",
"enabled": true,
"handle_connectivity": true,
"request": {
"data": {
"payload": "{{payload}}",
"payload_function": "",
"payload_type": "source_payload",
"target": "resource_stream"
}
},
"response": {
"data": {}
}
}
},
"autoprovisions": {
"pgw23_100_autoprovisioning": {
"config": {
"mode": "pattern",
"pattern": "wika_pgw23_100_.*"
},
"enabled": true
}
},
"buckets": {
"pgw23_100_data_bucket": {
"backend": "mongodb",
"data": {
"payload": "{{payload}}",
"payload_function": "decodeUplink",
"payload_type": "source_payload",
"resource": "uplink",
"source": "resource",
"update": "events"
},
"enabled": true,
"retention": {
"period": 3,
"unit": "months"
},
"tags": []
}
},
"code": {
"code": "/*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&# &&& \n&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&# &&& \n&&& &&# &&& \n&&& .&&&&& .&&&&& &&&&&& &&&&& .&&&&/ .&&&&&& &&&&&&&& &&# &&& \n&&& &&&&&& &&&&&&& &&&&& &&&&& .&&&&/ &&&&&&& &&&&&&&&&& &&# &&& \n&&& &&&&&( &&&&&&&&# &&&&&( &&&&& .&&&&/ &&&&&& &&&&& /&&&&& &&# &&& \n&&& &&&&& /&&&&,&&&& ,&&&&& &&&&& .&&&&&&&&&&& &&&&& &&&&&% &&# &&& \n&&& %&&&&& &&&& &&&&& &&&&& &&&&& .&&&&&&&&&&&&& &&&&&% &&&&&( &&# &&& \n&&& &&&&&&&&&( &&&&&&&&&. &&&&& .&&&&& &&&&&&# &&&&&&&&&&&&&&&&&. &&# &&& \n&&& &&&&&&&& &&&&&&&& &&&&& .&&&&/ &&&&&& *&&&&& &&&&& &&# &&& \n&&& &&&&&& /&&&&&& &&&&& .&&&&/ &&&&&& &&&&& #&&&&& &&# &&& \n&&& &&& \n&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& \n&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/\n\n/**\n * General information: \n * This JavaScript-based payload formatter is a parser to decode data from bytes into\n * JSON Object. It can only parse payload from PGW23.100.11 devices. \n * This parser follows the specification LoRaWAN® Payload Codec API Specification TS013-1.0.0.\n * \n * \n * SPDX-FileCopyrightText: Copyright (C) 2023 WIKA Alexander Wiegand SE & Co. KG \n * SPDX-License-Identifier: MIT\n * \n * SPDX-FileName: index.js\n * SPDX-PackageVersion: 2.0.0\n * \n*/\n\n// ***********************************************************************************\n// Public Decoding Section\n// ***********************************************************************************\n\n/**\n * ATTENTION: You must define the measurement ranges first, otherwise the script will not work.\n * The device configuration defines the measurement ranges for the supported measured variables of your used devices, e.g.\n * var PRESSURE_RANGE_START = 0;\n * var PRESSURE_RANGE_END = 10;\n * var DEVICE_TEMPERATURE_RANGE_START = -40;\n * var DEVICE_TEMPERATURE_RANGE_END = 60; \n */\n\nvar PRESSURE_RANGE_START = 0;\nvar PRESSURE_RANGE_END = 10;\nvar DEVICE_TEMPERATURE_RANGE_START = -40;\nvar DEVICE_TEMPERATURE_RANGE_END = 60;\n\n\n/**\n * Decode uplink entry point\n * @typedef {Object} input - An object provided by the IoT Flow framework\n * @property {number[]} input.bytes - Array of bytes represented as numbers as it has been sent from the device\n * @property {number} input.fPort - The Port Field on which the uplink has been sent\n * @property {Date} input.recvTime - The uplink message time recorded by the LoRaWAN network server\n */\n\n/**\n * Decoded uplink data\n * @typedef {Object} output - An object to be returned to the IoT Flow framework\n * @property {Object.<string, *>} data - The open JavaScript object representing the decoded uplink payload when no errors occurred\n * @property {string[]} [errors] - A list of error messages while decoding the uplink payload\n * @property {string[]} [warnings] - A list of warning messages that do not prevent the driver from decoding the uplink payload\n */\n\n/**\n * @typedef {Object} DecodedUplink - An object that represents the decoded uplink payload\n */\n\n/**\n * To decode the uplink data defined by LoRaWAN\n * @access public\n * @param {input} input - The object to decode\n * @returns {output} - The decoded object\n */\n\n\n/**\n * To decode from hex encoded string\n * @access public\n * @param {number} fPort - The Port Field on which the uplink has been sent\n * @param {string} hexEncodedString - A hex encoded string has been sent from the device\n * @returns {output} - The decoded object\n */\nfunction decodeHexString(fPort, hexEncodedString) {\n /**\n * @type {input}\n */\n var input = {};\n input.bytes = convertHexStringToBytes(hexEncodedString);\n input.fPort = fPort;\n\n return decode(input);\n}\n\n/**\n * To decode from base64 string\n * @access public\n * @param {number} fPort - The Port Field on which the uplink has been sent\n * @param {string} base64EncodedString - A base64 encoded string has been sent from the device\n * @returns {output} - The decoded object\n */\nfunction decodeBase64String(fPort, base64EncodedString) {\n /**\n * @type {input}\n */\n var input = {};\n input.bytes = convertBase64StringToBytes(base64EncodedString);\n input.fPort = fPort;\n\n return decode(input);\n}\n\n\n// ***********************************************************************************\n// Private Decoding Section\n// ***********************************************************************************\n\n/**\n * Generic Data Channel Values\n */\nvar DEVICE_NAME = \"PGW23.100.11\";\n\nvar GENERIC_DATA_CHANNEL_RANGE_START = 2500;\nvar GENERIC_DATA_CHANNEL_RANGE_END = 12500;\nvar ERROR_VALUE = 0xffff;\n\nvar CHANNEL_NAMES_DICTIONARY = ['pressure', 'device temperature', 'battery voltage'];\nvar ALARM_EVENT_NAMES_DICTIONARY = ['triggered', 'disappeared'];\nvar ALARM_CHANNEL_NAMES_DICTIONARY = ['pressure', 'device temperature'];\nvar PROCESS_ALARM_TYPE_NAMES_DICTIONARY = ['low threshold', 'high threshold', 'falling slope', 'rising slope', 'low threshold with delay', 'high threshold with delay'];\nvar TECHNICAL_ALARM_CAUSE_OF_FAILURE_NAMES_DICTIONARY = ['', 'general failure'];\nvar DEVICE_ALARM_CAUSE_OF_FAILURE_NAMES_DICTIONARY = ['', 'device dependent'];\nvar DEVICE_ALARM_TYPE_NAMES_DICTIONARY = ['low temperature alarm'];\n\n/**\n * The padStart() method of String values pads this string with another string (multiple times, if needed) until the resulting string reaches the given length.\n * The function is reimplemented to support ES5.\n * @access private\n * @param {number} targetLength - The length of the returned string\n * @param {string} padString - The string to modify\n * @returns {string} - The decoded object\n */\nString.prototype.padStart = function (targetLength, padString) {\n\n var tempString = this.valueOf();\n\n for(var i = this.length; i < targetLength; ++i)\n {\n tempString = padStrin\noffline: falseg + tempString;\n }\n\n return tempString; \n };\n\n function print(asd) {\n console.log(\"PPPBDA\" + asd);\n }\n\nfunction decodeUplink(arg) {\n function makeError(msg) {\n return addErrorMessage(createOutputObject(), msg);\n }\n\n try {\n let bytes;\n\n if (arg && Array.isArray(arg.bytes)) {\n bytes = arg.bytes.slice();\n } else if (arg && typeof arg.payload === 'string') {\n let hex = arg.payload.replace(/\\s+/g, '');\n if (hex.startsWith('0x') || hex.startsWith('0X')) hex = hex.slice(2);\n bytes = convertHexStringToBytes(hex);\n } else if (arg && typeof arg.payload_raw === 'string') {\n bytes = convertBase64StringToBytes(arg.payload_raw);\n } else {\n return makeError(\"Missing 'bytes', 'payload' (hex) or 'payload_raw' (base64) in decodeUplink argument\");\n }\n\n const fPort = (arg && typeof arg.fPort === 'number') ? arg.fPort : 0;\n\n const input = {\n bytes: bytes,\n fPort: fPort,\n recvTime: new Date().toISOString()\n };\n\n const result = decode(input);\n print(\"B\" + JSON.stringify(result));\n\n if (result && result.data && result.data.measurement && Array.isArray(result.data.measurement.channels)) {\n const flat = {};\n for (const ch of result.data.measurement.channels) {\n let key = ch.channelName.toLowerCase().replace(/\\s+/g, '_');\n if (key === 'device_temperature') key = 'temperature';\n flat[key] = ch.value;\n }\n return flat;\n }\n\n return result;\n } catch (e) {\n print(\"ERROR\" + (e && e.message ? e.message : String(e)));\n return addErrorMessage(\n createOutputObject(),\n \"Unexpected error in decodeUplink: \" + (e && e.message ? e.message : String(e))\n );\n }\n}\n\n/**\n * To decode the uplink data\n * @access private\n * @param {input} input - The object to decode\n * @returns {output} - The decoded object\n */\nfunction decode(input) {\n // Define output object\n\n var output = createOutputObject();\n output = checkMeasurementRanges(output);\n if (output.errors) {\n return output;\n }\n\n /* Select subfunction to decode message */\n switch (input.bytes[0]) {\n /* unused */\n default:\n case 0x00:\n case 0x06: // configuration status message is not supported\n case 0x09: // extended device identification message is not supported\n // Error, not enough bytes \n output = addErrorMessage(output, \"Data message type \" + input.bytes[0].toString(16).padStart(2, \"0\") + \" not supported\");\n break;\n\n /* Data message */\n case 0x01:\n case 0x02:\n /* Check if all bytes needed for decoding are there */\n if (input.bytes.length == 7) {\n // decode\n output = decodeDataMessage(input);\n }\n else {\n // Error, not enough bytes\n output = addErrorMessage(output, \"Data message 01/02 needs 7 bytes but got \" + input.bytes.length);\n }\n break;\n\n /* Process alarm */\n case 0x03:\n /* Check if all bytes needed for decoding are there and all bytes for each alarm */\n if (input.bytes.length >= 5 && !((input.bytes.length - 2) % 3)) {\n // decode\n output = decodeProcessAlarm(input);\n }\n else {\n // Error, not enough bytes\n output = addErrorMessage(output, \"Process alarm 03 needs at least 5 bytes and got \" + input.bytes.length + \". Also all bytes for each alarm needed\");\n }\n break;\n\n /* Technical alarm */\n case 0x04:\n /* Check if all bytes needed for decoding are there and all bytes for each alarm */\n if (input.bytes.length >= 5 && !((input.bytes.length - 2) % 3)) {\n // decode\n output = decodeTechnicalAlarm(input);\n }\n else {\n // Error, not enough bytes\n output = addErrorMessage(output, \"Technical alarm 04 needs 5 bytes but got \" + input.bytes.length + \". Also all bytes for each alarm needed\");\n }\n break;\n\n /* Device alarm */\n case 0x05:\n /* Check if all bytes needed for decoding are there */\n if (input.bytes.length == 4) {\n // decode\n output = decodeDeviceAlarm(input);\n }\n else {\n // Error, not enough bytes\n output = addErrorMessage(output, \"Device alarm 05 needs 4 bytes but got \" + input.bytes.length);\n }\n break;\n\n /* Device identification */\n case 0x07:\n /* Check if all bytes needed for decoding are there */\n if (input.bytes.length == 41) {\n // decode\n output = decodeDeviceIdentification(input);\n }\n else {\n // Error, not enough bytes\n output = addErrorMessage(output, \"Identification message 07 needs 41 bytes but got \" + input.bytes.length);\n }\n break;\n\n /* Keep alive */\n case 0x08:\n /* Check if all bytes needed for decoding are there */\n if (input.bytes.length == 3) {\n // Decode\n output = decodeKeepAliveMessage(input);\n }\n else {\n // Error, not enough bytes\n output = addErrorMessage(output, \"Keep alive message 08 needs 3 bytes but got \" + input.bytes.length);\n }\n break;\n }\n\n return output;\n}\n\n/**\n * Decodes a data message 01, 02 into an object\n * @access private\n * @param {Object} input - An object provided by the IoT Flow framework\n * @param {number[]} input.bytes - Array of bytes represented as numbers as it has been sent from the device\n * @param {number} input.fPort - The Port Field on which the uplink has been sent\n * @param {Date} input.recvTime - The uplink message time recorded by the LoRaWAN network server\n * @returns {output} - The decoded object\n */\nfunction decodeDataMessage(input) {\n // Output\n var output = createOutputObject();\n\n // data message type\n output.data.messageType = input.bytes[0];\n\n // current configuration id\n output.data.configurationId = input.bytes[1];\n\n output.data.measurement = {};\n output.data.measurement.channels = [];\n\n // pressure - channel 0\n var pressure = {};\n pressure.channelId = 0;\n pressure.channelName = CHANNEL_NAMES_DICTIONARY[pressure.channelId];\n pressure.value = getCalculatedPressure(input.bytes[3] << 8 | input.bytes[4]);\n output.data.measurement.channels.push(pressure);\n\n // temperature - channel 1\n var temperature = {};\n temperature.channelId = 1;\n temperature.channelName = CHANNEL_NAMES_DICTIONARY[temperature.channelId];\n temperature.value = getCalculatedTemperature(input.bytes[5] << 8 | input.bytes[6]);\n output.data.measurement.channels.push(temperature);\n\n // battery voltage - channel x\n var batteryVoltage = {};\n batteryVoltage.channelId = 2;\n batteryVoltage.channelName = CHANNEL_NAMES_DICTIONARY[batteryVoltage.channelId];\n // battery voltage in V as single not double\n batteryVoltage.value = (input.bytes[2] / 10);\n output.data.measurement.channels.push(batteryVoltage);\n \n return output;\n}\n\n/**\n * Decodes a process alarm 03 into an object\n * @access private\n * @param {Object} input - An object provided by the IoT Flow framework\n * @param {number[]} input.bytes - Array of bytes represented as numbers as it has been sent from the device\n * @param {number} input.fPort - The Port Field on which the uplink has been sent\n * @param {Date} input.recvTime - The uplink message time recorded by the LoRaWAN network server\n * @returns {output} - The decoded object\n */\nfunction decodeProcessAlarm(input) {\n\n var output = createOutputObject();\n output.data.processAlarms = [];\n\n // data message type\n output.data.messageType = input.bytes[0];\n\n // current configuration id\n output.data.configurationId = input.bytes[1];\n\n for (var byteIndex = 2, alarmCounter = 0; byteIndex < input.bytes.length; byteIndex += 3, alarmCounter++) {\n output.data.processAlarms[alarmCounter] = {};\n\n // Alarm event 0 = triggered, 1 = disappeared\n output.data.processAlarms[alarmCounter].event = (input.bytes[byteIndex] & 0x80) >> 7;\n output.data.processAlarms[alarmCounter].eventName = ALARM_EVENT_NAMES_DICTIONARY[output.data.processAlarms[alarmCounter].event];\n\n // Alarm channel 0 = pressure, 1 = device temperature\n output.data.processAlarms[alarmCounter].channelId = (input.bytes[byteIndex] & 0x78) >> 3;\n output.data.processAlarms[alarmCounter].channelName = ALARM_CHANNEL_NAMES_DICTIONARY[output.data.processAlarms[alarmCounter].channelId];\n\n // Alarm channel 0 = falling thresh, 1 = rising thresh, 2 = fal slope, 3 = rising slope, 4 = fall thresh delay, 5 = rise thresh delay\n output.data.processAlarms[alarmCounter].alarmType = (input.bytes[byteIndex] & 0x07);\n output.data.processAlarms[alarmCounter].alarmTypeName = PROCESS_ALARM_TYPE_NAMES_DICTIONARY[output.data.processAlarms[alarmCounter].alarmType];\n\n // Alarm value\n output.data.processAlarms[alarmCounter].value = getRealValueByChannelNumberAndAlarmType(output.data.processAlarms[alarmCounter].channelId, \n output.data.processAlarms[alarmCounter].alarmType,\n input.bytes[byteIndex + 1] << 8 | input.bytes[byteIndex + 2]);\n \n if (output.data.processAlarms[alarmCounter].value == ERROR_VALUE) {\n output = addWarningMessage(output, \"Invalid data for \" + output.data.processAlarms[alarmCounter].channelName + \"channel\");\n }\n\n }\n\n return output;\n}\n\n/**\n * Decodes a technical alarm 04 into an object\n * @access private\n * @param {Object} input - An object provided by the IoT Flow framework\n * @param {number[]} input.bytes - Array of bytes represented as numbers as it has been sent from the device\n * @param {number} input.fPort - The Port Field on which the uplink has been sent\n * @param {Date} input.recvTime - The uplink message time recorded by the LoRaWAN network server\n * @returns {output} - The decoded object\n */\nfunction decodeTechnicalAlarm(input) {\n // Output\n var output = createOutputObject();\n output.data.technicalAlarms = [];\n\n // data message type\n output.data.messageType = input.bytes[0];\n\n // current configuration id\n output.data.configurationId = input.bytes[1];\n\n for (var byteIndex = 2, alarmCounter = 0; byteIndex < input.bytes.length; byteIndex += 3, alarmCounter++) {\n output.data.technicalAlarms[alarmCounter] = {};\n\n // Alarm event 0 = triggered, 1 = disappeared\n output.data.technicalAlarms[alarmCounter].event = (input.bytes[byteIndex] & 0x80) >> 7;\n output.data.technicalAlarms[alarmCounter].eventName = ALARM_EVENT_NAMES_DICTIONARY[output.data.technicalAlarms[alarmCounter].event];\n\n // Alarm channel 0 = pressure, 1 = device temperature\n output.data.technicalAlarms[alarmCounter].channelId = (input.bytes[byteIndex] & 0x78) >> 3;\n output.data.technicalAlarms[alarmCounter].channelName = ALARM_CHANNEL_NAMES_DICTIONARY[output.data.technicalAlarms[alarmCounter].channelId];\n\n // Alarm channel 1 = general failure\n output.data.technicalAlarms[alarmCounter].causeOfFailure = (input.bytes[byteIndex] & 0x07);\n output.data.technicalAlarms[alarmCounter].causeOfFailureName = TECHNICAL_ALARM_CAUSE_OF_FAILURE_NAMES_DICTIONARY[output.data.technicalAlarms[alarmCounter].causeOfFailure];\n\n // Alarm value\n output.data.technicalAlarms[alarmCounter].value = getRealValueByChannelNumberAndAlarmType(output.data.technicalAlarms[alarmCounter].channelId, 0,\n input.bytes[byteIndex + 1] << 8 | input.bytes[byteIndex + 2]);\n\n if (output.data.technicalAlarms[alarmCounter].value == ERROR_VALUE) {\n output = addWarningMessage(output, \"Invalid data for \" + output.data.technicalAlarms[alarmCounter].channelName + \"channel\");\n }\n }\n\n return output;\n}\n\n/**\n * Decodes a device alarm 05 into an object\n * @access private\n * @param {Object} input - An object provided by the IoT Flow framework\n * @param {number[]} input.bytes - Array of bytes represented as numbers as it has been sent from the device\n * @param {number} input.fPort - The Port Field on which the uplink has been sent\n * @param {Date} input.recvTime - The uplink message time recorded by the LoRaWAN network server\n * @returns {output} - The decoded object\n */\nfunction decodeDeviceAlarm(input) {\n // Output\n var output = createOutputObject();\n\n // data message type\n output.data.messageType = input.bytes[0];\n\n // current configuration id\n output.data.configurationId = input.bytes[1];\n\n // Create deviceAlarm\n output.data.deviceAlarm = {};\n\n // Alarm event 0 = triggered, 1 = disappeared\n output.data.deviceAlarm.event = (input.bytes[2] & 0x80) >> 7;\n output.data.deviceAlarm.eventName = ALARM_EVENT_NAMES_DICTIONARY[output.data.deviceAlarm.event];\n\n // Generic or device dependent 1 = device dependent \n output.data.deviceAlarm.causeOfFailure = (input.bytes[2] & 0x60) >> 6;\n output.data.deviceAlarm.causeOfFailureName = DEVICE_ALARM_CAUSE_OF_FAILURE_NAMES_DICTIONARY[output.data.deviceAlarm.causeOfFailure];\n\n // Alarm type 0 = low temperature alarm\n output.data.deviceAlarm.alarmType = (input.bytes[2] & 0x1f);\n output.data.deviceAlarm.alarmTypeName = DEVICE_ALARM_TYPE_NAMES_DICTIONARY[output.data.deviceAlarm.alarmType];\n\n // The alarm value is an int8, but we have an int32, so we shift 24 bits to the left and 24 bits to the right \n // and as a result we get an 8 bit integer in a 32 bit integer\n output.data.deviceAlarm.value = input.bytes[3] << 24 >> 24;\n\n return output;\n}\n\n/**\n * Decodes a keep alive message 08 into an object\n * @access private\n * @param {Object} input - An object provided by the IoT Flow framework\n * @param {number[]} input.bytes - Array of bytes represented as numbers as it has been sent from the device\n * @param {number} input.fPort - The Port Field on which the uplink has been sent\n * @param {Date} input.recvTime - The uplink message time recorded by the LoRaWAN network server\n * @returns {output} - The decoded object\n */\nfunction decodeKeepAliveMessage(input) {\n // Output\n var output = createOutputObject();\n\n // data message type\n output.data.messageType = input.bytes[0];\n\n // current configuration id\n output.data.configurationId = input.bytes[1];\n\n output.data.deviceStatistic = {};\n // Battery level event indicator\n output.data.deviceStatistic.batteryLevelNewEvent = (input.bytes[2] & 0x80) >> 7 ? true : false;\n\n // battery level in percent\n output.data.deviceStatistic.batteryLevelPercent = input.bytes[2] & 0x7f;\n\n return output;\n}\n\n/**\n * Decodes a device identification message 07 into an object\n * @access private\n * @param {Object} input - An object provided by the IoT Flow framework\n * @param {number[]} input.bytes - Array of bytes represented as numbers as it has been sent from the device\n * @param {number} input.fPort - The Port Field on which the uplink has been sent\n * @param {Date} input.recvTime - The uplink message time recorded by the LoRaWAN network server\n * @returns {output} - The decoded object\n */\nfunction decodeDeviceIdentification(input) {\n // Output\n var output = createOutputObject();\n\n // Data message type \n output.data.messageType = input.bytes[0];\n\n // Configuration id\n output.data.configurationId = input.bytes[1];\n\n output.data.deviceInformation = {};\n // Product id raw\n output.data.deviceInformation.productId = input.bytes[2];\n\n // Wireless module type\n output.data.deviceInformation.productIdName = input.bytes[2] == 10 ? \"PGW23.100.11\" : input.bytes[2];\n\n // Wireless module firmware version\n output.data.deviceInformation.wirelessModuleFirmwareVersion = ((input.bytes[3] >> 4) & 0x0f).toString() + \".\" + (input.bytes[3] & 0x0f).toString() + \".\" + (input.bytes[4]).toString();\n\n // Wireless module hardware version\n output.data.deviceInformation.wirelessModuleHardwareVersion = ((input.bytes[5] >> 4) & 0x0f).toString() + \".\" + (input.bytes[5] & 0x0f).toString() + \".\" + (input.bytes[6]).toString();\n\n // Sensor module firmware version\n output.data.deviceInformation.sensorModuleFirmwareVersion = ((input.bytes[7] >> 4) & 0x0f).toString() + \".\" + (input.bytes[7] & 0x0f).toString() + \".\" + (input.bytes[8]).toString();\n\n // Sensor module hardware version\n output.data.deviceInformation.sensorModuleHardwareVersion = ((input.bytes[9] >> 4) & 0x0f).toString() + \".\" + (input.bytes[9] & 0x0f).toString() + \".\" + (input.bytes[10]).toString();\n\n // Sensor serial number\n output.data.deviceInformation.serialNumber = \"\";\n for (var i = 11; i < 22; i++) {\n if (input.bytes[i] == 0) break;\n output.data.deviceInformation.serialNumber += String.fromCharCode(input.bytes[i]);\n }\n\n // Pressure type\n switch (input.bytes[22]) {\n case 1:\n output.data.deviceInformation.pressureType = \"absolute\";\n break;\n\n case 2:\n output.data.deviceInformation.pressureType = \"relative\";\n break;\n\n case 3:\n output.data.deviceInformation.pressureType = \"differential\";\n break;\n\n default:\n output.data.deviceInformation.pressureType = \"unknown\";\n break;\n }\n\n // Min range pressure\n output.data.deviceInformation.measurementRangeStartPressure = convertHexToFloatIEEE754(input.bytes[26].toString(16).padStart(2, \"0\") + input.bytes[25].toString(16).padStart(2, \"0\") + input.bytes[24].toString(16).padStart(2, \"0\") + input.bytes[23].toString(16).padStart(2, \"0\"));\n output.data.deviceInformation.measurementRangeStartPressure = Number(output.data.deviceInformation.measurementRangeStartPressure.toFixed(6));\n\n // Max range pressure\n output.data.deviceInformation.measurementRangeEndPressure = convertHexToFloatIEEE754(input.bytes[30].toString(16).padStart(2, \"0\") + input.bytes[29].toString(16).padStart(2, \"0\") + input.bytes[28].toString(16).padStart(2, \"0\") + input.bytes[27].toString(16).padStart(2, \"0\"));\n output.data.deviceInformation.measurementRangeEndPressure = Number(output.data.deviceInformation.measurementRangeEndPressure.toFixed(6));\n\n // Min range device temperature\n output.data.deviceInformation.measurementRangeStartDeviceTemperature = convertHexToFloatIEEE754(input.bytes[34].toString(16).padStart(2, \"0\") + input.bytes[33].toString(16).padStart(2, \"0\") + input.bytes[32].toString(16).padStart(2, \"0\") + input.bytes[31].toString(16).padStart(2, \"0\"));\n output.data.deviceInformation.measurementRangeStartDeviceTemperature = Number(output.data.deviceInformation.measurementRangeStartDeviceTemperature.toFixed(6));\n\n // Max range device temperature\n output.data.deviceInformation.measurementRangeEndDeviceTemperature = convertHexToFloatIEEE754(input.bytes[38].toString(16).padStart(2, \"0\") + input.bytes[37].toString(16).padStart(2, \"0\") + input.bytes[36].toString(16).padStart(2, \"0\") + input.bytes[35].toString(16).padStart(2, \"0\"));\n output.data.deviceInformation.measurementRangeEndDeviceTemperature = Number(output.data.deviceInformation.measurementRangeEndDeviceTemperature.toFixed(6));\n\n // Unit pressure\n output.data.deviceInformation.pressureUnit = input.bytes[39];\n output.data.deviceInformation.pressureUnitName = returnPhysicalUnitFromId(input.bytes[39]);\n\n // Unit pressure\n output.data.deviceInformation.deviceTemperatureUnit = input.bytes[40];\n output.data.deviceInformation.deviceTemperatureUnitName = returnPhysicalUnitFromId(input.bytes[40]);\n\n return output;\n}\n// ***********************************************************************************\n// Additional Functions Section\n// ***********************************************************************************\n/**\n * Converts a hex string number to float number follows the IEEE 754 standard and it's ES5 compatible\n * @access private\n * @param {string} hexString - Float as string \"3.141\"\n * @return {number} - returns a float\n * @see https://gist.github.com/Jozo132/2c0fae763f5dc6635a6714bb741d152f 2022 by Jozo132 \n */\nfunction convertHexToFloatIEEE754(hexString) {\n var int = parseInt(hexString, 16);\n if (int > 0 || int < 0) {\n var sign = (int >>> 31) ? -1 : 1;\n var exp = (int >>> 23 & 0xff) - 127;\n var mantissa = ((int & 0x7fffff) + 0x800000).toString(2);\n var float32 = 0\n for (var i = 0; i < mantissa.length; i += 1) { float32 += parseInt(mantissa[i]) ? Math.pow(2, exp) : 0; exp-- }\n return float32 * sign;\n } else return 0\n}\n\n/**\n * Checks the user defined measurement ranges\n * @access private\n * @param {output} output - Output object\n * @return {output} - Returns if the ranges are correct defined\n */\nfunction checkMeasurementRanges(output) {\n if (typeof PRESSURE_RANGE_START === 'undefined') {\n output = addErrorMessage(output, \"The PRESSURE_RANGE_START was not set.\");\n }\n\n if (typeof PRESSURE_RANGE_END === 'undefined') {\n output = addErrorMessage(output, \"The PRESSURE_RANGE_END was not set.\");\n }\n\n if (typeof DEVICE_TEMPERATURE_RANGE_START === 'undefined') {\n output = addErrorMessage(output, \"The DEVICE_TEMPERATURE_RANGE_START was not set.\");\n }\n\n if (typeof DEVICE_TEMPERATURE_RANGE_END === 'undefined') {\n output = addErrorMessage(output, \"The DEVICE_TEMPERATURE_RANGE_END was not set.\");\n }\n\n if (PRESSURE_RANGE_START >= PRESSURE_RANGE_END) {\n output = addErrorMessage(output, \"The PRESSURE_RANGE_START must not be greater or equal to PRESSURE_RANGE_END, \" + PRESSURE_RANGE_START + \" >= \" + PRESSURE_RANGE_END + \". \");\n }\n\n if (DEVICE_TEMPERATURE_RANGE_START >= DEVICE_TEMPERATURE_RANGE_END) {\n output = addErrorMessage(output, \"The DEVICE_TEMPERATURE_RANGE_START must not be greater or equal to DEVICE_TEMPERATURE_RANGE_END, \" + DEVICE_TEMPERATURE_RANGE_START + \" >= \" + DEVICE_TEMPERATURE_RANGE_END + \".\");\n }\n\n return output;\n}\n\n/**\n * Add warning to output object\n * @param {output} output\n * @param {string} warningMessage\n * @access private \n */\nfunction addWarningMessage(output, warningMessage) {\n // use only functional supported by ECMA-262 5th edition. The nullish assign and .at are not supported. output.warnings ??= [];\n output.warnings = output.warnings || [];\n output.warnings.push(DEVICE_NAME + \" (JS): \" + warningMessage);\n return output;\n}\n\n/**\n * Add private to output object\n * @param {output} output\n * @param {string} errorMessage\n * @access private \n */\nfunction addErrorMessage(output, errorMessage) {\n output.errors = output.errors || [];\n output.errors.push(DEVICE_NAME + \" (JS): \" + errorMessage);\n return output;\n}\n\n/**\n * Create an empty output object\n * @returns {output} - Returns an output object\n * @access private\n */\nfunction createOutputObject() {\n return {\n data: {},\n }\n}\n\n\n/**\n * Set measurement ranges only for test purposes\n * @access protected\n * @param {Number} pressureRangeStart range start\n * @param {Number} pressureRangeEnd range end\n * @param {Number} temperatureRangeStart range start\n * @param {Number} temperatureRangeEnd range end\n */\nfunction setMeasurementRanges(pressureRangeStart, pressureRangeEnd, temperatureRangeStart, temperatureRangeEnd) {\n PRESSURE_RANGE_START = pressureRangeStart;\n PRESSURE_RANGE_END = pressureRangeEnd;\n DEVICE_TEMPERATURE_RANGE_START = temperatureRangeStart;\n DEVICE_TEMPERATURE_RANGE_END = temperatureRangeEnd;\n}\n\n\n/**\n * Returns the real physical value of the channel value based on measurement range\n * @access private\n * @param {Number} channelValue channel value as integer \n * @param {Number} measurementRangeStart range start\n * @param {Number} measurementRangeEnd range end\n * @param {Number} measuringRangeStart range start (MRS)\n * @param {Number} measuringRangeEnd range end (MRE)\n * @return {Number} Returns real physical value e.g. 10 °C\n */\nfunction getCalculatedValue(channelValue, measurementRangeStart, measurementRangeEnd, measuringRangeStart, measuringRangeEnd) {\n var calculatedValue = (channelValue - measuringRangeStart) * ((measurementRangeEnd - measurementRangeStart) / (measuringRangeEnd - measuringRangeStart)) + measurementRangeStart;\n // round to the third number after the comma\n return Math.round(calculatedValue * 1000) / 1000;\n}\n\n/**\n * Returns the real physical value of pressure based on measurement range\n * @param {Number} channelValue \n * @access private\n */\nfunction getCalculatedPressure(channelValue) {\n return getCalculatedValue(channelValue, PRESSURE_RANGE_START, PRESSURE_RANGE_END, GENERIC_DATA_CHANNEL_RANGE_START, GENERIC_DATA_CHANNEL_RANGE_END);\n}\n\n/**\n * Returns the real physical value of temperature based on measurement range\n * @param {Number} channelValue \n * @access private\n */\nfunction getCalculatedTemperature(channelValue) {\n return getCalculatedValue(channelValue, DEVICE_TEMPERATURE_RANGE_START, DEVICE_TEMPERATURE_RANGE_END, GENERIC_DATA_CHANNEL_RANGE_START, GENERIC_DATA_CHANNEL_RANGE_END);\n}\n\n/**\n * Returns the real physical value of the channel number based on measurement range\n * @param {Number} channelValue \n * @param {Number} alarmType \n * @param {Number} channelNumber \n * @access private\n */\nfunction getRealValueByChannelNumberAndAlarmType(channelNumber, alarmType, channelValue) {\n if (channelNumber == 0) // pressure channel\n {\n if (alarmType == 3 || alarmType == 4) {\n return getSlopeValue(channelValue, PRESSURE_RANGE_START, PRESSURE_RANGE_END);\n }\n else {\n return getThresholdValue(channelValue, PRESSURE_RANGE_START, PRESSURE_RANGE_END);\n }\n }\n else if (channelNumber == 1) // temperature channel\n {\n if (alarmType == 3 || alarmType == 4) {\n return getSlopeValue(channelValue, DEVICE_TEMPERATURE_RANGE_START, DEVICE_TEMPERATURE_RANGE_END);\n }\n else {\n return getThresholdValue(channelValue, DEVICE_TEMPERATURE_RANGE_START, DEVICE_TEMPERATURE_RANGE_END);\n }\n }\n\n return ERROR_VALUE;\n}\n\n/**\n * Returns the real threshold value of pressure based on measurement range (measurend)\n * @param {Number} channelValue \n * @param {Number} measurementRangeStart range start\n * @param {Number} measurementRangeEnd range end\n * @access private\n */\nfunction getThresholdValue(channelValue, measurementRangeStart, measurementRangeEnd) {\n return getCalculatedValue(channelValue, measurementRangeStart, measurementRangeEnd, 2500, 12500);\n}\n\n/**\n * Returns the real physical value of pressure based on measurement range (measurend/minute)\n * @param {Number} channelValue \n * @param {Number} measurementRangeStart range start\n * @param {Number} measurementRangeEnd range end\n * @access private\n */\nfunction getSlopeValue(channelValue, measurementRangeStart, measurementRangeEnd) {\n return getCalculatedValue(channelValue, measurementRangeStart, measurementRangeEnd, 0, 10000);\n}\n\n/**\n * To convert a hex encoded string to a integer array\n * @param {string} hexEncodedString\n * @access private\n */\nfunction convertHexStringToBytes(hexEncodedString) {\n if (hexEncodedString.startsWith(\"0x\")) {\n hexEncodedString = hexEncodedString.slice(2);\n }\n\n // remove spaces\n hexEncodedString = hexEncodedString.replace(/\\s/g, '');\n\n var bytes = [];\n\n // convert byte to byte (2 characters are 1 byte)\n for (var i = 0; i < hexEncodedString.length; i += 2) {\n // extract 2 characters\n var hex = hexEncodedString.substr(i, 2);\n\n // convert hex pair to integer\n var intValue = parseInt(hex, 16);\n\n bytes.push(intValue);\n }\n\n return bytes;\n}\n\n/**\n * To convert a base64 encoded string to a integer array\n * @param {string} base64EncodedString\n * @access private\n */\nfunction convertBase64StringToBytes(base64EncodedString) {\n var bytes = [];\n\n // convert base64 to string\n var decodedBytes = Buffer.from(base64EncodedString, 'base64')\n\n // convert byte to byte (2 characters are 1 byte)\n for (var i = 0; i < decodedBytes.length; i++) {\n // convert byte to integer\n var intValue = decodedBytes[i];\n\n bytes.push(intValue);\n }\n\n return bytes;\n}\n\n/**\n * Returns the printable name of a measurand for a LPP supporting devices e.g.: 1 = \"Temperature\"\n * @access private\n * @param {Number} id Identifier as integer \n * @return {string} Returns the printable name of a physical unit PGW23.100.11 e.g.: 1 = \"mBar\"\n */\nfunction returnPhysicalUnitFromId(id) {\n switch (id) {\n case 1:\n return \"inH2O\";\n case 2:\n return \"inHg\";\n case 3:\n return \"ftH2O\";\n case 4:\n return \"mmH2O\";\n case 5:\n return \"mmHg\";\n case 6:\n return \"psi\";\n case 7:\n return \"bar\";\n case 8:\n return \"mbar\";\n case 9:\n return \"g/cm²\";\n case 10:\n return \"kg/cm²\";\n case 11:\n return \"Pa\";\n case 12:\n return \"kPa\";\n case 13:\n return \"Torr\";\n case 14:\n return \"at\";\n case 145:\n return \"inH2O (60 °F)\";\n case 170:\n return \"cmH2O (4 °C)\";\n case 171:\n return \"mH2O (4 °C)\";\n case 172:\n return \"cmHg\";\n case 173:\n return \"lb/ft²\";\n case 174:\n return \"hPa\";\n case 175:\n return \"psia\";\n case 176:\n return \"kg/m²\";\n case 177:\n return \"ftH2O (4 °C)\";\n case 178:\n return \"ftH2O (60 °F)\";\n case 179:\n return \"mHg\";\n case 180:\n return \"Mpsi\";\n case 237:\n return \"MPa\";\n case 238:\n return \"inH2O (4 °C)\";\n case 239:\n return \"mmH2O (4 °C)\";\n case 32:\n return \"°C\";\n case 33:\n return \"°F\";\n default:\n return \"Unknown\";\n }\n}\n\n// ***********************************************************************************\n// Export functions Section\n// ***********************************************************************************\nif (typeof exports !== 'undefined') {\n exports.decodeUplink = decodeUplink;\n exports.setMeasurementRanges = setMeasurementRanges;\n exports.decodeHexString = decodeHexString;\n exports.decodeBase64String = decodeBase64String;\n}",
"environment": "javascript",
"storage": "",
"version": "1.0"
},
"properties": {
"source": {
"data": {
"payload": "{{payload}}",
"payload_function": "getPluginSource",
"payload_type": "source_payload",
"resource": "uplink",
"source": "resource",
"update": "events"
},
"default": {
"source": "value"
},
"enabled": true
},
"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": {
"name": "Wika Dashboard",
"placeholders": {
"sources": []
},
"properties": {
"background_image": "#132533",
"border_radius": "7",
"hide_header": false,
"template": true
},
"tabs": [
{
"icon": "fas fa-tachometer-alt",
"widgets": [
{
"layout": {
"col": 0,
"row": 0,
"sizeX": 1,
"sizeY": 13
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
}
},
"properties": {
"refresh_interval": 0
},
"sources": [
{
"color": "#1abc9c",
"image_url": "https://www.wika.com/media/Images/Product_700x700/Pressure/pgw23.100_en_co_rs_w410_h410_image.jpg",
"name": "Source 1",
"source": "image_url"
}
],
"type": "image"
},
{
"layout": {
"col": 3,
"row": 0,
"sizeX": 3,
"sizeY": 13
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
}
},
"properties": {
"mapType": "satellite",
"showClustering": true,
"showConnected": true,
"showDisconnected": true,
"showOptions": false,
"showSearch": true
},
"sources": [
{
"color": "#1abc9c",
"name": "Source 1"
}
],
"type": "assets_map"
},
{
"api": {},
"layout": {
"col": 2,
"row": 0,
"sizeX": 1,
"sizeY": 13
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
},
"title": "Temperature",
"updateTs": 1752583769912
},
"properties": {
"alignTimeSeries": false,
"colors": [
{
"color": "#ff9500",
"max": 80,
"min": 70
},
{
"color": "#d40202",
"max": 100,
"min": 80
}
],
"dataAppend": false,
"majorTicks": 10,
"max": 100,
"min": 0,
"options": "var options = {\n series: [\n {\n type: 'gauge',\n startAngle: 180,\n endAngle: 0,\n min: 0,\n max: 240,\n splitNumber: 12,\n itemStyle: {\n color: '#58D9F9',\n shadowColor: 'rgba(0,138,255,0.45)',\n shadowBlur: 10,\n shadowOffsetX: 2,\n shadowOffsetY: 2\n },\n progress: {\n show: true,\n roundCap: true,\n width: 18\n },\n pointer: {\n icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',\n length: '75%',\n width: 16,\n offsetCenter: [0, '5%']\n },\n axisLine: {\n roundCap: true,\n lineStyle: {\n width: 18\n }\n },\n axisTick: {\n splitNumber: 2,\n lineStyle: {\n width: 2,\n color: '#999'\n }\n },\n splitLine: {\n length: 12,\n lineStyle: {\n width: 3,\n color: '#999'\n }\n },\n axisLabel: {\n distance: 30,\n color: '#999',\n fontSize: 20\n },\n title: {\n show: false\n },\n detail: {\n backgroundColor: '#fff',\n borderColor: '#999',\n borderWidth: 2,\n width: '60%',\n lineHeight: 40,\n height: 40,\n borderRadius: 8,\n offsetCenter: [0, '35%'],\n valueAnimation: true,\n formatter: function (value) {\n return '{value|' + value.toFixed(0) + '}{unit|km/h}';\n },\n rich: {\n value: {\n fontSize: 50,\n fontWeight: 'bolder',\n color: '#777'\n },\n unit: {\n fontSize: 20,\n color: '#999',\n padding: [0, 0, -20, 10]\n }\n }\n },\n data: [\n {\n value: 100\n }\n ]\n }\n ]\n};",
"plateColor": "#ffffff",
"realTimeUpdate": true,
"showValue": true,
"textColor": "#1E313E",
"tickColor": "#000000",
"unit": "ºC"
},
"sources": [
{
"aggregation": {},
"bucket": {
"backend": "mongodb",
"id": "pgw23_100_data_bucket",
"mapping": "temperature",
"tags": {
"device": [],
"group": []
}
},
"color": "#1abc9c",
"name": "pressure",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "latest",
"period": "latest",
"value": 6
}
}
],
"type": "tachometer"
},
{
"api": {},
"layout": {
"col": 1,
"row": 0,
"sizeX": 1,
"sizeY": 13
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
},
"title": "Pressure",
"updateTs": 1752583769912
},
"properties": {
"alignTimeSeries": false,
"colors": [
{
"color": "#ff9500",
"max": 8,
"min": 6
},
{
"color": "#d40202",
"max": 10,
"min": 8
}
],
"dataAppend": false,
"majorTicks": 1,
"max": 10,
"min": 0,
"options": "var options = {\n series: [\n {\n type: 'gauge',\n startAngle: 180,\n endAngle: 0,\n min: 0,\n max: 240,\n splitNumber: 12,\n itemStyle: {\n color: '#58D9F9',\n shadowColor: 'rgba(0,138,255,0.45)',\n shadowBlur: 10,\n shadowOffsetX: 2,\n shadowOffsetY: 2\n },\n progress: {\n show: true,\n roundCap: true,\n width: 18\n },\n pointer: {\n icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',\n length: '75%',\n width: 16,\n offsetCenter: [0, '5%']\n },\n axisLine: {\n roundCap: true,\n lineStyle: {\n width: 18\n }\n },\n axisTick: {\n splitNumber: 2,\n lineStyle: {\n width: 2,\n color: '#999'\n }\n },\n splitLine: {\n length: 12,\n lineStyle: {\n width: 3,\n color: '#999'\n }\n },\n axisLabel: {\n distance: 30,\n color: '#999',\n fontSize: 20\n },\n title: {\n show: false\n },\n detail: {\n backgroundColor: '#fff',\n borderColor: '#999',\n borderWidth: 2,\n width: '60%',\n lineHeight: 40,\n height: 40,\n borderRadius: 8,\n offsetCenter: [0, '35%'],\n valueAnimation: true,\n formatter: function (value) {\n return '{value|' + value.toFixed(0) + '}{unit|km/h}';\n },\n rich: {\n value: {\n fontSize: 50,\n fontWeight: 'bolder',\n color: '#777'\n },\n unit: {\n fontSize: 20,\n color: '#999',\n padding: [0, 0, -20, 10]\n }\n }\n },\n data: [\n {\n value: 100\n }\n ]\n }\n ]\n};",
"plateColor": "#ffffff",
"realTimeUpdate": true,
"showValue": true,
"textColor": "#1E313E",
"tickColor": "#000000",
"unit": "bar"
},
"sources": [
{
"aggregation": {},
"bucket": {
"backend": "mongodb",
"id": "pgw23_100_data_bucket",
"mapping": "pressure",
"tags": {
"device": [],
"group": []
}
},
"color": "#1abc9c",
"name": "pressure",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "latest",
"period": "latest",
"value": 6
}
}
],
"type": "tachometer"
},
{
"api": {},
"layout": {
"col": 3,
"row": 13,
"sizeX": 3,
"sizeY": 8
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
},
"title": "Temperature"
},
"properties": {
"alignTimeSeries": false,
"dataAppend": false,
"options": "var options = {\n chart: {\n type: 'line',\n zoom: {\n type: 'x',\n enabled: true,\n autoScaleYaxis: true,\n allowMouseWheelZoom: false\n },\n toolbar: {\n autoSelected: 'zoom'\n }\n },\n stroke: {\n curve: 'straight',\n width: 4\n },\n grid: {\n row: {\n colors: ['#f3f3f3', 'transparent'],\n opacity: 0.5\n },\n },\n xaxis: {\n type: 'datetime',\n tooltip: {\n enabled: false\n },\n labels: {\n datetimeUTC: false\n }\n },\n yaxis: {\n labels: {\n \"formatter\": function (val) {\n if ( val !== null && typeof val !== 'undefined' )\n return val.toFixed(2);\n }\n }\n },\n tooltip: {\n x: {\n format: 'dd/MM/yyyy HH:mm:ss'\n }\n }\n};\n",
"realTimeUpdate": true
},
"sources": [
{
"aggregation": {},
"bucket": {
"backend": "mongodb",
"id": "pgw23_100_data_bucket",
"mapping": "temperature",
"tags": {
"device": [
"wika_pgw23_100_12345"
],
"group": []
}
},
"color": "#d3aa17",
"name": "Temperature",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 6
}
}
],
"type": "apex_charts"
},
{
"layout": {
"col": 0,
"row": 13,
"sizeX": 3,
"sizeY": 8
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"currentSubtitle": "",
"currentTitle": "Pressure",
"showOffline": {
"type": "none"
},
"title": "Pressure"
},
"properties": {
"alignTimeSeries": false,
"dataAppend": false,
"options": "var options = {\n chart: {\n type: 'line',\n zoom: {\n type: 'x',\n enabled: true,\n autoScaleYaxis: true,\n allowMouseWheelZoom: false\n },\n toolbar: {\n autoSelected: 'zoom'\n }\n },\n stroke: {\n curve: 'straight',\n width: 4\n },\n grid: {\n row: {\n colors: ['#f3f3f3', 'transparent'],\n opacity: 0.5\n },\n },\n xaxis: {\n type: 'datetime',\n tooltip: {\n enabled: false\n },\n labels: {\n datetimeUTC: false\n }\n },\n yaxis: {\n labels: {\n \"formatter\": function (val) {\n if ( val !== null && typeof val !== 'undefined' )\n return val.toFixed(2);\n }\n }\n },\n tooltip: {\n x: {\n format: 'dd/MM/yyyy HH:mm:ss'\n }\n }\n};\n",
"realTimeUpdate": true
},
"sources": [
{
"aggregation": {},
"bucket": {
"backend": "mongodb",
"id": "pgw23_100_data_bucket",
"mapping": "pressure",
"tags": {
"device": [
"wika_pgw23_100_12345"
],
"group": []
}
},
"color": "#1abc9c",
"name": "Pressure",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 6
}
}
],
"type": "apex_charts"
}
]
}
]
}
}
]
}
}
]
}
}