Compare commits

..

4 Commits

Author SHA1 Message Date
znetsixe
c96ad94c40 feat(commands): adopt unified command envelope — msg.origin provenance
Resolve command origin via msg.origin (registry-stamped, default parent) with a
legacy fallback to the previous payload.source/msg.source field. Feeds the
existing mode/allowedSources arbitration unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 18:41:16 +02:00
znetsixe
bd67b22197 style: palette swatch → (domain-hue redesign 2026-05-21)
Sidebar swatch now follows function family rather than S88 level, so the
palette is visually identifiable instead of monochromatically blue. Editor-group
rectangles in flow.json still follow S88 — only the registerType color changed.
Full table + rationale: superproject .claude/rules/node-red-flow-layout.md §10.0
and .claude/refactor/OPEN_QUESTIONS.md (2026-05-21 entry).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:05:55 +02:00
znetsixe
91f98414d1 fix(commands): point set.mode description at the schema enum
Old description said "auto / manual" but the schema declares four modes
(auto, virtualControl, fysicalControl, maintenance). New description
enumerates the allowed values and refers readers to the schema as the
source of truth. wiki-gen regenerated Reference-Contracts.md to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 16:05:40 +02:00
znetsixe
d81aedc9bc docs(wiki): regenerate topic-contract AUTOGEN block via wiki-gen
Replaces the agent-written placeholder inside Reference-Contracts.md with
the authoritative table generated from src/commands/index.js. Both the
BEGIN and END markers are normalized to the canonical form used by
`@evolv/wiki-gen`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 10:11:51 +02:00
4 changed files with 28 additions and 24 deletions

View File

