Waterschap Brabantse Delta · R&D-lab · uitleg
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.
RnD/EVOLV, submodule
rotatingMachine @ 3403a1b84803 (branch
feat/simulate-speed), na versheids-herverificatie tegen
origin/main (zie verantwoording onderaan).docs/research/) en
op het EVOLV-spoor in HELIX.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-driven — static 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.
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"].
["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.| Poort | Label | Belangrijkste keys |
|---|---|---|
| 0 | process |
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. |
| 1 | dbase |
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). |
| 2 | parent |
Eenmalig bij startup { topic:'child.register', payload:node.id,
positionVsParent } (default 'atEquipment') naar de ouder
(BaseNodeAdapter.js:121-127; CONTRACT.md:47-50). |
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.
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).
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 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 %.
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.
np, ook op het affiniteitspad (affinityFlow.js:24-25).deriveThreshold 80 % mag (docs/research/README.md:48-59).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).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).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).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).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).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.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).
rotatingMachine.html:19-62).
Volledige defaults/enums: nodes/generalFunctions/src/configs/rotatingMachine.json.| Veld | Default | Betekenis |
|---|---|---|
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). |
curveSource | auto | measured indien geladen, anders theoretical. |
speed / movementMode | 1 / staticspeed | Bewegingssnelheid %/s (required); staticspeed = lineair, dynspeed = ease-in/out. |
curveUnits | mbar / 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/geoLon | — | FROST-config-node + geolocatie. |
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).
outputY, wat met actief affiniteitsmodel de voorspelling clobberde ("400x
off") en de live rig-drift-telemetrie corrumpeerde. Gefixt (slot-read i.p.v.
outputY) in commit 1dda2a2, gemerged via PR #15
(fix/dual-curve-affinity-coherence) — nu ancestor van
origin/main (src/measurement/measurementHandlers.js).applyDiff
zette fDimension alleen op de actieve predictors, waardoor de theoretische
predictor op boot-ΔP bevroor en gapVsTheoryPct flows op twee drukken
vergeleek. Gefixt in 1dda2a2 (PR #15).updateCurve omzeilde dual-curve-activatie
(bug 3). Curveless-boot + on-demand fetch leverde kale predictors (geen
activeCurveSource, kapotte setCurveSource); de in-place-branch
kon measured met theoretical overschrijven. Gefixt in 1dda2a2
(PR #15).applyDiff → calcEfficiency met rauwe
outputY (bug 4). Schreef bogus efficiency/specifieke-energie-
samples, óók op idle machines. Gefixt in commit ed8a184
(PR #15).monotone_cubic_spline is niet monotoon (bug 5,
generalFunctions). De Fritsch-Carlson-limiter is gegate op
method == "fritschcarlson" terwijl het type-string
"monotone_cubic_spline" is: dead code, dus plain cubic Hermite met
overshoot op steile cliffs (src/predict/interpolation.js:301,312). De fix
c5f6224 op branch fix/monotone-cubic-overshoot is géén
ancestor van gF origin/main (herverifieerd 2026-07-17) — nog niet
gemerged. aanname: mogelijk onbedoeld. De F1-figuur
hierboven toont de bedoelde monotone curve.docs/research/README.md:21-30, maar de gedeployde
ksb-calio-40-80.json heeft 40 keys. Het affiniteitspad lost dit alternatief
op (predictors.js:16-18).flowLongterNRMSD — een verkeerd gespelde legacy-key wordt naast de
correcte flowLongTermNRMSD geëmit (compat; src/io/output.js:38-40).child.register blijft legacy msg.topic tot de
platformbrede registratiemigratie (src/commands/index.js:92-97).rotatingMachine.html i.p.v.
src/editor/*; expliciet benoemd als te migreren
(.claude/rules/node-architecture.md).https://lab.wbd-rd.nl/rnd-lab/projects/evolv-rotatingmachine.docs/research/README.md, docs/research/cliff-clamp-shutoff.html,
docs/research/residuals-by-dp.html (RnD/rotatingMachine).CONTRACT.md en test/_output-manifest.md.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.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.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.