From b4364094c6b07defeabff5784c6ba085938d2372 Mon Sep 17 00:00:00 2001 From: znetsixe <73483679+znetsixe@users.noreply.github.com> Date: Thu, 2 Oct 2025 17:08:41 +0200 Subject: [PATCH 1/3] Stable version of machinegroup control --- src/groupcontrol.test.js | 748 +++++++++++++-------------------------- src/nodeClass.js | 3 +- src/specificClass.js | 288 ++++++++++----- 3 files changed, 452 insertions(+), 587 deletions(-) diff --git a/src/groupcontrol.test.js b/src/groupcontrol.test.js index 796876d..96c04d2 100644 --- a/src/groupcontrol.test.js +++ b/src/groupcontrol.test.js @@ -1,548 +1,288 @@ +// ...existing code... const MachineGroup = require('./specificClass.js'); const Machine = require('../../rotatingMachine/src/specificClass'); const Measurement = require('../../measurement/src/specificClass'); const specs = require('../../generalFunctions/datasets/assetData/curves/hidrostal-H05K-S03R.json'); -function createBaseMachineConfig(machineNum, name, specs) { - return { - general: { - logging: { enabled: true, logLevel: "warn" }, - name: name, - id: machineNum, - unit: "m3/h" - }, - functionality: { - softwareType: "machine", - role: "rotationaldevicecontroller" - }, - asset: { - category: "pump", - type: "centrifugal", - model: "hidrostal-h05k-s03r", - supplier: "hydrostal", - machineCurve: specs - }, - mode: { - current: "auto", - allowedActions: { - auto: ["execSequence", "execMovement", "statusCheck"], - virtualControl: ["execMovement", "statusCheck"], - fysicalControl: ["statusCheck"] - }, - allowedSources: { - auto: ["parent", "GUI"], - virtualControl: ["GUI"], - fysicalControl: ["fysical"] - } - }, - sequences: { - startup: ["starting", "warmingup", "operational"], - shutdown: ["stopping", "coolingdown", "idle"], - emergencystop: ["emergencystop", "off"], - boot: ["idle", "starting", "warmingup", "operational"] - } - }; -} - -function createBaseMachineGroupConfig(name) { - return { - general: { - logging: { enabled: true, logLevel: "debug" }, - name: name - }, - functionality: { - softwareType: "machinegroup", - role: "groupcontroller" - }, - scaling: { - current: "normalized" - }, - mode: { - current: "optimalcontrol" - } - }; -} - +const stateConfig = { time:{starting:0,warmingup:0,stopping:0,coolingdown:0}, movement:{speed:1000,mode:"staticspeed"} }; const ptConfig = { - general: { - logging: { enabled: true, logLevel: "debug" }, - name: "testpt", - id: "0", - unit: "mbar", + general:{ logging:{enabled:false,logLevel:"warn"}, name:"testpt", id:"pt-1", unit:"mbar" }, + functionality:{ softwareType:"measurement", role:"sensor" }, + asset:{ category:"sensor", type:"pressure", model:"testmodel", supplier:"vega", unit:"mbar" }, + scaling:{ absMin:0, absMax:4000 } +}; + +const testSuite = []; +const efficiencyComparisons = []; + +function logPass(name, details="") { + const entry = { name, status:"PASS", details }; + testSuite.push(entry); + console.log(`โœ… ${name}${details ? ` โ€” ${details}` : ""}`); +} +function logFail(name, error) { + const entry = { name, status:"FAIL", details:error?.message || error }; + testSuite.push(entry); + console.error(`โŒ ${name} โ€” ${entry.details}`); +} +function approxEqual(actual, expected, tolerancePct=1) { + const tolerance = (expected * tolerancePct) / 100; + return actual >= expected - tolerance && actual <= expected + tolerance; +} +async function sleep(ms){ return new Promise(resolve => setTimeout(resolve, ms)); } + +function createMachineConfig(id,label) { + return { + general:{ logging:{enabled:false,logLevel:"warn"}, name:label, id, unit:"m3/h" }, + functionality:{ softwareType:"machine", role:"rotationaldevicecontroller" }, + asset:{ category:"pump", type:"centrifugal", model:"hidrostal-h05k-s03r", supplier:"hydrostal", machineCurve:specs }, + mode:{ + current:"auto", + allowedActions:{ + auto:["execSequence","execMovement","flowMovement","statusCheck"], + virtualControl:["execMovement","statusCheck"], + fysicalControl:["statusCheck"] + }, + allowedSources:{ + auto:["parent","GUI"], + virtualControl:["GUI"], + fysicalControl:["fysical"] + } }, - functionality: { - softwareType: "measurement", - role: "sensor" - }, - asset: { - category: "sensor", - type: "pressure", - model: "testmodel", - supplier: "vega" - }, - scaling: { - absMin: 0, - absMax: 4000, + sequences:{ + startup:["starting","warmingup","operational"], + shutdown:["stopping","coolingdown","idle"], + emergencystop:["emergencystop","off"], + boot:["idle","starting","warmingup","operational"] } + }; } -const stateConfig = { - time:{starting:0, warmingup:0, stopping:0, coolingdown:0}, - movement:{speed:1000, mode:"staticspeed"}, +async function bootstrapGroup() { + const groupCfg = { + general:{ logging:{enabled:false,logLevel:"warn"}, name:"testmachinegroup" }, + functionality:{ softwareType:"machinegroup", role:"groupcontroller" }, + scaling:{ current:"normalized" }, + mode:{ current:"optimalcontrol" } + }; + + const mg = new MachineGroup(groupCfg); + const pt = new Measurement(ptConfig); + + for (let idx=1; idx<=2; idx++){ + const machine = new Machine(createMachineConfig(String(idx),`machine-${idx}`), stateConfig); + mg.childRegistrationUtils.registerChild(machine,"downstream"); + machine.childRegistrationUtils.registerChild(pt,"downstream"); + } + pt.calculateInput(1000); + await sleep(10); + return { mg, pt }; } -async function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); +function captureState(mg,label){ + return { + label, + machines: Object.entries(mg.machines).map(([id,machine]) => ({ + id, + state: machine.state.getCurrentState(), + position: machine.state.getCurrentPosition(), + predictedFlow: machine.measurements.type("flow").variant("predicted").position("downstream").getCurrentValue() || 0, + predictedPower: machine.measurements.type("power").variant("predicted").position("upstream").getCurrentValue() || 0 + })), + totals: { + flow: mg.measurements.type("flow").variant("predicted").position("downstream").getCurrentValue() || 0, + power: mg.measurements.type("power").variant("predicted").position("upstream").getCurrentValue() || 0, + efficiency: mg.measurements.type("efficiency").variant("predicted").position("downstream").getCurrentValue() || 0 + } + }; } -function logMachineStates(mg, testName) { - console.log(`\n=== ${testName} ===`); - console.log(`scaling: ${mg.scaling}, mode: ${mg.mode}`); - console.log(`flow range: ${mg.dynamicTotals.flow.min.toFixed(2)} - ${mg.dynamicTotals.flow.max.toFixed(2)} m3/h`); - - Object.entries(mg.machines).forEach(([id, machine]) => { - const state = machine.state.getCurrentState(); - const flow = machine.measurements?.type("flow")?.variant("predicted")?.position("downstream")?.getCurrentValue() || 0; - const power = machine.measurements?.type("power")?.variant("predicted")?.position("upstream")?.getCurrentValue() || 0; - const position = machine.state?.getCurrentPosition(); - - console.log(`machine ${id}: state=${state}, position=${position.toFixed(2)}, flow=${flow.toFixed(2)}, power=${power.toFixed(2)}`); - }); - - const totalFlow = mg.measurements?.type("flow")?.variant("predicted")?.position("downstream")?.getCurrentValue() || 0; - const totalPower = mg.measurements?.type("power")?.variant("predicted")?.position("upstream")?.getCurrentValue() || 0; - console.log(`total: flow=${totalFlow.toFixed(2)}, power=${totalPower.toFixed(2)}`); - - // ADD THIS RETURN STATEMENT - this is what was missing! - return { - totalFlow, - totalPower, - efficiency: totalPower > 0 ? totalFlow / totalPower : 0 - }; -} - -async function testPriorityVsOptimalEfficiency(mg, pt1) { - - const demandIncrement = 1; // Test every 1% for detailed comparison - console.log("\n๐Ÿ”ฌ PRIORITY vs OPTIMAL CONTROL EFFICIENCY COMPARISON"); - console.log("=".repeat(80)); - - const results = []; - - console.log("\n๐Ÿ“Š Testing OPTIMAL CONTROL (every 10% for speed)..."); +async function testNormalizedScaling(mg,pt){ + const label = "Normalized scaling tracks expected flow"; + try{ mg.setScaling("normalized"); - mg.setMode("optimalcontrol"); - - // Test every 10% for speed and give machines time to start - for (let demand = 0; demand <= 100; demand += demandIncrement) { - try { - console.log(`\n๐Ÿ”„ Setting optimal demand to ${demand}%`); - await mg.handleInput("parent", demand); - pt1.calculateInput(1400); - - const data = logMachineStates(mg, `optimal ${demand}%`); - - results.push({ - demand, - optimal: { - flow: data.totalFlow, - power: data.totalPower, - efficiency: data.efficiency - } - }); - - console.log(`โœ… optimal ${demand}%: flow=${data.totalFlow.toFixed(2)}, power=${data.totalPower.toFixed(2)}, eff=${data.efficiency.toFixed(4)}`); - - } catch (err) { - console.error(`โŒ error at optimal ${demand}%:`, err.message); - } + const dynamic = mg.calcDynamicTotals(); + const checkpoints = [0,10,25,50,75,100]; + for (const demand of checkpoints){ + await mg.handleInput("parent", demand); + pt.calculateInput(1400); + await sleep(20); + + const totals = mg.measurements.type("flow").variant("predicted").position("downstream").getCurrentValue() || 0; + const expected = dynamic.flow.min + (demand/100)*(dynamic.flow.max - dynamic.flow.min); + if(!approxEqual(totals, expected, 2)){ + throw new Error(`Flow ${totals.toFixed(2)} outside expectation ${expected.toFixed(2)} @ ${demand}%`); + } } - - console.log("\n๐Ÿ“Š Testing PRIORITY CONTROL (every 10% for speed)..."); - mg.setMode("prioritycontrol"); - - let resultIndex = 0; - for (let demand = 0; demand <= 100; demand += demandIncrement) { - try { - console.log(`\n๐Ÿ”„ Setting priority demand to ${demand}%`); - await mg.handleInput("parent", demand); - pt1.calculateInput(1400); - - const data = logMachineStates(mg, `priority ${demand}%`); - - // Add priority data to existing result - if (results[resultIndex]) { - results[resultIndex].priority = { - flow: data.totalFlow, - power: data.totalPower, - efficiency: data.efficiency - }; - } - - console.log(`โœ… priority ${demand}%: flow=${data.totalFlow.toFixed(2)}, power=${data.totalPower.toFixed(2)}, eff=${data.efficiency.toFixed(4)}`); - - resultIndex++; - - } catch (err) { - console.error(`โŒ error at priority ${demand}%:`, err.message); - resultIndex++; - } - } - - // Generate comparison report - generateEfficiencyReport(results); + logPass(label); + }catch(err){ logFail(label, err); } } -// Add this report generation function -function generateEfficiencyReport(results) { - console.log("\n" + "=".repeat(100)); - console.log("๐Ÿ“ˆ EFFICIENCY COMPARISON REPORT"); - console.log("=".repeat(100)); - - // Filter complete results with actual data - const completeResults = results.filter(r => - r.optimal && r.priority && - r.optimal.power > 0 && r.priority.power > 0 && - r.optimal.flow > 0 && r.priority.flow > 0 - ); - - if (completeResults.length === 0) { - console.log("โŒ No complete results with active machines to compare"); - console.log("๐Ÿ’ก This might indicate machines are not starting properly"); - - // Show what data we do have - console.log("\n๐Ÿ” DEBUGGING DATA:"); - results.forEach(r => { - if (r.optimal || r.priority) { - console.log(`${r.demand}%: optimal=${r.optimal?.power || 'missing'}, priority=${r.priority?.power || 'missing'}`); - } - }); - return; - } - - console.log(`\n๐Ÿ“Š Successfully analyzed ${completeResults.length} test points with active machines`); - - // Calculate summary statistics - let totalPowerDiff = 0; - let totalEffDiff = 0; - let validComparisons = 0; - - console.log("\n๐Ÿ“‹ DETAILED BREAKDOWN:"); - console.log("Demand | Optimal Power | Priority Power | Power Diff | Optimal Eff | Priority Eff | Eff Diff"); - console.log("-------|---------------|----------------|------------|-------------|--------------|----------"); - - completeResults.forEach(r => { - const powerDiff = r.priority.power - r.optimal.power; - const effDiff = r.priority.efficiency - r.optimal.efficiency; - - totalPowerDiff += powerDiff; - totalEffDiff += effDiff; - validComparisons++; - - console.log( - `${r.demand}% | ${r.optimal.power.toFixed(3).padStart(11)} | ${r.priority.power.toFixed(3).padStart(12)} | ` + - `${powerDiff.toFixed(3).padStart(8)} | ${r.optimal.efficiency.toFixed(4).padStart(9)} | ` + - `${r.priority.efficiency.toFixed(4).padStart(10)} | ${effDiff.toFixed(4).padStart(7)}` - ); - }); - - if (validComparisons > 0) { - const avgPowerDiff = totalPowerDiff / validComparisons; - const avgEffDiff = totalEffDiff / validComparisons; - - console.log("\n๐Ÿ“Š SUMMARY:"); - console.log(`Valid comparisons: ${validComparisons}`); - console.log(`Average power difference: ${avgPowerDiff.toFixed(3)} kW`); - console.log(`Average efficiency difference: ${avgEffDiff.toFixed(4)} m3/h per kW`); - - console.log("\n๐Ÿ’ก RECOMMENDATION:"); - if (avgEffDiff > 0.001) { - console.log(`โœ… Priority Control shows ${avgEffDiff.toFixed(4)} better efficiency on average`); - } else if (avgEffDiff < -0.001) { - console.log(`โœ… Optimal Control shows ${Math.abs(avgEffDiff).toFixed(4)} better efficiency on average`); - } else { - console.log(`โš–๏ธ Both control methods show similar efficiency`); - } - } -} - -async function testNormalizedScaling(mg, pt1) { - console.log("\n๐Ÿงช testing normalized scaling (0-100%)..."); - mg.setScaling("normalized"); - - //fetch ranges - const maxflow = mg.dynamicTotals.flow.max; - console.log(`max group flow capacity: ${maxflow.toFixed(2)} m3/h`); - const minFlow = mg.dynamicTotals.flow.min; - console.log(`min group flow capacity: ${minFlow.toFixed(2)} m3/h`); - - const testPoints = [0, 10, 25, 50, 75, 90, 100]; - const testPressurePoints = [800, 1200, 1600, 2000]; - - for (const pressure of testPressurePoints) { - try { - console.log(`\n--- testing at ${pressure} mbar ---`); - pt1.calculateInput(pressure); - logMachineStates(mg, `${pressure} mbar, before demand tests`); - - - for (const demand of testPoints) { - try { - console.log(`\n--- normalized demand: ${demand}% ---`); - await mg.handleInput("parent", demand); - - - logMachineStates(mg, `normalized ${demand}%`); - - //check if total flow is within expected range - const totalFlow = mg.measurements?.type("flow")?.variant("predicted")?.position("downstream")?.getCurrentValue() || 0; - const expectedFlow = minFlow + (demand / 100) * (maxflow - minFlow); - const percentTolerance = 0.1 ; // % tolerance of expected flow - const tolerance = (expectedFlow * percentTolerance) / 100; - - if (totalFlow < expectedFlow - tolerance || totalFlow > expectedFlow + tolerance) { - console.warn(`โš ๏ธ Total flow (${totalFlow.toFixed(2)} m3/h) is outside expected range (${(expectedFlow - tolerance).toFixed(2)} - ${(expectedFlow + tolerance).toFixed(2)} m3/h)`); - } - else { - console.log( `Difference between expected and actual flow: ${(totalFlow - expectedFlow).toFixed(2)} m3/h`); - console.log(`โœ… Total flow (${totalFlow.toFixed(2)} m3/h) is within expected range (${(expectedFlow - tolerance).toFixed(2)} - ${(expectedFlow + tolerance).toFixed(2)} m3/h)`); - } - - } catch (err) { - console.error(`โŒ error at ${demand}%:`, err.message); - } - } - - } catch (err) { - console.error(`โŒ error setting pressure to ${pressure}:`, err.message); - } -} -} - -async function testAbsoluteScaling(mg, pt1) { - console.log("\n๐Ÿงช testing absolute scaling..."); +async function testAbsoluteScaling(mg,pt){ + const label = "Absolute scaling accepts direct flow targets"; + try{ mg.setScaling("absolute"); - + mg.setMode("optimalcontrol"); const absMin = mg.dynamicTotals.flow.min; const absMax = mg.dynamicTotals.flow.max; - const testPoints = [ - absMin, - absMin + 20, - (absMin + absMax) / 2, - absMax - 20, - absMax - ]; - - for (const demand of testPoints) { - try { - console.log(`\n--- absolute demand: ${demand.toFixed(2)} m3/h ---`); - await mg.handleInput("parent", demand); - pt1.calculateInput(1400); - - logMachineStates(mg, `absolute ${demand.toFixed(2)} m3/h`); - - } catch (err) { - console.error(`โŒ error at ${demand.toFixed(2)}:`, err.message); - } + const demandPoints = [absMin, absMin+20, (absMin+absMax)/2, absMax-20]; + + for(const setpoint of demandPoints){ + await mg.handleInput("parent", setpoint); + pt.calculateInput(1400); + await sleep(20); + const flow = mg.measurements.type("flow").variant("predicted").position("downstream").getCurrentValue() || 0; + if(!approxEqual(flow, setpoint, 2)){ + throw new Error(`Flow ${flow.toFixed(2)} != demand ${setpoint.toFixed(2)}`); + } } + logPass(label); + }catch(err){ logFail(label, err); } } -async function testControlModes(mg, pt1) { - console.log("\n๐Ÿงช testing different control modes..."); - const modes = ["optimalcontrol", "prioritycontrol", "prioritypercentagecontrol"]; - const testDemand = 50; // 50% demand - +async function testModeTransitions(mg,pt){ + const label = "Mode transitions keep machines responsive"; + try{ + const modes = ["optimalcontrol","prioritycontrol","prioritypercentagecontrol"]; mg.setScaling("normalized"); - - for (const mode of modes) { - try { - console.log(`\n--- testing ${mode} ---`); - mg.setMode(mode); - await mg.handleInput("parent", testDemand); - pt1.calculateInput(1400); - - logMachineStates(mg, `${mode} at ${testDemand}%`); - - } catch (err) { - console.error(`โŒ error testing mode ${mode}:`, err.message); - } + for(const mode of modes){ + mg.setMode(mode); + await mg.handleInput("parent", 50); + pt.calculateInput(1300); + await sleep(20); + const snapshot = captureState(mg, mode); + const active = snapshot.machines.filter(m => m.state !== "idle"); + if(active.length === 0){ + throw new Error(`No active machines after switching to ${mode}`); + } } + logPass(label); + }catch(err){ logFail(label, err); } } -async function testRampUpDown(mg, pt1) { - console.log("\n๐Ÿงช testing ramp up and down..."); - mg.setScaling("normalized"); +async function testRampBehaviour(mg,pt){ + const label = "Ramp up/down keeps monotonic flow"; + try{ mg.setMode("optimalcontrol"); - - // Ramp up - console.log("\n--- ramp up test ---"); - for (let demand = 0; demand <= 100; demand += 20) { - try { - console.log(`ramping up to ${demand}%`); - await mg.handleInput("parent", demand); - pt1.calculateInput(1400); - - if (demand % 40 === 0) { // Log every other step - logMachineStates(mg, `ramp up ${demand}%`); - } - - } catch (err) { - console.error(`โŒ error ramping up to ${demand}%:`, err.message); - } + mg.setScaling("normalized"); + const upDemands = [0,20,40,60,80,100]; + let lastFlow = 0; + for(const demand of upDemands){ + await mg.handleInput("parent", demand); + pt.calculateInput(1500); + await sleep(15); + const flow = mg.measurements.type("flow").variant("predicted").position("downstream").getCurrentValue() || 0; + if(flow < lastFlow - 1){ + throw new Error(`Flow decreased during ramp up: ${flow.toFixed(2)} < ${lastFlow.toFixed(2)}`); + } + lastFlow = flow; } - - // Ramp down - console.log("\n--- ramp down test ---"); - for (let demand = 100; demand >= 0; demand -= 20) { - try { - console.log(`ramping down to ${demand}%`); - await mg.handleInput("parent", demand); - pt1.calculateInput(1400); - - if (demand % 40 === 0) { // Log every other step - logMachineStates(mg, `ramp down ${demand}%`); - } - - } catch (err) { - console.error(`โŒ error ramping down to ${demand}%:`, err.message); - } + const downDemands = [100,80,60,40,20,0]; + lastFlow = Infinity; + for(const demand of downDemands){ + await mg.handleInput("parent", demand); + pt.calculateInput(1200); + await sleep(15); + const flow = mg.measurements.type("flow").variant("predicted").position("downstream").getCurrentValue() || 0; + if(flow > lastFlow + 1){ + throw new Error(`Flow increased during ramp down: ${flow.toFixed(2)} > ${lastFlow.toFixed(2)}`); + } + lastFlow = flow; } + logPass(label); + }catch(err){ logFail(label, err); } } -async function testPressureResponse(mg, pt1) { - console.log("\n๐Ÿงช testing pressure response..."); - mg.setScaling("normalized"); +async function testPressureAdaptation(mg,pt){ + const label = "Pressure changes update predictions"; + try{ mg.setMode("optimalcontrol"); - - const pressures = [800, 1200, 1600, 2000]; - const demand = 50; - + mg.setScaling("normalized"); + const pressures = [800,1200,1600,2000]; + let previousFlow = null; + for(const p of pressures){ + pt.calculateInput(p); + await mg.handleInput("parent", 50); + await sleep(20); + const flow = mg.measurements.type("flow").variant("predicted").position("downstream").getCurrentValue() || 0; + if(previousFlow !== null && Math.abs(flow - previousFlow) < 0.5){ + throw new Error(`Flow did not react to pressure shift (${previousFlow.toFixed(2)} -> ${flow.toFixed(2)})`); + } + previousFlow = flow; + } + logPass(label); + }catch(err){ logFail(label, err); } +} + + +async function comparePriorityVsOptimal(mg, pt){ + const label = "Priority vs Optimal efficiency comparison"; + try{ + mg.setScaling("normalized"); + const pressures = [800, 1100, 1400, 1700]; + const demands = [...Array(21)].map((_, idx) => idx * 5); + for (const pressure of pressures) { - try { - console.log(`\n--- testing at ${pressure} mbar ---`); - pt1.calculateInput(pressure); - await mg.handleInput("parent", demand); + pt.calculateInput(pressure); + await sleep(15); - - logMachineStates(mg, `${pressure} mbar, ${demand}%`); - - } catch (err) { - console.error(`โŒ error at pressure ${pressure}:`, err.message); - } + for (const demand of demands) { + mg.setMode("optimalcontrol"); + await mg.handleInput("parent", demand); + pt.calculateInput(pressure); + await sleep(20); + const optimalTotals = captureState(mg, `optimal-${pressure}-${demand}`).totals; + + mg.setMode("prioritycontrol"); + await mg.handleInput("parent", demand); + pt.calculateInput(pressure); + await sleep(20); + const priorityTotals = captureState(mg, `priority-${pressure}-${demand}`).totals; + + efficiencyComparisons.push({ + pressure, + demandPercent: demand, + optimalFlow: Number(optimalTotals.flow.toFixed(3)), + optimalPower: Number(optimalTotals.power.toFixed(3)), + optimalEfficiency: Number((optimalTotals.efficiency || 0).toFixed(4)), + priorityFlow: Number(priorityTotals.flow.toFixed(3)), + priorityPower: Number(priorityTotals.power.toFixed(3)), + priorityEfficiency: Number((priorityTotals.efficiency || 0).toFixed(4)), + efficiencyDelta: Number(((priorityTotals.efficiency || 0) - (optimalTotals.efficiency || 0)).toFixed(4)), + powerDelta: Number((priorityTotals.power - optimalTotals.power).toFixed(3)) + }); + } } + + logPass(label, "efficiencyComparisons array populated"); + } catch (err) { + logFail(label, err); + } } -async function testEdgeCases(mg, pt1) { - console.log("\n๐Ÿงช testing edge cases..."); - mg.setScaling("normalized"); - mg.setMode("optimalcontrol"); - - const edgeCases = [ - { demand: -10, name: "negative demand" }, - { demand: 0, name: "zero demand" }, - { demand: 0.5, name: "fractional demand" }, - { demand: 110, name: "over 100%" }, - { demand: 999, name: "extreme demand" } - ]; - - for (const testCase of edgeCases) { - try { - console.log(`\n--- testing ${testCase.name}: ${testCase.demand} ---`); - await mg.handleInput("parent", testCase.demand); - pt1.calculateInput(1400); - logMachineStates(mg, testCase.name); - - } catch (err) { - console.error(`โŒ error testing ${testCase.name}:`, err.message); - } - } +async function run(){ + console.log("๐Ÿš€ Starting machine-group integration tests..."); + const { mg, pt } = await bootstrapGroup(); + + await testNormalizedScaling(mg, pt); + await testAbsoluteScaling(mg, pt); + await testModeTransitions(mg, pt); + await testRampBehaviour(mg, pt); + await testPressureAdaptation(mg, pt); + await comparePriorityVsOptimal(mg, pt); + + console.log("\n๐Ÿ“‹ TEST SUMMARY"); + console.table(testSuite); + console.log("\n๐Ÿ“Š efficiencyComparisons:"); + console.dir(efficiencyComparisons, { depth:null }); + console.log("โœ… All tests completed."); } -async function testPerformanceMetrics(mg, pt1) { - console.log("\n๐Ÿงช testing performance metrics..."); - mg.setScaling("normalized"); - mg.setMode("optimalcontrol"); - - const demands = [25, 50, 75]; - const results = []; - - for (const demand of demands) { - try { - const startTime = Date.now(); - await mg.handleInput("parent", demand); - pt1.calculateInput(1400); - const endTime = Date.now(); - - const totalFlow = mg.measurements?.type("flow")?.variant("predicted")?.position("downstream")?.getCurrentValue() || 0; - const totalPower = mg.measurements?.type("power")?.variant("predicted")?.position("upstream")?.getCurrentValue() || 0; - const efficiency = totalFlow > 0 ? (totalFlow / totalPower).toFixed(3) : 0; - - results.push({ - demand, - flow: totalFlow.toFixed(2), - power: totalPower.toFixed(2), - efficiency, - responseTime: endTime - startTime - }); - - - } catch (err) { - console.error(`โŒ error testing performance at ${demand}%:`, err.message); - } - } - - console.log("\n=== performance summary ==="); - console.log("demand | flow | power | efficiency | response(ms)"); - console.log("-------|--------|--------|-----------|---------"); - results.forEach(r => { - console.log(`${r.demand}% | ${r.flow} | ${r.power} | ${r.efficiency} | ${r.responseTime}`); - }); -} - -async function runAllTests() { - console.log("๐Ÿš€ starting comprehensive machinegroup tests...\n"); - - try { - // Setup - const machineGroupConfig = createBaseMachineGroupConfig("testmachinegroup"); - const machineConfigs = {}; - machineConfigs[1] = createBaseMachineConfig(1, "testmachine1", specs); - machineConfigs[2] = createBaseMachineConfig(2, "testmachine2", specs); - - const mg = new MachineGroup(machineGroupConfig); - const pt1 = new Measurement(ptConfig); - const numofMachines = 2; - - // Register machines - for (let i = 1; i <= numofMachines; i++) { - const machine = new Machine(machineConfigs[i],stateConfig); - mg.childRegistrationUtils.registerChild(machine, "downstream"); - } - - mg.machines[1].childRegistrationUtils.registerChild(pt1, "downstream"); - mg.machines[2].childRegistrationUtils.registerChild(pt1, "downstream"); - - console.log(`โœ… setup complete: ${Object.keys(mg.machines).length} machines registered`); - console.log(`flow range: ${mg.dynamicTotals.flow.min.toFixed(2)} - ${mg.dynamicTotals.flow.max.toFixed(2)} m3/h\n`); - - // Run test suites - //await testPriorityVsOptimalEfficiency(mg, pt1); - await testNormalizedScaling(mg, pt1); - await testAbsoluteScaling(mg, pt1); - await testControlModes(mg, pt1); - await testRampUpDown(mg, pt1); - await testPressureResponse(mg, pt1); - await testEdgeCases(mg, pt1); - await testPerformanceMetrics(mg, pt1); - - console.log("\n๐ŸŽ‰ all tests completed successfully!"); - - } catch (err) { - console.error("๐Ÿ’ฅ test suite failed:", err.message); - console.error("stack trace:", err.stack); - } -} +run().catch(err => { + console.error("๐Ÿ’ฅ Test harness crashed:", err); +}); +// ...existing code... // Run all tests -runAllTests(); \ No newline at end of file +run(); \ No newline at end of file diff --git a/src/nodeClass.js b/src/nodeClass.js index 050982a..37f1cdd 100644 --- a/src/nodeClass.js +++ b/src/nodeClass.js @@ -58,6 +58,7 @@ class nodeClass { } _updateNodeStatus() { + //console.log('Updating node status...'); const mg = this.source; const mode = mg.mode; const scaling = mg.scaling; @@ -72,7 +73,7 @@ class nodeClass { const totalPower = mg.measurements ?.type("power") ?.variant("predicted") - ?.position("upstream") + ?.position("atEquipment") ?.getCurrentValue() || 0; // Calculate total capacity based on available machines with safety checks diff --git a/src/specificClass.js b/src/specificClass.js index 3d0c017..a45d9f5 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -50,12 +50,6 @@ class MachineGroup { } - // when a child gets updated do something - handleChildChange() { - this.absoluteTotals = this.calcAbsoluteTotals(); - //for reference and not to recalc these values continiously - this.dynamicTotals = this.calcDynamicTotals(); - } registerChild(child,softwareType) { this.logger.debug('Setting up childs specific for this class'); @@ -69,15 +63,26 @@ class MachineGroup { child.measurements.emitter.on("pressure.measured.differential", (eventData) => { - this.logger.debug(`Pressure update from ${child.config.general.id}: ${eventData.value} ${eventData.unit}`); - this.handleChildChange(); + this.logger.debug(`Pressure update from ${child.config.general.id}: ${eventData.value} ${eventData.unit}`); + this.handlePressureChange(); + }); + child.measurements.emitter.on("pressure.measured.downstream", (eventData) => { + this.logger.debug(`Pressure update from ${child.config.general.id}: ${eventData.value} ${eventData.unit}`); + this.handlePressureChange(); + }); + + child.measurements.emitter.on("flow.predicted.downstream", (eventData) => { + this.logger.debug(`Flow prediction update from ${child.config.general.id}: ${eventData.value} ${eventData.unit}`); + //later change to this.handleFlowPredictionChange(); + this.handlePressureChange(); + }); + + } } - - calcAbsoluteTotals() { const absoluteTotals = { flow: { min: Infinity, max: 0 }, power: { min: Infinity, max: 0 } }; @@ -99,6 +104,7 @@ class MachineGroup { if( maxPower > totals.power.max ){ totals.power.max = maxPower; } }); + //surplus machines for max flow and power if( totals.flow.min < absoluteTotals.flow.min ){ absoluteTotals.flow.min = totals.flow.min; } if( totals.power.min < absoluteTotals.power.min ){ absoluteTotals.power.min = totals.power.min; } @@ -107,6 +113,29 @@ class MachineGroup { }); + if(absoluteTotals.flow.min === Infinity) { + this.logger.warn(`Flow min ${absoluteTotals.flow.min} is Infinity. Setting to 0.`); + absoluteTotals.flow.min = 0; + } + + if(absoluteTotals.power.min === Infinity) { + this.logger.warn(`Power min ${absoluteTotals.power.min} is Infinity. Setting to 0.`); + absoluteTotals.power.min = 0; + } + + if(absoluteTotals.flow.max === -Infinity) { + this.logger.warn(`Flow max ${absoluteTotals.flow.max} is -Infinity. Setting to 0.`); + absoluteTotals.flow.max = 0; + } + + if(absoluteTotals.power.max === -Infinity) { + this.logger.warn(`Power max ${absoluteTotals.power.max} is -Infinity. Setting to 0.`); + absoluteTotals.power.max = 0; + } + + // Place data in object for external use + this.absoluteTotals = absoluteTotals; + return absoluteTotals; } @@ -114,9 +143,10 @@ class MachineGroup { //max and min current flow and power based on their actual pressure curve calcDynamicTotals() { - const dynamicTotals = { flow: { min: Infinity, max: 0 }, power: { min: Infinity, max: 0 }, NCog : 0 }; + const dynamicTotals = { flow: { min: Infinity, max: 0, act: 0 }, power: { min: Infinity, max: 0, act: 0 }, NCog : 0 }; this.logger.debug(`\n --------- Calculating dynamic totals for ${Object.keys(this.machines).length} machines. @ current pressure settings : ----------`); + Object.values(this.machines).forEach(machine => { this.logger.debug(`Processing machine with id: ${machine.config.general.id}`); this.logger.debug(`Current pressure settings: ${JSON.stringify(machine.predictFlow.currentF)}`); @@ -125,18 +155,27 @@ class MachineGroup { const maxFlow = machine.predictFlow.currentFxyYMax; const minPower = machine.predictPower.currentFxyYMin; const maxPower = machine.predictPower.currentFxyYMax; + const actFlow = machine.measurements.type("flow").variant("predicted").position("downstream").getCurrentValue(); + const actPower = machine.measurements.type("power").variant("predicted").position("atEquipment").getCurrentValue(); + + this.logger.debug(`Machine ${machine.config.general.id} - Min Flow: ${minFlow}, Max Flow: ${maxFlow}, Min Power: ${minPower}, Max Power: ${maxPower}, NCog: ${machine.NCog}`); if( minFlow < dynamicTotals.flow.min ){ dynamicTotals.flow.min = minFlow; } if( minPower < dynamicTotals.power.min ){ dynamicTotals.power.min = minPower; } dynamicTotals.flow.max += maxFlow; dynamicTotals.power.max += maxPower; + dynamicTotals.flow.act += actFlow; + dynamicTotals.power.act += actPower; //fetch total Normalized Cog over all machines dynamicTotals.NCog += machine.NCog; }); + // Place data in object for external use + this.dynamicTotals = dynamicTotals; + return dynamicTotals; } @@ -166,10 +205,16 @@ class MachineGroup { } handlePressureChange() { - this.logger.info("Pressure change detected."); - this.calcDynamicTotals(); + this.logger.info("---------------------->>>>>>>>>>>>>>>>>>>>>>>>>>>Pressure change detected."); + // Recalculate totals + const { flow, power } = this.calcDynamicTotals(); + + this.logger.debug(`Dynamic Totals after pressure change - Flow: Min ${flow.min}, Max ${flow.max}, Act ${flow.act} | Power: Min ${power.min}, Max ${power.max}, Act ${power.act}`); + this.measurements.type("flow").variant("predicted").position("downstream").value(flow.act); + this.measurements.type("power").variant("predicted").position("atEquipment").value(power.act); + const { maxEfficiency, lowestEfficiency } = this.calcGroupEfficiency(this.machines); - const efficiency = this.measurements.type("efficiency").variant("predicted").position("downstream").getCurrentValue(); + const efficiency = this.measurements.type("efficiency").variant("predicted").position("atEquipment").getCurrentValue(); this.calcDistanceBEP(efficiency,maxEfficiency,lowestEfficiency); } @@ -232,7 +277,6 @@ class MachineGroup { // Generate all possible subsets of machines (power set) Object.keys(machines).forEach(machineId => { - //machineId = parseInt(machineId); const state = machines[machineId].state.getCurrentState(); const validActionForMode = machines[machineId].isValidActionForMode("execSequence", "auto"); @@ -334,7 +378,6 @@ class MachineGroup { } // -------- Mode and Input Management -------- // - isValidActionForMode(action, mode) { const allowedActionsSet = this.config.mode.allowedActions[mode] || []; return allowedActionsSet.has(action); @@ -346,8 +389,18 @@ class MachineGroup { this.logger.debug(`Scaling set to: ${scaling}`); } + async abortActiveMovements(reason = "new demand") { + await Promise.all(Object.values(this.machines).map(async machine => { + this.logger.warn(`Aborting active movements for machine ${machine.config.general.id} due to: ${reason}`); + if (typeof machine.abortMovement === "function") { + await machine.abortMovement(reason); + } + })); + } + //handle input from parent / user / UI async optimalControl(Qd, powerCap = Infinity) { + try{ //we need to force the pressures of all machines to be equal to the highest pressure measured in the group // this is to ensure a correct evaluation of the flow and power consumption @@ -361,20 +414,25 @@ class MachineGroup { const maxDownstream = Math.max(...pressures.map(p => p.downstream)); const minUpstream = Math.min(...pressures.map(p => p.upstream)); + this.logger.debug(`Max downstream pressure: ${maxDownstream}, Min upstream pressure: ${minUpstream}`); + //set the pressures Object.entries(this.machines).forEach(([machineId, machine]) => { if(machine.state.getCurrentState() !== "operational" && machine.state.getCurrentState() !== "accelerating" && machine.state.getCurrentState() !== "decelerating"){ + + //Equilize pressures over all machines so we can make a proper calculation machine.measurements.type("pressure").variant("measured").position("downstream").value(maxDownstream); machine.measurements.type("pressure").variant("measured").position("upstream").value(minUpstream); + // after updating the measurement directly we need to force the update of the value OLIFANT this is not so clear now in the code // we need to find a better way to do this but for now it works machine.getMeasuredPressure(); } }); + //fetch dynamic totals + const dynamicTotals = this.dynamicTotals; - //update dynamic totals - const dynamicTotals = this.calcDynamicTotals(); const machineStates = Object.entries(this.machines).reduce((acc, [machineId, machine]) => { acc[machineId] = machine.state.getCurrentState(); return acc; @@ -396,48 +454,48 @@ class MachineGroup { } // fetch all valid combinations that meet expectations - const combinations = this.validPumpCombinations(this.machines, Qd, powerCap); - // + const combinations = this.validPumpCombinations(this.machines, Qd, powerCap); const bestResult = this.calcBestCombination(combinations, Qd); if(bestResult.bestCombination === null){ this.logger.warn(`Demand: ${Qd.toFixed(2)} -> No valid combination found => not updating control `); return; } - + const debugInfo = bestResult.bestCombination.map(({ machineId, flow }) => `${machineId}: ${flow.toFixed(2)} units`).join(" | "); this.logger.debug(`Moving to demand: ${Qd.toFixed(2)} -> Pumps: [${debugInfo}] => Total Power: ${bestResult.bestPower.toFixed(2)}`); //store the total delivered power - this.measurements.type("power").variant("predicted").position("upstream").value(bestResult.bestPower); + this.measurements.type("power").variant("predicted").position("atEquipment").value(bestResult.bestPower); this.measurements.type("flow").variant("predicted").position("downstream").value(bestResult.bestFlow); - this.measurements.type("efficiency").variant("predicted").position("downstream").value(bestResult.bestFlow / bestResult.bestPower); - this.measurements.type("Ncog").variant("predicted").position("downstream").value(bestResult.bestCog); + this.measurements.type("efficiency").variant("predicted").position("atEquipment").value(bestResult.bestFlow / bestResult.bestPower); + this.measurements.type("Ncog").variant("predicted").position("atEquipment").value(bestResult.bestCog); await Promise.all(Object.entries(this.machines).map(async ([machineId, machine]) => { - - const pumpInfo = bestResult.bestCombination.find(item => item.machineId == machineId); + // Find the flow for this machine in the best combination + this.logger.debug(`Searching for machine ${machineId} with state ${machineStates[machineId]} in best combination.`); + const pumpInfo = bestResult.bestCombination.find(item => item.machineId == machineId); let flow; if(pumpInfo !== undefined){ flow = pumpInfo.flow; } else { - this.logger.debug(`Machine ${machineId} not in best combination, setting flow to 0`); + this.logger.debug(`Machine ${machineId} not in best combination, setting flow control to 0`); flow = 0; } - if( (flow <= 0 ) && ( machineStates[machineId] === "operational" || machineStates[machineId] === "accelerating" || machineStates[machineId] === "decelerating" ) ){ await machine.handleInput("parent", "execSequence", "shutdown"); } - else if(machineStates[machineId] === "idle" && flow > 0){ + + if(machineStates[machineId] === "idle" && flow > 0){ await machine.handleInput("parent", "execSequence", "startup"); - } - else if(machineStates[machineId] === "operational" && flow > 0 ){ await machine.handleInput("parent", "flowMovement", flow); } - + + if(machineStates[machineId] === "operational" && flow > 0 ){ + await machine.handleInput("parent", "flowMovement", flow); + } })); - } catch(err){ this.logger.error(err); @@ -499,7 +557,7 @@ class MachineGroup { .map(id => ({ id, machine: this.machines[id] })); } else { machinesInPriorityOrder = Object.entries(this.machines) - .map(([id, machine]) => ({ id: parseInt(id), machine })) + .map(([id, machine]) => ({ id: id, machine })) .sort((a, b) => a.id - b.id); } return machinesInPriorityOrder; @@ -545,14 +603,6 @@ class MachineGroup { // Update dynamic totals const dynamicTotals = this.calcDynamicTotals(); - // Handle zero demand by shutting down all machines early exit - if (Qd <= 0) { - await Promise.all(Object.entries(this.machines).map(async ([machineId, machine]) => { - if (this.isMachineActive(machineId)) { await machine.handleInput("parent", "execSequence", "shutdown"); } - })); - return; - } - // Cap flow demand to min/max possible values Qd = this.capFlowDemand(Qd,dynamicTotals); @@ -646,14 +696,16 @@ class MachineGroup { this.logger.debug(`Priority control for demand: ${totalFlow.toFixed(2)} -> Active pumps: [${debugInfo}] => Total Power: ${totalPower.toFixed(2)}`); // Store measurements - this.measurements.type("power").variant("predicted").position("upstream").value(totalPower); + this.measurements.type("power").variant("predicted").position("atEquipment").value(totalPower); this.measurements.type("flow").variant("predicted").position("downstream").value(totalFlow); - this.measurements.type("efficiency").variant("predicted").position("downstream").value(totalFlow / totalPower); - this.measurements.type("Ncog").variant("predicted").position("downstream").value(totalCog); + this.measurements.type("efficiency").variant("predicted").position("atEquipment").value(totalFlow / totalPower); + this.measurements.type("Ncog").variant("predicted").position("atEquipment").value(totalCog); + this.logger.debug(`Flow distribution: ${JSON.stringify(flowDistribution)}`); // Apply the flow distribution to machines await Promise.all(flowDistribution.map(async ({ machineId, flow }) => { const machine = this.machines[machineId]; + this.logger.debug(this.machines[machineId].state); const currentState = this.machines[machineId].state.getCurrentState(); if (flow <= 0 && (currentState === "operational" || currentState === "accelerating" || currentState === "decelerating")) { @@ -758,8 +810,10 @@ class MachineGroup { // fetch and store measurements Object.entries(this.machines).forEach(([machineId, machine]) => { - const powerValue = machine.measurements.type("power").variant("predicted").position("upstream").getCurrentValue(); + + const powerValue = machine.measurements.type("power").variant("predicted").position("atEquipment").getCurrentValue(); const flowValue = machine.measurements.type("flow").variant("predicted").position("downstream").getCurrentValue(); + if (powerValue !== null) { totalPower.push(powerValue); } @@ -768,10 +822,11 @@ class MachineGroup { } }); - this.measurements.type("power").variant("predicted").position("upstream").value(totalPower.reduce((a, b) => a + b, 0)); + this.measurements.type("power").variant("predicted").position("atEquipment").value(totalPower.reduce((a, b) => a + b, 0)); this.measurements.type("flow").variant("predicted").position("downstream").value(totalFlow.reduce((a, b) => a + b, 0)); + if(totalPower.reduce((a, b) => a + b, 0) > 0){ - this.measurements.type("efficiency").variant("predicted").position("downstream").value(totalFlow.reduce((a, b) => a + b, 0) / totalPower.reduce((a, b) => a + b, 0)); + this.measurements.type("efficiency").variant("predicted").position("atEquipment").value(totalFlow.reduce((a, b) => a + b, 0) / totalPower.reduce((a, b) => a + b, 0)); } } @@ -780,43 +835,80 @@ class MachineGroup { } } - async handleInput(source, Qd, powerCap = Infinity, priorityList = null) { + async handleInput(source, demand, powerCap = Infinity, priorityList = null) { + + //abort current movements + await this.abortActiveMovements("new demand received"); const scaling = this.scaling; const mode = this.mode; - let rawInput = Qd; + const dynamicTotals = this.calcDynamicTotals(); + const demandQ = parseFloat(demand); + let demandQout = 0; // keep output Q by default 0 for safety + + this.logger.debug(`Handling input from ${source}: Demand = ${demand}, Power Cap = ${powerCap}, Priority List = ${priorityList}`); switch (scaling) { case "absolute": - // No scaling needed but cap range - if (Qd < this.absoluteTotals.flow.min) { - this.logger.warn(`Flow demand ${Qd} is below minimum possible flow ${this.absoluteTotals.flow.min}. Capping to minimum flow.`); - Qd = this.absoluteTotals.flow.min; - } else if (Qd > this.absoluteTotals.flow.max) { - this.logger.warn(`Flow demand ${Qd} is above maximum possible flow ${this.absoluteTotals.flow.max}. Capping to maximum flow.`); - Qd = this.absoluteTotals.flow.max; + if (isNaN(demandQ)) { + this.logger.warn(`Invalid absolute flow demand: ${demand}. Must be a number.`); + demandQout = 0; + return; + } + + if (demandQ < absoluteTotals.flow.min) { + this.logger.warn(`Flow demand ${demandQ} is below minimum possible flow ${absoluteTotals.flow.min}. Capping to minimum flow.`); + demandQout = this.absoluteTotals.flow.min; + } else if (demandQout > absoluteTotals.flow.max) { + this.logger.warn(`Flow demand ${demandQ} is above maximum possible flow ${absoluteTotals.flow.max}. Capping to maximum flow.`); + demandQout = absoluteTotals.flow.max; + }else if(demandQout <= 0){ + this.logger.debug(`Turning machines off`); + demandQout = 0; + //return early and turn all machines off + this.turnOffAllMachines(); + return; } break; case "normalized": - // Scale demand to 0-100% linear between min and max flow this is auto capped - Qd = this.interpolation.interpolate_lin_single_point(Qd, 0, 100, this.dynamicTotals.flow.min, this.dynamicTotals.flow.max); + + this.logger.debug(`Normalizing flow demand: ${demandQ} with min: ${dynamicTotals.flow.min} and max: ${dynamicTotals.flow.max}`); + if(demand < 0){ + this.logger.debug(`Turning machines off`); + demandQout = 0; + //return early and turn all machines off + this.turnOffAllMachines(); + return; + } + else{ + // Scale demand to 0-100% linear between min and max flow this is auto capped + demandQout = this.interpolation.interpolate_lin_single_point(demandQ, 0, 100, dynamicTotals.flow.min, dynamicTotals.flow.max ); + this.logger.debug(`Normalized flow demand ${demandQ}% to: ${demandQout} Q units`); + } break; } + + + // Execute control based on mode switch(mode) { case "prioritycontrol": - await this.equalFlowControl(Qd,powerCap,priorityList); + this.logger.debug(`Calculating prio control. Input flow demand: ${demandQ} scaling : ${scaling} -> ${demandQout}`); + await this.equalFlowControl(demandQout,powerCap,priorityList); break; + case "prioritypercentagecontrol": + this.logger.debug(`Calculating prio percentage control. Input flow demand: ${demandQ} scaling : ${scaling} -> ${demandQout}`); if(scaling !== "normalized"){ this.logger.warn("Priority percentage control is only valid with normalized scaling."); return; } - await this.prioPercentageControl(rawInput,priorityList); + await this.prioPercentageControl(demandQout,priorityList); break; case "optimalcontrol": - await this.optimalControl(Qd,powerCap); + this.logger.debug(`Calculating optimal control. Input flow demand: ${demandQ} scaling : ${scaling} -> ${demandQout}`); + await this.optimalControl(demandQout,powerCap); break; default: @@ -831,6 +923,12 @@ class MachineGroup { } + async turnOffAllMachines(){ + await Promise.all(Object.entries(this.machines).map(async ([machineId, machine]) => { + if (this.isMachineActive(machineId)) { await machine.handleInput("parent", "execSequence", "shutdown"); } + })); + } + setMode(mode) { this.mode = mode; } @@ -845,6 +943,7 @@ class MachineGroup { this.measurements.getVariants(type).forEach(variant => { const downstreamVal = this.measurements.type(type).variant(variant).position("downstream").getCurrentValue(); + const atEquipmentVal = this.measurements.type(type).variant(variant).position("atEquipment").getCurrentValue(); const upstreamVal = this.measurements.type(type).variant(variant).position("upstream").getCurrentValue(); if (downstreamVal != null) { @@ -853,6 +952,9 @@ class MachineGroup { if (upstreamVal != null) { output[`upstream_${variant}_${type}`] = upstreamVal; } + if (atEquipmentVal != null) { + output[`atEquipment_${variant}_${type}`] = atEquipmentVal; + } if (downstreamVal != null && upstreamVal != null) { const diffVal = this.measurements.type(type).variant(variant).difference().value; output[`differential_${variant}_${type}`] = diffVal; @@ -876,17 +978,17 @@ class MachineGroup { } module.exports = MachineGroup; - /* + const Machine = require('../../rotatingMachine/src/specificClass'); const Measurement = require('../../measurement/src/specificClass'); const specs = require('../../generalFunctions/datasets/assetData/curves/hidrostal-H05K-S03R.json'); -const { number } = require("../../generalFunctions/src/convert/lodash/lodash._objecttypes"); +const { max } = require("mathjs"); function createBaseMachineConfig(machineNum, name,specs) { return { general: { - logging: { enabled: true, logLevel: "warn" }, + logging: { enabled: true, logLevel: "debug" }, name: name, id: machineNum, unit: "m3/h" @@ -924,6 +1026,23 @@ function createBaseMachineConfig(machineNum, name,specs) { }; } +function createStateConfig(){ + return { + time:{ + starting: 1, + stopping: 1, + warmingup: 1, + coolingdown: 1, + emergencystop: 1 + }, + movement:{ + mode:"dynspeed", + speed:100, + maxSpeed: 1000 + } + } +}; + function createBaseMachineGroupConfig(name) { return { general: { @@ -944,9 +1063,13 @@ function createBaseMachineGroupConfig(name) { } const machineGroupConfig = createBaseMachineGroupConfig("testmachinegroup"); +const stateConfigs = {}; const machineConfigs = {}; -machineConfigs[1]= createBaseMachineConfig(1,"testmachine",specs); -machineConfigs[2] = createBaseMachineConfig(2,"testmachine2",specs); +stateConfigs[1] = createStateConfig(); +stateConfigs[2] = createStateConfig(); +machineConfigs[1]= createBaseMachineConfig("asdfkj;asdf","testmachine",specs); +machineConfigs[2] = createBaseMachineConfig("asdfkj;asdf2","testmachine2",specs); + const ptConfig = { general: { @@ -976,14 +1099,16 @@ async function makeMachines(){ const pt1 = new Measurement(ptConfig); const numofMachines = 2; for(let i = 1; i <= numofMachines; i++){ - const machine = new Machine(machineConfigs[i]); + const machine = new Machine(machineConfigs[i],stateConfigs[i]); //mg.machines[i] = machine; mg.childRegistrationUtils.registerChild(machine, "downstream"); } - mg.machines[1].childRegistrationUtils.registerChild(pt1, "downstream"); - mg.machines[2].childRegistrationUtils.registerChild(pt1, "downstream"); - //mg.setMode("prioritycontrol"); + Object.keys(mg.machines).forEach(machineId => { + mg.machines[machineId].childRegistrationUtils.registerChild(pt1, "downstream"); + }); + + mg.setMode("prioritycontrol"); mg.setScaling("normalized"); const absMax = mg.dynamicTotals.flow.max; @@ -992,14 +1117,13 @@ async function makeMachines(){ const percMax = 100; try{ - /* + /* for(let demand = mg.dynamicTotals.flow.min ; demand <= mg.dynamicTotals.flow.max ; demand += 2){ //set pressure console.log("------------------------------------"); await mg.handleInput("parent",demand); pt1.calculateInput(1400); - console.log("Waiting for 0.2 sec "); //await new Promise(resolve => setTimeout(resolve, 200)); console.log("------------------------------------"); @@ -1012,24 +1136,24 @@ async function makeMachines(){ await mg.handleInput("parent",demand); pt1.calculateInput(1400); - console.log("Waiting for 0.2 sec "); //await new Promise(resolve => setTimeout(resolve, 200)); console.log("------------------------------------"); } - //*/ -/* - for(let demand = 0 ; demand <= 100 ; demand += 1){ + //*//* + + for(let demand = 0 ; demand <= 50 ; demand += 1){ //set pressure - console.log(`processing demand of ${demand}`); + console.log(`TESTING: processing demand of ${demand}`); await mg.handleInput("parent",demand); - console.log(mg.machines[1].state.getCurrentState()); - console.log(mg.machines[2].state.getCurrentState()); + Object.keys(mg.machines).forEach(machineId => { + console.log(mg.machines[machineId].state.getCurrentState()); + }); + + console.log(`updating pressure to 1400 mbar`); pt1.calculateInput(1400); - console.log("Waiting for 0.2 sec "); - //await new Promise(resolve => setTimeout(resolve, 200)); console.log("------------------------------------"); } From 15501e8b1d140a6489a44a1eb17b26dd6c66631e Mon Sep 17 00:00:00 2001 From: Rene De ren Date: Fri, 3 Oct 2025 15:33:37 +0200 Subject: [PATCH 2/3] updates from laptop --- src/specificClass.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/specificClass.js b/src/specificClass.js index a45d9f5..753b10f 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -61,7 +61,6 @@ class MachineGroup { //listen for machine pressure changes this.logger.debug(`Listening for pressure changes from machine ${child.config.general.id}`); - child.measurements.emitter.on("pressure.measured.differential", (eventData) => { this.logger.debug(`Pressure update from ${child.config.general.id}: ${eventData.value} ${eventData.unit}`); this.handlePressureChange(); @@ -906,6 +905,7 @@ class MachineGroup { } await this.prioPercentageControl(demandQout,priorityList); break; + case "optimalcontrol": this.logger.debug(`Calculating optimal control. Input flow demand: ${demandQ} scaling : ${scaling} -> ${demandQout}`); await this.optimalControl(demandQout,powerCap); From 8c59a921d53fe585060bba4a77f9c0005ed7a348 Mon Sep 17 00:00:00 2001 From: znetsixe <73483679+znetsixe@users.noreply.github.com> Date: Sun, 5 Oct 2025 07:55:23 +0200 Subject: [PATCH 3/3] syncing --- src/specificClass.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/specificClass.js b/src/specificClass.js index a45d9f5..849ed5f 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -59,8 +59,7 @@ class MachineGroup { this.machines[child.config.general.id] === undefined ? this.machines[child.config.general.id] = child : this.logger.warn(`Machine ${child.config.general.id} is already registered.`); //listen for machine pressure changes - this.logger.debug(`Listening for pressure changes from machine ${child.config.general.id}`); - + this.logger.debug(`Listening for changes from child ${child.config.general.id} `); child.measurements.emitter.on("pressure.measured.differential", (eventData) => { this.logger.debug(`Pressure update from ${child.config.general.id}: ${eventData.value} ${eventData.unit}`);