@@ -9,6 +9,16 @@ function _logger(source, ctx) {
return ctx?.logger || source?.logger || null; return ctx?.logger || source?.logger || null;
} }
// Resolve the command origin (control authority: parent | GUI | fysical).
// The shared commandRegistry stamps msg.origin (default 'parent'); legacy flows
// carried it as payload.source. Prefer the legacy field when present so existing
// flows keep working, otherwise use the registry-stamped msg.origin.
function _origin(msg) {
const p = msg && msg.payload;
if (p && typeof p === 'object' && typeof p.source === 'string' && p.source) return p.source;
return (typeof msg?.origin === 'string' && msg.origin) ? msg.origin : 'parent';
}
exports.setMode = (source, msg) => { exports.setMode = (source, msg) => {
source.setMode(msg.payload); source.setMode(msg.payload);
}; };
@@ -31,25 +41,18 @@ exports.registerChild = (source, msg, ctx) => {
}; };
exports.execSequence = async (source, msg) => { exports.execSequence = async (source, msg) => {
const { source: seqSource, action: seqAction, parameter } = msg.payload || {}; const { action: seqAction, parameter } = msg.payload || {};
await source.handleInput(seqSource, seqAction, parameter); await source.handleInput(_origin(msg), seqAction, parameter);
}; };
exports.totalFlowChange = async (source, msg) => { exports.totalFlowChange = async (source, msg) => {
const payload = msg.payload || {}; const payload = msg.payload || {};
if (payload && typeof payload === 'object' && Object.prototype.hasOwnProperty.call(payload, 'source')) {
const src = payload.source || 'parent';
const action = payload.action || 'totalFlowChange'; const action = payload.action || 'totalFlowChange';
await source.handleInput(src, action, payload); await source.handleInput(_origin(msg), action, payload);
return;
}
await source.handleInput('parent', 'totalFlowChange', payload);
}; };
exports.emergencyStop = async (source, msg) => { exports.emergencyStop = async (source, msg) => {
const payload = msg.payload || {}; await source.handleInput(_origin(msg), 'emergencystop');
const src = payload.source || 'parent';
await source.handleInput(src, 'emergencystop');
}; };
exports.setReconcileInterval = (source, msg) => { exports.setReconcileInterval = (source, msg) => {

View File

@@ -12,7 +12,7 @@ module.exports = [
topic: 'set.mode', topic: 'set.mode',
aliases: ['setMode'], aliases: ['setMode'],
payloadSchema: { type: 'string' }, payloadSchema: { type: 'string' },
description: 'Switch the valve group between auto / manual control modes.', description: 'Switch the operating mode. Allowed: `auto`, `virtualControl`, `fysicalControl`, `maintenance` (schema-validated in `valveGroupControl.json` → `mode.current`).',
handler: handlers.setMode, handler: handlers.setMode,
}, },
{ {

View File

@@ -14,7 +14,7 @@
<script> <script>
RED.nodes.registerType('valveGroupControl',{ RED.nodes.registerType('valveGroupControl',{
category: "EVOLV", category: "EVOLV",
color: "#50a8d9", color: "#2A8A82",
defaults: { defaults: {
// Define default properties // Define default properties
name: { value: "" }, name: { value: "" },
@@ -91,6 +91,7 @@
<label for="node-input-dbaseOutputFormat"><i class="fa fa-database"></i> Database Output</label> <label for="node-input-dbaseOutputFormat"><i class="fa fa-database"></i> Database Output</label>
<select id="node-input-dbaseOutputFormat" style="width:60%;"> <select id="node-input-dbaseOutputFormat" style="width:60%;">
<option value="influxdb">influxdb</option> <option value="influxdb">influxdb</option>
<option value="frost">frost</option>
<option value="json">json</option> <option value="json">json</option>
<option value="csv">csv</option> <option value="csv">csv</option>
</select> </select>

View File

@@ -15,19 +15,19 @@
The registry lives in `src/commands/index.js`. Each descriptor maps a canonical `msg.topic` to its handler; aliases emit a one-time deprecation warning the first time they fire. The registry lives in `src/commands/index.js`. Each descriptor maps a canonical `msg.topic` to its handler; aliases emit a one-time deprecation warning the first time they fire.
<!-- BEGIN AUTOGEN: topic-contract — populate via wiki-gen tool (TODO) --> <!-- BEGIN AUTOGEN: topic-contract -->
| Canonical topic | Aliases | Payload | Unit | Effect | | Canonical topic | Aliases | Payload | Unit | Effect |
|:---|:---|:---|:---|:---| |---|---|---|---|---|
| `set.mode` | `setMode` | `string` (`auto` / `virtualControl` / `fysicalControl` / `maintenance`) | &mdash; | Switch operational mode via `source.setMode(payload)`. Each mode has its own allow-list of sources (`mode.allowedSources`). | | `set.mode` | `setMode` | `string` | — | Switch the operating mode. Allowed: `auto`, `virtualControl`, `fysicalControl`, `maintenance` (schema-validated in `valveGroupControl.json``mode.current`). |
| `set.position` | `setpoint` | any | &mdash; | **No-op pending Phase 7.** Reserved for future per-valve positional override; the handler is debug-logged only. | | `set.position` | `setpoint` | any | — | Set the group-level valve position (currently a no-op pending Phase 7). |
| `child.register` | `registerChild` | `string` (child node id) | &mdash; | Resolve via `RED.nodes.getNode`; if the child exposes `.source`, register through `childRegistrationUtils.registerChild(child.source, msg.positionVsParent)`. | | `child.register` | `registerChild` | `string` | — | Register a child valve with this group. |
| `cmd.execSequence` | `execSequence` | `{ source, action, parameter }` | &mdash; | Forward to `source.handleInput(source, action, parameter)`. The `action` typically names a sequence; the parameter typically names the state list. | | `cmd.execSequence` | `execSequence` | `object` | — | Run a group-wide sequence (startup / shutdown / emergencystop). |
| `data.totalFlow` | `totalFlowChange` | number, `{ value, position?, variant?, unit? }`, or `{ source, action, ... }` | `volumeFlowRate` (default `m3/h`) | Update total measured/predicted flow at the configured position; drives `calcValveFlows` to re-distribute. If `payload.source` is present, route via `handleInput(src, action, payload)`; otherwise treat as `parent`/`totalFlowChange`. | | `data.totalFlow` | `totalFlowChange` | any | — | Notify the group that the total flow setpoint has changed. |
| `cmd.emergencyStop` | `emergencyStop`, `emergencystop` | optional `{ source }` | &mdash; | Run the `emergencystop` sequence via `handleInput(src, 'emergencystop')`. Default source is `parent`. | | `cmd.emergencyStop` | `emergencyStop`, `emergencystop` | any | — | Trigger an emergency stop across all valves in the group. |
| `set.reconcileInterval` | `setReconcileInterval` | number &mdash; seconds (> 0) | seconds | Re-tune the periodic flow-reconciliation interval (`setReconcileIntervalSeconds`). Min clamp 100 ms. Non-finite or &le; 0 logs a warn and is dropped. | | `set.reconcileInterval` | `setReconcileInterval` | any | — | Update the reconciliation interval (seconds). |
<!-- END AUTOGEN --> <!-- END AUTOGEN: topic-contract -->
### Mode / source allow-lists ### Mode / source allow-lists