[
    {
        "id": "tabXilicaDash",
        "type": "tab",
        "label": "Xilica FR1 Dashboard",
        "disabled": false,
        "info": ""
    },
    {
        "id": "8ec2e59f8a7e993a",
        "type": "ui-text",
        "z": "tabXilicaDash",
        "group": "ui_group_master",
        "order": "",
        "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": 1090,
        "y": 400,
        "wires": []
    },
    {
        "id": "f64df68a17fafed8",
        "type": "ui-slider",
        "z": "tabXilicaDash",
        "group": "ui_group_master",
        "name": "MASTER_GAIN (dB)",
        "label": "MASTER_GAIN (dB)",
        "order": "",
        "width": "1",
        "height": "7",
        "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": 540,
        "y": 240,
        "wires": [
            [
                "ebeeed016133d1b1"
            ]
        ]
    },
    {
        "id": "ebeeed016133d1b1",
        "type": "function",
        "z": "tabXilicaDash",
        "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": 850,
        "y": 240,
        "wires": [
            [
                "37a2739c39fb28c6"
            ]
        ]
    },
    {
        "id": "ff5d04b8e8358ac7",
        "type": "inject",
        "z": "tabXilicaDash",
        "name": "poll GET",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "0.1",
        "crontab": "",
        "once": true,
        "onceDelay": "0.5",
        "topic": "",
        "payload": "GET MASTER_GAIN",
        "payloadType": "str",
        "x": 480,
        "y": 320,
        "wires": [
            [
                "37a2739c39fb28c6"
            ]
        ]
    },
    {
        "id": "37a2739c39fb28c6",
        "type": "function",
        "z": "tabXilicaDash",
        "name": "append \\r",
        "func": "if (typeof msg.payload !== 'string') msg.payload = String(msg.payload);\nmsg.payload += \"\\r\";\nreturn msg;",
        "outputs": 1,
        "x": 740,
        "y": 340,
        "wires": [
            [
                "6f693811595db658"
            ]
        ]
    },
    {
        "id": "6f693811595db658",
        "type": "tcp request",
        "z": "tabXilicaDash",
        "name": "FR1 192.168.1.244:10007",
        "server": "192.168.1.244",
        "port": "10007",
        "out": "sit",
        "ret": "string",
        "splitc": " ",
        "newline": "",
        "trim": false,
        "tls": "",
        "x": 550,
        "y": 400,
        "wires": [
            [
                "fd2ea6ae724caf88",
                "2f4b662b4ed88da3",
                "8d72b821fca250d0"
            ]
        ]
    },
    {
        "id": "2f4b662b4ed88da3",
        "type": "function",
        "z": "tabXilicaDash",
        "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": 820,
        "y": 400,
        "wires": [
            [
                "8ec2e59f8a7e993a"
            ],
            [
                "8746aa1d3cf6fc69"
            ]
        ]
    },
    {
        "id": "fd2ea6ae724caf88",
        "type": "debug",
        "z": "tabXilicaDash",
        "name": "Raw replies",
        "active": false,
        "tosidebar": true,
        "complete": "payload",
        "statusVal": "",
        "statusType": "auto",
        "x": 850,
        "y": 500,
        "wires": []
    },
    {
        "id": "8746aa1d3cf6fc69",
        "type": "link out",
        "z": "tabXilicaDash",
        "name": "link out 2",
        "mode": "link",
        "links": [
            "f267313c2d612dea"
        ],
        "x": 985,
        "y": 440,
        "wires": []
    },
    {
        "id": "f267313c2d612dea",
        "type": "link in",
        "z": "tabXilicaDash",
        "name": "link in 2",
        "links": [
            "8746aa1d3cf6fc69"
        ],
        "x": 265,
        "y": 240,
        "wires": [
            [
                "f64df68a17fafed8"
            ]
        ]
    },
    {
        "id": "c2fce4e36d067d34",
        "type": "inject",
        "z": "tabXilicaDash",
        "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": [
            [
                "2100bc16276dbd8d"
            ]
        ]
    },
    {
        "id": "8d72b821fca250d0",
        "type": "function",
        "z": "tabXilicaDash",
        "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": 600,
        "y": 840,
        "wires": [
            [
                "a3665ff2cf1a93da"
            ]
        ]
    },
    {
        "id": "2100bc16276dbd8d",
        "type": "function",
        "z": "tabXilicaDash",
        "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": [
            [
                "6f693811595db658"
            ]
        ]
    },
    {
        "id": "3d0401ed2bb481cd",
        "type": "debug",
        "z": "tabXilicaDash",
        "name": "Template OUT",
        "active": true,
        "tosidebar": true,
        "complete": "payload",
        "statusVal": "",
        "statusType": "auto",
        "x": 1100,
        "y": 840,
        "wires": []
    },
    {
        "id": "a3665ff2cf1a93da",
        "type": "ui-template",
        "z": "tabXilicaDash",
        "group": "ui_group_meters",
        "page": "",
        "ui": "",
        "name": "vu_master",
        "order": 0,
        "width": 0,
        "height": 0,
        "head": "",
        "format": "<template>\n  <div class=\"vu2\">\n    <div class=\"vu2-hdr\">\n      <strong>VU_MASTER</strong>\n      <span class=\"vu2-readout\">{{ level.toFixed(1) }} dB</span>\n    </div>\n\n    <div class=\"vu2-bar\">\n      <div class=\"vu2-fill\" :style=\"{ width: percent + '%' }\"></div>\n      <div class=\"vu2-peak\" :style=\"{ left: peakPercent + '%' }\"></div>\n      <span class=\"vu2-tick\" v-for=\"t in ticks\" :key=\"t\" :style=\"{ left: t + '%' }\"></span>\n    </div>\n\n    <div class=\"vu2-ftr\">\n      <span>{{ min }} dB</span>\n      <span>−12</span>\n      <span>−3</span>\n      <span>{{ max }} dB</span>\n    </div>\n  </div>\n</template>\n\n<script>\nexport default {\n  // IMPORTANT for Dashboard 2: msg must be a prop to be reactive\n  props: ['msg'],\n\n  data: () => ({\n    min: -60,\n    max: 0,\n    level: -60,\n    peak: -60,\n    decayDbPerSec: 6,   // peak falls this many dB per second\n    _timer: null,\n    ticks: [0, 80, 95, 100] // % marks along the bar\n  }),\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\n  watch: {\n    // react to any incoming message\n    msg: {\n      deep: true,\n      immediate: true,\n      handler (m) {\n        if (!m) return;\n\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\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; // update peak on rise\n          }\n        }\n      }\n    }\n  },\n\n  mounted () {\n    // peak decay loop\n    const dt = 100; // ms\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\n  unmounted () {\n    if (this._timer) clearInterval(this._timer);\n  }\n}\n</script>\n\n<style>\n.vu2 { width:100%; font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif; }\n.vu2-hdr, .vu2-ftr {\n  display:flex; justify-content:space-between; align-items:center;\n}\n.vu2-readout { font-variant-numeric: tabular-nums; opacity:.8; }\n\n.vu2-bar {\n  position:relative; width:100%; height:18px; margin:6px 0 4px; border-radius:4px;\n  background:#0b0f14; overflow:hidden; box-shadow:inset 0 0 0 1px rgba(255,255,255,.06);\n}\n.vu2-fill {\n  position:absolute; top:0; bottom:0; left:0; z-index:1; transition:width 90ms linear;\n  background:linear-gradient(90deg,\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.vu2-peak {\n  position:absolute; top:0; bottom:0; width:2px; background:rgba(255,255,255,.9);\n  box-shadow:0 0 4px rgba(255,255,255,.9); z-index:2;\n}\n.vu2-tick {\n  position:absolute; top:0; bottom:0; width:1px; background:rgba(255,255,255,.15); z-index:0;\n}\n.vu2-ftr span { font-size:12px; color:rgba(255,255,255,.6); }\n</style>\n",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 870,
        "y": 840,
        "wires": [
            [
                "3d0401ed2bb481cd"
            ]
        ]
    },
    {
        "id": "ui_group_master",
        "type": "ui-group",
        "name": "Master Gain",
        "page": "ui_page_solaro",
        "width": "2",
        "height": "8",
        "showTitle": true,
        "className": "",
        "visible": "true",
        "disabled": "false",
        "groupType": "default"
    },
    {
        "id": "ui_group_meters",
        "type": "ui-group",
        "name": "Meters",
        "page": "ui_page_solaro",
        "width": "6",
        "height": "6",
        "showTitle": true,
        "className": "",
        "visible": "true",
        "disabled": "false",
        "groupType": "default"
    },
    {
        "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"
            }
        ],
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "ui_base_main",
        "type": "ui-base",
        "name": "Main UI",
        "path": "/ui",
        "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"
        }
    }
]
