wip: pre-ship-it state — example dashboard tweaks
This commit is contained in:
@@ -166,8 +166,8 @@
|
|||||||
"id": "b30af582f935bcb7",
|
"id": "b30af582f935bcb7",
|
||||||
"type": "comment",
|
"type": "comment",
|
||||||
"z": "77f00aef1c966167",
|
"z": "77f00aef1c966167",
|
||||||
"name": "PumpingStation — Dashboard (Tier 2)",
|
"name": "PumpingStation \u2014 Dashboard (Tier 2)",
|
||||||
"info": "Same command surface as the Basic example, driven by a FlowFuse dashboard.\n\nOpen /dashboard/pumpingstation-basic after deploy.\n\nCONTROLS panel\n- Mode buttons → set.mode (manual / levelbased)\n- Inflow / Outflow buttons → set.inflow / set.outflow (60 / 80 m³/h)\n- Demand button → set.demand (40 m³/h, manual mode only)\n- Calibrate buttons → cmd.calibrate.volume / cmd.calibrate.level\n\nSTATUS panel\n- 7 text rows: Mode, Direction, Level, Volume, Volume %, percControl, Manual demand\n\nTRENDS panel\n- 4 charts: Level (m), Volume (m³), Volume %, Flow (in/out/net m³/h)\n\nRAW OUTPUT panel\n- Full key/value dump of the latest Port 0 cache (sorted). Shows every field the node emits including basin geometry, safety thresholds, predicted overflow/underflow.\n\nThe fan-out function caches last-known values so delta-only Port 0 updates never blank a row.",
|
"info": "Same command surface as the Basic example, driven by a FlowFuse dashboard.\n\nOpen /dashboard/pumpingstation-basic after deploy.\n\nCONTROLS panel\n- Mode buttons \u2192 set.mode (manual / levelbased)\n- Inflow / Outflow buttons \u2192 set.inflow / set.outflow (60 / 80 m\u00b3/h)\n- Demand button \u2192 set.demand (40 m\u00b3/h, manual mode only)\n- Calibrate buttons \u2192 cmd.calibrate.volume / cmd.calibrate.level\n\nSTATUS panel\n- 7 text rows: Mode, Direction, Level, Volume, Volume %, percControl, Manual demand\n\nTRENDS panel\n- 4 charts: Level (m), Volume (m\u00b3), Volume %, Flow (in/out/net m\u00b3/h)\n\nRAW OUTPUT panel\n- Full key/value dump of the latest Port 0 cache (sorted). Shows every field the node emits including basin geometry, safety thresholds, predicted overflow/underflow.\n\nThe fan-out function caches last-known values so delta-only Port 0 updates never blank a row.",
|
||||||
"x": 660,
|
"x": 660,
|
||||||
"y": 320,
|
"y": 320,
|
||||||
"wires": []
|
"wires": []
|
||||||
@@ -332,13 +332,13 @@
|
|||||||
"z": "77f00aef1c966167",
|
"z": "77f00aef1c966167",
|
||||||
"g": "a9f9b38b0e00c1d7",
|
"g": "a9f9b38b0e00c1d7",
|
||||||
"group": "ui_group_ctrl",
|
"group": "ui_group_ctrl",
|
||||||
"name": "Inflow 60 m³/h",
|
"name": "Inflow 60 m\u00b3/h",
|
||||||
"label": "Inflow 60 m³/h",
|
"label": "Inflow 60 m\u00b3/h",
|
||||||
"order": 3,
|
"order": 3,
|
||||||
"width": "3",
|
"width": "3",
|
||||||
"height": "1",
|
"height": "1",
|
||||||
"emulateClick": false,
|
"emulateClick": false,
|
||||||
"tooltip": "Push a measured inflow of 60 m³/h into the basin balance",
|
"tooltip": "Push a measured inflow of 60 m\u00b3/h into the basin balance",
|
||||||
"color": "",
|
"color": "",
|
||||||
"bgcolor": "",
|
"bgcolor": "",
|
||||||
"icon": "south",
|
"icon": "south",
|
||||||
@@ -360,13 +360,13 @@
|
|||||||
"z": "77f00aef1c966167",
|
"z": "77f00aef1c966167",
|
||||||
"g": "a9f9b38b0e00c1d7",
|
"g": "a9f9b38b0e00c1d7",
|
||||||
"group": "ui_group_ctrl",
|
"group": "ui_group_ctrl",
|
||||||
"name": "Outflow 80 m³/h",
|
"name": "Outflow 80 m\u00b3/h",
|
||||||
"label": "Outflow 80 m³/h",
|
"label": "Outflow 80 m\u00b3/h",
|
||||||
"order": 4,
|
"order": 4,
|
||||||
"width": "3",
|
"width": "3",
|
||||||
"height": "1",
|
"height": "1",
|
||||||
"emulateClick": false,
|
"emulateClick": false,
|
||||||
"tooltip": "Push a measured outflow of 80 m³/h into the basin balance",
|
"tooltip": "Push a measured outflow of 80 m\u00b3/h into the basin balance",
|
||||||
"color": "",
|
"color": "",
|
||||||
"bgcolor": "",
|
"bgcolor": "",
|
||||||
"icon": "north",
|
"icon": "north",
|
||||||
@@ -388,13 +388,13 @@
|
|||||||
"z": "77f00aef1c966167",
|
"z": "77f00aef1c966167",
|
||||||
"g": "42bf82c87d05f498",
|
"g": "42bf82c87d05f498",
|
||||||
"group": "ui_group_ctrl",
|
"group": "ui_group_ctrl",
|
||||||
"name": "Demand 40 m³/h",
|
"name": "Demand 40 m\u00b3/h",
|
||||||
"label": "Demand 40 m³/h (manual)",
|
"label": "Demand 40 m\u00b3/h (manual)",
|
||||||
"order": 5,
|
"order": 5,
|
||||||
"width": "6",
|
"width": "6",
|
||||||
"height": "1",
|
"height": "1",
|
||||||
"emulateClick": false,
|
"emulateClick": false,
|
||||||
"tooltip": "Operator outflow demand — only forwarded when mode = manual",
|
"tooltip": "Operator outflow demand \u2014 only forwarded when mode = manual",
|
||||||
"color": "",
|
"color": "",
|
||||||
"bgcolor": "",
|
"bgcolor": "",
|
||||||
"icon": "speed",
|
"icon": "speed",
|
||||||
@@ -416,13 +416,13 @@
|
|||||||
"z": "77f00aef1c966167",
|
"z": "77f00aef1c966167",
|
||||||
"g": "234bdce20170061a",
|
"g": "234bdce20170061a",
|
||||||
"group": "ui_group_ctrl",
|
"group": "ui_group_ctrl",
|
||||||
"name": "Calibrate V=25 m³",
|
"name": "Calibrate V=25 m\u00b3",
|
||||||
"label": "Calibrate V = 25 m³",
|
"label": "Calibrate V = 25 m\u00b3",
|
||||||
"order": 6,
|
"order": 6,
|
||||||
"width": "3",
|
"width": "3",
|
||||||
"height": "1",
|
"height": "1",
|
||||||
"emulateClick": false,
|
"emulateClick": false,
|
||||||
"tooltip": "Snap the predicted-volume integrator to 25 m³",
|
"tooltip": "Snap the predicted-volume integrator to 25 m\u00b3",
|
||||||
"color": "",
|
"color": "",
|
||||||
"bgcolor": "",
|
"bgcolor": "",
|
||||||
"icon": "tune",
|
"icon": "tune",
|
||||||
@@ -472,8 +472,8 @@
|
|||||||
"z": "77f00aef1c966167",
|
"z": "77f00aef1c966167",
|
||||||
"g": "grp_status_panel",
|
"g": "grp_status_panel",
|
||||||
"name": "fan-out Port 0 (status + charts + raw)",
|
"name": "fan-out Port 0 (status + charts + raw)",
|
||||||
"func": "// Port 0 emits delta-only — cache last-known so deltas never blank a row.\n// Keys with dots use the runtime childId (= node id), so we pattern-match\n// by prefix rather than hardcoding.\nconst cache = context.get('cache') || {};\nconst p = msg.payload || {};\nfor (const k in p) cache[k] = p[k];\ncontext.set('cache', cache);\n\nconst findByPrefix = (prefix) => {\n for (const k of Object.keys(cache)) if (k.startsWith(prefix)) return cache[k];\n return null;\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 level = findByPrefix('level.predicted.atequipment.');\nconst volume = findByPrefix('volume.predicted.atequipment.');\nconst volPct = findByPrefix('volumePercent.predicted.atequipment.');\nconst qInS = findByPrefix('flow.predicted.in.');\nconst qOutS = findByPrefix('flow.predicted.out.');\nconst qNetS = findByPrefix('netFlowRate.predicted.atequipment.');\nconst qInH = Number.isFinite(+qInS) ? +qInS * 3600 : null;\nconst qOutH = Number.isFinite(+qOutS) ? +qOutS * 3600 : null;\nconst qNetH = Number.isFinite(+qNetS) ? +qNetS * 3600 : null;\nconst pct = cache.percControl;\nconst dem = cache.manualDemand;\nconst mode = cache.mode || '—';\nconst dir = cache.direction || '—';\n\nconst chart = (topic, v) => Number.isFinite(+v) ? { topic, payload: +v } : null;\n\n// Raw view: every cached key, sorted, with values prettified for display.\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 widgets\n { payload: mode },\n { payload: dir },\n { payload: num(level, 2, 'm') },\n { payload: num(volume, 2, 'm³') },\n { payload: num(volPct, 2, '%') },\n { payload: num(pct, 1, '%') },\n { payload: mode === 'manual'\n ? (Number.isFinite(+dem) ? num(dem, 1, 'm³/h') : 'not set')\n : '—' },\n // 7–9: single-series charts\n chart('Level', level),\n chart('Volume', volume),\n chart('Volume %', volPct),\n // 10–12: flow chart (three series share the same chart node)\n chart('Inflow', qInH),\n chart('Outflow', qOutH),\n chart('Net', qNetH),\n // 13: raw key/value rows for the ui-template\n { payload: rawRows },\n];\n",
|
"func": "// Port 0 emits delta-only \u2014 cache last-known so deltas never blank a row.\n// Keys with dots use the runtime childId (= node id), so we pattern-match\n// by prefix rather than hardcoding.\nconst cache = context.get('cache') || {};\nconst p = msg.payload || {};\nfor (const k in p) cache[k] = p[k];\ncontext.set('cache', cache);\n\nconst findByPrefix = (prefix) => {\n for (const k of Object.keys(cache)) if (k.startsWith(prefix)) return cache[k];\n return null;\n};\nconst num = (v, dp, unit) => {\n const n = +v;\n if (!Number.isFinite(n)) return '\u2014';\n return n.toFixed(dp) + (unit ? ' ' + unit : '');\n};\n\nconst level = findByPrefix('level.predicted.atequipment.');\nconst volume = findByPrefix('volume.predicted.atequipment.');\nconst volPct = findByPrefix('volumePercent.predicted.atequipment.');\nconst qInS = findByPrefix('flow.predicted.in.');\nconst qOutS = findByPrefix('flow.predicted.out.');\nconst qNetS = findByPrefix('netFlowRate.predicted.atequipment.');\nconst qInH = Number.isFinite(+qInS) ? +qInS * 3600 : null;\nconst qOutH = Number.isFinite(+qOutS) ? +qOutS * 3600 : null;\nconst qNetH = Number.isFinite(+qNetS) ? +qNetS * 3600 : null;\nconst pct = cache.percControl;\nconst dem = cache.manualDemand;\nconst mode = cache.mode || '\u2014';\nconst dir = cache.direction || '\u2014';\n\nconst chart = (topic, v) => Number.isFinite(+v) ? { topic, payload: +v } : null;\n\n// Raw view: every cached key, sorted, with values prettified for display.\nconst rawRows = Object.keys(cache).sort().map((k) => {\n const v = cache[k];\n let display;\n if (v === null || v === undefined) display = '\u2014';\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\u20136: status text widgets\n { payload: mode },\n { payload: dir },\n { payload: num(level, 2, 'm') },\n { payload: num(volume, 2, 'm\u00b3') },\n { payload: num(volPct, 2, '%') },\n { payload: num(pct, 1, '%') },\n { payload: mode === 'manual'\n ? (Number.isFinite(+dem) ? num(dem, 1, 'm\u00b3/h') : 'not set')\n : '\u2014' },\n // 7\u20139: single-series charts\n chart('Level', level),\n chart('Volume', volume),\n chart('Volume %', volPct),\n // 10\u201312: flow chart (three series share the same chart node)\n chart('Inflow', qInH),\n chart('Outflow', qOutH),\n chart('Net', qNetH),\n // 13: raw key/value rows for the ui-template\n { payload: rawRows },\n // 14: percControl chart\n chart('percControl', pct),\n];\n",
|
||||||
"outputs": 14,
|
"outputs": 15,
|
||||||
"timeout": 0,
|
"timeout": 0,
|
||||||
"noerr": 0,
|
"noerr": 0,
|
||||||
"initialize": "",
|
"initialize": "",
|
||||||
@@ -523,6 +523,9 @@
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
"ui_tpl_raw"
|
"ui_tpl_raw"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ui_chart_pumping_perccontrol"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -740,8 +743,8 @@
|
|||||||
"z": "77f00aef1c966167",
|
"z": "77f00aef1c966167",
|
||||||
"g": "grp_status_panel",
|
"g": "grp_status_panel",
|
||||||
"group": "ui_group_trends",
|
"group": "ui_group_trends",
|
||||||
"name": "Volume (m³)",
|
"name": "Volume (m\u00b3)",
|
||||||
"label": "Volume (m³)",
|
"label": "Volume (m\u00b3)",
|
||||||
"order": 2,
|
"order": 2,
|
||||||
"width": 6,
|
"width": 6,
|
||||||
"height": 4,
|
"height": 4,
|
||||||
@@ -754,7 +757,7 @@
|
|||||||
"xAxisPropertyType": "timestamp",
|
"xAxisPropertyType": "timestamp",
|
||||||
"xAxisFormat": "",
|
"xAxisFormat": "",
|
||||||
"xAxisFormatType": "auto",
|
"xAxisFormatType": "auto",
|
||||||
"yAxisLabel": "m³",
|
"yAxisLabel": "m\u00b3",
|
||||||
"yAxisProperty": "payload",
|
"yAxisProperty": "payload",
|
||||||
"yAxisPropertyType": "msg",
|
"yAxisPropertyType": "msg",
|
||||||
"xmin": "",
|
"xmin": "",
|
||||||
@@ -862,8 +865,8 @@
|
|||||||
"z": "77f00aef1c966167",
|
"z": "77f00aef1c966167",
|
||||||
"g": "grp_status_panel",
|
"g": "grp_status_panel",
|
||||||
"group": "ui_group_trends",
|
"group": "ui_group_trends",
|
||||||
"name": "Flow (m³/h)",
|
"name": "Flow (m\u00b3/h)",
|
||||||
"label": "Flow (m³/h) — Inflow / Outflow / Net",
|
"label": "Flow (m\u00b3/h) \u2014 Inflow / Outflow / Net",
|
||||||
"order": 4,
|
"order": 4,
|
||||||
"width": 6,
|
"width": 6,
|
||||||
"height": 4,
|
"height": 4,
|
||||||
@@ -876,7 +879,7 @@
|
|||||||
"xAxisPropertyType": "timestamp",
|
"xAxisPropertyType": "timestamp",
|
||||||
"xAxisFormat": "",
|
"xAxisFormat": "",
|
||||||
"xAxisFormatType": "auto",
|
"xAxisFormatType": "auto",
|
||||||
"yAxisLabel": "m³/h",
|
"yAxisLabel": "m\u00b3/h",
|
||||||
"yAxisProperty": "payload",
|
"yAxisProperty": "payload",
|
||||||
"yAxisPropertyType": "msg",
|
"yAxisPropertyType": "msg",
|
||||||
"xmin": "",
|
"xmin": "",
|
||||||
@@ -1029,7 +1032,7 @@
|
|||||||
"enableLog": false,
|
"enableLog": false,
|
||||||
"logLevel": "error",
|
"logLevel": "error",
|
||||||
"positionVsParent": "atEquipment",
|
"positionVsParent": "atEquipment",
|
||||||
"positionIcon": "⊥",
|
"positionIcon": "\u22a5",
|
||||||
"hasDistance": false,
|
"hasDistance": false,
|
||||||
"distance": "",
|
"distance": "",
|
||||||
"controlMode": "levelbased",
|
"controlMode": "levelbased",
|
||||||
@@ -1066,5 +1069,68 @@
|
|||||||
"modules": {
|
"modules": {
|
||||||
"EVOLV": "1.0.29"
|
"EVOLV": "1.0.29"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ui_chart_pumping_perccontrol",
|
||||||
|
"type": "ui-chart",
|
||||||
|
"z": "77f00aef1c966167",
|
||||||
|
"g": "grp_status_panel",
|
||||||
|
"group": "ui_group_trends",
|
||||||
|
"name": "percControl",
|
||||||
|
"label": "percControl (%) \u2014 pumping-station demand",
|
||||||
|
"order": 5,
|
||||||
|
"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": "0",
|
||||||
|
"ymax": "100",
|
||||||
|
"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": 1240,
|
||||||
|
"y": 560,
|
||||||
|
"wires": [
|
||||||
|
[]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
Reference in New Issue
Block a user