feat(dashboard): add basin fill gauge, countdown, and basin trend charts
Some checks failed
CI / lint-and-test (push) Has been cancelled
Some checks failed
CI / lint-and-test (push) Has been cancelled
PS control page now shows 7 fields instead of 5:
- Direction (filling/draining/steady)
- Basin level (m)
- Basin volume (m³)
- Fill level (%)
- Net flow (m³/h, signed)
- Time to full/empty (countdown in min or s)
- Inflow (m³/h)
Two new trend pages per time window (short 10 min / long 1 hour):
- Basin chart: 3 series (Basin fill %, Basin level m, Net flow m³/h)
on both Trends 10 min and Trends 1 hour pages.
PS formatter now extracts direction, netFlow, seconds from the delta-
compressed port 0 cache and computes fillPct from vol/maxVol. Dispatcher
sends 10 outputs (7 text + 3 trend numerics to both short+long basin
charts).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -611,14 +611,26 @@ def build_process_tab():
|
|||||||
"const vol = find('volume.predicted.');\n"
|
"const vol = find('volume.predicted.');\n"
|
||||||
"const qIn = find('flow.measured.upstream.') || find('flow.measured.in.');\n"
|
"const qIn = find('flow.measured.upstream.') || find('flow.measured.in.');\n"
|
||||||
"const qOut = find('flow.measured.downstream.') || find('flow.measured.out.');\n"
|
"const qOut = find('flow.measured.downstream.') || find('flow.measured.out.');\n"
|
||||||
|
"// Compute derived metrics\n"
|
||||||
|
"const maxVol = 9.33; // must match basinVolume * basinHeight / basinHeight = basinVolume / surfaceArea * height\n"
|
||||||
|
"const fillPct = vol != null ? Math.round(Number(vol) / maxVol * 100) : null;\n"
|
||||||
|
"const netM3h = (c.netFlow != null) ? Number(c.netFlow) * 3600 : null;\n"
|
||||||
|
"const seconds = (c.seconds != null && Number.isFinite(Number(c.seconds))) ? Number(c.seconds) : null;\n"
|
||||||
|
"const timeStr = seconds != null ? (seconds > 60 ? Math.round(seconds/60) + ' min' : Math.round(seconds) + ' s') : 'n/a';\n"
|
||||||
"msg.payload = {\n"
|
"msg.payload = {\n"
|
||||||
" level: lvl != null ? Number(lvl).toFixed(2) + ' m' : 'n/a',\n"
|
" direction: c.direction || 'steady',\n"
|
||||||
" volume: vol != null ? Number(vol).toFixed(1) + ' m³' : 'n/a',\n"
|
" level: lvl != null ? Number(lvl).toFixed(2) + ' m' : 'n/a',\n"
|
||||||
" qIn: qIn != null ? (Number(qIn) * 3600).toFixed(0) + ' m³/h' : 'n/a',\n"
|
" volume: vol != null ? Number(vol).toFixed(1) + ' m³' : 'n/a',\n"
|
||||||
" qOut: qOut != null ? (Number(qOut) * 3600).toFixed(0) + ' m³/h' : 'n/a',\n"
|
" fillPct: fillPct != null ? fillPct + '%' : 'n/a',\n"
|
||||||
" state: c.state || c.direction || 'idle',\n"
|
" netFlow: netM3h != null ? netM3h.toFixed(0) + ' m³/h' : 'n/a',\n"
|
||||||
" levelNum: lvl != null ? Number(lvl) : null,\n"
|
" timeLeft: timeStr,\n"
|
||||||
" volumeNum: vol != null ? Number(vol) : null,\n"
|
" qIn: qIn != null ? (Number(qIn) * 3600).toFixed(0) + ' m³/h' : 'n/a',\n"
|
||||||
|
" qOut: qOut != null ? (Number(qOut) * 3600).toFixed(0) + ' m³/h' : 'n/a',\n"
|
||||||
|
" // Numerics for trends\n"
|
||||||
|
" levelNum: lvl != null ? Number(lvl) : null,\n"
|
||||||
|
" volumeNum: vol != null ? Number(vol) : null,\n"
|
||||||
|
" fillPctNum: fillPct,\n"
|
||||||
|
" netFlowNum: netM3h,\n"
|
||||||
"};\n"
|
"};\n"
|
||||||
"return msg;",
|
"return msg;",
|
||||||
outputs=1, wires=[["lout_evt_ps"]],
|
outputs=1, wires=[["lout_evt_ps"]],
|
||||||
@@ -719,10 +731,12 @@ def build_ui_tab():
|
|||||||
ui_group(g_pump_b, "4b. Pump B", PG, width=4, order=6),
|
ui_group(g_pump_b, "4b. Pump B", PG, width=4, order=6),
|
||||||
ui_group(g_pump_c, "4c. Pump C", PG, width=4, order=7),
|
ui_group(g_pump_c, "4c. Pump C", PG, width=4, order=7),
|
||||||
# Trends on separate pages
|
# Trends on separate pages
|
||||||
ui_group(g_trend_short_flow, "Flow (10 min)", PG_SHORT, width=12, order=1),
|
ui_group(g_trend_short_flow, "Flow (10 min)", PG_SHORT, width=12, order=1),
|
||||||
ui_group(g_trend_short_power, "Power (10 min)", PG_SHORT, width=12, order=2),
|
ui_group(g_trend_short_power, "Power (10 min)", PG_SHORT, width=12, order=2),
|
||||||
ui_group(g_trend_long_flow, "Flow (1 hour)", PG_LONG, width=12, order=1),
|
ui_group("ui_grp_trend_short_basin", "Basin (10 min)", PG_SHORT, width=12, order=3),
|
||||||
ui_group(g_trend_long_power, "Power (1 hour)", PG_LONG, width=12, order=2),
|
ui_group(g_trend_long_flow, "Flow (1 hour)", PG_LONG, width=12, order=1),
|
||||||
|
ui_group(g_trend_long_power, "Power (1 hour)", PG_LONG, width=12, order=2),
|
||||||
|
ui_group("ui_grp_trend_long_basin", "Basin (1 hour)", PG_LONG, width=12, order=3),
|
||||||
]
|
]
|
||||||
|
|
||||||
nodes.append(comment("c_ui_title", TAB_UI, LANE_X[2], 20,
|
nodes.append(comment("c_ui_title", TAB_UI, LANE_X[2], 20,
|
||||||
@@ -863,30 +877,56 @@ def build_ui_tab():
|
|||||||
CH_PS_EVT, source_out_ids=["lout_evt_ps"],
|
CH_PS_EVT, source_out_ids=["lout_evt_ps"],
|
||||||
downstream=["dispatch_ps"]
|
downstream=["dispatch_ps"]
|
||||||
))
|
))
|
||||||
|
# PS dispatcher: 10 outputs — 7 text fields + 3 trend numerics
|
||||||
nodes.append(function_node(
|
nodes.append(function_node(
|
||||||
"dispatch_ps", TAB_UI, LANE_X[1], y + 160,
|
"dispatch_ps", TAB_UI, LANE_X[1], y + 160,
|
||||||
"dispatch PS",
|
"dispatch PS",
|
||||||
"const p = msg.payload || {};\n"
|
"const p = msg.payload || {};\n"
|
||||||
|
"const ts = Date.now();\n"
|
||||||
"return [\n"
|
"return [\n"
|
||||||
" {payload: String(p.state || 'idle')},\n"
|
" {payload: String(p.direction || 'steady')},\n"
|
||||||
" {payload: String(p.level || 'n/a')},\n"
|
" {payload: String(p.level || 'n/a')},\n"
|
||||||
" {payload: String(p.volume || 'n/a')},\n"
|
" {payload: String(p.volume || 'n/a')},\n"
|
||||||
" {payload: String(p.qIn || 'n/a')},\n"
|
" {payload: String(p.fillPct || 'n/a')},\n"
|
||||||
" {payload: String(p.qOut || 'n/a')},\n"
|
" {payload: String(p.netFlow || 'n/a')},\n"
|
||||||
|
" {payload: String(p.timeLeft || 'n/a')},\n"
|
||||||
|
" {payload: String(p.qIn || 'n/a')},\n"
|
||||||
|
" // Trend numerics\n"
|
||||||
|
" p.fillPctNum != null ? {topic: 'Basin fill', payload: p.fillPctNum, timestamp: ts} : null,\n"
|
||||||
|
" p.levelNum != null ? {topic: 'Basin level', payload: p.levelNum, timestamp: ts} : null,\n"
|
||||||
|
" p.netFlowNum != null ? {topic: 'Net flow', payload: p.netFlowNum,timestamp: ts} : null,\n"
|
||||||
"];",
|
"];",
|
||||||
outputs=5,
|
outputs=10,
|
||||||
wires=[["ui_ps_state"], ["ui_ps_level"], ["ui_ps_volume"], ["ui_ps_qin"], ["ui_ps_qout"]],
|
wires=[
|
||||||
|
["ui_ps_direction"],
|
||||||
|
["ui_ps_level"],
|
||||||
|
["ui_ps_volume"],
|
||||||
|
["ui_ps_fill"],
|
||||||
|
["ui_ps_netflow"],
|
||||||
|
["ui_ps_timeleft"],
|
||||||
|
["ui_ps_qin"],
|
||||||
|
# Trend outputs → both short + long charts
|
||||||
|
["trend_short_basin", "trend_long_basin"], # fill %
|
||||||
|
["trend_short_basin", "trend_long_basin"], # level
|
||||||
|
["trend_short_basin", "trend_long_basin"], # net flow
|
||||||
|
],
|
||||||
))
|
))
|
||||||
nodes.append(ui_text("ui_ps_state", TAB_UI, LANE_X[2], y + 160, g_ps,
|
|
||||||
"PS state", "Basin state", "{{msg.payload}}"))
|
# PS text widgets
|
||||||
nodes.append(ui_text("ui_ps_level", TAB_UI, LANE_X[2], y + 200, g_ps,
|
nodes.append(ui_text("ui_ps_direction", TAB_UI, LANE_X[2], y + 160, g_ps,
|
||||||
"PS level", "Basin level", "{{msg.payload}}"))
|
"PS direction", "Direction", "{{msg.payload}}"))
|
||||||
nodes.append(ui_text("ui_ps_volume", TAB_UI, LANE_X[2], y + 240, g_ps,
|
nodes.append(ui_text("ui_ps_level", TAB_UI, LANE_X[2], y + 200, g_ps,
|
||||||
"PS volume","Basin volume", "{{msg.payload}}"))
|
"PS level", "Basin level", "{{msg.payload}}"))
|
||||||
nodes.append(ui_text("ui_ps_qin", TAB_UI, LANE_X[2], y + 280, g_ps,
|
nodes.append(ui_text("ui_ps_volume", TAB_UI, LANE_X[2], y + 240, g_ps,
|
||||||
"PS Qin", "Inflow", "{{msg.payload}}"))
|
"PS volume", "Basin volume", "{{msg.payload}}"))
|
||||||
nodes.append(ui_text("ui_ps_qout", TAB_UI, LANE_X[2], y + 320, g_ps,
|
nodes.append(ui_text("ui_ps_fill", TAB_UI, LANE_X[2], y + 280, g_ps,
|
||||||
"PS Qout", "Pumped out", "{{msg.payload}}"))
|
"PS fill %", "Fill level", "{{msg.payload}}"))
|
||||||
|
nodes.append(ui_text("ui_ps_netflow", TAB_UI, LANE_X[2], y + 320, g_ps,
|
||||||
|
"PS net flow", "Net flow", "{{msg.payload}}"))
|
||||||
|
nodes.append(ui_text("ui_ps_timeleft", TAB_UI, LANE_X[2], y + 360, g_ps,
|
||||||
|
"PS time left", "Time to full/empty", "{{msg.payload}}"))
|
||||||
|
nodes.append(ui_text("ui_ps_qin", TAB_UI, LANE_X[2], y + 400, g_ps,
|
||||||
|
"PS Qin", "Inflow", "{{msg.payload}}"))
|
||||||
|
|
||||||
# ===== SECTION: Per-pump panels =====
|
# ===== SECTION: Per-pump panels =====
|
||||||
y_pumps_start = 1000
|
y_pumps_start = 1000
|
||||||
@@ -1046,6 +1086,26 @@ def build_ui_tab():
|
|||||||
order=1,
|
order=1,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# ===== Basin charts (fill %, level, net flow) =====
|
||||||
|
# Short-term
|
||||||
|
nodes.append(ui_chart(
|
||||||
|
"trend_short_basin", TAB_UI, LANE_X[3], y_charts + 360,
|
||||||
|
"ui_grp_trend_short_basin",
|
||||||
|
"Basin — 10 min", "Basin metrics",
|
||||||
|
width=12, height=8,
|
||||||
|
remove_older="10", remove_older_unit="60", remove_older_points="300",
|
||||||
|
y_axis_label="", order=1,
|
||||||
|
))
|
||||||
|
# Long-term
|
||||||
|
nodes.append(ui_chart(
|
||||||
|
"trend_long_basin", TAB_UI, LANE_X[3], y_charts + 440,
|
||||||
|
"ui_grp_trend_long_basin",
|
||||||
|
"Basin — 1 hour", "Basin metrics",
|
||||||
|
width=12, height=8,
|
||||||
|
remove_older="60", remove_older_unit="60", remove_older_points="1800",
|
||||||
|
y_axis_label="", order=1,
|
||||||
|
))
|
||||||
|
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -881,7 +881,7 @@
|
|||||||
"type": "function",
|
"type": "function",
|
||||||
"z": "tab_process",
|
"z": "tab_process",
|
||||||
"name": "format PS port 0",
|
"name": "format PS port 0",
|
||||||
"func": "const p = msg.payload || {};\nconst c = context.get('c') || {};\nObject.assign(c, p);\ncontext.set('c', c);\nfunction find(prefix) {\n for (const k in c) { if (k.indexOf(prefix) === 0) return c[k]; }\n return null;\n}\nconst lvl = find('level.predicted.');\nconst vol = find('volume.predicted.');\nconst qIn = find('flow.measured.upstream.') || find('flow.measured.in.');\nconst qOut = find('flow.measured.downstream.') || find('flow.measured.out.');\nmsg.payload = {\n level: lvl != null ? Number(lvl).toFixed(2) + ' m' : 'n/a',\n volume: vol != null ? Number(vol).toFixed(1) + ' m\u00b3' : 'n/a',\n qIn: qIn != null ? (Number(qIn) * 3600).toFixed(0) + ' m\u00b3/h' : 'n/a',\n qOut: qOut != null ? (Number(qOut) * 3600).toFixed(0) + ' m\u00b3/h' : 'n/a',\n state: c.state || c.direction || 'idle',\n levelNum: lvl != null ? Number(lvl) : null,\n volumeNum: vol != null ? Number(vol) : null,\n};\nreturn msg;",
|
"func": "const p = msg.payload || {};\nconst c = context.get('c') || {};\nObject.assign(c, p);\ncontext.set('c', c);\nfunction find(prefix) {\n for (const k in c) { if (k.indexOf(prefix) === 0) return c[k]; }\n return null;\n}\nconst lvl = find('level.predicted.');\nconst vol = find('volume.predicted.');\nconst qIn = find('flow.measured.upstream.') || find('flow.measured.in.');\nconst qOut = find('flow.measured.downstream.') || find('flow.measured.out.');\n// Compute derived metrics\nconst maxVol = 9.33; // must match basinVolume * basinHeight / basinHeight = basinVolume / surfaceArea * height\nconst fillPct = vol != null ? Math.round(Number(vol) / maxVol * 100) : null;\nconst netM3h = (c.netFlow != null) ? Number(c.netFlow) * 3600 : null;\nconst seconds = (c.seconds != null && Number.isFinite(Number(c.seconds))) ? Number(c.seconds) : null;\nconst timeStr = seconds != null ? (seconds > 60 ? Math.round(seconds/60) + ' min' : Math.round(seconds) + ' s') : 'n/a';\nmsg.payload = {\n direction: c.direction || 'steady',\n level: lvl != null ? Number(lvl).toFixed(2) + ' m' : 'n/a',\n volume: vol != null ? Number(vol).toFixed(1) + ' m\u00b3' : 'n/a',\n fillPct: fillPct != null ? fillPct + '%' : 'n/a',\n netFlow: netM3h != null ? netM3h.toFixed(0) + ' m\u00b3/h' : 'n/a',\n timeLeft: timeStr,\n qIn: qIn != null ? (Number(qIn) * 3600).toFixed(0) + ' m\u00b3/h' : 'n/a',\n qOut: qOut != null ? (Number(qOut) * 3600).toFixed(0) + ' m\u00b3/h' : 'n/a',\n // Numerics for trends\n levelNum: lvl != null ? Number(lvl) : null,\n volumeNum: vol != null ? Number(vol) : null,\n fillPctNum: fillPct,\n netFlowNum: netM3h,\n};\nreturn msg;",
|
||||||
"outputs": 1,
|
"outputs": 1,
|
||||||
"noerr": 0,
|
"noerr": 0,
|
||||||
"initialize": "",
|
"initialize": "",
|
||||||
@@ -1343,6 +1343,20 @@
|
|||||||
"disabled": false,
|
"disabled": false,
|
||||||
"visible": true
|
"visible": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "ui_grp_trend_short_basin",
|
||||||
|
"type": "ui-group",
|
||||||
|
"name": "Basin (10 min)",
|
||||||
|
"page": "ui_page_short_trends",
|
||||||
|
"width": "12",
|
||||||
|
"height": "1",
|
||||||
|
"order": 3,
|
||||||
|
"showTitle": true,
|
||||||
|
"className": "",
|
||||||
|
"groupType": "default",
|
||||||
|
"disabled": false,
|
||||||
|
"visible": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "ui_grp_trend_long_flow",
|
"id": "ui_grp_trend_long_flow",
|
||||||
"type": "ui-group",
|
"type": "ui-group",
|
||||||
@@ -1371,6 +1385,20 @@
|
|||||||
"disabled": false,
|
"disabled": false,
|
||||||
"visible": true
|
"visible": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "ui_grp_trend_long_basin",
|
||||||
|
"type": "ui-group",
|
||||||
|
"name": "Basin (1 hour)",
|
||||||
|
"page": "ui_page_long_trends",
|
||||||
|
"width": "12",
|
||||||
|
"height": "1",
|
||||||
|
"order": 3,
|
||||||
|
"showTitle": true,
|
||||||
|
"className": "",
|
||||||
|
"groupType": "default",
|
||||||
|
"disabled": false,
|
||||||
|
"visible": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "c_ui_title",
|
"id": "c_ui_title",
|
||||||
"type": "comment",
|
"type": "comment",
|
||||||
@@ -1829,8 +1857,8 @@
|
|||||||
"type": "function",
|
"type": "function",
|
||||||
"z": "tab_ui",
|
"z": "tab_ui",
|
||||||
"name": "dispatch PS",
|
"name": "dispatch PS",
|
||||||
"func": "const p = msg.payload || {};\nreturn [\n {payload: String(p.state || 'idle')},\n {payload: String(p.level || 'n/a')},\n {payload: String(p.volume || 'n/a')},\n {payload: String(p.qIn || 'n/a')},\n {payload: String(p.qOut || 'n/a')},\n];",
|
"func": "const p = msg.payload || {};\nconst ts = Date.now();\nreturn [\n {payload: String(p.direction || 'steady')},\n {payload: String(p.level || 'n/a')},\n {payload: String(p.volume || 'n/a')},\n {payload: String(p.fillPct || 'n/a')},\n {payload: String(p.netFlow || 'n/a')},\n {payload: String(p.timeLeft || 'n/a')},\n {payload: String(p.qIn || 'n/a')},\n // Trend numerics\n p.fillPctNum != null ? {topic: 'Basin fill', payload: p.fillPctNum, timestamp: ts} : null,\n p.levelNum != null ? {topic: 'Basin level', payload: p.levelNum, timestamp: ts} : null,\n p.netFlowNum != null ? {topic: 'Net flow', payload: p.netFlowNum,timestamp: ts} : null,\n];",
|
||||||
"outputs": 5,
|
"outputs": 10,
|
||||||
"noerr": 0,
|
"noerr": 0,
|
||||||
"initialize": "",
|
"initialize": "",
|
||||||
"finalize": "",
|
"finalize": "",
|
||||||
@@ -1839,7 +1867,7 @@
|
|||||||
"y": 760,
|
"y": 760,
|
||||||
"wires": [
|
"wires": [
|
||||||
[
|
[
|
||||||
"ui_ps_state"
|
"ui_ps_direction"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"ui_ps_level"
|
"ui_ps_level"
|
||||||
@@ -1847,24 +1875,42 @@
|
|||||||
[
|
[
|
||||||
"ui_ps_volume"
|
"ui_ps_volume"
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"ui_ps_fill"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ui_ps_netflow"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ui_ps_timeleft"
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"ui_ps_qin"
|
"ui_ps_qin"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"ui_ps_qout"
|
"trend_short_basin",
|
||||||
|
"trend_long_basin"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"trend_short_basin",
|
||||||
|
"trend_long_basin"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"trend_short_basin",
|
||||||
|
"trend_long_basin"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "ui_ps_state",
|
"id": "ui_ps_direction",
|
||||||
"type": "ui-text",
|
"type": "ui-text",
|
||||||
"z": "tab_ui",
|
"z": "tab_ui",
|
||||||
"group": "ui_grp_ps",
|
"group": "ui_grp_ps",
|
||||||
"order": 1,
|
"order": 1,
|
||||||
"width": "0",
|
"width": "0",
|
||||||
"height": "0",
|
"height": "0",
|
||||||
"name": "PS state",
|
"name": "PS direction",
|
||||||
"label": "Basin state",
|
"label": "Direction",
|
||||||
"format": "{{msg.payload}}",
|
"format": "{{msg.payload}}",
|
||||||
"layout": "row-left",
|
"layout": "row-left",
|
||||||
"style": false,
|
"style": false,
|
||||||
@@ -1915,6 +1961,66 @@
|
|||||||
"y": 840,
|
"y": 840,
|
||||||
"wires": []
|
"wires": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "ui_ps_fill",
|
||||||
|
"type": "ui-text",
|
||||||
|
"z": "tab_ui",
|
||||||
|
"group": "ui_grp_ps",
|
||||||
|
"order": 1,
|
||||||
|
"width": "0",
|
||||||
|
"height": "0",
|
||||||
|
"name": "PS fill %",
|
||||||
|
"label": "Fill level",
|
||||||
|
"format": "{{msg.payload}}",
|
||||||
|
"layout": "row-left",
|
||||||
|
"style": false,
|
||||||
|
"font": "",
|
||||||
|
"fontSize": 14,
|
||||||
|
"color": "#000000",
|
||||||
|
"x": 640,
|
||||||
|
"y": 880,
|
||||||
|
"wires": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ui_ps_netflow",
|
||||||
|
"type": "ui-text",
|
||||||
|
"z": "tab_ui",
|
||||||
|
"group": "ui_grp_ps",
|
||||||
|
"order": 1,
|
||||||
|
"width": "0",
|
||||||
|
"height": "0",
|
||||||
|
"name": "PS net flow",
|
||||||
|
"label": "Net flow",
|
||||||
|
"format": "{{msg.payload}}",
|
||||||
|
"layout": "row-left",
|
||||||
|
"style": false,
|
||||||
|
"font": "",
|
||||||
|
"fontSize": 14,
|
||||||
|
"color": "#000000",
|
||||||
|
"x": 640,
|
||||||
|
"y": 920,
|
||||||
|
"wires": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ui_ps_timeleft",
|
||||||
|
"type": "ui-text",
|
||||||
|
"z": "tab_ui",
|
||||||
|
"group": "ui_grp_ps",
|
||||||
|
"order": 1,
|
||||||
|
"width": "0",
|
||||||
|
"height": "0",
|
||||||
|
"name": "PS time left",
|
||||||
|
"label": "Time to full/empty",
|
||||||
|
"format": "{{msg.payload}}",
|
||||||
|
"layout": "row-left",
|
||||||
|
"style": false,
|
||||||
|
"font": "",
|
||||||
|
"fontSize": 14,
|
||||||
|
"color": "#000000",
|
||||||
|
"x": 640,
|
||||||
|
"y": 960,
|
||||||
|
"wires": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "ui_ps_qin",
|
"id": "ui_ps_qin",
|
||||||
"type": "ui-text",
|
"type": "ui-text",
|
||||||
@@ -1932,27 +2038,7 @@
|
|||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
"color": "#000000",
|
"color": "#000000",
|
||||||
"x": 640,
|
"x": 640,
|
||||||
"y": 880,
|
"y": 1000,
|
||||||
"wires": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "ui_ps_qout",
|
|
||||||
"type": "ui-text",
|
|
||||||
"z": "tab_ui",
|
|
||||||
"group": "ui_grp_ps",
|
|
||||||
"order": 1,
|
|
||||||
"width": "0",
|
|
||||||
"height": "0",
|
|
||||||
"name": "PS Qout",
|
|
||||||
"label": "Pumped out",
|
|
||||||
"format": "{{msg.payload}}",
|
|
||||||
"layout": "row-left",
|
|
||||||
"style": false,
|
|
||||||
"font": "",
|
|
||||||
"fontSize": 14,
|
|
||||||
"color": "#000000",
|
|
||||||
"x": 640,
|
|
||||||
"y": 920,
|
|
||||||
"wires": []
|
"wires": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -3311,6 +3397,130 @@
|
|||||||
[]
|
[]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "trend_short_basin",
|
||||||
|
"type": "ui-chart",
|
||||||
|
"z": "tab_ui",
|
||||||
|
"group": "ui_grp_trend_short_basin",
|
||||||
|
"name": "Basin \u2014 10 min",
|
||||||
|
"label": "Basin metrics",
|
||||||
|
"order": 1,
|
||||||
|
"chartType": "line",
|
||||||
|
"interpolation": "linear",
|
||||||
|
"category": "topic",
|
||||||
|
"categoryType": "msg",
|
||||||
|
"xAxisLabel": "",
|
||||||
|
"xAxisType": "time",
|
||||||
|
"xAxisProperty": "",
|
||||||
|
"xAxisPropertyType": "timestamp",
|
||||||
|
"xAxisFormat": "",
|
||||||
|
"xAxisFormatType": "auto",
|
||||||
|
"xmin": "",
|
||||||
|
"xmax": "",
|
||||||
|
"yAxisLabel": "",
|
||||||
|
"yAxisProperty": "payload",
|
||||||
|
"yAxisPropertyType": "msg",
|
||||||
|
"ymin": "",
|
||||||
|
"ymax": "",
|
||||||
|
"removeOlder": "10",
|
||||||
|
"removeOlderUnit": "60",
|
||||||
|
"removeOlderPoints": "300",
|
||||||
|
"action": "append",
|
||||||
|
"stackSeries": false,
|
||||||
|
"pointShape": "circle",
|
||||||
|
"pointRadius": 4,
|
||||||
|
"showLegend": true,
|
||||||
|
"bins": 10,
|
||||||
|
"colors": [
|
||||||
|
"#0095FF",
|
||||||
|
"#FF0000",
|
||||||
|
"#FF7F0E",
|
||||||
|
"#2CA02C",
|
||||||
|
"#A347E1",
|
||||||
|
"#D62728",
|
||||||
|
"#FF9896",
|
||||||
|
"#9467BD",
|
||||||
|
"#C5B0D5"
|
||||||
|
],
|
||||||
|
"textColor": [
|
||||||
|
"#666666"
|
||||||
|
],
|
||||||
|
"textColorDefault": true,
|
||||||
|
"gridColor": [
|
||||||
|
"#e5e5e5"
|
||||||
|
],
|
||||||
|
"gridColorDefault": true,
|
||||||
|
"width": 12,
|
||||||
|
"height": 8,
|
||||||
|
"className": "",
|
||||||
|
"x": 900,
|
||||||
|
"y": 2640,
|
||||||
|
"wires": [
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "trend_long_basin",
|
||||||
|
"type": "ui-chart",
|
||||||
|
"z": "tab_ui",
|
||||||
|
"group": "ui_grp_trend_long_basin",
|
||||||
|
"name": "Basin \u2014 1 hour",
|
||||||
|
"label": "Basin metrics",
|
||||||
|
"order": 1,
|
||||||
|
"chartType": "line",
|
||||||
|
"interpolation": "linear",
|
||||||
|
"category": "topic",
|
||||||
|
"categoryType": "msg",
|
||||||
|
"xAxisLabel": "",
|
||||||
|
"xAxisType": "time",
|
||||||
|
"xAxisProperty": "",
|
||||||
|
"xAxisPropertyType": "timestamp",
|
||||||
|
"xAxisFormat": "",
|
||||||
|
"xAxisFormatType": "auto",
|
||||||
|
"xmin": "",
|
||||||
|
"xmax": "",
|
||||||
|
"yAxisLabel": "",
|
||||||
|
"yAxisProperty": "payload",
|
||||||
|
"yAxisPropertyType": "msg",
|
||||||
|
"ymin": "",
|
||||||
|
"ymax": "",
|
||||||
|
"removeOlder": "60",
|
||||||
|
"removeOlderUnit": "60",
|
||||||
|
"removeOlderPoints": "1800",
|
||||||
|
"action": "append",
|
||||||
|
"stackSeries": false,
|
||||||
|
"pointShape": "circle",
|
||||||
|
"pointRadius": 4,
|
||||||
|
"showLegend": true,
|
||||||
|
"bins": 10,
|
||||||
|
"colors": [
|
||||||
|
"#0095FF",
|
||||||
|
"#FF0000",
|
||||||
|
"#FF7F0E",
|
||||||
|
"#2CA02C",
|
||||||
|
"#A347E1",
|
||||||
|
"#D62728",
|
||||||
|
"#FF9896",
|
||||||
|
"#9467BD",
|
||||||
|
"#C5B0D5"
|
||||||
|
],
|
||||||
|
"textColor": [
|
||||||
|
"#666666"
|
||||||
|
],
|
||||||
|
"textColorDefault": true,
|
||||||
|
"gridColor": [
|
||||||
|
"#e5e5e5"
|
||||||
|
],
|
||||||
|
"gridColorDefault": true,
|
||||||
|
"width": 12,
|
||||||
|
"height": 8,
|
||||||
|
"className": "",
|
||||||
|
"x": 900,
|
||||||
|
"y": 2720,
|
||||||
|
"wires": [
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "tab_drivers",
|
"id": "tab_drivers",
|
||||||
"type": "tab",
|
"type": "tab",
|
||||||
|
|||||||
Reference in New Issue
Block a user