Plugin file
Plugin configuration file
{
"name": "wika-pew1000",
"version": "1.0.0",
"description": "Wireless pressure sensor",
"author": "Thinger.io",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/thinger-io/plugins.git",
"directory": "wika-pew1000"
},
"metadata": {
"name": "WIKA PEW-1000",
"description": "Wireless pressure sensor",
"image": "https://s3.eu-west-1.amazonaws.com/thinger.io.files/plugins/wika-pew1000/img/wikapew1000.png"
},
"resources": {
"products": [
{
"description": "Wireless pressure sensor",
"enabled": true,
"name": "Wika PEW1000",
"product": "wika_pew1000",
"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": {
"enabled": true,
"handle_connectivity": true,
"request": {
"data": {
"payload": "{{payload}}",
"payload_function": "",
"payload_type": "source_payload",
"target": "resource_stream"
}
},
"response": {
"data": {}
}
}
},
"autoprovisions": {
"PEW1000_autoprovisioning": {
"config": {
"mode": "pattern",
"pattern": "wika_pew1000_.*"
},
"enabled": true
}
},
"buckets": {
"pew1000_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 PEW-1000 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 = 16;\nvar DEVICE_TEMPERATURE_RANGE_START = -20;\nvar DEVICE_TEMPERATURE_RANGE_END = 80;\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 */\nfunction decodeUplink(input) {\n // Ignore Gateway mesagges\n if (input[\"cmd\"] != \"rx\") {\n return;\n }\n\n let battery = input[\"bat\"];\n let freq = input[\"freq\"]\n\n console.log(\"PPPBDA \"+JSON.stringify(input));\n\n const hex = typeof input === 'string' ? input : input.data || '';\n const bytes = hex.match(/.{1,2}/g).map(b => parseInt(b, 16));\n input.bytes = bytes;\n\n const output = decode(input);\n\n const flat = {};\n\n for (const key in output.data) {\n if (typeof output.data[key] !== 'object' || output.data[key] === null) {\n flat[key] = output.data[key];\n }\n }\n\n if (output.data.measurement && Array.isArray(output.data.measurement.channels)) {\n output.data.measurement.channels.forEach(channel => {\n const name = channel.channelName?.toLowerCase().replace(/\\s+/g, '_') || `channel_${channel.channelId}`;\n flat[name] = channel.value;\n });\n }\n\n if (output.data.deviceInformation && typeof output.data.deviceInformation === 'object') {\n for (const key in output.data.deviceInformation) {\n flat[`deviceInformation_${key}`] = output.data.deviceInformation[key];\n }\n }\n\n if (output.data.deviceStatistic && typeof output.data.deviceStatistic === 'object') {\n for (const key in output.data.deviceStatistic) {\n flat[`deviceStatistic_${key}`] = output.data.deviceStatistic[key];\n }\n }\n\n if (output.data.deviceAlarm && typeof output.data.deviceAlarm === 'object') {\n for (const key in output.data.deviceAlarm) {\n flat[`deviceAlarm_${key}`] = output.data.deviceAlarm[key];\n }\n }\n\n if (output.data.processAlarms && Array.isArray(output.data.processAlarms)) {\n output.data.processAlarms.forEach((alarm, i) => {\n for (const key in alarm) {\n flat[`processAlarm_${i}_${key}`] = alarm[key];\n }\n });\n }\n\n if (output.data.technicalAlarms && Array.isArray(output.data.technicalAlarms)) {\n output.data.technicalAlarms.forEach((alarm, i) => {\n for (const key in alarm) {\n flat[`technicalAlarm_${i}_${key}`] = alarm[key];\n }\n });\n }\n\n if (Object.keys(flat).length === 0) {\n return;\n }\n\n //Add potentially interesting values\n flat[\"battery (%)\"] = parseInt(battery) * (100 / 254);\n flat[\"frecuency\"] = freq;\n\n return flat;\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 = \"PEW-1000\";\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'];\n// alarmType\n/// Bitmask PEW-1XXX\n/// [0] = ALU saturation error (1 = activated)\n/// [1] = Sensor memory integrity error (1 = activated)\n/// [2] = Sensor busy error (1 = activated)\n/// [3] = Reserved\n/// [4] = Sensor communication error (1 = activated)\n/// [5] = Pressure out of limit (1 = activated)\n/// [6] = Temperature out of limit (1 = activated)\n/**\n * @type {{[key: number]: string}} \n */\nvar TECHNICAL_ALARM_TYPE_NAMES_DICTIONARY = {\n 1: \"ALU saturation error\", 2: \"sensor memory integrity error\", 4: \"sensor busy error\", 8: \"reserved\",\n 16: \"sensor communication error\", 32: \"pressure out of limit\", 64: \"temperature out of limit\"\n};\n/**\n * @type {{[key: number]: string}} \n */\nvar DEVICE_ALARM_TYPE_NAMES_DICTIONARY = { 0: \"battery low\", 4: \"acknowledged message not emitted\" };\nvar DEVICE_ALARM_CAUSE_OF_FAILURE_NAMES_DICTIONARY = ['generic', 'device dependent'];\n\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 = padString + tempString;\n }\n\n return tempString; \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 console.log(\"PPPBDA decoding...\" + JSON.stringify(input));\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 == 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 >= 3 && 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 >= 8 && input.bytes.length <= 38) {\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 console.log(\"PPPBDA Success Decoding (output): \" + JSON.stringify(output));\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 console.log(\"PPPBDA decoding data message...\" + JSON.stringify(input));\n\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 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 output.data.technicalAlarms[0].alarmType = (input.bytes[2] & 0x7f);\n\n // Go through each bit and check if set\n output.data.technicalAlarms[0].alarmTypeNames = [];\n for (var j = 0, i = 0; j < 7; j++) {\n // Check if bit is set\n if (output.data.technicalAlarms[0].alarmType & (1 << j)) {\n output.data.technicalAlarms[0].alarmTypeNames[i] = TECHNICAL_ALARM_TYPE_NAMES_DICTIONARY[1 << j];\n i++;\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 battery, 4 = Acknowledged message not emitted\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 switch (output.data.deviceAlarm.alarmType) {\n // low battery alarm has an value\n case 0:\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) / 10;\n break;\n\n // All other don't have any alarm value\n case 4:\n default:\n break;\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] == 11 ? \"PEW\" : input.bytes[2];\n\n // Product sub id\n output.data.deviceInformation.productSubId = input.bytes[3];\n\n // Product sub id resolved\n switch (input.bytes[3]) {\n case 0:\n output.data.deviceInformation.productSubIdName = \"LoRaWAN\";\n break;\n\n case 1:\n output.data.deviceInformation.productSubIdName = \"MIOTY\";\n break;\n\n default:\n output.data.deviceInformation.productSubIdName = \"Unknown\";\n break;\n }\n\n // Wireless module firmware version\n output.data.deviceInformation.wirelessModuleFirmwareVersion = ((input.bytes[4] >> 4) & 0x0f).toString() + \".\" + (input.bytes[4] & 0x0f).toString() + \".\" + (input.bytes[5]).toString();\n\n // Wireless module hardware version\n output.data.deviceInformation.wirelessModuleHardwareVersion = ((input.bytes[6] >> 4) & 0x0f).toString() + \".\" + (input.bytes[6] & 0x0f).toString() + \".\" + (input.bytes[7]).toString();\n\n /* return function if no sensor data is available */\n if (input.bytes.length < 38) {\n output = addErrorMessage(output, \"Device identification frame 07 has not all bytes included, received \" + input.bytes.length + \"/38 bytes\");\n return output;\n }\n\n // Sensor serial number\n output.data.deviceInformation.serialNumber = \"\";\n for (var i = 8; i < 19; i++) {\n if (input.bytes[i] == 0) {\n break;\n }\n output.data.deviceInformation.serialNumber += String.fromCharCode(input.bytes[i]);\n }\n\n // Pressure type\n switch (input.bytes[19]) {\n case 1:\n output.data.deviceInformation.pressureType = \"absolute\";\n break;\n\n case 2:\n output.data.deviceInformation.pressureType = \"gauge / relative\";\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[20].toString(16).padStart(2, \"0\") + input.bytes[21].toString(16).padStart(2, \"0\") + input.bytes[22].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[24].toString(16).padStart(2, \"0\") + input.bytes[25].toString(16).padStart(2, \"0\") + input.bytes[26].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[28].toString(16).padStart(2, \"0\") + input.bytes[29].toString(16).padStart(2, \"0\") + input.bytes[30].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[32].toString(16).padStart(2, \"0\") + input.bytes[33].toString(16).padStart(2, \"0\") + input.bytes[34].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[36];\n output.data.deviceInformation.pressureUnitName = returnPhysicalUnitFromId(input.bytes[36]);\n\n // Unit pressure\n output.data.deviceInformation.deviceTemperatureUnit = input.bytes[37];\n output.data.deviceInformation.deviceTemperatureUnitName = returnPhysicalUnitFromId(input.bytes[37]);\n\n return output;\n}\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 * Adds 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 * Adds private to output object\n * @param {output} output\n * @param {string} errorMessage\n * @access private \n */\nfunction addErrorMessage(output, errorMessage) {\n console.log(\"PPPBDA ERROR: \" + errorMessage);\n output.errors = output.errors || [];\n output.errors.push(DEVICE_NAME + \" (JS): \" + errorMessage);\n return output;\n}\n\n/**\n * Creates 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/**\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\nfunction getPluginSource(payload) {\n return payload[\"source\"];\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}\n\n\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 PEW1000 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": 11
},
"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/pew_1000_pew_1200_en_co_rs_w410_h410_image.jpg",
"name": "Source 1",
"source": "image_url"
}
],
"type": "image"
},
{
"layout": {
"col": 3,
"row": 0,
"sizeX": 1,
"sizeY": 5
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
},
"title": "Device Temperature"
},
"properties": {
"color": "#1E313E",
"gradient": true,
"max": 80,
"min": 0,
"unit": "ºC"
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "pew1000_data_bucket",
"mapping": "device_temperature",
"tags": {
"device": [
"wika_pew1000_70B3D597B0000D15"
],
"group": []
}
},
"color": "#1abc9c",
"name": "Source 1",
"source": "bucket",
"timespan": {
"mode": "latest"
}
}
],
"type": "gauge"
},
{
"layout": {
"col": 3,
"row": 5,
"sizeX": 1,
"sizeY": 3
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
},
"title": "Battery Level"
},
"properties": {
"icon": "",
"iconSize": "",
"max": 100,
"min": 0,
"unit": "%"
},
"sources": [
{
"color": "#1abc9c",
"device_bucket": {
"backend": "mongodb",
"device": "wika_pew1000_70B3D597B0000D15",
"id": "pew1000_data_bucket",
"mapping": "battery (%)",
"tags": {
"device": [],
"group": []
}
},
"name": "Source 1",
"source": "device_bucket",
"timespan": {
"mode": "latest"
}
}
],
"type": "progressbar"
},
{
"layout": {
"col": 4,
"row": 0,
"sizeX": 2,
"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"
},
{
"layout": {
"col": 3,
"row": 8,
"sizeX": 1,
"sizeY": 3
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
},
"title": "Battery Voltage"
},
"properties": {
"color": "#1E313E",
"decimal_places": 1,
"icon": "",
"size": "75px",
"unit": "V",
"unit_size": "20px",
"weight": "font-bold"
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "pew1000_data_bucket",
"mapping": "battery_voltage",
"tags": {
"device": [
"wika_pew1000_70B3D597B0000D15"
],
"group": []
}
},
"color": "#1abc9c",
"name": "Source 1",
"source": "bucket",
"timespan": {
"mode": "latest"
}
}
],
"type": "text"
},
{
"layout": {
"col": 0,
"row": 11,
"sizeX": 4,
"sizeY": 7
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
},
"title": "Pressure"
},
"properties": {
"axis": true,
"fill": true,
"legend": false,
"multiple_axes": false
},
"sources": [
{
"aggregation": {},
"bucket": {
"backend": "mongodb",
"id": "pew1000_data_bucket",
"mapping": "pressure",
"tags": {
"device": [
"wika_pew1000_70B3D597B0000D15"
],
"group": []
}
},
"color": "#1abc9c",
"name": "Pressure",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 2
}
}
],
"type": "chart"
},
{
"api": {},
"layout": {
"col": 1,
"row": 0,
"sizeX": 2,
"sizeY": 11
},
"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": "pew1000_data_bucket",
"mapping": "pressure",
"tags": {
"device": [
"wika_pew1000_70B3D597B0000D15"
],
"group": []
}
},
"color": "#1abc9c",
"name": "pressure",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "latest",
"period": "latest",
"value": 6
}
}
],
"type": "tachometer"
}
]
}
]
}
}
]
}
}
]
}
}