1068 lines
33 KiB
JSON
1068 lines
33 KiB
JSON
[
|
||
{
|
||
"id": "b475cb7c37295110",
|
||
"type": "tab",
|
||
"label": "Xilica FR1 Dashboard",
|
||
"disabled": false,
|
||
"info": ""
|
||
},
|
||
{
|
||
"id": "42887eae2f29d3f0",
|
||
"type": "tab",
|
||
"label": "Xilica FR1 Dashboard",
|
||
"disabled": false,
|
||
"info": ""
|
||
},
|
||
{
|
||
"id": "ui_base_main",
|
||
"type": "ui-base",
|
||
"name": "Main UI",
|
||
"path": "/ui",
|
||
"appIcon": "",
|
||
"includeClientData": true,
|
||
"acceptsClientConfig": [
|
||
"ui-control",
|
||
"ui-notification"
|
||
],
|
||
"showPathInSidebar": false,
|
||
"headerContent": "dashboard",
|
||
"titleBarStyle": "fixed",
|
||
"showReconnectNotification": true,
|
||
"notificationDisplayTime": 5,
|
||
"showDisconnectNotification": true,
|
||
"allowInstall": true
|
||
},
|
||
{
|
||
"id": "efd7cd1777464c3d",
|
||
"type": "ui-theme",
|
||
"name": "LAV THEME",
|
||
"colors": {
|
||
"surface": "#d7d6d6",
|
||
"primary": "#5c727a",
|
||
"bgPage": "#eeeeee",
|
||
"groupBg": "#ffffff",
|
||
"groupOutline": "#cccccc"
|
||
},
|
||
"sizes": {
|
||
"density": "default",
|
||
"pagePadding": "12px",
|
||
"groupGap": "12px",
|
||
"groupBorderRadius": "4px",
|
||
"widgetGap": "12px"
|
||
}
|
||
},
|
||
{
|
||
"id": "ui_page_solaro",
|
||
"type": "ui-page",
|
||
"name": "Solaro FR1",
|
||
"ui": "ui_base_main",
|
||
"path": "",
|
||
"icon": "volume-high",
|
||
"layout": "grid",
|
||
"theme": "efd7cd1777464c3d",
|
||
"breakpoints": [
|
||
{
|
||
"name": "Default",
|
||
"px": "0",
|
||
"cols": "3"
|
||
},
|
||
{
|
||
"name": "Tablet",
|
||
"px": "576",
|
||
"cols": "6"
|
||
},
|
||
{
|
||
"name": "Small Desktop",
|
||
"px": "768",
|
||
"cols": "9"
|
||
},
|
||
{
|
||
"name": "Desktop",
|
||
"px": "1024",
|
||
"cols": "12"
|
||
}
|
||
],
|
||
"order": 1,
|
||
"className": "",
|
||
"visible": true,
|
||
"disabled": false
|
||
},
|
||
{
|
||
"id": "ui_group_master",
|
||
"type": "ui-group",
|
||
"name": "Master Gain",
|
||
"page": "ui_page_solaro",
|
||
"width": "4",
|
||
"height": "8",
|
||
"order": 2,
|
||
"showTitle": true,
|
||
"className": "",
|
||
"visible": "true",
|
||
"disabled": "false",
|
||
"groupType": "default"
|
||
},
|
||
{
|
||
"id": "959e129052ea58f8",
|
||
"type": "ui-page",
|
||
"name": "Network",
|
||
"ui": "ui_base_main",
|
||
"path": "/network",
|
||
"icon": "wifi",
|
||
"layout": "grid",
|
||
"theme": "efd7cd1777464c3d",
|
||
"breakpoints": [
|
||
{
|
||
"name": "Default",
|
||
"px": "0",
|
||
"cols": "3"
|
||
},
|
||
{
|
||
"name": "Tablet",
|
||
"px": "576",
|
||
"cols": "6"
|
||
},
|
||
{
|
||
"name": "Small Desktop",
|
||
"px": "768",
|
||
"cols": "9"
|
||
},
|
||
{
|
||
"name": "Desktop",
|
||
"px": "1024",
|
||
"cols": "12"
|
||
}
|
||
],
|
||
"order": 2,
|
||
"className": "",
|
||
"visible": true,
|
||
"disabled": false
|
||
},
|
||
{
|
||
"id": "e46886701344918d",
|
||
"type": "global-config",
|
||
"env": []
|
||
},
|
||
{
|
||
"id": "c1fc81e5a697e224",
|
||
"type": "ui-group",
|
||
"name": "Wi-Fi",
|
||
"page": "959e129052ea58f8",
|
||
"width": "11",
|
||
"height": "11",
|
||
"order": 1,
|
||
"showTitle": true,
|
||
"className": "",
|
||
"visible": "true",
|
||
"disabled": "false",
|
||
"groupType": "default"
|
||
},
|
||
{
|
||
"id": "e3b7d0b0ef7a4a01",
|
||
"type": "ui-page",
|
||
"name": "Solaro Channels",
|
||
"ui": "ui_base_main",
|
||
"path": "/channels",
|
||
"icon": "view-dashboard-outline",
|
||
"layout": "tabs",
|
||
"theme": "efd7cd1777464c3d",
|
||
"breakpoints": [
|
||
{
|
||
"name": "Default",
|
||
"px": "0",
|
||
"cols": "3"
|
||
},
|
||
{
|
||
"name": "Tablet",
|
||
"px": "576",
|
||
"cols": "6"
|
||
},
|
||
{
|
||
"name": "Small Desktop",
|
||
"px": "768",
|
||
"cols": "9"
|
||
},
|
||
{
|
||
"name": "Desktop",
|
||
"px": "1024",
|
||
"cols": "12"
|
||
}
|
||
],
|
||
"order": 3,
|
||
"className": "",
|
||
"visible": "true",
|
||
"disabled": "false"
|
||
},
|
||
{
|
||
"id": "e7bb2d8c0e0e2312",
|
||
"type": "ui-group",
|
||
"name": "Full Range",
|
||
"page": "e3b7d0b0ef7a4a01",
|
||
"width": "12",
|
||
"height": "8",
|
||
"order": 2,
|
||
"showTitle": true,
|
||
"className": "",
|
||
"visible": "true",
|
||
"disabled": "false",
|
||
"groupType": "default"
|
||
},
|
||
{
|
||
"id": "ee3561b1b7a5a913",
|
||
"type": "ui-group",
|
||
"name": "Subwoofers",
|
||
"page": "e3b7d0b0ef7a4a01",
|
||
"width": "12",
|
||
"height": "8",
|
||
"order": 3,
|
||
"showTitle": true,
|
||
"className": "",
|
||
"visible": "true",
|
||
"disabled": "false",
|
||
"groupType": "default"
|
||
},
|
||
{
|
||
"id": "6f58b0a32d6db114",
|
||
"type": "ui-group",
|
||
"name": "Right Side",
|
||
"page": "e3b7d0b0ef7a4a01",
|
||
"width": "4",
|
||
"height": "8",
|
||
"order": 4,
|
||
"showTitle": true,
|
||
"className": "",
|
||
"visible": "true",
|
||
"disabled": "false",
|
||
"groupType": "default"
|
||
},
|
||
{
|
||
"id": "d0b6f2a2e4f2c211",
|
||
"type": "ui-group",
|
||
"name": "Left Side",
|
||
"page": "e3b7d0b0ef7a4a01",
|
||
"width": "4",
|
||
"height": "8",
|
||
"order": 1,
|
||
"showTitle": true,
|
||
"className": "",
|
||
"visible": "true",
|
||
"disabled": "false",
|
||
"groupType": "default"
|
||
},
|
||
{
|
||
"id": "ab164b2f3eebf7d2",
|
||
"type": "ui-group",
|
||
"name": "Meters",
|
||
"page": "ui_page_solaro",
|
||
"width": "6",
|
||
"height": "6",
|
||
"order": 1,
|
||
"showTitle": true,
|
||
"className": "",
|
||
"visible": "true",
|
||
"disabled": "false",
|
||
"groupType": "default"
|
||
},
|
||
{
|
||
"id": "99314ffcd1e195f5",
|
||
"type": "ui-text",
|
||
"z": "42887eae2f29d3f0",
|
||
"group": "ui_group_master",
|
||
"order": 2,
|
||
"width": "",
|
||
"height": "",
|
||
"name": "Current (dB)",
|
||
"label": "Current (dB)",
|
||
"layout": "row-spread",
|
||
"style": false,
|
||
"font": "",
|
||
"fontSize": "",
|
||
"color": "#000000",
|
||
"wrapText": false,
|
||
"className": "",
|
||
"value": "payload",
|
||
"valueType": "msg",
|
||
"x": 1430,
|
||
"y": 280,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "b6e3985ddcab4c91",
|
||
"type": "ui-slider",
|
||
"z": "42887eae2f29d3f0",
|
||
"group": "ui_group_master",
|
||
"name": "MASTER_GAIN (dB)",
|
||
"label": "MASTER_GAIN (dB)",
|
||
"order": 1,
|
||
"width": "2",
|
||
"height": "8",
|
||
"passthru": false,
|
||
"outs": "all",
|
||
"topic": "",
|
||
"topicType": "str",
|
||
"thumbLabel": "true",
|
||
"showTicks": "false",
|
||
"min": -100,
|
||
"max": -18,
|
||
"step": 0.5,
|
||
"className": "",
|
||
"iconPrepend": "",
|
||
"iconAppend": "",
|
||
"color": "grey",
|
||
"colorTrack": "blue",
|
||
"colorThumb": "black",
|
||
"showTextField": false,
|
||
"x": 200,
|
||
"y": 300,
|
||
"wires": [
|
||
[
|
||
"2979ab7ac78ee6fb"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "2979ab7ac78ee6fb",
|
||
"type": "function",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "build SET MASTER_GAIN <v>",
|
||
"func": "const v = Number(msg.payload);\n// show value on the slider label while sending\nnode.status({text: v.toFixed(1)+\" dB\"});\nmsg.payload = `SET MASTER_GAIN ${v}`;\nreturn msg;",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 450,
|
||
"y": 300,
|
||
"wires": [
|
||
[
|
||
"f2a2b33c99f73ef4"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "deb330478d545760",
|
||
"type": "inject",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "poll GET",
|
||
"props": [
|
||
{
|
||
"p": "payload"
|
||
}
|
||
],
|
||
"repeat": "0.1",
|
||
"crontab": "",
|
||
"once": true,
|
||
"onceDelay": "0.5",
|
||
"topic": "",
|
||
"payload": "GET MASTER_GAIN",
|
||
"payloadType": "str",
|
||
"x": 120,
|
||
"y": 360,
|
||
"wires": [
|
||
[
|
||
"f2a2b33c99f73ef4"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "f2a2b33c99f73ef4",
|
||
"type": "function",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "append \\r",
|
||
"func": "if (typeof msg.payload !== 'string') msg.payload = String(msg.payload);\nmsg.payload += \"\\r\";\nreturn msg;",
|
||
"outputs": 1,
|
||
"x": 680,
|
||
"y": 340,
|
||
"wires": [
|
||
[
|
||
"b025382cef8ddeae"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "b025382cef8ddeae",
|
||
"type": "tcp request",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "FR1 192.168.1.244:10007",
|
||
"server": "192.168.1.244",
|
||
"port": "10007",
|
||
"out": "sit",
|
||
"ret": "string",
|
||
"splitc": " ",
|
||
"newline": "",
|
||
"trim": false,
|
||
"tls": "",
|
||
"x": 910,
|
||
"y": 400,
|
||
"wires": [
|
||
[
|
||
"d8273409d003853b",
|
||
"eee5fc7d794d0a80",
|
||
"eaf29f8c14a08046",
|
||
"93a6e10d2495c341"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "eee5fc7d794d0a80",
|
||
"type": "function",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "parse GET reply → number",
|
||
"func": "// Expected: MASTER_GAIN=-6.0\\r or OK\\r after SET\nconst s = (msg.payload||\"\").trim();\nif (/^MASTER_GAIN\\s*=/.test(s)) {\n const m = s.match(/=\\s*([-+]?\\d+(?:\\.\\d+)?)/);\n if (m) {\n const val = Number(m[1]);\n // update text and slider\n node.send([{payload: val.toFixed(1)}, {payload: val}]);\n }\n}\nreturn null; // outputs handled via node.send",
|
||
"outputs": 2,
|
||
"x": 1220,
|
||
"y": 320,
|
||
"wires": [
|
||
[
|
||
"99314ffcd1e195f5"
|
||
],
|
||
[
|
||
"56f258d24535e171"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "d8273409d003853b",
|
||
"type": "debug",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "Raw replies",
|
||
"active": false,
|
||
"tosidebar": true,
|
||
"complete": "payload",
|
||
"statusVal": "",
|
||
"statusType": "auto",
|
||
"x": 1170,
|
||
"y": 360,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "56f258d24535e171",
|
||
"type": "link out",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "link out 2",
|
||
"mode": "link",
|
||
"links": [
|
||
"95de5752c6e3da4b"
|
||
],
|
||
"x": 1385,
|
||
"y": 340,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "95de5752c6e3da4b",
|
||
"type": "link in",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "link in 2",
|
||
"links": [
|
||
"56f258d24535e171"
|
||
],
|
||
"x": 55,
|
||
"y": 300,
|
||
"wires": [
|
||
[
|
||
"b6e3985ddcab4c91"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "739b4b01aa486c67",
|
||
"type": "inject",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "poll VU_MASTER",
|
||
"props": [
|
||
{
|
||
"p": "payload"
|
||
}
|
||
],
|
||
"repeat": "0.2",
|
||
"crontab": "",
|
||
"once": true,
|
||
"onceDelay": "0.5",
|
||
"topic": "",
|
||
"payload": "GET VU_MASTER",
|
||
"payloadType": "str",
|
||
"x": 150,
|
||
"y": 400,
|
||
"wires": [
|
||
[
|
||
"749a672579e69106"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "eaf29f8c14a08046",
|
||
"type": "function",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "parse VU_MASTER replies",
|
||
"func": "const s = String(msg.payload || '').trim();\nconst m = s.match(/^VU[_\\s]?MASTER\\s*=\\s*([-+]?\\d+(?:\\.\\d+)?)/i);\nif (!m) return null;\nmsg.payload = Number(m[1]); // e.g. -28.7\nreturn msg;\n",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 1220,
|
||
"y": 400,
|
||
"wires": [
|
||
[
|
||
"f7d60274be119ba9"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "749a672579e69106",
|
||
"type": "function",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "append \\r",
|
||
"func": "if (typeof msg.payload !== 'string') msg.payload = String(msg.payload);\nmsg.payload += \"\\r\";\nreturn msg;",
|
||
"outputs": 1,
|
||
"x": 320,
|
||
"y": 400,
|
||
"wires": [
|
||
[
|
||
"b025382cef8ddeae"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "22cd982ac31a676b",
|
||
"type": "debug",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "Template OUT",
|
||
"active": false,
|
||
"tosidebar": true,
|
||
"complete": "payload",
|
||
"statusVal": "",
|
||
"statusType": "auto",
|
||
"x": 1600,
|
||
"y": 400,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "f7d60274be119ba9",
|
||
"type": "ui-template",
|
||
"z": "42887eae2f29d3f0",
|
||
"group": "ab164b2f3eebf7d2",
|
||
"page": "",
|
||
"ui": "",
|
||
"name": "vu_master",
|
||
"order": 1,
|
||
"width": "2",
|
||
"height": "8",
|
||
"head": "",
|
||
"format": "<template>\n <div class=\"vu-vertical\">\n <div class=\"vu-hdr\">\n <strong>VU_MASTER</strong>\n <span class=\"vu-readout\">{{ level.toFixed(1) }} dB</span>\n </div>\n\n <div class=\"vu-bar\">\n <div class=\"vu-fill\" :style=\"{ height: percent + '%' }\"></div>\n <div class=\"vu-peak\" :style=\"{ bottom: peakPercent + '%' }\"></div>\n <span class=\"vu-tick\" v-for=\"t in ticks\" :key=\"t\" :style=\"{ bottom: t + '%' }\"></span>\n </div>\n\n <div class=\"vu-ftr\">\n <span>{{ max }} dB</span>\n <span>−3</span>\n <span>−12</span>\n <span>{{ min }} dB</span>\n </div>\n </div>\n</template>\n\n<script>\nexport default {\n props: ['msg'],\n data: () => ({\n min: -60,\n max: 0,\n level: -60,\n peak: -60,\n decayDbPerSec: 6,\n _timer: null,\n ticks: [0, 80, 95, 100]\n }),\n computed: {\n percent () {\n const v = Math.min(this.max, Math.max(this.min, this.level));\n return ((v - this.min) / (this.max - this.min)) * 100;\n },\n peakPercent () {\n const v = Math.min(this.max, Math.max(this.min, this.peak));\n return ((v - this.min) / (this.max - this.min)) * 100;\n }\n },\n watch: {\n msg: {\n deep: true,\n immediate: true,\n handler (m) {\n if (!m) return;\n if (m.min !== undefined && Number.isFinite(+m.min)) this.min = +m.min;\n if (m.max !== undefined && Number.isFinite(+m.max)) this.max = +m.max;\n if (m.payload !== undefined) {\n const n = Number(m.payload);\n if (Number.isFinite(n)) {\n this.level = n;\n if (n > this.peak) this.peak = n;\n }\n }\n }\n }\n },\n mounted () {\n const dt = 100;\n this._timer = setInterval(() => {\n const step = this.decayDbPerSec * (dt / 1000);\n if (this.peak > this.level) this.peak = Math.max(this.level, this.peak - step);\n else this.peak = this.level;\n }, dt);\n },\n unmounted () {\n if (this._timer) clearInterval(this._timer);\n }\n}\n</script>\n\n<style>\n.vu-vertical {\n width: 100px;\n height: 450px;\n display: flex;\n flex-direction: column;\n align-items: center;\n font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n}\n\n.vu-hdr, .vu-ftr {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n}\n\n.vu-readout {\n font-variant-numeric: tabular-nums;\n opacity: .8;\n}\n\n.vu-bar {\n position: relative;\n flex: 1;\n width: 30px;\n margin: 6px 0;\n border-radius: 4px;\n background: #0b0f14;\n overflow: hidden;\n box-shadow: inset 0 0 0 1px rgba(255,255,255,.06);\n display: flex;\n flex-direction: column-reverse;\n}\n\n.vu-fill {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n z-index: 1;\n transition: height 90ms linear;\n background: linear-gradient(0deg,\n #10b981 0%, #10b981 80%, /* green up to -12 dB */\n #f59e0b 80%, #f59e0b 95%, /* amber to -3 dB */\n #ef4444 95%, #ef4444 100%); /* red above -3 dB */\n}\n\n.vu-peak {\n position: absolute;\n left: 0;\n right: 0;\n height: 2px;\n background: rgba(255,255,255,.9);\n box-shadow: 0 0 4px rgba(255,255,255,.9);\n z-index: 2;\n}\n\n.vu-tick {\n position: absolute;\n left: 0;\n right: 0;\n height: 1px;\n background: rgba(255,255,255,.15);\n z-index: 0;\n}\n\n.vu-ftr span {\n font-size: 12px;\n color: rgba(255,255,255,.6);\n}\n</style>\n",
|
||
"storeOutMessages": true,
|
||
"passthru": true,
|
||
"resendOnRefresh": true,
|
||
"templateScope": "local",
|
||
"className": "",
|
||
"x": 1430,
|
||
"y": 400,
|
||
"wires": [
|
||
[
|
||
"22cd982ac31a676b"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "98fcc2746ef99725",
|
||
"type": "inject",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "Poll CH4/5/6 (200ms)",
|
||
"props": [
|
||
{
|
||
"p": "payload"
|
||
}
|
||
],
|
||
"repeat": "",
|
||
"crontab": "",
|
||
"once": true,
|
||
"onceDelay": "0.5",
|
||
"topic": "",
|
||
"payload": "",
|
||
"payloadType": "str",
|
||
"x": 160,
|
||
"y": 520,
|
||
"wires": [
|
||
[
|
||
"831afc4f6e643a54"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "831afc4f6e643a54",
|
||
"type": "function",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "Round-robin GET CH4/5/6",
|
||
"func": "// Build a single multi-line SUBSCRIBE message for CH4OUT–CH28OUT\n// Each subscription is sent via TCP to Solaro\n\nconst lines = [];\n\n// Set update rate to 100ms\nlines.push('INTERVAL 100');\n\n// Generate all channel names dynamically (CH4OUT → CH28OUT)\nfor (let i = 4; i <= 28; i++) {\n lines.push(`SUBSCRIBE CH${i}OUT \"TCP\"`);\n}\nfor (let i = 1; i <= 4; i++) {\n lines.push(`SUBSCRIBE CH${i}_FROUT \"TCP\"`);\n}\nfor (let i = 25; i <= 28; i++) {\n lines.push(`SUBSCRIBE CH${i}_FROUT \"TCP\"`);\n}\nfor (let i = 1; i <= 4; i++) {\n lines.push(`SUBSCRIBE CH${i}_SUBOUT \"TCP\"`);\n}\nfor (let i = 25; i <= 28; i++) {\n lines.push(`SUBSCRIBE CH${i}_SUBOUT \"TCP\"`);\n}\n// Join commands with CR and append final CR (required by Solaro API)\nmsg.payload = lines.join('\\r') + '\\r';\n\nreturn msg;\n",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 400,
|
||
"y": 520,
|
||
"wires": [
|
||
[
|
||
"037d562b5e1e1b78"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "037d562b5e1e1b78",
|
||
"type": "function",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "append \\r",
|
||
"func": "if (typeof msg.payload !== 'string') msg.payload = String(msg.payload);\nmsg.payload += \"\\r\";\nreturn msg;",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 600,
|
||
"y": 520,
|
||
"wires": [
|
||
[
|
||
"e5b7326c3b0a69e3",
|
||
"b025382cef8ddeae"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "59dbb335713fd5f0",
|
||
"type": "rbe",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "Only on change per channel",
|
||
"func": "rbe",
|
||
"gap": "",
|
||
"start": "",
|
||
"inout": "out",
|
||
"septopics": true,
|
||
"property": "payload.state",
|
||
"topi": "topic",
|
||
"x": 960,
|
||
"y": 580,
|
||
"wires": [
|
||
[
|
||
"a7281d90be3924f1"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "c728f8b711b33b49",
|
||
"type": "udp out",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "OSC → CueCore2 10.2.71.130:8000",
|
||
"addr": "10.2.71.130",
|
||
"iface": "",
|
||
"port": "8000",
|
||
"ipv": "udp4",
|
||
"outport": "",
|
||
"base64": false,
|
||
"x": 1630,
|
||
"y": 580,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "a7281d90be3924f1",
|
||
"type": "function",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "Encode OSC /position/<idx> int <state>",
|
||
"func": "// Remember which channel was last ON\nlet lastOn = context.get('lastOn') || null;\n\nconst { idx, state } = msg.payload;\n\n// Only act when something changes\nif (state === 1) {\n // If a new channel turns ON\n if (lastOn !== idx) {\n // Update context\n context.set('lastOn', idx);\n\n // Encode OSC message for this channel\n function pad4(buf) {\n const pad = (4 - (buf.length % 4)) % 4;\n return Buffer.concat([buf, Buffer.alloc(pad)]);\n }\n\n function oscString(s) {\n return pad4(\n Buffer.concat([\n Buffer.from(s, 'utf8'),\n Buffer.from([0])\n ])\n );\n }\n\n const addr = oscString(`/position/${idx}`);\n const types = oscString(',i');\n const arg = Buffer.alloc(4);\n arg.writeInt32BE(1, 0); // ON\n\n msg.payload = Buffer.concat([addr, types, arg]);\n node.status({ text: `ON → position ${idx}` });\n return msg;\n } else {\n // Same channel turned on again → ignore\n return null;\n }\n} else if (state === 0 && lastOn === idx) {\n // If the currently active one turned off → reset\n context.set('lastOn', null);\n node.status({ text: `OFF → position ${idx}` });\n}\n\nreturn null;\n",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 1260,
|
||
"y": 580,
|
||
"wires": [
|
||
[
|
||
"e72e32cf5af0e00f",
|
||
"c728f8b711b33b49"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "f24442d61d44086a",
|
||
"type": "debug",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "Parsed {idx,state}",
|
||
"active": true,
|
||
"tosidebar": true,
|
||
"complete": "payload",
|
||
"statusVal": "",
|
||
"statusType": "auto",
|
||
"x": 1550,
|
||
"y": 480,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "e72e32cf5af0e00f",
|
||
"type": "debug",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "OSC bytes",
|
||
"active": true,
|
||
"tosidebar": true,
|
||
"complete": "payload",
|
||
"x": 1570,
|
||
"y": 520,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "e5b7326c3b0a69e3",
|
||
"type": "debug",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "FR1 raw",
|
||
"active": true,
|
||
"tosidebar": true,
|
||
"complete": "payload",
|
||
"x": 620,
|
||
"y": 460,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "93a6e10d2495c341",
|
||
"type": "function",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "Parse CHxOUT=TRUE/0 → {idx,state}",
|
||
"func": "const s = String(msg.payload ?? '').trim();\n\n// Accepts #CH12OUT, #CH1_FROUT, #CH28_SUBOUT\nconst m = s.match(/^(#CH((?:\\d+)(?:_(?:FR|SUB))?)OUT)\\s*=\\s*(.*)$/i);\nif (!m) return null;\n\nconst name = m[1];\nconst raw = m[3];\n\n// Get the leading digits after #CH, even if there's _FR/_SUB\nconst idxMatch = name.match(/^#CH(\\d+)/i);\nconst idx = idxMatch ? Number(idxMatch[1]) : NaN;\n\nlet state;\nif (/^(TRUE|on)$/i.test(raw)) {\n state = 0;\n} else if (/^(FALSE|off)$/i.test(raw)) {\n state = 1;\n} else if (/^[-+]?\\d+(?:\\.\\d+)?$/.test(raw)) {\n state = Number(raw) !== 0 ? 1 : 0;\n} else {\n state = 0;\n}\n\nreturn {\n topic: name.toUpperCase(),\n payload: { idx, state }\n};\n",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 1250,
|
||
"y": 480,
|
||
"wires": [
|
||
[
|
||
"f24442d61d44086a",
|
||
"59dbb335713fd5f0"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "689ea5b3b7e5b6fb",
|
||
"type": "ui-button",
|
||
"z": "42887eae2f29d3f0",
|
||
"group": "c1fc81e5a697e224",
|
||
"name": "Scan Wi-Fi",
|
||
"label": "Scan Wi-Fi",
|
||
"order": 1,
|
||
"width": "1",
|
||
"height": "1",
|
||
"emulateClick": false,
|
||
"className": "",
|
||
"icon": "",
|
||
"iconPosition": "left",
|
||
"payload": "",
|
||
"payloadType": "str",
|
||
"topic": "",
|
||
"topicType": "str",
|
||
"buttonColor": "",
|
||
"textColor": "",
|
||
"iconColor": "",
|
||
"enableClick": true,
|
||
"enablePointerdown": false,
|
||
"pointerdownPayload": "",
|
||
"pointerdownPayloadType": "str",
|
||
"enablePointerup": false,
|
||
"pointerupPayload": "",
|
||
"pointerupPayloadType": "str",
|
||
"x": 130,
|
||
"y": 740,
|
||
"wires": [
|
||
[
|
||
"e5a955dbcbd0b0f9"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "e5a955dbcbd0b0f9",
|
||
"type": "exec",
|
||
"z": "42887eae2f29d3f0",
|
||
"command": "bash",
|
||
"addpay": false,
|
||
"append": "-lc \"sudo -n nmcli -t -f SSID,SIGNAL,SECURITY dev wifi rescan >/dev/null 2>&1; sudo -n nmcli -t -f SSID,SIGNAL,SECURITY dev wifi list\"",
|
||
"useSpawn": "false",
|
||
"timer": "",
|
||
"winHide": false,
|
||
"name": "nmcli scan",
|
||
"x": 360,
|
||
"y": 750,
|
||
"wires": [
|
||
[
|
||
"c5625882bf1d9523",
|
||
"650fa075c7bd5ee4"
|
||
],
|
||
[],
|
||
[
|
||
"70623f49cca4d94e"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "c5625882bf1d9523",
|
||
"type": "function",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "parse nmcli → options",
|
||
"func": "// Input (stdout): \"SSID:SIGNAL:SECURITY\" lines\nconst lines = String(msg.payload || \"\")\n .split(/\\r?\\n/)\n .map(s => s.trim())\n .filter(Boolean);\n\nconst bestBySsid = new Map();\nfor (const line of lines) {\n const parts = line.split(\":\");\n const ssid = parts[0] || \"\"; // may be empty (hidden)\n const signal = Number(parts[1] || 0);\n const sec = parts.slice(2).join(\":\"); // SECURITY may contain colons\n\n const display = ssid || \"(hidden)\";\n const label = `${display} (${signal}%)${sec ? \" • \" + sec : \"\"}`;\n\n const prev = bestBySsid.get(ssid);\n if (!prev || signal > prev.signal) {\n bestBySsid.set(ssid, { label, value: ssid, signal });\n }\n}\n\nmsg.options = [...bestBySsid.values()].map(({ label, value }) => ({ label, value }));\nmsg.payload = null; // keep payload clean\nreturn msg;\n",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 570,
|
||
"y": 750,
|
||
"wires": [
|
||
[
|
||
"4c38affd4fc6f7fd"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "4c38affd4fc6f7fd",
|
||
"type": "ui-dropdown",
|
||
"z": "42887eae2f29d3f0",
|
||
"group": "c1fc81e5a697e224",
|
||
"name": "SSID",
|
||
"label": "SSID",
|
||
"tooltip": "",
|
||
"order": 2,
|
||
"width": 3,
|
||
"height": 1,
|
||
"passthru": true,
|
||
"multiple": false,
|
||
"chips": false,
|
||
"clearable": false,
|
||
"options": [],
|
||
"topic": "",
|
||
"topicType": "str",
|
||
"className": "",
|
||
"typeIsComboBox": true,
|
||
"msgTrigger": "onChange",
|
||
"x": 770,
|
||
"y": 750,
|
||
"wires": [
|
||
[
|
||
"9143c8d7cb5f0695"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "9143c8d7cb5f0695",
|
||
"type": "change",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "store ssid",
|
||
"rules": [
|
||
{
|
||
"t": "set",
|
||
"p": "ssid",
|
||
"pt": "flow",
|
||
"to": "payload",
|
||
"tot": "msg"
|
||
}
|
||
],
|
||
"x": 950,
|
||
"y": 750,
|
||
"wires": [
|
||
[]
|
||
]
|
||
},
|
||
{
|
||
"id": "5d220b230ade6d6d",
|
||
"type": "ui-text-input",
|
||
"z": "42887eae2f29d3f0",
|
||
"group": "c1fc81e5a697e224",
|
||
"name": "Password",
|
||
"label": "Password",
|
||
"order": 3,
|
||
"width": 3,
|
||
"height": 1,
|
||
"topic": "",
|
||
"mode": "password",
|
||
"delay": "0",
|
||
"className": "",
|
||
"x": 1000,
|
||
"y": 800,
|
||
"wires": [
|
||
[
|
||
"1011308429ae7b27"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "1011308429ae7b27",
|
||
"type": "change",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "store pass (flow)",
|
||
"rules": [
|
||
{
|
||
"t": "set",
|
||
"p": "pass",
|
||
"pt": "flow",
|
||
"to": "payload",
|
||
"tot": "msg"
|
||
}
|
||
],
|
||
"x": 1200,
|
||
"y": 800,
|
||
"wires": [
|
||
[]
|
||
]
|
||
},
|
||
{
|
||
"id": "814b0a944c467115",
|
||
"type": "ui-button",
|
||
"z": "42887eae2f29d3f0",
|
||
"group": "c1fc81e5a697e224",
|
||
"name": "Connect",
|
||
"label": "Connect",
|
||
"order": 4,
|
||
"width": "1",
|
||
"height": "1",
|
||
"emulateClick": false,
|
||
"className": "",
|
||
"icon": "",
|
||
"iconPosition": "left",
|
||
"payload": "",
|
||
"payloadType": "str",
|
||
"topic": "",
|
||
"topicType": "str",
|
||
"buttonColor": "",
|
||
"textColor": "",
|
||
"iconColor": "",
|
||
"enableClick": true,
|
||
"enablePointerdown": false,
|
||
"pointerdownPayload": "",
|
||
"pointerdownPayloadType": "str",
|
||
"enablePointerup": false,
|
||
"pointerupPayload": "",
|
||
"pointerupPayloadType": "str",
|
||
"x": 120,
|
||
"y": 900,
|
||
"wires": [
|
||
[
|
||
"7a4b562cba69efaa"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "7a4b562cba69efaa",
|
||
"type": "function",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "build nmcli cmd",
|
||
"func": "const ssid = flow.get('ssid');\nconst pass = flow.get('pass') || '';\nif (!ssid) return [null, { payload: 'Select an SSID first.' }];\n\nconst q = s => `'` + String(s).replace(/'/g, `'\\\\''`) + `'`; // safe single-quote\n\nlet inner = `/usr/bin/nmcli dev wifi connect ${q(ssid)}`;\nif (pass) inner += ` password ${q(pass)}`;\n\n// Exec node runs: sh -c \"<inner with sudo>\"\nconst cmd = `-c \"sudo -n ${inner}\"`;\n\nreturn [\n { payload: cmd }, // → Exec (runs: sh -c \"sudo -n /usr/bin/nmcli …\")\n { payload: `Connecting to ${ssid}...` } // → Status\n];\n",
|
||
"outputs": 2,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 330,
|
||
"y": 900,
|
||
"wires": [
|
||
[
|
||
"9f7621962705882b"
|
||
],
|
||
[
|
||
"f84f8a46913a2fef"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "9f7621962705882b",
|
||
"type": "exec",
|
||
"z": "42887eae2f29d3f0",
|
||
"command": "sh",
|
||
"addpay": "payload",
|
||
"append": "",
|
||
"useSpawn": "false",
|
||
"timer": "",
|
||
"winHide": false,
|
||
"name": "nmcli connect",
|
||
"x": 640,
|
||
"y": 860,
|
||
"wires": [
|
||
[
|
||
"c066f2a161e02498"
|
||
],
|
||
[],
|
||
[
|
||
"70623f49cca4d94e"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "c066f2a161e02498",
|
||
"type": "function",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "result → status",
|
||
"func": "const s = String(msg.payload||'');\nlet ok = /successfully activated|Device.*successfully activated/i.test(s);\nlet txt = ok ? 'Connected.' : s.trim() || 'Failed.';\nreturn { payload: txt };\n",
|
||
"outputs": 1,
|
||
"x": 710,
|
||
"y": 1000,
|
||
"wires": [
|
||
[
|
||
"f84f8a46913a2fef"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "f84f8a46913a2fef",
|
||
"type": "ui-text",
|
||
"z": "42887eae2f29d3f0",
|
||
"group": "c1fc81e5a697e224",
|
||
"order": 5,
|
||
"width": 6,
|
||
"height": 1,
|
||
"name": "Status",
|
||
"label": "Status",
|
||
"layout": "row-spread",
|
||
"style": false,
|
||
"font": "",
|
||
"fontSize": "",
|
||
"color": "#000000",
|
||
"wrapText": true,
|
||
"className": "",
|
||
"value": "payload",
|
||
"valueType": "msg",
|
||
"x": 870,
|
||
"y": 960,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "70623f49cca4d94e",
|
||
"type": "debug",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "stderr",
|
||
"active": false,
|
||
"tosidebar": true,
|
||
"complete": "payload",
|
||
"x": 810,
|
||
"y": 800,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "650fa075c7bd5ee4",
|
||
"type": "debug",
|
||
"z": "42887eae2f29d3f0",
|
||
"name": "nmcli scan debug",
|
||
"active": true,
|
||
"tosidebar": true,
|
||
"console": false,
|
||
"tostatus": false,
|
||
"complete": "payload",
|
||
"targetType": "msg",
|
||
"statusVal": "",
|
||
"statusType": "auto",
|
||
"x": 590,
|
||
"y": 660,
|
||
"wires": []
|
||
}
|
||
] |