2025-10-14 13:52:34 +02:00
<!--
| S88-niveau | Primair (blokkleur) | Tekstkleur |
| ---------------------- | ------------------- | ---------- |
| **Area** | `#0f52a5` | wit |
| **Process Cell** | `#0c99d9` | wit |
| **Unit** | `#50a8d9` | zwart |
| **Equipment (Module)** | `#86bbdd` | zwart |
| **Control Module** | `#a9daee` | zwart |
2025-05-14 10:31:50 +02:00
2025-10-14 13:52:34 +02:00
-->
2025-06-23 13:23:51 +02:00
< script src = "/measurement/menu.js" > < / script > <!-- Load the menu script for dynamic dropdowns -->
< script src = "/measurement/configData.js" > < / script > <!-- Load the config script for node information -->
2025-06-20 17:14:22 +02:00
2025-06-12 17:05:28 +02:00
< script >
2025-05-14 10:31:50 +02:00
RED.nodes.registerType("measurement", {
2025-06-25 17:25:13 +02:00
category: "EVOLV",
2025-10-14 13:52:34 +02:00
color: "#a9daee", // color for the node based on the S88 schema
2025-05-14 10:31:50 +02:00
defaults: {
// Define default properties
2025-11-13 19:38:25 +01:00
name: { value: "" }, // use asset category as name
2025-05-14 10:31:50 +02:00
feat: digital (MQTT) mode + fix silent dispatcher bug for camelCase methods
Runtime:
- Fix silent no-op when user selected any camelCase smoothing or outlier
method from the editor. validateEnum in generalFunctions lowercases enum
values (zScore -> zscore, lowPass -> lowpass, ...) but the dispatcher
compared against camelCase keys. Effect: 5 of 11 smoothing methods
(lowPass, highPass, weightedMovingAverage, bandPass, savitzkyGolay) and
2 of 3 outlier methods (zScore, modifiedZScore) silently fell through.
Users got the raw last value or no outlier filtering with no error log.
Review any pre-2026-04-13 flows that relied on these methods.
Fix: normalize method names to lowercase on both sides of the lookup.
- New Channel class (src/channel.js) — self-contained per-channel pipeline:
outlier -> offset -> scaling -> smoothing -> min/max -> constrain -> emit.
Pure domain logic, no Node-RED deps, reusable by future nodes that need
the same signal-conditioning chain.
Digital mode:
- config.mode.current = 'digital' opts in. config.channels declares one
entry per expected JSON key; each channel has its own type, position,
unit, distance, and optional scaling/smoothing/outlierDetection blocks
that override the top-level analog-mode fields. One MQTT-shaped payload
({t:22.5, h:45, p:1013}) dispatches N independent pipelines and emits N
MeasurementContainer slots from a single input message.
- Backward compatible: absent mode config = analog = pre-digital behaviour.
Every existing measurement flow keeps working unchanged.
UI:
- HTML editor: new Mode dropdown and Channels JSON textarea. The Node-RED
help panel is rewritten end-to-end with topic reference, port contracts,
per-mode configuration, smoothing/outlier method tables, and a note
about the pre-fix behaviour.
- README.md rewritten (was a one-line stub).
Tests (12 -> 71, all green):
- test/basic/smoothing-methods.basic.test.js (+16): every smoothing method
including the formerly-broken camelCase ones.
- test/basic/outlier-detection.basic.test.js (+10): every outlier method,
fall-through, toggle.
- test/basic/scaling-and-interpolation.basic.test.js (+10): offset,
interpolateLinear, constrain, handleScaling edge cases, min/max
tracking, updateOutputPercent fallback, updateOutputAbs emit dedup.
- test/basic/calibration-and-stability.basic.test.js (+11): calibrate
(stable and unstable), isStable, evaluateRepeatability refusals,
toggleSimulation, tick simulation on/off.
- test/integration/digital-mode.integration.test.js (+12): channel build
(including malformed entries), payload dispatch, multi-channel emit,
unknown keys, per-channel scaling/smoothing/outlier, empty channels,
non-numeric value rejection, getDigitalOutput shape, analog-default
back-compat.
E2E verified on Dockerized Node-RED: analog regression unchanged; digital
mode deploys with three channels, dispatches MQTT-style payload, emits
per-channel events, accumulates per-channel smoothing, ignores unknown
keys.
Depends on generalFunctions commit e50be2e (permissive unit check +
mode/channels schema).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 13:43:03 +02:00
// Input mode: 'analog' (scalar payload, default) or 'digital' (object payload, many channels)
mode: { value: "analog" },
channels: { value: "[]" },
// Define specific properties (analog-mode pipeline defaults)
2025-05-14 10:31:50 +02:00
scaling: { value: false },
i_min: { value: 0, required: true },
i_max: { value: 0, required: true },
i_offset: { value: 0 },
o_min: { value: 0, required: true },
o_max: { value: 1, required: true },
simulator: { value: false },
smooth_method: { value: "" },
count: { value: "10", required: true },
2026-05-11 17:29:15 +02:00
stabilityThreshold: { value: 0.01 },
2026-03-12 16:39:25 +01:00
processOutputFormat: { value: "process" },
dbaseOutputFormat: { value: "influxdb" },
2025-05-14 10:31:50 +02:00
//define asset properties
2025-06-25 14:52:20 +02:00
uuid: { value: "" },
2025-05-14 10:31:50 +02:00
supplier: { value: "" },
2025-06-20 17:14:22 +02:00
category: { value: "" },
assetType: { value: "" },
2025-05-14 10:31:50 +02:00
model: { value: "" },
unit: { value: "" },
2026-01-29 09:16:33 +01:00
assetTagNumber: { value: "" },
2025-05-14 10:31:50 +02:00
2025-06-25 10:43:15 +02:00
//logger properties
enableLog: { value: false },
logLevel: { value: "error" },
//physicalAspect
2025-06-25 11:45:32 +02:00
positionVsParent: { value: "" },
2025-07-01 15:24:18 +02:00
positionIcon: { value: "" },
2025-09-05 16:20:12 +02:00
hasDistance: { value: false },
distance: { value: 0 },
distanceUnit: { value: "m" },
distanceDescription: { value: "" }
2025-06-25 10:43:15 +02:00
2025-05-14 10:31:50 +02:00
},
inputs: 1,
2025-06-20 17:14:22 +02:00
outputs: 3,
2025-06-25 17:25:13 +02:00
inputLabels: ["Input"],
2025-06-20 17:14:22 +02:00
outputLabels: ["process", "dbase", "parent"],
2025-10-14 13:52:34 +02:00
icon: "font-awesome/fa-sliders",
2025-05-14 10:31:50 +02:00
label: function () {
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
const modeTag = this.mode === 'digital' ? ' [digital]' : '';
return (this.positionIcon || "") + " " + (this.assetType || "Measurement") + modeTag;
2025-05-14 10:31:50 +02:00
},
2025-06-20 17:14:22 +02:00
2025-05-14 10:31:50 +02:00
oneditprepare: function() {
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
const node = this;
fix(editor): asset/logger/position menus broken by TDZ ReferenceError in oneditprepare
The previous oneditprepare ran applyMode(initialMode) early in the
function, which called validateChannelsJson(), which referenced const
declarations (channelsArea, channelsHint) that were declared later in
the same function. JavaScript hoists const into the Temporal Dead Zone,
so accessing them before the declaration line throws a ReferenceError.
That uncaught throw aborted the rest of oneditprepare — including the
waitForMenuData() call that initialises the asset / logger / position
menu placeholders. Symptom for the user: opening a measurement node in
the editor showed Mode + analog fields but the asset menu was empty.
Fixes:
1. Move waitForMenuData() to the very top of oneditprepare so the
shared menu init is independent of any later mode-block work. Even
if the mode logic ever throws again, the asset / logger / position
menus still render.
2. Resolve every DOM reference (modeSelect, analogBlock, digitalBlock,
modeHint, channelsArea, channelsHint) at the top of the function
before any helper that touches them is invoked. validateChannelsJson
and applyMode now read closed-over names that are guaranteed to be
initialised.
3. Guard applyMode(initialMode) with try/catch as defense in depth and
add null-checks on every DOM reference. A future template change
that drops one of the IDs will only no-op rather than break the
editor.
No runtime change. 71/71 tests still green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:15:06 +02:00
// === Asset / logger / position placeholders (dynamic menus) ===
// Kick these off FIRST so that any error in the downstream mode
// logic can never block the shared menus. Historical regression:
// a ReferenceError in the mode block aborted oneditprepare and
// stopped the asset menu from rendering at all.
const waitForMenuData = () => {
if (window.EVOLV?.nodes?.measurement?.initEditor) {
window.EVOLV.nodes.measurement.initEditor(node);
} else {
setTimeout(waitForMenuData, 50);
}
};
waitForMenuData();
// IMPORTANT: all DOM references are resolved up front so helper
// functions called during initial applyMode() don't trip over the
// Temporal Dead Zone on later `const` declarations.
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
fix(editor): asset/logger/position menus broken by TDZ ReferenceError in oneditprepare
The previous oneditprepare ran applyMode(initialMode) early in the
function, which called validateChannelsJson(), which referenced const
declarations (channelsArea, channelsHint) that were declared later in
the same function. JavaScript hoists const into the Temporal Dead Zone,
so accessing them before the declaration line throws a ReferenceError.
That uncaught throw aborted the rest of oneditprepare — including the
waitForMenuData() call that initialises the asset / logger / position
menu placeholders. Symptom for the user: opening a measurement node in
the editor showed Mode + analog fields but the asset menu was empty.
Fixes:
1. Move waitForMenuData() to the very top of oneditprepare so the
shared menu init is independent of any later mode-block work. Even
if the mode logic ever throws again, the asset / logger / position
menus still render.
2. Resolve every DOM reference (modeSelect, analogBlock, digitalBlock,
modeHint, channelsArea, channelsHint) at the top of the function
before any helper that touches them is invoked. validateChannelsJson
and applyMode now read closed-over names that are guaranteed to be
initialised.
3. Guard applyMode(initialMode) with try/catch as defense in depth and
add null-checks on every DOM reference. A future template change
that drops one of the IDs will only no-op rather than break the
editor.
No runtime change. 71/71 tests still green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:15:06 +02:00
const modeSelect = document.getElementById('node-input-mode');
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
const analogBlock = document.getElementById('analog-only-fields');
const digitalBlock = document.getElementById('digital-only-fields');
const modeHint = document.getElementById('mode-hint');
fix(editor): asset/logger/position menus broken by TDZ ReferenceError in oneditprepare
The previous oneditprepare ran applyMode(initialMode) early in the
function, which called validateChannelsJson(), which referenced const
declarations (channelsArea, channelsHint) that were declared later in
the same function. JavaScript hoists const into the Temporal Dead Zone,
so accessing them before the declaration line throws a ReferenceError.
That uncaught throw aborted the rest of oneditprepare — including the
waitForMenuData() call that initialises the asset / logger / position
menu placeholders. Symptom for the user: opening a measurement node in
the editor showed Mode + analog fields but the asset menu was empty.
Fixes:
1. Move waitForMenuData() to the very top of oneditprepare so the
shared menu init is independent of any later mode-block work. Even
if the mode logic ever throws again, the asset / logger / position
menus still render.
2. Resolve every DOM reference (modeSelect, analogBlock, digitalBlock,
modeHint, channelsArea, channelsHint) at the top of the function
before any helper that touches them is invoked. validateChannelsJson
and applyMode now read closed-over names that are guaranteed to be
initialised.
3. Guard applyMode(initialMode) with try/catch as defense in depth and
add null-checks on every DOM reference. A future template change
that drops one of the IDs will only no-op rather than break the
editor.
No runtime change. 71/71 tests still green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:15:06 +02:00
const channelsArea = document.getElementById('node-input-channels');
const channelsHint = document.getElementById('channels-validation');
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
fix(editor): asset/logger/position menus broken by TDZ ReferenceError in oneditprepare
The previous oneditprepare ran applyMode(initialMode) early in the
function, which called validateChannelsJson(), which referenced const
declarations (channelsArea, channelsHint) that were declared later in
the same function. JavaScript hoists const into the Temporal Dead Zone,
so accessing them before the declaration line throws a ReferenceError.
That uncaught throw aborted the rest of oneditprepare — including the
waitForMenuData() call that initialises the asset / logger / position
menu placeholders. Symptom for the user: opening a measurement node in
the editor showed Mode + analog fields but the asset menu was empty.
Fixes:
1. Move waitForMenuData() to the very top of oneditprepare so the
shared menu init is independent of any later mode-block work. Even
if the mode logic ever throws again, the asset / logger / position
menus still render.
2. Resolve every DOM reference (modeSelect, analogBlock, digitalBlock,
modeHint, channelsArea, channelsHint) at the top of the function
before any helper that touches them is invoked. validateChannelsJson
and applyMode now read closed-over names that are guaranteed to be
initialised.
3. Guard applyMode(initialMode) with try/catch as defense in depth and
add null-checks on every DOM reference. A future template change
that drops one of the IDs will only no-op rather than break the
editor.
No runtime change. 71/71 tests still green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:15:06 +02:00
// Initialise the mode < select > from the saved node.mode. Legacy
// nodes (saved before the mode field existed) fall back to
// 'analog' so they keep behaving exactly like before.
const initialMode = (node.mode === 'digital' || node.mode === 'analog') ? node.mode : 'analog';
if (modeSelect) modeSelect.value = initialMode;
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
fix(editor): asset/logger/position menus broken by TDZ ReferenceError in oneditprepare
The previous oneditprepare ran applyMode(initialMode) early in the
function, which called validateChannelsJson(), which referenced const
declarations (channelsArea, channelsHint) that were declared later in
the same function. JavaScript hoists const into the Temporal Dead Zone,
so accessing them before the declaration line throws a ReferenceError.
That uncaught throw aborted the rest of oneditprepare — including the
waitForMenuData() call that initialises the asset / logger / position
menu placeholders. Symptom for the user: opening a measurement node in
the editor showed Mode + analog fields but the asset menu was empty.
Fixes:
1. Move waitForMenuData() to the very top of oneditprepare so the
shared menu init is independent of any later mode-block work. Even
if the mode logic ever throws again, the asset / logger / position
menus still render.
2. Resolve every DOM reference (modeSelect, analogBlock, digitalBlock,
modeHint, channelsArea, channelsHint) at the top of the function
before any helper that touches them is invoked. validateChannelsJson
and applyMode now read closed-over names that are guaranteed to be
initialised.
3. Guard applyMode(initialMode) with try/catch as defense in depth and
add null-checks on every DOM reference. A future template change
that drops one of the IDs will only no-op rather than break the
editor.
No runtime change. 71/71 tests still green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:15:06 +02:00
// Populate the channels textarea from the saved node.channels
// (stored as a raw JSON string; parsing happens server-side).
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
if (channelsArea & & typeof node.channels === 'string') {
channelsArea.value = node.channels;
}
fix(editor): asset/logger/position menus broken by TDZ ReferenceError in oneditprepare
The previous oneditprepare ran applyMode(initialMode) early in the
function, which called validateChannelsJson(), which referenced const
declarations (channelsArea, channelsHint) that were declared later in
the same function. JavaScript hoists const into the Temporal Dead Zone,
so accessing them before the declaration line throws a ReferenceError.
That uncaught throw aborted the rest of oneditprepare — including the
waitForMenuData() call that initialises the asset / logger / position
menu placeholders. Symptom for the user: opening a measurement node in
the editor showed Mode + analog fields but the asset menu was empty.
Fixes:
1. Move waitForMenuData() to the very top of oneditprepare so the
shared menu init is independent of any later mode-block work. Even
if the mode logic ever throws again, the asset / logger / position
menus still render.
2. Resolve every DOM reference (modeSelect, analogBlock, digitalBlock,
modeHint, channelsArea, channelsHint) at the top of the function
before any helper that touches them is invoked. validateChannelsJson
and applyMode now read closed-over names that are guaranteed to be
initialised.
3. Guard applyMode(initialMode) with try/catch as defense in depth and
add null-checks on every DOM reference. A future template change
that drops one of the IDs will only no-op rather than break the
editor.
No runtime change. 71/71 tests still green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:15:06 +02:00
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
function validateChannelsJson() {
if (!channelsHint) return;
fix(editor): asset/logger/position menus broken by TDZ ReferenceError in oneditprepare
The previous oneditprepare ran applyMode(initialMode) early in the
function, which called validateChannelsJson(), which referenced const
declarations (channelsArea, channelsHint) that were declared later in
the same function. JavaScript hoists const into the Temporal Dead Zone,
so accessing them before the declaration line throws a ReferenceError.
That uncaught throw aborted the rest of oneditprepare — including the
waitForMenuData() call that initialises the asset / logger / position
menu placeholders. Symptom for the user: opening a measurement node in
the editor showed Mode + analog fields but the asset menu was empty.
Fixes:
1. Move waitForMenuData() to the very top of oneditprepare so the
shared menu init is independent of any later mode-block work. Even
if the mode logic ever throws again, the asset / logger / position
menus still render.
2. Resolve every DOM reference (modeSelect, analogBlock, digitalBlock,
modeHint, channelsArea, channelsHint) at the top of the function
before any helper that touches them is invoked. validateChannelsJson
and applyMode now read closed-over names that are guaranteed to be
initialised.
3. Guard applyMode(initialMode) with try/catch as defense in depth and
add null-checks on every DOM reference. A future template change
that drops one of the IDs will only no-op rather than break the
editor.
No runtime change. 71/71 tests still green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:15:06 +02:00
if (!modeSelect || modeSelect.value !== 'digital') {
channelsHint.textContent = '';
return;
}
const raw = (channelsArea & & channelsArea.value || '').trim();
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
if (!raw || raw === '[]') {
channelsHint.innerHTML = '< span style = "color:#b45309;" > Digital mode with no channels — no measurements will be emitted.< / span > ';
return;
}
try {
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed)) throw new Error('must be an array');
const missing = parsed
fix(editor): asset/logger/position menus broken by TDZ ReferenceError in oneditprepare
The previous oneditprepare ran applyMode(initialMode) early in the
function, which called validateChannelsJson(), which referenced const
declarations (channelsArea, channelsHint) that were declared later in
the same function. JavaScript hoists const into the Temporal Dead Zone,
so accessing them before the declaration line throws a ReferenceError.
That uncaught throw aborted the rest of oneditprepare — including the
waitForMenuData() call that initialises the asset / logger / position
menu placeholders. Symptom for the user: opening a measurement node in
the editor showed Mode + analog fields but the asset menu was empty.
Fixes:
1. Move waitForMenuData() to the very top of oneditprepare so the
shared menu init is independent of any later mode-block work. Even
if the mode logic ever throws again, the asset / logger / position
menus still render.
2. Resolve every DOM reference (modeSelect, analogBlock, digitalBlock,
modeHint, channelsArea, channelsHint) at the top of the function
before any helper that touches them is invoked. validateChannelsJson
and applyMode now read closed-over names that are guaranteed to be
initialised.
3. Guard applyMode(initialMode) with try/catch as defense in depth and
add null-checks on every DOM reference. A future template change
that drops one of the IDs will only no-op rather than break the
editor.
No runtime change. 71/71 tests still green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:15:06 +02:00
.map((c, i) => (c & & c.key & & c.type ? null : 'entry ' + i + ': missing key or type'))
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
.filter(Boolean);
if (missing.length) {
channelsHint.innerHTML = '< span style = "color:#b45309;" > ' + missing.join('; ') + '< / span > ';
} else {
channelsHint.innerHTML = '< span style = "color:#047857;" > ' + parsed.length + ' channel(s) defined: ' + parsed.map((c) => c.key).join(', ') + '< / span > ';
}
} catch (e) {
channelsHint.innerHTML = '< span style = "color:#b91c1c;" > Invalid JSON: ' + e.message + '< / span > ';
}
}
fix(editor): asset/logger/position menus broken by TDZ ReferenceError in oneditprepare
The previous oneditprepare ran applyMode(initialMode) early in the
function, which called validateChannelsJson(), which referenced const
declarations (channelsArea, channelsHint) that were declared later in
the same function. JavaScript hoists const into the Temporal Dead Zone,
so accessing them before the declaration line throws a ReferenceError.
That uncaught throw aborted the rest of oneditprepare — including the
waitForMenuData() call that initialises the asset / logger / position
menu placeholders. Symptom for the user: opening a measurement node in
the editor showed Mode + analog fields but the asset menu was empty.
Fixes:
1. Move waitForMenuData() to the very top of oneditprepare so the
shared menu init is independent of any later mode-block work. Even
if the mode logic ever throws again, the asset / logger / position
menus still render.
2. Resolve every DOM reference (modeSelect, analogBlock, digitalBlock,
modeHint, channelsArea, channelsHint) at the top of the function
before any helper that touches them is invoked. validateChannelsJson
and applyMode now read closed-over names that are guaranteed to be
initialised.
3. Guard applyMode(initialMode) with try/catch as defense in depth and
add null-checks on every DOM reference. A future template change
that drops one of the IDs will only no-op rather than break the
editor.
No runtime change. 71/71 tests still green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:15:06 +02:00
function applyMode(mode) {
const isDigital = mode === 'digital';
if (analogBlock) analogBlock.style.display = isDigital ? 'none' : 'block';
if (digitalBlock) digitalBlock.style.display = isDigital ? 'block' : 'none';
if (modeHint) {
modeHint.textContent = isDigital
? 'msg.payload must be an OBJECT, e.g. {"temperature": 22.5, "humidity": 45}. Define each key below.'
: 'msg.payload must be a NUMBER (or numeric string). Configure scaling/smoothing below.';
2025-06-20 17:14:22 +02:00
}
fix(editor): asset/logger/position menus broken by TDZ ReferenceError in oneditprepare
The previous oneditprepare ran applyMode(initialMode) early in the
function, which called validateChannelsJson(), which referenced const
declarations (channelsArea, channelsHint) that were declared later in
the same function. JavaScript hoists const into the Temporal Dead Zone,
so accessing them before the declaration line throws a ReferenceError.
That uncaught throw aborted the rest of oneditprepare — including the
waitForMenuData() call that initialises the asset / logger / position
menu placeholders. Symptom for the user: opening a measurement node in
the editor showed Mode + analog fields but the asset menu was empty.
Fixes:
1. Move waitForMenuData() to the very top of oneditprepare so the
shared menu init is independent of any later mode-block work. Even
if the mode logic ever throws again, the asset / logger / position
menus still render.
2. Resolve every DOM reference (modeSelect, analogBlock, digitalBlock,
modeHint, channelsArea, channelsHint) at the top of the function
before any helper that touches them is invoked. validateChannelsJson
and applyMode now read closed-over names that are guaranteed to be
initialised.
3. Guard applyMode(initialMode) with try/catch as defense in depth and
add null-checks on every DOM reference. A future template change
that drops one of the IDs will only no-op rather than break the
editor.
No runtime change. 71/71 tests still green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:15:06 +02:00
validateChannelsJson();
}
if (modeSelect) modeSelect.addEventListener('change', (e) => applyMode(e.target.value));
if (channelsArea) channelsArea.addEventListener('input', validateChannelsJson);
try { applyMode(initialMode); } catch (e) {
console.error('measurement: applyMode failed', e);
}
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
// === Smoothing method dropdown (analog only) ===
2025-06-24 10:48:40 +02:00
const smoothMethodSelect = document.getElementById('node-input-smooth_method');
const options = window.EVOLV?.nodes?.measurement?.config?.smoothing?.smoothMethod?.rules?.values || [];
smoothMethodSelect.innerHTML = '';
const emptyOption = document.createElement('option');
emptyOption.value = '';
emptyOption.textContent = 'Select method...';
smoothMethodSelect.appendChild(emptyOption);
options.forEach(option => {
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
const optionElement = document.createElement('option');
optionElement.value = option.value;
optionElement.textContent = option.value;
optionElement.title = option.description;
smoothMethodSelect.appendChild(optionElement);
2025-06-24 10:48:40 +02:00
});
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
if (node.smooth_method) smoothMethodSelect.value = node.smooth_method;
// === Scale rows toggle (analog only) ===
const chk = document.getElementById('node-input-scaling');
const rowMin = document.getElementById('row-input-i_min');
const rowMax = document.getElementById('row-input-i_max');
function toggleScalingRows() {
const show = chk.checked;
rowMin.style.display = show ? 'block' : 'none';
rowMax.style.display = show ? 'block' : 'none';
2025-06-24 10:48:40 +02:00
}
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
chk.addEventListener('change', toggleScalingRows);
toggleScalingRows();
2025-06-25 14:52:20 +02:00
2025-06-24 10:48:40 +02:00
//------------------- END OF CUSTOM config UI ELEMENTS ------------------- //
2025-06-20 17:14:22 +02:00
},
2025-05-14 10:31:50 +02:00
oneditsave: function () {
const node = this;
2025-06-24 10:48:40 +02:00
// Validate asset properties using the asset menu
if (window.EVOLV?.nodes?.measurement?.assetMenu?.saveEditor) {
success = window.EVOLV.nodes.measurement.assetMenu.saveEditor(this);
}
2025-05-14 10:31:50 +02:00
2025-06-25 10:43:15 +02:00
// Validate logger properties using the logger menu
if (window.EVOLV?.nodes?.measurement?.loggerMenu?.saveEditor) {
success = window.EVOLV.nodes.measurement.loggerMenu.saveEditor(node);
}
2025-07-01 15:24:18 +02:00
// save position field
2025-07-01 17:02:17 +02:00
if (window.EVOLV?.nodes?.measurement?.positionMenu?.saveEditor) {
2025-07-24 13:15:56 +02:00
window.EVOLV.nodes.measurement.positionMenu.saveEditor(this);
2025-07-01 15:24:18 +02:00
}
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
// Mode is the top-level switch. Always save it first; its value
// drives which other fields are meaningful.
node.mode = document.getElementById('node-input-mode').value || 'analog';
// Channels JSON (digital). We store the raw string and let the
// server-side nodeClass.js parse it so we can surface parse errors
// at deploy time instead of silently dropping bad config.
node.channels = document.getElementById('node-input-channels').value || '[]';
// Analog smoothing method.
node.smooth_method = document.getElementById('node-input-smooth_method').value || '';
2025-05-14 10:31:50 +02:00
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
// Save checkbox properties (always safe to read regardless of mode;
// these elements exist in the DOM even when their section is hidden).
2025-06-24 10:48:40 +02:00
["scaling", "simulator"].forEach(
2025-05-14 10:31:50 +02:00
(field) => (node[field] = document.getElementById(`node-input-${field}`).checked)
);
["i_min", "i_max", "i_offset", "o_min", "o_max", "count"].forEach(
(field) => (node[field] = parseFloat(document.getElementById(`node-input-${field}`).value) || 0)
);
2026-05-11 17:29:15 +02:00
// Calibration stability threshold: 0 is a valid (very strict) value, so
// fall back to the default 0.01 only when the field is empty / NaN.
const stRaw = document.getElementById('node-input-stabilityThreshold').value;
const stParsed = parseFloat(stRaw);
node.stabilityThreshold = Number.isFinite(stParsed) ? stParsed : 0.01;
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
// Mode-dependent validation. In digital mode we don't care about
// scaling completeness (the channels have their own per-channel
// scaling); in analog mode we still warn about half-filled ranges.
if (node.mode === 'analog' & & node.scaling & & (isNaN(node.i_min) || isNaN(node.i_max))) {
2025-05-14 10:31:50 +02:00
RED.notify("Scaling enabled, but input range is incomplete!", "error");
}
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
if (node.mode === 'digital') {
try {
const parsed = JSON.parse(node.channels || '[]');
if (!Array.isArray(parsed) || parsed.length === 0) {
RED.notify("Digital mode: no channels defined. The node will emit nothing.", "warning");
}
} catch (e) {
RED.notify("Digital mode: Channels JSON is invalid (" + e.message + ")", "error");
}
}
2025-05-14 10:31:50 +02:00
},
});
< / script >
<!-- Main UI -->
< script type = "text/html" data-template-name = "measurement" >
2025-06-25 10:43:15 +02:00
feat: digital (MQTT) mode + fix silent dispatcher bug for camelCase methods
Runtime:
- Fix silent no-op when user selected any camelCase smoothing or outlier
method from the editor. validateEnum in generalFunctions lowercases enum
values (zScore -> zscore, lowPass -> lowpass, ...) but the dispatcher
compared against camelCase keys. Effect: 5 of 11 smoothing methods
(lowPass, highPass, weightedMovingAverage, bandPass, savitzkyGolay) and
2 of 3 outlier methods (zScore, modifiedZScore) silently fell through.
Users got the raw last value or no outlier filtering with no error log.
Review any pre-2026-04-13 flows that relied on these methods.
Fix: normalize method names to lowercase on both sides of the lookup.
- New Channel class (src/channel.js) — self-contained per-channel pipeline:
outlier -> offset -> scaling -> smoothing -> min/max -> constrain -> emit.
Pure domain logic, no Node-RED deps, reusable by future nodes that need
the same signal-conditioning chain.
Digital mode:
- config.mode.current = 'digital' opts in. config.channels declares one
entry per expected JSON key; each channel has its own type, position,
unit, distance, and optional scaling/smoothing/outlierDetection blocks
that override the top-level analog-mode fields. One MQTT-shaped payload
({t:22.5, h:45, p:1013}) dispatches N independent pipelines and emits N
MeasurementContainer slots from a single input message.
- Backward compatible: absent mode config = analog = pre-digital behaviour.
Every existing measurement flow keeps working unchanged.
UI:
- HTML editor: new Mode dropdown and Channels JSON textarea. The Node-RED
help panel is rewritten end-to-end with topic reference, port contracts,
per-mode configuration, smoothing/outlier method tables, and a note
about the pre-fix behaviour.
- README.md rewritten (was a one-line stub).
Tests (12 -> 71, all green):
- test/basic/smoothing-methods.basic.test.js (+16): every smoothing method
including the formerly-broken camelCase ones.
- test/basic/outlier-detection.basic.test.js (+10): every outlier method,
fall-through, toggle.
- test/basic/scaling-and-interpolation.basic.test.js (+10): offset,
interpolateLinear, constrain, handleScaling edge cases, min/max
tracking, updateOutputPercent fallback, updateOutputAbs emit dedup.
- test/basic/calibration-and-stability.basic.test.js (+11): calibrate
(stable and unstable), isStable, evaluateRepeatability refusals,
toggleSimulation, tick simulation on/off.
- test/integration/digital-mode.integration.test.js (+12): channel build
(including malformed entries), payload dispatch, multi-channel emit,
unknown keys, per-channel scaling/smoothing/outlier, empty channels,
non-numeric value rejection, getDigitalOutput shape, analog-default
back-compat.
E2E verified on Dockerized Node-RED: analog regression unchanged; digital
mode deploys with three channels, dispatches MQTT-style payload, emits
per-channel events, accumulates per-channel smoothing, ignores unknown
keys.
Depends on generalFunctions commit e50be2e (permissive unit check +
mode/channels schema).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 13:43:03 +02:00
<!-- Input mode -->
< div class = "form-row" >
< label for = "node-input-mode" > < i class = "fa fa-exchange" > < / i > Input Mode< / label >
< select id = "node-input-mode" style = "width:60%;" >
< option value = "analog" > analog — one scalar per msg.payload (classic PLC)< / option >
< option value = "digital" > digital — object payload with many channel keys (MQTT/IoT)< / option >
< / select >
< / div >
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
< div class = "form-row" id = "mode-hint" style = "margin-left:105px; font-size:12px; color:#666;" > < / div >
<!-- ===================== DIGITAL MODE FIELDS ===================== -->
< div id = "digital-only-fields" >
< div class = "form-row" id = "row-input-channels" >
< label for = "node-input-channels" > < i class = "fa fa-list" > < / i > Channels (JSON)< / label >
< textarea id = "node-input-channels" rows = "6" style = "width:60%; font-family:monospace;" placeholder = '[{"key":"temperature","type":"temperature","position":"atEquipment","unit":"C","scaling":{"enabled":false,"inputMin":0,"inputMax":1,"absMin":-50,"absMax":150,"offset":0},"smoothing":{"smoothWindow":5,"smoothMethod":"mean"}}]' > < / textarea >
< div class = "form-tips" > One entry per payload key. Each channel has its own type / position / unit / scaling / smoothing / outlier detection. See README for the full schema.< / div >
< / div >
< div class = "form-row" id = "channels-validation" style = "margin-left:105px; font-size:12px;" > < / div >
feat: digital (MQTT) mode + fix silent dispatcher bug for camelCase methods
Runtime:
- Fix silent no-op when user selected any camelCase smoothing or outlier
method from the editor. validateEnum in generalFunctions lowercases enum
values (zScore -> zscore, lowPass -> lowpass, ...) but the dispatcher
compared against camelCase keys. Effect: 5 of 11 smoothing methods
(lowPass, highPass, weightedMovingAverage, bandPass, savitzkyGolay) and
2 of 3 outlier methods (zScore, modifiedZScore) silently fell through.
Users got the raw last value or no outlier filtering with no error log.
Review any pre-2026-04-13 flows that relied on these methods.
Fix: normalize method names to lowercase on both sides of the lookup.
- New Channel class (src/channel.js) — self-contained per-channel pipeline:
outlier -> offset -> scaling -> smoothing -> min/max -> constrain -> emit.
Pure domain logic, no Node-RED deps, reusable by future nodes that need
the same signal-conditioning chain.
Digital mode:
- config.mode.current = 'digital' opts in. config.channels declares one
entry per expected JSON key; each channel has its own type, position,
unit, distance, and optional scaling/smoothing/outlierDetection blocks
that override the top-level analog-mode fields. One MQTT-shaped payload
({t:22.5, h:45, p:1013}) dispatches N independent pipelines and emits N
MeasurementContainer slots from a single input message.
- Backward compatible: absent mode config = analog = pre-digital behaviour.
Every existing measurement flow keeps working unchanged.
UI:
- HTML editor: new Mode dropdown and Channels JSON textarea. The Node-RED
help panel is rewritten end-to-end with topic reference, port contracts,
per-mode configuration, smoothing/outlier method tables, and a note
about the pre-fix behaviour.
- README.md rewritten (was a one-line stub).
Tests (12 -> 71, all green):
- test/basic/smoothing-methods.basic.test.js (+16): every smoothing method
including the formerly-broken camelCase ones.
- test/basic/outlier-detection.basic.test.js (+10): every outlier method,
fall-through, toggle.
- test/basic/scaling-and-interpolation.basic.test.js (+10): offset,
interpolateLinear, constrain, handleScaling edge cases, min/max
tracking, updateOutputPercent fallback, updateOutputAbs emit dedup.
- test/basic/calibration-and-stability.basic.test.js (+11): calibrate
(stable and unstable), isStable, evaluateRepeatability refusals,
toggleSimulation, tick simulation on/off.
- test/integration/digital-mode.integration.test.js (+12): channel build
(including malformed entries), payload dispatch, multi-channel emit,
unknown keys, per-channel scaling/smoothing/outlier, empty channels,
non-numeric value rejection, getDigitalOutput shape, analog-default
back-compat.
E2E verified on Dockerized Node-RED: analog regression unchanged; digital
mode deploys with three channels, dispatches MQTT-style payload, emits
per-channel events, accumulates per-channel smoothing, ignores unknown
keys.
Depends on generalFunctions commit e50be2e (permissive unit check +
mode/channels schema).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 13:43:03 +02:00
< / div >
fix(editor): make Input Mode the top-level switch, hide wrong-mode fields
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:00:34 +02:00
<!-- ===================== ANALOG MODE FIELDS ===================== -->
< div id = "analog-only-fields" >
< hr >
<!-- Scaling Checkbox -->
< div class = "form-row" >
< label for = "node-input-scaling"
>< i class = "fa fa-compress" > < / i > Scaling< / label >
< input type = "checkbox" id = "node-input-scaling" style = "width:20px; vertical-align:baseline;" / >
< span > Enable input scaling?< / span >
< / div >
<!-- Source Min/Max (only if scaling is true) -->
< div class = "form-row" id = "row-input-i_min" >
< label for = "node-input-i_min" > < i class = "fa fa-arrow-down" > < / i > Source Min< / label >
< input type = "number" id = "node-input-i_min" placeholder = "0" / >
< / div >
< div class = "form-row" id = "row-input-i_max" >
< label for = "node-input-i_max" > < i class = "fa fa-arrow-up" > < / i > Source Max< / label >
< input type = "number" id = "node-input-i_max" placeholder = "3000" / >
< / div >
<!-- Offset -->
< div class = "form-row" >
< label for = "node-input-i_offset" > < i class = "fa fa-adjust" > < / i > Input Offset< / label >
< input type = "number" id = "node-input-i_offset" placeholder = "0" / >
< / div >
<!-- Output / Process Min/Max -->
< div class = "form-row" >
< label for = "node-input-o_min" > < i class = "fa fa-tag" > < / i > Process Min< / label >
< input type = "number" id = "node-input-o_min" placeholder = "0" / >
< / div >
< div class = "form-row" >
< label for = "node-input-o_max" > < i class = "fa fa-tag" > < / i > Process Max< / label >
< input type = "number" id = "node-input-o_max" placeholder = "1" / >
< / div >
<!-- Simulator Checkbox -->
< div class = "form-row" >
< label for = "node-input-simulator" > < i class = "fa fa-cog" > < / i > Simulator< / label >
< input type = "checkbox" id = "node-input-simulator" style = "width:20px; vertical-align:baseline;" / >
< span > Activate internal simulation?< / span >
< / div >
<!-- Smoothing Method -->
< div class = "form-row" >
< label for = "node-input-smooth_method" > < i class = "fa fa-line-chart" > < / i > Smoothing< / label >
< select id = "node-input-smooth_method" style = "width:60%;" >
< / select >
< / div >
<!-- Smoothing Window -->
< div class = "form-row" >
< label for = "node-input-count" > Window< / label >
< input type = "number" id = "node-input-count" placeholder = "10" style = "width:60px;" / >
< div class = "form-tips" > Number of samples for smoothing< / div >
< / div >
2026-05-11 17:29:15 +02:00
<!-- Calibration Stability Threshold -->
< div class = "form-row" >
< label for = "node-input-stabilityThreshold" > < i class = "fa fa-balance-scale" > < / i > Stability Threshold< / label >
< input type = "number" id = "node-input-stabilityThreshold" placeholder = "0.01" step = "any" style = "width:100px;" / >
< span style = "margin-left:6px; color:#666;" > (scaling-units)< / span >
< div class = "form-tips" > Maximum stdDev of the rolling window for calibrate() and evaluateRepeatability() to accept the buffer as stable. Default 0.01.< / div >
< / div >
2025-05-14 10:31:50 +02:00
< / div >
2026-03-12 16:39:25 +01:00
< hr >
< h3 > Output Formats< / h3 >
< div class = "form-row" >
< label for = "node-input-processOutputFormat" > < i class = "fa fa-random" > < / i > Process Output< / label >
< select id = "node-input-processOutputFormat" style = "width:60%;" >
< option value = "process" > process< / option >
< option value = "json" > json< / option >
< option value = "csv" > csv< / option >
< / select >
< / div >
< div class = "form-row" >
< label for = "node-input-dbaseOutputFormat" > < i class = "fa fa-database" > < / i > Database Output< / label >
< select id = "node-input-dbaseOutputFormat" style = "width:60%;" >
< option value = "influxdb" > influxdb< / option >
< option value = "json" > json< / option >
< option value = "csv" > csv< / option >
< / select >
< / div >
2025-06-20 17:14:22 +02:00
<!-- Optional Extended Fields: supplier, cat, type, model, unit -->
2025-06-25 10:43:15 +02:00
<!-- Asset fields will be injected here -->
< div id = "asset-fields-placeholder" > < / div >
2025-05-14 10:31:50 +02:00
<!-- loglevel checkbox -->
2025-06-25 10:43:15 +02:00
< div id = "logger-fields-placeholder" > < / div >
2025-05-14 10:31:50 +02:00
2025-06-25 10:43:15 +02:00
<!-- Position fields will be injected here -->
< div id = "position-fields-placeholder" > < / div >
2025-05-14 10:31:50 +02:00
< / script >
< script type = "text/html" data-help-name = "measurement" >
feat: digital (MQTT) mode + fix silent dispatcher bug for camelCase methods
Runtime:
- Fix silent no-op when user selected any camelCase smoothing or outlier
method from the editor. validateEnum in generalFunctions lowercases enum
values (zScore -> zscore, lowPass -> lowpass, ...) but the dispatcher
compared against camelCase keys. Effect: 5 of 11 smoothing methods
(lowPass, highPass, weightedMovingAverage, bandPass, savitzkyGolay) and
2 of 3 outlier methods (zScore, modifiedZScore) silently fell through.
Users got the raw last value or no outlier filtering with no error log.
Review any pre-2026-04-13 flows that relied on these methods.
Fix: normalize method names to lowercase on both sides of the lookup.
- New Channel class (src/channel.js) — self-contained per-channel pipeline:
outlier -> offset -> scaling -> smoothing -> min/max -> constrain -> emit.
Pure domain logic, no Node-RED deps, reusable by future nodes that need
the same signal-conditioning chain.
Digital mode:
- config.mode.current = 'digital' opts in. config.channels declares one
entry per expected JSON key; each channel has its own type, position,
unit, distance, and optional scaling/smoothing/outlierDetection blocks
that override the top-level analog-mode fields. One MQTT-shaped payload
({t:22.5, h:45, p:1013}) dispatches N independent pipelines and emits N
MeasurementContainer slots from a single input message.
- Backward compatible: absent mode config = analog = pre-digital behaviour.
Every existing measurement flow keeps working unchanged.
UI:
- HTML editor: new Mode dropdown and Channels JSON textarea. The Node-RED
help panel is rewritten end-to-end with topic reference, port contracts,
per-mode configuration, smoothing/outlier method tables, and a note
about the pre-fix behaviour.
- README.md rewritten (was a one-line stub).
Tests (12 -> 71, all green):
- test/basic/smoothing-methods.basic.test.js (+16): every smoothing method
including the formerly-broken camelCase ones.
- test/basic/outlier-detection.basic.test.js (+10): every outlier method,
fall-through, toggle.
- test/basic/scaling-and-interpolation.basic.test.js (+10): offset,
interpolateLinear, constrain, handleScaling edge cases, min/max
tracking, updateOutputPercent fallback, updateOutputAbs emit dedup.
- test/basic/calibration-and-stability.basic.test.js (+11): calibrate
(stable and unstable), isStable, evaluateRepeatability refusals,
toggleSimulation, tick simulation on/off.
- test/integration/digital-mode.integration.test.js (+12): channel build
(including malformed entries), payload dispatch, multi-channel emit,
unknown keys, per-channel scaling/smoothing/outlier, empty channels,
non-numeric value rejection, getDigitalOutput shape, analog-default
back-compat.
E2E verified on Dockerized Node-RED: analog regression unchanged; digital
mode deploys with three channels, dispatches MQTT-style payload, emits
per-channel events, accumulates per-channel smoothing, ignores unknown
keys.
Depends on generalFunctions commit e50be2e (permissive unit check +
mode/channels schema).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 13:43:03 +02:00
< p > < b > Measurement< / b > : signal conditioning for a sensor or a bundle of sensors. Runs offset → scaling → smoothing → outlier filtering on each incoming value and publishes into the shared < code > MeasurementContainer< / code > .< / p >
< h3 > Input modes< / h3 >
< ul >
< li > < b > analog< / b > (default) — < code > msg.payload< / code > is a single number (PLC / 4-20 mA style). One pipeline, one output measurement.< / li >
< li > < b > digital< / b > — < code > msg.payload< / code > is an object with many keys (MQTT / JSON IoT). Each key maps to its own < i > channel< / i > with independent scaling, smoothing, outlier detection, type, position, unit. One message → N measurements.< / li >
< / ul >
< h3 > Topics (< code > msg.topic< / code > )< / h3 >
< ul >
< li > < code > measurement< / code > — main input. analog: number; digital: object keyed by channel names.< / li >
< li > < code > simulator< / code > — toggle the internal random-walk source.< / li >
< li > < code > outlierDetection< / code > — toggle the outlier filter.< / li >
< li > < code > calibrate< / code > — set offset so current output matches < code > Source Min< / code > (scaling on) / < code > Process Min< / code > (scaling off). Requires a stable window.< / li >
< / ul >
< h3 > Output ports< / h3 >
< ol >
< li > < b > process< / b > — delta-compressed payload. analog: < code > {mAbs, mPercent, totalMinValue, totalMaxValue, totalMinSmooth, totalMaxSmooth}< / code > . digital: < code > {channels: { key: {...} }}< / code > .< / li >
< li > < b > dbase< / b > — InfluxDB line-protocol telemetry.< / li >
< li > < b > parent< / b > — < code > registerChild< / code > handshake for the parent equipment node.< / li >
< / ol >
< h3 > Analog configuration< / h3 >
< ul >
< li > < b > Scaling< / b > : enables linear interpolation from < code > [Source Min, Source Max]< / code > to < code > [Process Min, Process Max]< / code > .< / li >
< li > < b > Input Offset< / b > : additive bias applied before scaling.< / li >
< li > < b > Smoothing< / b > : < code > none< / code > | < code > mean< / code > | < code > min< / code > | < code > max< / code > | < code > sd< / code > | < code > lowPass< / code > | < code > highPass< / code > | < code > weightedMovingAverage< / code > | < code > bandPass< / code > | < code > median< / code > | < code > kalman< / code > | < code > savitzkyGolay< / code > .< / li >
< li > < b > Window< / b > : sample count for the smoothing window.< / li >
< li > < b > Outlier detection< / b > (via < code > outlierDetection< / code > topic toggle): < code > zScore< / code > , < code > iqr< / code > , < code > modifiedZScore< / code > .< / li >
< / ul >
< h3 > Digital configuration< / h3 >
< p > Populate the < b > Channels (JSON)< / b > field with an array. Each entry:< / p >
< pre > {
"key": "temperature",
"type": "temperature",
"position": "atEquipment",
"unit": "C",
"scaling": { "enabled": false, "inputMin": 0, "inputMax": 1, "absMin": -50, "absMax": 150, "offset": 0 },
"smoothing": { "smoothWindow": 5, "smoothMethod": "mean" },
"outlierDetection": { "enabled": true, "method": "zScore", "threshold": 3 }
}< / pre >
< p > < code > scaling< / code > , < code > smoothing< / code > , < code > outlierDetection< / code > are optional — missing sections fall back to the analog-mode fields above.< / p >
< p > Unknown < code > type< / code > values (anything not in < code > pressure/flow/power/temperature/volume/length/mass/energy< / code > ) are accepted without unit compatibility checks, so user-defined channels like < code > humidity< / code > , < code > co2< / code > , < code > voc< / code > work out of the box.< / p >
2025-05-14 10:31:50 +02:00
< / script >