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
|
## 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.
|
- **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.
|
- **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.
|
- **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
|
ROW = 80 # standard inter-row pitch
|
||||||
SECTION_GAP = 200 # additional shift between major sections
|
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)
|
# Cross-tab link channel names (the wiring contract)
|
||||||
@@ -356,9 +363,14 @@ def build_process_tab():
|
|||||||
))
|
))
|
||||||
|
|
||||||
# Two measurement sensors (upstream + downstream)
|
# 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")):
|
for j, pos in enumerate(("upstream", "downstream")):
|
||||||
mid = f"meas_{pump}_{pos[0]}"
|
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'}"
|
mid_label = f"PT-{label.split()[1]}-{'Up' if pos == 'upstream' else 'Dn'}"
|
||||||
nodes.append({
|
nodes.append({
|
||||||
"id": mid, "type": "measurement", "z": TAB_PROCESS,
|
"id": mid, "type": "measurement", "z": TAB_PROCESS,
|
||||||
@@ -366,7 +378,7 @@ def build_process_tab():
|
|||||||
"mode": "analog", "channels": "[]",
|
"mode": "analog", "channels": "[]",
|
||||||
"scaling": False,
|
"scaling": False,
|
||||||
"i_min": 0, "i_max": 1, "i_offset": 0,
|
"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,
|
"simulator": True,
|
||||||
"smooth_method": "mean", "count": "5",
|
"smooth_method": "mean", "count": "5",
|
||||||
"processOutputFormat": "process", "dbaseOutputFormat": "influxdb",
|
"processOutputFormat": "process", "dbaseOutputFormat": "influxdb",
|
||||||
@@ -375,7 +387,7 @@ def build_process_tab():
|
|||||||
"assetType": "pressure", "model": "vega-pressure-10",
|
"assetType": "pressure", "model": "vega-pressure-10",
|
||||||
"unit": "mbar", "assetTagNumber": f"PT-{i+1}-{pos[0].upper()}",
|
"unit": "mbar", "assetTagNumber": f"PT-{i+1}-{pos[0].upper()}",
|
||||||
"enableLog": False, "logLevel": "warn",
|
"enableLog": False, "logLevel": "warn",
|
||||||
"positionVsParent": pos, "positionIcon": "",
|
"positionVsParent": pos, "positionIcon": POSITION_ICON.get(pos, ""),
|
||||||
"hasDistance": False, "distance": 0, "distanceUnit": "m",
|
"hasDistance": False, "distance": 0, "distanceUnit": "m",
|
||||||
"distanceDescription": "",
|
"distanceDescription": "",
|
||||||
"x": LANE_X[1], "y": y_section + 40 + j * 50,
|
"x": LANE_X[1], "y": y_section + 40 + j * 50,
|
||||||
@@ -424,7 +436,7 @@ def build_process_tab():
|
|||||||
"curvePressureUnit": "mbar", "curveFlowUnit": "m3/h",
|
"curvePressureUnit": "mbar", "curveFlowUnit": "m3/h",
|
||||||
"curvePowerUnit": "kW", "curveControlUnit": "%",
|
"curvePowerUnit": "kW", "curveControlUnit": "%",
|
||||||
"enableLog": False, "logLevel": "warn",
|
"enableLog": False, "logLevel": "warn",
|
||||||
"positionVsParent": "atEquipment", "positionIcon": "",
|
"positionVsParent": "atEquipment", "positionIcon": POSITION_ICON["atEquipment"],
|
||||||
"hasDistance": False, "distance": 0, "distanceUnit": "m",
|
"hasDistance": False, "distance": 0, "distanceUnit": "m",
|
||||||
"distanceDescription": "",
|
"distanceDescription": "",
|
||||||
"x": LANE_X[3], "y": y_section + 80,
|
"x": LANE_X[3], "y": y_section + 80,
|
||||||
@@ -493,7 +505,7 @@ def build_process_tab():
|
|||||||
"assetType": "machinegroupcontrol",
|
"assetType": "machinegroupcontrol",
|
||||||
"model": "default", "unit": "m3/h", "supplier": "evolv",
|
"model": "default", "unit": "m3/h", "supplier": "evolv",
|
||||||
"enableLog": False, "logLevel": "warn",
|
"enableLog": False, "logLevel": "warn",
|
||||||
"positionVsParent": "atEquipment", "positionIcon": "",
|
"positionVsParent": "atEquipment", "positionIcon": POSITION_ICON["atEquipment"],
|
||||||
"hasDistance": False, "distance": 0, "distanceUnit": "m",
|
"hasDistance": False, "distance": 0, "distanceUnit": "m",
|
||||||
"distanceDescription": "",
|
"distanceDescription": "",
|
||||||
"processOutputFormat": "process", "dbaseOutputFormat": "influxdb",
|
"processOutputFormat": "process", "dbaseOutputFormat": "influxdb",
|
||||||
@@ -572,7 +584,7 @@ def build_process_tab():
|
|||||||
"category": "station", "assetType": "pumpingstation",
|
"category": "station", "assetType": "pumpingstation",
|
||||||
"model": "default", "unit": "m3/s", "supplier": "evolv",
|
"model": "default", "unit": "m3/s", "supplier": "evolv",
|
||||||
"enableLog": False, "logLevel": "warn",
|
"enableLog": False, "logLevel": "warn",
|
||||||
"positionVsParent": "atEquipment", "positionIcon": "",
|
"positionVsParent": "atEquipment", "positionIcon": POSITION_ICON["atEquipment"],
|
||||||
"hasDistance": False, "distance": 0, "distanceUnit": "m",
|
"hasDistance": False, "distance": 0, "distanceUnit": "m",
|
||||||
"distanceDescription": "",
|
"distanceDescription": "",
|
||||||
"processOutputFormat": "process", "dbaseOutputFormat": "influxdb",
|
"processOutputFormat": "process", "dbaseOutputFormat": "influxdb",
|
||||||
@@ -586,19 +598,18 @@ def build_process_tab():
|
|||||||
"controlMode": "levelbased",
|
"controlMode": "levelbased",
|
||||||
"basinVolume": 30,
|
"basinVolume": 30,
|
||||||
"basinHeight": 4,
|
"basinHeight": 4,
|
||||||
"heightInlet": 3.5,
|
"inflowLevel": 3.5,
|
||||||
"heightOutlet": 0.3,
|
"outflowLevel": 0.3,
|
||||||
"heightOverflow": 3.8,
|
"overflowLevel": 3.8,
|
||||||
"inletPipeDiameter": 0.3,
|
"inletPipeDiameter": 0.3,
|
||||||
"outletPipeDiameter": 0.3,
|
"outletPipeDiameter": 0.3,
|
||||||
# Level-based control thresholds
|
# Level-based control thresholds
|
||||||
"startLevel": 2.0, # pumps ON above 2.0 m (50% of height)
|
"minLevel": 1.0, # pumps OFF below 1.0 m (25% of height)
|
||||||
"stopLevel": 1.0, # pumps OFF below 1.0 m (25% of height)
|
"startLevel": 2.0, # 0% pump demand — ramp begins here
|
||||||
"minFlowLevel": 2.0, # 0% pump demand at startLevel (must match startLevel!)
|
"maxLevel": 3.5, # 100% pump demand at this level
|
||||||
"maxFlowLevel": 3.5, # 100% pump demand at this level
|
|
||||||
# Hydraulics
|
# Hydraulics
|
||||||
"refHeight": "NAP",
|
"refHeight": "NAP",
|
||||||
"minHeightBasedOn": "inlet",
|
"minHeightBasedOn": "outlet", # basin drains to outlet pipe (0.3 m), not inlet
|
||||||
"basinBottomRef": 0,
|
"basinBottomRef": 0,
|
||||||
"staticHead": 12,
|
"staticHead": 12,
|
||||||
"maxDischargeHead": 24,
|
"maxDischargeHead": 24,
|
||||||
@@ -631,15 +642,16 @@ def build_process_tab():
|
|||||||
"}\n"
|
"}\n"
|
||||||
"const lvl = find('level.predicted.');\n"
|
"const lvl = find('level.predicted.');\n"
|
||||||
"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.predicted.in.');\n"
|
||||||
"const qOut = find('flow.measured.downstream.') || find('flow.measured.out.');\n"
|
"const qOut = find('flow.predicted.out.');\n"
|
||||||
|
"const netFlowRate = find('netFlowRate.predicted.');\n"
|
||||||
"// Compute derived metrics\n"
|
"// Compute derived metrics\n"
|
||||||
"// Basin capacity = basinVolume (config). Don't hardcode — read it once.\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"
|
"if (!context.get('maxVol')) context.set('maxVol', 30.0); // basinVolume from PS config\n"
|
||||||
"const maxVol = context.get('maxVol');\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 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 netM3h = netFlowRate != null ? Number(netFlowRate) * 3600 : null;\n"
|
||||||
"const seconds = (c.seconds != null && Number.isFinite(Number(c.seconds))) ? Number(c.seconds) : 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"
|
"const timeStr = seconds != null ? (seconds > 60 ? Math.round(seconds/60) + ' min' : Math.round(seconds) + ' s') : 'n/a';\n"
|
||||||
"msg.payload = {\n"
|
"msg.payload = {\n"
|
||||||
" direction: c.direction || 'steady',\n"
|
" direction: c.direction || 'steady',\n"
|
||||||
@@ -655,6 +667,9 @@ def build_process_tab():
|
|||||||
" volumeNum: vol != null ? Number(vol) : null,\n"
|
" volumeNum: vol != null ? Number(vol) : null,\n"
|
||||||
" fillPctNum: fillPct,\n"
|
" fillPctNum: fillPct,\n"
|
||||||
" netFlowNum: netM3h,\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"
|
"};\n"
|
||||||
"return msg;",
|
"return msg;",
|
||||||
outputs=1, wires=[["lout_evt_ps"]],
|
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.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.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.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=[
|
wires=[
|
||||||
["ui_ps_direction"],
|
["ui_ps_direction"],
|
||||||
["ui_ps_level"],
|
["ui_ps_level"],
|
||||||
@@ -934,7 +952,10 @@ def build_ui_tab():
|
|||||||
# Trend + gauge outputs — split level and fill to separate charts
|
# 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_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", "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) =====
|
# ===== Basin charts + gauges (fill %, level, net flow) =====
|
||||||
# Gauge segment definitions (reused for both pages)
|
# Gauge segment definitions (reused for both pages)
|
||||||
TANK_SEGMENTS = [
|
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": "#ff9800", "from": 1.0}, # orange: between stop and start
|
||||||
{"color": "#2196f3", "from": 2.0}, # blue: normal operating (startLevel)
|
{"color": "#2196f3", "from": 2.0}, # blue: normal operating (startLevel)
|
||||||
{"color": "#ff9800", "from": 3.5}, # orange: approaching overflow
|
{"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 = [
|
FILL_SEGMENTS = [
|
||||||
{"color": "#f44336", "from": 0},
|
{"color": "#f44336", "from": 0},
|
||||||
@@ -1314,19 +1335,8 @@ def build_setup_tab():
|
|||||||
CH_MODE, target_in_ids=["lin_mode"]
|
CH_MODE, target_in_ids=["lin_mode"]
|
||||||
))
|
))
|
||||||
|
|
||||||
y = 350
|
# Auto-startup removed: the MGC starts pumps on demand from the PS.
|
||||||
nodes.append(inject(
|
# Starting pumps before the PS requests them causes flow below startLevel.
|
||||||
"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"]
|
|
||||||
))
|
|
||||||
|
|
||||||
# (Random demand removed — sinus inflow drives the demo automatically.
|
# (Random demand removed — sinus inflow drives the demo automatically.
|
||||||
# No explicit "random on" inject needed.)
|
# No explicit "random on" inject needed.)
|
||||||
|
|||||||
@@ -37,8 +37,8 @@
|
|||||||
"i_min": 0,
|
"i_min": 0,
|
||||||
"i_max": 1,
|
"i_max": 1,
|
||||||
"i_offset": 0,
|
"i_offset": 0,
|
||||||
"o_min": 50,
|
"o_min": 100,
|
||||||
"o_max": 400,
|
"o_max": 100,
|
||||||
"simulator": true,
|
"simulator": true,
|
||||||
"smooth_method": "mean",
|
"smooth_method": "mean",
|
||||||
"count": "5",
|
"count": "5",
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
"enableLog": false,
|
"enableLog": false,
|
||||||
"logLevel": "warn",
|
"logLevel": "warn",
|
||||||
"positionVsParent": "upstream",
|
"positionVsParent": "upstream",
|
||||||
"positionIcon": "",
|
"positionIcon": "\u2192",
|
||||||
"hasDistance": false,
|
"hasDistance": false,
|
||||||
"distance": 0,
|
"distance": 0,
|
||||||
"distanceUnit": "m",
|
"distanceUnit": "m",
|
||||||
@@ -80,8 +80,8 @@
|
|||||||
"i_min": 0,
|
"i_min": 0,
|
||||||
"i_max": 1,
|
"i_max": 1,
|
||||||
"i_offset": 0,
|
"i_offset": 0,
|
||||||
"o_min": 800,
|
"o_min": 1300,
|
||||||
"o_max": 2200,
|
"o_max": 1300,
|
||||||
"simulator": true,
|
"simulator": true,
|
||||||
"smooth_method": "mean",
|
"smooth_method": "mean",
|
||||||
"count": "5",
|
"count": "5",
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
"enableLog": false,
|
"enableLog": false,
|
||||||
"logLevel": "warn",
|
"logLevel": "warn",
|
||||||
"positionVsParent": "downstream",
|
"positionVsParent": "downstream",
|
||||||
"positionIcon": "",
|
"positionIcon": "\u2190",
|
||||||
"hasDistance": false,
|
"hasDistance": false,
|
||||||
"distance": 0,
|
"distance": 0,
|
||||||
"distanceUnit": "m",
|
"distanceUnit": "m",
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"enableLog": false,
|
"enableLog": false,
|
||||||
"logLevel": "warn",
|
"logLevel": "warn",
|
||||||
"positionVsParent": "atEquipment",
|
"positionVsParent": "atEquipment",
|
||||||
"positionIcon": "",
|
"positionIcon": "\u22a5",
|
||||||
"hasDistance": false,
|
"hasDistance": false,
|
||||||
"distance": 0,
|
"distance": 0,
|
||||||
"distanceUnit": "m",
|
"distanceUnit": "m",
|
||||||
@@ -258,8 +258,8 @@
|
|||||||
"i_min": 0,
|
"i_min": 0,
|
||||||
"i_max": 1,
|
"i_max": 1,
|
||||||
"i_offset": 0,
|
"i_offset": 0,
|
||||||
"o_min": 50,
|
"o_min": 100,
|
||||||
"o_max": 400,
|
"o_max": 100,
|
||||||
"simulator": true,
|
"simulator": true,
|
||||||
"smooth_method": "mean",
|
"smooth_method": "mean",
|
||||||
"count": "5",
|
"count": "5",
|
||||||
@@ -275,7 +275,7 @@
|
|||||||
"enableLog": false,
|
"enableLog": false,
|
||||||
"logLevel": "warn",
|
"logLevel": "warn",
|
||||||
"positionVsParent": "upstream",
|
"positionVsParent": "upstream",
|
||||||
"positionIcon": "",
|
"positionIcon": "\u2192",
|
||||||
"hasDistance": false,
|
"hasDistance": false,
|
||||||
"distance": 0,
|
"distance": 0,
|
||||||
"distanceUnit": "m",
|
"distanceUnit": "m",
|
||||||
@@ -301,8 +301,8 @@
|
|||||||
"i_min": 0,
|
"i_min": 0,
|
||||||
"i_max": 1,
|
"i_max": 1,
|
||||||
"i_offset": 0,
|
"i_offset": 0,
|
||||||
"o_min": 800,
|
"o_min": 1300,
|
||||||
"o_max": 2200,
|
"o_max": 1300,
|
||||||
"simulator": true,
|
"simulator": true,
|
||||||
"smooth_method": "mean",
|
"smooth_method": "mean",
|
||||||
"count": "5",
|
"count": "5",
|
||||||
@@ -318,7 +318,7 @@
|
|||||||
"enableLog": false,
|
"enableLog": false,
|
||||||
"logLevel": "warn",
|
"logLevel": "warn",
|
||||||
"positionVsParent": "downstream",
|
"positionVsParent": "downstream",
|
||||||
"positionIcon": "",
|
"positionIcon": "\u2190",
|
||||||
"hasDistance": false,
|
"hasDistance": false,
|
||||||
"distance": 0,
|
"distance": 0,
|
||||||
"distanceUnit": "m",
|
"distanceUnit": "m",
|
||||||
@@ -409,7 +409,7 @@
|
|||||||
"enableLog": false,
|
"enableLog": false,
|
||||||
"logLevel": "warn",
|
"logLevel": "warn",
|
||||||
"positionVsParent": "atEquipment",
|
"positionVsParent": "atEquipment",
|
||||||
"positionIcon": "",
|
"positionIcon": "\u22a5",
|
||||||
"hasDistance": false,
|
"hasDistance": false,
|
||||||
"distance": 0,
|
"distance": 0,
|
||||||
"distanceUnit": "m",
|
"distanceUnit": "m",
|
||||||
@@ -479,8 +479,8 @@
|
|||||||
"i_min": 0,
|
"i_min": 0,
|
||||||
"i_max": 1,
|
"i_max": 1,
|
||||||
"i_offset": 0,
|
"i_offset": 0,
|
||||||
"o_min": 50,
|
"o_min": 100,
|
||||||
"o_max": 400,
|
"o_max": 100,
|
||||||
"simulator": true,
|
"simulator": true,
|
||||||
"smooth_method": "mean",
|
"smooth_method": "mean",
|
||||||
"count": "5",
|
"count": "5",
|
||||||
@@ -496,7 +496,7 @@
|
|||||||
"enableLog": false,
|
"enableLog": false,
|
||||||
"logLevel": "warn",
|
"logLevel": "warn",
|
||||||
"positionVsParent": "upstream",
|
"positionVsParent": "upstream",
|
||||||
"positionIcon": "",
|
"positionIcon": "\u2192",
|
||||||
"hasDistance": false,
|
"hasDistance": false,
|
||||||
"distance": 0,
|
"distance": 0,
|
||||||
"distanceUnit": "m",
|
"distanceUnit": "m",
|
||||||
@@ -522,8 +522,8 @@
|
|||||||
"i_min": 0,
|
"i_min": 0,
|
||||||
"i_max": 1,
|
"i_max": 1,
|
||||||
"i_offset": 0,
|
"i_offset": 0,
|
||||||
"o_min": 800,
|
"o_min": 1300,
|
||||||
"o_max": 2200,
|
"o_max": 1300,
|
||||||
"simulator": true,
|
"simulator": true,
|
||||||
"smooth_method": "mean",
|
"smooth_method": "mean",
|
||||||
"count": "5",
|
"count": "5",
|
||||||
@@ -539,7 +539,7 @@
|
|||||||
"enableLog": false,
|
"enableLog": false,
|
||||||
"logLevel": "warn",
|
"logLevel": "warn",
|
||||||
"positionVsParent": "downstream",
|
"positionVsParent": "downstream",
|
||||||
"positionIcon": "",
|
"positionIcon": "\u2190",
|
||||||
"hasDistance": false,
|
"hasDistance": false,
|
||||||
"distance": 0,
|
"distance": 0,
|
||||||
"distanceUnit": "m",
|
"distanceUnit": "m",
|
||||||
@@ -630,7 +630,7 @@
|
|||||||
"enableLog": false,
|
"enableLog": false,
|
||||||
"logLevel": "warn",
|
"logLevel": "warn",
|
||||||
"positionVsParent": "atEquipment",
|
"positionVsParent": "atEquipment",
|
||||||
"positionIcon": "",
|
"positionIcon": "\u22a5",
|
||||||
"hasDistance": false,
|
"hasDistance": false,
|
||||||
"distance": 0,
|
"distance": 0,
|
||||||
"distanceUnit": "m",
|
"distanceUnit": "m",
|
||||||
@@ -703,7 +703,7 @@
|
|||||||
"enableLog": false,
|
"enableLog": false,
|
||||||
"logLevel": "warn",
|
"logLevel": "warn",
|
||||||
"positionVsParent": "atEquipment",
|
"positionVsParent": "atEquipment",
|
||||||
"positionIcon": "",
|
"positionIcon": "\u22a5",
|
||||||
"hasDistance": false,
|
"hasDistance": false,
|
||||||
"distance": 0,
|
"distance": 0,
|
||||||
"distanceUnit": "m",
|
"distanceUnit": "m",
|
||||||
@@ -845,7 +845,7 @@
|
|||||||
"enableLog": false,
|
"enableLog": false,
|
||||||
"logLevel": "warn",
|
"logLevel": "warn",
|
||||||
"positionVsParent": "atEquipment",
|
"positionVsParent": "atEquipment",
|
||||||
"positionIcon": "",
|
"positionIcon": "\u22a5",
|
||||||
"hasDistance": false,
|
"hasDistance": false,
|
||||||
"distance": 0,
|
"distance": 0,
|
||||||
"distanceUnit": "m",
|
"distanceUnit": "m",
|
||||||
@@ -855,17 +855,16 @@
|
|||||||
"controlMode": "levelbased",
|
"controlMode": "levelbased",
|
||||||
"basinVolume": 30,
|
"basinVolume": 30,
|
||||||
"basinHeight": 4,
|
"basinHeight": 4,
|
||||||
"heightInlet": 3.5,
|
"inflowLevel": 3.5,
|
||||||
"heightOutlet": 0.3,
|
"outflowLevel": 0.3,
|
||||||
"heightOverflow": 3.8,
|
"overflowLevel": 3.8,
|
||||||
"inletPipeDiameter": 0.3,
|
"inletPipeDiameter": 0.3,
|
||||||
"outletPipeDiameter": 0.3,
|
"outletPipeDiameter": 0.3,
|
||||||
|
"minLevel": 1.0,
|
||||||
"startLevel": 2.0,
|
"startLevel": 2.0,
|
||||||
"stopLevel": 1.0,
|
"maxLevel": 3.5,
|
||||||
"minFlowLevel": 2.0,
|
|
||||||
"maxFlowLevel": 3.5,
|
|
||||||
"refHeight": "NAP",
|
"refHeight": "NAP",
|
||||||
"minHeightBasedOn": "inlet",
|
"minHeightBasedOn": "outlet",
|
||||||
"basinBottomRef": 0,
|
"basinBottomRef": 0,
|
||||||
"staticHead": 12,
|
"staticHead": 12,
|
||||||
"maxDischargeHead": 24,
|
"maxDischargeHead": 24,
|
||||||
@@ -892,7 +891,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.');\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,
|
"outputs": 1,
|
||||||
"noerr": 0,
|
"noerr": 0,
|
||||||
"initialize": "",
|
"initialize": "",
|
||||||
@@ -1896,8 +1895,8 @@
|
|||||||
"type": "function",
|
"type": "function",
|
||||||
"z": "tab_ui",
|
"z": "tab_ui",
|
||||||
"name": "dispatch PS",
|
"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];",
|
"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": 10,
|
"outputs": 13,
|
||||||
"noerr": 0,
|
"noerr": 0,
|
||||||
"initialize": "",
|
"initialize": "",
|
||||||
"finalize": "",
|
"finalize": "",
|
||||||
@@ -1939,8 +1938,20 @@
|
|||||||
"gauge_ps_level_long"
|
"gauge_ps_level_long"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"trend_short_level",
|
"trend_short_flow",
|
||||||
"trend_long_level"
|
"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,
|
"x": 380,
|
||||||
"y": 250,
|
"y": 250,
|
||||||
"wires": []
|
"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/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-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/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