Plugin file
Plugin configuration file
{
"name": "milesight-iot-vs135",
"version": "1.0.0",
"description": "VS135 is a sensor that uses second-generation ToF technology to accuratelycount people with an excellent privacy protection",
"author": "Thinger.io",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/thinger-io/plugins.git",
"directory": "milesight-iot-vs135"
},
"metadata": {
"name": "Milesight-Iot VS135",
"description": "VS135 is a sensor that uses second-generation ToF technology to accuratelycount people with an excellent privacy protection",
"image": "assets/vs135.png",
"category": "devices",
"vendor": "milesight-iot"
},
"resources": {
"products": [
{
"description": "VS135 is a sensor that uses second-generation ToF technology to accuratelycount people with an excellent privacy protection",
"enabled": true,
"name": "Milesight-Iot VS135",
"product": "milesight_iot_vs135",
"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}",
"plugin": "{{property.uplink.source}}",
"target": "plugin_endpoint"
}
}
},
"uplink": {
"enabled": true,
"handle_connectivity": true,
"request": {
"data": {
"payload": "{{payload}}",
"payload_type": "source_payload",
"resource_stream": "uplink",
"target": "resource_stream"
}
}
}
},
"autoprovisions": {
"device_autoprovisioning": {
"config": {
"mode": "pattern",
"pattern": "vs135_.*"
},
"enabled": true
}
},
"buckets": {
"milesight_vs135_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 VS133 / VS135\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\nvar total_in_chns = [0x03, 0x06, 0x09, 0x0c];\nvar total_out_chns = [0x04, 0x07, 0x0a, 0x0d];\nvar period_chns = [0x05, 0x08, 0x0b, 0x0e];\nvar child_total_in_chns = [0x11, 0x14, 0x17, 0x1a];\nvar child_total_out_chns = [0x12, 0x15, 0x18, 0x1b];\nvar child_period_chns = [0x13, 0x16, 0x19, 0x1c];\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 === 0x1f) {\n decoded.firmware_version = readFirmwareVersion(bytes.slice(i, i + 4));\n i += 4;\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 // LINE TOTAL IN\n else if (includes(total_in_chns, channel_id) && channel_type === 0xd2) {\n var channel_in_name = \"line_\" + ((channel_id - total_in_chns[0]) / 3 + 1);\n decoded[channel_in_name + \"_total_in\"] = readUInt32LE(bytes.slice(i, i + 4));\n i += 4;\n }\n // LINE TOTAL IN (child)\n else if (includes(child_total_in_chns, channel_id) && channel_type === 0xd2) {\n var child_total_in_name = \"line_\" + ((channel_id - child_total_in_chns[0]) / 3 + 1);\n decoded[child_total_in_name + \"_child_total_in\"] = readUInt32LE(bytes.slice(i, i + 4));\n i += 4;\n }\n // LINE TOTAL OUT\n else if (includes(total_out_chns, channel_id) && channel_type === 0xd2) {\n var channel_out_name = \"line_\" + ((channel_id - total_out_chns[0]) / 3 + 1);\n decoded[channel_out_name + \"_total_out\"] = readUInt32LE(bytes.slice(i, i + 4));\n i += 4;\n }\n // LINE TOTAL OUT (child)\n else if (includes(child_total_out_chns, channel_id) && channel_type === 0xd2) {\n var child_total_out_name = \"line_\" + ((channel_id - child_total_out_chns[0]) / 3 + 1);\n decoded[child_total_out_name + \"_child_total_out\"] = readUInt32LE(bytes.slice(i, i + 4));\n i += 4;\n }\n // LINE PERIOD\n else if (includes(period_chns, channel_id) && channel_type === 0xcc) {\n var channel_period_name = \"line_\" + ((channel_id - period_chns[0]) / 3 + 1);\n decoded[channel_period_name + \"_period_in\"] = readUInt16LE(bytes.slice(i, i + 2));\n decoded[channel_period_name + \"_period_out\"] = readUInt16LE(bytes.slice(i + 2, i + 4));\n i += 4;\n }\n // LINE PERIOD (child)\n else if (includes(child_period_chns, channel_id) && channel_type === 0xcc) {\n var child_period_name = \"line_\" + ((channel_id - child_period_chns[0]) / 3 + 1);\n decoded[child_period_name + \"_child_period_in\"] = readUInt16LE(bytes.slice(i, i + 2));\n decoded[channel_period_name + \"_child_period_out\"] = readUInt16LE(bytes.slice(i + 2, i + 4));\n i += 4;\n }\n // REGION COUNT\n else if (channel_id === 0x0f && channel_type === 0xe3) {\n decoded.region_1_count = readUInt8(bytes[i]);\n decoded.region_2_count = readUInt8(bytes[i + 1]);\n decoded.region_3_count = readUInt8(bytes[i + 2]);\n decoded.region_4_count = readUInt8(bytes[i + 3]);\n i += 4;\n }\n // REGION COUNT (child)\n else if (channel_id === 0x1d && channel_type === 0xe3) {\n decoded.region_1_child_count = readUInt8(bytes[i]);\n decoded.region_2_child_count = readUInt8(bytes[i + 1]);\n decoded.region_3_child_count = readUInt8(bytes[i + 2]);\n decoded.region_4_child_count = readUInt8(bytes[i + 3]);\n i += 4;\n }\n // REGION DWELL TIME\n else if (channel_id === 0x10 && channel_type === 0xe4) {\n var dwell_channel_name = \"region_\" + bytes[i];\n decoded[dwell_channel_name + \"_avg_dwell\"] = readUInt16LE(bytes.slice(i + 1, i + 3));\n decoded[dwell_channel_name + \"_max_dwell\"] = readUInt16LE(bytes.slice(i + 3, i + 5));\n i += 5;\n }\n // REGION DWELL TIME (child)\n else if (channel_id === 0x1e && channel_type === 0xe4) {\n var child_dwell_channel_name = \"region_\" + bytes[i];\n decoded[child_dwell_channel_name + \"_child_avg_dwell\"] = readUInt16LE(bytes.slice(i + 1, i + 3));\n decoded[child_dwell_channel_name + \"_child_max_dwell\"] = readUInt16LE(bytes.slice(i + 3, i + 5));\n i += 5;\n }\n // ALARM\n else if (channel_id === 0x50 && channel_type === 0xfc) {\n var node_id = bytes[i + 1];\n var node_name = node_id === 0x00 ? \"master\" : \"node_\" + node_id;\n decoded[node_name + \"_occlusion_alarm\"] = readAlarmType(bytes[i + 2]);\n i += 3;\n }\n // HISTORY (v1.0.9)\n else if (channel_id === 0x20 && channel_type === 0xce) {\n var historyResult = readHistory(bytes, i);\n i = historyResult.offset;\n decoded.history = decoded.history || [];\n decoded.history.push(historyResult.data);\n }\n // DOWNLINK RESPONSE\n else if (channel_id === 0xfe || channel_id === 0xff) {\n var respResult = handle_downlink_response(channel_type, bytes, i);\n decoded = Object.assign(decoded, respResult.data);\n i = respResult.offset;\n }\n // DOWNLINK RESPONSE EXT\n else if (channel_id === 0xf8 || channel_id === 0xf9) {\n var resultExt = handle_downlink_response_ext(channel_id, channel_type, bytes, i);\n decoded = Object.assign(decoded, resultExt.data);\n i = resultExt.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 0x03:\n decoded.report_interval = readUInt16LE(bytes.slice(offset, offset + 2));\n offset += 2;\n break;\n case 0x04:\n decoded.confirm_mode_enable = readEnableStatus(bytes[offset]);\n offset += 1;\n break;\n case 0x10:\n decoded.reboot = readYesNoStatus(1);\n offset += 1;\n break;\n case 0x11:\n decoded.timestamp = readUInt32LE(bytes.slice(offset, offset + 4));\n offset += 4;\n break;\n case 0x27:\n decoded.clear_history = readYesNoStatus(1);\n offset += 1;\n break;\n case 0x40:\n decoded.adr_enable = readEnableStatus(bytes[offset]);\n offset += 1;\n break;\n case 0x42:\n decoded.wifi_enable = readEnableStatus(bytes[offset]);\n offset += 1;\n break;\n case 0x43:\n decoded.periodic_report_enable = readEnableStatus(bytes[offset]);\n offset += 1;\n break;\n case 0x44:\n decoded.trigger_report_enable = readEnableStatus(bytes[offset]);\n offset += 1;\n break;\n case 0x51:\n decoded.clear_total_count = 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 interval_type = readUInt8(bytes[offset]);\n if (interval_type === 0) {\n decoded.retransmit_interval = readUInt16LE(bytes.slice(offset + 1, offset + 3));\n } else if (interval_type === 1) {\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 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 0x84:\n decoded.sync_time_from_gateway_config = {};\n decoded.sync_time_from_gateway_config.enable = readEnableStatus(bytes[offset]);\n decoded.sync_time_from_gateway_config.period = readUInt16LE(bytes.slice(offset + 1, offset + 3));\n offset += 3;\n break;\n case 0x85:\n decoded.rejoin_config = {};\n decoded.rejoin_config.enable = readEnableStatus(bytes[offset]);\n decoded.rejoin_config.max_count = readUInt8(bytes[offset + 1]);\n offset += 2;\n break;\n case 0x86:\n decoded.data_rate = readUInt8(bytes[offset]);\n offset += 1;\n break;\n case 0x87:\n decoded.tx_power_level = readUInt8(bytes[offset]);\n offset += 1;\n break;\n case 0x88:\n decoded.log_config = {};\n decoded.log_config.console_log_level = readLogLevel(bytes[offset]);\n decoded.log_config.file_log_level = readLogLevel(bytes[offset + 1]);\n offset += 2;\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;\n var minor = bytes[1] & 0xff;\n return \"v\" + major + \".\" + minor;\n}\n\nfunction readFirmwareVersion(bytes) {\n var serial = bytes[0] & 0xff;\n var major = bytes[1] & 0xff;\n var odm = bytes[2] & 0xff;\n var minor = bytes[3] & 0xff;\n return \"v\" + serial + \".\" + major + \".\" + odm + \".\" + 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 readAlarmType(type) {\n var alarm_map = { 0: \"alarm_released\", 1: \"alarm_triggered\" };\n return getValue(alarm_map, type);\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 yes_no_map = { 0: \"no\", 1: \"yes\" };\n return getValue(yes_no_map, status);\n}\n\nfunction readHistory(bytes, offset) {\n var i = offset;\n\n var timestamp = readUInt32LE(bytes.slice(i, i + 4));\n var data_type = readUInt8(bytes[i + 4]);\n\n var data = {};\n data.timestamp = timestamp;\n if (data_type === 0x03) {\n data.line_1_total_in = readUInt32LE(bytes.slice(i + 5, i + 9));\n i += 9;\n } else if (data_type === 0x04) {\n data.line_1_total_out = readUInt32LE(bytes.slice(i + 5, i + 9));\n i += 9;\n } else if (data_type === 0x05) {\n data.line_1_period_in = readUInt16LE(bytes.slice(i + 5, i + 7));\n data.line_1_period_out = readUInt16LE(bytes.slice(i + 7, i + 9));\n i += 9;\n } else if (data_type === 0x06) {\n data.line_2_total_in = readUInt32LE(bytes.slice(i + 5, i + 9));\n i += 9;\n } else if (data_type === 0x07) {\n data.line_2_total_out = readUInt32LE(bytes.slice(i + 5, i + 9));\n i += 9;\n } else if (data_type === 0x08) {\n data.line_2_period_in = readUInt16LE(bytes.slice(i + 5, i + 7));\n data.line_2_period_out = readUInt16LE(bytes.slice(i + 7, i + 9));\n i += 9;\n } else if (data_type === 0x09) {\n data.line_3_total_in = readUInt32LE(bytes.slice(i + 5, i + 9));\n i += 9;\n } else if (data_type === 0x0a) {\n data.line_3_total_out = readUInt32LE(bytes.slice(i + 5, i + 9));\n i += 9;\n } else if (data_type === 0x0b) {\n data.line_3_period_in = readUInt16LE(bytes.slice(i + 5, i + 7));\n data.line_3_period_out = readUInt16LE(bytes.slice(i + 7, i + 9));\n i += 9;\n } else if (data_type === 0x0c) {\n data.line_4_total_in = readUInt32LE(bytes.slice(i + 5, i + 9));\n i += 9;\n } else if (data_type === 0x0d) {\n data.line_4_total_out = readUInt32LE(bytes.slice(i + 5, i + 9));\n i += 9;\n } else if (data_type === 0x0e) {\n data.line_4_period_in = readUInt16LE(bytes.slice(i + 5, i + 7));\n data.line_4_period_out = readUInt16LE(bytes.slice(i + 7, i + 9));\n i += 9;\n } else if (data_type === 0x0f) {\n data.region_1_count = readUInt8(bytes[i + 5]);\n data.region_2_count = readUInt8(bytes[i + 6]);\n data.region_3_count = readUInt8(bytes[i + 7]);\n data.region_4_count = readUInt8(bytes[i + 8]);\n i += 9;\n } else if (data_type === 0x10) {\n var avg_dwell_region = readUInt8(bytes[i + 5]);\n var avg_dwell_region_name = \"region_\" + avg_dwell_region + \"_avg_dwell\";\n data[avg_dwell_region_name] = readUInt16LE(bytes.slice(i + 6, i + 8));\n i += 8;\n } else if (data_type === 0x20) {\n var max_dwell_region = readUInt8(bytes[i + 5]);\n var max_dwell_region_name = \"region_\" + max_dwell_region + \"_max_dwell\";\n data[max_dwell_region_name] = readUInt16LE(bytes.slice(i + 6, i + 8));\n i += 8;\n } else if (data_type === 0x11) {\n data.line_1_child_total_in = readUInt32LE(bytes.slice(i + 5, i + 9));\n i += 9;\n } else if (data_type === 0x12) {\n data.line_1_child_total_out = readUInt32LE(bytes.slice(i + 5, i + 9));\n i += 9;\n } else if (data_type === 0x13) {\n data.line_1_child_period_in = readUInt16LE(bytes.slice(i + 5, i + 7));\n data.line_1_child_period_out = readUInt16LE(bytes.slice(i + 7, i + 9));\n i += 9;\n } else if (data_type === 0x14) {\n data.line_2_child_total_in = readUInt32LE(bytes.slice(i + 5, i + 9));\n i += 9;\n } else if (data_type === 0x15) {\n data.line_2_child_total_out = readUInt32LE(bytes.slice(i + 5, i + 9));\n i += 9;\n } else if (data_type === 0x16) {\n data.line_2_child_period_in = readUInt16LE(bytes.slice(i + 5, i + 7));\n data.line_2_child_period_out = readUInt16LE(bytes.slice(i + 7, i + 9));\n i += 9;\n } else if (data_type === 0x17) {\n data.line_3_child_total_in = readUInt32LE(bytes.slice(i + 5, i + 9));\n i += 9;\n } else if (data_type === 0x18) {\n data.line_3_child_total_out = readUInt32LE(bytes.slice(i + 5, i + 9));\n i += 9;\n } else if (data_type === 0x19) {\n data.line_3_child_period_in = readUInt16LE(bytes.slice(i + 5, i + 7));\n data.line_3_child_period_out = readUInt16LE(bytes.slice(i + 7, i + 9));\n i += 9;\n } else if (data_type === 0x1a) {\n data.line_4_child_total_in = readUInt32LE(bytes.slice(i + 5, i + 9));\n i += 9;\n } else if (data_type === 0x1b) {\n data.line_4_child_total_out = readUInt32LE(bytes.slice(i + 5, i + 9));\n i += 9;\n } else if (data_type === 0x1c) {\n data.line_4_child_period_in = readUInt16LE(bytes.slice(i + 5, i + 7));\n data.line_4_child_period_out = readUInt16LE(bytes.slice(i + 7, i + 9));\n i += 9;\n } else if (data_type === 0x1d) {\n data.region_1_child_count = readUInt8(bytes[i + 5]);\n data.region_2_child_count = readUInt8(bytes[i + 6]);\n data.region_3_child_count = readUInt8(bytes[i + 7]);\n data.region_4_child_count = readUInt8(bytes[i + 8]);\n i += 9;\n } else if (data_type === 0x1e) {\n var child_avg_dwell_region = readUInt8(bytes[i + 5]);\n var child_avg_dwell_region_name = \"region_\" + child_avg_dwell_region + \"_child_avg_dwell\";\n data[child_avg_dwell_region_name] = readUInt16LE(bytes.slice(i + 6, i + 8));\n i += 8;\n } else if (data_type === 0x3c) {\n var child_max_dwell_region = readUInt8(bytes[i + 5]);\n var child_max_dwell_region_name = \"region_\" + child_max_dwell_region + \"_child_max_dwell\";\n data[child_max_dwell_region_name] = readUInt16LE(bytes.slice(i + 6, i + 8));\n i += 8;\n }\n\n return { data: data, offset: i };\n}\n\nfunction readLogLevel(level) {\n var log_level_map = { 1: \"fatal\", 2: \"error\", 3: \"warning\", 4: \"debug\", 5: \"trace\" };\n return getValue(log_level_map, level);\n}\n\nfunction readUInt8(bytes) {\n return bytes & 0xff;\n}\n\nfunction readUInt16LE(bytes) {\n var value = (bytes[1] << 8) + bytes[0];\n return value & 0xffff;\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 includes(data, value) {\n var size = data.length;\n for (var i = 0; i < size; i++) {\n if (data[i] == value) {\n return true;\n }\n }\n return false;\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_vs135_decoder": {
"data": {
"payload": "{{payload}}",
"payload_function": "decodeThingerUplink",
"payload_type": "source_payload",
"resource": "uplink",
"source": "resource",
"update": "events"
},
"enabled": true,
"sink": {
"payload": "{{payload}}",
"payload_type": "source_payload",
"resource_stream": "uplink_decoded",
"target": "resource_stream"
},
"split_data": false
}
},
"properties": {
"uplink": {
"data": {
"payload": "{{payload}}",
"payload_type": "source_payload",
"resource": "uplink",
"source": "resource",
"update": "events"
},
"default": {
"source": "value"
},
"enabled": true
}
}
},
"_resources": {
"properties": [
{
"property": "dashboard",
"value": {
"tabs": [
{
"name": "People Counting",
"widgets": [
{
"layout": {
"col": 0,
"row": 0,
"sizeX": 2,
"sizeY": 4
},
"panel": {
"color": "#ffffff",
"title": "Total In (Line 1)"
},
"properties": {
"decimalPlaces": 0,
"textAlign": "center",
"textColor": "#2ebd59",
"textSize": "50px",
"textWeight": "font-light",
"unit": "people",
"unitSize": "18px"
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "line_1_total_in",
"tags": {
"device": [],
"group": []
}
},
"source": "bucket",
"timespan": {
"mode": "latest"
}
}
],
"type": "text"
},
{
"layout": {
"col": 2,
"row": 0,
"sizeX": 2,
"sizeY": 4
},
"panel": {
"color": "#ffffff",
"title": "Total Out (Line 1)"
},
"properties": {
"decimalPlaces": 0,
"textAlign": "center",
"textColor": "#e74c3c",
"textSize": "50px",
"textWeight": "font-light",
"unit": "people",
"unitSize": "18px"
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "line_1_total_out",
"tags": {
"device": [],
"group": []
}
},
"source": "bucket",
"timespan": {
"mode": "latest"
}
}
],
"type": "text"
},
{
"layout": {
"col": 4,
"row": 0,
"sizeX": 2,
"sizeY": 4
},
"panel": {
"color": "#ffffff",
"title": "Net Count (Line 1)"
},
"properties": {
"decimalPlaces": 0,
"textAlign": "center",
"textColor": "#3498db",
"textSize": "50px",
"textWeight": "font-bold",
"unit": "people",
"unitSize": "18px"
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "line_1_total_in",
"tags": {
"device": [],
"group": []
}
},
"source": "bucket",
"timespan": {
"mode": "latest"
}
}
],
"type": "text"
},
{
"layout": {
"col": 0,
"row": 4,
"sizeX": 6,
"sizeY": 8
},
"panel": {
"color": "#ffffff",
"title": "People Flow (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 },\n yaxis: {\n title: {\n text: 'People Count'\n }\n },\n tooltip: {\n x: {\n format: 'dd/MM/yyyy HH:mm'\n }\n },\n legend: {\n position: 'top'\n }\n};\n",
"realTimeUpdate": true
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "line_1_total_in",
"tags": {
"device": [],
"group": []
}
},
"color": "#2ebd59",
"name": "Total In",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
},
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "line_1_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": 12,
"sizeX": 6,
"sizeY": 8
},
"panel": {
"color": "#ffffff",
"title": "Period Flow (24h)"
},
"properties": {
"alignTimeSeries": false,
"dataAppend": false,
"options": "var options = {\n chart: {\n type: 'area',\n stacked: true\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 },\n yaxis: {\n title: {\n text: 'People per Period'\n }\n },\n tooltip: {\n x: {\n format: 'dd/MM/yyyy HH:mm'\n }\n },\n fill: {\n type: 'gradient',\n gradient: {\n opacityFrom: 0.6,\n opacityTo: 0.1\n }\n }\n};\n",
"realTimeUpdate": true
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "line_1_period_in",
"tags": {
"device": [],
"group": []
}
},
"color": "#3498db",
"name": "Period In",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
},
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "line_1_period_out",
"tags": {
"device": [],
"group": []
}
},
"color": "#f39c12",
"name": "Period Out",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
}
],
"type": "apex_charts"
}
]
},
{
"name": "Occupancy & Regions",
"widgets": [
{
"layout": {
"col": 0,
"row": 0,
"sizeX": 2,
"sizeY": 5
},
"panel": {
"color": "#ffffff",
"title": "Region 1"
},
"properties": {
"color": "#3498db",
"max": 50,
"min": 0,
"unit": "people"
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "region_1_count",
"tags": {
"device": [],
"group": []
}
},
"source": "bucket",
"timespan": {
"mode": "latest"
}
}
],
"type": "donutchart"
},
{
"layout": {
"col": 2,
"row": 0,
"sizeX": 2,
"sizeY": 5
},
"panel": {
"color": "#ffffff",
"title": "Region 2"
},
"properties": {
"color": "#2ebd59",
"max": 50,
"min": 0,
"unit": "people"
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "region_2_count",
"tags": {
"device": [],
"group": []
}
},
"source": "bucket",
"timespan": {
"mode": "latest"
}
}
],
"type": "donutchart"
},
{
"layout": {
"col": 4,
"row": 0,
"sizeX": 2,
"sizeY": 5
},
"panel": {
"color": "#ffffff",
"title": "Region 3"
},
"properties": {
"color": "#f39c12",
"max": 50,
"min": 0,
"unit": "people"
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "region_3_count",
"tags": {
"device": [],
"group": []
}
},
"source": "bucket",
"timespan": {
"mode": "latest"
}
}
],
"type": "donutchart"
},
{
"layout": {
"col": 0,
"row": 5,
"sizeX": 6,
"sizeY": 8
},
"panel": {
"color": "#ffffff",
"title": "Region Occupancy (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: 2\n },\n xaxis: {\n type: 'datetime',\n labels: {\n datetimeUTC: false\n }\n },\n yaxis: {\n title: {\n text: 'People Count'\n }\n },\n tooltip: {\n x: {\n format: 'dd/MM/yyyy HH:mm'\n }\n }\n};\n",
"realTimeUpdate": true
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "region_1_count",
"tags": {
"device": [],
"group": []
}
},
"color": "#3498db",
"name": "Region 1",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
},
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "region_2_count",
"tags": {
"device": [],
"group": []
}
},
"color": "#2ebd59",
"name": "Region 2",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
},
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "region_3_count",
"tags": {
"device": [],
"group": []
}
},
"color": "#f39c12",
"name": "Region 3",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
},
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "region_4_count",
"tags": {
"device": [],
"group": []
}
},
"color": "#e74c3c",
"name": "Region 4",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
}
],
"type": "apex_charts"
},
{
"layout": {
"col": 0,
"row": 13,
"sizeX": 6,
"sizeY": 8
},
"panel": {
"color": "#ffffff",
"title": "Average Dwell Time (24h)"
},
"properties": {
"alignTimeSeries": false,
"dataAppend": false,
"options": "var options = {\n chart: {\n type: 'bar'\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 },\n yaxis: {\n title: {\n text: 'Dwell Time (seconds)'\n }\n },\n tooltip: {\n x: {\n format: 'dd/MM/yyyy HH:mm'\n }\n }\n};\n",
"realTimeUpdate": true
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "region_1_avg_dwell",
"tags": {
"device": [],
"group": []
}
},
"color": "#9b59b6",
"name": "Region 1 Avg Dwell",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
},
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "region_2_avg_dwell",
"tags": {
"device": [],
"group": []
}
},
"color": "#1abc9c",
"name": "Region 2 Avg Dwell",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
}
],
"type": "apex_charts"
}
]
},
{
"name": "Data Log",
"widgets": [
{
"layout": {
"col": 0,
"row": 0,
"sizeX": 6,
"sizeY": 15
},
"panel": {
"color": "#ffffff",
"title": "Last 24h Activity Log"
},
"properties": {
"source": "code",
"template": "<div style=\"width:100%; height:100%; overflow-y:auto\">\n <table class=\"table table-striped table-condensed\">\n <thead>\n <tr>\n <th>Timestamp</th>\n <th>Total In</th>\n <th>Total Out</th>\n <th>Period In</th>\n <th>Period Out</th>\n <th>Region 1</th>\n <th>Region 2</th>\n </tr>\n </thead>\n <tbody>\n <tr ng-repeat=\"entry in value\">\n <td>{{ entry.ts | date:'medium' }}</td>\n <td>{{ entry.line_1_total_in || '—' }}</td>\n <td>{{ entry.line_1_total_out || '—' }}</td>\n <td>{{ entry.line_1_period_in || '—' }}</td>\n <td>{{ entry.line_1_period_out || '—' }}</td>\n <td>{{ entry.region_1_count || '—' }}</td>\n <td>{{ entry.region_2_count || '—' }}</td>\n </tr>\n </tbody>\n </table>\n</div>\n"
},
"sources": [
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "ts",
"tags": {
"device": [],
"group": []
}
},
"name": "ts",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
},
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "line_1_total_in",
"tags": {
"device": [],
"group": []
}
},
"name": "line_1_total_in",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
},
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "line_1_total_out",
"tags": {
"device": [],
"group": []
}
},
"name": "line_1_total_out",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
},
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "line_1_period_in",
"tags": {
"device": [],
"group": []
}
},
"name": "line_1_period_in",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
},
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "line_1_period_out",
"tags": {
"device": [],
"group": []
}
},
"name": "line_1_period_out",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
},
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "region_1_count",
"tags": {
"device": [],
"group": []
}
},
"name": "region_1_count",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
},
{
"bucket": {
"backend": "mongodb",
"id": "milesight_vs135_data",
"mapping": "region_2_count",
"tags": {
"device": [],
"group": []
}
},
"name": "region_2_count",
"source": "bucket",
"timespan": {
"magnitude": "hour",
"mode": "relative",
"period": "latest",
"value": 24
}
}
],
"type": "html_time"
}
]
}
]
}
}
]
}
}
]
}
}