P9.3 + examples: fresh 3-tier flows + pilot wiki Home.md
examples/ (new — was empty except standalone-demo.js):
01-Basic.json 14 nodes, inject + dashboard, no parent
02-Integration.json 32 nodes, 2 tabs, measurement + MGC + 2 pumps,
link-out/link-in channels per node-red-flow-layout.md
03-Dashboard.json 63 nodes, 3 tabs (process + UI + setup),
FlowFuse charts + sliders, trend-split pattern
README.md load instructions
tools/build-examples.js regenerator
All canonical topic names only (set.*, cmd.*, data.*, child.*). No
legacy aliases. Every ui-* widget has x/y. Every chart has the full
mandatory key set from node-red-flow-layout.md §4.
wiki/Home.md (new) — pilot page for the 14-section visual-first template.
Sections 5 (topic-contract) + 9 (data-model) are auto-generated via the
new npm run wiki:* scripts; everything else hand-written following
.claude/refactor/WIKI_TEMPLATE.md.
package.json — added wiki:contract / wiki:datamodel / wiki:all scripts
wired to ../generalFunctions/scripts/wikiGen.js.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:50:45 +02:00
[
2026-05-29 18:47:19 +02:00
{
"id" : "ps_basic_tab" ,
"type" : "tab" ,
"label" : "PumpingStation - Basic" ,
"disabled" : false ,
"info" : "Tier 1: single pumpingStation node driven by inject nodes only. Demonstrates the canonical Phase-2 topic API: set.mode, set.inflow, set.demand."
} ,
{
"id" : "ps_basic_title" ,
"type" : "comment" ,
"z" : "ps_basic_tab" ,
"name" : "PumpingStation - Basic\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nA 50 m³ basin (3.5 m tall, inflow at 3.0 m, outflow at 0.2 m,\noverflow at 3.2 m). controlMode = levelbased, manual demand allowed\nonly when set.mode = manual.\n\nHOW TO USE:\n 1. Deploy the flow.\n 2. Click \"set.mode = manual\" so set.demand is honoured.\n 3. Click \"set.inflow = 60 m3/h\" to push wastewater into the basin.\n 4. Watch the basin fill on Port 0 (level, volume, percControl rise).\n 5. Click \"calibrate volume 25 m3\" to jump straight to half-full.\n\nAliases (changemode, q_in, Qd, …) still work but log a deprecation\nwarning - fresh flows use the canonical names." ,
"info" : "" ,
"x" : 600 ,
"y" : 40 ,
"wires" : [ ]
} ,
{
"id" : "ps_basic_inj_mode" ,
"type" : "inject" ,
"z" : "ps_basic_tab" ,
"name" : "set.mode = manual" ,
"props" : [
{
"p" : "topic" ,
"vt" : "str"
} ,
{
"p" : "payload" ,
"v" : "manual" ,
"vt" : "str"
}
] ,
"topic" : "set.mode" ,
"repeat" : "" ,
"crontab" : "" ,
"once" : false ,
"onceDelay" : "" ,
"x" : 200 ,
"y" : 160 ,
"wires" : [
[
"ps_basic_node"
]
]
} ,
{
"id" : "ps_basic_inj_mode_lvl" ,
"type" : "inject" ,
"z" : "ps_basic_tab" ,
"name" : "set.mode = levelbased" ,
"props" : [
{
"p" : "topic" ,
"vt" : "str"
} ,
{
"p" : "payload" ,
"v" : "levelbased" ,
"vt" : "str"
}
] ,
"topic" : "set.mode" ,
"repeat" : "" ,
"crontab" : "" ,
"once" : false ,
"onceDelay" : "" ,
"x" : 220 ,
"y" : 200 ,
"wires" : [
[
"ps_basic_node"
]
]
} ,
{
"id" : "ps_basic_inj_inflow" ,
"type" : "inject" ,
"z" : "ps_basic_tab" ,
"name" : "set.inflow = 60 m3/h" ,
"props" : [
{
"p" : "topic" ,
"vt" : "str"
} ,
{
"p" : "payload" ,
"v" : "60" ,
"vt" : "num"
} ,
{
"p" : "unit" ,
"v" : "m3/h" ,
"vt" : "str"
}
] ,
"topic" : "set.inflow" ,
"repeat" : "" ,
"crontab" : "" ,
"once" : false ,
"onceDelay" : "" ,
"x" : 200 ,
"y" : 260 ,
"wires" : [
[
"ps_basic_node"
]
]
} ,
{
"id" : "ps_basic_inj_demand" ,
"type" : "inject" ,
"z" : "ps_basic_tab" ,
"name" : "set.demand = 40 m3/h" ,
"props" : [
{
"p" : "topic" ,
"vt" : "str"
} ,
{
"p" : "payload" ,
"v" : "40" ,
"vt" : "num"
} ,
{
"p" : "unit" ,
"v" : "m3/h" ,
"vt" : "str"
}
] ,
"topic" : "set.demand" ,
"repeat" : "" ,
"crontab" : "" ,
"once" : false ,
"onceDelay" : "" ,
"x" : 200 ,
"y" : 300 ,
"wires" : [
[
"ps_basic_node"
]
]
} ,
{
"id" : "ps_basic_inj_calvol" ,
"type" : "inject" ,
"z" : "ps_basic_tab" ,
"name" : "calibrate volume 25 m3" ,
"props" : [
{
"p" : "topic" ,
"vt" : "str"
} ,
{
"p" : "payload" ,
"v" : "25" ,
"vt" : "num"
} ,
{
"p" : "unit" ,
"v" : "m3" ,
"vt" : "str"
}
] ,
"topic" : "cmd.calibrate.volume" ,
"repeat" : "" ,
"crontab" : "" ,
"once" : false ,
"onceDelay" : "" ,
"x" : 220 ,
"y" : 360 ,
"wires" : [
[
"ps_basic_node"
]
]
} ,
{
"id" : "ps_basic_inj_callvl" ,
"type" : "inject" ,
"z" : "ps_basic_tab" ,
"name" : "calibrate level 1.5 m" ,
"props" : [
{
"p" : "topic" ,
"vt" : "str"
} ,
{
"p" : "payload" ,
"v" : "1.5" ,
"vt" : "num"
} ,
{
"p" : "unit" ,
"v" : "m" ,
"vt" : "str"
}
] ,
"topic" : "cmd.calibrate.level" ,
"repeat" : "" ,
"crontab" : "" ,
"once" : false ,
"onceDelay" : "" ,
"x" : 220 ,
"y" : 400 ,
"wires" : [
[
"ps_basic_node"
]
]
} ,
{
"id" : "ps_basic_node" ,
"type" : "pumpingStation" ,
"z" : "ps_basic_tab" ,
"name" : "Pumping Station" ,
"simulator" : false ,
"basinVolume" : 50 ,
"basinHeight" : 3.5 ,
"inflowLevel" : 3 ,
"outflowLevel" : 0.2 ,
"overflowLevel" : 3.2 ,
"defaultFluid" : "wastewater" ,
"inletPipeDiameter" : 0.3 ,
"outletPipeDiameter" : 0.3 ,
"pipelineLength" : 80 ,
"maxDischargeHead" : 24 ,
"staticHead" : 12 ,
"maxInflowRate" : 200 ,
"temperatureReferenceDegC" : 15 ,
"timeleftToFullOrEmptyThresholdSeconds" : 0 ,
"enableDryRunProtection" : true ,
"enableOverfillProtection" : true ,
"dryRunThresholdPercent" : 2 ,
"overfillThresholdPercent" : 98 ,
"minHeightBasedOn" : "outlet" ,
"processOutputFormat" : "process" ,
"dbaseOutputFormat" : "influxdb" ,
"refHeight" : "NAP" ,
"basinBottomRef" : 1 ,
"uuid" : "example-ps-001" ,
"supplier" : "WBD-RD" ,
"category" : "station" ,
"assetType" : "pumpingstation" ,
"model" : "demo-50m3" ,
"unit" : "m3/h" ,
"enableLog" : true ,
"logLevel" : "info" ,
"positionVsParent" : "atEquipment" ,
"positionIcon" : "" ,
"hasDistance" : false ,
"distance" : "" ,
"distanceUnit" : "m" ,
"distanceDescription" : "" ,
"controlMode" : "levelbased" ,
"startLevel" : 1.2 ,
"minLevel" : 0.4 ,
"maxLevel" : 2.8 ,
"flowSetpoint" : null ,
"flowDeadband" : null ,
"x" : 1320 ,
"y" : 300 ,
"wires" : [
[
"ps_basic_format"
] ,
[
"ps_basic_dbg_influx"
] ,
[
"ps_basic_dbg_parent"
]
]
} ,
{
"id" : "ps_basic_format" ,
"type" : "function" ,
"z" : "ps_basic_tab" ,
"name" : "Merge deltas + format" ,
"func" : "const p = (msg && msg.payload && typeof msg.payload === 'object') ? msg.payload : {};\nconst cache = context.get('c') || {};\nObject.assign(cache, p);\ncontext.set('c', cache);\nfunction pick(prefix) {\n for (const k of Object.keys(cache)) if (k === prefix || k.indexOf(prefix + '.') === 0) {\n const v = Number(cache[k]); if (Number.isFinite(v)) return v;\n } return null;\n}\nconst vol = pick('volume.predicted.atequipment');\nconst lvl = pick('level.predicted.atequipment');\nconst flIn = pick('flow.predicted.in');\nmsg.payload = {\n state: cache.state || 'unknown',\n controlMode: cache.controlMode || cache.mode || 'n/a',\n direction: cache.direction || 'n/a',\n percControl: cache.percControl != null ? Number(cache.percControl).toFixed(1) + ' %' : 'n/a',\n volume: vol != null ? vol.toFixed(2) + ' m3' : 'n/a',\n volumePercent: cache.volumePercent != null ? Number(cache.volumePercent).toFixed(1) + ' %' : 'n/a',\n level: lvl != null ? lvl.toFixed(3) + ' m' : 'n/a',\n inflow: flIn != null ? (flIn * 3600).toFixed(1) + ' m3/h' : 'n/a',\n timeToFull: cache.timeToFull != null ? Number(cache.timeToFull).toFixed(0) + ' s' : 'n/a',\n timeToEmpty: cache.timeToEmpty != null ? Number(cache.timeToEmpty).toFixed(0) + ' s' : 'n/a'\n};\nreturn msg;" ,
"outputs" : 1 ,
"noerr" : 0 ,
"initialize" : "" ,
"finalize" : "" ,
"libs" : [ ] ,
"x" : 1560 ,
"y" : 280 ,
"wires" : [
[
"ps_basic_dbg_process"
]
]
} ,
{
"id" : "ps_basic_dbg_process" ,
"type" : "debug" ,
"z" : "ps_basic_tab" ,
"name" : "Port 0: Process" ,
"active" : true ,
"tosidebar" : true ,
"console" : false ,
"tostatus" : false ,
"complete" : "payload" ,
"targetType" : "msg" ,
"x" : 1800 ,
"y" : 240 ,
"wires" : [ ]
} ,
{
"id" : "ps_basic_dbg_influx" ,
"type" : "debug" ,
"z" : "ps_basic_tab" ,
"name" : "Port 1: InfluxDB" ,
"active" : false ,
"tosidebar" : true ,
"console" : false ,
"tostatus" : false ,
"complete" : "true" ,
"targetType" : "full" ,
"x" : 1800 ,
"y" : 320 ,
"wires" : [ ]
} ,
{
"id" : "ps_basic_dbg_parent" ,
"type" : "debug" ,
"z" : "ps_basic_tab" ,
"name" : "Port 2: Parent reg" ,
"active" : true ,
"tosidebar" : true ,
"console" : false ,
"tostatus" : false ,
"complete" : "true" ,
"targetType" : "full" ,
"x" : 1800 ,
"y" : 380 ,
"wires" : [ ]
} ,
{
"id" : "grp_ps_basic" ,
"type" : "group" ,
"z" : "ps_basic_tab" ,
"name" : "Pumping Station (PC)" ,
"style" : {
"label" : true ,
"stroke" : "#000000" ,
"fill" : "#0c99d9" ,
"fill-opacity" : "0.10"
P9.3 + examples: fresh 3-tier flows + pilot wiki Home.md
examples/ (new — was empty except standalone-demo.js):
01-Basic.json 14 nodes, inject + dashboard, no parent
02-Integration.json 32 nodes, 2 tabs, measurement + MGC + 2 pumps,
link-out/link-in channels per node-red-flow-layout.md
03-Dashboard.json 63 nodes, 3 tabs (process + UI + setup),
FlowFuse charts + sliders, trend-split pattern
README.md load instructions
tools/build-examples.js regenerator
All canonical topic names only (set.*, cmd.*, data.*, child.*). No
legacy aliases. Every ui-* widget has x/y. Every chart has the full
mandatory key set from node-red-flow-layout.md §4.
wiki/Home.md (new) — pilot page for the 14-section visual-first template.
Sections 5 (topic-contract) + 9 (data-model) are auto-generated via the
new npm run wiki:* scripts; everything else hand-written following
.claude/refactor/WIKI_TEMPLATE.md.
package.json — added wiki:contract / wiki:datamodel / wiki:all scripts
wired to ../generalFunctions/scripts/wikiGen.js.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:50:45 +02:00
} ,
2026-05-29 18:47:19 +02:00
"nodes" : [
"ps_basic_node" ,
"ps_basic_format"
] ,
"x" : 1290 ,
"y" : 230 ,
"w" : 500 ,
"h" : 140
}
]