Replaces the static parameters-diagram-above-form-rows layout with a single interactive SVG where every threshold input sits directly on the tank at its proportional y-position. Typing a value repositions the corresponding line + input + label live. What moved into the diagram (via <foreignObject> holding real <input> elements with their existing node-input-* IDs so Node-RED save/restore is untouched): basinHeight — top of tank (fixed at rim by definition) overflowLevel — weir crest (red, dashed) maxLevel — 100 % demand line (orange, dashed) startLevel — ramp-start line (green, dashed) minLevel — MGC-shutdown line (purple, dashed) inflowLevel — Inlet arrow + input on left outflowLevel — Outlet arrow + input on right dryRunLevel — read-only, computed from outflow × (1+dryRunPct/100) Also in the diagram: - Dead-volume band fills the area below outflowLevel dynamically - Warning ribbon appears below the tank if ordering invariants break (mirrors specificClass._validateThresholdOrdering) - All positions scale against the user's basinHeight; if empty, a default 5 m scale is used just to keep the diagram readable What stayed as regular form rows: - Basin Volume (m³) — not a height, can't be placed on a y-axis - minLevel / startLevel / maxLevel were in the Control Strategy > Level-based section; removed from there and moved into the diagram (the level-based subsection now contains a one-line pointer) - Safety % inputs (dryRun, overfill) stay in the Safety section with their derived-level readouts, now synced with the diagram No schema changes, no field additions, no behaviour changes in the runtime. Pure editor-UX. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
535 lines
24 KiB
HTML
535 lines
24 KiB
HTML
<!--
|
|
| S88-niveau | Primair (blokkleur) | Tekstkleur |
|
|
| ---------------------- | ------------------- | ---------- |
|
|
| **Area** | `#0f52a5` | wit |
|
|
| **Process Cell** | `#0c99d9` | wit |
|
|
| **Unit** | `#50a8d9` | zwart |
|
|
| **Equipment (Module)** | `#86bbdd` | zwart |
|
|
| **Control Module** | `#a9daee` | zwart |
|
|
|
|
-->
|
|
<script src="/pumpingStation/menu.js"></script> <!-- Load the menu script for dynamic dropdowns -->
|
|
<script src="/pumpingStation/configData.js"></script> <!-- Load the config script for node information -->
|
|
|
|
<script>//test
|
|
RED.nodes.registerType("pumpingStation", {
|
|
category: "EVOLV",
|
|
color: "#0c99d9", // color for the node based on the S88 schema
|
|
defaults: {
|
|
name: { value: "" },
|
|
|
|
// Define station-specific properties
|
|
simulator: { value: false },
|
|
basinVolume: { value: 1 }, // m³, total empty basin
|
|
basinHeight: { value: 1 }, // m, floor to top
|
|
inflowLevel: { value: 0.8 }, // m, centre of inlet pipe above floor
|
|
outflowLevel: { value: 0.2 }, // m, centre of outlet pipe above floor
|
|
overflowLevel: { value: 0.9 }, // m, overflow elevation
|
|
defaultFluid: { value: "wastewater" },
|
|
inletPipeDiameter: { value: 0.3 }, // m
|
|
outletPipeDiameter: { value: 0.3 }, // m
|
|
pipelineLength: { value: 80 }, // m
|
|
maxDischargeHead: { value: 24 }, // m
|
|
staticHead: { value: 12 }, // m
|
|
maxInflowRate: { value: 200 }, // m³/h
|
|
temperatureReferenceDegC: { value: 15 },
|
|
timeleftToFullOrEmptyThresholdSeconds:{value:0}, // time threshold to safeguard starting or stopping pumps in seconds
|
|
enableDryRunProtection: { value: true },
|
|
enableOverfillProtection: { value: true },
|
|
dryRunThresholdPercent: { value: 2 },
|
|
overfillThresholdPercent: { value: 98 },
|
|
minHeightBasedOn: { value: "outlet" }, // basis for minimum height check: inlet or outlet
|
|
processOutputFormat: { value: "process" },
|
|
dbaseOutputFormat: { value: "influxdb" },
|
|
|
|
// Advanced reference information
|
|
refHeight: { value: "NAP" }, // reference height
|
|
basinBottomRef: { value: 1 }, // absolute elevation of basin floor
|
|
|
|
//define asset properties
|
|
uuid: { value: "" },
|
|
supplier: { value: "" },
|
|
category: { value: "" },
|
|
assetType: { value: "" },
|
|
model: { value: "" },
|
|
unit: { value: "" },
|
|
|
|
//logger properties
|
|
enableLog: { value: false },
|
|
logLevel: { value: "error" },
|
|
|
|
//physicalAspect
|
|
positionVsParent: { value: "" },
|
|
positionIcon: { value: "" },
|
|
hasDistance: { value: false },
|
|
distance: { value: 0 },
|
|
distanceUnit: { value: "m" },
|
|
distanceDescription: { value: "" },
|
|
|
|
// control strategy
|
|
controlMode: { value: "none" },
|
|
startLevel: { value: null },
|
|
minLevel: { value: null },
|
|
maxLevel: { value: null },
|
|
flowSetpoint: { value: null },
|
|
flowDeadband: { value: null }
|
|
|
|
},
|
|
|
|
inputs: 1,
|
|
outputs: 3,
|
|
inputLabels: ["Input"],
|
|
outputLabels: ["process", "dbase", "parent"],
|
|
icon: "font-awesome/fa-tint",
|
|
|
|
label: function () {
|
|
return this.positionIcon + " PumpingStation";
|
|
},
|
|
|
|
oneditprepare: function() {
|
|
const waitForMenuData = () => {
|
|
if (window.EVOLV?.nodes?.pumpingStation?.initEditor) {
|
|
window.EVOLV.nodes.pumpingStation.initEditor(this);
|
|
} else {
|
|
setTimeout(waitForMenuData, 50);
|
|
}
|
|
};
|
|
// Wait for the menu data to be ready before initializing the editor
|
|
waitForMenuData();
|
|
|
|
// NODE SPECIFIC
|
|
document.getElementById("node-input-basinVolume");
|
|
document.getElementById("node-input-basinHeight");
|
|
document.getElementById("node-input-inflowLevel");
|
|
document.getElementById("node-input-outflowLevel");
|
|
document.getElementById("node-input-overflowLevel");
|
|
document.getElementById("node-input-refHeight");
|
|
document.getElementById("node-input-basinBottomRef");
|
|
|
|
const refHeightEl = document.getElementById("node-input-refHeight");
|
|
if (refHeightEl) {
|
|
refHeightEl.value = this.refHeight || "NAP";
|
|
}
|
|
|
|
const minHeightBasedOnEl = document.getElementById("node-input-minHeightBasedOn");
|
|
if (minHeightBasedOnEl) {
|
|
minHeightBasedOnEl.value = this.minHeightBasedOn;
|
|
}
|
|
|
|
const dryRunToggle = document.getElementById("node-input-enableDryRunProtection");
|
|
const dryRunPercent = document.getElementById("node-input-dryRunThresholdPercent");
|
|
const overfillToggle = document.getElementById("node-input-enableOverfillProtection");
|
|
const overfillPercent = document.getElementById("node-input-overfillThresholdPercent");
|
|
|
|
const toggleInput = (toggleEl, inputEl) => {
|
|
if (!toggleEl || !inputEl) { return; }
|
|
inputEl.disabled = !toggleEl.checked;
|
|
inputEl.parentElement.classList.toggle('disabled', inputEl.disabled);
|
|
};
|
|
|
|
if (dryRunToggle && dryRunPercent) {
|
|
dryRunToggle.checked = !!this.enableDryRunProtection;
|
|
dryRunPercent.value = Number.isFinite(this.dryRunThresholdPercent) ? this.dryRunThresholdPercent : 2;
|
|
dryRunToggle.addEventListener('change', () => toggleInput(dryRunToggle, dryRunPercent));
|
|
toggleInput(dryRunToggle, dryRunPercent);
|
|
}
|
|
|
|
if (overfillToggle && overfillPercent) {
|
|
overfillToggle.checked = !!this.enableOverfillProtection;
|
|
overfillPercent.value = Number.isFinite(this.overfillThresholdPercent) ? this.overfillThresholdPercent : 98;
|
|
overfillToggle.addEventListener('change', () => toggleInput(overfillToggle, overfillPercent));
|
|
toggleInput(overfillToggle, overfillPercent);
|
|
}
|
|
|
|
const timeLeftInput = document.getElementById("node-input-timeleftToFullOrEmptyThresholdSeconds");
|
|
if (timeLeftInput) {
|
|
timeLeftInput.value = Number.isFinite(this.timeleftToFullOrEmptyThresholdSeconds)
|
|
? this.timeleftToFullOrEmptyThresholdSeconds
|
|
: 0;
|
|
}
|
|
|
|
// control mode toggle UI
|
|
const toggleModeSections = (val) => {
|
|
document.querySelectorAll('.ps-mode-section').forEach((el) => el.style.display = 'none');
|
|
const active = document.getElementById(`ps-mode-${val}`);
|
|
if (active) active.style.display = '';
|
|
};
|
|
|
|
const modeSelect = document.getElementById('node-input-controlMode');
|
|
if (modeSelect) {
|
|
modeSelect.value = this.controlMode || 'none';
|
|
toggleModeSections(modeSelect.value);
|
|
modeSelect.addEventListener('change', (e) => toggleModeSections(e.target.value));
|
|
}
|
|
|
|
const setNumberField = (id, val) => {
|
|
const el = document.getElementById(id);
|
|
if (el) el.value = Number.isFinite(val) ? val : '';
|
|
};
|
|
|
|
setNumberField('node-input-startLevel', this.startLevel);
|
|
setNumberField('node-input-minLevel', this.minLevel);
|
|
setNumberField('node-input-maxLevel', this.maxLevel);
|
|
setNumberField('node-input-flowSetpoint', this.flowSetpoint);
|
|
setNumberField('node-input-flowDeadband', this.flowDeadband);
|
|
|
|
// Interactive diagram: place every threshold line/input at its
|
|
// proportional y on the tank, plus compute derived safety levels
|
|
// (dryRunLevel, overfillLevel) that are shown both in the diagram
|
|
// and next to the safety-% fields. Same formulas as
|
|
// specificClass._validateThresholdOrdering.
|
|
const DIAG = { topY: 40, botY: 380 };
|
|
const fNum = (id) => {
|
|
const v = parseFloat(document.getElementById(`node-input-${id}`)?.value);
|
|
return Number.isFinite(v) ? v : null;
|
|
};
|
|
const yForLevel = (val, basinH) => {
|
|
if (val == null || !basinH) return null;
|
|
const y = DIAG.botY - (val / basinH) * (DIAG.botY - DIAG.topY);
|
|
return Math.max(DIAG.topY - 8, Math.min(DIAG.botY + 8, y));
|
|
};
|
|
const placeRow = (id, y) => {
|
|
if (y == null) return;
|
|
const line = document.getElementById(`ps-line-${id}`);
|
|
const label = document.getElementById(`ps-label-${id}`);
|
|
const unit = document.getElementById(`ps-unit-${id}`);
|
|
const fo = document.getElementById(`ps-fo-${id}`);
|
|
const sub = document.getElementById(`ps-sub-${id}`);
|
|
if (line) { line.setAttribute('y1', y); line.setAttribute('y2', y); }
|
|
if (label) label.setAttribute('y', y + 4);
|
|
if (unit) unit.setAttribute('y', y + 4);
|
|
if (fo) fo.setAttribute('y', y - 11);
|
|
if (sub) sub.setAttribute('y', y + 15);
|
|
};
|
|
const redraw = () => {
|
|
const basinH = fNum('basinHeight') || 5;
|
|
placeRow('overflowLevel', yForLevel(fNum('overflowLevel'), basinH));
|
|
placeRow('maxLevel', yForLevel(fNum('maxLevel'), basinH));
|
|
placeRow('startLevel', yForLevel(fNum('startLevel'), basinH));
|
|
placeRow('minLevel', yForLevel(fNum('minLevel'), basinH));
|
|
placeRow('inflowLevel', yForLevel(fNum('inflowLevel'), basinH));
|
|
const outflowY = yForLevel(fNum('outflowLevel'), basinH);
|
|
placeRow('outflowLevel', outflowY);
|
|
// Dead-volume band fills from outflowLevel down to the floor
|
|
const deadvol = document.getElementById('ps-deadvol');
|
|
if (deadvol && outflowY != null) {
|
|
deadvol.setAttribute('y', outflowY);
|
|
deadvol.setAttribute('height', Math.max(0, DIAG.botY - outflowY));
|
|
}
|
|
// Derived dryRunLevel (safety, from %)
|
|
const basedOn = document.getElementById('node-input-minHeightBasedOn')?.value || 'outlet';
|
|
const refLow = basedOn === 'inlet' ? fNum('inflowLevel') : fNum('outflowLevel');
|
|
const dryPct = fNum('dryRunThresholdPercent');
|
|
const ovfPct = fNum('overfillThresholdPercent');
|
|
const ovf = fNum('overflowLevel');
|
|
const dryLvl = (refLow != null && dryPct != null) ? refLow * (1 + dryPct / 100) : null;
|
|
const ovfLvl = (ovf != null && ovfPct != null) ? ovf * (ovfPct / 100) : null;
|
|
placeRow('dryRunLevel', yForLevel(dryLvl, basinH));
|
|
const dryLbl = document.getElementById('ps-label-dryRunLevel');
|
|
if (dryLbl) dryLbl.textContent = dryLvl != null
|
|
? `dryRunLevel ≈ ${dryLvl.toFixed(2)} m (safety — from %)`
|
|
: 'dryRunLevel ≈ — m (safety — from %)';
|
|
// Safety-section readouts (same values, second view)
|
|
const d1 = document.getElementById('derived-dryRunLevel');
|
|
if (d1) d1.textContent = dryLvl != null ? `→ dryRunLevel ≈ ${dryLvl.toFixed(2)} m` : '→ dryRunLevel ≈ — m';
|
|
const d2 = document.getElementById('derived-overfillLevel');
|
|
if (d2) d2.textContent = ovfLvl != null ? `→ overfillLevel ≈ ${ovfLvl.toFixed(2)} m` : '→ overfillLevel ≈ — m';
|
|
// Ordering warning ribbon
|
|
const warn = document.getElementById('ps-warning');
|
|
const issues = [];
|
|
const pairs = [
|
|
['outflowLevel', 'inflowLevel', '<'],
|
|
['inflowLevel', 'overflowLevel', '<'],
|
|
['minLevel', 'startLevel', '<='],
|
|
['startLevel', 'maxLevel', '<'],
|
|
['maxLevel', 'overflowLevel', '<='],
|
|
];
|
|
for (const [a, b, op] of pairs) {
|
|
const av = fNum(a), bv = fNum(b);
|
|
if (av == null || bv == null) continue;
|
|
if (op === '<' ? !(av < bv) : !(av <= bv)) issues.push(`${a} ${op} ${b}`);
|
|
}
|
|
if (warn) {
|
|
if (issues.length) { warn.setAttribute('visibility', 'visible'); warn.textContent = `⚠ Check ordering: ${issues.join(', ')}`; }
|
|
else { warn.setAttribute('visibility', 'hidden'); }
|
|
}
|
|
};
|
|
['basinHeight','overflowLevel','maxLevel','startLevel','minLevel','inflowLevel','outflowLevel',
|
|
'dryRunThresholdPercent','overfillThresholdPercent','minHeightBasedOn'].forEach((id) => {
|
|
const el = document.getElementById(`node-input-${id}`);
|
|
if (el) { el.addEventListener('input', redraw); el.addEventListener('change', redraw); }
|
|
});
|
|
setTimeout(redraw, 60);
|
|
|
|
//------------------- END OF CUSTOM config UI ELEMENTS ------------------- //
|
|
},
|
|
oneditsave: function () {
|
|
const node = this;
|
|
|
|
//window.EVOLV?.nodes?.pumpingStation?.assetMenu?.saveEditor?.(node);
|
|
window.EVOLV?.nodes?.pumpingStation?.loggerMenu?.saveEditor?.(node);
|
|
window.EVOLV?.nodes?.pumpingStation?.positionMenu?.saveEditor?.(node);
|
|
|
|
//node specific
|
|
node.refHeight = document.getElementById("node-input-refHeight").value || "NAP";
|
|
node.minHeightBasedOn = document.getElementById("node-input-minHeightBasedOn").value || "outlet";
|
|
node.simulator = document.getElementById("node-input-simulator").checked;
|
|
|
|
["basinVolume","basinHeight","inflowLevel","outflowLevel","overflowLevel","basinBottomRef","timeleftToFullOrEmptyThresholdSeconds","dryRunThresholdPercent","overfillThresholdPercent"]
|
|
.forEach(field => {
|
|
node[field] = parseFloat(document.getElementById(`node-input-${field}`).value) || 0;
|
|
});
|
|
|
|
node.refHeight = document.getElementById("node-input-refHeight").value || "";
|
|
node.enableDryRunProtection = document.getElementById("node-input-enableDryRunProtection").checked;
|
|
node.enableOverfillProtection = document.getElementById("node-input-enableOverfillProtection").checked;
|
|
|
|
// control strategy
|
|
node.controlMode = document.getElementById('node-input-controlMode').value || 'none';
|
|
|
|
const parseNum = (id) => parseFloat(document.getElementById(id)?.value);
|
|
node.startLevel = parseNum('node-input-startLevel');
|
|
node.minLevel = parseNum('node-input-minLevel');
|
|
node.maxLevel = parseNum('node-input-maxLevel');
|
|
node.flowSetpoint = parseNum('node-input-flowSetpoint');
|
|
node.flowDeadband = parseNum('node-input-flowDeadband');
|
|
|
|
},
|
|
|
|
});
|
|
</script>
|
|
|
|
<!-- Main UI -->
|
|
|
|
<script type="text/html" data-template-name="pumpingStation">
|
|
|
|
<h4>Simulation</h4>
|
|
<div class="form-row">
|
|
<label for="node-input-simulator"><i class="fa fa-play-circle"></i> Simulator</label>
|
|
<input type="checkbox" id="node-input-simulator" style="width:20px;vertical-align:baseline;" />
|
|
<span>Run station in simulated mode</span>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<h4>Basin parameters</h4>
|
|
<p style="font-size:12px;color:#777;margin:0 0 8px 0;">Heights are measured from the basin floor (0 m). Enter values next to each line — the diagram scales to whatever you enter.</p>
|
|
|
|
<style>
|
|
#ps-basin-diagram input[type=number] {
|
|
width: 100%; height: 20px; box-sizing: border-box;
|
|
font-size: 11px; padding: 1px 4px; margin: 0;
|
|
border: 1px solid #ccc; border-radius: 3px; background: #fff;
|
|
}
|
|
#ps-basin-diagram input[type=number]:focus { outline: 1px solid #0c99d9; border-color: #0c99d9; }
|
|
</style>
|
|
|
|
<svg id="ps-basin-diagram" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 520 430"
|
|
style="display:block;width:100%;max-width:540px;margin:0 0 12px 0;background:#fff;border:1px solid #e5e5e5;border-radius:4px;"
|
|
font-family="Arial,sans-serif" font-size="11">
|
|
<defs>
|
|
<marker id="ps-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="#1F4E79" />
|
|
</marker>
|
|
</defs>
|
|
|
|
<!-- Tank body -->
|
|
<rect x="200" y="40" width="120" height="340" fill="#F0F8FF" stroke="#333" stroke-width="1.5" />
|
|
<!-- Dead-volume band (y + height updated dynamically below outflowLevel) -->
|
|
<rect id="ps-deadvol" x="201" width="118" fill="#AACCE0" />
|
|
|
|
<!-- basinHeight — always at tank rim (y=40 in viewBox coords) -->
|
|
<line id="ps-line-basinHeight" x1="195" y1="40" x2="325" y2="40" stroke="#333" stroke-width="1.5" />
|
|
<text id="ps-label-basinHeight" x="330" y="44" fill="#333">basinHeight</text>
|
|
<foreignObject id="ps-fo-basinHeight" x="425" y="29" width="70" height="22">
|
|
<input xmlns="http://www.w3.org/1999/xhtml" type="number" id="node-input-basinHeight" min="0" step="0.1" />
|
|
</foreignObject>
|
|
<text id="ps-unit-basinHeight" x="500" y="44" fill="#555">m</text>
|
|
|
|
<!-- overflowLevel -->
|
|
<line id="ps-line-overflowLevel" x1="195" x2="325" stroke="#C0392B" stroke-dasharray="4 2" stroke-width="1.5" />
|
|
<text id="ps-label-overflowLevel" x="330" fill="#C0392B">overflowLevel</text>
|
|
<foreignObject id="ps-fo-overflowLevel" x="425" width="70" height="22">
|
|
<input xmlns="http://www.w3.org/1999/xhtml" type="number" id="node-input-overflowLevel" min="0" step="0.01" />
|
|
</foreignObject>
|
|
<text id="ps-unit-overflowLevel" x="500" fill="#555">m</text>
|
|
|
|
<!-- maxLevel -->
|
|
<line id="ps-line-maxLevel" x1="195" x2="325" stroke="#D68910" stroke-dasharray="4 2" stroke-width="1.5" />
|
|
<text id="ps-label-maxLevel" x="330" fill="#D68910">maxLevel</text>
|
|
<foreignObject id="ps-fo-maxLevel" x="425" width="70" height="22">
|
|
<input xmlns="http://www.w3.org/1999/xhtml" type="number" id="node-input-maxLevel" min="0" step="0.01" />
|
|
</foreignObject>
|
|
<text id="ps-unit-maxLevel" x="500" fill="#555">m</text>
|
|
|
|
<!-- startLevel -->
|
|
<line id="ps-line-startLevel" x1="195" x2="325" stroke="#1E8449" stroke-dasharray="4 2" stroke-width="1.5" />
|
|
<text id="ps-label-startLevel" x="330" fill="#1E8449">startLevel</text>
|
|
<foreignObject id="ps-fo-startLevel" x="425" width="70" height="22">
|
|
<input xmlns="http://www.w3.org/1999/xhtml" type="number" id="node-input-startLevel" min="0" step="0.01" />
|
|
</foreignObject>
|
|
<text id="ps-unit-startLevel" x="500" fill="#555">m</text>
|
|
|
|
<!-- Inlet — arrow + input on the left -->
|
|
<line id="ps-line-inflowLevel" x1="140" x2="200" stroke="#1F4E79" stroke-width="2" marker-end="url(#ps-arrow)" />
|
|
<text id="ps-label-inflowLevel" x="135" text-anchor="end" fill="#1F4E79" font-weight="bold">Inlet</text>
|
|
<text id="ps-sub-inflowLevel" x="135" text-anchor="end" fill="#777" font-size="9">bottom of pipe</text>
|
|
<foreignObject id="ps-fo-inflowLevel" x="5" width="70" height="22">
|
|
<input xmlns="http://www.w3.org/1999/xhtml" type="number" id="node-input-inflowLevel" min="0" step="0.01" />
|
|
</foreignObject>
|
|
<text id="ps-unit-inflowLevel" x="80" fill="#555">m</text>
|
|
|
|
<!-- minLevel -->
|
|
<line id="ps-line-minLevel" x1="195" x2="325" stroke="#6C3483" stroke-dasharray="4 2" stroke-width="1.5" />
|
|
<text id="ps-label-minLevel" x="330" fill="#6C3483">minLevel</text>
|
|
<foreignObject id="ps-fo-minLevel" x="425" width="70" height="22">
|
|
<input xmlns="http://www.w3.org/1999/xhtml" type="number" id="node-input-minLevel" min="0" step="0.01" />
|
|
</foreignObject>
|
|
<text id="ps-unit-minLevel" x="500" fill="#555">m</text>
|
|
|
|
<!-- dryRunLevel (derived, read-only) -->
|
|
<line id="ps-line-dryRunLevel" x1="195" x2="325" stroke="#C0392B" stroke-dasharray="1 2" stroke-width="1" opacity="0.6" />
|
|
<text id="ps-label-dryRunLevel" x="330" fill="#C0392B" font-size="10" font-style="italic">dryRunLevel ≈ — m (safety — from %)</text>
|
|
|
|
<!-- Outlet — arrow on right, input below the threshold column -->
|
|
<line id="ps-line-outflowLevel" x1="320" x2="360" stroke="#1F4E79" stroke-width="2" marker-end="url(#ps-arrow)" />
|
|
<text id="ps-label-outflowLevel" x="365" fill="#1F4E79" font-weight="bold">Outlet</text>
|
|
<text id="ps-sub-outflowLevel" x="365" fill="#777" font-size="9">top of pipe</text>
|
|
<foreignObject id="ps-fo-outflowLevel" x="425" width="70" height="22">
|
|
<input xmlns="http://www.w3.org/1999/xhtml" type="number" id="node-input-outflowLevel" min="0" step="0.01" />
|
|
</foreignObject>
|
|
<text id="ps-unit-outflowLevel" x="500" fill="#555">m</text>
|
|
|
|
<!-- Floor / datum -->
|
|
<line x1="195" y1="380" x2="325" y2="380" stroke="#000" stroke-width="2" />
|
|
<text x="330" y="384" fill="#000">0 m (datum)</text>
|
|
|
|
<!-- Ordering-warning ribbon -->
|
|
<text id="ps-warning" x="260" y="410" text-anchor="middle" fill="#C0392B" font-size="10" font-style="italic" visibility="hidden"></text>
|
|
</svg>
|
|
|
|
<div class="form-row">
|
|
<label for="node-input-basinVolume"><i class="fa fa-cube"></i> Basin Volume (m³)</label>
|
|
<input type="number" id="node-input-basinVolume" min="0" step="0.1" />
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<h4>Control Strategy</h4>
|
|
<div class="form-row">
|
|
<label for="node-input-controlMode"><i class="fa fa-sliders"></i> Control mode</label>
|
|
<select id="node-input-controlMode">
|
|
<option value="none">None / Manual</option>
|
|
<option value="levelbased">Level-based</option>
|
|
<option value="flowbased">Flow-based</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div id="ps-mode-levelbased" class="ps-mode-section">
|
|
<p style="font-size:12px;color:#777;margin:0;">Level-based uses <code>minLevel</code> / <code>startLevel</code> / <code>maxLevel</code> from the diagram above.</p>
|
|
</div>
|
|
|
|
<div id="ps-mode-flowbased" class="ps-mode-section" style="display:none">
|
|
<div class="form-row">
|
|
<label for="node-input-flowSetpoint">Flow setpoint</label>
|
|
<input type="number" id="node-input-flowSetpoint" placeholder="m3/h" />
|
|
</div>
|
|
<div class="form-row">
|
|
<label for="node-input-flowDeadband">Deadband</label>
|
|
<input type="number" id="node-input-flowDeadband" placeholder="m3/h" />
|
|
</div>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<h4>Reference</h4>
|
|
|
|
<!-- Reference data -->
|
|
<div class="form-row">
|
|
<label for="node-input-minHeightBasedOn"><i class="fa fa-arrows-v"></i> Minimum Height Based On</label>
|
|
<select id="node-input-minHeightBasedOn" style="width:60%;">
|
|
<option value="inlet">Inlet Elevation</option>
|
|
<option value="outlet">Outlet Elevation</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-row">
|
|
<label for="node-input-refHeight"><i class="fa fa-map-marker"></i> Reference height</label>
|
|
<select id="node-input-refHeight" style="width:60%;">
|
|
<option value="NAP">NAP</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label for="node-input-basinBottomRef"><i class="fa fa-level-down"></i> Basin floor above datum (m)</label>
|
|
<input type="number" id="node-input-basinBottomRef" step="0.01" />
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<h4>Safety</h4>
|
|
|
|
<!-- Safety settings -->
|
|
<div class="form-row">
|
|
<label for="node-input-timeleftToFullOrEmptyThresholdSeconds"><i class="fa fa-clock-o"></i> Time To Empty/Full (s)</label>
|
|
<input type="number" id="node-input-timeleftToFullOrEmptyThresholdSeconds" min="0" step="1" />
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label for="node-input-enableDryRunProtection">
|
|
<i class="fa fa-shield"></i> Dry-run Protection
|
|
</label>
|
|
<input type="checkbox" id="node-input-enableDryRunProtection" style="width:20px;vertical-align:baseline;" />
|
|
<span>Prevent pumps from running on low volume</span>
|
|
</div>
|
|
<div class="form-row">
|
|
<label for="node-input-dryRunThresholdPercent" style="padding-left:20px;">Low Volume Threshold (%)</label>
|
|
<input type="number" id="node-input-dryRunThresholdPercent" min="0" max="100" step="0.1" style="width:80px;" />
|
|
<span id="derived-dryRunLevel" style="margin-left:8px;color:#777;font-size:12px;">→ dryRunLevel ≈ — m</span>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label for="node-input-enableOverfillProtection">
|
|
<i class="fa fa-exclamation-triangle"></i> Overfill Protection
|
|
</label>
|
|
<input type="checkbox" id="node-input-enableOverfillProtection" style="width:20px;vertical-align:baseline;" />
|
|
<span>Stop filling when approaching overflow</span>
|
|
</div>
|
|
<div class="form-row">
|
|
<label for="node-input-overfillThresholdPercent" style="padding-left:20px;">High Volume Threshold (%)</label>
|
|
<input type="number" id="node-input-overfillThresholdPercent" min="0" max="100" step="0.1" style="width:80px;" />
|
|
<span id="derived-overfillLevel" style="margin-left:8px;color:#777;font-size:12px;">→ overfillLevel ≈ — m</span>
|
|
</div>
|
|
<hr>
|
|
|
|
<h3>Output Formats</h3>
|
|
<div class="form-row">
|
|
<label for="node-input-processOutputFormat"><i class="fa fa-random"></i> Process Output</label>
|
|
<select id="node-input-processOutputFormat" style="width:60%;">
|
|
<option value="process">process</option>
|
|
<option value="json">json</option>
|
|
<option value="csv">csv</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-row">
|
|
<label for="node-input-dbaseOutputFormat"><i class="fa fa-database"></i> Database Output</label>
|
|
<select id="node-input-dbaseOutputFormat" style="width:60%;">
|
|
<option value="influxdb">influxdb</option>
|
|
<option value="json">json</option>
|
|
<option value="csv">csv</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Shared asset/logger/position menus -->
|
|
<div id="asset-fields-placeholder"></div>
|
|
<div id="logger-fields-placeholder"></div>
|
|
<div id="position-fields-placeholder"></div>
|
|
|
|
|
|
</script>
|
|
|
|
|
|
<script type="text/html" data-help-name="pumpingStation">
|
|
|
|
|
|
</script>
|