Skip to content

Plugin file

Plugin configuration file
{
    "name": "iot-factory-metering-node",
    "version": "1.0.0",
    "description": "M-BUS Master Gateway and LORAWAN Converter",
    "author": "Thinger.io",
    "license": "MIT",
    "repository": {
        "type": "git",
        "url": "https://github.com/thinger-io/plugins.git",
        "directory": "iot-factory-metering-node"
    },
    "metadata": {
        "name": "Iot-Factory METERING-NODE",
        "description": "M-BUS Master Gateway and LORAWAN Converter",
        "image": "assets/metering-node.png",
        "category": "devices",
        "vendor": "iot-factory"
    },
    "resources": {
        "products": [
            {
                "description": "M-BUS Master Gateway and LORAWAN Converter",
                "enabled": true,
                "name": "Iot-Factory METERING-NODE",
                "product": "iot_factory_metering_node",
                "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.uplink.source}}",
                                    "target": "plugin_endpoint"
                                }
                            }
                        },
                        "uplink": {
                            "device_id_resolver": "getId",
                            "enabled": true,
                            "handle_connectivity": true,
                            "request": {
                                "data": {
                                    "payload": "{{payload}}",
                                    "payload_function": "",
                                    "payload_type": "source_payload",
                                    "resource_stream": "uplink",
                                    "target": "resource_stream"
                                }
                            }
                        }
                    },
                    "autoprovisions": {
                        "device_autoprovisioning": {
                            "config": {
                                "mode": "pattern",
                                "pattern": "metering-node-.*"
                            },
                            "enabled": true
                        }
                    },
                    "buckets": {
                        "iot_factory_metering_node_data": {
                            "backend": "mongodb",
                            "data": {
                                "payload": "{{payload}}",
                                "payload_function": "decodeThingerUplink",
                                "payload_type": "source_payload",
                                "resource": "uplink",
                                "source": "resource",
                                "update": "events"
                            },
                            "enabled": true,
                            "retention": {
                                "period": 3,
                                "unit": "months"
                            },
                            "tags": []
                        }
                    },
                    "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//-----------------------------------------------------\n// Packet parser\n//-----------------------------------------------------\n//Port to which the packet was received for LoRaWAN servers (in other cases, you must specify 10)\n//bytes -  Data buffer\nfunction Decode (fPort, bytes, variables) \n{\n  var result = {\n    status_decode: false,\n    raw: bytes\n  };\n  if( fPort === 10 )\n  {\n    var offset = 0;\n    setpowerInformation(bytes,offset,result);\n    offset++;\n    setProtocolInformation(bytes,offset,result);\n    offset++;\n    if( result.is_sn ) \n    {\n      result.sn = toUInt32LE(bytes,offset);\n      offset += 4;\n    }\n    if( result.is_payload_size )\n    {\n      result.payload_size = toUInt16LE(bytes,offset);\n      offset += 2;\n    }\n    delete result.is_sn;\n    delete result.is_payload_size;\n    //var ts = new Date();\n    //result.server_isotime = ts.toISOString();\n    //result.server_unixtime = Math.floor(ts.getTime()/1000);\n    parseFrames(bytes,offset,result);\n    if( !!result.error == false) result.status_decode = true;\n  }\n  return result;\n}\n//-----------------------------------------------------\n// Frame type parser\n//-----------------------------------------------------\nfunction parseFrames(buf,offset,obj) \n{\n  if( buf[offset] === undefined || buf[offset+1] === undefined ) return;\n  var frameHeader = buf[offset] | ( buf[ offset+1 ] << 8 );\n  var MASK_TYPE_FRAME   = 0x0fff; //0b0000111111111111\n  var MASK_REASON_FRAME = 0xf000; //0b1111000000000000\n  var typeFrame   = ( frameHeader & MASK_TYPE_FRAME )   >> 0;\n  var reasonFrame = ( frameHeader & MASK_REASON_FRAME ) >> 12;\n  offset +=2;\n  if(obj.frames === undefined) obj.frames = [];\n\n  if ( typeFrame == 0x00 ) parseFramesDeviceInfo(buf,offset,obj,reasonFrame);\n\n  else if ( typeFrame == 0x03 ) parseFramesGNSS(buf,offset,obj,reasonFrame);\n  else if ( typeFrame == 0x04 ) parseFramesWiFiGeo(buf,offset,obj,reasonFrame);\n  else if ( typeFrame == 0x05 ) parseFramesMotionActivity(buf,offset,obj,reasonFrame);\n  else if ( typeFrame == 0x06 ) parseFramesShock(buf,offset,obj,reasonFrame);\n\n  else if ( typeFrame == 0x07 ) parseFramesWasteSensor(buf,offset,obj,reasonFrame);\n  else if ( typeFrame == 0x08 ) parseFramesModbus(buf,offset,obj,reasonFrame);\n\n  else if ( typeFrame == 0x09 ) parseFramesIBeacon(buf,offset,obj,reasonFrame);\n\n  else if ( typeFrame == 0x0A ) parseFrames4_20mA(buf,offset,obj,reasonFrame);\n  else if ( typeFrame == 0x0B ) parseFramesDigitalInput(buf,offset,obj,reasonFrame); \n  else if ( typeFrame == 0x0C ) parseFramesAnalogInput(buf,offset,obj,reasonFrame);\n  else if ( typeFrame == 0x0D ) parseFramesPulseCounter(buf,offset,obj,reasonFrame);\n  else if ( typeFrame == 0x0E ) parseFramesLbsGeo(buf,offset,obj,reasonFrame);\n\n  else if ( typeFrame == 0x10 ) parseFramesRs485(buf,offset,obj,reasonFrame);\n\n  else if ( typeFrame == 0x11 ) parseFramesSOS(buf,offset,obj,reasonFrame);\n  else if ( typeFrame == 0x12 ) parseFramesTurn(buf,offset,obj,reasonFrame);\n  else if ( typeFrame == 0x13 ) parseFramesMovement(buf,offset,obj,reasonFrame);\n\n  else if ( typeFrame == 0x14 ) parseFramesIMU(buf,offset,obj,reasonFrame); \n\n  else if ( typeFrame == 0x15 ) parseFramesODK(buf,offset,obj,reasonFrame); \n // else if ( typeFrame == 0x16 ) parseFramesMBUS(buf,offset,obj,reasonFrame); \n  else if ( typeFrame == 0x17 ) parseFramesModernState(buf,offset,obj,reasonFrame);\n  else if ( typeFrame == 0x18 ) parseFramesHumidity(buf,offset,obj,reasonFrame);\n  else if ( typeFrame == 0x19 ) parseFramesIlluminance (buf,offset,obj,reasonFrame);\n  else if ( typeFrame == 0x20 ) parseFramesAcceleration (buf,offset,obj,reasonFrame);\n\n  else if ( typeFrame == 0x21 ) parseFramesGuard (buf,offset,obj,reasonFrame);\n  else if ( typeFrame == 0x22 ) parseFramesInclination  (buf,offset,obj,reasonFrame);\n\n  else if ( typeFrame == 0x0f ) parseFramestemperature(buf,offset,obj,reasonFrame);\n  else unknownFrame(obj,typeFrame,offset-2);\n}\n//-----------------------------------------------------\n// Frame parsers\n//-----------------------------------------------------\nfunction unknownFrame(obj,type,offset)\n{\n  obj.error = 'unknown_frame';\n  obj.error_debug_info = 'type='+type+'|offset='+offset;\n}\nfunction parseFramesIMU(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'IMU';\n  if( reason == 0x00 ) frame.reason = 'regular';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n\n  frame.roll = toInt16LE(buf,offset);\n  offset += 2;\n  frame.pith = toInt16LE(buf,offset);\n  offset += 2;\n  frame.yaw = toInt16LE(buf,offset);\n  offset += 2;\n\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\n\nfunction parseFramesDeviceInfo(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'device_info';\n  if( reason == 0x00 ) frame.reason = 'boot';\n  else if( reason == 0x01 ) frame.reason = 'result_request';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  frame.model_id = toUInt16LE(buf, offset);\n  frame.model_name = getModelNameById(frame.model_id);\n  offset += 2;\n  setDeviceFirmwareVersion(buf[offset],frame);\n  offset++;\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\n\nfunction parseFramesODK(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'ODK';\n  if( reason == 0x00 ) frame.reason = 'regular';\n  else if( reason == 0x01 ) frame.reason = 'alarm';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  frame.channel_number = toUInt8(buf,offset);\n  offset++;\n  if( buf[offset] === 0x00 ) frame.state = 'NORMAL1';\n  else if( buf[offset] === 0x01 ) frame.state = 'NORMAL2';\n  else if( buf[offset] === 0x02 ) frame.state = 'NORMAL3';\n  else if( buf[offset] === 0x03 ) frame.state = 'NORMAL4';\n  else if( buf[offset] === 0x04 ) frame.state = 'NORMAL5';\n  else if( buf[offset] === 0x05 ) frame.state = 'WET';\n  else if( buf[offset] === 0x06 ) frame.state = 'BREAK';\n  else if( buf[offset] === 0x07 ) frame.state = 'WET_BREAK';\n  else if( buf[offset] === 0x08 ) frame.state = 'UNKNOWN';\n  else frame.state = 'unknown';\n  offset++;\n  frame.current_resistance = toUInt32LE(buf,offset);\n  offset += 4;\n  frame.min_resistance = toUInt32LE(buf,offset);\n  offset += 4;\n  frame.max_resistance = toUInt32LE(buf,offset);\n  offset += 4;\n  frame.middle_resistance = toUInt32LE(buf,offset);\n  offset += 4;\n  frame.current_conduct_resistance = toUInt32LE(buf,offset);\n  offset += 4;\n\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\nfunction parseFramesModernState(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'modern_state';\n  if( reason == 0x00 ) frame.reason = 'regular';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n\n  frame.iccid = toASCII(buf,offset,20);\n  offset += 20;\n  frame.rssi = toInt16LE(buf,offset);\n  offset += 2;\n  frame.iccid = toASCII(buf,offset,15);\n  offset += 15;\n\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\nfunction parseFramesHumidity(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'humidity';\n  if( reason == 0x00 ) frame.reason = 'regular';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  frame.humidity_pct = toUInt16LE(buf,offset);\n  if( frame.humidity_pct > 1000 || frame.humidity_pct < 0 ) frame.humidity_pct= 'unknown';\n  else frame.humidity_pct = frame.humidity_pct/10;\n  offset += 2;\n\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\nfunction parseFramesIlluminance(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'illuminance';\n  if( reason == 0x00 ) frame.reason = 'regular';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  frame.illuminance_lex = toUInt16LE(buf,offset);\n  offset += 2;\n\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\nfunction parseFramesAcceleration(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'acceleration';\n  if( reason == 0x00 ) frame.reason = 'regular';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  frame.x_mG = toInt16LE(buf,offset);\n  offset += 2;\n  frame.y_mG = toInt16LE(buf,offset);\n  offset += 2;\n  frame.z_mG = toInt16LE(buf,offset);\n  offset += 2;\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\n\nfunction parseFramesWasteSensor(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'waste_sensor';\n  if( reason == 0x00 ) frame.reason = 'regular';\n  else if( reason == 0x01 ) frame.reason = 'waste_flush';\n  else if( reason == 0x02 ) frame.reason = 'temperature_alarm';\n  else if( reason == 0x03 ) frame.reason = 'open_alarm';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  if( buf[offset] == 0x00 ) frame.position = 'horizontally';\n  else if( buf[offset] == 0x01 ) frame.position = 'vertically';\n  else frame.position = 'unknown';\n  offset++;\n  frame.distance_upper_sensor_cm = toUInt16LE(buf, offset);\n  if( frame.distance_upper_sensor_cm < 0 || frame.distance_upper_sensor_cm > 300 ) frame.distance_upper_sensor_cm = 'unknown';\n  offset += 2;\n  frame.distance_side_sensor_cm = toUInt16LE(buf, offset);\n  if( frame.distance_side_sensor_cm < 0 || frame.distance_side_sensor_cm > 300 ) frame.distance_side_sensor_cm = 'unknown';\n  offset += 2;\n  frame.temperature_cel = toInt8(buf, offset);\n  offset++;\n  frame.waste_pct = toUInt8(buf, offset);\n  offset++;\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\nfunction parseFramesRs485(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'rs485';\n  if( reason == 0x00 ) frame.reason = 'regular';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  frame.measurement_row_number = toUInt8(buf,offset);\n  offset++;\n  frame.command_number_row = toUInt8(buf,offset);\n  offset++;\n  frame.size_answer = toUInt8(buf,offset);\n  offset++;\n  frame.answer = toHex(buf,offset,frame.size_answer);\n  offset += frame.size_answer;\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\nfunction parseFramesModbus(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'modbus';\n\n  if( reason == 0x00 ) frame.reason = 'regular';\n  else if( reason == 0x01 ) frame.reason = 'alarm';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  frame.name = toASCII(buf,offset,8);\n  offset += 8;\n  frame.slave_address = toUInt8(buf,offset);\n  offset++;\n  frame.register_address = toUInt16LE(buf,offset);\n  offset += 2;\n  frame.modbus_function = getNameModbusFunction(buf[offset]);\n  offset++;\n  frame.register_counter = toUInt8(buf,offset);\n  if( frame.register_counter < 0 || frame.register_counter > 0x04 ) frame.register_counter = 'unknown'; \n  offset++;\n  if( buf[offset] === 0x00 ) frame.signed_flag = 'unsigned';\n  else if( buf[offset] === 0x01 ) frame.signed_flag = 'signed';\n  else frame.signed_flag = 'unknown';\n  offset++;\n  frame.register_value = toHex(buf,offset,8);\n  offset += 8;\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\n\nfunction parseFramesTurn(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'turn';\n  if( reason == 0x01 ) frame.reason = 'turn_detect';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\nfunction parseFramesGuard(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'guard';\n  if( reason == 0x00 ) frame.reason = 'normal';\n  else if( reason == 0x01 ) frame.reason = 'alarm';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  frame.pin_number = toUInt8(buf,offset);\n  offset++;\n  frame.pin_status = toUInt8(buf,offset);\n  offset++;\n  if(frame.pin_status===1) frame.pin_status = 'open';\n  else if(frame.pin_status===0) frame.pin_status = 'close';\n  else frame.pin_status = 'unknown';\n\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\nfunction parseFramesInclination(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'inclination';\n  if( reason == 0x00 ) frame.reason = 'normal';\n  else if( reason == 0x01 ) frame.reason = 'alarm';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n\n  frame.x_axis_angle_pct = toUInt16LE(buf, offset) / 10;\n  offset += 2;\n  frame.y_axis_angle_pct = toUInt16LE(buf, offset) / 10;\n  offset += 2;\n  frame.z_axis_angle_pct = toUInt16LE(buf, offset) / 10;\n  offset += 2;\n\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\nfunction parseFramestemperature(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'temperature';\n  if( reason == 0x00 ) frame.reason = 'regular';\n  else if( reason == 0x01 ) frame.reason = 'alarm';\n  else frame.reason = 'unknown';\n\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n\n  frame.number_sensor = toUInt8(buf,offset);\n  offset++; \n\n  frame.temperature_cel = toInt16LE(buf,offset)/10;\n  offset += 2;\n\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\nfunction parseFramesMovement(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'movement';\n  if( reason == 0x00 ) frame.reason = 'regular';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  var isMovement = toUInt8(buf,offset);\n\n  if(isMovement === 0) frame.is_movement = false;\n  else if(isMovement === 1) frame.is_movement = true;\n  else  frame.is_movement = 'unknown';\n  offset++;\n\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\nfunction parseFramesSOS(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'sos';\n  if( reason == 0x01 ) frame.reason = 'sos_1_detect';\n  else if( reason == 0x02 ) frame.reason = 'sos_2_detect';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\nfunction parseFramesShock(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'shock';\n  if( reason == 0x01 ) frame.reason = 'shock_detect';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\nfunction parseFramesMotionActivity(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'motion_activity';\n  if( reason == 0x00 ) frame.reason = 'regular';\n  else if( reason == 0x01 ) frame.reason = 'shock_detect';\n  else if( reason == 0x02 ) frame.reason = 'stop_movement';\n  else if( reason == 0x03 ) frame.reason = 'start_movement';\n  else frame.reason = 'unknown';\n\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  frame.period_duration_min = toUInt8(buf,offset);\n  offset++;\n  frame.avg_index = toUInt8(buf,offset);\n  offset++;\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\n\nfunction parseFramesIBeacon(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'ibeacon'; \n  if( reason == 0x00 ) frame.reason = 'regular';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  frame.ibeacons_list = [];\n\n  var major1 = toUInt16LE(buf, offset);\n  offset += 2;\n  var minor1 = toUInt16LE(buf, offset);\n  offset += 2;\n  var rssi1 = toInt8(buf, offset);\n  offset++;\n  var major2 = toUInt16LE(buf, offset);\n  offset += 2;\n  var minor2 = toUInt16LE(buf, offset);\n  offset += 2;\n  var rssi2 = toInt8(buf, offset);\n  offset++;\n  var major3 = toUInt16LE(buf, offset);\n  offset += 2;\n  var minor3 = toUInt16LE(buf, offset);\n  offset += 2;\n  var rssi3 = toInt8(buf, offset);\n  offset++;\n\n  if( validIBeacon(major1,minor1,rssi1) ) frame.ibeacons_list.push({\n    major: major1,\n    minor: minor1,\n    rssi: rssi1\n  });\n  if( validIBeacon(major2,minor2,rssi2) ) frame.ibeacons_list.push({\n    major: major2,\n    minor: minor2,\n    rssi: rssi2\n  });\n  if( validIBeacon(major3,minor3,rssi3) ) frame.ibeacons_list.push({\n    major: major3,\n    minor: minor3,\n    rssi: rssi3\n  });\n\n  setOldIBeaconByte(buf[offset],frame);\n  offset++;\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\n\nfunction parseFrames4_20mA(buf,offset,obj,reason) \n{\n  var frame = {};\n  frame.type = '4_20mA'; \n  if( reason == 0x00 ) frame.reason = 'regular';\n  else if( reason == 0x01 ) frame.reason = 'alarm';\n  else frame.reason = 'unknown';\n\n  frame.number_sensor = toUInt8(buf,offset);\n  offset++;\n\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  frame.mA = toUInt32LE(buf,offset);\n  offset += 4;\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\nfunction parseFramesDigitalInput(buf,offset,obj,reason) \n{\n  var frame = {};\n  frame.type = 'digital_input'; \n  if( reason == 0x00 ) frame.reason = 'regular';\n  else if( reason == 0x01 ) frame.reason = 'alarm';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  frame.pin_number = toUInt8(buf,offset);\n  offset++;\n  if( buf[offset] === 0x00 ) frame.pin_state = 'low';\n  else if( buf[offset] === 0x01 ) frame.pin_state = 'high';\n  else frame.pin_state = 'unknown';\n  offset++;\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\nfunction parseFramesAnalogInput(buf,offset,obj,reason)  \n{\n  var frame = {};\n  frame.type = 'analog_input'; \n  if( reason == 0x00 ) frame.reason = 'regular';\n  else if( reason == 0x01 ) frame.reason = 'alarm';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  frame.pin_number = toUInt8(buf,offset);\n  offset++;\n  frame.mV = toUInt32LE(buf,offset);\n  offset += 4;\n  if( buf[offset] === 0x00 ) frame.input_status = 'ok';\n  else if( buf[offset] === 0x01 ) frame.input_status = 'overvoltage';\n  else if( buf[offset] === 0x02 ) frame.input_status = 'no_sensor';\n  else if( buf[offset] === 0x03 ) frame.input_status = 'input_error';\n  else frame.input_status = 'unknown';\n  offset++;\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\nfunction parseFramesPulseCounter(buf,offset,obj,reason)  \n{\n  var frame = {};\n  frame.type = 'pulse_counter'; \n  if( reason == 0x00 ) frame.reason = 'regular';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  frame.pin_number = toUInt8(buf,offset);\n  offset++;\n  frame.counter = toUInt32LE(buf,offset);\n  offset += 4;\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\nfunction parseFramesLbsGeo(buf,offset,obj,reason)  \n{\n  var frame = {};\n  frame.type = 'lbs_geolocation'; \n  if( reason == 0x00 ) frame.reason = 'regular';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  \n  var mcc1 = toUInt16LE(buf,offset);\n  offset += 2;\n  var mnc1 = toUInt16LE(buf,offset);\n  offset += 2;\n  var lac1 = toUInt16LE(buf,offset);\n  offset += 2;\n  var cellid1 = toUInt32LE(buf,offset);\n  offset += 4;\n  var power1 = toInt16LE(buf,offset);\n  offset += 2;\n\n  var mcc2 = toUInt16LE(buf,offset);\n  offset += 2;\n  var mnc2 = toUInt16LE(buf,offset);\n  offset += 2;\n  var lac2 = toUInt16LE(buf,offset);\n  offset += 2;\n  var cellid2 = toUInt32LE(buf,offset);\n  offset += 4;\n  var power2 = toInt16LE(buf,offset);\n  offset += 2;\n\n  var mcc3 = toUInt16LE(buf,offset);\n  offset += 2;\n  var mnc3 = toUInt16LE(buf,offset);\n  offset += 2;\n  var lac3 = toUInt16LE(buf,offset);\n  offset += 2;\n  var cellid3 = toUInt32LE(buf,offset);\n  offset += 4;\n  var power3 = toInt16LE(buf,offset);\n  offset += 2;\n  if( validLbsData(mcc1,mnc1,lac1,cellid1,power1) ) frame.lbs_list.push({\n    mcc: mcc1,\n    mnc: mnc1,\n    lac: lac1,\n    cellid: cellid1,\n    power_dBm: power1\n  });\n  if( validLbsData(mcc2,mnc2,lac2,cellid2,power2) ) frame.lbs_list.push({\n    mcc: mcc2,\n    mnc: mnc2,\n    lac: lac2,\n    cellid: cellid2,\n    power_dBm: power2\n  });\n  if( validLbsData(mcc3,mnc3,lac3,cellid3,power3) ) frame.lbs_list.push({\n    mcc: mcc3,\n    mnc: mnc3,\n    lac: lac3,\n    cellid: cellid3,\n    power_dBm: power3\n  });\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\n\nfunction parseFramesWiFiGeo(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'wifi'; \n  if( reason == 0x00 ) frame.reason = 'regular';\n  else if( reason == 0x01 ) frame.reason = 'change_mode';\n  else if( reason == 0x03 ) frame.reason = 'result_request';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n  frame.wifi_ap_list = [];\n  var bssid1 = buf.slice(offset,offset+6);\n  var mac1 = toMac(bssid1);\n  offset += 6;\n  var rssi1 = toInt8(buf,offset);\n  offset++;\n  \n  var bssid2 = buf.slice(offset,offset+6);\n  var mac2 = toMac(bssid2);\n  offset += 6;\n  var rssi2 = toInt8(buf,offset);\n  offset++;\n  \n  var bssid3 = buf.slice(offset,offset+6);\n  var mac3 = toMac(bssid3);\n  offset += 6;\n  var rssi3 = toInt8(buf,offset);\n  offset++;\n  \n  setOldWiFiGeoByte(buf[offset],frame);\n\n  if ( validBssid(bssid1) ) frame.wifi_ap_list.push({\n    mac: mac1,\n    rssi: rssi1,\n    is_home_network: frame.is_home_network_1\n  });\n  if ( validBssid(bssid2) ) frame.wifi_ap_list.push({\n    mac: mac2,\n    rssi: rssi2,\n    is_home_network: frame.is_home_network_2\n  });\n  if ( validBssid(bssid3) ) frame.wifi_ap_list.push({\n    mac: mac3,\n    rssi: rssi3,\n    is_home_network: frame.is_home_network_3\n  });\n\n  delete frame.is_home_network_1;\n  delete frame.is_home_network_2;\n  delete frame.is_home_network_3;\n\n  offset++;\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj);\n}\nfunction parseFramesGNSS(buf,offset,obj,reason)\n{\n  var frame = {};\n  frame.type = 'gnss'; \n  if( reason == 0x00 ) frame.reason = 'regular';\n  else if( reason == 0x01 ) frame.reason = 'change_mode';\n  else if( reason == 0x03 ) frame.reason = 'result_request';\n  else frame.reason = 'unknown';\n  frame.frame_unixtime = toUInt32LE(buf,offset);\n  frame.frame_isotime = new Date(frame.frame_unixtime*1000).toISOString();\n  offset += 4;\n\n  frame.lat = toIntLE(buf,offset,4) / 100000;\n  offset += 4;\n  frame.lon = toIntLE(buf,offset,4) / 100000;\n  offset += 4;\n  frame.alt = toUInt16LE(buf,offset);\n  offset += 2;\n  frame.speed_kmh = toUInt16LE(buf,offset);\n  offset += 2;\n  frame.hdop = toUInt8(buf,offset) / 10;\n  offset++;\n  setOldGnssByte(buf[offset],frame);\n  offset++;\n  obj.frames.push(frame);\n  parseFrames(buf,offset,obj); \n}\n//-----------------------------------------------------\n// Converters\n//-----------------------------------------------------\nfunction hexToBuffer( hex )\n{\n  var buffer=[];\n  for ( var i = 0; i < hex.length - 1; i = i + 2 )\n  {\n     var item = hex.substring( i, i + 2 );\n     var value = parseInt(item,16);\n     if(isNaN(value)) return false;\n     buffer.push( value );\n  }\n  return buffer;\n}\nfunction toInt8 (buf, offset) {\n  offset = offset >>> 0;\n  if (!(buf[offset] & 0x80)) return (buf[offset]);\n  return ((0xff - buf[offset] + 1) * -1);\n} \nfunction to32Real(bytes)\n{\n    var bits = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | (bytes[3]);\n    var sign = ((bits >>> 31) == 0) ? 1.0 : -1.0;\n    var e = ((bits >>> 23) & 0xff);\n    var m = (e == 0) ? (bits & 0x7fffff) << 1 : (bits & 0x7fffff) | 0x800000;\n    var f = sign * m * Math.pow(2, e - 150);\n    return f;\n}\nfunction toUInt8 (buf, offset) \n{\n  offset = offset >>> 0;\n  return (buf[offset]);\n}\nfunction toMac(arr)\n{\n  var res = '';\n  for ( var i = 0; i<arr.length; i++ )\n  {\n    var byte = arr[i].toString(16);\n    if( byte.length < 2 ) byte = '0'+byte;\n    if(i==0) res += byte;\n    else res += ':'+byte;\n  }\n  return res;\n}\nfunction toUInt16LE (buf, offset) \n{\n  offset = offset >>> 0;\n  return  buf[offset] | \n        ( buf[offset + 1] << 8 );\n}\n\nfunction toInt16LE (buf, offset) \n{\n    offset = offset >>> 0;\n    var val = buf[offset] | (buf[offset + 1] << 8);\n    return (val & 0x8000) ? val | 0xFFFF0000 : val;\n}\nfunction toUInt32LE (buf, offset) \n{\n  offset = offset >>> 0;\n  return ((buf[offset]) |\n          (buf[offset + 1] << 8) |\n          (buf[offset + 2] << 16)) +\n          (buf[offset + 3] * 0x1000000);\n}\nfunction toBcdLE (buf) \n{\n  //v_2\n  var MASK_1 = 0xf0; //0b11110000\n  var MASK_2 = 0x0f; //0b00001111\n  var z = 0;\n  var res = 0;\n  for( var i = 0; i < buf.length; i++ )\n  {\n    var t1 = ( buf[i] & MASK_1 ) >> 4;\n    var t2 = ( buf[i] & MASK_2 ) >> 0;\n    res += t2*Math.pow(10,z);\n    z++;\n    res += t1*Math.pow(10,z);\n    z++;\n  }\n  return res;\n}\nfunction toIntLE (buf, offset, length) \n{\n  offset = offset >>> 0;\n  length = length >>> 0;\n\n  var result = buf[offset];\n  var i = 0;\n  var mul = 1;\n  \n  while ( ++i < length && (mul *= 0x100) ) \n  {\n    var byte = buf[offset + i];\n    result += byte * mul;\n  }\n  \n  mul *= 0x80;\n  \n  if (result >= mul) \n  {\n    result -= Math.pow(2, 8 * length);\n  }\n\n  return result;\n}\nfunction toASCII(buf,offset,countByte)\n{\n  offset = offset >>> 0;\n  countByte = countByte >>> 0;\n  var result = '';\n  for (var i = offset; i < (offset+countByte); i++ )\n  {\n    result+=String.fromCharCode(buf[i]);\n  }\n  return result;\n}\nfunction toHex(buf,offset,countByte)\n{\n  offset = offset >>> 0;\n  countByte = countByte >>> 0;\n  var result = '';\n  for (var i = offset; i < (offset+countByte); i++ )\n  {\n    var byte = buf[i].toString(16);\n    if( byte.length == 1 ) byte = '0'+byte;\n    result = byte + result ;\n  }\n  return result;\n}\n//-----------------------------------------------------\n// Auxiliaries Functions\n//-----------------------------------------------------\nfunction setpowerInformation(buf,offset,obj)\n{\n    var MASK_TYPE_POWER     = 0x01; //0b00000001\n    var MASK_BATTERY_CHARGE = 0xfe; //0b11111110\n    var type_power = ( buf[offset] & MASK_TYPE_POWER ) >> 0;\n    if( type_power === 0 ) \n    {\n        obj.type_power = 'battery';\n    }\n    else \n    {\n        obj.type_power = 'external';\n    }\n    var charge = ( buf[offset] & MASK_BATTERY_CHARGE ) >> 1;\n    if( charge >= 0 )\n    {\n        obj.battery_pct = charge;\n    }\n}\nfunction setProtocolInformation(buf,offset,obj) \n{\n    var MASK_SI_SN              = 0x01; //0b00000001\n    var MASK_VERSION_W_PROTOCOL = 0x0e; //0b00001110\n    var MASK_IS_PAYLOAD_SIZE    = 0x10; //0b00010000\n    var MASK_RFU                = 0xe0; //0b11100000\n    var isSN             = ( buf[offset] & MASK_SI_SN )              >> 0;\n    var versionWProtocol = ( buf[offset] & MASK_VERSION_W_PROTOCOL ) >> 1;\n    var isPayloadSize    = ( buf[offset] & MASK_IS_PAYLOAD_SIZE )    >> 4;\n    var rfu              = ( buf[offset] & MASK_RFU )                >> 5;\n    obj.is_sn = !!isSN;\n    obj.version_protocol = versionWProtocol;\n    obj.is_payload_size = !!isPayloadSize;\n    // obj.rfu = rfu;\n}\nfunction setOldWiFiGeoByte(byte,obj)\n{\n  var MASK_RFU              = 0x0f; //0b00001111 \n  var MASK_MOVEMENT         = 0x10; //0b00010000\n  var MASK_IS_HOME_NETWORK1 = 0x20; //0b00100000\n  var MASK_IS_HOME_NETWORK2 = 0x40; //0b01000000\n  var MASK_IS_HOME_NETWORK3 = 0x80; //0b10000000\n  \n  var rfu            = ( byte & MASK_RFU )              >> 0;\n  var movement       = ( byte & MASK_MOVEMENT )         >> 4;\n  var isHomeNetwork1 = ( byte & MASK_IS_HOME_NETWORK1 ) >> 5;\n  var isHomeNetwork2 = ( byte & MASK_IS_HOME_NETWORK2 ) >> 6;\n  var isHomeNetwork3 = ( byte & MASK_IS_HOME_NETWORK3 ) >> 7;\n\n  // obj.rfu = rfu;\n  obj.in_movement = !!movement;\n  obj.is_home_network_1 = !!isHomeNetwork1;\n  obj.is_home_network_2 = !!isHomeNetwork2;\n  obj.is_home_network_3 = !!isHomeNetwork3;\n  \n}\nfunction setOldGnssByte(byte,obj)\n{\n  var MASK_USED_SATELLITES   = 0x1f; //0b00011111\n  var MASK_INSIDE_GEOFENCE_1 = 0x20; //0b00100000\n  var MASK_INSIDE_GEOFENCE_2 = 0x40; //0b01000000\n  var MASK_MOVEMENT          = 0x80; //0b10000000\n  var usedSattellites = ( byte & MASK_USED_SATELLITES )   >> 0;\n  var insideGeofence1 = ( byte & MASK_INSIDE_GEOFENCE_1 ) >> 5;\n  var insideGeofence2 = ( byte & MASK_INSIDE_GEOFENCE_2 ) >> 6;\n  var movement        = ( byte & MASK_MOVEMENT )          >> 7;\n  obj.used_sat = usedSattellites;\n  obj.geofence1 = !!insideGeofence1?'inside':'outside';\n  obj.geofence2 = !!insideGeofence2?'inside':'outside';\n  obj.in_movement = !!movement;\n}\nfunction setDeviceFirmwareVersion(byte,obj)\n{\n  var MASK_MAJOR = 0x0f; //0b00001111\n  var MASK_MINOR = 0xf0; //0b11110000\n  var major = ( byte & MASK_MAJOR ) >> 0;\n  var minor = ( byte & MASK_MINOR ) >> 4;\n  obj.version = major+'.'+minor;\n}\nfunction setOldIBeaconByte(byte,obj)\n{\n  var MASK_RFU      = 0x7f; //0b01111111\n  var MASK_MOVEMENT = 0x80; //0b10000000\n  var rfu      = ( byte & MASK_RFU )      >> 0;\n  var movement = ( byte & MASK_MOVEMENT ) >> 7;\n  obj.in_movement = !!movement;\n  // obj.rfu = rfu;\n}\nfunction getNameModbusFunction(id)\n{\n  if( id == 0x00 ) return 'read_coil_status';\n  else if( id == 0x01 ) return 'read_discrete_inputs';\n  else if( id == 0x02 ) return 'read_hlding_registers';\n  else if( id == 0x03 ) return 'input_registers';\n  else return 'unknown';\n} \nfunction getModelNameById(id)\n{\n  id = parseInt(id);\n  if( id === 1 ) return 'Airbit light controller';\n  else if( id === 200 ) return 'IXOCAT-LW-FULL';\t\n  else if( id === 201 ) return 'IXOCAT-LW-LIGHT';\t\n  else if( id === 3 ) return 'Betar LoRaWAN water meter LC';\t\n  else if( id === 4 ) return 'Betar Nb-IoT water meter LC';\t\n  else if( id === 500 ) return 'IXOCAT-NB-FULL';\t\n  else if( id === 501 ) return 'IXOCAT-NB-LIGHT';\t\n  else if( id === 7 ) return 'ERTX-L01';\t\n  else if( id === 8 ) return 'Construtag';\t\n  else if( id === 9 ) return 'MTBO-01-Nb';\t\n  else if( id === 10 ) return 'TaigaBikeTracker';\t\n  else if( id === 11 ) return 'MTBO-01-2G';\t\n  else if( id === 12 ) return 'SST_GAS_radio_module';\t\n  else if( id === 13 ) return 'NbIoT Button';\t\n  else if( id === 14 ) return 'МКРС-02';\t\n  else if( id === 15 ) return 'BS controller';\t\n  else if( id === 16 ) return 'Airbit light controller NbIoT';\t\n  else if( id === 17 ) return 'IoT node';\t\n  else if( id === 18 ) return 'NIS_METER';\t\n  else if( id === 19 ) return 'RoadsignTracker-LW-FULL';\t\n  else if( id === 21 ) return 'Stels Smoke Sensor';\t\n  else if( id === 22 ) return 'Betar LoRaWAN water meter HALL';\t\n  else if( id === 23 ) return 'Betar Nb-IoT water meter HALL';\t\n  else if( id === 24 ) return 'NodeG';\t\n  else if( id === 25 ) return 'TaigaTracker LoRaWAN';\t\n  else if( id === 26 ) return 'TaigaTracker Nb-IoT';\t\n  else if( id === 27 ) return 'TaigaBeacon';\t\n  else if( id === 28 ) return 'ERTX-ODK-2CH';\t\n  else if( id === 29 ) return 'ERTX crypto kit';\t\n  else if( id === 30 ) return 'TaigaPersonalTracker LW';\t\n  else if( id === 31 ) return 'TaigaPersonalTracker NB';\t\n  else if( id === 32 ) return 'TaigaPersonalTracker 2G';\t\n  else if( id === 33 ) return 'TaigaNode Lw';\t\n  else if( id === 34 ) return 'TaigaNode Nb';\t\n  else if( id === 35 ) return 'TaigaTracker2G';\t\n  else if( id === 36 ) return 'AirbitSensorHub-LW';\t\n  else if( id === 37 ) return 'AuroraNodeODK';\t\n  else if( id === 38 ) return 'TaigaBigTracker';\t\n  else if( id === 39 ) return 'NhrsPersonalTracker';\t\n  else if( id === 40 ) return 'WasteSensor-UKS';\t\n  else if( id === 41 ) return 'TaigaBikeTrackerE';\t\n  else if( id === 42 ) return 'ClickButton';\t\n  else if( id === 43 ) return 'FannaConcentrator';\t\n  else if( id === 44 ) return 'MyLogger';\t\n  else if( id === 45 ) return 'TaigaCartTracker';\t\n  else if( id === 46 ) return 'NtmPulseNB';\t\n  else if( id === 47 ) return 'SoftControl-Pass';\t\n  else if( id === 48 ) return 'TempButton';\t\n  else if( id === 49 ) return 'Uveos';\t\n  else if( id === 50 ) return 'McsmPulseNB';\t\n  else if( id === 51 ) return 'FannaConcentrator IN PWR';\t\n  else if( id === 52 ) return 'AuroraNodeOdkBoard';\t\n  else if( id === 53 ) return 'Siltrack V1 EXT ANT';\t\n  else if( id === 54 ) return 'RaceAnalyse';\t\n  else if( id === 55 ) return 'MaximaContainerTracker';\t\n  else if( id === 56 ) return 'FannaEframe';\t\n  else if( id === 57 ) return 'MaximaWasteSensorV2';\t\n  else if( id === 58 ) return 'Module SGMB-USPD-NB-1';\t\n  else if( id === 59 ) return 'AuroraNodeLTE';\t\n  else if( id === 60 ) return 'AuroraNodeNB';\t\n  else if( id === 61 ) return 'AuroraNodeETH';\t\n  else if( id === 62 ) return 'TaigaPulseCounterNb';\t\n  else if( id === 63 ) return 'FannaAnchor';\t\n  else if( id === 64 ) return 'AuroraNodeExtBoards';\t\n  else if( id === 65 ) return 'TaigaPersonalTracker LW_without GNSS';\t\n  else if( id === 66 ) return 'TaigaPersonalTracker NB_without GNSS';\t\n  else if( id === 67 ) return 'AuroraNodeGTS';\t\n  else if( id === 68 ) return 'TetronTrackerLight';\t\n  else if( id === 69 ) return 'SGMB-NB-2G';\t\n  else if( id === 70 ) return 'TaigaPulseCounterLW';\n  else if( id === 71 ) return 'AnalogPCBBoard';\t\n  else if( id === 75 ) return 'ErtxTermologgerNb';\t\n  else return 'unknown';\n}\n\nfunction validIBeacon( major, minor, rssi )\n{\n  if( major < 0 || major > 65535 ) return false;\n  if( minor < 0 || minor > 65535 ) return false;\n  if( rssi < -128 || rssi > 0 ) return false;\n  return true;\n}\nfunction validLbsData(mcc,mnc,lac,cellid,power)\n{\n  if( mcc < 0 || mcc > 65535 ) return false;\n  if( mnc < 0 || mnc > 65535 ) return false;\n  if( lac < 0 || lac > 65535 ) return false;\n  if( cellid < 0 || cellid > 0xFFFFFFFF ) return false;\n  if( power < -32768 || power > 32768 ) return false;\n  return true;\n}\nfunction validBssid(buf)\n{\n  var count00 = 0;\n  var countFF = 0;\n  for(var i = 0 ; i < buf.length; i++ )\n  {\n    if( buf[i] !== 0x00 && buf[i] !== 0xff ) break;\n    else if( buf[i] === 0x00 ) count00++;\n    else if( buf[i] === 0xff ) countFF++;\n  }\n  if( count00 === buf.length ) return false;\n  else if( countFF === buf.length ) return false;\n  else return true;\n}\n\n/*************************************************** */\n\nfunction decodeUplink(input) {\n    let data = {};\n    let error = {};\n    let errors = [];\n    let buffer = input.bytes;\n  \n    if(buffer !== false && buffer.length>0)\n    {\n    data = Decode(10,buffer); \n    delete data.raw;\n  \n    if (data && data.error !== undefined){\n        error.error_type = data.error;\n        delete data.error;\n        error.debug_info = data.error_debug_info;\n        delete data.error_debug_info;\n        errors.push(error);\n    }\n    \n  \n  }\n  \n  return {\n  data,\n  warnings: [],\n  errors : errors\n  };\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": [
                                            {
                                                "layout": {
                                                    "col": 0,
                                                    "row": 0,
                                                    "sizeX": 2,
                                                    "sizeY": 6
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "Battery Level"
                                                },
                                                "properties": {
                                                    "color": "#f0e924",
                                                    "max": 100,
                                                    "min": 0,
                                                    "unit": "%"
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "iot_factory_metering_node_data",
                                                            "mapping": "battery_pct",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "color": "#1abc9c",
                                                        "name": "Battery",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "mode": "latest"
                                                        }
                                                    }
                                                ],
                                                "type": "donutchart"
                                            },
                                            {
                                                "layout": {
                                                    "col": 2,
                                                    "row": 0,
                                                    "sizeX": 2,
                                                    "sizeY": 6
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "Power Type"
                                                },
                                                "properties": {
                                                    "source": "code",
                                                    "template": "<div style=\"width:100%; height:100%; display:flex; align-items:center; justify-content:center; font-size:24px; font-weight:bold;\">\n  <span ng-if=\"value.type_power === 'battery'\" style=\"color:#f39c12;\">🔋 Battery</span>\n  <span ng-if=\"value.type_power === 'external'\" style=\"color:#27ae60;\">⚡ External</span>\n  <span ng-if=\"!value.type_power\" style=\"color:#95a5a6;\">Unknown</span>\n</div>"
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "iot_factory_metering_node_data",
                                                            "mapping": "type_power",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "color": "#1abc9c",
                                                        "name": "Power Type",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "mode": "latest"
                                                        }
                                                    }
                                                ],
                                                "type": "html_time"
                                            },
                                            {
                                                "layout": {
                                                    "col": 4,
                                                    "row": 0,
                                                    "sizeX": 2,
                                                    "sizeY": 6
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "Device Status"
                                                },
                                                "properties": {
                                                    "source": "code",
                                                    "template": "<div style=\"width:100%; height:100%; padding:10px; font-size:14px;\">\n  <div><strong>Protocol Version:</strong> {{ value.version_protocol || 'N/A' }}</div>\n  <div><strong>Decode Status:</strong> <span ng-style=\"{color: value.status_decode ? '#27ae60' : '#e74c3c'}\">{{ value.status_decode ? 'Success' : 'Failed' }}</span></div>\n  <div ng-if=\"value.sn\"><strong>Serial Number:</strong> {{ value.sn }}</div>\n  <div ng-if=\"value.payload_size\"><strong>Payload Size:</strong> {{ value.payload_size }} bytes</div>\n</div>"
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "iot_factory_metering_node_data",
                                                            "mapping": "version_protocol",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "name": "Protocol Version",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "mode": "latest"
                                                        }
                                                    },
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "iot_factory_metering_node_data",
                                                            "mapping": "status_decode",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "name": "Status",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "mode": "latest"
                                                        }
                                                    },
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "iot_factory_metering_node_data",
                                                            "mapping": "sn",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "name": "Serial",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "mode": "latest"
                                                        }
                                                    },
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "iot_factory_metering_node_data",
                                                            "mapping": "payload_size",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "name": "Payload Size",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "mode": "latest"
                                                        }
                                                    }
                                                ],
                                                "type": "html_time"
                                            },
                                            {
                                                "layout": {
                                                    "col": 0,
                                                    "row": 6,
                                                    "sizeX": 6,
                                                    "sizeY": 12
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "Metering Frames"
                                                },
                                                "properties": {
                                                    "source": "code",
                                                    "template": "<div style=\"width:100%; height:100%; overflow-y:auto; padding:10px;\">\n  <table class=\"table table-striped table-condensed\">\n    <thead>\n      <tr>\n        <th>Timestamp</th>\n        <th>Frame Type</th>\n        <th>Reason</th>\n        <th>Data</th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr ng-repeat=\"entry in value\" ng-if=\"entry.frames && entry.frames.length > 0\">\n        <td colspan=\"4\" style=\"padding:0;\">\n          <table class=\"table table-condensed\" style=\"margin:0;\">\n            <tr ng-repeat=\"frame in entry.frames\">\n              <td style=\"width:25%;\">{{ frame.frame_isotime || entry.ts | date:'medium' }}</td>\n              <td style=\"width:20%;\"><span class=\"label\" ng-style=\"{'background-color': getFrameColor(frame.type)}\">{{ frame.type }}</span></td>\n              <td style=\"width:20%;\">{{ frame.reason }}</td>\n              <td style=\"width:35%;\">\n                <span ng-if=\"frame.temperature_cel !== undefined\">Temp: {{ frame.temperature_cel }}°C</span>\n                <span ng-if=\"frame.humidity_pct !== undefined\">Humidity: {{ frame.humidity_pct }}%</span>\n                <span ng-if=\"frame.counter !== undefined\">Counter: {{ frame.counter }}</span>\n                <span ng-if=\"frame.mA !== undefined\">Current: {{ frame.mA }}mA</span>\n                <span ng-if=\"frame.mV !== undefined\">Voltage: {{ frame.mV }}mV</span>\n                <span ng-if=\"frame.register_value !== undefined\">Modbus: {{ frame.register_value }}</span>\n                <span ng-if=\"frame.answer !== undefined\">RS485: {{ frame.answer }}</span>\n              </td>\n            </tr>\n          </table>\n        </td>\n      </tr>\n    </tbody>\n  </table>\n</div>\n<script>\n$scope.getFrameColor = function(type) {\n  var colors = {\n    'temperature': '#e74c3c',\n    'humidity': '#3498db',\n    'modbus': '#9b59b6',\n    'rs485': '#34495e',\n    'pulse_counter': '#16a085',\n    'analog_input': '#f39c12',\n    'digital_input': '#27ae60'\n  };\n  return colors[type] || '#95a5a6';\n};\n</script>"
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "iot_factory_metering_node_data",
                                                            "mapping": "frames",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "name": "Frames",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "magnitude": "day",
                                                            "mode": "relative",
                                                            "period": "latest",
                                                            "value": 7
                                                        }
                                                    },
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "iot_factory_metering_node_data",
                                                            "mapping": "ts",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "name": "Timestamp",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "magnitude": "day",
                                                            "mode": "relative",
                                                            "period": "latest",
                                                            "value": 7
                                                        }
                                                    }
                                                ],
                                                "type": "html_time"
                                            }
                                        ]
                                    },
                                    {
                                        "name": "Temperature",
                                        "widgets": [
                                            {
                                                "layout": {
                                                    "col": 0,
                                                    "row": 0,
                                                    "sizeX": 3,
                                                    "sizeY": 6
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "Latest Temperature"
                                                },
                                                "properties": {
                                                    "color": "#e74c3c",
                                                    "max": 100,
                                                    "min": -40,
                                                    "unit": "°C"
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "iot_factory_metering_node_data",
                                                            "mapping": "frames[?(@.type=='temperature')].temperature_cel",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "color": "#e74c3c",
                                                        "name": "Temperature",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "mode": "latest"
                                                        }
                                                    }
                                                ],
                                                "type": "donutchart"
                                            },
                                            {
                                                "layout": {
                                                    "col": 0,
                                                    "row": 6,
                                                    "sizeX": 6,
                                                    "sizeY": 12
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "Temperature History"
                                                },
                                                "properties": {
                                                    "axis": true,
                                                    "fill": false,
                                                    "legend": true,
                                                    "multiple_axes": false
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "iot_factory_metering_node_data",
                                                            "mapping": "frames[?(@.type=='temperature')].temperature_cel",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "color": "#e74c3c",
                                                        "name": "Temperature",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "magnitude": "day",
                                                            "mode": "relative",
                                                            "period": "latest",
                                                            "value": 7
                                                        }
                                                    }
                                                ],
                                                "type": "chart"
                                            },
                                            {
                                                "layout": {
                                                    "col": 3,
                                                    "row": 0,
                                                    "sizeX": 3,
                                                    "sizeY": 6
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "Temperature Sensors"
                                                },
                                                "properties": {
                                                    "source": "code",
                                                    "template": "<div style=\"width:100%; height:100%; overflow-y:auto; padding:10px;\">\n  <table class=\"table table-striped table-condensed\">\n    <thead>\n      <tr>\n        <th>Sensor #</th>\n        <th>Temperature</th>\n        <th>Time</th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr ng-repeat=\"entry in value\" ng-if=\"entry.frames\">\n        <tr ng-repeat=\"frame in entry.frames\" ng-if=\"frame.type === 'temperature'\">\n          <td>{{ frame.number_sensor || 'N/A' }}</td>\n          <td>{{ frame.temperature_cel }}°C</td>\n          <td>{{ frame.frame_isotime | date:'short' }}</td>\n        </tr>\n      </tr>\n    </tbody>\n  </table>\n</div>"
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "iot_factory_metering_node_data",
                                                            "mapping": "frames",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "name": "Frames",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "magnitude": "hour",
                                                            "mode": "relative",
                                                            "period": "latest",
                                                            "value": 24
                                                        }
                                                    }
                                                ],
                                                "type": "html_time"
                                            }
                                        ]
                                    },
                                    {
                                        "name": "Modbus/RS485",
                                        "widgets": [
                                            {
                                                "layout": {
                                                    "col": 0,
                                                    "row": 0,
                                                    "sizeX": 6,
                                                    "sizeY": 12
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "Modbus Registers"
                                                },
                                                "properties": {
                                                    "source": "code",
                                                    "template": "<div style=\"width:100%; height:100%; overflow-y:auto; padding:10px;\">\n  <table class=\"table table-striped table-condensed\">\n    <thead>\n      <tr>\n        <th>Time</th>\n        <th>Name</th>\n        <th>Slave Addr</th>\n        <th>Register</th>\n        <th>Function</th>\n        <th>Value</th>\n        <th>Signed</th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr ng-repeat=\"entry in value\" ng-if=\"entry.frames\">\n        <tr ng-repeat=\"frame in entry.frames\" ng-if=\"frame.type === 'modbus'\">\n          <td>{{ frame.frame_isotime | date:'short' }}</td>\n          <td>{{ frame.name }}</td>\n          <td>{{ frame.slave_address }}</td>\n          <td>{{ frame.register_address }}</td>\n          <td>{{ frame.modbus_function }}</td>\n          <td><code>{{ frame.register_value }}</code></td>\n          <td>{{ frame.signed_flag }}</td>\n        </tr>\n      </tr>\n    </tbody>\n  </table>\n</div>"
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "iot_factory_metering_node_data",
                                                            "mapping": "frames",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "name": "Frames",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "magnitude": "day",
                                                            "mode": "relative",
                                                            "period": "latest",
                                                            "value": 7
                                                        }
                                                    }
                                                ],
                                                "type": "html_time"
                                            },
                                            {
                                                "layout": {
                                                    "col": 0,
                                                    "row": 12,
                                                    "sizeX": 6,
                                                    "sizeY": 12
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "RS485 Messages"
                                                },
                                                "properties": {
                                                    "source": "code",
                                                    "template": "<div style=\"width:100%; height:100%; overflow-y:auto; padding:10px;\">\n  <table class=\"table table-striped table-condensed\">\n    <thead>\n      <tr>\n        <th>Time</th>\n        <th>Measurement Row</th>\n        <th>Command #</th>\n        <th>Size</th>\n        <th>Answer (Hex)</th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr ng-repeat=\"entry in value\" ng-if=\"entry.frames\">\n        <tr ng-repeat=\"frame in entry.frames\" ng-if=\"frame.type === 'rs485'\">\n          <td>{{ frame.frame_isotime | date:'short' }}</td>\n          <td>{{ frame.measurement_row_number }}</td>\n          <td>{{ frame.command_number_row }}</td>\n          <td>{{ frame.size_answer }} bytes</td>\n          <td><code>{{ frame.answer }}</code></td>\n        </tr>\n      </tr>\n    </tbody>\n  </table>\n</div>"
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "iot_factory_metering_node_data",
                                                            "mapping": "frames",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "name": "Frames",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "magnitude": "day",
                                                            "mode": "relative",
                                                            "period": "latest",
                                                            "value": 7
                                                        }
                                                    }
                                                ],
                                                "type": "html_time"
                                            }
                                        ]
                                    },
                                    {
                                        "name": "Counters & Analog",
                                        "widgets": [
                                            {
                                                "layout": {
                                                    "col": 0,
                                                    "row": 0,
                                                    "sizeX": 3,
                                                    "sizeY": 12
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "Pulse Counters"
                                                },
                                                "properties": {
                                                    "source": "code",
                                                    "template": "<div style=\"width:100%; height:100%; overflow-y:auto; padding:10px;\">\n  <table class=\"table table-striped table-condensed\">\n    <thead>\n      <tr>\n        <th>Time</th>\n        <th>Pin #</th>\n        <th>Counter Value</th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr ng-repeat=\"entry in value\" ng-if=\"entry.frames\">\n        <tr ng-repeat=\"frame in entry.frames\" ng-if=\"frame.type === 'pulse_counter'\">\n          <td>{{ frame.frame_isotime | date:'short' }}</td>\n          <td>{{ frame.pin_number }}</td>\n          <td><strong>{{ frame.counter }}</strong></td>\n        </tr>\n      </tr>\n    </tbody>\n  </table>\n</div>"
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "iot_factory_metering_node_data",
                                                            "mapping": "frames",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "name": "Frames",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "magnitude": "day",
                                                            "mode": "relative",
                                                            "period": "latest",
                                                            "value": 7
                                                        }
                                                    }
                                                ],
                                                "type": "html_time"
                                            },
                                            {
                                                "layout": {
                                                    "col": 3,
                                                    "row": 0,
                                                    "sizeX": 3,
                                                    "sizeY": 12
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "Analog Inputs"
                                                },
                                                "properties": {
                                                    "source": "code",
                                                    "template": "<div style=\"width:100%; height:100%; overflow-y:auto; padding:10px;\">\n  <table class=\"table table-striped table-condensed\">\n    <thead>\n      <tr>\n        <th>Time</th>\n        <th>Pin #</th>\n        <th>Voltage (mV)</th>\n        <th>Status</th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr ng-repeat=\"entry in value\" ng-if=\"entry.frames\">\n        <tr ng-repeat=\"frame in entry.frames\" ng-if=\"frame.type === 'analog_input'\">\n          <td>{{ frame.frame_isotime | date:'short' }}</td>\n          <td>{{ frame.pin_number }}</td>\n          <td>{{ frame.mV }} mV</td>\n          <td><span class=\"label\" ng-class=\"{'label-success': frame.input_status === 'ok', 'label-danger': frame.input_status !== 'ok'}\">{{ frame.input_status }}</span></td>\n        </tr>\n      </tr>\n    </tbody>\n  </table>\n</div>"
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "iot_factory_metering_node_data",
                                                            "mapping": "frames",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "name": "Frames",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "magnitude": "day",
                                                            "mode": "relative",
                                                            "period": "latest",
                                                            "value": 7
                                                        }
                                                    }
                                                ],
                                                "type": "html_time"
                                            },
                                            {
                                                "layout": {
                                                    "col": 0,
                                                    "row": 12,
                                                    "sizeX": 6,
                                                    "sizeY": 12
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "4-20mA Sensors"
                                                },
                                                "properties": {
                                                    "source": "code",
                                                    "template": "<div style=\"width:100%; height:100%; overflow-y:auto; padding:10px;\">\n  <table class=\"table table-striped table-condensed\">\n    <thead>\n      <tr>\n        <th>Time</th>\n        <th>Sensor #</th>\n        <th>Current (mA)</th>\n        <th>Reason</th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr ng-repeat=\"entry in value\" ng-if=\"entry.frames\">\n        <tr ng-repeat=\"frame in entry.frames\" ng-if=\"frame.type === '4_20mA'\">\n          <td>{{ frame.frame_isotime | date:'short' }}</td>\n          <td>{{ frame.number_sensor }}</td>\n          <td><strong>{{ frame.mA }}</strong> mA</td>\n          <td><span class=\"label\" ng-class=\"{'label-warning': frame.reason === 'alarm', 'label-default': frame.reason === 'regular'}\">{{ frame.reason }}</span></td>\n        </tr>\n      </tr>\n    </tbody>\n  </table>\n</div>"
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "iot_factory_metering_node_data",
                                                            "mapping": "frames",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "name": "Frames",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "magnitude": "day",
                                                            "mode": "relative",
                                                            "period": "latest",
                                                            "value": 7
                                                        }
                                                    }
                                                ],
                                                "type": "html_time"
                                            }
                                        ]
                                    }
                                ]
                            }
                        }
                    ]
                }
            }
        ]
    }
}