926 lines
36 KiB
JSON
926 lines
36 KiB
JSON
[
|
||
{
|
||
"id": "8bbc764cd051ef1b",
|
||
"type": "tab",
|
||
"label": "Xilica FR1 Dashboard",
|
||
"disabled": false,
|
||
"info": ""
|
||
},
|
||
{
|
||
"id": "a46fbefc8db2270b",
|
||
"type": "group",
|
||
"z": "8bbc764cd051ef1b",
|
||
"name": "CueCore2 Group",
|
||
"style": {
|
||
"label": true
|
||
},
|
||
"nodes": [
|
||
"4e52ab5c6cc5f16a",
|
||
"3c81e7f06c408f8a",
|
||
"af2d1ab8e73b9667",
|
||
"d4bb5ead71493ebe",
|
||
"66a230ec07338045"
|
||
],
|
||
"x": 1114,
|
||
"y": 439,
|
||
"w": 1432,
|
||
"h": 268
|
||
},
|
||
{
|
||
"id": "66a230ec07338045",
|
||
"type": "group",
|
||
"z": "8bbc764cd051ef1b",
|
||
"g": "a46fbefc8db2270b",
|
||
"name": "CueCore 2 UI (device info)",
|
||
"style": {
|
||
"label": true
|
||
},
|
||
"nodes": [
|
||
"a7f24e2687a40804",
|
||
"91810af23bcdb612",
|
||
"07ea11aa3a92234b"
|
||
],
|
||
"x": 1554,
|
||
"y": 539,
|
||
"w": 572,
|
||
"h": 142
|
||
},
|
||
{
|
||
"id": "ui_base_main",
|
||
"type": "ui-base",
|
||
"name": "Main UI",
|
||
"path": "/ui",
|
||
"includeClientData": true,
|
||
"acceptsClientConfig": [
|
||
"ui-control",
|
||
"ui-notification"
|
||
],
|
||
"headerContent": "page",
|
||
"titleBarStyle": "default",
|
||
"showReconnectNotification": true,
|
||
"notificationDisplayTime": 5,
|
||
"showDisconnectNotification": true,
|
||
"allowInstall": true
|
||
},
|
||
{
|
||
"id": "efd7cd1777464c3d",
|
||
"type": "ui-theme",
|
||
"name": "Theme Name",
|
||
"colors": {
|
||
"surface": "#ffffff",
|
||
"primary": "#0094ce",
|
||
"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": "5",
|
||
"height": "8",
|
||
"order": 1,
|
||
"showTitle": true,
|
||
"className": "",
|
||
"visible": "true",
|
||
"disabled": "false",
|
||
"groupType": "default"
|
||
},
|
||
{
|
||
"id": "0309a6afbb4232a9",
|
||
"type": "global-config",
|
||
"env": []
|
||
},
|
||
{
|
||
"id": "e57cccd09cde445b",
|
||
"type": "ui-text",
|
||
"z": "8bbc764cd051ef1b",
|
||
"group": "ui_group_master",
|
||
"order": 3,
|
||
"width": "1",
|
||
"height": "1",
|
||
"name": "Current (dB)",
|
||
"label": "Current (dB)",
|
||
"layout": "row-spread",
|
||
"style": false,
|
||
"font": "",
|
||
"fontSize": "",
|
||
"color": "#000000",
|
||
"wrapText": false,
|
||
"className": "",
|
||
"value": "payload",
|
||
"valueType": "msg",
|
||
"x": 1470,
|
||
"y": 200,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "db4f60616c5bb903",
|
||
"type": "ui-slider",
|
||
"z": "8bbc764cd051ef1b",
|
||
"group": "ui_group_master",
|
||
"name": "MASTER_GAIN (dB)",
|
||
"label": "MASTER_GAIN (dB)",
|
||
"order": 2,
|
||
"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": 180,
|
||
"y": 240,
|
||
"wires": [
|
||
[
|
||
"ab91656d7341e4d7"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "ab91656d7341e4d7",
|
||
"type": "function",
|
||
"z": "8bbc764cd051ef1b",
|
||
"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": 240,
|
||
"wires": [
|
||
[
|
||
"f08eb151bb70d817"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "f08eb151bb70d817",
|
||
"type": "function",
|
||
"z": "8bbc764cd051ef1b",
|
||
"name": "append \\r",
|
||
"func": "if (typeof msg.payload !== 'string') msg.payload = String(msg.payload);\nmsg.payload += \"\\r\";\nreturn msg;",
|
||
"outputs": 1,
|
||
"x": 680,
|
||
"y": 240,
|
||
"wires": [
|
||
[
|
||
"366f5e1cbb7a0f46"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "366f5e1cbb7a0f46",
|
||
"type": "tcp request",
|
||
"z": "8bbc764cd051ef1b",
|
||
"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": [
|
||
[
|
||
"2141a20e5c74aa81",
|
||
"7f1ff26625a38e58",
|
||
"4e52ab5c6cc5f16a",
|
||
"c8fc4af3e7fdbb59"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "2141a20e5c74aa81",
|
||
"type": "function",
|
||
"z": "8bbc764cd051ef1b",
|
||
"name": "parse GET reply → number",
|
||
"func": "\n\n// 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,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 1220,
|
||
"y": 240,
|
||
"wires": [
|
||
[
|
||
"e57cccd09cde445b"
|
||
],
|
||
[
|
||
"dd021e3fbc30688d"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "dd021e3fbc30688d",
|
||
"type": "link out",
|
||
"z": "8bbc764cd051ef1b",
|
||
"name": "link out 2",
|
||
"mode": "link",
|
||
"links": [
|
||
"fb625579ecce6897"
|
||
],
|
||
"x": 1405,
|
||
"y": 280,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "fb625579ecce6897",
|
||
"type": "link in",
|
||
"z": "8bbc764cd051ef1b",
|
||
"name": "link in 2",
|
||
"links": [
|
||
"dd021e3fbc30688d"
|
||
],
|
||
"x": 35,
|
||
"y": 240,
|
||
"wires": [
|
||
[
|
||
"db4f60616c5bb903"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "7f1ff26625a38e58",
|
||
"type": "function",
|
||
"z": "8bbc764cd051ef1b",
|
||
"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]); \nreturn msg;\n",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 1220,
|
||
"y": 320,
|
||
"wires": [
|
||
[
|
||
"260885bbfa09d5c8"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "260885bbfa09d5c8",
|
||
"type": "ui-template",
|
||
"z": "8bbc764cd051ef1b",
|
||
"group": "ui_group_master",
|
||
"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": 320,
|
||
"wires": [
|
||
[]
|
||
]
|
||
},
|
||
{
|
||
"id": "323e1bf353cf9505",
|
||
"type": "function",
|
||
"z": "8bbc764cd051ef1b",
|
||
"name": "Round-robin (SUBSCRIBER)",
|
||
"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\n/*\n* SUBSCRIPTIONS\n*/\n\n// Generate all channel names dynamically (CH4OUT → CH28OUT)\nfor (let i = 4; i <= 24; 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\nlines.push('SUBSCRIBE VU_MASTER \"TCP\"');\nlines.push('SUBSCRIBE MASTER_GAIN \"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": 640,
|
||
"y": 400,
|
||
"wires": [
|
||
[
|
||
"366f5e1cbb7a0f46",
|
||
"83d5fbc8960e2b43"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "3c81e7f06c408f8a",
|
||
"type": "rbe",
|
||
"z": "8bbc764cd051ef1b",
|
||
"g": "a46fbefc8db2270b",
|
||
"name": "Only on change per channel",
|
||
"func": "rbe",
|
||
"gap": "",
|
||
"start": "",
|
||
"inout": "out",
|
||
"septopics": true,
|
||
"property": "payload.state",
|
||
"topi": "topic",
|
||
"x": 1660,
|
||
"y": 480,
|
||
"wires": [
|
||
[
|
||
"af2d1ab8e73b9667"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "d4bb5ead71493ebe",
|
||
"type": "udp out",
|
||
"z": "8bbc764cd051ef1b",
|
||
"g": "a46fbefc8db2270b",
|
||
"name": "OSC → CueCore2 10.2.71.130:8000",
|
||
"addr": "10.2.71.130",
|
||
"iface": "",
|
||
"port": "8000",
|
||
"ipv": "udp4",
|
||
"outport": "",
|
||
"base64": false,
|
||
"x": 2370,
|
||
"y": 480,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "af2d1ab8e73b9667",
|
||
"type": "function",
|
||
"z": "8bbc764cd051ef1b",
|
||
"g": "a46fbefc8db2270b",
|
||
"name": "Encode OSC /position/<idx> int <state>",
|
||
"func": "/**\n * One output (Buffer) → your UDP OSC node\n * Behavior:\n * - On state===1: send /position/<idx>,1 and mark idx as ON\n * - On state===0: unmark idx; if none left ON, send OFF (/position/0,0)\n */\n\n// ---------- CONFIG (change if your CueCore expects different OFF) ----------\nconst ON_ADDR = (i) => `/position/${i}`;\nconst ON_ARG = 1;\nconst OFF_ADDR = '/position/0';\nconst OFF_ARG = 0;\n\n// ---------- OSC helpers ----------\nfunction pad4(buf){ const pad=(4-(buf.length%4))%4; return Buffer.concat([buf, Buffer.alloc(pad)]); }\nfunction oscString(s){ return pad4(Buffer.concat([Buffer.from(s,'utf8'), Buffer.from([0]) ])); }\nfunction oscInt(i){ const b=Buffer.alloc(4); b.writeInt32BE(i,0); return b; }\nfunction makeOsc(addr, intVal){\n const a = oscString(addr);\n const t = oscString(',i');\n const v = oscInt(intVal);\n return Buffer.concat([a,t,v]);\n}\n\n// ---------- Input ----------\nconst { idx, state } = msg.payload || {};\nif (!idx || Number.isNaN(Number(idx))) return null;\n\n// ---------- Track which channels are ON ----------\nlet onSet = context.get('onSet') || {}; // { [idx]: true }\nlet changed = false;\n\nif (state === 1) {\n if (!onSet[idx]) {\n onSet[idx] = true;\n changed = true;\n }\n context.set('onSet', onSet);\n\n // Always point to the (new) ON speaker\n const buf = makeOsc(ON_ADDR(idx), ON_ARG);\n node.status({ text: `ON → /position/${idx}` });\n return { payload: buf };\n}\n\nif (state === 0) {\n if (onSet[idx]) {\n delete onSet[idx];\n changed = true;\n }\n context.set('onSet', onSet);\n\n // If no channels remain ON → send OFF\n if (Object.keys(onSet).length === 0) {\n const buf = makeOsc(OFF_ADDR, OFF_ARG);\n node.status({ text: 'ALL OFF → /position/0' });\n return { payload: buf };\n }\n\n // Some other channel still ON → no OSC needed\n node.status({ text: `OFF ${idx} (others still ON)` });\n return null;\n}\n\nreturn null;\n",
|
||
"outputs": 1,
|
||
"timeout": "",
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 2000,
|
||
"y": 480,
|
||
"wires": [
|
||
[
|
||
"d4bb5ead71493ebe"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "4e52ab5c6cc5f16a",
|
||
"type": "function",
|
||
"z": "8bbc764cd051ef1b",
|
||
"g": "a46fbefc8db2270b",
|
||
"name": "Parse CHxOUT=TRUE/0 → {idx,state}",
|
||
"func": "const s = String(msg.payload ?? '').trim();\n\n\n// SHould accept $CH12OUT, #CH_FROUT and #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\n// I need to get the leading digits after the #CH, exceptions are _FR and _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": 1290,
|
||
"y": 480,
|
||
"wires": [
|
||
[
|
||
"3c81e7f06c408f8a",
|
||
"91810af23bcdb612"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "91810af23bcdb612",
|
||
"type": "function",
|
||
"z": "8bbc764cd051ef1b",
|
||
"g": "66a230ec07338045",
|
||
"name": "UI mess convert",
|
||
"func": "// Converts {idx, state} → UI message for uibuilder\nconst { idx, state } = msg.payload || {};\nif (!idx || Number.isNaN(idx)) return null;\n\n// Speaker names/descriptions (optional)\nconst SPEAKERS = {\n 6: { name: \"Front Left\", desc: \"Xilica AMP X — 500 W, 90°×40°\" },\n 5: { name: \"Front Right\", desc: \"Xilica AMP X — 500 W, 90°×40°\" },\n 7: { name: \"Front Right\", desc: \"Xilica AMP X — 500 W, 90°×40°\" },\n 19: { name: \"Balcony Left\", desc: \"Passive, 300 W, 100°×50°\" },\n 20: { name: \"Balcony Right\", desc: \"Passive, 300 W, 100°×50°\" },\n};\n\n// Remember which one is active\nlet active = context.get('active') || null;\n\nif (state === 1) {\n const spk = SPEAKERS[idx] || { name: `Speaker #${idx}`, desc: \"\" };\n context.set('active', idx);\n\n return {\n payload: {\n type: \"deviceInfo\",\n img: `./img/${idx}.webp`, // uses your PNG files\n html: `\n <h2>${spk.name}</h2>\n <p>${spk.desc}</p>\n <p><small><em>Channel ${idx}</em></small></p>\n `\n }\n };\n}\n\nif (state === 0 && active === idx) {\n context.set('active', null);\n return { payload: { type: \"idle\" } };\n}\n\nreturn null;\n",
|
||
"outputs": 1,
|
||
"timeout": 0,
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 1660,
|
||
"y": 580,
|
||
"wires": [
|
||
[
|
||
"a7f24e2687a40804",
|
||
"07ea11aa3a92234b"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "c8fc4af3e7fdbb59",
|
||
"type": "debug",
|
||
"z": "8bbc764cd051ef1b",
|
||
"name": "Raw replies",
|
||
"active": false,
|
||
"tosidebar": true,
|
||
"complete": "payload",
|
||
"statusVal": "",
|
||
"statusType": "auto",
|
||
"x": 1170,
|
||
"y": 400,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "83d5fbc8960e2b43",
|
||
"type": "debug",
|
||
"z": "8bbc764cd051ef1b",
|
||
"name": "Raw replies",
|
||
"active": false,
|
||
"tosidebar": true,
|
||
"complete": "payload",
|
||
"statusVal": "",
|
||
"statusType": "auto",
|
||
"x": 910,
|
||
"y": 280,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "7a3c4e3c8b5c0c8b",
|
||
"type": "debug",
|
||
"z": "8bbc764cd051ef1b",
|
||
"name": "Raw replies",
|
||
"active": true,
|
||
"tosidebar": true,
|
||
"complete": "payload",
|
||
"statusVal": "",
|
||
"statusType": "auto",
|
||
"x": 990,
|
||
"y": 500,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "5f160e68439d75a7",
|
||
"type": "function",
|
||
"z": "8bbc764cd051ef1b",
|
||
"name": "starter",
|
||
"func": "// Load persisted flags\nlet xilicaLivePrev = context.get('xilicaLive') || false;\nlet visualProductionsLivePrev = context.get('visualProductionsLive') || false;\nlet systemOnPrev = context.get('systemOn') || false;\n\n// Update flags from this message\nlet xilicaLive = xilicaLivePrev;\nlet visualProductionsLive = visualProductionsLivePrev;\n\nif (msg.payload === 'XILICA=LIVE') xilicaLive = true;\nif (msg.payload === 'XILICA=DEAD') xilicaLive = false;\nif (msg.payload === 'VISUALPRODUCTIONS=LIVE') visualProductionsLive = true;\nif (msg.payload === 'VISUALPRODUCTIONS=DEAD') visualProductionsLive = false;\n\n// Persist device flags\ncontext.set('xilicaLive', xilicaLive);\ncontext.set('visualProductionsLive', visualProductionsLive);\n\n// Determine overall system state\nconst systemOnNow = xilicaLive && visualProductionsLive;\n\n// Detect device reconnect (Xilica DEAD→LIVE)\nconst xilicaReconnected = (!xilicaLivePrev && xilicaLive);\n\n// Detect overall ON/OFF change\nconst systemStateChanged = (systemOnNow !== systemOnPrev);\n\n// Persist new system state\ncontext.set('systemOn', systemOnNow);\n\n// Conditions that should trigger a re-subscribe:\nif (systemStateChanged || xilicaReconnected) {\n node.status({text:`Trigger subscribe (x:${xilicaLive} v:${visualProductionsLive} on:${systemOnNow})`});\n return { payload: 'SUBSCRIBE' };\n}\n\n// Otherwise do nothing\nreturn null;\n",
|
||
"outputs": 1,
|
||
"timeout": 0,
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 830,
|
||
"y": 720,
|
||
"wires": [
|
||
[]
|
||
]
|
||
},
|
||
{
|
||
"id": "3eeee62903e4dc53",
|
||
"type": "inject",
|
||
"z": "8bbc764cd051ef1b",
|
||
"name": "INJECT PING",
|
||
"props": [
|
||
{
|
||
"p": "payload"
|
||
}
|
||
],
|
||
"repeat": "2",
|
||
"crontab": "",
|
||
"once": true,
|
||
"onceDelay": "0.5",
|
||
"topic": "",
|
||
"payload": "",
|
||
"payloadType": "str",
|
||
"x": 180,
|
||
"y": 700,
|
||
"wires": [
|
||
[
|
||
"2be25aa44261aba0",
|
||
"4a69e31ccbd2b33a"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "2be25aa44261aba0",
|
||
"type": "ping",
|
||
"z": "8bbc764cd051ef1b",
|
||
"protocol": "Automatic",
|
||
"mode": "triggered",
|
||
"name": "",
|
||
"host": "192.168.1.244",
|
||
"timer": "20",
|
||
"inputs": 1,
|
||
"x": 390,
|
||
"y": 680,
|
||
"wires": [
|
||
[
|
||
"79313575c65917d2"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "4a69e31ccbd2b33a",
|
||
"type": "ping",
|
||
"z": "8bbc764cd051ef1b",
|
||
"protocol": "Automatic",
|
||
"mode": "triggered",
|
||
"name": "",
|
||
"host": "10.2.71.130",
|
||
"timer": "20",
|
||
"inputs": 1,
|
||
"x": 400,
|
||
"y": 740,
|
||
"wires": [
|
||
[
|
||
"bbae029ed37eba59"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "79313575c65917d2",
|
||
"type": "function",
|
||
"z": "8bbc764cd051ef1b",
|
||
"name": "xilica live?",
|
||
"func": "const v = Number(msg.payload);\n// show value on the slider label while sending\nif(msg.payload != null){\n msg.payload = 'XILICA=LIVE';\n} else {\n msg.payload = 'XILICA=DEAD'\n}\nreturn { payload: msg.payload };\n",
|
||
"outputs": 1,
|
||
"timeout": 0,
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 650,
|
||
"y": 680,
|
||
"wires": [
|
||
[
|
||
"5f160e68439d75a7"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "bbae029ed37eba59",
|
||
"type": "function",
|
||
"z": "8bbc764cd051ef1b",
|
||
"name": "visual productions live?",
|
||
"func": "if(msg.payload != null){\n msg.payload = 'VISUALPRODUCTIONS=LIVE';\n} else {\n msg.payload = 'VISUALPRODUCTIONS=DEAD'\n}\nreturn msg;",
|
||
"outputs": 1,
|
||
"timeout": 0,
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 610,
|
||
"y": 740,
|
||
"wires": [
|
||
[
|
||
"5f160e68439d75a7"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "49cd35801588c2a9",
|
||
"type": "inject",
|
||
"z": "8bbc764cd051ef1b",
|
||
"name": "INJECT PING",
|
||
"props": [
|
||
{
|
||
"p": "payload"
|
||
}
|
||
],
|
||
"repeat": "60",
|
||
"crontab": "",
|
||
"once": true,
|
||
"onceDelay": "0.5",
|
||
"topic": "",
|
||
"payload": "",
|
||
"payloadType": "str",
|
||
"x": 360,
|
||
"y": 380,
|
||
"wires": [
|
||
[
|
||
"323e1bf353cf9505"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "07ea11aa3a92234b",
|
||
"type": "debug",
|
||
"z": "8bbc764cd051ef1b",
|
||
"g": "66a230ec07338045",
|
||
"name": "Raw replies",
|
||
"active": false,
|
||
"tosidebar": true,
|
||
"complete": "payload",
|
||
"statusVal": "",
|
||
"statusType": "auto",
|
||
"x": 1890,
|
||
"y": 640,
|
||
"wires": []
|
||
},
|
||
{
|
||
"id": "a7f24e2687a40804",
|
||
"type": "uibuilder",
|
||
"z": "8bbc764cd051ef1b",
|
||
"g": "66a230ec07338045",
|
||
"name": "DEVICE INFO PAGE",
|
||
"topic": "",
|
||
"url": "device-info",
|
||
"okToGo": true,
|
||
"fwdInMessages": false,
|
||
"allowScripts": false,
|
||
"allowStyles": false,
|
||
"copyIndex": true,
|
||
"templateFolder": "blank",
|
||
"extTemplate": "",
|
||
"showfolder": false,
|
||
"reload": false,
|
||
"sourceFolder": "src",
|
||
"deployedVersion": "7.5.0",
|
||
"showMsgUib": false,
|
||
"title": "",
|
||
"descr": "",
|
||
"editurl": "vscode://vscode-remote/ssh-remote+192.168.1.37/home/lavadmin/.node-red/uibuilder/device-info/?windowId=_blank",
|
||
"x": 1960,
|
||
"y": 580,
|
||
"wires": [
|
||
[],
|
||
[]
|
||
]
|
||
},
|
||
{
|
||
"id": "05ed1a581484a8f0",
|
||
"type": "ui-slider",
|
||
"z": "8bbc764cd051ef1b",
|
||
"group": "ui_group_master",
|
||
"name": "Dimmer",
|
||
"label": "Dimmer",
|
||
"tooltip": "",
|
||
"order": 0,
|
||
"width": 0,
|
||
"height": 0,
|
||
"passthru": false,
|
||
"outs": "all",
|
||
"topic": "topic",
|
||
"topicType": "msg",
|
||
"thumbLabel": "true",
|
||
"showTicks": "always",
|
||
"min": 0,
|
||
"max": "100",
|
||
"step": 1,
|
||
"className": "",
|
||
"iconPrepend": "",
|
||
"iconAppend": "",
|
||
"color": "",
|
||
"colorTrack": "",
|
||
"colorThumb": "",
|
||
"showTextField": false,
|
||
"x": 1940,
|
||
"y": 340,
|
||
"wires": [
|
||
[
|
||
"1f590b0ab6481968"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "1f590b0ab6481968",
|
||
"type": "function",
|
||
"z": "8bbc764cd051ef1b",
|
||
"name": "Dimmer OSC",
|
||
"func": "/**\n * Input: msg.payload = 0..100 (percent from slider)\n * Output: msg.payload = Buffer (OSC packet)\n * Address: /core/va/1/set (int32 0..255)\n */\n\nconst pct = Number(msg.payload);\nif (!Number.isFinite(pct)) return null;\n\n// clamp 0..100 and convert to 0..255\nconst p = Math.max(0, Math.min(100, pct));\nconst val = Math.round(p * 255 / 100);\n\n// ---------- OSC helpers (same style as your /position encoder) ----------\nfunction pad4(buf) {\n const pad = (4 - (buf.length % 4)) % 4;\n return Buffer.concat([buf, Buffer.alloc(pad)]);\n}\n\nfunction oscString(s) {\n return pad4(Buffer.concat([Buffer.from(s, 'utf8'), Buffer.from([0])]));\n}\n\nfunction oscInt(i) {\n const b = Buffer.alloc(4);\n b.writeInt32BE(i, 0);\n return b;\n}\n\nfunction makeOsc(addr, intVal) {\n const a = oscString(addr);\n const t = oscString(',i'); // int argument\n const v = oscInt(intVal);\n return Buffer.concat([a, t, v]);\n}\n\n// build packet for Variable 1\nconst buf = makeOsc('/core/va/1/set', val);\nnode.status({ text: `Dimmer VA1 = ${val} (${p.toFixed(0)}%)` });\n\nmsg.payload = buf;\nreturn msg;\n",
|
||
"outputs": 1,
|
||
"timeout": 0,
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 2150,
|
||
"y": 340,
|
||
"wires": [
|
||
[
|
||
"d4bb5ead71493ebe"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "73778ab3a60941fe",
|
||
"type": "ui-slider",
|
||
"z": "8bbc764cd051ef1b",
|
||
"group": "ui_group_master",
|
||
"name": "Tilt",
|
||
"label": "Tilt",
|
||
"tooltip": "",
|
||
"order": 0,
|
||
"width": 0,
|
||
"height": 0,
|
||
"passthru": false,
|
||
"outs": "all",
|
||
"topic": "topic",
|
||
"topicType": "msg",
|
||
"thumbLabel": "true",
|
||
"showTicks": "always",
|
||
"min": 0,
|
||
"max": "100",
|
||
"step": 1,
|
||
"className": "",
|
||
"iconPrepend": "",
|
||
"iconAppend": "",
|
||
"color": "",
|
||
"colorTrack": "",
|
||
"colorThumb": "",
|
||
"showTextField": false,
|
||
"x": 1910,
|
||
"y": 260,
|
||
"wires": [
|
||
[
|
||
"e862c513a1798795"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "e862c513a1798795",
|
||
"type": "function",
|
||
"z": "8bbc764cd051ef1b",
|
||
"name": "Tilt OSC",
|
||
"func": "/**\n * Input: msg.payload = 0..100 (percent from slider)\n * Output: msg.payload = Buffer (OSC packet)\n * OSC Address: /core/va/2/set (int32 0..255) -> Tilt variable\n */\n\nconst pct = Number(msg.payload);\nif (!Number.isFinite(pct)) return null;\n\n// clamp 0..100 and convert to 0..255\nconst p = Math.max(0, Math.min(100, pct));\nconst val = Math.round(p * 255 / 100);\n\n// ---------- OSC helpers ----------\nfunction pad4(buf) {\n const pad = (4 - (buf.length % 4)) % 4;\n return Buffer.concat([buf, Buffer.alloc(pad)]);\n}\n\nfunction oscString(s) {\n return pad4(Buffer.concat([Buffer.from(s, 'utf8'), Buffer.from([0])]));\n}\n\nfunction oscInt(i) {\n const b = Buffer.alloc(4);\n b.writeInt32BE(i, 0);\n return b;\n}\n\nfunction makeOsc(addr, intVal) {\n const a = oscString(addr);\n const t = oscString(',i'); // int argument\n const v = oscInt(intVal);\n return Buffer.concat([a, t, v]);\n}\n\n// build packet for Variable 2 (Tilt)\nconst buf = makeOsc('/core/va/2/set', val);\nnode.status({ text: `Tilt VA2 = ${val} (${p.toFixed(0)}%)` });\n\nmsg.payload = buf;\nreturn msg;\n",
|
||
"outputs": 1,
|
||
"timeout": 0,
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 2120,
|
||
"y": 260,
|
||
"wires": [
|
||
[
|
||
"d4bb5ead71493ebe"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "bb45bbb615881c63",
|
||
"type": "ui-slider",
|
||
"z": "8bbc764cd051ef1b",
|
||
"group": "ui_group_master",
|
||
"name": "Pan",
|
||
"label": "Pan",
|
||
"tooltip": "",
|
||
"order": 0,
|
||
"width": 0,
|
||
"height": 0,
|
||
"passthru": false,
|
||
"outs": "all",
|
||
"topic": "topic",
|
||
"topicType": "msg",
|
||
"thumbLabel": "true",
|
||
"showTicks": "always",
|
||
"min": 0,
|
||
"max": "100",
|
||
"step": 1,
|
||
"className": "",
|
||
"iconPrepend": "",
|
||
"iconAppend": "",
|
||
"color": "",
|
||
"colorTrack": "",
|
||
"colorThumb": "",
|
||
"showTextField": false,
|
||
"x": 1910,
|
||
"y": 200,
|
||
"wires": [
|
||
[
|
||
"0207c951094f5e4c"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "0207c951094f5e4c",
|
||
"type": "function",
|
||
"z": "8bbc764cd051ef1b",
|
||
"name": "Pan OSC",
|
||
"func": "/**\n * Input: msg.payload = 0..100 (percent from slider)\n * Output: msg.payload = Buffer (OSC packet)\n * OSC Address: /core/va/2/set (int32 0..255) -> Tilt variable\n */\n\nconst pct = Number(msg.payload);\nif (!Number.isFinite(pct)) return null;\n\n// clamp 0..100 and convert to 0..255\nconst p = Math.max(0, Math.min(100, pct));\nconst val = Math.round(p * 255 / 100);\n\n// ---------- OSC helpers ----------\nfunction pad4(buf) {\n const pad = (4 - (buf.length % 4)) % 4;\n return Buffer.concat([buf, Buffer.alloc(pad)]);\n}\n\nfunction oscString(s) {\n return pad4(Buffer.concat([Buffer.from(s, 'utf8'), Buffer.from([0])]));\n}\n\nfunction oscInt(i) {\n const b = Buffer.alloc(4);\n b.writeInt32BE(i, 0);\n return b;\n}\n\nfunction makeOsc(addr, intVal) {\n const a = oscString(addr);\n const t = oscString(',i'); // int argument\n const v = oscInt(intVal);\n return Buffer.concat([a, t, v]);\n}\n\n// build packet for Variable 2 (Tilt)\nconst buf = makeOsc('/core/va/3/set', val);\nnode.status({ text: `Pan VA3 = ${val} (${p.toFixed(0)}%)` });\n\nmsg.payload = buf;\nreturn msg;\n",
|
||
"outputs": 1,
|
||
"timeout": 0,
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 2120,
|
||
"y": 200,
|
||
"wires": [
|
||
[
|
||
"d4bb5ead71493ebe"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "045c2998caddfbb0",
|
||
"type": "ui-slider",
|
||
"z": "8bbc764cd051ef1b",
|
||
"group": "ui_group_master",
|
||
"name": "Focus",
|
||
"label": "Focus",
|
||
"tooltip": "",
|
||
"order": 0,
|
||
"width": 0,
|
||
"height": 0,
|
||
"passthru": false,
|
||
"outs": "all",
|
||
"topic": "topic",
|
||
"topicType": "msg",
|
||
"thumbLabel": "true",
|
||
"showTicks": "always",
|
||
"min": 0,
|
||
"max": "100",
|
||
"step": 1,
|
||
"className": "",
|
||
"iconPrepend": "",
|
||
"iconAppend": "",
|
||
"color": "",
|
||
"colorTrack": "",
|
||
"colorThumb": "",
|
||
"showTextField": false,
|
||
"x": 1870,
|
||
"y": 140,
|
||
"wires": [
|
||
[
|
||
"f2c869993a5774cd"
|
||
]
|
||
]
|
||
},
|
||
{
|
||
"id": "f2c869993a5774cd",
|
||
"type": "function",
|
||
"z": "8bbc764cd051ef1b",
|
||
"name": "Focus OSC",
|
||
"func": "/**\n * Input: msg.payload = 0..100 (percent from slider)\n * Output: msg.payload = Buffer (OSC packet)\n * OSC Address: /core/va/2/set (int32 0..255) -> Tilt variable\n */\n\nconst pct = Number(msg.payload);\nif (!Number.isFinite(pct)) return null;\n\n// clamp 0..100 and convert to 0..255\nconst p = Math.max(0, Math.min(100, pct));\nconst val = Math.round(p * 255 / 100);\n\n// ---------- OSC helpers ----------\nfunction pad4(buf) {\n const pad = (4 - (buf.length % 4)) % 4;\n return Buffer.concat([buf, Buffer.alloc(pad)]);\n}\n\nfunction oscString(s) {\n return pad4(Buffer.concat([Buffer.from(s, 'utf8'), Buffer.from([0])]));\n}\n\nfunction oscInt(i) {\n const b = Buffer.alloc(4);\n b.writeInt32BE(i, 0);\n return b;\n}\n\nfunction makeOsc(addr, intVal) {\n const a = oscString(addr);\n const t = oscString(',i'); // int argument\n const v = oscInt(intVal);\n return Buffer.concat([a, t, v]);\n}\n\n// build packet for Variable 2 (Tilt)\nconst buf = makeOsc('/core/va/4/set', val);\nnode.status({ text: `Focus VA4 = ${val} (${p.toFixed(0)}%)` });\n\nmsg.payload = buf;\nreturn msg;\n",
|
||
"outputs": 1,
|
||
"timeout": 0,
|
||
"noerr": 0,
|
||
"initialize": "",
|
||
"finalize": "",
|
||
"libs": [],
|
||
"x": 2090,
|
||
"y": 140,
|
||
"wires": [
|
||
[
|
||
"d4bb5ead71493ebe"
|
||
]
|
||
]
|
||
}
|
||
] |