Plugin file
Plugin configuration file
{
"name": "milesight-iot-vs360",
"version": "1.0.0",
"description": "The VS360 is a people counting sensor that is based on IR Breakbeam technology. Equipped with two devices,the principle is that the target blocks the infrared beam by detecting the area to achieve the purpose of counting.",
"author": "Thinger.io",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/thinger-io/plugins.git",
"directory": "milesight-iot-vs360"
},
"metadata": {
"name": "Milesight-Iot VS360",
"description": "The VS360 is a people counting sensor that is based on IR Breakbeam technology. Equipped with two devices,the principle is that the target blocks the infrared beam by detecting the area to achieve the purpose of counting.",
"image": "assets/vs360.png",
"category": "devices",
"vendor": "milesight-iot"
},
"resources": {
"products": [
{
"config": {
"icons": []
},
"description": "The VS360 is a people counting sensor that is based on IR Breakbeam technology. Equipped with two devices,the principle is that the target blocks the infrared beam by detecting the area to achieve the purpose of counting.",
"enabled": true,
"name": "Milesight-Iot VS360",
"product": "milesight_iot_vs360",
"profile": {
"api": {
"downlink": {
"enabled": true,
"handle_connectivity": false,
"request": {
"data": {
"path": "/downlink",
"payload": "{\r\n \"data\" : \"{{payload.data=\"\"}}\",\r\n \"port\" : {{payload.port=2}},\r\n \"priority\": {{payload.priority=3}},\r\n \"confirmed\" : {{payload.confirmed=false}},\r\n \"uplink\" : {{property.uplink}} \r\n}",
"payload_function": "",
"payload_type": "",
"plugin": "{{property.uplink.source}}",
"target": "plugin_endpoint"
}
},
"response": {
"data": {}
}
},
"uplink": {
"enabled": true,
"handle_connectivity": true,
"request": {
"data": {
"payload": "{{payload}}",
"payload_function": "",
"payload_type": "source_payload",
"resource_stream": "uplink",
"target": "resource_stream"
}
},
"response": {
"data": {}
}
}
},
"autoprovisions": {
"device_autoprovisioning": {
"config": {
"mode": "pattern",
"pattern": "vs360_.*"
},
"enabled": true
}
},
"buckets": {
"milesight_vs360_data": {
"backend": "mongodb",
"data": {
"payload": "{{payload}}",
"payload_function": "",
"payload_type": "source_payload",
"resource_stream": "uplink_decoded",
"source": "resource_stream"
},
"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\nfunction decodeUplink(input) {\n var res = Decoder(input.bytes, input.fPort);\n if (res.error) {\n return {\n errors: [res.error],\n };\n }\n return {\n data: res,\n };\n}\n/**\n * Payload Decoder\n *\n * Copyright 2025 Milesight IoT\n *\n * @product VS360\n */\nvar RAW_VALUE = 0x00;\n\n/* eslint no-redeclare: \"off\" */\n/* eslint-disable */\n\n// The Things Network\nfunction Decoder(bytes, port) {\n return milesightDeviceDecode(bytes);\n}\n/* eslint-enable */\n\nfunction milesightDeviceDecode(bytes) {\n var decoded = {};\n\n for (var i = 0; i < bytes.length; ) {\n var channel_id = bytes[i++];\n var channel_type = bytes[i++];\n\n // IPSO VERSION\n if (channel_id === 0xff && channel_type === 0x01) {\n decoded.ipso_version = readProtocolVersion(bytes[i]);\n i += 1;\n }\n // HARDWARE VERSION\n else if (channel_id === 0xff && channel_type === 0x09) {\n decoded.hardware_version = readHardwareVersion(bytes.slice(i, i + 2));\n i += 2;\n }\n // FIRMWARE VERSION\n else if (channel_id === 0xff && channel_type === 0x0a) {\n decoded.firmware_version = readFirmwareVersion(bytes.slice(i, i + 2));\n i += 2;\n }\n // TSL VERSION\n else if (channel_id === 0xff && channel_type === 0xff) {\n decoded.tsl_version = readTslVersion(bytes.slice(i, i + 2));\n i += 2;\n }\n // SERIAL NUMBER\n else if (channel_id === 0xff && channel_type === 0x16) {\n decoded.sn = readSerialNumber(bytes.slice(i, i + 8));\n i += 8;\n }\n // LORAWAN CLASS TYPE\n else if (channel_id === 0xff && channel_type === 0x0f) {\n decoded.lorawan_class = readLoRaWANClass(bytes[i]);\n i += 1;\n }\n // RESET EVENT\n else if (channel_id === 0xff && channel_type === 0xfe) {\n decoded.reset_event = readResetEvent(1);\n i += 1;\n }\n // DEVICE STATUS\n else if (channel_id === 0xff && channel_type === 0x0b) {\n decoded.device_status = readDeviceStatus(1);\n i += 1;\n }\n // BATTERY(MAIN)\n else if (channel_id === 0x01 && channel_type === 0x75) {\n decoded.battery_main = readUInt8(bytes[i]);\n i += 1;\n }\n // BATTERY(NODE)\n else if (channel_id === 0x02 && channel_type === 0x75) {\n decoded.battery_node = readUInt8(bytes[i]);\n i += 1;\n }\n // EVENT\n else if (channel_id === 0x03 && channel_type === 0xf4) {\n var data = {};\n data.type = readEventType(bytes[i]);\n data.status = readEventStatus(bytes[i + 1]);\n decoded.event = decoded.event || [];\n decoded.event.push(data);\n i += 2;\n }\n // TOTAL IN / OUT\n else if (channel_id === 0x04 && channel_type === 0xcc) {\n decoded.total_in = readUInt16LE(bytes.slice(i, i + 2));\n decoded.total_out = readUInt16LE(bytes.slice(i + 2, i + 4));\n i += 4;\n }\n // PERIOD IN / OUT\n else if (channel_id === 0x05 && channel_type === 0xcc) {\n decoded.period_in = readUInt16LE(bytes.slice(i, i + 2));\n decoded.period_out = readUInt16LE(bytes.slice(i + 2, i + 4));\n i += 4;\n }\n // TOTAL IN / OUT ALARM\n else if (channel_id === 0x84 && channel_type === 0xcc) {\n decoded.total_in = readUInt16LE(bytes.slice(i, i + 2));\n decoded.total_out = readUInt16LE(bytes.slice(i + 2, i + 4));\n decoded.total_count_alarm = readAlarmType(bytes[i + 4]);\n i += 5;\n }\n // PERIOD IN / OUT ALARM\n else if (channel_id === 0x85 && channel_type === 0xcc) {\n decoded.period_in = readUInt16LE(bytes.slice(i, i + 2));\n decoded.period_out = readUInt16LE(bytes.slice(i + 2, i + 4));\n decoded.period_count_alarm = readAlarmType(bytes[i + 4]);\n i += 5;\n }\n // TIMESTAMP\n else if (channel_id === 0x0a && channel_type === 0xef) {\n decoded.timestamp = readUInt32LE(bytes.slice(i, i + 4));\n i += 4;\n }\n // HISTORICAL DATA\n else if (channel_id === 0x20 && channel_type === 0xce) {\n var data = {};\n var timestamp = readUInt32LE(bytes.slice(i, i + 4));\n var type = bytes[i + 4];\n // historical data without total in/out\n if (type === 0) {\n data.timestamp = timestamp;\n data.period_in = readUInt16LE(bytes.slice(i + 5, i + 7));\n data.period_out = readUInt16LE(bytes.slice(i + 7, i + 9));\n i += 9;\n }\n // historical data with total in/out\n else if (type === 1) {\n data.timestamp = timestamp;\n data.period_in = readUInt16LE(bytes.slice(i + 5, i + 7));\n data.period_out = readUInt16LE(bytes.slice(i + 7, i + 9));\n data.total_in = readUInt16LE(bytes.slice(i + 9, i + 11));\n data.total_out = readUInt16LE(bytes.slice(i + 11, i + 13));\n i += 13;\n }\n decoded.history = decoded.history || [];\n decoded.history.push(data);\n }\n // DOWNLINK RESPONSE\n else if (channel_id === 0xfe || channel_id === 0xff) {\n var result = handle_downlink_response(channel_type, bytes, i);\n decoded = Object.assign(decoded, result.data);\n i = result.offset;\n } else if (channel_id === 0xf8 || channel_id === 0xf9) {\n var result = handle_downlink_response_ext(channel_id, channel_type, bytes, i);\n decoded = Object.assign(decoded, result.data);\n i = result.offset;\n } else {\n break;\n }\n }\n\n return decoded;\n}\n\nfunction handle_downlink_response(channel_type, bytes, offset) {\n var decoded = {};\n\n switch (channel_type) {\n case 0x06:\n var data = readUInt8(bytes[offset]);\n var trigger_source = (data >>> 3) & 0x07;\n if (trigger_source === 1) {\n decoded.people_period_alarm_config = {};\n decoded.people_period_alarm_config.condition = readConditionType(data & 0x07);\n decoded.people_period_alarm_config.threshold_out = readUInt16LE(bytes.slice(offset + 1, offset + 3));\n decoded.people_period_alarm_config.threshold_in = readUInt16LE(bytes.slice(offset + 3, offset + 5));\n } else if (trigger_source === 2) {\n decoded.people_cumulative_alarm_config = {};\n decoded.people_cumulative_alarm_config.condition = readConditionType(data & 0x07);\n decoded.people_cumulative_alarm_config.threshold_out = readUInt16LE(bytes.slice(offset + 1, offset + 3));\n decoded.people_cumulative_alarm_config.threshold_in = readUInt16LE(bytes.slice(offset + 3, offset + 5));\n } else if (trigger_source === 3) {\n decoded.temperature_alarm_config = {};\n decoded.temperature_alarm_config.condition = readConditionType(data & 0x07);\n decoded.temperature_alarm_config.threshold_min = readUInt16LE(bytes.slice(offset + 1, offset + 3)) / 10;\n decoded.temperature_alarm_config.threshold_max = readUInt16LE(bytes.slice(offset + 3, offset + 5)) / 10;\n }\n offset += 9;\n break;\n case 0x10:\n decoded.reboot = readYesNoStatus(1);\n offset += 1;\n break;\n case 0x28:\n decoded.report_status = readYesNoStatus(1);\n offset += 1;\n break;\n case 0x35:\n decoded.d2d_key = readHexString(bytes.slice(offset, offset + 8));\n offset += 8;\n break;\n case 0x4a:\n decoded.sync_time = readYesNoStatus(1);\n offset += 1;\n break;\n case 0x68:\n decoded.history_enable = readEnableStatus(bytes[offset]);\n offset += 1;\n break;\n case 0x69:\n decoded.retransmit_enable = readEnableStatus(bytes[offset]);\n offset += 1;\n break;\n case 0x6a:\n var data = readUInt8(bytes[offset]);\n if (data === 0x00) {\n decoded.retransmit_interval = readUInt16LE(bytes.slice(offset + 1, offset + 3));\n } else if (data === 0x01) {\n decoded.resend_interval = readUInt16LE(bytes.slice(offset + 1, offset + 3));\n }\n offset += 3;\n break;\n case 0x6d:\n decoded.stop_transmit = readYesNoStatus(1);\n offset += 1;\n break;\n case 0x75:\n decoded.hibernate_config = {};\n decoded.hibernate_config.enable = readEnableStatus(bytes[offset]);\n decoded.hibernate_config.start_time = readUInt16LE(bytes.slice(offset + 1, offset + 3));\n decoded.hibernate_config.end_time = readUInt16LE(bytes.slice(offset + 3, offset + 5));\n var weekdays = readUInt8(bytes[offset + 5]);\n decoded.hibernate_config.weekdays = {};\n var weekday_bit_offset = { monday: 1, tuesday: 2, wednesday: 3, thursday: 4, friday: 5, saturday: 6, sunday: 7 };\n for (var key in weekday_bit_offset) {\n decoded.hibernate_config.weekdays[key] = readEnableStatus((weekdays >> weekday_bit_offset[key]) & 0x01);\n }\n offset += 6;\n break;\n case 0x84:\n decoded.d2d_enable = readEnableStatus(bytes[offset]);\n offset += 1;\n break;\n case 0x8e:\n // skip the first byte\n decoded.report_interval = readUInt16LE(bytes.slice(offset + 1, offset + 3));\n offset += 3;\n break;\n case 0x96:\n var d2d_master_config = {};\n d2d_master_config.mode = readD2DMode(readUInt8(bytes[offset]));\n d2d_master_config.enable = readEnableStatus(bytes[offset + 1]);\n d2d_master_config.lora_uplink_enable = readEnableStatus(bytes[offset + 2]);\n d2d_master_config.d2d_cmd = readD2DCommand(bytes.slice(offset + 3, offset + 5));\n d2d_master_config.time = readUInt16LE(bytes.slice(offset + 5, offset + 7));\n d2d_master_config.time_enable = readEnableStatus(bytes[offset + 7]);\n offset += 8;\n decoded.d2d_master_config = decoded.d2d_master_config || [];\n decoded.d2d_master_config.push(d2d_master_config);\n break;\n case 0xa6:\n decoded.reset_cumulative_enable = readEnableStatus(bytes[offset]);\n offset += 1;\n break;\n case 0xa9:\n decoded.report_cumulative_enable = readEnableStatus(bytes[offset]);\n offset += 1;\n break;\n case 0xbd:\n decoded.time_zone = readTimeZone(readInt16LE(bytes.slice(offset, offset + 2)));\n offset += 2;\n break;\n case 0xa8:\n var data = readUInt8(bytes[offset]);\n if (data === 0x01) {\n decoded.reset_cumulative_in = readYesNoStatus(1);\n } else if (data === 0x02) {\n decoded.reset_cumulative_out = readYesNoStatus(1);\n }\n offset += 1;\n break;\n case 0xed:\n decoded.reset_cumulative_schedule_config = {};\n decoded.reset_cumulative_schedule_config.weekday = readWeekday(readUInt8(bytes.slice(offset, offset + 1)));\n decoded.reset_cumulative_schedule_config.hour = readUInt8(bytes.slice(offset + 1, offset + 2));\n decoded.reset_cumulative_schedule_config.minute = readUInt8(bytes.slice(offset + 2, offset + 3));\n offset += 3;\n break;\n case 0xfc:\n decoded.counting_mode = readCountingMode(bytes[offset]);\n offset += 1;\n break;\n case 0xfd:\n decoded.led_indicator_enable = readEnableStatus(bytes[offset]);\n offset += 1;\n break;\n default:\n throw new Error(\"unknown downlink response\");\n }\n\n return { data: decoded, offset: offset };\n}\n\nfunction handle_downlink_response_ext(code, channel_type, bytes, offset) {\n var decoded = {};\n\n switch (channel_type) {\n case 0x10:\n decoded.report_type = readReportType(bytes[offset]);\n offset += 1;\n break;\n default:\n throw new Error(\"unknown downlink response\");\n }\n\n if (hasResultFlag(code)) {\n var result_value = readUInt8(bytes[offset]);\n offset += 1;\n\n if (result_value !== 0) {\n var request = decoded;\n decoded = {};\n decoded.device_response_result = {};\n decoded.device_response_result.channel_type = channel_type;\n decoded.device_response_result.result = readResultStatus(result_value);\n decoded.device_response_result.request = request;\n }\n }\n\n return { data: decoded, offset: offset };\n}\n\nfunction hasResultFlag(code) {\n return code === 0xf8;\n}\n\nfunction readResultStatus(status) {\n var status_map = { 0: \"success\", 1: \"forbidden\", 2: \"invalid parameter\" };\n return getValue(status_map, status);\n}\n\nfunction readProtocolVersion(bytes) {\n var major = (bytes & 0xf0) >> 4;\n var minor = bytes & 0x0f;\n return \"v\" + major + \".\" + minor;\n}\n\nfunction readHardwareVersion(bytes) {\n var major = (bytes[0] & 0xff).toString(16);\n var minor = (bytes[1] & 0xff) >> 4;\n return \"v\" + major + \".\" + minor;\n}\n\nfunction readFirmwareVersion(bytes) {\n var major = (bytes[0] & 0xff).toString(16);\n var minor = (bytes[1] & 0xff).toString(16);\n return \"v\" + major + \".\" + minor;\n}\n\nfunction readTslVersion(bytes) {\n var major = bytes[0] & 0xff;\n var minor = bytes[1] & 0xff;\n return \"v\" + major + \".\" + minor;\n}\n\nfunction readSerialNumber(bytes) {\n var temp = [];\n for (var idx = 0; idx < bytes.length; idx++) {\n temp.push((\"0\" + (bytes[idx] & 0xff).toString(16)).slice(-2));\n }\n return temp.join(\"\");\n}\n\nfunction readLoRaWANClass(type) {\n var class_map = {\n 0: \"Class A\",\n 1: \"Class B\",\n 2: \"Class C\",\n 3: \"Class CtoB\",\n };\n return getValue(class_map, type);\n}\n\nfunction readResetEvent(status) {\n var status_map = { 0: \"normal\", 1: \"reset\" };\n return getValue(status_map, status);\n}\n\nfunction readDeviceStatus(status) {\n var status_map = { 0: \"off\", 1: \"on\" };\n return getValue(status_map, status);\n}\n\nfunction readEnableStatus(status) {\n var status_map = { 0: \"disable\", 1: \"enable\" };\n return getValue(status_map, status);\n}\n\nfunction readYesNoStatus(status) {\n var status_map = { 0: \"no\", 1: \"yes\" };\n return getValue(status_map, status);\n}\n\nfunction readAlarmType(type) {\n var alarm_map = { 1: \"threshold_alarm\" };\n return getValue(alarm_map, type);\n}\n\nfunction readEventType(type) {\n var event_type = { 0: \"counting_anomaly\", 1: \"node_device_without_response\", 2: \"devices_misaligned\" };\n return getValue(event_type, type);\n}\n\nfunction readEventStatus(status) {\n var event_status = { 0: \"alarm_release\", 1: \"alarm\" };\n return getValue(event_status, status);\n}\n\nfunction readTimeZone(time_zone) {\n var timezone_map = { \"-720\": \"UTC-12\", \"-660\": \"UTC-11\", \"-600\": \"UTC-10\", \"-570\": \"UTC-9:30\", \"-540\": \"UTC-9\", \"-480\": \"UTC-8\", \"-420\": \"UTC-7\", \"-360\": \"UTC-6\", \"-300\": \"UTC-5\", \"-240\": \"UTC-4\", \"-210\": \"UTC-3:30\", \"-180\": \"UTC-3\", \"-120\": \"UTC-2\", \"-60\": \"UTC-1\", 0: \"UTC\", 60: \"UTC+1\", 120: \"UTC+2\", 180: \"UTC+3\", 210: \"UTC+3:30\", 240: \"UTC+4\", 270: \"UTC+4:30\", 300: \"UTC+5\", 330: \"UTC+5:30\", 345: \"UTC+5:45\", 360: \"UTC+6\", 390: \"UTC+6:30\", 420: \"UTC+7\", 480: \"UTC+8\", 540: \"UTC+9\", 570: \"UTC+9:30\", 600: \"UTC+10\", 630: \"UTC+10:30\", 660: \"UTC+11\", 720: \"UTC+12\", 765: \"UTC+12:45\", 780: \"UTC+13\", 840: \"UTC+14\" };\n return getValue(timezone_map, time_zone);\n}\n\nfunction readWeekday(weekday) {\n var weekday_map = { 0: \"everyday\", 1: \"sunday\", 2: \"monday\", 3: \"tuesday\", 4: \"wednesday\", 5: \"thursday\", 6: \"friday\", 7: \"saturday\" };\n return getValue(weekday_map, weekday);\n}\n\nfunction readCountingMode(mode) {\n var mode_map = { 2: \"high_mode\", 3: \"low_mode\" };\n return getValue(mode_map, mode);\n}\n\nfunction readD2DMode(mode) {\n var mode_map = { 1: \"someone_enter\", 2: \"someone_leave\", 3: \"counting_threshold_alarm\" };\n return getValue(mode_map, mode);\n}\n\nfunction readConditionType(type) {\n var condition_map = { 0: \"disable\", 1: \"below\", 2: \"above\", 3: \"between\", 4: \"outside\" };\n return getValue(condition_map, type);\n}\n\nfunction readReportType(type) {\n var report_type_map = { 0: \"period\", 1: \"immediately\" };\n return getValue(report_type_map, type);\n}\n\n/* eslint-disable */\nfunction readUInt8(bytes) {\n return bytes & 0xff;\n}\n\nfunction readInt8(bytes) {\n var ref = readUInt8(bytes);\n return ref > 0x7f ? ref - 0x100 : ref;\n}\n\nfunction readUInt16LE(bytes) {\n var value = (bytes[1] << 8) + bytes[0];\n return value & 0xffff;\n}\n\nfunction readInt16LE(bytes) {\n var ref = readUInt16LE(bytes);\n return ref > 0x7fff ? ref - 0x10000 : ref;\n}\n\nfunction readUInt32LE(bytes) {\n var value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0];\n return (value & 0xffffffff) >>> 0;\n}\n\nfunction readInt32LE(bytes) {\n var ref = readUInt32LE(bytes);\n return ref > 0x7fffffff ? ref - 0x100000000 : ref;\n}\n\nfunction readHexString(bytes) {\n var temp = [];\n for (var i = 0; i < bytes.length; i++) {\n temp.push((\"0\" + (bytes[i] & 0xff).toString(16)).slice(-2));\n }\n return temp.join(\"\");\n}\n\nfunction readD2DCommand(bytes) {\n return (\"0\" + (bytes[1] & 0xff).toString(16)).slice(-2) + (\"0\" + (bytes[0] & 0xff).toString(16)).slice(-2);\n}\n\nfunction getValue(map, key) {\n if (RAW_VALUE) return key;\n\n var value = map[key];\n if (!value) value = \"unknown\";\n return value;\n}\n\nif (!Object.assign) {\n Object.defineProperty(Object, \"assign\", {\n enumerable: false,\n configurable: true,\n writable: true,\n value: function (target) {\n \"use strict\";\n if (target == null) {\n throw new TypeError(\"Cannot convert first argument to object\");\n }\n\n var to = Object(target);\n for (var i = 1; i < arguments.length; i++) {\n var nextSource = arguments[i];\n if (nextSource == null) {\n continue;\n }\n nextSource = Object(nextSource);\n\n var keysArray = Object.keys(Object(nextSource));\n for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {\n var nextKey = keysArray[nextIndex];\n var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);\n if (desc !== undefined && desc.enumerable) {\n // concat array\n if (Array.isArray(to[nextKey]) && Array.isArray(nextSource[nextKey])) {\n to[nextKey] = to[nextKey].concat(nextSource[nextKey]);\n } else {\n to[nextKey] = nextSource[nextKey];\n }\n }\n }\n }\n return to;\n },\n });\n}",
"environment": "javascript",
"storage": "",
"version": "1.0"
},
"flows": {
"milesight_vs360_decoder": {
"data": {
"payload": "{{payload}}",
"payload_function": "decodeThingerUplink",
"payload_type": "source_payload",
"resource": "uplink",
"source": "resource",
"update": "events"
},
"enabled": true,
"sink": {
"payload": "{{payload}}",
"payload_function": "",
"payload_type": "source_payload",
"resource_stream": "uplink_decoded",
"target": "resource_stream"
},
"split_data": false
}
},
"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": "People Counter",
"widgets": [
{
"layout": {
"col": 0,
"row": 0,
"sizeX": 2,
"sizeY": 5
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
},
"title": "Total In"
},
"properties": {
"decimalPlaces": 0,
"enableExtraTextColor": false,
"enableIconColor": false,
"enableIconSize": false,
"extraText": "",
"extraTextColor": "#1E313E",
"extraTextColorConditions": [],
"extraTextConditions": [],
"extraTextPosition": "above-value",
"extraTextSize": "20px",
"extraTextWeight": "font-light",
"icon": "",
"iconColor": "#1E313E",
"iconColorConditions": [],
"iconConditions": [],
"iconGap": "8px",
"iconPosition": "before-value",
"iconSize": "75px",
"iconVerticalOffset": "0px",
"link": "",
"textAlign": "center",
"textColor": "#1E313E",
"textColorConditions": [],
"textSize": "75px",
"textWeight": "font-light",
"unit": "",
"unitSize": "20px"
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs360_data",
"mapping": "total_in",
"tags": {
"device": [],
"group": []
}
},
"color": "#2ecc71",
"name": "Total In",
"source": "bucket",
"timespan": {
"mode": "latest"
}
}
],
"type": "text"
},
{
"layout": {
"col": 2,
"row": 0,
"sizeX": 2,
"sizeY": 5
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
},
"title": "Total Out"
},
"properties": {
"decimalPlaces": 0,
"enableExtraTextColor": false,
"enableIconColor": false,
"enableIconSize": false,
"extraText": "",
"extraTextColor": "#1E313E",
"extraTextColorConditions": [],
"extraTextConditions": [],
"extraTextPosition": "above-value",
"extraTextSize": "20px",
"extraTextWeight": "font-light",
"icon": "",
"iconColor": "#1E313E",
"iconColorConditions": [],
"iconConditions": [],
"iconGap": "8px",
"iconPosition": "before-value",
"iconSize": "75px",
"iconVerticalOffset": "0px",
"link": "",
"textAlign": "center",
"textColor": "#1E313E",
"textColorConditions": [],
"textSize": "75px",
"textWeight": "font-light",
"unit": "",
"unitSize": "20px"
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs360_data",
"mapping": "total_out",
"tags": {
"device": [],
"group": []
}
},
"color": "#e74c3c",
"name": "Total Out",
"source": "bucket",
"timespan": {
"mode": "latest"
}
}
],
"type": "text"
},
{
"layout": {
"col": 4,
"row": 0,
"sizeX": 2,
"sizeY": 5
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
},
"title": "Net Count"
},
"properties": {
"decimalPlaces": 0,
"enableExtraTextColor": false,
"enableIconColor": false,
"enableIconSize": false,
"extraText": "",
"extraTextColor": "#1E313E",
"extraTextColorConditions": [],
"extraTextConditions": [],
"extraTextPosition": "above-value",
"extraTextSize": "20px",
"extraTextWeight": "font-light",
"icon": "",
"iconColor": "#1E313E",
"iconColorConditions": [],
"iconConditions": [],
"iconGap": "8px",
"iconPosition": "before-value",
"iconSize": "75px",
"iconVerticalOffset": "0px",
"link": "",
"textAlign": "center",
"textColor": "#1E313E",
"textColorConditions": [],
"textSize": "75px",
"textWeight": "font-light",
"unit": "",
"unitSize": "20px"
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs360_data",
"mapping": "total_in",
"tags": {
"device": [],
"group": []
}
},
"color": "#3498db",
"name": "Total In",
"source": "bucket",
"timespan": {
"mode": "latest"
}
},
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs360_data",
"mapping": "total_out",
"tags": {
"device": [],
"group": []
}
},
"color": "#e74c3c",
"name": "Total Out",
"processing": {
"expression": "total_in - total_out"
},
"source": "bucket",
"timespan": {
"mode": "latest"
}
}
],
"type": "text"
},
{
"layout": {
"col": 0,
"row": 5,
"sizeX": 6,
"sizeY": 10
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
},
"title": "People Count (24h)"
},
"properties": {
"alignTimeSeries": false,
"dataAppend": false,
"options": "var options = {\n chart: {\n type: 'line',\n stacked: false\n },\n dataLabels: {\n enabled: false\n },\n stroke: {\n curve: 'smooth',\n width: 3\n },\n xaxis: {\n type: 'datetime',\n labels: {\n datetimeUTC: false\n },\n tooltip: {\n enabled: false\n }\n },\n yaxis: {\n labels: {\n formatter: function (val) {\n if (val !== null && typeof val !== 'undefined')\n return val.toFixed(0);\n }\n }\n },\n tooltip: {\n x: {\n format: 'dd/MM/yyyy HH:mm:ss'\n }\n },\n legend: {\n position: 'top'\n }\n};\n",
"realTimeUpdate": true
},
"sources": [
{
"aggregation": {},
"bucket": {
"backend": "mongodb",
"id": "milesight_vs360_data",
"mapping": "total_in",
"tags": {
"device": [],
"group": []
}
},
"color": "#2ecc71",
"name": "Total In",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
},
{
"aggregation": {},
"bucket": {
"backend": "mongodb",
"id": "milesight_vs360_data",
"mapping": "total_out",
"tags": {
"device": [],
"group": []
}
},
"color": "#e74c3c",
"name": "Total Out",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
}
],
"type": "apex_charts"
},
{
"layout": {
"col": 0,
"row": 15,
"sizeX": 6,
"sizeY": 10
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
},
"title": "Period Count (24h)"
},
"properties": {
"alignTimeSeries": false,
"dataAppend": false,
"options": "var options = {\n chart: {\n type: 'bar',\n stacked: false\n },\n plotOptions: {\n bar: {\n horizontal: false,\n columnWidth: '55%',\n endingShape: 'rounded'\n }\n },\n dataLabels: {\n enabled: false\n },\n xaxis: {\n type: 'datetime',\n labels: {\n datetimeUTC: false\n },\n tooltip: {\n enabled: false\n }\n },\n yaxis: {\n labels: {\n formatter: function (val) {\n if (val !== null && typeof val !== 'undefined')\n return val.toFixed(0);\n }\n }\n },\n tooltip: {\n x: {\n format: 'dd/MM/yyyy HH:mm:ss'\n }\n },\n legend: {\n position: 'top'\n }\n};\n",
"realTimeUpdate": true
},
"sources": [
{
"aggregation": {},
"bucket": {
"backend": "mongodb",
"id": "milesight_vs360_data",
"mapping": "period_in",
"tags": {
"device": [],
"group": []
}
},
"color": "#3498db",
"name": "Period In",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
},
{
"aggregation": {},
"bucket": {
"backend": "mongodb",
"id": "milesight_vs360_data",
"mapping": "period_out",
"tags": {
"device": [],
"group": []
}
},
"color": "#f39c12",
"name": "Period Out",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
}
],
"type": "apex_charts"
},
{
"layout": {
"col": 0,
"row": 25,
"sizeX": 2,
"sizeY": 5
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
},
"title": "Battery Main"
},
"properties": {
"color": "#2ebd59",
"gradient": false,
"max": 100,
"min": 0,
"unit": "%"
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs360_data",
"mapping": "battery_main",
"tags": {
"device": [],
"group": []
}
},
"color": "#2ebd59",
"name": "Battery Main",
"source": "bucket",
"timespan": {
"mode": "latest"
}
}
],
"type": "gauge"
},
{
"layout": {
"col": 2,
"row": 25,
"sizeX": 2,
"sizeY": 5
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
},
"title": "Battery Node"
},
"properties": {
"color": "#2ebd59",
"gradient": false,
"max": 100,
"min": 0,
"unit": "%"
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs360_data",
"mapping": "battery_node",
"tags": {
"device": [],
"group": []
}
},
"color": "#2ebd59",
"name": "Battery Node",
"source": "bucket",
"timespan": {
"mode": "latest"
}
}
],
"type": "gauge"
},
{
"layout": {
"col": 4,
"row": 25,
"sizeX": 2,
"sizeY": 5
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
},
"title": "Battery History (24h)"
},
"properties": {
"alignTimeSeries": false,
"dataAppend": false,
"options": "var options = {\n chart: {\n type: 'line'\n },\n dataLabels: {\n enabled: false\n },\n stroke: {\n curve: 'smooth'\n },\n xaxis: {\n type: 'datetime',\n labels: {\n datetimeUTC: false\n },\n tooltip: {\n enabled: false\n }\n },\n yaxis: {\n min: 0,\n max: 100\n },\n tooltip: {\n x: {\n format: 'dd/MM/yyyy HH:mm:ss'\n }\n }\n};\n",
"realTimeUpdate": true
},
"sources": [
{
"aggregation": {},
"bucket": {
"backend": "mongodb",
"id": "milesight_vs360_data",
"mapping": "battery_main",
"tags": {
"device": [],
"group": []
}
},
"color": "#2ebd59",
"name": "Main",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
},
{
"aggregation": {},
"bucket": {
"backend": "mongodb",
"id": "milesight_vs360_data",
"mapping": "battery_node",
"tags": {
"device": [],
"group": []
}
},
"color": "#f39c12",
"name": "Node",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
}
],
"type": "apex_charts"
},
{
"layout": {
"col": 0,
"row": 30,
"sizeX": 3,
"sizeY": 5
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
},
"title": "Device Status"
},
"properties": {
"decimalPlaces": 0,
"enableExtraTextColor": false,
"enableIconColor": false,
"enableIconSize": false,
"extraText": "",
"extraTextColor": "#1E313E",
"extraTextColorConditions": [],
"extraTextConditions": [],
"extraTextPosition": "above-value",
"extraTextSize": "20px",
"extraTextWeight": "font-light",
"icon": "",
"iconColor": "#1E313E",
"iconColorConditions": [],
"iconConditions": [],
"iconGap": "8px",
"iconPosition": "before-value",
"iconSize": "75px",
"iconVerticalOffset": "0px",
"link": "",
"textAlign": "center",
"textColor": "#1E313E",
"textColorConditions": [],
"textSize": "30px",
"textWeight": "font-light",
"unit": "",
"unitSize": "20px"
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs360_data",
"mapping": "device_status",
"tags": {
"device": [],
"group": []
}
},
"color": "#3498db",
"name": "Status",
"source": "bucket",
"timespan": {
"mode": "latest"
}
}
],
"type": "text"
},
{
"layout": {
"col": 3,
"row": 30,
"sizeX": 3,
"sizeY": 5
},
"panel": {
"color": "#ffffff",
"currentColor": "#ffffff",
"showOffline": {
"type": "none"
},
"title": "Counting Mode"
},
"properties": {
"decimalPlaces": 0,
"enableExtraTextColor": false,
"enableIconColor": false,
"enableIconSize": false,
"extraText": "",
"extraTextColor": "#1E313E",
"extraTextColorConditions": [],
"extraTextConditions": [],
"extraTextPosition": "above-value",
"extraTextSize": "20px",
"extraTextWeight": "font-light",
"icon": "",
"iconColor": "#1E313E",
"iconColorConditions": [],
"iconConditions": [],
"iconGap": "8px",
"iconPosition": "before-value",
"iconSize": "75px",
"iconVerticalOffset": "0px",
"link": "",
"textAlign": "center",
"textColor": "#1E313E",
"textColorConditions": [],
"textSize": "30px",
"textWeight": "font-light",
"unit": "",
"unitSize": "20px"
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs360_data",
"mapping": "counting_mode",
"tags": {
"device": [],
"group": []
}
},
"color": "#9b59b6",
"name": "Mode",
"source": "bucket",
"timespan": {
"mode": "latest"
}
}
],
"type": "text"
}
]
}
]
}
}
]
}
}
]
}
}