From 2c5704b5c0413f6b8a43430f1b1418de5e71c7b6 Mon Sep 17 00:00:00 2001 From: znetsixe Date: Tue, 12 May 2026 17:11:50 +0200 Subject: [PATCH] feat(diffuser): resolve supplier curves via assetResolver + wire asset menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _loadSpecs() now calls loadCurve(model) instead of returning a hardcoded literal. Default model 'gva-elastox-r' keeps the legacy GVA numbers; the editor cascade (supplier → type → model → unit) lets users pick Jäger, Aerostrip, or PIK/PRK once those curve files land in generalFunctions. Editor changes: - diffuser.js serves /diffuser/menu.js + /diffuser/configData.js - diffuser.html loads the shared MenuManager scripts, includes asset-fields-placeholder + logger-fields-placeholder, and runs the shared init/save lifecycle. - Density field re-labelled "Bottom coverage [%]" — semantics were always meant to be % surface-area coverage; "elements per m²" was a prior mis-conversion. Default flipped 2.4 → 15 (typical fine-bubble). - New defaults: model, unit, assetTagNumber. specificClass: - buildDomainConfig now forwards uiConfig.model/unit/assetTagNumber under config.asset.* so _loadSpecs can resolve it. - _loadSpecs walks config.asset.model || config.model || DEFAULT, falls through to GVA on a missing curve file (with a clear error if neither resolves to a usable otr_curve + p_curve). All 8 unit + structure tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- diffuser.html | 76 +++++++++++++++++++++++++++++++++----------- diffuser.js | 22 +++++++++++++ src/nodeClass.js | 10 +++++- src/specificClass.js | 32 ++++++++++++++++--- 4 files changed, 115 insertions(+), 25 deletions(-) diff --git a/diffuser.html b/diffuser.html index 969e73f..b3a2eea 100644 --- a/diffuser.html +++ b/diffuser.html @@ -1,17 +1,29 @@ + + + + @@ -40,8 +81,11 @@ RED.nodes.registerType('diffuser', {
- - + + +
+ Fraction of tank floor occupied by diffuser membrane (%). Used as the curve-family key. +
@@ -55,6 +99,7 @@ RED.nodes.registerType('diffuser', {
+

Output Formats

@@ -72,21 +117,14 @@ RED.nodes.registerType('diffuser', {
-
- - -
-
- - -
+ + +
+ + +
diff --git a/diffuser.js b/diffuser.js index 8674480..86c6d1e 100644 --- a/diffuser.js +++ b/diffuser.js @@ -1,9 +1,31 @@ const nameOfNode = 'diffuser'; const nodeClass = require('./src/nodeClass.js'); +const { MenuManager, configManager } = require('generalFunctions'); module.exports = function(RED) { RED.nodes.registerType(nameOfNode, function(config) { RED.nodes.createNode(this, config); this.nodeClass = new nodeClass(config, RED, this, nameOfNode); }); + + const menuMgr = new MenuManager(); + const cfgMgr = new configManager(); + + RED.httpAdmin.get(`/${nameOfNode}/menu.js`, (req, res) => { + try { + const script = menuMgr.createEndpoint(nameOfNode, ['asset', 'logger']); + res.type('application/javascript').send(script); + } catch (err) { + res.status(500).send(`// Error generating menu: ${err.message}`); + } + }); + + RED.httpAdmin.get(`/${nameOfNode}/configData.js`, (req, res) => { + try { + const script = cfgMgr.createEndpoint(nameOfNode); + res.type('application/javascript').send(script); + } catch (err) { + res.status(500).send(`// Error generating configData: ${err.message}`); + } + }); }; diff --git a/src/nodeClass.js b/src/nodeClass.js index 7973638..87e9475 100644 --- a/src/nodeClass.js +++ b/src/nodeClass.js @@ -14,11 +14,19 @@ class nodeClass extends BaseNodeAdapter { buildDomainConfig(uiConfig) { const n = (v, fb) => (Number.isFinite(Number(v)) ? Number(v) : fb); + const s = (v, fb) => (typeof v === 'string' && v.length ? v : fb); return { + asset: { + model: s(uiConfig.model, 'gva-elastox-r'), + assetTagNumber: s(uiConfig.assetTagNumber, ''), + }, + general: { + unit: s(uiConfig.unit, 'Nm3/h'), + }, diffuser: { number: n(uiConfig.number, 1), elements: n(uiConfig.i_elements, 1), - density: n(uiConfig.i_diff_density, 2.4), + density: n(uiConfig.i_diff_density, 15), waterHeight: n(uiConfig.i_m_water, 0), alfaFactor: n(uiConfig.alfaf, 0.7), headerPressure: n(uiConfig.i_pressure, 0), diff --git a/src/specificClass.js b/src/specificClass.js index 214dbb0..42ea754 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -1,6 +1,12 @@ 'use strict'; -const { BaseDomain, statusBadge, interpolation, gravity, convert } = require('generalFunctions'); +const { BaseDomain, statusBadge, interpolation, gravity, convert, loadCurve } = require('generalFunctions'); + +// Default curve used when the node's asset model field is not set. Preserves +// the historical behaviour of the hardcoded _loadSpecs() (GVA ELASTOX-R at +// density 2.4 elements/m²) — the existing test suite calibrates against +// these numbers. +const DEFAULT_DIFFUSER_MODEL = 'gva-elastox-r'; class Diffuser extends BaseDomain { static name = 'diffuser'; @@ -19,7 +25,7 @@ class Diffuser extends BaseDomain { this.i_water_density = _num(d.waterDensity, 997); this.i_alfa_factor = _num(d.alfaFactor, 0.7); this.i_n_elements = _posInt(d.elements, 1); - this.i_diff_density = _num(d.density, 2.4); + this.i_diff_density = _num(d.density, 15); this.i_m_water = _num(d.waterHeight, 0); this.i_flow = 0; this.zoneVolume = _num(d.zoneVolume, 0); @@ -219,11 +225,27 @@ class Diffuser extends BaseDomain { } _loadSpecs() { + // Curve lookup id: prefer the asset-menu-saved field, fall back to the + // legacy GVA ELASTOX-R reference (same numbers as the previous inline + // _loadSpecs). If a configured id misses the registry, fall back too — + // a missing curve would otherwise crash the constructor in production. + const cfgModel = + this.config?.asset?.model || + this.config?.model || + DEFAULT_DIFFUSER_MODEL; + const raw = loadCurve(cfgModel) || loadCurve(DEFAULT_DIFFUSER_MODEL); + if (!raw || !raw.otr_curve || !raw.p_curve) { + throw new Error( + `diffuser: curve '${cfgModel}' is missing otr_curve/p_curve (registry has: ${Object.keys(raw || {}).join(',') || 'nothing'})`, + ); + } return { - supplier: 'GVA', type: 'ELASTOX-R', + supplier: raw._meta?.supplier || null, + type: raw._meta?.type || null, + model: raw._meta?.model || cfgModel, units: { Nm3: { temp: 20, pressure: 1.01325, RH: 0 } }, - otr_curve: { 2.4: { x: [2, 3, 4, 5, 6, 7, 8, 9, 10], y: [26, 25, 24, 23.5, 23, 22.75, 22.5, 22.25, 22] } }, - p_curve: { 0: { x: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], y: [40, 42.5, 45, 47.5, 50, 51.5, 53, 54.5, 56, 57.5, 59] } }, + otr_curve: raw.otr_curve, + p_curve: raw.p_curve, }; } }