Waterschap Brabantse Delta · R&D-lab · uitleg
coresync is de brug tussen de live EVOLV-plantflow en de FROST-server
(OGC SensorThings API): het geeft elk asset één duurzame identiteit in het asset-register
en streamt telemetrie ernaartoe zonder de server te overspoelen. De node wijkt bewust
af van het standaardpatroon van de repo — geen command-envelope, geen
tick-loop, geen 3-lagenpatroon: hij routeert op msg.topic en stuurt
kant-en-klare HTTP-request-envelopes uit die een externe http request-node
uitvoert. Deze pagina volgt letterlijk wat de code doet aan de hand van één meetwaarde en een
interactieve figuur die de swinging-door-reducer getrouw naspeelt.
RnD/EVOLV, submodule
coresync @ 9d1d565da740 (de werkkopie wijkt af van de gitlink);
na versheids-herverificatie tegen origin/main (zie verantwoording onderaan).coresync zit aan de rand van de flow van een site, stroomafwaarts van de
measurement- en machine-nodes, en voedt FROST parallel aan de lokale InfluxDB-historian
(docs/functional.html:146). Het is infrastructuur, geen procesequipment:
er is geen S88-level-lane; palette-swatch #54647B "dark slate — infrastructuur"
(coresync.html:4). Eén coresync per flow volstaat — de node multiplext alle
streams op een eigen afgeleide sleutel.
coresync is anders dan de andere twaalf nodes. Houd dit vast bij het lezen:
msg.command-verb en geen command registry —
coresync routeert op msg.topic: telemetrie (al het overige),
frost.response, model.set, coresync.flush
(src/coreSyncDomain.js:57-68).setInterval: volledig message-driven, output verschijnt
alléén bij binnenkomende berichten. Geen nodeClass.js/specificClass.js;
domein = coreSyncDomain.js (src/coreSyncDomain.js:28).http request-node; poort 2 =
altijd null (geen parent/child) (src/coreSyncDomain.js:85).Rode draad door de pagina: één dbase-rij van
een rotatingMachine —
{measurement:'pump1', fields:{'flow.measured.downstream':42.0}, tags:{tagcode:'FT3'}}.
De reis: normaliseren → stream-identiteit afleiden → swinging-door (ε-deur) →
FROST-envelope → verplichte loopback frost.response → metadata-keten →
Observation. De afgeleide sleutel wordt
streamKey = FT3:flow:measured:downstream:<sensorTag>, met
externalKeys thing:FT3, observedProperty:flow,
datastream:<streamKey> (src/identity.js:38-79). De
swinging-door-getallen in de figuur zijn met node nagerekend tégen
src/reducer.js.
Het I/O-contract in één oogopslag. coresync kent geen verbs — kies
een topic, stel de velden in, kopieer het exacte injecteerbare msg, en
klik Injecteer ▸ om te zien wat er uit de drie poorten komt (met live afgeleide
identiteit en getrouwe envelope-vormen). Elke bewering draagt een pad:regel-chip.
Poortlabels ["process","dbase","parent"] (coresync.html:25-28).
msg.topicPoort 1 is altijd een array van deze envelopes.
Voor de rode-draadstream FT3 produceert de metadata-keten na elkaar een
lookup, bij een lege lookup een create, en zodra alle ids bekend zijn de
observation. {FROST_BASE} = de geresolvede FROST-basis-URL
(config-node/env); de $filter rijdt op de wire URL-encoded; de
Authorization-header (Basic) is hier gemaskeerd — hij komt uit de
frost-server config-node en staat nooit in flow.json
(src/frostRequests.js:19-112, coresync.js:117-121).
msg.topic (src/coreSyncDomain.js:57-68);
alleen velden met typeof value==='number' && Number.isFinite(value)
tellen mee (src/normalizer.js:24).msg.topic | payload | effect |
|---|---|---|
| (al het overige) = telemetrie | EVOLV dbase-rij
{measurement, fields, tags, timestamp?/time?/ts?, source?} of array
daarvan | elk eindig-numeriek veld → stream → reducer → evt.
Observation-requests (src/coreSyncDomain.js:70-86) |
frost.response | antwoord van de http-request-node, met
originele _coreSync-meta teruggeëchood | metadata/observation-state
vooruit; verplichte loopback — zonder blijft elke stream hangen op lookup
(src/coreSyncDomain.js:88-136) |
model.set | {tag, catalogId:'<softwareType>:<modelId>',
serialNumber?, reason?, by?, phenomenonTime?} | PATCH Thing +
append-only installedModel-Observation (src/modelSet.js:82-99) |
coresync.flush | — | elke stream stuurt zijn
vastgehouden laatste punt als knot uit (src/coreSyncDomain.js:64-66) |
[port0, port1, port2]).
dbaseFormat kent alleen de waarde 'frost' — er is geen
Influx-formattering in deze node (coresync.html:66-69).| poort | label | inhoud |
|---|---|---|
| 0 | process | diagnostiek; telemetriepad stuurt hier
niets (src/coreSyncDomain.js:85). Wel: frost.error
{streamKey, requestId, statusCode, kind, action} en
model.set.done/.failed/.rejected; meerdere tegelijk → array
(src/coreSyncDomain.js:281-296, src/modelSet.js:275-319). |
| 1 | dbase | altijd een array van envelopes
(of null): {topic, requestId, _coreSync, method, url, headers, payload};
topics frost.metadata.lookup/.create, frost.observation.create,
frost.thing.patch (src/frostRequests.js:19-112,
src/modelSet.js:222-242). |
| 2 | parent | altijd null in deze
build; geen parent/child-registratie, geen childRegistrationUtils
(docs/contract.html:93). |
coresync doet zelf geen HTTP. Telemetrie komt
van de measurement/machine-nodes binnen; poort 1 stuurt envelopes naar een externe
http request-node, die FROST belt; het antwoord moet als frost.response
terug de input in. Zonder die loopback hangt elke stream op zijn eerste lookup.
frost.response-loopback
p0 diagnostiek · meestal null
Naast het datapad kent coresync drie editor-time
admin-endpoints (alle onder RED.auth.needsPermission('coresync.read')), die de
tagpicker van een asset-node vóór Deploy aanroept:
GET /coresync/allocate-tag (mint tag+uuid, idempotente rebind op bestaand uuid),
GET /coresync/resolve-tag (bindt aan bestaand Thing, maakt nóóit aan) en
GET /coresync/list-things (coresync.js:37-112). Foutcontract: 400 bij
ontbrekende query-param, 502 bij FROST-fout. Deze endpoints bellen FROST wél direct via de
eigen minimale HTTP-client (src/httpClient.js:3-8).
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, en het codepad.
Dit is de kern: hoe weinig punten FROST krijgt zonder de curve te verliezen. Kies een
ruwe reeks, schuif de tolerantie ε (percentage van de meetwaarde) en de absolute vloer.
De grafiek toont de ruwe samples (dicht), de overgebleven knots (weinig) en de
lineaire heropbouw die Grafana/Influx zónder decoder tekent. De onderste grafiek
bewijst dat de fout |heropbouw − origineel| onder de ε-band blijft voor élk
sample. De reducer hieronder is een herimplementatie van src/reducer.js,
vooraf met node geverifieerd (rechte ramp 200 samples → exact 2 knots;
ε-garantie én knot-aantal-pariteit over ramp/driehoek/sinus/ruis — testcases als
commentaar in de pagina-JS).
[(v−ε/2−v₀)/Δt, (v+ε/2−v₀)/Δt]
op t.o.v. het anker; de banden worden geïntersecteerd. Loopt de intersectie leeg, dan
wordt het vórige punt een knot en her-ankeren de deuren daar. Een lineaire redraw heeft
max 2× de deurafwijking als fout, dus ε/2-deuren houden de
decoder-vrije redraw aantoonbaar binnen ε (src/reducer.js:155-190).msg.command, geen command registry. De vier
msg.topic-waarden zijn de enige "taal": telemetrie,
frost.response, model.set, coresync.flush. De
toestand zit per stream (id-cache, reducer, pending observations)
(src/coreSyncDomain.js:57-68,148-168).src/reducer.js:161-165, test/basic/coresync.basic.test.js:358-384).streamKey +
deterministische externalKeys (thing:<tag>,
datastream:<streamKey>, …). De gemunte tag+uuid staat óók op het
geprinte QR-label; het beoogde reconnect-pad is QR-scan
(src/identity.js:63-79, coresync.js:94-96).OM_Observation, object-result). Warn-but-accept validatie;
een onbekende tag is de enige harde fout — "model.set never creates Things"
(src/modelSet.js:145-167,222-270).maxQueuedObservationsPerStream (config genegeerd, cap hardcoded
op 2), diagnosticsEnabled (nergens gelezen), backoffFailures
(geteld, nooit gebruikt), _coreSync.nextKind (gezet, nooit gevuld/gelezen).
aanname deels onbedoeld
(src/coreSyncDomain.js:39,132,252-262, src/frostRequests.js:39).thing:ft3 bij edge-startup, src/coreSyncDomain.js:43-51).
De editor-time tag-allocatie is niet cross-instance veilig — prototype; de echte
oplossing is een Postgres UNIQUE-index (src/allocator.js:14-18).Editor-defaults in coresync.html:5-24, doorgegeven aan het domein in
coresync.js:129-149. De belangrijkste velden:
coresync.html:5-24 voor de volledige lijst (o.a.
legacy angleToleranceDeg, timeScaleMs, dead-band guards
minDeltaTimeMs/minDeltaValue). De config-node frost-server
houdt username/password in Node-RED's encrypted store — nooit in
flow.json (frostServer.js:9-10).| veld | default | werking |
|---|---|---|
frostServer / frostBaseUrl | '' | FROST-verbinding; precedentie: config-node > veld > FROST_BASE_URL/FROST_VERSION env > localhost-fallback (src/frostConfig.js:24-48) |
serviceVersion | 'v1.1' | API-versie in het URL-pad (coresync.html:9) |
comparisonMode | 'sdt' | reducer-algoritme; angle/relative-slope zijn legacy, selecteerbaar voor al uitgerolde flows (src/reducer.js:4-9) |
defaultTolerancePct | 1 | ε-percentage van de meetwaarde als de stream geen instrumentspec draagt (src/reducer.js:10-15) |
toleranceFloor | 0 | absolute ondergrens ε (deadband nabij nul; coresync.html:92) |
maxGapMs | 300000 | forceert een knot (reason:'max-gap') als het anker ouder is; 0 = uit (src/reducer.js:23,109-113) |
burstWindowMs | 0 | sub-tick bursts binnen het venster samenvouwen tot één observatie; ~10 ms geadviseerd bij rotatingMachine-dubbelsamples (coresync.html:116, src/reducer.js:101-107) |
assetTagOverride / sensorTagOverride | '' | forceren thingTag/sensorTag boven tags/measurement (src/identity.js:51-61) |
Alle punten hieronder zijn code-observaties d.d. 2026-07-17 (statische analyse, niet
in runtime geverifieerd); elk draagt een pad:regel.
maxQueuedObservationsPerStream is effectief dood — de waarde wordt
geconfigureerd en genormaliseerd naar default 2 (src/coreSyncDomain.js:39),
maar _enqueuePendingObservation hardcodeert de capaciteit op 2 (eerste +
laatste punt) en leest de config-waarde nergens
(src/coreSyncDomain.js:252-262). aanname: mogelijk
onbedoeld. Het "keeps first and latest"-gedrag zelf is wél getest
(test/basic/coresync.basic.test.js:155-168).diagnosticsEnabled is een dood configveld — gedefinieerd in de
editor-defaults (coresync.html:23) zonder UI-rij, en nergens in de
runtime-code gelezen (grep: alleen html-hit).backoffFailures wordt geteld maar nooit gelezen
(src/coreSyncDomain.js:132) — er is geen retry/backoff op mislukte
metadata-requests; herstel leunt op de volgende knot van dezelfde of een sibling-stream._coreSync.nextKind is vestigiaal — het veld wordt in de envelope
gezet (src/frostRequests.js:39) maar nergens gevuld of gelezen.src/allocator.js:14-18, test/_output-manifest.md:50-54).Prefer: return=representation (lege POST-body) →
id-recovery loopt via re-lookup/uuid-read-back (test/_output-manifest.md:55-56,
src/coreSyncDomain.js:116-127).src/nodeClass.js/src/specificClass.js; tweede node-bestand
frostServer.js in dezelfde map. Of dit als "legacy drift" geregistreerd staat
is onzeker — coresync ontbreekt in de drift-tabel (EVOLV/CLAUDE.md);
aanname dat registratie beoogd was.package.json:7) in plaats van
node --test zoals de repo-regel voorschrijft; de output-manifest dekt bovendien
alleen de asset-registry-surface en model.set — het pre-existing telemetriepad is "not yet
back-filled" (test/_output-manifest.md:3-7).https://lab.wbd-rd.nl/rnd-lab/projects/evolv-coresync.docs/functional.html, docs/contract.html,
docs/identity.html; ontwerpdocument voor model.set:
docs/prd/coresync-frost-model-identity-history.md in de EVOLV-superproject-repo
(genoemd in src/modelSet.js:5; niet in deze nodemap gecontroleerd).docs/contract.html en
test/_output-manifest.md.RnD/EVOLV
(super-repo 74c4089, 2026-07-03) — submodule coresync geanalyseerd
@ 9d1d565da740 (de werkkopie wijkt af van de gitlink in het super-repo), plus het
feitendossier van deze analyse. Analysedatum 2026-07-17.git fetch
gedraaid: de geanalyseerde tip 9d1d565da740 is een ancestor van
origin/main (16384adc5209, 17 commits verder). De reducer-,
identity-, frostRequests- en normalizer-modules zijn identiek aan origin/main;
alleen coreSyncDomain.js is op main later vereenvoudigd (de shared-entity
registry). De figuur en console zijn tegen de geanalyseerde revisie geverifieerd.pad:regel-verwijzing. De swinging-door-figuur en de envelope-vormen
zijn getrouwe herimplementaties, vooraf met node tegen src/reducer.js
doorgerekend (ramp 200→2 knots; ε-garantie + knot-aantal-pariteit over 5 signaalvormen;
testcases als commentaar in de pagina-JS).node --test.