Waterschap Brabantse Delta · R&D-lab · uitleg
valveGroupControl (VGC) is de S88-Unit-orkestrator die meerdere
kind-ventielen (valve-nodes) als één groep bestuurt. Kern: hij verdeelt de
totale groepsflow over de beschikbare kleppen naar rato van hun Kv met een korte
iteratieve solve (max 2 passes, tolerantie 0,001) die per klep terugleest wat
het kind écht accepteerde, en publiceert de maximale delta-P over de groep. Daarnaast
aggregeert hij de fluid contracts (gas/liquid) van upstream bronnen tot één groepscontract.
De node volgt het EVOLV-drielagenpatroon met de rekenlogica in aparte concern-modules. Deze
pagina volgt letterlijk wat de code doet aan de hand van één voorbeeld en een interactieve
figuur die de solve getrouw naspeelt.
RnD/EVOLV, submodule
valveGroupControl @ 13a14b882b63 (na versheids-herverificatie:
werkkopie == origin/main; zie verantwoording onderaan). Let op: de
entrybestanden heten vgc.js/vgc.html (legacy drift), maar de
Node-RED type-id is valveGroupControl (vgc.js:1,8).valve-kinderen zelf (hun Kv volgt uit hun eigen supplier-curve); zie
daarvoor het valve-artifact op het EVOLV-spoor.VGC is een S88-Unit (lane L4, kleur #50a8d9; node-CLAUDE.md,
tabel "S88 classification"; commentaar in vgc.html:1-10). De parent-child-relatie
werkt twee kanten op. Als parent registreert VGC twee klassen kinderen:
valve-kinderen (in this.valves, src/specificClass.js:47,62-83)
en upstream bronnen van de canonieke types
machine/machinegroup/pumpingstation/valvegroupcontrol
(in this.sources, src/specificClass.js:15,48-50;
src/sources/fluidContract.js:111-123). Als child registreert VGC zichzelf
bij zíjn eigen parent via één child.register-bericht op poort 2 bij opstart
(generalFunctions/src/nodered/BaseNodeAdapter.js:117-132). Een VGC kan dus zelf
weer bron of kind zijn van een andere VGC of een valve.
De node is tick-gedreven: de adapter zet static tickInterval = 1000 en
roept elke seconde tick() = calcValveFlows() aan — een periodieke
reconcile-pass die de per-ventiel-toewijzing hersynchroniseert als een kind is gedrift tussen
event-driven herberekeningen (src/nodeClass.js:9-14; src/specificClass.js:246-249).
Omdat de tick-modus actief is, koppelt de adapter de event-driven
output-changed-listener niet aan (BaseNodeAdapter.js:137-153):
alle notifyOutputChanged()-aanroepen in het domein zijn daardoor inert; output
verschijnt maximaal 1 Hz via de tick, met delta-compressie erbovenop.
Rode draad door de hele pagina: een groep van 3 kleppen met
Kv = 4 / 8 / 12; klep 1 verzadigt bij
10 m³/h (accepteert niet meer). Groepsvraag 90 m³/h. De verdeling en de
iteratieve solve zijn met node tegen de échte VGC-code
(src/groupOps/flowDistribution.js, functie distributeFlow) doorgerekend
— verdeling, aantal passes, tolerantie en Kv-proportionaliteit geverifieerd; de klep-verzadiging
is een illustratief kind-eigenschap (zie de figuur).
Het I/O-contract in één oogopslag: kies een verb, stel de velden in,
kopieer het exacte injecteerbare msg, en klik Injecteer ▸ om te zien wat er
uit de drie poorten komt — met live doorgerekende waarden op de voorbeeldgroep
(3 kleppen, Kv 4/8/12, klep 1 verzadigt bij 10 m³/h). Elke bewering draagt een
pad:regel-chip. Poortlabels ["process","dbase","parent"]. De
console gebruikt meta.origin = parent (de default).
["process","dbase","parent"],
vgc.html:39). Poort 0/1 gaan samen elke tick, delta-gecomprimeerd:
is er niets gewijzigd, dan geen bericht (BaseNodeAdapter.js:156-163;
generalFunctions/src/helper/outputUtils.js:17-67). Output-units: flow
m3/h, druk mbar.| Poort | Label | Belangrijkste keys |
|---|---|---|
| 0 | process |
msg.topic = general.name (default
"ValveGroupControl"). Payload: dynamische keys
<position>_<variant>_<type> — posities lowercase
(MeasurementContainer.js:287). Door VGC zelf geschreven:
atequipment_measured_flow, atequipment_predicted_flow
(som toegewezen flows, flowDistribution.js:106),
deltamax_predicted_pressure (flowDistribution.js:123); plus
de scalars mode en maxDeltaP altijd in het ruwe object
(src/io/output.js:16-31). |
| 1 | dbase |
Zelfde velden via de influxdb-formatter (of
frost/json/csv via dbaseOutputFormat,
vgc.html:23,91-97), inclusief geflattende config-tags (id, softwareType,
role, positionVsParent, uuid, tagcode, geoLocation, …); lege tags gedropt
(outputUtils.js:54,87-133). |
| 2 | parent |
Eenmalig bij opstart (na 100 ms): {topic:'child.register',
payload:node.id, positionVsParent, distance}
(BaseNodeAdapter.js:117-132); verder stuurt _emitOutputs
er altijd null op. |
fluidContractChange (fluidContract.js:108),
reconcileIntervalChange (src/specificClass.js:242) en per
meetschrijfactie <type>.<variant>.<positie-lowercase>
zoals flow.predicted.atequipment (CONTRACT.md:50-62).De parent/bron staat boven, de klep-kinderen onder.
Registratie loopt via draden (poort 2): de kleppen registreren omhoog bij VGC,
VGC registreert omhoog bij zijn parent. Telemetrie gaat via poort 0/1.
De besturing naar de kleppen loopt in-process: VGC schrijft rechtstreeks
valve.updateFlow('predicted', q, 'downstream') op de geregistreerde kind-objecten.
De kleppen praten terug met in-proces events: positionChange en
deltaPChange triggeren respectievelijk een herverdeling en een max-delta-P-herberekening.
Kind-registratie: de klep stuurt
{topic:'child.register', payload:<node-id>, positionVsParent} → VGC resolvet
het object via RED.nodes.getNode(id).source en registreert via de
ChildRouter (handlers.js:58-67; BaseDomain.js:65-68).
_registerValve doet een duck-type-check (updateFlow,
state.getCurrentState, measurements verplicht), weigert duplicaat-id's,
en bindt de kind-events (src/specificClass.js:62-83,92-100). Bronnen abonneren
op zes flow-events (flow.{predicted,measured}.{downstream,atEquipment,atequipment})
die de groepstotaalflow zetten en herverdeling triggeren (fluidContract.js:18-25,111-139).
Klik een stap in de lijst (of gebruik ◀ ▶). Boven zie je waar in de pijplijn je bent;
rechts wat er gebeurt, de waarde van het rode-draadvoorbeeld op dat punt (met node
tegen flowDistribution.js doorgerekend), en het codepad.
De solve verdeelt de groepsvraag over de beschikbare kleppen naar rato van hun Kv, schrijft
per klep direct een target (valve.updateFlow('predicted', q, 'downstream')), leest
daarna terug wat elke klep accepteerde, en herverdeelt de resterende residual —
maximaal 2 passes, of tot |residual| ≤ 0,001 m³/h
(flowDistribution.js:40-75). Schuif de Kv's, de per-klep verzadigingsgrens
(het maximale debiet dat het kind accepteert) en de groepsvraag; de balken tonen het
Kv-proportionele ideaal, wat na pass 1 werd geaccepteerd, en de eindtoewijzing na pass 2.
Verzadigt een klep, dan schuift de tweede pass de rest Kv-proportioneel naar de kleppen die nog
ruimte hebben. De verzadigingsgrens is een illustratief kind-gedrag: in werkelijkheid
komt "geaccepteerd < gevraagd" uit de valve-node zelf, die VGC via
flow.predicted.downstream terugleest (_readAcceptedFlow,
flowDistribution.js:29-38).
lastFlowSolve.residual bewaard, niet gealarmeerd, en zit niet in
getOutput() (flowDistribution.js:74,107;
output.js:16-31). Downstream dus onzichtbaar.Modi (this.currentMode, init uit config.mode.current default
auto): auto | virtualControl | fysicalControl |
maintenance (generalFunctions/src/configs/valveGroupControl.json:153-177).
Per modus bepaalt allowedSources welke origin commando's mag geven
(isValidSourceForMode, src/specificClass.js:144-147): auto
→ {parent, GUI, fysical}; virtualControl → {GUI, fysical}; fysicalControl
→ {fysical}. Voor maintenance bestaat géén allowedSources-entry
— zie de aandachtspunten. De state wordt bij configure direct op
operational gezet (src/specificClass.js:44-45); er zijn geen
tijd-/overgangsconfiguraties per state. Sequenties (executeSequence,
src/specificClass.js:162-173) lopen een geordende Set van statenamen af; defaults
(valveGroupControl.json:267-308): startup =
starting→warmingup→operational; shutdown = stopping→coolingdown→idle;
emergencystop = emergencystop→off; boot = idle→starting→warmingup→operational.
nodes/generalFunctions/src/configs/valveGroupControl.json. De
verdeel-/reconciliatieparameters maxPasses en residualTolerance zijn
niet configureerbaar — hardcoded defaults (flowDistribution.js:8;
src/specificClass.js:39).| Sleutel | Default | Betekenis |
|---|---|---|
general.name | "ValveGroupControl" | Label én msg.topic/measurement-naam (valveGroupControl.json:3-9). |
general.unit | "unitless" | Bij configure overschreven naar de flow-outputunit m3/h (src/specificClass.js:27-29). |
functionality.softwareType | "valvegroupcontrol" | Bepaalt router-dispatch bij registratie als kind van een andere parent (valveGroupControl.json:61-76). |
mode.current | "auto" | Startmodus; mode.allowedSources = per modus een Set toegestane origins (valveGroupControl.json:153-212). |
action (enum) | statusCheck | Documentatieve actielijst; valvePositionChange/valveDeltaPchange staan erin maar zijn niet geïmplementeerd (zie aandachtspunten; valveGroupControl.json:234-266). |
sequences.* | Sets | Geordende statelijsten voor startup/shutdown/emergencystop/boot (valveGroupControl.json:267-308). |
Statusbadge (editor, elke 1000 ms): "<mode> | flow=<m³/h> m3/h |
<n> valve(s) connected" — groen bij ≥1 beschikbare klep, rood bij geen
(src/io/output.js:33-45).
Alle punten zijn code-observaties d.d. 2026-07-17 op submodule
valveGroupControl @ 13a14b882b63 (== origin/main;
runtime-gedrag niet geverifieerd). De open-issue-lijst is niet geraadpleegd — onzeker of deze
al als issue bekend zijn.
isValidSourceForMode doet
(this.config.mode.allowedSources[mode] || []).has(source)
(src/specificClass.js:144-147). Voor maintenance bestaat géén
allowedSources-entry (valveGroupControl.json:183-208), de fallback
is een Array, en Array.prototype.has bestaat niet. De aanroep staat
vóór het try-blok van handleInput (src/specificClass.js:198-205),
dus elke execSequence/totalFlow/emergencyStop in
maintenance escaleert tot een exception die pas in de adapter wordt gevangen
(BaseNodeAdapter.js:165-182). aanname: bedoeld is
vermoedelijk "geen bronnen toegestaan" met een nette warn — dit is een code-observatie,
geen geverifieerde runtime-crash.valveGroupControl.json:259-262), maar de handler loopt via
handleInput en dus door de bron-check: in fysicalControl wordt een
emergencyStop met origin parent/GUI geweigerd
(handlers.js:37-39; src/specificClass.js:198-203), en in
maintenance raakt hij de TypeError hierboven. Documentatie en implementatie
sporen hier niet. hypothese: onbedoelde koppeling.flowDistribution.js:53-57); accepteert een kind minder dan gevraagd, dan kan na
2 passes een rest-residual blijven staan (interactieve figuur hierboven, rode
draad: 0,833 m³/h). Alleen in lastFlowSolve.residual geregistreerd, niet
gealarmeerd, en niet in getOutput() (flowDistribution.js:74,107;
output.js:16-31) — downstream onzichtbaar.action bevat
valvePositionChange en valveDeltaPchange
(valveGroupControl.json:234-266), maar de switch in handleInput
kent alleen execSequence/totalFlowChange/emergencyStop/
statusCheck (src/specificClass.js:207-231); onbekende acties
vallen in de default-warn. Positie-/deltaP-wijzigingen lopen in werkelijkheid via
child-events.tickInterval = 1000), koppelt de
adapter de output-changed-listener niet aan (BaseNodeAdapter.js:137-153);
alle notifyOutputChanged()-aanroepen in het domein
(src/specificClass.js:140,159) zijn daardoor in de praktijk inert — output
loopt uitsluitend via de 1 Hz-tick met delta-compressie.GET /valveGroupControl/menu.js en
…/configData.js) hebben géén RED.auth.needsPermission-guard
(vgc.js:20-38); de repo-security-regel noemt die als standaard. Niet gedekt door
tests: nul-Kv-pad, negatieve delta-P in calcMaxDeltaP, tick-gedrag,
executeSequence, maintenance-gating; geen test/_output-manifest.md.Gepland/transitional: het position-verb is een
bewuste no-op "pending Phase 7" (index.js:28; handlers.js:21-23);
child.register loopt transitional op het legacy msg.topic-pad
(index.js:66-70); de rename vgc.{js,html} →
valveGroupControl.{js,html} staat gepland in de EVOLV-CLAUDE.md:51-54.
https://lab.wbd-rd.nl/rnd-lab/projects/evolv-valvegroupcontrol.machineGroupControl (pomp-groep) op hetzelfde
spoor, en de valve-node (die de Kv per klep levert).RnD/valveGroupControl):
docs/functional.html, docs/contract.html,
docs/group-coordination.html, README.md; het commando-contract in
CONTRACT.md.RnD/EVOLV
(super-repo 74c4089, 2026-07-03) — submodule valveGroupControl @
13a14b882b63, plus het feitendossier van deze analyse. Analysedatum 2026-07-17.git fetch
gedraaid: de geanalyseerde submodule-SHA 13a14b882b63 is gelijk aan
origin/main (en aan de werkkopie-HEAD) — de analyse beschrijft de actuele tip.pad:regel-verwijzing. De interactieve figuur en de console zijn
getrouwe herimplementaties: de Kv-proportionele verdeling en de iteratieve solve zijn met
node tegen de échte distributeFlow-code doorgerekend (aantal passes,
tolerantie 0,001 en Kv-proportionaliteit geverifieerd; testgevallen als commentaar in de
pagina-JS). Illustratieve aannames (o.a. de per-klep verzadigingsgrens) zijn expliciet zo
gelabeld.