--- title: "Session: rotatingMachine trial-ready — FSM interruptibility, config schema, UX fixes" created: 2026-04-13 updated: 2026-04-13 status: proven tags: [session, rotatingMachine, state-machine, docker, e2e] --- # 2026-04-13 — rotatingMachine trial-ready ## Scope Honest review + production-hardening pass on `rotatingMachine`. Fixes landed on top of the 2026-04-07 hardening and are verified against a Docker-hosted Node-RED stack. ## Findings (before fixes) From a live E2E run captured via the Node-RED debug websocket (`/comms`): - **Clean startup→operational→shutdown→idle path** works to spec: 3 s starting + 2 s warmup + 3 s stopping + 2 s cooldown, matching config exactly. - **Tick cadence:** 1000 ms (min 1000, max 1005, avg 1002.5). - **Predictions** gate correctly on pressure injection; at 900 mbar Δp the hidrostal-H05K-S03R curve yields a monotonic flow/power response. - **State machine FSM** *rejects* `stopping`/`coolingdown`/`idle` transitions while the machine is in `accelerating`/`decelerating`, leaving a shutdown command silently dropped. Log symptom: `Invalid transition from accelerating to stopping. Transition not executed.` - **Sequence `emergencyStop` not defined** warn appears when a parent orchestrator with the capital-S casing (e.g. `machineGroupControl` config) forwards the sequence name. - **Config validator strips** `functionality.distance` and top-level `output` that `buildConfig` adds; every deploy prints removal warnings. - Cosmetic: typo "acurate" in single-side pressure warn; editor lacks unit hints for `speed` / `startup` / etc. ## Fixes ### 1. Interruptible movement (`generalFunctions/src/state/state.js`) `moveTo`'s `catch` block now detects `Movement aborted` / `Transition aborted` errors and transitions the FSM back to `operational`, unblocking subsequent sequence transitions. A new `movementAborted` event is emitted for observability. ### 2. Auto-abort on shutdown/emergency-stop (`rotatingMachine/src/specificClass.js`) `executeSequence` now: - Normalizes the sequence name to lowercase (defensive against parent callers using mixed case). - When `shutdown` or `emergencystop` is requested from `accelerating`/`decelerating`, calls `state.abortCurrentMovement(...)` and waits up to 2 s for the FSM to return to `operational` via the new `_waitForOperational(timeoutMs)` helper that listens on the state emitter. ### 3. Config schema sync (`generalFunctions/src/configs/rotatingMachine.json`) Added to the schema: - `functionality.distance`, `.distanceUnit`, `.distanceDescription` (produced by the HTML editor). - Top-level `output.process` / `output.dbase` (produced by `buildConfig`). Also reverted an overly broad `buildConfig` addition to only emit `distance` (not `distanceUnit`/`distanceDescription`) so other nodes aren't forced to add these to their schemas. ### 4. UX polish - Fixed typo "acurate" → "accurate" in the single-side pressure warning, plus made the message actionable. - Added unit hints to Reaction Speed / Startup / Warmup / Shutdown / Cooldown fields in the editor. - Expanded the Node-RED help panel with a topic reference, state diagram, prediction rules, and port documentation. ## Tests added `test/integration/interruptible-movement.integration.test.js` — three regression tests for the FSM fix: - `shutdown during accelerating aborts the move and reaches idle` - `emergency stop during accelerating reaches off` - `executeSequence accepts mixed-case sequence names` `test/integration/curve-prediction.integration.test.js` — 12 parametrized tests across both shipped pump curves (`hidrostal-H05K-S03R` and `hidrostal-C5-D03R-SHN1`): - Curve loader returns nq + np with matching pressure slices. - Predicted flow and power at mid-pressure / mid-ctrl are finite and inside the curve envelope. - Flow is monotonically non-decreasing across a ctrl sweep at fixed pressure. - Flow decreases (or stays level) when pressure rises at fixed ctrl — centrifugal-pump physics. - CoG / NCog are computed, finite, and inside [0, 100] controller units. - Reverse predictor (flow → ctrl via reversed nq) round-trips within 10 % of the known controller position. `test/e2e/curve-prediction-benchmark.py` + `test/e2e/README.md` — live Dockerized Node-RED benchmark that deploys one rotatingMachine per curve and records a (pressure × ctrl) sweep. Full unit suite: **91/91 passing** (was 76/76 on the morning review). ## E2E verification (Dockerized Node-RED) Via `/tmp/rm_e2e_verify.py` — deploys the example flow to `docker compose`-hosted Node-RED, drives it via `POST /inject/:id`, captures port-output via `ws://localhost:1880/comms`. | Scenario | Observed state sequence | Pass? | |---|---|---| | Shutdown fired while `accelerating` | starting → warmingup → operational → accelerating → decelerating → stopping → coolingdown → **idle** | ✅ | | Emergency stop fired while `accelerating` | starting → warmingup → operational → accelerating → **off** | ✅ | | Clean startup → shutdown (regression) | starting → warmingup → operational → stopping → coolingdown → idle | ✅ | Container log scan over a 3-minute window: - `Unknown key` warns: 0 (was 6+ per deploy) - `acurate` typo: 0 (was 2) - `Invalid transition from accelerating/decelerating to ...` errors: 0 (was 3+) - `Sequence '...' not defined`: 0 (was 1) ### Dual-curve prediction sweep Via `nodes/rotatingMachine/test/e2e/curve-prediction-benchmark.py`. Deploys two live rotatingMachines, one per pump curve, and runs a (pressure × ctrl) sweep per pump. Each pump is tested only inside its own curve envelope. | Pump | Pressures swept (mbar) | Ctrl setpoints (%) | Samples in envelope | Flow monotonic | Flow observed (m³/h) | Power observed (kW) | |---|---|---|---|---|---|---| | hidrostal-H05K-S03R | 700 / 2300 / 3900 | 20 / 40 / 60 / 80 | 12/12 ✅ | ✅ | 10.3 – 208.3 | 12.3 – 50.3 | | hidrostal-C5-D03R-SHN1 | 400 / 1700 / 2900 | 20 / 40 / 60 / 80 | 12/12 ✅ | ✅ | 8.7 – 45.6 | 0.7 – 13.0 | Inverse-pressure monotonicity (centrifugal-pump physics) also verified: for both pumps, flow at the highest pressure slice is strictly lower than flow at the lowest pressure slice for the same ctrl. **Known limitation** captured in the memory file: extrapolating pressure *below* the curve's minimum slice produces nonsensical flow values (e.g. H05K at 400 mbar ctrl=20% predicts ~30 000 m³/h vs envelope max 227 m³/h). Upstream `measurement` nodes are expected to clamp sensors to realistic ranges; rotatingMachine itself does not. Separately, the C5 curve still exhibits the previously-documented power non-monotonicity at p=1700 mbar (sparse-data spline artefact noted in the 2026-04-07 session); this is compensated by the group-optimization marginal-cost refinement loop. ## Files changed ``` nodes/generalFunctions/src/state/state.js # abort recovery nodes/generalFunctions/src/configs/index.js # buildConfig trim nodes/generalFunctions/src/configs/rotatingMachine.json # schema sync nodes/rotatingMachine/src/specificClass.js # exec + typo nodes/rotatingMachine/rotatingMachine.html # UX hints + help nodes/rotatingMachine/test/integration/interruptible-movement.integration.test.js # +3 tests (FSM) nodes/rotatingMachine/test/integration/curve-prediction.integration.test.js # +12 tests (dual curve) nodes/rotatingMachine/test/e2e/curve-prediction-benchmark.py # new E2E benchmark nodes/rotatingMachine/test/e2e/README.md # benchmark docs nodes/rotatingMachine/README.md # rewrite ``` ## Production readiness Status: **trial-ready**. The caveats flagged in the 2026-04-13 memory file (`node_rotatingMachine.md`) are resolved. Remaining items are in the wishlist (interruptible curve validation feedback, domain review of ctrl≈0% + backpressure flow prediction, opt-in full-snapshot port-0 mode, per-machine `/health` endpoint). ## Verification command ```bash cd /mnt/d/gitea/EVOLV docker compose up -d nodered influxdb cd nodes/rotatingMachine && npm test python3 /tmp/rm_e2e_verify.py # end-to-end smoke ```