'use strict'; // Handler functions for pumpingStation commands. Each handler receives: // source: the domain (specificClass) instance — has the public methods // (changeMode, calibratePredicted*, setManualInflow, ...). // msg: the Node-RED input message. // ctx: { node, RED, send, logger } — provided by BaseNodeAdapter. // // Handlers are pure functions: they don't keep state. Validation that goes // beyond the registry's typeof-check ladder lives here. function _logger(source, ctx) { return ctx?.logger || source?.logger || null; } exports.setMode = (source, msg) => { source.changeMode(msg.payload); }; exports.registerChild = (source, msg, ctx) => { const log = _logger(source, ctx); const childId = msg.payload; const childObj = ctx?.RED?.nodes?.getNode?.(childId); if (!childObj || !childObj.source) { log?.warn?.(`registerChild: child '${childId}' not found or has no .source`); return; } source.childRegistrationUtils.registerChild(childObj.source, msg.positionVsParent); }; exports.calibrateVolume = (source, msg, ctx) => { const log = _logger(source, ctx); const v = parseFloat(msg.payload); if (!Number.isFinite(v)) { log?.warn?.(`cmd.calibrate.volume: non-numeric payload '${msg.payload}'`); return; } source.calibratePredictedVolume(v); }; exports.calibrateLevel = (source, msg, ctx) => { const log = _logger(source, ctx); const v = parseFloat(msg.payload); if (!Number.isFinite(v)) { log?.warn?.(`cmd.calibrate.level: non-numeric payload '${msg.payload}'`); return; } source.calibratePredictedLevel(v); }; exports.setInflow = (source, msg) => { // Payload is either a number (legacy q_in shape) or // { value, unit, timestamp } (richer object form). const p = msg.payload; let value; let unit; let timestamp; if (p !== null && typeof p === 'object') { value = Number(p.value); unit = p.unit; timestamp = p.timestamp || Date.now(); } else { value = Number(p); unit = msg?.unit; timestamp = msg?.timestamp || Date.now(); } source.setManualInflow(value, timestamp, unit); }; exports.setOutflow = (source, msg) => { // Manual q_out — basin-docs dashboard injects a drain rate without // wiring a real pump. Same payload shape as q_in. const p = msg.payload; let value; let unit; let timestamp; if (p !== null && typeof p === 'object') { value = Number(p.value); unit = p.unit; timestamp = p.timestamp || Date.now(); } else { value = Number(p); unit = msg?.unit; timestamp = msg?.timestamp || Date.now(); } source.setManualOutflow(value, timestamp, unit); }; exports.setDemand = (source, msg, ctx) => { const log = _logger(source, ctx); // generalFunctions/commandRegistry's _normaliseUnits has already converted // msg.payload to m3/h (the descriptor's units.default — see // commands/index.js). Accepts {value, unit} objects upstream; we just read // the normalized number here. _manualDemand is stored in m3/h, no further // conversion needed. const demand = Number(msg?.payload); if (!Number.isFinite(demand)) { log?.warn?.(`set.demand: invalid Qd value '${JSON.stringify(msg?.payload)}'`); return; } if (source.mode !== 'manual') { log?.debug?.( `set.demand ignored in '${source.mode}' mode; switch to manual to use the demand slider` ); return; } // forwardDemandToChildren returns a promise — surface failures via logger. Promise.resolve(source.forwardDemandToChildren(demand)).catch((err) => { log?.error?.(`set.demand: failed to forward demand: ${err && err.message}`); }); };