Waterschap Brabantse Delta · R&D-lab · uitleg

EVOLV · rotatingMachine — één pomp: van flowSetpoint tot voorspelling

eigenaar R&D-lab laatst getest 2026-07-17 versie 2.0 concept
rotatingMachine is de digitale twin én besturingsmodule voor één roterende machine — pomp, compressor of blower. Uit een stuurpositie (control %) en een gemeten drukverschil ΔP voorspelt hij flow, vermogen, rendement, BEP-afstand en drift t.o.v. de echte meting; daarnaast draait hij de S88-toestandsmachine (startup/shutdown/estop/ maintenance). De node is event-driven: hij rekent op state- en measurement-events, niet op een vaste tick. Deze pagina volgt letterlijk wat de code doet aan de hand van één voorbeeld en een paar interactieve figuren die de kernfuncties getrouw naspelen.

Doel & rol in de keten

rotatingMachine is een S88-Equipment Module (lane L3, kleur #86bbdd; CLAUDE.md, tabel "S88 classification"; src/specificClass.js:3). Als child registreert hij zich bij een groepscontroller (machineGroupControl of pumpingStation) via de Port-2 child.register-handshake; als parent is hij zelf eigenaar van measurement-kinderen (pressure/flow/power/temperature/speed) (CONTRACT.md:47-50, 83-92). De functionele rol is RotationalDeviceController, softwareType rotatingmachine (configs/rotatingMachine.json:60-73).

De node is event-drivenstatic tickInterval = null: state- en measurement-events triggeren de hercompute, alleen de status-badge wordt elke 1000 ms gepolld (src/nodeClass.js:8-13). Output verschijnt op het domein-event 'output-changed' via notifyOutputChanged() (src/specificClass.js:527,539; BaseNodeAdapter.js:147-162). Eén delta-uitzondering: static alwaysEmitFields = ['ctrl'] — de gerealiseerde stuurpositie gaat élke emissie mee, anders comprimeert de delta hem weg en verdwijnt de Grafana "% Control"-lijn (src/nodeClass.js:14-17).

Rode draad door de hele pagina: een KSB Calio 40-80 (echte catalogusdata; pilot-rig sweep 2026-06-24, 1617 punten, 40 ΔP-keys) op werkdruk ΔP = 363 mbar. Eén commando vanaf een idle machine: msg.command = { flowSetpoint: { value: 5.2, unit: 'm3/h' } }. Alle getallen in de rode draad zijn met node tegen het echte curvebestand (ksb-calio-40-80.json) doorgerekend; illustratieve aannames zijn expliciet zo gelabeld.

Praat met deze node

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 voorbeeldfamilie ΔP 363 mbar van de Calio 40-80. Elke bewering draagt een pad:regel-chip. Poortlabels ["process","dbase","parent"].

Stuurinput · 1 poort
Bericht
Poortenoutput · 3 poorten
Volledige poort-referentie
Poortcontract (labels ["process","dbase","parent"], rotatingMachine.html:64-66). Conventie: een ontbrekende meting betekent key afwezig, niet null (test/_output-manifest.md:9-21). Output-units: mbar, m³/h, kW, °C.
PoortLabelBelangrijkste keys
0process Delta-gecomprimeerde snapshot (src/io/output.js:26-82): flattened measurement-keys "<type>.<variant>.<position>.<childId>" (o.a. flow.predicted.downstream/atEquipment/max/min, power.predicted.atEquipment, efficiency.predicted.*, specificEnergyConsumption.*, pumpHead.*, hydraulicPower.*, nHydraulicEfficiency.*); scalars state, runtime, ctrl (altijd), moveTimeleft, mode, cog, NCog, NCogPercent, maintenanceTime; drift flowNrmse, flowLongterNRMSD (sic) + flowLongTermNRMSD, …ImmediateLevel, …DriftValid (idem power); gezondheid predictionQuality, predictionConfidence, predictionPressureSource, predictionFlags; BEP effDistFromPeak, effRelDistFromPeak; dual-curve curveSource, curveSourceAvailable en — alléén bij actieve measured — measuredCurveSource, flowTheoretical, gapVsTheoryPct.
1dbase Zelfde raw snapshot geformatteerd via de influxdb-formatter (default); omzetbaar naar frost (CoreSync) / json / csv (output.dbase, config r.131-143; BaseNodeAdapter.js:160-162).
2parent Eenmalig bij startup { topic:'child.register', payload:node.id, positionVsParent } (default 'atEquipment') naar de ouder (BaseNodeAdapter.js:121-127; CONTRACT.md:47-50).

Wie praat met wie

De parent staat boven, de measurement-kinderen onder. Registratie en telemetrie lopen via draden; de kind-measurements komen als in-process events binnen. De echte pomp (edgeIO/Modbus) zit buiten deze repo: veldwaarden komen binnen als measurement-kind of via simulate.

MGC / pumpingStation parent rotatingMachine deze node · S88 Equipment echte pomp / edgeIO buiten deze repo (Modbus) pressure ×2 up / downstream flow measurement power measurement speed → affiniteitspad temp measurement p2 registratie p0/p1 → proces / Influx in-process events (measurement.emitter) veldwaarde → measurement / simulate
registratie · via draad (poort 2) telemetrie · via draad (poort 0/1) in-process events · measurement/state-emitter veld → twin · buiten deze repo

Kind-measurements binden op "<type>.measured.<position>" van de kind-emitter, met dedup bij herregistratie en volledige detach in close() (src/measurement/childRegistrar.js:12-45). Echte drukkinderen krijgen voorrang op de virtuele dashboard-sim-*-slots (src/pressure/pressureInitialization.js:75-88). In-process events voor de parent: source.measurements.emitter vuurt <type>.<variant>.<position>, source.state.emitter vuurt positionChange/stateChange (CONTRACT.md:52-81).

De reis van één flowSetpoint

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 ksb-calio-40-80.json doorgerekend), en het codepad.

