Waterschap Brabantse Delta · R&D-lab · uitleg
edgeIO is de Node-RED Control Module voor één OnLogic geïsoleerde digitale
I/O-kaart (ADP120/ADP102-familie) over USB-CDC — géén Modbus, geen binair protocol.
De node leest 4 geïsoleerde digitale ingangen, 4 uitgangen en per-ingang edge-counters, en
stuurt output-/toggle-/PWM-/sink-source-commando's door tegen de interactieve
Zephyr-tekstshell van de kaart. Hij is opgebouwd volgens het EVOLV-lagenpatroon
(wrapper → adapter → domein) met daaronder een aparte hardware-driver. Dit is de
fysieke veldgrens van EVOLV — echte hardware-I/O, niet de digitale twin. Deze pagina
volgt letterlijk wat de code doet aan de hand van één output-commando, een interactieve
contract-console en een tastbaar DIO-paneel.
RnD/EVOLV, submodule edgeIO
@ 5e0cf0b1e6da. Op 2026-07-17 na git fetch geverifieerd:
werkkopie = origin/main (zie verantwoording onderaan).edgeIO is de fysieke veldgrens van EVOLV — "the plant's hands and eyes at the edge". De
node draait typisch op de RevPi/edge-gateway die Node-RED host, bekabeld naar relais,
run-terugmeldingen van pompen, vlotterschakelaars en deurcontacten. Eén edgeIO-node bedient
precies één DIO-kaart (docs/functional.html:51,170). In S88-termen is dit
een Control Module (kleur #a9daee, plaatsingslaan L2;
CLAUDE.md, tabel "S88 classification"). Bij opstart registreert de node zich bij
een bovenliggende parent-node via de Port-2 child.register-handshake.
De architectuur is de standaard EVOLV-drielaag plus een aparte hardware-driver:
wrapper edgeIO.js (RED.nodes.registerType('edgeIO', …)) → adapter
src/nodeClass.js (class nodeClass extends BaseNodeAdapter) → domein
src/specificClass.js (class EdgeIO extends BaseDomain, bezit de
seriële verbinding + poll-loop) → driver src/serialShell.js
(class SerialShell + autodetectPort() + toState(),
client voor de Zephyr-shell).
De node is event-driven — static tickInterval = null
(src/nodeClass.js:12): er is geen tick-loop op adapterniveau. In plaats daarvan
bezit het domein een eigen poll-loop (setInterval(() => this._poll(),
this.pollIntervalMs), src/specificClass.js:87-90, met
pollIntervalMs = max(50, io.pollInterval||1000), r18) en roept bij een wijziging
notifyOutputChanged() aan; de adapter abonneert op dat event en stuurt poorten
0/1. Output verschijnt dus niet op een vast ritme maar wanneer de snapshot verandert. De
statusbadge wordt wél op interval ververst (static statusInterval = 1000 ms).
buildDomainConfig forceert bij autoDetect !== false het veld
port op null — autodetect wint zolang het vinkje aan staat
(src/nodeClass.js:18-33).
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. Een output-commando licht meteen de
bijbehorende uitgang op in het DIO-paneel hieronder. Elke bewering
draagt een pad:regel-chip. Poortlabels ["process","dbase","parent"].
["process","dbase","parent"],
edgeIO.html:53-55). Poorten 0/1 emitteren event-driven bij
snapshot-wijziging; poort 2 is de eenmalige child-registratie.| Poort | Label | Belangrijkste keys |
|---|---|---|
| in 0 | commands |
verb-envelope msg.command = { <verb>:{params}, meta };
dispatch via commandRegistry.js:205-254, die msg.params/
msg.meta zet en msg.origin uit meta.origin
stempelt (default parent). Legacy msg.topic/aliassen/
payload.pin zijn verwijderd → unknown command + drop
(CONTRACT.md:6-31). |
| uit 0 | process |
msg.topic = config.general.name; snapshot-keys uit
getOutput() = {...this.state}
(specificClass.js:162-164): connected,
input0..input3, output0..output3,
edge0..edge3 (edgeN alleen bij includeEdges;
opbouw specificClass.js:96-101). Delta-gecomprimeerd: alleen
gewijzigde velden, geen msg als niets wijzigde
(outputUtils.js:17-41,49,66). Uitzondering:
static alwaysEmitFields = ['connected']
(src/nodeClass.js:16) — connected gaat bij élke emissie
mee zodat downstream health-checks een disconnect nooit missen. |
| uit 1 | dbase |
Zelfde snapshot via de 'influxdb'-formatter; tags geflattened uit
de config (id, softwareType, role, positionVsParent, uuid, tagcode, category, type,
model, unit; lege waarden gedropt) (outputUtils.js:87-133). Formaat
instelbaar (influxdb/json/csv). |
| uit 2 | parent |
Eenmalig, 100 ms na opstart (REGISTRATION_DELAY_MS):
{ topic:'child.register', payload:<node.id>, positionVsParent
(default 'atEquipment'), distance }
(BaseNodeAdapter.js:23,117-132). |
edgeIO zit op de grens tussen de software-kant (boven) en de echte hardware (onder). De parent-EVOLV-node staat boven: registratie en telemetrie lopen via draden (poort 2 resp. 0/1). Onder edgeIO ligt de DIO-kaart, bereikt via USB-CDC / de Zephyr-shell — relais, vlotters, pomprun-terugmelding. Dit is de echte-hardware-kant, niet de twin.
Anders dan de twin-nodes praat edgeIO niet met een
gesimuleerd proces maar met een fysieke kaart: de driver stuurt tekstcommando's over
USB-CDC en leest de werkelijke pin-standen terug. De sink/source-modus bepaalt de
bekrachtigingsrichting van de open-collector-uitgangen (src/serialShell.js:171-200).
Klik een stap in de lijst (of gebruik ◀ ▶). Boven zie je waar in de pijplijn je bent;
rechts wat er gebeurt, het rode-draadvoorbeeld op dat punt en het codepad. Rode draad:
msg.command = { output: { pin: 0, state: true } } naar een verbonden kaart.
Een getrouw model van de kaart. Klik een ingang (DI0–DI3) om een
dry-contact te simuleren; elke flank telt op in de bijbehorende edge-counter. Kies dan een
uitgang, een state-token en de kaart-modus en klik Zet uitgang ▸ — of injecteer
een output-commando in de console hierboven; de uitgang licht dan hier op. De
gedimde tokens (banana, off, leeg) demonstreren de fail-safe:
alles wat geen truthy-token is, wordt inactive.
active/inactive
correct met dio get output (true/false/high/low lezen
geïnverteerd terug, kale 0/1 kan genegeerd worden). Truthy-tokens
['1','true','high','active','on'] ⇒ active; élke andere
string — ook onherkenbare rommel — ⇒ inactive: "an output must never
energise on a garbage command" (src/serialShell.js:206-215). Node-getest —
zie de testcases in de pagina-JS.pollIntervalMs). Pulstreinen sneller dan het poll-interval worden
alleen door de hardware-edge-counters gevangen
(docs/functional.html:62,166). Change-detectie in het domein via
JSON.stringify(next) !== JSON.stringify(this.state)
(src/specificClass.js:102).docs/direct-io.html:52,113). De queue chaint óók na een rejection
(this.queue = this.queue.then(run, run)) zodat één falend commando de queue
niet blokkeert; per commando één waiter met timeout 2000 ms
(src/serialShell.js:148-150).serial.deviceName
(DIO0), serial.port en asset.model dragen daarom
"normalize":"preserve" — anders wordt DIO0 → dio0
en gooit de shell Couldn't bind
(CLAUDE.md "Config schema"; docs/direct-io.html:117).closed/error of een transportfout in de
poll (regex /not open|closed|timeout/i) flipt connected naar
false en plant _scheduleReconnect() een nieuwe poging na
4000 ms (src/specificClass.js:79-85,109-115). Poll-fouten die
niet op transportverlies wijzen worden élke cyclus als error gelogd zonder
reconnect (r117).SerialPort.list() op VID:PID
353f:a104 (+ legacy 1fc9:0094/15a2:0300) en kiest
interface 0 (if00) (src/serialShell.js:221-235). In
containers zonder udev faalt dat: mount /run/udev:ro, zet de Node-RED-gebruiker
in dialout, of geef een expliciete port
(docs/direct-io.html:116; README.md:54-55).Er is geen formele S88-state-machine (geen idle/running/…); de levenscyclus is een
verbindingstoestand. configure() roept direct _connect()
(src/specificClass.js:33): pad resolven (expliciet of autodetect), shell openen,
getNum('inputs'/'outputs') opvragen (fouten stil geslikt → fallback 4/4, r64-65),
connected = true, pollen starten plus één directe poll. De statusbadge toont geel
reconnecting, rood disconnected, of groen met bitstrings als
in 0000 · out 0100 (src/specificClass.js:166-175). De kaart-modus
setMode('sink'|'source') is device-wide; andere waarden gooien
invalid mode en worden niet in de snapshot bijgehouden (r145-150).
nodes/generalFunctions/src/configs/edgeIO.json — bewust níét in de
node-repo (CLAUDE.md "Config schema"). De preserve-vlaggen
voorkomen lowercasing van hoofdlettergevoelige velden.| Veld | Default | Betekenis |
|---|---|---|
serial.autoDetect | true |
scant USB-CDC op VID:PID 353f:a104 (huidig), 1fc9:0094
(ADP120 legacy), 15a2:0300 (ADP102 legacy), interface 0 (config r167-172) |
serial.port | null |
expliciet devicepad; normalize:"preserve" — hoofdlettergevoelig op Linux (r174-181) |
serial.deviceName | "DIO0" |
Zephyr-devicenaam; preserve, hoofdlettergevoelig (r183-189) |
serial.baudRate | 115200 |
nominaal, niet elektrisch significant (CDC-ACM) (r191-198) |
io.pollInterval | 1000 |
ms tussen volledige reads, minimum 50 (r201-207) |
io.includeEdges | true |
leest per poll ook dio edge per ingang (r209-214) |
output.process / output.dbase |
"process" / "influxdb" |
formaat poort 0 resp. 1 (ook json/csv, r84-107) |
asset.supplier / asset.model |
"OnLogic" / "ADP120" |
asset-metadata; model heeft preserve (r154) |
Alle punten zijn code-observaties d.d. 2026-07-17 (statisch, niet in runtime
geverifieerd). De werkkopie edgeIO @ 5e0cf0b1e6da is na
git fetch gelijk aan origin/main.
refresh emit niet altijd — CONTRACT.md:19 en de
registry-description zeggen "emits on Port 0/1", maar refresh()
→ _poll() roept notifyOutputChanged() alleen aan als de snapshot
gewijzigd is (specificClass.js:102-104,152-154); een refresh op ongewijzigde
staat emit niets. aanname: mogelijk onbedoeld, of de docs zijn
onnauwkeurig.setPwm/setMode updaten de snapshot niet
(specificClass.js:140-150); er is geen PWM- of modus-status in de output.
Onzeker (niet in code/docs benoemd): wat dio get output per poll teruggeeft
op een PWM-gestuurde pin.specificClass.js:116-117).getNum-fouten stil geslikt — fouten bij connect vallen terug op de
default 4/4 zonder melding (specificClass.js:64-65).docs/direct-io.html:52,113).test/_output-manifest.md in de node-map (vereist door
.claude/rules/output-coverage.md voor prospectieve wijzigingen; backfill is
repo-breed backlog). Geen TODO/FIXME-markers in de broncode gevonden (grep over js/md/html,
2026-07-17).RnD/edgeIO
(git.wbd-rd.nl) zijn per repo-regel de backlog-bron; die zijn voor dit dossier niet
geraadpleegd — onzeker of daar aanvullende known issues staan.Testdekking (runner node --test): registry-vorm en handler-gedrag
(test/basic/commands.basic.test.js), toState/_parse
(test/basic/serialShell.basic.test.js), randgevallen incl. onherkenbare strings →
inactive en autodetect zonder hardware
(test/edge/serialShell.edge.test.js), en een domein-integratietest met fake shell
die auto-skipt in een standalone checkout
(test/integration/domain.integration.test.js:9-21).
https://lab.wbd-rd.nl/rnd-lab/projects/evolv-edgeio
(stop wordt nog aangemaakt).CONTRACT.md (+ HTML-spiegel docs/contract.html),
docs/functional.html, docs/direct-io.html en
examples/basic.flow.json (demoflow met injects voor output 0 aan/uit en toggle
van output 1).RnD/EVOLV (super-repo 74c4089, 2026-07-03) — submodule
edgeIO @ 5e0cf0b1e6da, plus het feitendossier van deze analyse.
Analysedatum 2026-07-17.git fetch
gedraaid: de submodule-werkkopie 5e0cf0b1e6da is gelijk aan
origin/main (geen divergentie).pad:regel-verwijzing. De interactieve console en het DIO-paneel
zijn getrouwe herimplementaties; de toState-mapping is vooraf met
node tegen src/serialShell.js doorgerekend (testcases als
commentaar in de pagina-JS).