2 Commits

Author SHA1 Message Date
znetsixe
965e3ba305 fix(examples): add required ui-chart props to basic.flow.json (flow-lint)
4 charts were missing interpolation + colors — FlowFuse ui-chart renders blank
without them. Added the canonical required-property set. Lint-clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 19:18:42 +02:00
znetsixe
9427b64bbe feat(commands): route data.flow through the registry unit normaliser; wiki sync
- data.flow: declare unit:'m3/h' (payloadSchema any) and drop the handler's own
  convert() call — the registry now converts any number/string/{value,unit} to
  m3/h before the handler. Handler reads the normalised scalar.
- Regenerate wiki topic-contract.

data.flow integration test green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 18:41:33 +02:00
4 changed files with 64 additions and 20 deletions

View File

@@ -239,7 +239,19 @@
"action": "append", "action": "append",
"x": 1170, "x": 1170,
"y": 120, "y": 120,
"wires": [] "wires": [],
"interpolation": "linear",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
]
}, },
{ {
"id": "monster_basic_chart_total", "id": "monster_basic_chart_total",
@@ -264,7 +276,19 @@
"action": "append", "action": "append",
"x": 1180, "x": 1180,
"y": 180, "y": 180,
"wires": [] "wires": [],
"interpolation": "linear",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
]
}, },
{ {
"id": "monster_basic_chart_bucket", "id": "monster_basic_chart_bucket",
@@ -289,7 +313,19 @@
"action": "append", "action": "append",
"x": 1180, "x": 1180,
"y": 240, "y": 240,
"wires": [] "wires": [],
"interpolation": "linear",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
]
}, },
{ {
"id": "monster_basic_chart_pulse", "id": "monster_basic_chart_pulse",
@@ -314,7 +350,19 @@
"action": "append", "action": "append",
"x": 1190, "x": 1190,
"y": 300, "y": 300,
"wires": [] "wires": [],
"interpolation": "linear",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
]
}, },
{ {
"id": "monster_basic_text_status", "id": "monster_basic_text_status",

View File

@@ -5,8 +5,6 @@
// handler (the legacy nodeClass did it inline) — anything else inbound // handler (the legacy nodeClass did it inline) — anything else inbound
// is passed straight through to source.handleInput. // is passed straight through to source.handleInput.
const { convert } = require('generalFunctions');
exports.cmdStart = (source, msg) => { exports.cmdStart = (source, msg) => {
source.handleInput('i_start', Boolean(msg.payload)); source.handleInput('i_start', Boolean(msg.payload));
}; };
@@ -20,17 +18,15 @@ exports.setRain = (source, msg) => {
}; };
exports.dataFlow = (source, msg, ctx) => { exports.dataFlow = (source, msg, ctx) => {
// The registry has already normalised any accepted shape (number, numeric
// string, or { value, unit }) to a number in m3/h and tagged msg.unit.
const log = ctx?.logger || source.logger; const log = ctx?.logger || source.logger;
const value = Number(msg.payload?.value); const value = Number(msg.payload);
const unit = msg.payload?.unit; if (!Number.isFinite(value)) {
if (!Number.isFinite(value) || !unit) { log?.warn?.(`data.flow payload must be numeric, got '${JSON.stringify(msg.payload)}'.`);
log?.warn?.('data.flow payload must include numeric value and unit.');
return; return;
} }
let converted = value; source.handleInput('input_q', { value, unit: msg.unit || 'm3/h' });
try { converted = convert(value).from(unit).to('m3/h'); }
catch (err) { log?.warn?.(`data.flow unit conversion failed: ${err.message}`); return; }
source.handleInput('input_q', { value: converted, unit: 'm3/h' });
}; };
exports.setMode = (source, msg) => { exports.setMode = (source, msg) => {

View File

@@ -32,11 +32,11 @@ module.exports = [
{ {
topic: 'data.flow', topic: 'data.flow',
aliases: ['input_q'], aliases: ['input_q'],
payloadSchema: { type: 'object' }, // any: number, numeric string, or { value, unit } — the registry normalises
// Compound payload `{value, unit}` — handler converts internally to m3/h. // all of them to a number in `unit` (m3/h) before the handler runs.
// Registry-level normalisation is skipped (the handler reads payload.value / payloadSchema: { type: 'any' },
// payload.unit directly; flattening would break it). unit: 'm3/h',
description: 'Push the upstream flow measurement (payload: {value, unit}).', description: 'Push the upstream flow measurement (payload: number or {value, unit}).',
handler: handlers.dataFlow, handler: handlers.dataFlow,
}, },
{ {

View File

@@ -23,7 +23,7 @@ The registry lives in `src/commands/index.js`. Each descriptor maps a canonical
| `cmd.start` | `i_start` | any | — | Trigger / release the sampler start gate. | | `cmd.start` | `i_start` | any | — | Trigger / release the sampler start gate. |
| `set.schedule` | `monsternametijden` | any | — | Replace the sampling-times schedule. | | `set.schedule` | `monsternametijden` | any | — | Replace the sampling-times schedule. |
| `set.rain` | `rain_data` | any | — | Push current rain-event data into the sampler logic. | | `set.rain` | `rain_data` | any | — | Push current rain-event data into the sampler logic. |
| `data.flow` | `input_q` | `object` | — | Push the upstream flow measurement (payload: {value, unit}). | | `data.flow` | `input_q` | any | `volumeFlowRate` (default `m3/h`) | Push the upstream flow measurement (payload: number or {value, unit}). |
| `set.mode` | `setMode` | any | — | Switch the monster between auto / manual modes. | | `set.mode` | `setMode` | any | — | Switch the monster between auto / manual modes. |
| `set.model-prediction` | `model_prediction` | any | — | Push the upstream rain-prediction snapshot used by the sampler. | | `set.model-prediction` | `model_prediction` | any | — | Push the upstream rain-prediction snapshot used by the sampler. |
| `child.register` | `registerChild` | `string` | — | Register a child node (typically a measurement) with this monster. | | `child.register` | `registerChild` | `string` | — | Register a child node (typically a measurement) with this monster. |