Propagate threshold rename; point platform manual at pumpingStation wiki
Some checks failed
CI / lint-and-test (push) Has been cancelled
Some checks failed
CI / lint-and-test (push) Has been cancelled
Follows pumpingStation@a218945 + generalFunctions@4252292 rename: - Bump pumpingStation and generalFunctions submodule pointers. - Update examples/pumpingstation-3pumps-dashboard/ (build_flow.py, flow.json, README.md) to use the new threshold names. Collapsed minFlowLevel into startLevel; reshuffled order to match the basin bottom-to-top: minLevel, startLevel, maxLevel. - wiki/manuals/README.md: drop the stale pumpingStation.md line and point readers at pumpingStation/wiki instead (docs have moved into the node's own repo). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -91,7 +91,7 @@ The Emergency Stop button always works regardless of mode and uses the new inter
|
||||
|
||||
## Notable design choices
|
||||
|
||||
- **PS is in `manual` control mode** (`controlMode: "manual"`). The default `levelbased` mode would auto-shut all pumps as soon as basin level dips below `stopLevel` (1 m default), which masks the demo. Manual = observation only.
|
||||
- **PS is in `manual` control mode** (`controlMode: "manual"`). The default `levelbased` mode would auto-shut all pumps as soon as basin level dips below `minLevel` (1 m default), which masks the demo. Manual = observation only.
|
||||
- **PS safety guards (dry-run / overfill) disabled.** With no real inflow the basin will frequently look "empty" — that's expected for a demo, not a fault. In production you'd configure a real `q_in` source and leave safeties on.
|
||||
- **MGC scaling = `absolute`, mode = `optimalcontrol`.** Set via inject at deploy. Demand in m³/h, BEP-driven distribution.
|
||||
- **demand_router gates Qd ≤ 0.** A demand of 0 would shut every running pump (via MGC.turnOffAllMachines). Use the explicit Stop All button to actually take pumps down.
|
||||
|
||||
@@ -52,6 +52,13 @@ LANE_X = [120, 380, 640, 900, 1160, 1420]
|
||||
ROW = 80 # standard inter-row pitch
|
||||
SECTION_GAP = 200 # additional shift between major sections
|
||||
|
||||
# Position icon map — must match generalFunctions/src/menu/physicalPosition.js
|
||||
POSITION_ICON = {
|
||||
"upstream": "\u2192", # →
|
||||
"downstream": "\u2190", # ←
|
||||
"atEquipment": "\u22a5", # ⊥
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Cross-tab link channel names (the wiring contract)
|
||||
@@ -356,9 +363,14 @@ def build_process_tab():
|
||||
))
|
||||
|
||||
# Two measurement sensors (upstream + downstream)
|
||||
# Static pressures: upstream ~100 mbar (basin hydrostatic),
|
||||
# downstream ~1300 mbar (discharge head ≈ 12 m).
|
||||
# Differential = 1200 mbar = 120 kPa — mid-range on the
|
||||
# hidrostal curve (700–3900 mbar). Simulator OFF so the
|
||||
# value is stable and predictable.
|
||||
for j, pos in enumerate(("upstream", "downstream")):
|
||||
mid = f"meas_{pump}_{pos[0]}"
|
||||
absmin, absmax = (50, 400) if pos == "upstream" else (800, 2200)
|
||||
static_val = 100 if pos == "upstream" else 1300
|
||||
mid_label = f"PT-{label.split()[1]}-{'Up' if pos == 'upstream' else 'Dn'}"
|
||||
nodes.append({
|
||||
"id": mid, "type": "measurement", "z": TAB_PROCESS,
|
||||
@@ -366,7 +378,7 @@ def build_process_tab():
|
||||
"mode": "analog", "channels": "[]",
|
||||
"scaling": False,
|
||||
"i_min": 0, "i_max": 1, "i_offset": 0,
|
||||
"o_min": absmin, "o_max": absmax,
|
||||
"o_min": static_val, "o_max": static_val,
|
||||
"simulator": True,
|
||||
"smooth_method": "mean", "count": "5",
|
||||
"processOutputFormat": "process", "dbaseOutputFormat": "influxdb",
|
||||
@@ -375,7 +387,7 @@ def build_process_tab():
|
||||
"assetType": "pressure", "model": "vega-pressure-10",
|
||||
"unit": "mbar", "assetTagNumber": f"PT-{i+1}-{pos[0].upper()}",
|
||||
"enableLog": False, "logLevel": "warn",
|
||||
"positionVsParent": pos, "positionIcon": "",
|
||||
"positionVsParent": pos, "positionIcon": POSITION_ICON.get(pos, ""),
|
||||
"hasDistance": False, "distance": 0, "distanceUnit": "m",
|
||||
"distanceDescription": "",
|
||||
"x": LANE_X[1], "y": y_section + 40 + j * 50,
|
||||
@@ -424,7 +436,7 @@ def build_process_tab():
|
||||
"curvePressureUnit": "mbar", "curveFlowUnit": "m3/h",
|
||||
"curvePowerUnit": "kW", "curveControlUnit": "%",
|
||||
"enableLog": False, "logLevel": "warn",
|
||||
"positionVsParent": "atEquipment", "positionIcon": "",
|
||||
"positionVsParent": "atEquipment", "positionIcon": POSITION_ICON["atEquipment"],
|
||||
"hasDistance": False, "distance": 0, "distanceUnit": "m",
|
||||
"distanceDescription": "",
|
||||
"x": LANE_X[3], "y": y_section + 80,
|
||||
@@ -493,7 +505,7 @@ def build_process_tab():
|
||||
"assetType": "machinegroupcontrol",
|
||||
"model": "default", "unit": "m3/h", "supplier": "evolv",
|
||||
"enableLog": False, "logLevel": "warn",
|
||||
"positionVsParent": "atEquipment", "positionIcon": "",
|
||||
"positionVsParent": "atEquipment", "positionIcon": POSITION_ICON["atEquipment"],
|
||||
"hasDistance": False, "distance": 0, "distanceUnit": "m",
|
||||
"distanceDescription": "",
|
||||
"processOutputFormat": "process", "dbaseOutputFormat": "influxdb",
|
||||
@@ -572,7 +584,7 @@ def build_process_tab():
|
||||
"category": "station", "assetType": "pumpingstation",
|
||||
"model": "default", "unit": "m3/s", "supplier": "evolv",
|
||||
"enableLog": False, "logLevel": "warn",
|
||||
"positionVsParent": "atEquipment", "positionIcon": "",
|
||||
"positionVsParent": "atEquipment", "positionIcon": POSITION_ICON["atEquipment"],
|
||||
"hasDistance": False, "distance": 0, "distanceUnit": "m",
|
||||
"distanceDescription": "",
|
||||
"processOutputFormat": "process", "dbaseOutputFormat": "influxdb",
|
||||
@@ -586,19 +598,18 @@ def build_process_tab():
|
||||
"controlMode": "levelbased",
|
||||
"basinVolume": 30,
|
||||
"basinHeight": 4,
|
||||
"heightInlet": 3.5,
|
||||
"heightOutlet": 0.3,
|
||||
"heightOverflow": 3.8,
|
||||
"inflowLevel": 3.5,
|
||||
"outflowLevel": 0.3,
|
||||
"overflowLevel": 3.8,
|
||||
"inletPipeDiameter": 0.3,
|
||||
"outletPipeDiameter": 0.3,
|
||||
# Level-based control thresholds
|
||||
"startLevel": 2.0, # pumps ON above 2.0 m (50% of height)
|
||||
"stopLevel": 1.0, # pumps OFF below 1.0 m (25% of height)
|
||||
"minFlowLevel": 2.0, # 0% pump demand at startLevel (must match startLevel!)
|
||||
"maxFlowLevel": 3.5, # 100% pump demand at this level
|
||||
"minLevel": 1.0, # pumps OFF below 1.0 m (25% of height)
|
||||
"startLevel": 2.0, # 0% pump demand — ramp begins here
|
||||
"maxLevel": 3.5, # 100% pump demand at this level
|
||||
# Hydraulics
|
||||
"refHeight": "NAP",
|
||||
"minHeightBasedOn": "inlet",
|
||||
"minHeightBasedOn": "outlet", # basin drains to outlet pipe (0.3 m), not inlet
|
||||
"basinBottomRef": 0,
|
||||
"staticHead": 12,
|
||||
"maxDischargeHead": 24,
|
||||
@@ -631,15 +642,16 @@ def build_process_tab():
|
||||
"}\n"
|
||||
"const lvl = find('level.predicted.');\n"
|
||||
"const vol = find('volume.predicted.');\n"
|
||||
"const qIn = find('flow.measured.upstream.') || find('flow.measured.in.');\n"
|
||||
"const qOut = find('flow.measured.downstream.') || find('flow.measured.out.');\n"
|
||||
"const qIn = find('flow.predicted.in.');\n"
|
||||
"const qOut = find('flow.predicted.out.');\n"
|
||||
"const netFlowRate = find('netFlowRate.predicted.');\n"
|
||||
"// Compute derived metrics\n"
|
||||
"// Basin capacity = basinVolume (config). Don't hardcode — read it once.\n"
|
||||
"if (!context.get('maxVol')) context.set('maxVol', 30.0); // basinVolume from PS config\n"
|
||||
"const maxVol = context.get('maxVol');\n"
|
||||
"const fillPct = vol != null ? Math.min(100, Math.max(0, 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 netM3h = netFlowRate != null ? Number(netFlowRate) * 3600 : null;\n"
|
||||
"const seconds = (c.timeleft != null && Number.isFinite(Number(c.timeleft))) ? Number(c.timeleft) : null;\n"
|
||||
"const timeStr = seconds != null ? (seconds > 60 ? Math.round(seconds/60) + ' min' : Math.round(seconds) + ' s') : 'n/a';\n"
|
||||
"msg.payload = {\n"
|
||||
" direction: c.direction || 'steady',\n"
|
||||
@@ -655,6 +667,9 @@ def build_process_tab():
|
||||
" volumeNum: vol != null ? Number(vol) : null,\n"
|
||||
" fillPctNum: fillPct,\n"
|
||||
" netFlowNum: netM3h,\n"
|
||||
" percControl: c.percControl != null ? Number(c.percControl) : null,\n"
|
||||
" qInNum: qIn != null ? Number(qIn) * 3600 : null,\n"
|
||||
" qOutNum: qOut != null ? Number(qOut) * 3600 : null,\n"
|
||||
"};\n"
|
||||
"return msg;",
|
||||
outputs=1, wires=[["lout_evt_ps"]],
|
||||
@@ -921,8 +936,11 @@ def build_ui_tab():
|
||||
" 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"
|
||||
" p.percControl != null ? {topic: 'PS demand', payload: p.percControl, timestamp: ts} : null,\n"
|
||||
" p.qInNum != null ? {topic: 'Inflow', payload: p.qInNum, timestamp: ts} : null,\n"
|
||||
" p.qOutNum != null ? {topic: 'Outflow', payload: p.qOutNum, timestamp: ts} : null,\n"
|
||||
"];",
|
||||
outputs=10,
|
||||
outputs=13,
|
||||
wires=[
|
||||
["ui_ps_direction"],
|
||||
["ui_ps_level"],
|
||||
@@ -934,7 +952,10 @@ def build_ui_tab():
|
||||
# Trend + gauge outputs — split level and fill to separate charts
|
||||
["trend_short_fill", "trend_long_fill", "gauge_ps_fill", "gauge_ps_fill_long"], # fill % → fill chart + gauges
|
||||
["trend_short_level", "trend_long_level", "gauge_ps_level", "gauge_ps_level_long"], # level → level chart + gauges
|
||||
["trend_short_level", "trend_long_level"], # net flow → level chart (shared axis)
|
||||
["trend_short_flow", "trend_long_flow"], # net flow → flow charts
|
||||
["trend_short_fill", "trend_long_fill"], # percControl (%) → fill charts (same y-axis)
|
||||
["trend_short_flow", "trend_long_flow"], # inflow m3/h → flow charts
|
||||
["trend_short_flow", "trend_long_flow"], # outflow m3/h → flow charts
|
||||
],
|
||||
))
|
||||
|
||||
@@ -1118,11 +1139,11 @@ def build_ui_tab():
|
||||
# ===== Basin charts + gauges (fill %, level, net flow) =====
|
||||
# Gauge segment definitions (reused for both pages)
|
||||
TANK_SEGMENTS = [
|
||||
{"color": "#f44336", "from": 0}, # red: below stopLevel (1.0 m)
|
||||
{"color": "#f44336", "from": 0}, # red: below minLevel (1.0 m)
|
||||
{"color": "#ff9800", "from": 1.0}, # orange: between stop and start
|
||||
{"color": "#2196f3", "from": 2.0}, # blue: normal operating (startLevel)
|
||||
{"color": "#ff9800", "from": 3.5}, # orange: approaching overflow
|
||||
{"color": "#f44336", "from": 3.8}, # red: overflow zone (heightOverflow)
|
||||
{"color": "#f44336", "from": 3.8}, # red: overflow zone (overflowLevel)
|
||||
]
|
||||
FILL_SEGMENTS = [
|
||||
{"color": "#f44336", "from": 0},
|
||||
@@ -1314,19 +1335,8 @@ def build_setup_tab():
|
||||
CH_MODE, target_in_ids=["lin_mode"]
|
||||
))
|
||||
|
||||
y = 350
|
||||
nodes.append(inject(
|
||||
"setup_pumps_startup", TAB_SETUP, LANE_X[0], y,
|
||||
"auto-startup all pumps",
|
||||
topic="execSequence",
|
||||
payload='{"source":"GUI","action":"execSequence","parameter":"startup"}',
|
||||
payload_type="json", once=True, once_delay="4",
|
||||
wires=["lout_setup_station_start"]
|
||||
))
|
||||
nodes.append(link_out(
|
||||
"lout_setup_station_start", TAB_SETUP, LANE_X[1], y,
|
||||
CH_STATION_START, target_in_ids=["lin_station_start"]
|
||||
))
|
||||
# Auto-startup removed: the MGC starts pumps on demand from the PS.
|
||||
# Starting pumps before the PS requests them causes flow below startLevel.
|
||||
|
||||
# (Random demand removed — sinus inflow drives the demo automatically.
|
||||
# No explicit "random on" inject needed.)
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
"i_min": 0,
|
||||
"i_max": 1,
|
||||
"i_offset": 0,
|
||||
"o_min": 50,
|
||||
"o_max": 400,
|
||||
"o_min": 100,
|
||||
"o_max": 100,
|
||||
"simulator": true,
|
||||
"smooth_method": "mean",
|
||||
"count": "5",
|
||||
@@ -54,7 +54,7 @@
|
||||
"enableLog": false,
|
||||
"logLevel": "warn",
|
||||
"positionVsParent": "upstream",
|
||||
"positionIcon": "",
|
||||
"positionIcon": "\u2192",
|
||||
"hasDistance": false,
|
||||
"distance": 0,
|
||||
"distanceUnit": "m",
|
||||
@@ -80,8 +80,8 @@
|
||||
"i_min": 0,
|
||||
"i_max": 1,
|
||||
"i_offset": 0,
|
||||
"o_min": 800,
|
||||
"o_max": 2200,
|
||||
"o_min": 1300,
|
||||
"o_max": 1300,
|
||||
"simulator": true,
|
||||
"smooth_method": "mean",
|
||||
"count": "5",
|
||||
@@ -97,7 +97,7 @@
|
||||
"enableLog": false,
|
||||
"logLevel": "warn",
|
||||
"positionVsParent": "downstream",
|
||||
"positionIcon": "",
|
||||
"positionIcon": "\u2190",
|
||||
"hasDistance": false,
|
||||
"distance": 0,
|
||||
"distanceUnit": "m",
|
||||
@@ -188,7 +188,7 @@
|
||||
"enableLog": false,
|
||||
"logLevel": "warn",
|
||||
"positionVsParent": "atEquipment",
|
||||
"positionIcon": "",
|
||||
"positionIcon": "\u22a5",
|
||||
"hasDistance": false,
|
||||
"distance": 0,
|
||||
"distanceUnit": "m",
|
||||
@@ -258,8 +258,8 @@
|
||||
"i_min": 0,
|
||||
"i_max": 1,
|
||||
"i_offset": 0,
|
||||
"o_min": 50,
|
||||
"o_max": 400,
|
||||
"o_min": 100,
|
||||
"o_max": 100,
|
||||
"simulator": true,
|
||||
"smooth_method": "mean",
|
||||
"count": "5",
|
||||
@@ -275,7 +275,7 @@
|
||||
"enableLog": false,
|
||||
"logLevel": "warn",
|
||||
"positionVsParent": "upstream",
|
||||
"positionIcon": "",
|
||||
"positionIcon": "\u2192",
|
||||
"hasDistance": false,
|
||||
"distance": 0,
|
||||
"distanceUnit": "m",
|
||||
@@ -301,8 +301,8 @@
|
||||
"i_min": 0,
|
||||
"i_max": 1,
|
||||
"i_offset": 0,
|
||||
"o_min": 800,
|
||||
"o_max": 2200,
|
||||
"o_min": 1300,
|
||||
"o_max": 1300,
|
||||
"simulator": true,
|
||||
"smooth_method": "mean",
|
||||
"count": "5",
|
||||
@@ -318,7 +318,7 @@
|
||||
"enableLog": false,
|
||||
"logLevel": "warn",
|
||||
"positionVsParent": "downstream",
|
||||
"positionIcon": "",
|
||||
"positionIcon": "\u2190",
|
||||
"hasDistance": false,
|
||||
"distance": 0,
|
||||
"distanceUnit": "m",
|
||||
@@ -409,7 +409,7 @@
|
||||
"enableLog": false,
|
||||
"logLevel": "warn",
|
||||
"positionVsParent": "atEquipment",
|
||||
"positionIcon": "",
|
||||
"positionIcon": "\u22a5",
|
||||
"hasDistance": false,
|
||||
"distance": 0,
|
||||
"distanceUnit": "m",
|
||||
@@ -479,8 +479,8 @@
|
||||
"i_min": 0,
|
||||
"i_max": 1,
|
||||
"i_offset": 0,
|
||||
"o_min": 50,
|
||||
"o_max": 400,
|
||||
"o_min": 100,
|
||||
"o_max": 100,
|
||||
"simulator": true,
|
||||
"smooth_method": "mean",
|
||||
"count": "5",
|
||||
@@ -496,7 +496,7 @@
|
||||
"enableLog": false,
|
||||
"logLevel": "warn",
|
||||
"positionVsParent": "upstream",
|
||||
"positionIcon": "",
|
||||
"positionIcon": "\u2192",
|
||||
"hasDistance": false,
|
||||
"distance": 0,
|
||||
"distanceUnit": "m",
|
||||
@@ -522,8 +522,8 @@
|
||||
"i_min": 0,
|
||||
"i_max": 1,
|
||||
"i_offset": 0,
|
||||
"o_min": 800,
|
||||
"o_max": 2200,
|
||||
"o_min": 1300,
|
||||
"o_max": 1300,
|
||||
"simulator": true,
|
||||
"smooth_method": "mean",
|
||||
"count": "5",
|
||||
@@ -539,7 +539,7 @@
|
||||
"enableLog": false,
|
||||
"logLevel": "warn",
|
||||
"positionVsParent": "downstream",
|
||||
"positionIcon": "",
|
||||
"positionIcon": "\u2190",
|
||||
"hasDistance": false,
|
||||
"distance": 0,
|
||||
"distanceUnit": "m",
|
||||
@@ -630,7 +630,7 @@
|
||||
"enableLog": false,
|
||||
"logLevel": "warn",
|
||||
"positionVsParent": "atEquipment",
|
||||
"positionIcon": "",
|
||||
"positionIcon": "\u22a5",
|
||||
"hasDistance": false,
|
||||
"distance": 0,
|
||||
"distanceUnit": "m",
|
||||
@@ -703,7 +703,7 @@
|
||||
"enableLog": false,
|
||||
"logLevel": "warn",
|
||||
"positionVsParent": "atEquipment",
|
||||
"positionIcon": "",
|
||||
"positionIcon": "\u22a5",
|
||||
"hasDistance": false,
|
||||
"distance": 0,
|
||||
"distanceUnit": "m",
|
||||
@@ -845,7 +845,7 @@
|
||||
"enableLog": false,
|
||||
"logLevel": "warn",
|
||||
"positionVsParent": "atEquipment",
|
||||
"positionIcon": "",
|
||||
"positionIcon": "\u22a5",
|
||||
"hasDistance": false,
|
||||
"distance": 0,
|
||||
"distanceUnit": "m",
|
||||
@@ -855,17 +855,16 @@
|
||||
"controlMode": "levelbased",
|
||||
"basinVolume": 30,
|
||||
"basinHeight": 4,
|
||||
"heightInlet": 3.5,
|
||||
"heightOutlet": 0.3,
|
||||
"heightOverflow": 3.8,
|
||||
"inflowLevel": 3.5,
|
||||
"outflowLevel": 0.3,
|
||||
"overflowLevel": 3.8,
|
||||
"inletPipeDiameter": 0.3,
|
||||
"outletPipeDiameter": 0.3,
|
||||
"minLevel": 1.0,
|
||||
"startLevel": 2.0,
|
||||
"stopLevel": 1.0,
|
||||
"minFlowLevel": 2.0,
|
||||
"maxFlowLevel": 3.5,
|
||||
"maxLevel": 3.5,
|
||||
"refHeight": "NAP",
|
||||
"minHeightBasedOn": "inlet",
|
||||
"minHeightBasedOn": "outlet",
|
||||
"basinBottomRef": 0,
|
||||
"staticHead": 12,
|
||||
"maxDischargeHead": 24,
|
||||
@@ -892,7 +891,7 @@
|
||||
"type": "function",
|
||||
"z": "tab_process",
|
||||
"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.');\n// Compute derived metrics\n// Basin capacity = basinVolume (config). Don't hardcode \u2014 read it once.\nif (!context.get('maxVol')) context.set('maxVol', 30.0); // basinVolume from PS config\nconst maxVol = context.get('maxVol');\nconst fillPct = vol != null ? Math.min(100, Math.max(0, 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;",
|
||||
"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.predicted.in.');\nconst qOut = find('flow.predicted.out.');\nconst netFlowRate = find('netFlowRate.predicted.');\n// Compute derived metrics\n// Basin capacity = basinVolume (config). Don't hardcode \u2014 read it once.\nif (!context.get('maxVol')) context.set('maxVol', 30.0); // basinVolume from PS config\nconst maxVol = context.get('maxVol');\nconst fillPct = vol != null ? Math.min(100, Math.max(0, Math.round(Number(vol) / maxVol * 100))) : null;\nconst netM3h = netFlowRate != null ? Number(netFlowRate) * 3600 : null;\nconst seconds = (c.timeleft != null && Number.isFinite(Number(c.timeleft))) ? Number(c.timeleft) : 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 percControl: c.percControl != null ? Number(c.percControl) : null,\n qInNum: qIn != null ? Number(qIn) * 3600 : null,\n qOutNum: qOut != null ? Number(qOut) * 3600 : null,\n};\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
@@ -1896,8 +1895,8 @@
|
||||
"type": "function",
|
||||
"z": "tab_ui",
|
||||
"name": "dispatch PS",
|
||||
"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": 10,
|
||||
"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 p.percControl != null ? {topic: 'PS demand', payload: p.percControl, timestamp: ts} : null,\n p.qInNum != null ? {topic: 'Inflow', payload: p.qInNum, timestamp: ts} : null,\n p.qOutNum != null ? {topic: 'Outflow', payload: p.qOutNum, timestamp: ts} : null,\n];",
|
||||
"outputs": 13,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
@@ -1939,8 +1938,20 @@
|
||||
"gauge_ps_level_long"
|
||||
],
|
||||
[
|
||||
"trend_short_level",
|
||||
"trend_long_level"
|
||||
"trend_short_flow",
|
||||
"trend_long_flow"
|
||||
],
|
||||
[
|
||||
"trend_short_fill",
|
||||
"trend_long_fill"
|
||||
],
|
||||
[
|
||||
"trend_short_flow",
|
||||
"trend_long_flow"
|
||||
],
|
||||
[
|
||||
"trend_short_flow",
|
||||
"trend_long_flow"
|
||||
]
|
||||
]
|
||||
},
|
||||
@@ -4101,49 +4112,5 @@
|
||||
"x": 380,
|
||||
"y": 250,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "setup_pumps_startup",
|
||||
"type": "inject",
|
||||
"z": "tab_setup",
|
||||
"name": "auto-startup all pumps",
|
||||
"props": [
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
},
|
||||
{
|
||||
"p": "payload",
|
||||
"v": "{\"source\":\"GUI\",\"action\":\"execSequence\",\"parameter\":\"startup\"}",
|
||||
"vt": "json"
|
||||
}
|
||||
],
|
||||
"topic": "execSequence",
|
||||
"payload": "{\"source\":\"GUI\",\"action\":\"execSequence\",\"parameter\":\"startup\"}",
|
||||
"payloadType": "json",
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": true,
|
||||
"onceDelay": "4",
|
||||
"x": 120,
|
||||
"y": 350,
|
||||
"wires": [
|
||||
[
|
||||
"lout_setup_station_start"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "lout_setup_station_start",
|
||||
"type": "link out",
|
||||
"z": "tab_setup",
|
||||
"name": "cmd:station-startup",
|
||||
"mode": "link",
|
||||
"links": [
|
||||
"lin_station_start"
|
||||
],
|
||||
"x": 380,
|
||||
"y": 350,
|
||||
"wires": []
|
||||
}
|
||||
]
|
||||
|
||||
Submodule nodes/generalFunctions updated: 693517cc8f...4252292ae1
Submodule nodes/pumpingStation updated: 5e2ebe4d96...a2189457f6
@@ -8,3 +8,7 @@ Local reference manuals used by EVOLV agents while implementing and reviewing No
|
||||
- `manuals/node-red/messages-and-editor-structure.md`: Message shape and HTML/editor/runtime contracts.
|
||||
- `manuals/node-red/flowfuse-ui-chart-manual.md`: FlowFuse `ui-chart` data contract and runtime controls.
|
||||
- `manuals/node-red/flowfuse-dashboard-layout-manual.md`: Compact FlowFuse dashboard layout guidance.
|
||||
- `manuals/nodes/rotatingMachine.md`: rotatingMachine node — operator reference (editor config, topics, ports, state machine, pressure/curve predictions).
|
||||
- `manuals/nodes/measurement.md`: measurement node — operator reference (analog/digital modes, pipeline, smoothing/outliers).
|
||||
|
||||
Note: pumpingStation documentation has moved into its own repo's `wiki/` folder — see [pumpingStation/wiki](https://gitea.wbd-rd.nl/RnD/pumpingStation/src/branch/main/wiki).
|
||||
|
||||
Reference in New Issue
Block a user