Compare commits

..

3 Commits

Author SHA1 Message Date
znetsixe
b40b2c736f 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:15 +02:00
znetsixe
74951e7a23 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:53 +02:00
znetsixe
167b1026f1 docs(CONTRACT): document valve's lack of a maintenance state machine
valve's schema mode enum includes `maintenance` (which gates sources)
but the FSM has no `entermaintenance` / `exitmaintenance` states and
the schema's `sequences` block has only startup / shutdown /
emergencystop / boot. Maintenance mode therefore disables external
sources but doesn't run a maintenance sequence — different shape
from rotatingMachine. Added a Limitations section to the CONTRACT so
this is explicit rather than surfacing as a wiki TODO.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 16:35:28 +02:00
3 changed files with 28 additions and 5 deletions

View File

@@ -68,6 +68,18 @@ from the measurement container (e.g. `delta_predicted_pressure`,
(`idle → starting → warmingup → operational → accelerating → (`idle → starting → warmingup → operational → accelerating →
decelerating → stopping → coolingdown → idle`, plus `off`). decelerating → stopping → coolingdown → idle`, plus `off`).
## Limitations vs `rotatingMachine`
- **No `maintenance` state machine.** The schema's `mode.current` enum
accepts `maintenance` (gates sources via `isValidSourceForMode`), but
the FSM has no `entermaintenance` / `exitmaintenance` states and the
`sequences` schema declares only `startup`, `shutdown`, `emergencystop`,
and `boot`. Configuring `maintenance` mode therefore disables external
command sources but does not put the valve through a maintenance
sequence. Aligns with valve's role as a passive flow-controlled
actuator; lift to RM-style FSM if/when site maintenance procedures
require explicit state transitions.
## Events emitted by `source.measurements.emitter` ## Events emitted by `source.measurements.emitter`
The `MeasurementContainer` fires `<type>.<variant>.<position>` whenever The `MeasurementContainer` fires `<type>.<variant>.<position>` whenever

View File

@@ -7,23 +7,33 @@
function _logger(source, ctx) { return ctx?.logger || source?.logger || null; } function _logger(source, ctx) { return ctx?.logger || source?.logger || null; }
function _send(ctx, ports) { if (typeof ctx?.send === 'function') ctx.send(ports); } function _send(ctx, ports) { if (typeof ctx?.send === 'function') ctx.send(ports); }
// 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);
}; };
exports.startup = async (source, msg) => { exports.startup = async (source, msg) => {
const p = msg.payload || {}; const p = msg.payload || {};
await source.handleInput(p.source ?? 'parent', 'execSequence', 'startup'); await source.handleInput(_origin(msg), 'execSequence', 'startup');
}; };
exports.shutdown = async (source, msg) => { exports.shutdown = async (source, msg) => {
const p = msg.payload || {}; const p = msg.payload || {};
await source.handleInput(p.source ?? 'parent', 'execSequence', 'shutdown'); await source.handleInput(_origin(msg), 'execSequence', 'shutdown');
}; };
exports.estop = async (source, msg) => { exports.estop = async (source, msg) => {
const p = msg.payload || {}; const p = msg.payload || {};
await source.handleInput(p.source ?? 'parent', p.action ?? 'emergencystop'); await source.handleInput(_origin(msg), p.action ?? 'emergencystop');
}; };
// Legacy umbrella: payload.action selects the canonical verb. // Legacy umbrella: payload.action selects the canonical verb.
@@ -41,7 +51,7 @@ exports.execSequenceAlias = async (source, msg, ctx) => {
exports.setPosition = async (source, msg) => { exports.setPosition = async (source, msg) => {
const p = msg.payload || {}; const p = msg.payload || {};
const action = p.action ?? 'execMovement'; const action = p.action ?? 'execMovement';
await source.handleInput(p.source ?? 'parent', action, Number(p.setpoint)); await source.handleInput(_origin(msg), action, Number(p.setpoint));
}; };
exports.dataFlow = (source, msg) => { exports.dataFlow = (source, msg) => {

View File

@@ -14,7 +14,7 @@
<script> <script>
RED.nodes.registerType("valve", { RED.nodes.registerType("valve", {
category: "EVOLV", category: "EVOLV",
color: "#86bbdd", // color for the node based on the S88 schema color: "#3CAEA3",
defaults: { defaults: {
// Define default properties // Define default properties
@@ -131,6 +131,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>