This commit is contained in:
znetsixe
2026-03-11 11:13:17 +01:00
parent d56f8a382c
commit 6287708c1e
7 changed files with 1102 additions and 123 deletions

View File

@@ -47,10 +47,47 @@
//load local dependencies
const EventEmitter = require('events');
const {loadCurve,logger,configUtils,configManager,state, nrmse, MeasurementContainer, predict, interpolation , childRegistrationUtils} = require('generalFunctions');
const { loadCurve, logger, configUtils, configManager, state, MeasurementContainer, predict, childRegistrationUtils, convert } = require('generalFunctions');
const { ValveHydraulicModel, normalizeServiceType } = require('./hydraulicModel');
const SERVICE_TYPES = new Set(['gas', 'liquid']);
const DEFAULT_SOURCE_SERVICE_TYPE = Object.freeze({
machine: 'liquid',
rotatingmachine: 'liquid',
machinegroup: 'liquid',
machinegroupcontrol: 'liquid',
pumpingstation: 'liquid',
});
const CANONICAL_UNITS = Object.freeze({
pressure: 'Pa',
flow: 'm3/s',
temperature: 'K',
});
const DEFAULT_IO_UNITS = Object.freeze({
pressure: 'mbar',
flow: 'm3/h',
temperature: 'C',
});
const FORMULA_UNITS = Object.freeze({
pressure: 'mbar',
flow: 'm3/h',
temperature: 'K',
});
const FALLBACK_SUPPLIER_CURVE = Object.freeze({
'1.204': {
'125': {
x: [0, 100],
y: [0, 1],
},
},
});
class Valve {
constructor(valveConfig = {}, stateConfig = {}) {
constructor(valveConfig = {}, stateConfig = {}, runtimeOptions = {}) {
//basic setup
this.emitter = new EventEmitter(); // nodig voor ontvangen en uitvoeren van events emit() --> Zien als internet berichten (niet bedraad in node-red)
@@ -59,15 +96,37 @@ class Valve {
this.defaultConfig = this.configManager.getConfig('valve'); // Load default config for rotating machine ( use software type name ? )
this.configUtils = new configUtils(this.defaultConfig);
// Load a specific curve
// Load supplier-specific curve data (if available for model)
this.model = valveConfig.asset.model; // Get the model from the valveConfig
this.curve = this.model ? loadCurve(this.model) : null;
//Init config and check if it is valid
this.config = this.configUtils.initConfig(valveConfig);
this.unitPolicy = this._buildUnitPolicy(this.config);
this.config = this.configUtils.updateConfig(this.config, {
general: { unit: this.unitPolicy.output.flow },
asset: { ...this.config.asset, unit: this.unitPolicy.output.flow },
});
// Initialize measurements
this.measurements = new MeasurementContainer();
this.measurements = new MeasurementContainer({
autoConvert: true,
defaultUnits: {
pressure: this.unitPolicy.output.pressure,
flow: this.unitPolicy.output.flow,
temperature: this.unitPolicy.output.temperature,
},
preferredUnits: {
pressure: this.unitPolicy.output.pressure,
flow: this.unitPolicy.output.flow,
temperature: this.unitPolicy.output.temperature,
},
canonicalUnits: this.unitPolicy.canonical,
storeCanonical: true,
strictUnitValidation: true,
throwOnInvalidUnit: true,
requireUnitForTypes: ['pressure', 'flow', 'temperature'],
}, this.logger);
this.child = {}; // object to hold child information so we know on what to subscribe
// Init after config is set
@@ -75,22 +134,51 @@ class Valve {
this.state.stateManager.currentState = "operational"; // Set default state to operational
this.kv = 0; //default
this.rho = 1,225 //dichtheid van lucht standaard
this.T = 293; // temperatuur in K standaard
this.downstreamP = 0.54 //hardcodes for now --> assumed to be constant watercolumn and deltaP diffuser
this.kv = 0; // default
const configuredServiceType = this._normalizeOptionalServiceType(runtimeOptions?.serviceType || valveConfig?.asset?.serviceType);
this.expectedServiceType = configuredServiceType;
this.serviceType = configuredServiceType || normalizeServiceType(runtimeOptions?.serviceType || valveConfig?.asset?.serviceType);
this.upstreamFluidSources = new Map();
this._fluidContractListeners = new Map();
this.fluidCompatibility = {
status: configuredServiceType ? 'pending' : 'unknown',
expectedServiceType: configuredServiceType || null,
receivedServiceType: null,
upstreamServiceTypes: [],
sourceCount: 0,
message: configuredServiceType
? `Waiting for upstream fluid contract (${configuredServiceType}).`
: 'No upstream fluid contract available.',
};
this.hydraulicModel = new ValveHydraulicModel(
{
serviceType: this.serviceType,
gasChokedRatioLimit: runtimeOptions?.gasChokedRatioLimit ?? valveConfig?.asset?.gasChokedRatioLimit,
},
this.logger
);
this.rho = this._resolvePositiveNumber(
runtimeOptions?.fluidDensity,
valveConfig?.asset?.fluidDensity,
this.hydraulicModel.defaultDensity
);
this.T = this._resolvePositiveNumber(
runtimeOptions?.fluidTemperatureK,
valveConfig?.asset?.fluidTemperatureK,
this.hydraulicModel.defaultTemperatureK
);
this.currentMode = this.config.mode.current;
// wanneer hij deze ontvangt is de positie van de klep verandererd en gaat hij de updateposition functie aanroepen wat dan alle metingen en standen gaat updaten
this.state.emitter.on("positionChange", (data) => {
this._onPositionChange = (data) => {
this.logger.debug(`Position change detected: ${data}`);
this.updatePosition()}); //To update deltaP
this.updatePosition();
};
this.state.emitter.on("positionChange", this._onPositionChange); //To update deltaP
this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility
this.vCurve = this.curve[1.204]; // specificy the desired density RECALC THIS AUTOMTICALLY BASED ON DENSITY OF AIR LATER OLIFANT!!
this.predictKv = new predict({curve:this.vCurve}); // load valve size (x : ctrl , y : kv relationship)
//this.logger.debug(`PredictKv initialized with curve: ${JSON.stringify(this.predictKv)}`);
this._initSupplierCurvePredictor();
}
// -------- Config -------- //
@@ -121,7 +209,11 @@ class Valve {
break;
case "emergencyStop":
this.logger.warn(`Emergency stop activated by '${source}'.`);
await this.executeSequence("emergencyStop");
await this.executeSequence("emergencystop");
break;
case "emergencystop":
this.logger.warn(`Emergency stop activated by '${source}'.`);
await this.executeSequence("emergencystop");
break;
case "statusCheck":
this.logger.info(`Status Check: Mode = '${this.currentMode}', Source = '${source }'.`);
@@ -151,6 +243,413 @@ class Valve {
this.logger.info(`Mode successfully changed to '${newMode}'.`);
}
_buildUnitPolicy(config = {}) {
const flowUnit = this._resolveUnitOrFallback(
config?.general?.unit || config?.asset?.unit,
'volumeFlowRate',
DEFAULT_IO_UNITS.flow
);
return {
canonical: { ...CANONICAL_UNITS },
output: {
flow: flowUnit,
pressure: DEFAULT_IO_UNITS.pressure,
temperature: DEFAULT_IO_UNITS.temperature,
}
};
}
_resolveUnitOrFallback(candidate, expectedMeasure, fallbackUnit) {
const fallback = String(fallbackUnit || '').trim();
const raw = typeof candidate === 'string' ? candidate.trim() : '';
if (!raw) {
return fallback;
}
try {
const desc = convert().describe(raw);
if (expectedMeasure && desc.measure !== expectedMeasure) {
throw new Error(`expected '${expectedMeasure}', got '${desc.measure}'`);
}
return raw;
} catch (error) {
this.logger?.warn?.(`Invalid unit '${raw}' (${error.message}); falling back to '${fallback}'.`);
return fallback;
}
}
_outputUnitForType(type) {
switch (String(type || '').toLowerCase()) {
case 'flow':
return this.unitPolicy.output.flow;
case 'pressure':
return this.unitPolicy.output.pressure;
case 'temperature':
return this.unitPolicy.output.temperature;
default:
return null;
}
}
_readMeasurement(type, variant, position, unit = null) {
const requestedUnit = unit || this._outputUnitForType(type);
return this.measurements
.type(type)
.variant(variant)
.position(position)
.getCurrentValue(requestedUnit || undefined);
}
_writeMeasurement(type, variant, position, value, unit = null, timestamp = Date.now()) {
if (!Number.isFinite(value)) {
return;
}
this.measurements
.type(type)
.variant(variant)
.position(position)
.value(value, timestamp, unit || undefined);
}
_resolvePositiveNumber(...candidates) {
for (const candidate of candidates) {
const parsed = Number(candidate);
if (Number.isFinite(parsed) && parsed > 0) {
return parsed;
}
}
return undefined;
}
_normalizeOptionalServiceType(value) {
const raw = String(value || '').trim().toLowerCase();
if (SERVICE_TYPES.has(raw)) {
return raw;
}
return null;
}
_deriveDefaultServiceTypeForSoftwareType(softwareType) {
const key = String(softwareType || '').trim().toLowerCase();
return DEFAULT_SOURCE_SERVICE_TYPE[key] || null;
}
_extractFluidContractFromChild(child, softwareType) {
const sourceType = String(softwareType || child?.config?.functionality?.softwareType || '').trim().toLowerCase();
let contractFromChild = null;
if (typeof child?.getFluidContract === 'function') {
try {
contractFromChild = child.getFluidContract();
} catch (error) {
this.logger.warn(`Failed to read child fluid contract: ${error.message}`);
}
}
const contractStatus = String(contractFromChild?.status || '').trim().toLowerCase();
if (contractStatus === 'conflict') {
return {
status: 'conflict',
serviceType: null,
sourceType,
};
}
const contractType = this._normalizeOptionalServiceType(contractFromChild?.serviceType);
if (contractType) {
return {
status: 'resolved',
serviceType: contractType,
sourceType,
};
}
const directType = this._normalizeOptionalServiceType(
child?.serviceType
|| child?.expectedServiceType
|| child?.config?.asset?.serviceType
);
if (directType) {
return {
status: 'resolved',
serviceType: directType,
sourceType,
};
}
const fallbackType = this._deriveDefaultServiceTypeForSoftwareType(sourceType);
if (fallbackType) {
return {
status: 'inferred',
serviceType: fallbackType,
sourceType,
};
}
return {
status: 'unknown',
serviceType: null,
sourceType,
};
}
_bindFluidContractListener(sourceId, child, sourceType) {
if (!sourceId || this._fluidContractListeners.has(sourceId)) {
return;
}
if (!child?.emitter || typeof child.emitter.on !== 'function') {
return;
}
const handler = () => {
const latest = this._extractFluidContractFromChild(child, sourceType);
const existing = this.upstreamFluidSources.get(sourceId) || {};
existing.contract = latest;
this.upstreamFluidSources.set(sourceId, existing);
this._updateFluidCompatibilityState();
};
child.emitter.on('fluidContractChange', handler);
this._fluidContractListeners.set(sourceId, {
emitter: child.emitter,
handler,
});
}
_computeFluidCompatibilitySnapshot() {
const expectedServiceType = this.expectedServiceType || null;
const contracts = Array.from(this.upstreamFluidSources.values())
.map((entry) => entry?.contract)
.filter(Boolean);
const upstreamServiceTypes = Array.from(new Set(
contracts
.map((contract) => this._normalizeOptionalServiceType(contract.serviceType))
.filter(Boolean)
));
const hasConflict = contracts.some((contract) => String(contract.status || '').toLowerCase() === 'conflict');
const sourceCount = this.upstreamFluidSources.size;
if (hasConflict || upstreamServiceTypes.length > 1) {
return {
status: 'conflict',
expectedServiceType,
receivedServiceType: upstreamServiceTypes.length === 1 ? upstreamServiceTypes[0] : null,
upstreamServiceTypes,
sourceCount,
message: `Conflicting upstream fluids detected: ${upstreamServiceTypes.join(', ') || 'unknown'}.`,
};
}
if (upstreamServiceTypes.length === 1) {
const receivedServiceType = upstreamServiceTypes[0];
if (expectedServiceType && expectedServiceType !== receivedServiceType) {
return {
status: 'mismatch',
expectedServiceType,
receivedServiceType,
upstreamServiceTypes,
sourceCount,
message: `Expected ${expectedServiceType}, received ${receivedServiceType}.`,
};
}
return {
status: expectedServiceType ? 'match' : 'inferred',
expectedServiceType,
receivedServiceType,
upstreamServiceTypes,
sourceCount,
message: expectedServiceType
? `Fluid contract validated: ${receivedServiceType}.`
: `Fluid inferred from upstream: ${receivedServiceType}.`,
};
}
return {
status: expectedServiceType ? 'pending' : 'unknown',
expectedServiceType,
receivedServiceType: null,
upstreamServiceTypes: [],
sourceCount,
message: expectedServiceType
? `Waiting for upstream fluid contract (${expectedServiceType}).`
: 'No upstream fluid contract available.',
};
}
_updateFluidCompatibilityState() {
const next = this._computeFluidCompatibilitySnapshot();
const previous = this.fluidCompatibility || {};
const changed = (
previous.status !== next.status
|| previous.expectedServiceType !== next.expectedServiceType
|| previous.receivedServiceType !== next.receivedServiceType
|| previous.sourceCount !== next.sourceCount
|| (previous.message || '') !== (next.message || '')
);
this.fluidCompatibility = next;
if (!changed) {
return;
}
if (next.status === 'mismatch' || next.status === 'conflict') {
this.logger.warn(`Fluid compatibility warning: ${next.message}`);
} else {
this.logger.info(`Fluid compatibility update: ${next.message}`);
}
this.emitter.emit('fluidCompatibilityChange', this.getFluidCompatibility());
this.emitter.emit('fluidContractChange', this.getFluidContract());
}
getFluidCompatibility() {
const state = this.fluidCompatibility || {};
return {
status: state.status || 'unknown',
expectedServiceType: state.expectedServiceType || null,
receivedServiceType: state.receivedServiceType || null,
upstreamServiceTypes: Array.isArray(state.upstreamServiceTypes) ? [...state.upstreamServiceTypes] : [],
sourceCount: Number(state.sourceCount) || 0,
message: state.message || '',
};
}
getFluidContract() {
const compatibility = this.getFluidCompatibility();
if (compatibility.status === 'conflict') {
return {
status: 'conflict',
serviceType: null,
expectedServiceType: compatibility.expectedServiceType,
observedServiceType: compatibility.receivedServiceType,
source: 'valve',
};
}
const advertisedServiceType = compatibility.expectedServiceType || null;
return {
status: advertisedServiceType ? 'resolved' : 'unknown',
serviceType: advertisedServiceType,
expectedServiceType: compatibility.expectedServiceType,
observedServiceType: compatibility.receivedServiceType,
source: 'valve',
};
}
registerChild(child, softwareType) {
if (!child || typeof child !== 'object') {
this.logger.warn('registerChild skipped: invalid child payload');
return false;
}
const sourceType = String(softwareType || child?.config?.functionality?.softwareType || '').trim().toLowerCase();
const sourceId = child?.config?.general?.id
|| child?.config?.general?.name
|| `source-${this.upstreamFluidSources.size + 1}`;
const contract = this._extractFluidContractFromChild(child, sourceType);
this.upstreamFluidSources.set(sourceId, {
child,
sourceType,
contract,
});
this._bindFluidContractListener(sourceId, child, sourceType);
this._updateFluidCompatibilityState();
this.logger.info(`Source '${sourceId}' (${sourceType || 'unknown'}) registered for fluid contract.`);
return true;
}
_initSupplierCurvePredictor() {
const supplierCurve = this._resolveSupplierCurveData();
const densityTarget = Number.isFinite(this.rho) && this.rho > 0 ? this.rho : this.hydraulicModel.defaultDensity;
const densityKey = this._pickNearestNumericKey(Object.keys(supplierCurve), densityTarget);
const densityCurveFamily = supplierCurve[densityKey];
const diameterTarget = Number(this.config?.asset?.valveDiameter);
const diameterKey = this._pickNearestNumericKey(
Object.keys(densityCurveFamily || {}),
Number.isFinite(diameterTarget) && diameterTarget > 0 ? diameterTarget : 125
);
this.curveSelection = {
densityKey: Number(densityKey),
diameterKey: Number(diameterKey),
};
this.rho = Number.isFinite(this.rho) && this.rho > 0 ? this.rho : this.hydraulicModel.defaultDensity;
this.T = Number.isFinite(this.T) && this.T > 0 ? this.T : this.hydraulicModel.defaultTemperatureK;
this.predictKv = new predict({ curve: densityCurveFamily || FALLBACK_SUPPLIER_CURVE['1.204'] });
this.predictKv.fDimension = this.curveSelection.diameterKey;
this.logger.info(
`Using supplier curve model='${this.model || "inline"}', densityCurve=${this.curveSelection.densityKey}, diameter=${this.curveSelection.diameterKey}, serviceType=${this.serviceType}`
);
}
_resolveSupplierCurveData() {
if (this._isValidSupplierCurveData(this.curve)) {
return this.curve;
}
if (this._isValidSupplierCurveData(this.config?.asset?.valveCurve)) {
return this.config.asset.valveCurve;
}
this.logger.warn("No valid supplier curve data found, using fallback curve.");
return FALLBACK_SUPPLIER_CURVE;
}
_isValidSupplierCurveData(curveData) {
if (!curveData || typeof curveData !== "object") {
return false;
}
const densityKeys = Object.keys(curveData);
if (!densityKeys.length) {
return false;
}
for (const densityKey of densityKeys) {
const diameters = curveData[densityKey];
if (!diameters || typeof diameters !== "object") {
return false;
}
const diameterKeys = Object.keys(diameters);
if (!diameterKeys.length) {
return false;
}
for (const diameterKey of diameterKeys) {
const curve = diameters[diameterKey];
if (!Array.isArray(curve?.x) || !Array.isArray(curve?.y) || curve.x.length < 2 || curve.x.length !== curve.y.length) {
return false;
}
}
}
return true;
}
_pickNearestNumericKey(keys, target) {
const numericKeys = keys.map((key) => Number(key)).filter((value) => Number.isFinite(value));
if (!numericKeys.length) {
return String(target);
}
let selected = numericKeys[0];
let selectedDistance = Math.abs(selected - target);
for (const key of numericKeys) {
const distance = Math.abs(key - target);
if (distance < selectedDistance) {
selected = key;
selectedDistance = distance;
}
}
return String(selected);
}
_predictKvForPosition(positionPercent) {
if (!this.predictKv) {
return 0.1;
}
try {
this.predictKv.fDimension = this.curveSelection?.diameterKey || this.predictKv.fDimension;
const kv = Number(this.predictKv.y(positionPercent));
if (!Number.isFinite(kv)) {
return 0.1;
}
return Math.max(0.1, kv);
} catch (error) {
this.logger.warn(`Failed to predict Kv for position=${positionPercent}: ${error.message}`);
return 0.1;
}
}
// -------- Sequence Handlers -------- //
async executeSequence(sequenceName) {
@@ -196,9 +695,9 @@ class Valve {
}
}
updatePressure(variant,value,position) {
updatePressure(variant,value,position,unit = this.unitPolicy.output.pressure) {
if( value === null || value === undefined) {
this.logger.warn(`Received null or undefined value for flow update. Variant: ${variant}, Position: ${position}`);
this.logger.warn(`Received null or undefined value for pressure update. Variant: ${variant}, Position: ${position}`);
return;
}
this.logger.debug(`Updating pressure: variant=${variant}, value=${value}, position=${position}`);
@@ -206,18 +705,24 @@ class Valve {
switch (variant) {
case ("measured"):
// put value in measurements container
this.measurements.type("pressure").variant("measured").position(position).value(value);
this._writeMeasurement("pressure", "measured", position, Number(value), unit);
// get latest downstream pressure measurement
const measuredDownStreamP = this.measurements.type("pressure").variant("measured").position("downstream").getCurrentValue(); //update downstream pressure measurement
const measuredDownStreamP = this._readMeasurement("pressure", "measured", "downstream", FORMULA_UNITS.pressure);
const measuredFlow = this._readMeasurement("flow", "measured", "downstream", FORMULA_UNITS.flow);
const predictedFlow = this._readMeasurement("flow", "predicted", "downstream", FORMULA_UNITS.flow);
const activeFlow = Number.isFinite(predictedFlow) ? predictedFlow : measuredFlow;
// update predicted flow measurement
this.updateDeltaPKlep(value,this.kv,measuredDownStreamP,this.rho,this.T); //update deltaP based on new flow
this.updateDeltaPKlep(activeFlow,this.kv,measuredDownStreamP,this.rho,this.T); //update deltaP based on new flow
break;
case ("predicted"):
// put value in measurements container
this.measurements.type("pressure").variant("predicted").position(position).value(value);
const predictedDownStreamP = this.measurements.type("pressure").variant("measured").position("downstream").getCurrentValue(); //update downstream pressure measurement
this.updateDeltaPKlep(value,this.kv,predictedDownStreamP,this.rho,this.T); //update deltaP based on new flow
this._writeMeasurement("pressure", "predicted", position, Number(value), unit);
const predictedDownStreamP = this._readMeasurement("pressure", "predicted", "downstream", FORMULA_UNITS.pressure);
const measuredFlowFromPred = this._readMeasurement("flow", "measured", "downstream", FORMULA_UNITS.flow);
const predictedFlowFromPred = this._readMeasurement("flow", "predicted", "downstream", FORMULA_UNITS.flow);
const activeFlowFromPred = Number.isFinite(predictedFlowFromPred) ? predictedFlowFromPred : measuredFlowFromPred;
this.updateDeltaPKlep(activeFlowFromPred,this.kv,predictedDownStreamP,this.rho,this.T); //update deltaP based on new flow
break;
default:
@@ -226,15 +731,15 @@ class Valve {
}
}
updateMeasurement(variant, subType, value, position) {
updateMeasurement(variant, subType, value, position, unit) {
this.logger.debug(`---------------------- updating ${subType} ------------------ `);
switch (subType) {
case "pressure":
// Update pressure measurement
this.updatePressure(variant,value,position);
this.updatePressure(variant,value,position, unit || this.unitPolicy.output.pressure);
break;
case "flow":
this.updateFlow(variant,value,position);
this.updateFlow(variant,value,position, unit || this.unitPolicy.output.flow);
break;
case "power":
// Update power measurement
@@ -245,41 +750,33 @@ class Valve {
}
}
// NOTE: Omdat met zeer kleine getallen wordt gewerkt en er kwadraten in de formule zitten kan het zijn dat we alles *1000 moeten doen
// NOTE: q in m3/h (normalized basis), downstreamP in mbar(g), temp in K
updateDeltaPKlep(q,kv,downstreamP,rho,temp){
//q must be in Nm3/h
//temp must be in K
//q must be in m3/h
//downstreamP must be in bar so transfer from mbar to bar
downstreamP = downstreamP / 1000;
//convert downstreamP to absolute bar
downstreamP += 1.01325;
if( kv !== 0 && downstreamP != 0 && q != 0) { //check if kv and downstreamP are not zero to avoid division by zero
//calculate deltaP
let deltaP = ( q**2 * rho * temp ) / ( 514**2 * kv**2 * downstreamP);
//convert deltaP to mbar
deltaP = deltaP * 1000;
// Synchroniseer deltaP met het Valve-object
this.deltaPKlep = deltaP
// Opslaan in measurement container
this.measurements.type("pressure").variant("predicted").position("delta").value(deltaP);
this.logger.info('DeltaP updated to: ' + deltaP);
this.emitter.emit('deltaPChange', deltaP); // Emit event to notify valveGroupController of deltaP change
this.logger.info('DeltaPChange emitted to valveGroupController');
const result = this.hydraulicModel.calculateDeltaPMbar({
qM3h: q,
kv,
downstreamGaugeMbar: downstreamP,
rho,
tempK: temp,
});
if (!result || !Number.isFinite(result.deltaPMbar)) {
return;
}
}
const deltaP = result.deltaPMbar;
this.deltaPKlep = deltaP;
this.hydraulicDiagnostics = result.details || null;
this._writeMeasurement("pressure", "predicted", "delta", deltaP, this.unitPolicy.output.pressure);
this.logger.info('DeltaP updated to: ' + deltaP);
this.emitter.emit('deltaPChange', deltaP); // Emit event to notify valveGroupController of deltaP change
this.logger.info('DeltaPChange emitted to valveGroupController');
}
// Als er een nieuwe flow door de klep komt doordat de machines harder zijn gaan werken, dan update deze functie dit ook in de valve attributes en measurements
updateFlow(variant,value,position) {
updateFlow(variant,value,position,unit = this.unitPolicy.output.flow) {
if( value === null || value === undefined) {
this.logger.warn(`Received null or undefined value for flow update. Variant: ${variant}, Position: ${position}`);
return;
@@ -289,18 +786,20 @@ class Valve {
switch (variant) {
case ("measured"):
// put value in measurements container
this.measurements.type("flow").variant("measured").position(position).value(value);
this._writeMeasurement("flow", "measured", position, Number(value), unit);
// get latest downstream pressure measurement
const measuredDownStreamP = this.measurements.type("pressure").variant("measured").position("downstream").getCurrentValue(); //update downstream pressure measurement
const measuredDownStreamP = this._readMeasurement("pressure", "measured", "downstream", FORMULA_UNITS.pressure);
const measuredFlow = this._readMeasurement("flow", "measured", position, FORMULA_UNITS.flow);
// update predicted flow measurement
this.updateDeltaPKlep(value,this.kv,measuredDownStreamP,this.rho,this.T); //update deltaP based on new flow
this.updateDeltaPKlep(measuredFlow,this.kv,measuredDownStreamP,this.rho,this.T); //update deltaP based on new flow
break;
case ("predicted"):
// put value in measurements container
this.measurements.type("flow").variant("predicted").position(position).value(value);
const predictedDownStreamP = this.measurements.type("pressure").variant("measured").position("downstream").getCurrentValue(); //update downstream pressure measurement
this.updateDeltaPKlep(value,this.kv,predictedDownStreamP,this.rho,this.T); //update deltaP based on new flow
this._writeMeasurement("flow", "predicted", position, Number(value), unit);
const predictedDownStreamP = this._readMeasurement("pressure", "measured", "downstream", FORMULA_UNITS.pressure);
const predictedFlow = this._readMeasurement("flow", "predicted", position, FORMULA_UNITS.flow);
this.updateDeltaPKlep(predictedFlow,this.kv,predictedDownStreamP,this.rho,this.T); //update deltaP based on new flow
break;
default:
@@ -314,20 +813,15 @@ class Valve {
this.logger.debug('Calculating new deltaP');
const currentPosition = this.state.getCurrentPosition();
const measuredFlow = this.measurements.type("flow").variant("measured").position("downstream").getCurrentValue(); // haal de flow op uit de measurement containe
const predictedFlow = this.measurements.type("flow").variant("predicted").position("downstream").getCurrentValue(); // haal de predicted flow op uit de measurement container
const currentFlow = predictedFlow ;
const measuredFlow = this._readMeasurement("flow", "measured", "downstream", FORMULA_UNITS.flow);
const predictedFlow = this._readMeasurement("flow", "predicted", "downstream", FORMULA_UNITS.flow);
const currentFlow = Number.isFinite(predictedFlow) ? predictedFlow : measuredFlow;
const downstreamP = this.measurements.type("pressure").variant("measured").position("downstream").getCurrentValue(); // haal de downstream pressure op uit de measurement container
//const valveSize = 125; //NOTE: nu nog hardcoded maar moet een attribute van de valve worden
this.predictKv.fDimension = 125; //load valve size by defining fdimension in predict class
const downstreamP = this._readMeasurement("pressure", "measured", "downstream", FORMULA_UNITS.pressure);
const x = currentPosition; // dit is de positie van de klep waarvoor we delta P willen berekenen
const y = this.predictKv.y(x); // haal de waarde van kv op uit de spline
const y = this._predictKvForPosition(x); // haal de waarde van kv op uit de supplierscurve
this.kv = y; //update de kv waarde in de valve class
if (this.kv < 0.1){
this.kv = 0.1; //minimum waarde voor kv
}
this.logger.debug(`Kv value for position valve ${x} is ${this.kv}`); // log de waarde van kv
this.updateDeltaPKlep(currentFlow,this.kv,downstreamP,this.rho,this.T); //update deltaP
@@ -335,22 +829,46 @@ class Valve {
}
}
showCurve() {
return {
model: this.model || null,
serviceType: this.serviceType,
expectedServiceType: this.expectedServiceType,
gasChokedRatioLimit: this.hydraulicModel?.gasChokedRatioLimit,
selectedDensity: this.curveSelection?.densityKey ?? null,
selectedDiameter: this.curveSelection?.diameterKey ?? null,
curve: this.predictKv?.currentFxyCurve?.[this.predictKv?.fDimension] || null,
hydraulics: this.hydraulicDiagnostics || null,
};
}
destroy() {
if (this._onPositionChange && this.state?.emitter?.off) {
this.state.emitter.off("positionChange", this._onPositionChange);
}
for (const { emitter, handler } of this._fluidContractListeners.values()) {
if (typeof emitter?.off === 'function') {
emitter.off('fluidContractChange', handler);
} else if (typeof emitter?.removeListener === 'function') {
emitter.removeListener('fluidContractChange', handler);
}
}
this._fluidContractListeners.clear();
}
getOutput() {
// Improved output object generation
const output = {};
//build the output object
this.measurements.getTypes().forEach(type => {
this.measurements.getVariants().forEach(variant => {
this.measurements.getPositions().forEach(position => {
const value = this.measurements.type(type).variant(variant).position(position).getCurrentValue(); //get the current value of the measurement
if (value != null) {
output[`${position}_${variant}_${type}`] = value;
}
});
Object.entries(this.measurements.measurements || {}).forEach(([type, variants]) => {
Object.entries(variants || {}).forEach(([variant, positions]) => {
Object.keys(positions || {}).forEach((position) => {
const value = this._readMeasurement(type, variant, position, this._outputUnitForType(type));
if (value != null) {
output[`${position}_${variant}_${type}`] = value;
}
});
});
});