From 5d793142290bbb09a51bfcae6aeaf0c334ebe1d7 Mon Sep 17 00:00:00 2001 From: znetsixe Date: Thu, 21 May 2026 15:06:37 +0200 Subject: [PATCH] feat(units) + style: command unit-handling, frost dbase option, palette #D4A02E MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit measurement.html: • sidebar swatch → #D4A02E (amber, sensor family) — EVOLV palette redesign 2026-05-21 (see superproject .claude/rules/node-red-flow-layout.md §10.0). • Add "frost" option to dbaseOutputFormat dropdown (CoreSync FROST handoff). src/commands/handlers.js + test/basic/commands-units.basic.test.js: • Unit handling for data.measurement command. Analog + digital modes both accept scalar / object / per-channel-map payloads; supplied units are converted into the channel's configured (dropdown) unit. CONTRACT.md: document the unit semantics. Co-Authored-By: Claude Opus 4.7 (1M context) --- CONTRACT.md | 21 +- measurement.html | 3 +- src/commands/handlers.js | 129 ++++++++-- test/basic/commands-units.basic.test.js | 323 ++++++++++++++++++++++++ 4 files changed, 449 insertions(+), 27 deletions(-) create mode 100644 test/basic/commands-units.basic.test.js diff --git a/CONTRACT.md b/CONTRACT.md index 9fd9dc3..bd62208 100644 --- a/CONTRACT.md +++ b/CONTRACT.md @@ -10,10 +10,29 @@ Hand-maintained for Phase 3; the `## Inputs` table is generated from | `set.simulator` | `simulator` | none (payload ignored) | Toggles `source.toggleSimulation()` — flips `config.simulation.enabled`. | | `set.outlier-detection` | `outlierDetection` | none (payload ignored) | Toggles `source.toggleOutlierDetection()` — flips `config.outlierDetection.enabled`. | | `cmd.calibrate` | `calibrate` | none | Calls `source.calibrate()` — captures the current input as the zero/reference offset. | -| `data.measurement` | `measurement` | mode-dependent — see below | Pushes a sensor reading into the pipeline. Analog: numeric scalar (number or numeric string) → `source.inputValue`. Digital: object payload keyed by channel name → `source.handleDigitalPayload(payload)`. Wrong shape for the configured mode logs a helpful warning suggesting the other mode. | +| `data.measurement` | `measurement` | mode-dependent — see **Payload shape** below | Pushes a sensor reading into the pipeline. Analog → `source.inputValue`; digital → `source.handleDigitalPayload()`. Wrong shape for the configured mode logs a helpful warning suggesting the other mode. | Aliases log a one-time deprecation warning the first time they fire. +### `data.measurement` payload shape + +Both modes accept the same three forms, mirroring pumpingStation's +`set.inflow` contract: + +- **Bare scalar** — `msg.payload = 12.5` (number or numeric string). The unit + falls back to `msg.unit`, and finally to the channel's configured unit + (the dropdown selection in the node editor). +- **Rich object** — `msg.payload = { value, unit?, timestamp? }`. Used per- + call to declare the unit of a single sample. +- **Digital map** (digital mode only) — `msg.payload = { : , … }`. Each entry follows the rules above independently, so different channels in one message may carry different units. + +When a supplied unit differs from the channel's configured unit, the value +is converted into the channel unit via `generalFunctions.convert` before it +enters the outlier / scaling / smoothing pipeline. If the supplied unit is +unknown or belongs to a different measure (e.g. `kg` on a `pressure` +channel), the handler logs a warning and uses the raw value treated as the +channel unit — the sample is not silently dropped. + ## Outputs (msg.topic on Port 0/1/2) - **Port 0 (process):** `msg.topic = config.general.name`. Payload built by diff --git a/measurement.html b/measurement.html index 03e44c3..c5434d3 100644 --- a/measurement.html +++ b/measurement.html @@ -14,7 +14,7 @@