De 40-keys-curve, interactief

De tabulaire predictor is een ΔP-"multicurve": elke ΔP-familie (40 keys, de key is het drukverschil in mbar) draagt zijn eigen ctrl→flow (nq) en ctrl→power (np). Binnen een familie wordt geïnterpoleerd met een monotone-cubic-spline. Kies een familie en schuif de stuurpositie; de werkpuntmarker en de BEP-marker (piek van η = Q·ΔP/P over de familie) verspringen mee. Let op dat elke familie alleen zijn haalbare control-venster dekt — 70 mbar loopt maar van 8–14 %, 758 mbar alleen van 92–100 %.

68 %

Affiniteits-collapse, interactief

Draagt de curve een affinity-blok, dan loopt de flow-voorspelling via de affiniteitswetten in plaats van de tabel: alle toerentallen vallen samen op één dimensieloze φ(ψ)-lijn. Schuif toerental N en werkdruk ΔP; het werkpunt ψ = ΔP/N² wordt op de referentiecurve gelegd (buiten bereik: geklemd op het eindpunt) en levert φ, waaruit Q = φ·N volgt. Dit is het echte 19-punts φ(ψ)-blok uit ksb-calio-40-80.json.

3227 rpm
363 mbar
P volgt nooit affiniteit
Bij kleine pompen breekt een vaste-verliezen-vloer P∝N³ (R²≈0.20). Vermogen komt daarom altijd uit de tabulaire np, ook op het affiniteitspad (affinityFlow.js:24-25).
Empirie — p95 |residual| flow (m³/h)
affiniteit @ gemeten rpm0,263
40-key tabel0,319
affiniteit @ control %1,761
Afgeleide rpm uit control % pint de snelheid niet (±~140 rpm) — vandaar dat het pad zonder meting alleen onder deriveThreshold 80 % mag (docs/research/README.md:48-59).

Veiligheid, vloeren & het duale curve-model

OFF-contract
shutdown vanaf operational rampt eerst naar setpoint 0, doorloopt dan stopping→coolingdown→idle. Bij elke stateChange naar een niet-operationele toestand worden flow.predicted.downstream/atEquipment en power.predicted.atEquipment op 0/0/0 genuld — óók bij boot geseed, zodat het dashboard de off-toestand kan tonen (src/specificClass.js:515-522, 414-420). estop → off; shutdown/estop mogen een lopende beweging aborteren (sequenceController.js:72-91).
rpm-vloer
Een gemeten asssnelheid onder 0.1 × affinityMinRpm wordt als sensor-placeholder genegeerd (de ~1-rpm-vloer die z-info voor een stilstaande pomp rapporteert), anders explodeert ψ=ΔP/N² en stort de affiniteitsvoorspelling in. Voor de Calio: minRpm 1022 → vloer ≈102 rpm (≈10,7 rad/s) (src/specificClass.js:34-38, 479-507).
dual-curve
theoretical = catalogus (per model), measured = per-asset as-installed curve van het FROST-Thing (op node-uuid). auto = measured-indien-geladen; schakelen naar een niet-geladen bron is een gelogde no-op. Bij actieve measured óók gapVsTheoryPct = (cFlow−tFlow)/tFlow·100 (src/specificClass.js:160-301; predictionMath.js:52-56).
drift & predictionQuality
Een binnenkomende gemeten flow/power voedt errorMetrics.assessPoint (NRMSE; windowSize 30, minSamplesForLongTerm 10, ewmaAlpha 0.15, alignmentToleranceMs 2500). predictionConfidence-basis 0.9 (differentieel) / 0.55 (één zijde) / 0.2 (geen druk), minus penalties voor drift-levels en near_curve_edge (specificClass.js:348-355; predictionHealth.js:81-110).
curve-catalogus & normalisatie
Register: 4327 entries; 14 curvebestanden. Bij laden → canoniek SI: ΔP mbar→Pa als integer-key (voorkomt key-volgorde-scrambling), flow→m³/s, power→W, met 3×-mediaan-anomaliedetectie per druklaag (curveNormalizer.js:8-96). Het affinity-blok is al SI en wordt verbatim doorgegeven. Centrale store: ensureModelCurve met revalidatie/hot-swap (specificClass.js:315-331).
_pendingExtras-constructorpad
buildDomainConfig stasht stateConfig/errorMetricsConfig/curveSource op de klasse-statische Machine._pendingExtras omdat de config-schema-allowlist onbekende keys zou strippen; single-threaded JS maakt dat race-vrij (nodeClass.js:23-40; specificClass.js:52-72). Een eerdere default-param-clobber (state-machine bootte met schema-defaults) is daar gefixt.

