From f869296832245894447b8a3aba9205c08f5a903b Mon Sep 17 00:00:00 2001 From: znetsixe Date: Tue, 14 Apr 2026 08:27:11 +0200 Subject: [PATCH] feat: level-based control now reaches machine groups + manual Qd forwarding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two additions to pumpingStation: 1. _controlLevelBased now calls _applyMachineGroupLevelControl in addition to _applyMachineLevelControl when the basin is filling above startLevel. Previously only direct-child machines received the level-based percent-control signal; in a hierarchical topology (PS → MGC → pumps) the machines sit under MGC and PS.machines is empty, so the level control never reached them. 2. New 'Qd' input topic + forwardDemandToChildren() method. When PS is in 'manual' mode (matching the pattern from rotatingMachine's virtualControl), operator demand from a dashboard slider is forwarded to all child machine groups and direct machines. When PS is in any other mode (levelbased, flowbased, etc.), the Qd msg is silently dropped with a debug log so the automatic control isn't overridden. No breaking changes — existing flows that don't send 'Qd' are unaffected, and _controlLevelBased's additional call to machineGroupLevelControl is a no-op when no machine groups are registered. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/nodeClass.js | 21 +++++++++++++++++++++ src/specificClass.js | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/nodeClass.js b/src/nodeClass.js index 6e02b28..072a596 100644 --- a/src/nodeClass.js +++ b/src/nodeClass.js @@ -221,6 +221,27 @@ class nodeClass { this.source.setManualInflow(val, ts, unit); break; } + case 'Qd': { + // Manual demand: operator sets the target output via a + // dashboard slider. Only accepted when PS is in 'manual' + // mode — mirrors how rotatingMachine gates commands by + // mode (virtualControl vs auto). + const demand = Number(msg.payload); + if (!Number.isFinite(demand)) { + this.source.logger.warn(`Invalid Qd value: ${msg.payload}`); + break; + } + if (this.source.mode === 'manual') { + this.source.forwardDemandToChildren(demand).catch((err) => + this.source.logger.error(`Failed to forward demand: ${err.message}`) + ); + } else { + this.source.logger.debug( + `Qd ignored in ${this.source.mode} mode. Switch to manual to use the demand slider.` + ); + } + break; + } } done(); }); diff --git a/src/specificClass.js b/src/specificClass.js index f5d6076..ca96884 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -254,6 +254,11 @@ class PumpingStation { const percControl = this._scaleLevelToFlowPercent(level); this.logger.debug(`Controllevel based => Level ${level} control applying to pump : ${percControl}`); await this._applyMachineLevelControl(percControl); + // Also forward to machine groups (e.g. MGC) — the level-based + // control originally only addressed direct-child machines, but in + // a hierarchical topology (PS → MGC → pumps) the machines sit + // under MGC and PS.machines is empty. + await this._applyMachineGroupLevelControl(percControl); } if (level < stopLevel && direction === 'draining') { @@ -272,6 +277,38 @@ class PumpingStation { // placeholder for flow-based logic } + /** + * Forward a manual demand value to all child machine groups + direct + * machines. Called from the 'Qd' topic handler when PS is in manual + * mode — mirrors how rotatingMachine gates commands by mode. + * @param {number} demand - the operator-set demand (interpretation + * depends on MGC scaling: 'absolute' = m³/h, 'normalized' = 0-100%) + */ + async forwardDemandToChildren(demand) { + this.logger.info(`Manual demand forwarded: ${demand}`); + // Forward to machine groups (MGC) + if (this.machineGroups && Object.keys(this.machineGroups).length > 0) { + await Promise.all( + Object.values(this.machineGroups).map((group) => + group.handleInput('parent', demand).catch((err) => { + this.logger.error(`Failed to forward demand to group: ${err.message}`); + }) + ) + ); + } + // Forward to direct machines (if any) + if (this.machines && Object.keys(this.machines).length > 0) { + const perMachine = demand / Object.keys(this.machines).length; + for (const machine of Object.values(this.machines)) { + try { + await machine.handleInput('parent', 'execMovement', perMachine); + } catch (err) { + this.logger.error(`Failed to forward demand to machine: ${err.message}`); + } + } + } + } + async _applyMachineGroupLevelControl(percentControl) { if (!this.machineGroups || Object.keys(this.machineGroups).length === 0) return; await Promise.all(