diff --git a/examples/01-Basic.json b/examples/01-Basic.json
new file mode 100644
index 0000000..c579f55
--- /dev/null
+++ b/examples/01-Basic.json
@@ -0,0 +1,677 @@
+[
+ {
+ "id": "tab_mgc_basic",
+ "type": "tab",
+ "label": "MGC - Basic",
+ "disabled": false,
+ "info": "Tier 1: one machineGroupControl (MGC) coordinating three rotatingMachine pumps. Setup once-fires virtualControl + cmd.startup on all three pumps; then operator demand drives the MGC, which dispatches per-pump flow setpoints."
+ },
+ {
+ "id": "grp_mgc_unit",
+ "type": "group",
+ "z": "tab_mgc_basic",
+ "name": "Machine Group (Unit)",
+ "style": {
+ "label": true,
+ "stroke": "#000000",
+ "fill": "#50a8d9",
+ "fill-opacity": "0.10"
+ },
+ "nodes": [
+ "mgc_basic_node"
+ ],
+ "x": 974,
+ "y": 359,
+ "w": 152,
+ "h": 122
+ },
+ {
+ "id": "grp_pump_a",
+ "type": "group",
+ "z": "tab_mgc_basic",
+ "name": "Pump A (Equipment)",
+ "style": {
+ "label": true,
+ "stroke": "#000000",
+ "fill": "#86bbdd",
+ "fill-opacity": "0.10"
+ },
+ "nodes": [
+ "rm_basic_pump_a"
+ ],
+ "x": 694,
+ "y": 199,
+ "w": 142,
+ "h": 82
+ },
+ {
+ "id": "grp_pump_b",
+ "type": "group",
+ "z": "tab_mgc_basic",
+ "name": "Pump B (Equipment)",
+ "style": {
+ "label": true,
+ "stroke": "#000000",
+ "fill": "#86bbdd",
+ "fill-opacity": "0.10"
+ },
+ "nodes": [
+ "rm_basic_pump_b"
+ ],
+ "x": 694,
+ "y": 379,
+ "w": 142,
+ "h": 82
+ },
+ {
+ "id": "grp_pump_c",
+ "type": "group",
+ "z": "tab_mgc_basic",
+ "name": "Pump C (Equipment)",
+ "style": {
+ "label": true,
+ "stroke": "#000000",
+ "fill": "#86bbdd",
+ "fill-opacity": "0.10"
+ },
+ "nodes": [
+ "rm_basic_pump_c"
+ ],
+ "x": 694,
+ "y": 559,
+ "w": 142,
+ "h": 82
+ },
+ {
+ "id": "grp_drv_mode",
+ "type": "group",
+ "z": "tab_mgc_basic",
+ "name": "1. Control mode",
+ "style": {
+ "stroke": "#666666",
+ "fill": "#ffdf7f",
+ "fill-opacity": "0.15",
+ "label": true,
+ "color": "#333333"
+ },
+ "nodes": [
+ "inj_mode_optimal",
+ "inj_mode_priority"
+ ],
+ "x": 94,
+ "y": 99,
+ "w": 312,
+ "h": 122
+ },
+ {
+ "id": "grp_drv_scaling",
+ "type": "group",
+ "z": "tab_mgc_basic",
+ "name": "2. Scaling",
+ "style": {
+ "stroke": "#666666",
+ "fill": "#ffdf7f",
+ "fill-opacity": "0.15",
+ "label": true,
+ "color": "#333333"
+ },
+ "nodes": [
+ "inj_scaling_norm",
+ "inj_scaling_abs"
+ ],
+ "x": 94,
+ "y": 259,
+ "w": 312,
+ "h": 122
+ },
+ {
+ "id": "grp_drv_demand",
+ "type": "group",
+ "z": "tab_mgc_basic",
+ "name": "3. Operator demand (% of group capacity)",
+ "style": {
+ "stroke": "#666666",
+ "fill": "#ffdf7f",
+ "fill-opacity": "0.15",
+ "label": true,
+ "color": "#333333"
+ },
+ "nodes": [
+ "inj_demand_25",
+ "inj_demand_50",
+ "inj_demand_75",
+ "inj_demand_100",
+ "inj_demand_0"
+ ],
+ "x": 94,
+ "y": 419,
+ "w": 312,
+ "h": 222
+ },
+ {
+ "id": "grp_setup",
+ "type": "group",
+ "z": "tab_mgc_basic",
+ "name": "Setup — once on deploy",
+ "style": {
+ "stroke": "#666666",
+ "fill": "#dddddd",
+ "fill-opacity": "0.20",
+ "label": true,
+ "color": "#333333"
+ },
+ "nodes": [
+ "inj_setup_start",
+ "fn_setup_fanout"
+ ],
+ "x": 94,
+ "y": 679,
+ "w": 532,
+ "h": 82
+ },
+ {
+ "id": "grp_dbg",
+ "type": "group",
+ "z": "tab_mgc_basic",
+ "name": "Debug outputs (sidebar)",
+ "style": {
+ "stroke": "#666666",
+ "fill": "#d1d1d1",
+ "fill-opacity": "0.2",
+ "label": true,
+ "color": "#333333"
+ },
+ "nodes": [
+ "dbg_port0",
+ "dbg_port1",
+ "dbg_port2"
+ ],
+ "x": 1234,
+ "y": 339,
+ "w": 232,
+ "h": 202
+ },
+ {
+ "id": "cmt_title",
+ "type": "comment",
+ "z": "tab_mgc_basic",
+ "name": "MGC — Basic (Tier 1)",
+ "info": "One machineGroupControl coordinating three rotatingMachine pumps.\n\nDefaults: mode=optimalControl, scaling=normalized.\n\nSETUP — fires once on deploy\n- Switches all 3 pumps to virtualControl mode\n- Sends cmd.startup to all 3 pumps\nPumps register with the MGC automatically via Port 2 (child.register).\n\nHOW TO USE\n1. Deploy — the Setup group auto-runs after ~1.5 s, putting pumps in virtual + started.\n2. Click any \"set.demand = N %\" — MGC dispatches per-pump flow setpoints by BEP-gravitation (default) or priority list, depending on the mode.\n3. Switch scaling to `absolute` to interpret set.demand as m³/h instead of %.\n4. Switch mode to `priorityControl` for sequential equal-flow control; `optimalControl` (default) picks the best combination automatically.\n5. Send `set.demand = 0` to drain the group (turnOffAllMachines).\n\nPORTS (MGC)\n- Port 0: process output (mode, scaling, totals, dist-from-peak)\n- Port 1: InfluxDB-shaped payload\n- Port 2: parent-registration handshake (when wired into a pumpingStation)",
+ "x": 1100,
+ "y": 280,
+ "wires": []
+ },
+ {
+ "id": "inj_mode_optimal",
+ "type": "inject",
+ "z": "tab_mgc_basic",
+ "g": "grp_drv_mode",
+ "name": "set.mode = optimalControl",
+ "props": [
+ { "p": "topic", "vt": "str" },
+ { "p": "payload", "v": "optimalControl", "vt": "str" }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": "",
+ "topic": "set.mode",
+ "x": 260,
+ "y": 140,
+ "wires": [
+ [
+ "mgc_basic_node"
+ ]
+ ]
+ },
+ {
+ "id": "inj_mode_priority",
+ "type": "inject",
+ "z": "tab_mgc_basic",
+ "g": "grp_drv_mode",
+ "name": "set.mode = priorityControl",
+ "props": [
+ { "p": "topic", "vt": "str" },
+ { "p": "payload", "v": "priorityControl", "vt": "str" }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": "",
+ "topic": "set.mode",
+ "x": 260,
+ "y": 180,
+ "wires": [
+ [
+ "mgc_basic_node"
+ ]
+ ]
+ },
+ {
+ "id": "inj_scaling_norm",
+ "type": "inject",
+ "z": "tab_mgc_basic",
+ "g": "grp_drv_scaling",
+ "name": "set.scaling = normalized",
+ "props": [
+ { "p": "topic", "vt": "str" },
+ { "p": "payload", "v": "normalized", "vt": "str" }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": "",
+ "topic": "set.scaling",
+ "x": 260,
+ "y": 300,
+ "wires": [
+ [
+ "mgc_basic_node"
+ ]
+ ]
+ },
+ {
+ "id": "inj_scaling_abs",
+ "type": "inject",
+ "z": "tab_mgc_basic",
+ "g": "grp_drv_scaling",
+ "name": "set.scaling = absolute",
+ "props": [
+ { "p": "topic", "vt": "str" },
+ { "p": "payload", "v": "absolute", "vt": "str" }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": "",
+ "topic": "set.scaling",
+ "x": 260,
+ "y": 340,
+ "wires": [
+ [
+ "mgc_basic_node"
+ ]
+ ]
+ },
+ {
+ "id": "inj_demand_0",
+ "type": "inject",
+ "z": "tab_mgc_basic",
+ "g": "grp_drv_demand",
+ "name": "set.demand = 0 (stop)",
+ "props": [
+ { "p": "topic", "vt": "str" },
+ { "p": "payload", "v": "0", "vt": "num" }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": "",
+ "topic": "set.demand",
+ "x": 260,
+ "y": 460,
+ "wires": [
+ [
+ "mgc_basic_node"
+ ]
+ ]
+ },
+ {
+ "id": "inj_demand_25",
+ "type": "inject",
+ "z": "tab_mgc_basic",
+ "g": "grp_drv_demand",
+ "name": "set.demand = 25 %",
+ "props": [
+ { "p": "topic", "vt": "str" },
+ { "p": "payload", "v": "25", "vt": "num" }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": "",
+ "topic": "set.demand",
+ "x": 260,
+ "y": 500,
+ "wires": [
+ [
+ "mgc_basic_node"
+ ]
+ ]
+ },
+ {
+ "id": "inj_demand_50",
+ "type": "inject",
+ "z": "tab_mgc_basic",
+ "g": "grp_drv_demand",
+ "name": "set.demand = 50 %",
+ "props": [
+ { "p": "topic", "vt": "str" },
+ { "p": "payload", "v": "50", "vt": "num" }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": "",
+ "topic": "set.demand",
+ "x": 260,
+ "y": 540,
+ "wires": [
+ [
+ "mgc_basic_node"
+ ]
+ ]
+ },
+ {
+ "id": "inj_demand_75",
+ "type": "inject",
+ "z": "tab_mgc_basic",
+ "g": "grp_drv_demand",
+ "name": "set.demand = 75 %",
+ "props": [
+ { "p": "topic", "vt": "str" },
+ { "p": "payload", "v": "75", "vt": "num" }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": "",
+ "topic": "set.demand",
+ "x": 260,
+ "y": 580,
+ "wires": [
+ [
+ "mgc_basic_node"
+ ]
+ ]
+ },
+ {
+ "id": "inj_demand_100",
+ "type": "inject",
+ "z": "tab_mgc_basic",
+ "g": "grp_drv_demand",
+ "name": "set.demand = 100 %",
+ "props": [
+ { "p": "topic", "vt": "str" },
+ { "p": "payload", "v": "100", "vt": "num" }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": "",
+ "topic": "set.demand",
+ "x": 260,
+ "y": 620,
+ "wires": [
+ [
+ "mgc_basic_node"
+ ]
+ ]
+ },
+ {
+ "id": "inj_setup_start",
+ "type": "inject",
+ "z": "tab_mgc_basic",
+ "g": "grp_setup",
+ "name": "Auto-start pumps",
+ "props": [
+ { "p": "payload", "v": "go", "vt": "str" }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": true,
+ "onceDelay": "1.5",
+ "topic": "",
+ "x": 220,
+ "y": 720,
+ "wires": [
+ [
+ "fn_setup_fanout"
+ ]
+ ]
+ },
+ {
+ "id": "fn_setup_fanout",
+ "type": "function",
+ "z": "tab_mgc_basic",
+ "g": "grp_setup",
+ "name": "fan-out: virtualControl + startup → A/B/C",
+ "func": "// Fire two messages per pump: set.mode = virtualControl, then cmd.startup.\n// Each output is a message array — Node-RED dispatches them sequentially.\nconst setMode = { topic: 'set.mode', payload: 'virtualControl' };\nconst startup = { topic: 'cmd.startup', payload: {} };\nreturn [\n [setMode, startup], // → Pump A\n [setMode, startup], // → Pump B\n [setMode, startup], // → Pump C\n];\n",
+ "outputs": 3,
+ "timeout": 0,
+ "noerr": 0,
+ "initialize": "",
+ "finalize": "",
+ "libs": [],
+ "x": 480,
+ "y": 720,
+ "wires": [
+ [
+ "rm_basic_pump_a"
+ ],
+ [
+ "rm_basic_pump_b"
+ ],
+ [
+ "rm_basic_pump_c"
+ ]
+ ]
+ },
+ {
+ "id": "rm_basic_pump_a",
+ "type": "rotatingMachine",
+ "z": "tab_mgc_basic",
+ "g": "grp_pump_a",
+ "name": "Pump A",
+ "speed": "1",
+ "startup": "2",
+ "warmup": "1",
+ "shutdown": "2",
+ "cooldown": "1",
+ "movementMode": "staticspeed",
+ "machineCurve": "",
+ "uuid": "mgc-basic-pump-a",
+ "supplier": "hidrostal",
+ "category": "pump",
+ "assetType": "pump-centrifugal",
+ "model": "hidrostal-H05K-S03R",
+ "unit": "m3/h",
+ "curvePressureUnit": "mbar",
+ "curveFlowUnit": "m3/h",
+ "curvePowerUnit": "kW",
+ "curveControlUnit": "%",
+ "enableLog": false,
+ "logLevel": "info",
+ "positionVsParent": "atEquipment",
+ "positionIcon": "",
+ "hasDistance": false,
+ "distance": "",
+ "distanceUnit": "m",
+ "distanceDescription": "",
+ "x": 760,
+ "y": 240,
+ "wires": [
+ [],
+ [],
+ [
+ "mgc_basic_node"
+ ]
+ ]
+ },
+ {
+ "id": "rm_basic_pump_b",
+ "type": "rotatingMachine",
+ "z": "tab_mgc_basic",
+ "g": "grp_pump_b",
+ "name": "Pump B",
+ "speed": "1",
+ "startup": "2",
+ "warmup": "1",
+ "shutdown": "2",
+ "cooldown": "1",
+ "movementMode": "staticspeed",
+ "machineCurve": "",
+ "uuid": "mgc-basic-pump-b",
+ "supplier": "hidrostal",
+ "category": "pump",
+ "assetType": "pump-centrifugal",
+ "model": "hidrostal-H05K-S03R",
+ "unit": "m3/h",
+ "curvePressureUnit": "mbar",
+ "curveFlowUnit": "m3/h",
+ "curvePowerUnit": "kW",
+ "curveControlUnit": "%",
+ "enableLog": false,
+ "logLevel": "info",
+ "positionVsParent": "atEquipment",
+ "positionIcon": "",
+ "hasDistance": false,
+ "distance": "",
+ "distanceUnit": "m",
+ "distanceDescription": "",
+ "x": 760,
+ "y": 420,
+ "wires": [
+ [],
+ [],
+ [
+ "mgc_basic_node"
+ ]
+ ]
+ },
+ {
+ "id": "rm_basic_pump_c",
+ "type": "rotatingMachine",
+ "z": "tab_mgc_basic",
+ "g": "grp_pump_c",
+ "name": "Pump C",
+ "speed": "1",
+ "startup": "2",
+ "warmup": "1",
+ "shutdown": "2",
+ "cooldown": "1",
+ "movementMode": "staticspeed",
+ "machineCurve": "",
+ "uuid": "mgc-basic-pump-c",
+ "supplier": "hidrostal",
+ "category": "pump",
+ "assetType": "pump-centrifugal",
+ "model": "hidrostal-H05K-S03R",
+ "unit": "m3/h",
+ "curvePressureUnit": "mbar",
+ "curveFlowUnit": "m3/h",
+ "curvePowerUnit": "kW",
+ "curveControlUnit": "%",
+ "enableLog": false,
+ "logLevel": "info",
+ "positionVsParent": "atEquipment",
+ "positionIcon": "",
+ "hasDistance": false,
+ "distance": "",
+ "distanceUnit": "m",
+ "distanceDescription": "",
+ "x": 760,
+ "y": 600,
+ "wires": [
+ [],
+ [],
+ [
+ "mgc_basic_node"
+ ]
+ ]
+ },
+ {
+ "id": "mgc_basic_node",
+ "type": "machineGroupControl",
+ "z": "tab_mgc_basic",
+ "g": "grp_mgc_unit",
+ "name": "Machine Group",
+ "processOutputFormat": "process",
+ "dbaseOutputFormat": "influxdb",
+ "mode": "optimalControl",
+ "scaling": "normalized",
+ "uuid": "",
+ "supplier": "",
+ "category": "",
+ "assetType": "",
+ "model": "",
+ "unit": "",
+ "enableLog": false,
+ "logLevel": "info",
+ "positionVsParent": "atEquipment",
+ "positionIcon": "",
+ "hasDistance": false,
+ "distance": "",
+ "distanceUnit": "m",
+ "distanceDescription": "",
+ "x": 1050,
+ "y": 420,
+ "wires": [
+ [
+ "dbg_port0"
+ ],
+ [
+ "dbg_port1"
+ ],
+ [
+ "dbg_port2"
+ ]
+ ]
+ },
+ {
+ "id": "dbg_port0",
+ "type": "debug",
+ "z": "tab_mgc_basic",
+ "g": "grp_dbg",
+ "name": "Port 0: Process",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "payload",
+ "targetType": "msg",
+ "x": 1340,
+ "y": 380,
+ "wires": []
+ },
+ {
+ "id": "dbg_port1",
+ "type": "debug",
+ "z": "tab_mgc_basic",
+ "g": "grp_dbg",
+ "name": "Port 1: InfluxDB",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "true",
+ "targetType": "full",
+ "x": 1340,
+ "y": 440,
+ "wires": []
+ },
+ {
+ "id": "dbg_port2",
+ "type": "debug",
+ "z": "tab_mgc_basic",
+ "g": "grp_dbg",
+ "name": "Port 2: Parent reg",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "true",
+ "targetType": "full",
+ "x": 1350,
+ "y": 500,
+ "wires": []
+ },
+ {
+ "id": "mgc_global_cfg",
+ "type": "global-config",
+ "env": [],
+ "modules": {
+ "EVOLV": "1.0.29"
+ }
+ }
+]
diff --git a/examples/02-Dashboard.json b/examples/02-Dashboard.json
new file mode 100644
index 0000000..1b04f22
--- /dev/null
+++ b/examples/02-Dashboard.json
@@ -0,0 +1,1205 @@
+[
+ {
+ "id": "tab_mgc_dash",
+ "type": "tab",
+ "label": "MGC - Dashboard",
+ "disabled": false,
+ "info": "Tier 2: one machineGroupControl coordinating three rotatingMachine pumps, driven by a FlowFuse dashboard. Mode + scaling buttons, demand slider, live status rows, three trend charts, and a raw-output table."
+ },
+ {
+ "id": "grp_mgc_unit",
+ "type": "group",
+ "z": "tab_mgc_dash",
+ "name": "Machine Group (Unit)",
+ "style": {
+ "label": true,
+ "stroke": "#000000",
+ "fill": "#50a8d9",
+ "fill-opacity": "0.10"
+ },
+ "nodes": [
+ "mgc_dash_node"
+ ],
+ "x": 974,
+ "y": 379,
+ "w": 152,
+ "h": 122
+ },
+ {
+ "id": "grp_pump_a",
+ "type": "group",
+ "z": "tab_mgc_dash",
+ "name": "Pump A (Equipment)",
+ "style": {
+ "label": true,
+ "stroke": "#000000",
+ "fill": "#86bbdd",
+ "fill-opacity": "0.10"
+ },
+ "nodes": [
+ "rm_dash_pump_a"
+ ],
+ "x": 694,
+ "y": 219,
+ "w": 142,
+ "h": 82
+ },
+ {
+ "id": "grp_pump_b",
+ "type": "group",
+ "z": "tab_mgc_dash",
+ "name": "Pump B (Equipment)",
+ "style": {
+ "label": true,
+ "stroke": "#000000",
+ "fill": "#86bbdd",
+ "fill-opacity": "0.10"
+ },
+ "nodes": [
+ "rm_dash_pump_b"
+ ],
+ "x": 694,
+ "y": 399,
+ "w": 142,
+ "h": 82
+ },
+ {
+ "id": "grp_pump_c",
+ "type": "group",
+ "z": "tab_mgc_dash",
+ "name": "Pump C (Equipment)",
+ "style": {
+ "label": true,
+ "stroke": "#000000",
+ "fill": "#86bbdd",
+ "fill-opacity": "0.10"
+ },
+ "nodes": [
+ "rm_dash_pump_c"
+ ],
+ "x": 694,
+ "y": 579,
+ "w": 142,
+ "h": 82
+ },
+ {
+ "id": "grp_drv_mode",
+ "type": "group",
+ "z": "tab_mgc_dash",
+ "name": "1. Control mode",
+ "style": {
+ "stroke": "#666666",
+ "fill": "#ffdf7f",
+ "fill-opacity": "0.15",
+ "label": true,
+ "color": "#333333"
+ },
+ "nodes": [
+ "ui_btn_mode_optimal",
+ "ui_btn_mode_priority"
+ ],
+ "x": 94,
+ "y": 99,
+ "w": 312,
+ "h": 122
+ },
+ {
+ "id": "grp_drv_scaling",
+ "type": "group",
+ "z": "tab_mgc_dash",
+ "name": "2. Scaling",
+ "style": {
+ "stroke": "#666666",
+ "fill": "#ffdf7f",
+ "fill-opacity": "0.15",
+ "label": true,
+ "color": "#333333"
+ },
+ "nodes": [
+ "ui_btn_scaling_norm",
+ "ui_btn_scaling_abs"
+ ],
+ "x": 94,
+ "y": 259,
+ "w": 312,
+ "h": 122
+ },
+ {
+ "id": "grp_drv_demand",
+ "type": "group",
+ "z": "tab_mgc_dash",
+ "name": "3. Operator demand",
+ "style": {
+ "stroke": "#666666",
+ "fill": "#ffdf7f",
+ "fill-opacity": "0.15",
+ "label": true,
+ "color": "#333333"
+ },
+ "nodes": [
+ "ui_slider_demand",
+ "ui_btn_demand_stop"
+ ],
+ "x": 94,
+ "y": 419,
+ "w": 312,
+ "h": 122
+ },
+ {
+ "id": "grp_setup",
+ "type": "group",
+ "z": "tab_mgc_dash",
+ "name": "Setup — once on deploy + manual re-init",
+ "style": {
+ "stroke": "#666666",
+ "fill": "#dddddd",
+ "fill-opacity": "0.20",
+ "label": true,
+ "color": "#333333"
+ },
+ "nodes": [
+ "inj_setup_start",
+ "ui_btn_setup_init",
+ "fn_setup_fanout"
+ ],
+ "x": 94,
+ "y": 579,
+ "w": 532,
+ "h": 122
+ },
+ {
+ "id": "grp_status_panel",
+ "type": "group",
+ "z": "tab_mgc_dash",
+ "name": "Live status, trends, raw output",
+ "style": {
+ "stroke": "#666666",
+ "fill": "#bde0fe",
+ "fill-opacity": "0.20",
+ "label": true,
+ "color": "#333333"
+ },
+ "nodes": [
+ "fn_status_split",
+ "ui_txt_mode",
+ "ui_txt_scaling",
+ "ui_txt_flow",
+ "ui_txt_power",
+ "ui_txt_capacity",
+ "ui_txt_machines",
+ "ui_txt_bep",
+ "ui_chart_flow",
+ "ui_chart_power",
+ "ui_chart_bep",
+ "ui_tpl_raw"
+ ],
+ "x": 1234,
+ "y": 79,
+ "w": 712,
+ "h": 642
+ },
+ {
+ "id": "grp_dbg",
+ "type": "group",
+ "z": "tab_mgc_dash",
+ "name": "Debug outputs (sidebar)",
+ "style": {
+ "stroke": "#666666",
+ "fill": "#d1d1d1",
+ "fill-opacity": "0.2",
+ "label": true,
+ "color": "#333333"
+ },
+ "nodes": [
+ "dbg_port0",
+ "dbg_port1",
+ "dbg_port2"
+ ],
+ "x": 1234,
+ "y": 759,
+ "w": 252,
+ "h": 202
+ },
+ {
+ "id": "cmt_title",
+ "type": "comment",
+ "z": "tab_mgc_dash",
+ "name": "MGC — Dashboard (Tier 2)",
+ "info": "Same command surface as the Basic flow, driven by a FlowFuse dashboard.\n\nOpen /dashboard/mgc-basic after deploy.\n\nCONTROLS panel\n- Mode: optimalControl / priorityControl → set.mode\n- Scaling: normalized (0–100 %) / absolute (m³/h) → set.scaling\n- Demand slider 0–100 → set.demand (interpretation depends on scaling)\n- Stop button (set.demand = 0) and Initialize pumps button\n\nSTATUS panel\n- Mode / Scaling / Total flow / Total power / Capacity (Qmin–Qmax) / Active machines / BEP distance (rel %)\n\nTRENDS panel\n- Flow (m³/h) — predicted aggregate vs capacity\n- Power (kW)\n- BEP distance (rel %)\n\nRAW OUTPUT panel\n- Full key/value dump of the latest MGC Port 0 cache (sorted).\n\nPORTS (preserved for inspection)\n- Port 0: process output (changed fields only)\n- Port 1: InfluxDB-shaped {measurement, fields, tags, timestamp}\n- Port 2: parent registration (when wired into a pumpingStation)",
+ "x": 1100,
+ "y": 280,
+ "wires": []
+ },
+ {
+ "id": "ui_base_mgc",
+ "type": "ui-base",
+ "name": "EVOLV Demo",
+ "path": "/dashboard",
+ "appIcon": "",
+ "includeClientData": true,
+ "acceptsClientConfig": [
+ "ui-notification",
+ "ui-control"
+ ],
+ "showPathInSidebar": false,
+ "headerContent": "page",
+ "navigationStyle": "default",
+ "titleBarStyle": "default"
+ },
+ {
+ "id": "ui_theme_mgc",
+ "type": "ui-theme",
+ "name": "EVOLV Basic Theme",
+ "colors": {
+ "surface": "#ffffff",
+ "primary": "#50a8d9",
+ "bgPage": "#eeeeee",
+ "groupBg": "#ffffff",
+ "groupOutline": "#cccccc"
+ },
+ "sizes": {
+ "density": "default",
+ "pagePadding": "14px",
+ "groupGap": "14px",
+ "groupBorderRadius": "6px",
+ "widgetGap": "12px"
+ }
+ },
+ {
+ "id": "ui_page_mgc",
+ "type": "ui-page",
+ "name": "MGC Basic",
+ "ui": "ui_base_mgc",
+ "path": "/mgc-basic",
+ "icon": "settings-input-component",
+ "layout": "grid",
+ "theme": "ui_theme_mgc",
+ "breakpoints": [
+ {
+ "name": "Default",
+ "px": "0",
+ "cols": "12"
+ }
+ ],
+ "order": 1,
+ "className": ""
+ },
+ {
+ "id": "ui_group_ctrl",
+ "type": "ui-group",
+ "name": "Controls",
+ "page": "ui_page_mgc",
+ "width": "6",
+ "height": "1",
+ "order": 1,
+ "showTitle": true,
+ "className": ""
+ },
+ {
+ "id": "ui_group_status",
+ "type": "ui-group",
+ "name": "Status",
+ "page": "ui_page_mgc",
+ "width": "6",
+ "height": "1",
+ "order": 2,
+ "showTitle": true,
+ "className": ""
+ },
+ {
+ "id": "ui_group_trends",
+ "type": "ui-group",
+ "name": "Trends",
+ "page": "ui_page_mgc",
+ "width": "12",
+ "height": "1",
+ "order": 3,
+ "showTitle": true,
+ "className": ""
+ },
+ {
+ "id": "ui_group_raw",
+ "type": "ui-group",
+ "name": "Raw output (Port 0 cache)",
+ "page": "ui_page_mgc",
+ "width": "12",
+ "height": "1",
+ "order": 4,
+ "showTitle": true,
+ "className": ""
+ },
+ {
+ "id": "ui_btn_mode_optimal",
+ "type": "ui-button",
+ "z": "tab_mgc_dash",
+ "g": "grp_drv_mode",
+ "group": "ui_group_ctrl",
+ "name": "Mode: optimalControl",
+ "label": "Mode: optimalControl",
+ "order": 1,
+ "width": "3",
+ "height": "1",
+ "emulateClick": false,
+ "tooltip": "Best-combination optimiser (BEP-Gravitation / NCog)",
+ "color": "",
+ "bgcolor": "",
+ "icon": "auto_fix_high",
+ "payload": "optimalControl",
+ "payloadType": "str",
+ "topic": "set.mode",
+ "topicType": "str",
+ "x": 260,
+ "y": 140,
+ "wires": [
+ [
+ "mgc_dash_node"
+ ]
+ ]
+ },
+ {
+ "id": "ui_btn_mode_priority",
+ "type": "ui-button",
+ "z": "tab_mgc_dash",
+ "g": "grp_drv_mode",
+ "group": "ui_group_ctrl",
+ "name": "Mode: priorityControl",
+ "label": "Mode: priorityControl",
+ "order": 2,
+ "width": "3",
+ "height": "1",
+ "emulateClick": false,
+ "tooltip": "Sequential equal-flow control by priority list",
+ "color": "",
+ "bgcolor": "",
+ "icon": "format_list_numbered",
+ "payload": "priorityControl",
+ "payloadType": "str",
+ "topic": "set.mode",
+ "topicType": "str",
+ "x": 260,
+ "y": 180,
+ "wires": [
+ [
+ "mgc_dash_node"
+ ]
+ ]
+ },
+ {
+ "id": "ui_btn_scaling_norm",
+ "type": "ui-button",
+ "z": "tab_mgc_dash",
+ "g": "grp_drv_scaling",
+ "group": "ui_group_ctrl",
+ "name": "Scaling: normalized",
+ "label": "Scaling: normalized",
+ "order": 3,
+ "width": "3",
+ "height": "1",
+ "emulateClick": false,
+ "tooltip": "Interpret set.demand as 0–100 % of group capacity",
+ "color": "",
+ "bgcolor": "",
+ "icon": "percent",
+ "payload": "normalized",
+ "payloadType": "str",
+ "topic": "set.scaling",
+ "topicType": "str",
+ "x": 260,
+ "y": 300,
+ "wires": [
+ [
+ "mgc_dash_node"
+ ]
+ ]
+ },
+ {
+ "id": "ui_btn_scaling_abs",
+ "type": "ui-button",
+ "z": "tab_mgc_dash",
+ "g": "grp_drv_scaling",
+ "group": "ui_group_ctrl",
+ "name": "Scaling: absolute",
+ "label": "Scaling: absolute",
+ "order": 4,
+ "width": "3",
+ "height": "1",
+ "emulateClick": false,
+ "tooltip": "Interpret set.demand as m³/h (capped by group min/max)",
+ "color": "",
+ "bgcolor": "",
+ "icon": "exposure",
+ "payload": "absolute",
+ "payloadType": "str",
+ "topic": "set.scaling",
+ "topicType": "str",
+ "x": 260,
+ "y": 340,
+ "wires": [
+ [
+ "mgc_dash_node"
+ ]
+ ]
+ },
+ {
+ "id": "ui_slider_demand",
+ "type": "ui-slider",
+ "z": "tab_mgc_dash",
+ "g": "grp_drv_demand",
+ "group": "ui_group_ctrl",
+ "name": "Demand",
+ "label": "Demand",
+ "order": 5,
+ "width": "6",
+ "height": "1",
+ "topic": "set.demand",
+ "topicType": "str",
+ "icon": "speed",
+ "iconType": "fa",
+ "min": 0,
+ "max": 100,
+ "step": 1,
+ "thumbLabel": "always",
+ "tickLabel": "true",
+ "showTicks": false,
+ "passthru": true,
+ "outs": "all",
+ "className": "",
+ "x": 260,
+ "y": 460,
+ "wires": [
+ [
+ "mgc_dash_node"
+ ]
+ ]
+ },
+ {
+ "id": "ui_btn_demand_stop",
+ "type": "ui-button",
+ "z": "tab_mgc_dash",
+ "g": "grp_drv_demand",
+ "group": "ui_group_ctrl",
+ "name": "Demand = 0 (Stop)",
+ "label": "Stop (demand = 0)",
+ "order": 6,
+ "width": "6",
+ "height": "1",
+ "emulateClick": false,
+ "tooltip": "Send set.demand = 0 — MGC calls turnOffAllMachines",
+ "color": "#ffffff",
+ "bgcolor": "#cc6600",
+ "icon": "stop",
+ "payload": "0",
+ "payloadType": "num",
+ "topic": "set.demand",
+ "topicType": "str",
+ "x": 260,
+ "y": 500,
+ "wires": [
+ [
+ "mgc_dash_node"
+ ]
+ ]
+ },
+ {
+ "id": "ui_btn_setup_init",
+ "type": "ui-button",
+ "z": "tab_mgc_dash",
+ "g": "grp_setup",
+ "group": "ui_group_ctrl",
+ "name": "Initialize pumps",
+ "label": "Initialize pumps (virtualControl + startup)",
+ "order": 7,
+ "width": "12",
+ "height": "1",
+ "emulateClick": false,
+ "tooltip": "Re-runs the once-on-deploy setup: virtualControl mode + cmd.startup on all three pumps",
+ "color": "",
+ "bgcolor": "",
+ "icon": "play_arrow",
+ "payload": "go",
+ "payloadType": "str",
+ "topic": "",
+ "topicType": "str",
+ "x": 260,
+ "y": 620,
+ "wires": [
+ [
+ "fn_setup_fanout"
+ ]
+ ]
+ },
+ {
+ "id": "inj_setup_start",
+ "type": "inject",
+ "z": "tab_mgc_dash",
+ "g": "grp_setup",
+ "name": "Auto-start pumps",
+ "props": [
+ { "p": "payload", "v": "go", "vt": "str" }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": true,
+ "onceDelay": "1.5",
+ "topic": "",
+ "x": 260,
+ "y": 660,
+ "wires": [
+ [
+ "fn_setup_fanout"
+ ]
+ ]
+ },
+ {
+ "id": "fn_setup_fanout",
+ "type": "function",
+ "z": "tab_mgc_dash",
+ "g": "grp_setup",
+ "name": "fan-out: virtualControl + startup → A/B/C",
+ "func": "// Fire two messages per pump: set.mode = virtualControl, then cmd.startup.\nconst setMode = { topic: 'set.mode', payload: 'virtualControl' };\nconst startup = { topic: 'cmd.startup', payload: {} };\nreturn [\n [setMode, startup], // → Pump A\n [setMode, startup], // → Pump B\n [setMode, startup], // → Pump C\n];\n",
+ "outputs": 3,
+ "timeout": 0,
+ "noerr": 0,
+ "initialize": "",
+ "finalize": "",
+ "libs": [],
+ "x": 500,
+ "y": 640,
+ "wires": [
+ [
+ "rm_dash_pump_a"
+ ],
+ [
+ "rm_dash_pump_b"
+ ],
+ [
+ "rm_dash_pump_c"
+ ]
+ ]
+ },
+ {
+ "id": "fn_status_split",
+ "type": "function",
+ "z": "tab_mgc_dash",
+ "g": "grp_status_panel",
+ "name": "fan-out Port 0 (status + charts + raw)",
+ "func": "// MGC Port 0 emits delta-only. Cache last-known so deltas never blank a\n// row, then fan out one msg per ui-text / ui-chart / ui-template slot.\nconst cache = context.get('cache') || {};\nconst p = msg.payload || {};\nfor (const k in p) cache[k] = p[k];\ncontext.set('cache', cache);\n\nconst num = (v, dp, unit) => {\n const n = +v;\n if (!Number.isFinite(n)) return '—';\n return n.toFixed(dp) + (unit ? ' ' + unit : '');\n};\n\nconst mode = cache.mode || '—';\nconst scaling = cache.scaling || '—';\nconst flow = cache['atEquipment_predicted_flow'];\nconst power = cache['atEquipment_predicted_power'];\nconst qMin = cache.flowCapacityMin;\nconst qMax = cache.flowCapacityMax;\nconst nAct = cache.machineCountActive;\nconst nTot = cache.machineCount;\nconst bepRel = cache.relDistFromPeak;\n\nconst chart = (topic, v) => Number.isFinite(+v) ? { topic, payload: +v } : null;\n\nconst rawRows = Object.keys(cache).sort().map((k) => {\n const v = cache[k];\n let display;\n if (v === null || v === undefined) display = '—';\n else if (typeof v === 'number') display = Number.isInteger(v) ? String(v) : v.toFixed(4);\n else display = String(v);\n return { key: k, value: display };\n});\n\nreturn [\n // 0–6: status text\n { payload: mode },\n { payload: scaling },\n { payload: num(flow, 1, 'm³/h') },\n { payload: num(power, 2, 'kW') },\n { payload: Number.isFinite(+qMax) ? `${num(qMin, 1)} – ${num(qMax, 1, 'm³/h')}` : '—' },\n { payload: Number.isFinite(+nAct) && Number.isFinite(+nTot) ? `${nAct} / ${nTot}` : '—' },\n { payload: num(bepRel, 1, '%') },\n // 7–9: charts (flow vs capacity / power / bep distance)\n chart('Flow', flow),\n chart('Capacity', qMax),\n chart('Power', power),\n chart('BEP rel%', bepRel),\n // 11: raw rows for the ui-template\n { payload: rawRows },\n];\n",
+ "outputs": 12,
+ "timeout": 0,
+ "noerr": 0,
+ "initialize": "",
+ "finalize": "",
+ "libs": [],
+ "x": 1300,
+ "y": 140,
+ "wires": [
+ [
+ "ui_txt_mode"
+ ],
+ [
+ "ui_txt_scaling"
+ ],
+ [
+ "ui_txt_flow"
+ ],
+ [
+ "ui_txt_power"
+ ],
+ [
+ "ui_txt_capacity"
+ ],
+ [
+ "ui_txt_machines"
+ ],
+ [
+ "ui_txt_bep"
+ ],
+ [
+ "ui_chart_flow"
+ ],
+ [
+ "ui_chart_flow"
+ ],
+ [
+ "ui_chart_power"
+ ],
+ [
+ "ui_chart_bep"
+ ],
+ [
+ "ui_tpl_raw"
+ ]
+ ]
+ },
+ {
+ "id": "ui_txt_mode",
+ "type": "ui-text",
+ "z": "tab_mgc_dash",
+ "g": "grp_status_panel",
+ "group": "ui_group_status",
+ "order": 1,
+ "width": "6",
+ "height": "1",
+ "name": "Mode",
+ "label": "Mode",
+ "format": "{{msg.payload}}",
+ "layout": "row-spread",
+ "style": false,
+ "font": "",
+ "fontSize": 14,
+ "color": "#1F4E79",
+ "x": 1560,
+ "y": 100,
+ "wires": []
+ },
+ {
+ "id": "ui_txt_scaling",
+ "type": "ui-text",
+ "z": "tab_mgc_dash",
+ "g": "grp_status_panel",
+ "group": "ui_group_status",
+ "order": 2,
+ "width": "6",
+ "height": "1",
+ "name": "Scaling",
+ "label": "Scaling",
+ "format": "{{msg.payload}}",
+ "layout": "row-spread",
+ "style": false,
+ "font": "",
+ "fontSize": 14,
+ "color": "#1F4E79",
+ "x": 1560,
+ "y": 140,
+ "wires": []
+ },
+ {
+ "id": "ui_txt_flow",
+ "type": "ui-text",
+ "z": "tab_mgc_dash",
+ "g": "grp_status_panel",
+ "group": "ui_group_status",
+ "order": 3,
+ "width": "6",
+ "height": "1",
+ "name": "Total flow",
+ "label": "Total flow",
+ "format": "{{msg.payload}}",
+ "layout": "row-spread",
+ "style": false,
+ "font": "",
+ "fontSize": 14,
+ "color": "#1F4E79",
+ "x": 1570,
+ "y": 180,
+ "wires": []
+ },
+ {
+ "id": "ui_txt_power",
+ "type": "ui-text",
+ "z": "tab_mgc_dash",
+ "g": "grp_status_panel",
+ "group": "ui_group_status",
+ "order": 4,
+ "width": "6",
+ "height": "1",
+ "name": "Total power",
+ "label": "Total power",
+ "format": "{{msg.payload}}",
+ "layout": "row-spread",
+ "style": false,
+ "font": "",
+ "fontSize": 14,
+ "color": "#1F4E79",
+ "x": 1580,
+ "y": 220,
+ "wires": []
+ },
+ {
+ "id": "ui_txt_capacity",
+ "type": "ui-text",
+ "z": "tab_mgc_dash",
+ "g": "grp_status_panel",
+ "group": "ui_group_status",
+ "order": 5,
+ "width": "6",
+ "height": "1",
+ "name": "Capacity (Qmin–Qmax)",
+ "label": "Capacity",
+ "format": "{{msg.payload}}",
+ "layout": "row-spread",
+ "style": false,
+ "font": "",
+ "fontSize": 14,
+ "color": "#1F4E79",
+ "x": 1620,
+ "y": 260,
+ "wires": []
+ },
+ {
+ "id": "ui_txt_machines",
+ "type": "ui-text",
+ "z": "tab_mgc_dash",
+ "g": "grp_status_panel",
+ "group": "ui_group_status",
+ "order": 6,
+ "width": "6",
+ "height": "1",
+ "name": "Machines (active / total)",
+ "label": "Machines",
+ "format": "{{msg.payload}}",
+ "layout": "row-spread",
+ "style": false,
+ "font": "",
+ "fontSize": 14,
+ "color": "#1F4E79",
+ "x": 1630,
+ "y": 300,
+ "wires": []
+ },
+ {
+ "id": "ui_txt_bep",
+ "type": "ui-text",
+ "z": "tab_mgc_dash",
+ "g": "grp_status_panel",
+ "group": "ui_group_status",
+ "order": 7,
+ "width": "6",
+ "height": "1",
+ "name": "BEP distance (rel)",
+ "label": "BEP distance",
+ "format": "{{msg.payload}}",
+ "layout": "row-spread",
+ "style": false,
+ "font": "",
+ "fontSize": 14,
+ "color": "#7D3C98",
+ "x": 1600,
+ "y": 340,
+ "wires": []
+ },
+ {
+ "id": "ui_chart_flow",
+ "type": "ui-chart",
+ "z": "tab_mgc_dash",
+ "g": "grp_status_panel",
+ "group": "ui_group_trends",
+ "name": "Flow vs capacity",
+ "label": "Flow (m³/h) — predicted vs capacity",
+ "order": 1,
+ "width": 6,
+ "height": 4,
+ "chartType": "line",
+ "category": "topic",
+ "categoryType": "msg",
+ "xAxisLabel": "time",
+ "xAxisType": "time",
+ "xAxisProperty": "",
+ "xAxisPropertyType": "timestamp",
+ "xAxisFormat": "",
+ "xAxisFormatType": "auto",
+ "yAxisLabel": "m³/h",
+ "yAxisProperty": "payload",
+ "yAxisPropertyType": "msg",
+ "xmin": "",
+ "xmax": "",
+ "ymin": "",
+ "ymax": "",
+ "removeOlder": "15",
+ "removeOlderUnit": "60",
+ "removeOlderPoints": "",
+ "bins": 10,
+ "action": "append",
+ "stackSeries": false,
+ "pointShape": "circle",
+ "pointRadius": 4,
+ "interpolation": "linear",
+ "showLegend": true,
+ "className": "",
+ "colors": [
+ "#0095FF",
+ "#cccccc",
+ "#FF7F0E",
+ "#2CA02C",
+ "#A347E1",
+ "#D62728",
+ "#FF9896",
+ "#9467BD",
+ "#C5B0D5"
+ ],
+ "textColor": [
+ "#666666"
+ ],
+ "textColorDefault": true,
+ "gridColor": [
+ "#e5e5e5"
+ ],
+ "gridColorDefault": true,
+ "x": 1600,
+ "y": 400,
+ "wires": []
+ },
+ {
+ "id": "ui_chart_power",
+ "type": "ui-chart",
+ "z": "tab_mgc_dash",
+ "g": "grp_status_panel",
+ "group": "ui_group_trends",
+ "name": "Power",
+ "label": "Power (kW)",
+ "order": 2,
+ "width": 6,
+ "height": 4,
+ "chartType": "line",
+ "category": "topic",
+ "categoryType": "msg",
+ "xAxisLabel": "time",
+ "xAxisType": "time",
+ "xAxisProperty": "",
+ "xAxisPropertyType": "timestamp",
+ "xAxisFormat": "",
+ "xAxisFormatType": "auto",
+ "yAxisLabel": "kW",
+ "yAxisProperty": "payload",
+ "yAxisPropertyType": "msg",
+ "xmin": "",
+ "xmax": "",
+ "ymin": "",
+ "ymax": "",
+ "removeOlder": "15",
+ "removeOlderUnit": "60",
+ "removeOlderPoints": "",
+ "bins": 10,
+ "action": "append",
+ "stackSeries": false,
+ "pointShape": "circle",
+ "pointRadius": 4,
+ "interpolation": "linear",
+ "showLegend": false,
+ "className": "",
+ "colors": [
+ "#2CA02C",
+ "#FF0000",
+ "#FF7F0E",
+ "#0095FF",
+ "#A347E1",
+ "#D62728",
+ "#FF9896",
+ "#9467BD",
+ "#C5B0D5"
+ ],
+ "textColor": [
+ "#666666"
+ ],
+ "textColorDefault": true,
+ "gridColor": [
+ "#e5e5e5"
+ ],
+ "gridColorDefault": true,
+ "x": 1580,
+ "y": 440,
+ "wires": []
+ },
+ {
+ "id": "ui_chart_bep",
+ "type": "ui-chart",
+ "z": "tab_mgc_dash",
+ "g": "grp_status_panel",
+ "group": "ui_group_trends",
+ "name": "BEP distance (rel %)",
+ "label": "BEP distance (rel %)",
+ "order": 3,
+ "width": 6,
+ "height": 4,
+ "chartType": "line",
+ "category": "topic",
+ "categoryType": "msg",
+ "xAxisLabel": "time",
+ "xAxisType": "time",
+ "xAxisProperty": "",
+ "xAxisPropertyType": "timestamp",
+ "xAxisFormat": "",
+ "xAxisFormatType": "auto",
+ "yAxisLabel": "%",
+ "yAxisProperty": "payload",
+ "yAxisPropertyType": "msg",
+ "xmin": "",
+ "xmax": "",
+ "ymin": "",
+ "ymax": "",
+ "removeOlder": "15",
+ "removeOlderUnit": "60",
+ "removeOlderPoints": "",
+ "bins": 10,
+ "action": "append",
+ "stackSeries": false,
+ "pointShape": "circle",
+ "pointRadius": 4,
+ "interpolation": "linear",
+ "showLegend": false,
+ "className": "",
+ "colors": [
+ "#A347E1",
+ "#FF0000",
+ "#FF7F0E",
+ "#2CA02C",
+ "#0095FF",
+ "#D62728",
+ "#FF9896",
+ "#9467BD",
+ "#C5B0D5"
+ ],
+ "textColor": [
+ "#666666"
+ ],
+ "textColorDefault": true,
+ "gridColor": [
+ "#e5e5e5"
+ ],
+ "gridColorDefault": true,
+ "x": 1610,
+ "y": 480,
+ "wires": []
+ },
+ {
+ "id": "ui_tpl_raw",
+ "type": "ui-template",
+ "z": "tab_mgc_dash",
+ "g": "grp_status_panel",
+ "group": "ui_group_raw",
+ "name": "Raw output table",
+ "order": 1,
+ "width": "12",
+ "height": "8",
+ "head": "",
+ "format": "\n \n
\n \n | {{ row.key }} | \n {{ row.value }} | \n
\n
\n
\n\n\n\n",
+ "storeOutMessages": true,
+ "passthru": true,
+ "resendOnRefresh": true,
+ "templateScope": "local",
+ "className": "",
+ "x": 1620,
+ "y": 520,
+ "wires": [
+ []
+ ]
+ },
+ {
+ "id": "rm_dash_pump_a",
+ "type": "rotatingMachine",
+ "z": "tab_mgc_dash",
+ "g": "grp_pump_a",
+ "name": "Pump A",
+ "speed": "1",
+ "startup": "2",
+ "warmup": "1",
+ "shutdown": "2",
+ "cooldown": "1",
+ "movementMode": "staticspeed",
+ "machineCurve": "",
+ "uuid": "mgc-dash-pump-a",
+ "supplier": "hidrostal",
+ "category": "pump",
+ "assetType": "pump-centrifugal",
+ "model": "hidrostal-H05K-S03R",
+ "unit": "m3/h",
+ "curvePressureUnit": "mbar",
+ "curveFlowUnit": "m3/h",
+ "curvePowerUnit": "kW",
+ "curveControlUnit": "%",
+ "enableLog": false,
+ "logLevel": "info",
+ "positionVsParent": "atEquipment",
+ "positionIcon": "",
+ "hasDistance": false,
+ "distance": "",
+ "distanceUnit": "m",
+ "distanceDescription": "",
+ "x": 760,
+ "y": 260,
+ "wires": [
+ [],
+ [],
+ [
+ "mgc_dash_node"
+ ]
+ ]
+ },
+ {
+ "id": "rm_dash_pump_b",
+ "type": "rotatingMachine",
+ "z": "tab_mgc_dash",
+ "g": "grp_pump_b",
+ "name": "Pump B",
+ "speed": "1",
+ "startup": "2",
+ "warmup": "1",
+ "shutdown": "2",
+ "cooldown": "1",
+ "movementMode": "staticspeed",
+ "machineCurve": "",
+ "uuid": "mgc-dash-pump-b",
+ "supplier": "hidrostal",
+ "category": "pump",
+ "assetType": "pump-centrifugal",
+ "model": "hidrostal-H05K-S03R",
+ "unit": "m3/h",
+ "curvePressureUnit": "mbar",
+ "curveFlowUnit": "m3/h",
+ "curvePowerUnit": "kW",
+ "curveControlUnit": "%",
+ "enableLog": false,
+ "logLevel": "info",
+ "positionVsParent": "atEquipment",
+ "positionIcon": "",
+ "hasDistance": false,
+ "distance": "",
+ "distanceUnit": "m",
+ "distanceDescription": "",
+ "x": 760,
+ "y": 440,
+ "wires": [
+ [],
+ [],
+ [
+ "mgc_dash_node"
+ ]
+ ]
+ },
+ {
+ "id": "rm_dash_pump_c",
+ "type": "rotatingMachine",
+ "z": "tab_mgc_dash",
+ "g": "grp_pump_c",
+ "name": "Pump C",
+ "speed": "1",
+ "startup": "2",
+ "warmup": "1",
+ "shutdown": "2",
+ "cooldown": "1",
+ "movementMode": "staticspeed",
+ "machineCurve": "",
+ "uuid": "mgc-dash-pump-c",
+ "supplier": "hidrostal",
+ "category": "pump",
+ "assetType": "pump-centrifugal",
+ "model": "hidrostal-H05K-S03R",
+ "unit": "m3/h",
+ "curvePressureUnit": "mbar",
+ "curveFlowUnit": "m3/h",
+ "curvePowerUnit": "kW",
+ "curveControlUnit": "%",
+ "enableLog": false,
+ "logLevel": "info",
+ "positionVsParent": "atEquipment",
+ "positionIcon": "",
+ "hasDistance": false,
+ "distance": "",
+ "distanceUnit": "m",
+ "distanceDescription": "",
+ "x": 760,
+ "y": 620,
+ "wires": [
+ [],
+ [],
+ [
+ "mgc_dash_node"
+ ]
+ ]
+ },
+ {
+ "id": "mgc_dash_node",
+ "type": "machineGroupControl",
+ "z": "tab_mgc_dash",
+ "g": "grp_mgc_unit",
+ "name": "Machine Group",
+ "processOutputFormat": "process",
+ "dbaseOutputFormat": "influxdb",
+ "mode": "optimalControl",
+ "scaling": "normalized",
+ "uuid": "",
+ "supplier": "",
+ "category": "",
+ "assetType": "",
+ "model": "",
+ "unit": "",
+ "enableLog": false,
+ "logLevel": "info",
+ "positionVsParent": "atEquipment",
+ "positionIcon": "",
+ "hasDistance": false,
+ "distance": "",
+ "distanceUnit": "m",
+ "distanceDescription": "",
+ "x": 1050,
+ "y": 440,
+ "wires": [
+ [
+ "dbg_port0",
+ "fn_status_split"
+ ],
+ [
+ "dbg_port1"
+ ],
+ [
+ "dbg_port2"
+ ]
+ ]
+ },
+ {
+ "id": "dbg_port0",
+ "type": "debug",
+ "z": "tab_mgc_dash",
+ "g": "grp_dbg",
+ "name": "Port 0: Process",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "payload",
+ "targetType": "msg",
+ "x": 1340,
+ "y": 800,
+ "wires": []
+ },
+ {
+ "id": "dbg_port1",
+ "type": "debug",
+ "z": "tab_mgc_dash",
+ "g": "grp_dbg",
+ "name": "Port 1: InfluxDB",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "true",
+ "targetType": "full",
+ "x": 1340,
+ "y": 860,
+ "wires": []
+ },
+ {
+ "id": "dbg_port2",
+ "type": "debug",
+ "z": "tab_mgc_dash",
+ "g": "grp_dbg",
+ "name": "Port 2: Parent reg",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "true",
+ "targetType": "full",
+ "x": 1350,
+ "y": 920,
+ "wires": []
+ },
+ {
+ "id": "mgc_global_cfg",
+ "type": "global-config",
+ "env": [],
+ "modules": {
+ "EVOLV": "1.0.29"
+ }
+ }
+]
diff --git a/examples/README.md b/examples/README.md
index f94a51a..ef00b45 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -1,8 +1,51 @@
-# machineGroupControl Example Flows
+# machineGroupControl - Example Flows
-Import-ready Node-RED examples for machineGroupControl.
+Import-ready Node-RED examples for `machineGroupControl` (MGC). MGC is not a standalone node — it needs at least one `rotatingMachine` child to dispatch demand to. Both flows below ship three child pumps.
## Files
-- basic.flow.json
-- integration.flow.json
-- edge.flow.json
+
+| File | Tier | What it shows |
+|---|---|---|
+| `01-Basic.json` | 1 | One MGC + three `rotatingMachine` pumps driven by inject buttons. Setup once-fires `virtualControl` + `cmd.startup` on all three pumps; mode / scaling / demand are then driven by buttons. |
+| `02-Dashboard.json` | 2 | Same command surface driven by a FlowFuse Dashboard 2.0 page — mode + scaling buttons, demand slider, live status rows, three trend charts, and a raw-output table. |
+
+## Prerequisites
+
+- Node-RED with the EVOLV package installed (`machineGroupControl` and `rotatingMachine` registered).
+- For `02-Dashboard.json`: `@flowfuse/node-red-dashboard` (Dashboard 2.0).
+
+## Load a flow
+
+```bash
+curl -X POST -H 'Content-Type: application/json' \
+ --data @nodes/machineGroupControl/examples/01-Basic.json \
+ http://localhost:1880/flows
+```
+
+Or in the editor: Menu → Import → drag the file → Import.
+
+## Canonical command surface
+
+| Topic | Aliases | Payload | What it does |
+|---|---|---|---|
+| `set.mode` | `setMode` | `"optimalControl"`, `"priorityControl"`, `"prioritypercentagecontrol"`, `"maintenance"` | Switch dispatch strategy |
+| `set.scaling` | `setScaling` | `"normalized"`, `"absolute"` | Interpret demand as 0–100 % vs m³/h |
+| `set.demand` | `Qd` | number | Operator demand setpoint |
+| `child.register` | `registerChild` | child node id (string) | Manually register a child (Port 2 wiring does this automatically) |
+
+## 01-Basic — what to try
+
+1. Deploy. After ~1.5 s the Setup group auto-fires, putting all three pumps in `virtualControl` mode + sending `cmd.startup` to each.
+2. Click `set.demand = 50 %` — MGC's `optimalControl` picks the best pump combination by BEP-gravitation and dispatches `flowmovement` to the selected pumps.
+3. Click `set.demand = 100 %` — MGC switches to a higher combination, possibly engaging an extra pump.
+4. Switch mode to `priorityControl` and try the same demands — pumps now run equal-flow by priority order.
+5. Switch scaling to `absolute` — set.demand is now interpreted as m³/h (capped at the group min / max).
+6. `set.demand = 0` — MGC calls `turnOffAllMachines`, all pumps shut down.
+
+## 02-Dashboard — what to try
+
+1. Deploy → open `http://localhost:1880/dashboard/mgc-basic`.
+2. The dashboard auto-initialises the pumps; the `Initialize pumps` button on the page re-runs the setup manually.
+3. Drag the **Demand** slider — MGC dispatches and the Flow / Power / BEP charts react.
+4. Switch modes and scalings via the buttons; the Mode / Scaling rows in the Status panel reflect the change.
+5. Inspect the **Raw output** table for the full Port 0 surface (every field MGC emits, including `flowCapacityMax`, `machineCountActive`, `absDistFromPeak`, `relDistFromPeak`).
diff --git a/examples/basic.flow.json b/examples/basic.flow.json
deleted file mode 100644
index ad5b77f..0000000
--- a/examples/basic.flow.json
+++ /dev/null
@@ -1,6 +0,0 @@
-[
- {"id":"machineGroupControl_basic_tab","type":"tab","label":"machineGroupControl basic","disabled":false,"info":"machineGroupControl basic example"},
- {"id":"machineGroupControl_basic_node","type":"machineGroupControl","z":"machineGroupControl_basic_tab","name":"machineGroupControl basic","x":420,"y":180,"wires":[["machineGroupControl_basic_dbg"]]},
- {"id":"machineGroupControl_basic_inj","type":"inject","z":"machineGroupControl_basic_tab","name":"basic trigger","props":[{"p":"topic","vt":"str"},{"p":"payload","vt":"str"}],"topic":"ping","payload":"1","payloadType":"str","x":160,"y":180,"wires":[["machineGroupControl_basic_node"]]},
- {"id":"machineGroupControl_basic_dbg","type":"debug","z":"machineGroupControl_basic_tab","name":"machineGroupControl basic debug","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":660,"y":180,"wires":[]}
-]
diff --git a/examples/edge.flow.json b/examples/edge.flow.json
deleted file mode 100644
index 3f7839d..0000000
--- a/examples/edge.flow.json
+++ /dev/null
@@ -1,6 +0,0 @@
-[
- {"id":"machineGroupControl_edge_tab","type":"tab","label":"machineGroupControl edge","disabled":false,"info":"machineGroupControl edge example"},
- {"id":"machineGroupControl_edge_node","type":"machineGroupControl","z":"machineGroupControl_edge_tab","name":"machineGroupControl edge","x":420,"y":180,"wires":[["machineGroupControl_edge_dbg"]]},
- {"id":"machineGroupControl_edge_inj","type":"inject","z":"machineGroupControl_edge_tab","name":"unknown topic","props":[{"p":"topic","vt":"str"},{"p":"payload","vt":"str"}],"topic":"doesNotExist","payload":"x","payloadType":"str","x":170,"y":180,"wires":[["machineGroupControl_edge_node"]]},
- {"id":"machineGroupControl_edge_dbg","type":"debug","z":"machineGroupControl_edge_tab","name":"machineGroupControl edge debug","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":660,"y":180,"wires":[]}
-]
diff --git a/examples/integration.flow.json b/examples/integration.flow.json
deleted file mode 100644
index 58da112..0000000
--- a/examples/integration.flow.json
+++ /dev/null
@@ -1,6 +0,0 @@
-[
- {"id":"machineGroupControl_int_tab","type":"tab","label":"machineGroupControl integration","disabled":false,"info":"machineGroupControl integration example"},
- {"id":"machineGroupControl_int_node","type":"machineGroupControl","z":"machineGroupControl_int_tab","name":"machineGroupControl integration","x":420,"y":180,"wires":[["machineGroupControl_int_dbg"]]},
- {"id":"machineGroupControl_int_inj","type":"inject","z":"machineGroupControl_int_tab","name":"registerChild","props":[{"p":"topic","vt":"str"},{"p":"payload","vt":"str"}],"topic":"registerChild","payload":"example-child-id","payloadType":"str","x":170,"y":180,"wires":[["machineGroupControl_int_node"]]},
- {"id":"machineGroupControl_int_dbg","type":"debug","z":"machineGroupControl_int_tab","name":"machineGroupControl integration debug","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":680,"y":180,"wires":[]}
-]
diff --git a/mgc.html b/mgc.html
index 5a0344d..5015782 100644
--- a/mgc.html
+++ b/mgc.html
@@ -21,6 +21,10 @@
processOutputFormat: { value: "process" },
dbaseOutputFormat: { value: "influxdb" },
+ // Control strategy
+ mode: { value: "optimalControl" }, // optimalControl | priorityControl | prioritypercentagecontrol | maintenance
+ scaling: { value: "normalized" }, // normalized (0–100 %) | absolute (m³/h)
+
//define asset properties
uuid: { value: "" },
supplier: { value: "" },
@@ -40,7 +44,7 @@
distance: { value: 0 },
distanceUnit: { value: "m" },
distanceDescription: { value: "" }
-
+
},
inputs:1,
outputs:3,
@@ -84,6 +88,24 @@