// 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, inletPipeDiameter: 0.4, outletPipeDiameter: 0.3 }, hydraulics: { refHeight: 'NAP', basinBottomRef: 0, minHeightBasedOn: 'outlet' }, control: { mode: 'manual', allowedModes: new Set(['levelbased', 'manual']), levelbased: { minLevel: 0.5, startLevel: 2, maxLevel: 4, curveType: 'linear', logCurveFactor: 9 }, }, safety: { enableDryRunProtection: true, dryRunThresholdPercent: 50, enableHighVolumeSafety: false, highVolumeSafetyThresholdPercent: 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 }, ], };