2026-05-12 17:11:50 +02:00
<!-- Load the dynamic menu & config endpoints (asset cascade + logger fields) -->
< script src = "/diffuser/menu.js" > < / script >
< script src = "/diffuser/configData.js" > < / script >
2026-03-12 16:32:20 +01:00
< script type = "text/javascript" >
RED.nodes.registerType('diffuser', {
2026-05-12 17:11:50 +02:00
category: 'EVOLV',
2026-05-21 15:05:58 +02:00
color: '#6EB5E5',
2026-03-12 16:32:20 +01:00
defaults: {
name: { value: '', required: true },
number: { value: 1, required: true },
i_elements: { value: 1, required: true },
2026-05-12 17:11:50 +02:00
i_diff_density: { value: 15, required: true },
2026-03-12 16:32:20 +01:00
i_m_water: { value: 0, required: true },
alfaf: { value: 0.7, required: true },
2026-03-31 14:26:08 +02:00
i_zone_volume: { value: 0, required: false },
2026-03-12 16:39:25 +01:00
processOutputFormat: { value: 'process' },
dbaseOutputFormat: { value: 'influxdb' },
2026-05-12 17:11:50 +02:00
// Asset identifier surface. supplier / assetType / category are derived
// at runtime via assetResolver.resolveAssetMetadata(model); do NOT add
// them back here. See generalFunctions/src/registry/README.md.
assetTagNumber: { value: '' },
model: { value: '' },
unit: { value: '' },
2026-03-12 16:32:20 +01:00
enableLog: { value: false },
logLevel: { value: 'error' },
},
inputs: 1,
2026-05-10 22:09:26 +02:00
outputs: 3,
2026-03-12 16:32:20 +01:00
inputLabels: ['control'],
2026-05-10 22:09:26 +02:00
outputLabels: ['process', 'dbase', 'parent'],
2026-03-12 16:32:20 +01:00
icon: 'font-awesome/fa-tint',
label: function() {
2026-05-12 17:11:50 +02:00
const stem = this.model ? this.model : (this.name || 'diffuser');
return this.name ? `${this.name}_${this.number}` : stem;
},
oneditprepare: function() {
// wait for the menu scripts to load, then hand the node off to the
// shared asset / logger init code from generalFunctions
let menuRetries = 0;
const maxMenuRetries = 100; // 5 s at 50 ms intervals
const waitForMenuData = () => {
if (window.EVOLV?.nodes?.diffuser?.initEditor) {
window.EVOLV.nodes.diffuser.initEditor(this);
} else if (++menuRetries < maxMenuRetries ) {
setTimeout(waitForMenuData, 50);
} else {
console.warn('diffuser: menu scripts failed to load within 5 seconds');
}
};
waitForMenuData();
},
oneditsave: function() {
// Asset cascade saves model + unit + assetTagNumber
if (window.EVOLV?.nodes?.diffuser?.assetMenu?.saveEditor) {
window.EVOLV.nodes.diffuser.assetMenu.saveEditor(this);
}
// Logger fields
if (window.EVOLV?.nodes?.diffuser?.loggerMenu?.saveEditor) {
window.EVOLV.nodes.diffuser.loggerMenu.saveEditor(this);
}
2026-03-12 16:32:20 +01:00
},
});
< / script >
< script type = "text/html" data-template-name = "diffuser" >
< div class = "form-row" >
< label for = "node-input-name" > < i class = "fa fa-tag" > < / i > Name< / label >
< input type = "text" id = "node-input-name" placeholder = "Zone name" >
< / div >
< div class = "form-row" >
< label for = "node-input-number" > < i class = "fa fa-list-ol" > < / i > Number< / label >
< input type = "number" id = "node-input-number" min = "1" >
< / div >
< div class = "form-row" >
< label for = "node-input-i_elements" > < i class = "fa fa-th" > < / i > Elements< / label >
< input type = "number" id = "node-input-i_elements" min = "1" >
< / div >
< div class = "form-row" >
2026-05-12 17:11:50 +02:00
< label for = "node-input-i_diff_density" > < i class = "fa fa-braille" > < / i > Bottom coverage [%]< / label >
< input type = "number" id = "node-input-i_diff_density" step = "0.1" min = "0" max = "100" placeholder = "typical 10-25" >
< div style = "font-size:11px;color:#666;margin-left:160px;" >
Fraction of tank floor occupied by diffuser membrane (%). Used as the curve-family key.
< / div >
2026-03-12 16:32:20 +01:00
< / div >
< div class = "form-row" >
< label for = "node-input-i_m_water" > < i class = "fa fa-arrows-v" > < / i > Water Height< / label >
< input type = "number" id = "node-input-i_m_water" step = "0.1" min = "0" >
< / div >
< div class = "form-row" >
< label for = "node-input-alfaf" > < i class = "fa fa-flask" > < / i > Alfa Factor< / label >
< input type = "number" id = "node-input-alfaf" step = "0.01" min = "0" >
< / div >
2026-03-31 14:26:08 +02:00
< div class = "form-row" >
< label for = "node-input-i_zone_volume" > < i class = "fa fa-cube" > < / i > Zone Volume< / label >
< input type = "number" id = "node-input-i_zone_volume" step = "0.1" min = "0" placeholder = "m3" >
< / div >
2026-05-12 17:11:50 +02:00
2026-03-12 16:39:25 +01:00
< h3 > Output Formats< / h3 >
< div class = "form-row" >
< label for = "node-input-processOutputFormat" > < i class = "fa fa-random" > < / i > Process Output< / label >
< select id = "node-input-processOutputFormat" style = "width:60%;" >
< option value = "process" > process< / option >
< option value = "json" > json< / option >
< option value = "csv" > csv< / option >
< / select >
< / div >
< div class = "form-row" >
< label for = "node-input-dbaseOutputFormat" > < i class = "fa fa-database" > < / i > Database Output< / label >
< select id = "node-input-dbaseOutputFormat" style = "width:60%;" >
< option value = "influxdb" > influxdb< / option >
2026-05-21 15:05:58 +02:00
< option value = "frost" > frost< / option >
2026-03-12 16:39:25 +01:00
< option value = "json" > json< / option >
< option value = "csv" > csv< / option >
< / select >
< / div >
2026-05-12 17:11:50 +02:00
<!-- Asset fields injected here by the shared asset menu (supplier → type → model → unit) -->
< div id = "asset-fields-placeholder" > < / div >
<!-- Logger fields injected here by the shared logger menu -->
< div id = "logger-fields-placeholder" > < / div >
2026-03-12 16:32:20 +01:00
< / script >
< script type = "text/html" data-help-name = "diffuser" >
2026-05-12 17:11:50 +02:00
< p > Diffused aeration device model. Resolves a supplier curve (SSOTR vs flow per element, DWP vs flow) at the configured bottom-coverage %, and emits the resulting oxygen-transfer rate plus a zone OTR for the parent reactor.< / p >
2026-03-12 16:32:20 +01:00
< / script >