Files
showroom_nodered/flows.json
marockaspark e62ca4c2fd Create project
2025-11-19 15:21:35 +01:00

926 lines
36 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[
{
"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 CH4OUTCH28OUT\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"
]
]
}
]