67 lines
2.7 KiB
JavaScript
67 lines
2.7 KiB
JavaScript
|
|
// Dry-run safety trip — manual mode, fixed high demand, zero inflow.
|
||
|
|
// Levelbased control would taper demand as the level drops (its ramp),
|
||
|
|
// stalling drainage before safety fires. Manual mode holds demand
|
||
|
|
// constant so the level actually reaches the dry-run threshold.
|
||
|
|
|
||
|
|
module.exports = {
|
||
|
|
name: 'safety-dry-run-trip',
|
||
|
|
description: 'Manual mode, constant 100 % demand, zero inflow; expect safety to force-stop downstream pumps when level reaches the dry-run threshold.',
|
||
|
|
durationSec: 600,
|
||
|
|
|
||
|
|
config: {
|
||
|
|
general: { name: 'EvalDryRun', id: 'eval-dry-run', unit: 'm3/h',
|
||
|
|
logging: { enabled: false, logLevel: 'error' } },
|
||
|
|
functionality: { softwareType: 'pumpingStation', role: 'stationcontroller', positionVsParent: 'atEquipment' },
|
||
|
|
basin: { volume: 50, height: 5, inflowLevel: 3, outflowLevel: 0.2, overflowLevel: 4.5 },
|
||
|
|
hydraulics: { refHeight: 'NAP', basinBottomRef: 0, minHeightBasedOn: 'outlet' },
|
||
|
|
control: {
|
||
|
|
mode: 'manual',
|
||
|
|
allowedModes: new Set(['levelbased', 'manual']),
|
||
|
|
levelbased: { minLevel: 0.5, startLevel: 2, maxLevel: 4 },
|
||
|
|
},
|
||
|
|
safety: {
|
||
|
|
enableDryRunProtection: true,
|
||
|
|
dryRunThresholdPercent: 50,
|
||
|
|
enableOverfillProtection: false,
|
||
|
|
overfillThresholdPercent: 98,
|
||
|
|
timeleftToFullOrEmptyThresholdSeconds: 0,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
|
||
|
|
setup: async (ps) => {
|
||
|
|
const MAX_OUTFLOW = 0.04;
|
||
|
|
let mgcRunning = true; // gets toggled by safety's shutdown call
|
||
|
|
ps.machineGroups['mgc1'] = {
|
||
|
|
config: { general: { name: 'mgc1', id: 'mgc1' }, functionality: { positionVsParent: 'downstream' } },
|
||
|
|
turnOffAllMachines: () => {
|
||
|
|
mgcRunning = false;
|
||
|
|
ps.measurements.type('flow').variant('predicted').position('out').child('mgc1').value(0, Date.now(), 'm3/s');
|
||
|
|
},
|
||
|
|
handleInput: async (_src, demand) => {
|
||
|
|
if (!mgcRunning) return;
|
||
|
|
const d = Math.max(0, Math.min(100, Number(demand) || 0));
|
||
|
|
ps.measurements.type('flow').variant('predicted').position('out').child('mgc1').value((d / 100) * MAX_OUTFLOW, Date.now(), 'm3/s');
|
||
|
|
},
|
||
|
|
};
|
||
|
|
// Need a downstream machine for safety to shut down
|
||
|
|
ps.machines['pump1'] = {
|
||
|
|
config: { general: { name: 'pump1', id: 'pump1' }, functionality: { positionVsParent: 'downstream' } },
|
||
|
|
_isOperationalState: () => mgcRunning,
|
||
|
|
handleInput: async (_src, action) => {
|
||
|
|
if (action === 'execSequence') mgcRunning = false;
|
||
|
|
},
|
||
|
|
};
|
||
|
|
ps.calibratePredictedLevel(2.5);
|
||
|
|
},
|
||
|
|
|
||
|
|
inputs: (t, ps) => {
|
||
|
|
ps.setManualInflow(0, Date.now(), 'm3/s');
|
||
|
|
if (ps.mode === 'manual') ps.forwardDemandToChildren(100);
|
||
|
|
},
|
||
|
|
|
||
|
|
expectations: [
|
||
|
|
{ name: 'safety engages at some point', type: 'safety_trips_gt', value: 0 },
|
||
|
|
{ name: 'level never goes below outflow pipe', type: 'min_level_bounded', value: 0.2 },
|
||
|
|
],
|
||
|
|
};
|