Toestanden & configuratie

De toestandsmachine start op idle. Toestanden: off, idle, starting, warmingup, operational, accelerating, decelerating, stopping, coolingdown, maintenance (+ emergencystop als sequentiestap). "Operationeel" = operational | accelerating | decelerating | warmingup — alleen dán worden metingen verwerkt en voorspellingen berekend (src/state/stateBindings.js:8-13). Sequences (config r.405-461): startup = starting→warmingup→operational; shutdown = stopping→coolingdown→idle; emergencystop = emergencystop→off. Modi: auto | virtualControl | fysicalControl (default auto); in fysicalControl mag alleen statuscheck/estop/maintenance (flowController.js:33-34; config r.276-382).

Belangrijkste editor-velden (rotatingMachine.html:19-62). Volledige defaults/enums: nodes/generalFunctions/src/configs/rotatingMachine.json.
VeldDefaultBetekenis
model""Verplicht bij startup: bepaalt de curve via assetResolver.resolve('curves', model); supplier/categorie/assetType worden hieruit afgeleid. Opgeslagen legacy-velden supplier/category/assetType geven een harde deploy-error (nodeClass.js:64-79).
curveSourceautomeasured indien geladen, anders theoretical.
speed / movementMode1 / staticspeedBewegingssnelheid %/s (required); staticspeed = lineair, dynspeed = ease-in/out.
curveUnitsmbar / m³/h / kW / %Eenheden waarin het curvebestand is opgesteld.
asset.unit""Moet een geldige flow-unit zijn (hard); bij voorkeur uit de registry-lijst van het model (soft warn).
positionVsParent""atEquipment | upstream | downstream. ⚠ Let op: in het schema (config r.84-90) zijn de enum-beschrijvingen van upstream en downstream onderling verwisseld; de code gebruikt alleen de waarden, niet de teksten.
frostServer, geoLat/geoLonFROST-config-node + geolocatie.

Aandachtspunten uit de code-analyse

Alle punten zijn code-observaties d.d. 2026-07-17. De brief is geschreven op branch feat/simulate-speed (3403a1b) tóén de fix-branches nog niet gemerged waren; hieronder is de status per bug herverifieerd tegen origin/main met git merge-base --is-ancestor (rotatingMachine 8ea60fc, generalFunctions 70dd218).

Verder lezen

Bron statische code-analyse van de werkkopie RnD/EVOLV (super-repo 74c4089, 2026-07-03) — submodule rotatingMachine geanalyseerd @ 3403a1b84803 (branch feat/simulate-speed, 1 commit vóór het toenmalige origin/main), plus het feitendossier van deze analyse. Analysedatum 2026-07-17.
Versheids-herverificatie op 2026-07-17 is git fetch gedraaid en per §8-bug met git merge-base --is-ancestor gecontroleerd tegen de submodule-tips: rotatingMachine origin/main = 8ea60fc (bugs 1–4 gemerged via PR #15, plus PR #16 simulate-speed), generalFunctions origin/main = 70dd218 (bug 5 nog niet gemerged). De aandachtspunten hierboven beschrijven die actuele status.
Methode statische code-analyse (geen runtime-verificatie); elk feit draagt een pad:regel-verwijzing. De interactieve figuren en de console zijn getrouwe herimplementaties, vooraf met node tegen het echte curvebestand ksb-calio-40-80.json doorgerekend (testgevallen als commentaar in de pagina-JS); illustratieve aannames zijn expliciet zo gelabeld.
Beperking beschrijft de geanalyseerde revisie; gedrag op andere branches of deployments kan afwijken.
Contact R&D-lab · lab.wbd-rd.nl