/// /// Copyright (c) 2021 Sensus Slovensko a.s. /// using System; using System.Collections.Generic; using Common; using log4net; using Config.Entities; using TBF.Rig.ControlBoard.Uni; using TBF.Rig.GenericDevices; using TBF.Boxes; namespace TBF.Rig.Uni.RegValveLowRegulTimeSaturation { public class SetFlowOp : IOperation { private static readonly ILog log = LogManager.GetLogger(typeof(SetFlowOp)); public override string ToString() { return string.Format("SetFlowOp({0}, Qfrom={1}, Qto={2})", regV.Name, shrinkedTgtFlowLo, shrinkedTgtFlowHi); } public const double RqrdFlowRangeRatio = 0.5; /// /// Set by the constructor /// readonly UniCB uniCB; readonly RegValve regV; readonly IFlowMeter flowMeter; readonly double shrinkedTgtFlowLo; readonly double shrinkedTgtFlowHi; readonly double targetFlowAve; readonly int timeout; readonly int delay; readonly bool leaveFlowControlRunning; readonly double maximalFlow; /// /// Internal state of this operation /// enum OpState { Idle = 0, ValveMoveToPosition1, /// Wait 1 takt ValveMoveToPosition2, /// This is actual move to position ValveMoveToPosition3, /// Wait 1 takt SettingPosition, Wait4FlowMsrmntAndSendRVMove, /// Wait until the flow measurement starts, then issue reg. valve move command SettingFlow, /// Measure the flow and verify whether it is within limits sufficiently long time FlowReached, SendCommandAgain, } OpState opState; double currentReqFlowLo; double currentReqFlowHi; double targetPositionLo; double targetPositionHi; int setFlowTime; int expireTime; DoubleBox msrdFlow; int isFlowOkDuration; /// /// Set required water flow. Conditionally leave the measurement running. /// Events: FlowSet, FlowTimeOut /// /// Control board device /// Regulation valve component /// Flowmeter component /// Lower limit of the flow to be achieved in [m3/h] /// Upper limit of the flow to be achieved in [m3/h] /// DoubleBox for measured flow /// Timeout for the flow setting in [s] /// Flow setting starts after this delay in [s] /// true = Leave the measurement running after op. stop /// Only Elde.Valve flow are used, other flow on the lists are ignored public SetFlowOp(UniCB uniCB, IRegValve regValve, IFlowMeter flowMeter, double qFrom, double qTo, DoubleBox msrdFlow, int timeout, int delay, bool leaveFlowControlRunning) { this.uniCB = uniCB; if (this.uniCB == null) throw new ArgumentNullException("Control board is null or not Uni"); this.regV = regValve as RegValve; if (this.regV == null) throw new ArgumentNullException(string.Format("{0} is not Uni.RegValve", regValve.Name)); this.flowMeter = flowMeter; if (this.flowMeter == null) throw new ArgumentNullException("flowMeter"); maximalFlow = flowMeter.NominalFlow * 1.25; double rqrdFlowLoBeforeCorr = qFrom - MeasurementCorrection.GetCorrection(qFrom, flowMeter.Corrections); double rqrdFlowHiBeforeCorr = qTo - MeasurementCorrection.GetCorrection(qTo, flowMeter.Corrections); targetFlowAve = (rqrdFlowLoBeforeCorr + rqrdFlowHiBeforeCorr) / 2; shrinkedTgtFlowLo = (RqrdFlowRangeRatio * rqrdFlowLoBeforeCorr) + ((1 - RqrdFlowRangeRatio) * targetFlowAve); /// Move the lower limit 15% of the range up shrinkedTgtFlowHi = (RqrdFlowRangeRatio * rqrdFlowHiBeforeCorr) + ((1 - RqrdFlowRangeRatio) * targetFlowAve); /// Move the upper limit 15% of the range down this.msrdFlow = msrdFlow; this.timeout = timeout; this.delay = delay; this.leaveFlowControlRunning = leaveFlowControlRunning; log.Debug(this.ToString()); } /// /// Set required water flow - Do not leave the measurement running. /// Events: FlowSet /// public SetFlowOp(UniCB uniCB, IRegValve regValve, IFlowMeter flowMeter, double qFrom, double qTo, DoubleBox flowbox, int timeout, int delay) : this(uniCB, regValve, flowMeter, qFrom, qTo, flowbox, timeout, delay, false) { } /// /// Set required water flow - Do not leave the measurement running. /// Events: FlowSet /// public SetFlowOp(UniCB uniCB, IRegValve regValve, IFlowMeter flowMeter, double qFrom, double qTo, DoubleBox flowbox, int timeout) : this(uniCB, regValve, flowMeter, qFrom, qTo, flowbox, timeout, 0, false) { } /// /// Set required water flow - No timeout. /// Events: FlowSet /// public SetFlowOp(UniCB uniCB, IRegValve regValve, IFlowMeter flowMeter, double qFrom, double qTo, DoubleBox flowbox) : this(uniCB, regValve, flowMeter, qFrom, qTo, flowbox, int.MaxValue, 0, false) { } /// /// Fetch target position limits from the dictionary or return false /// /// Average of the flow targer range (input) /// Target valve position low limit (output) /// Target valve position high limit (output) /// true when positions for the target flow are stored in the memory, otherwise return false bool FetchTargetPosition(double avgReqFlow, out double positionLo, out double positionHi) { double targetPosition; if (regV.Dict.TryGetValue(avgReqFlow, out targetPosition)) { positionLo = Math.Max(targetPosition * 0.95, 0.0); positionHi = Math.Min(targetPosition * 1.05, 100.0); return true; } else { positionLo = 0; positionHi = 0; return false; } } void StoreTargetPosition(double avgReqFlow, double actPosition) { if (!regV.Dict.ContainsKey(targetFlowAve)) { regV.Dict.Add(new KeyValuePair(avgReqFlow, actPosition)); } return; } /// Start this operation public void Start() { log.InfoFormat("SetFlowOp:Start() rv#={0} flowMtr#={1} TARGET: flowLo={2} flowHi={3}", regV.Idx1, flowMeter.Idx1, currentReqFlowLo, currentReqFlowHi); /// Store the current time, etc. setFlowTime = StateMachine.Time + delay; expireTime = StateMachine.Time + timeout; if (expireTime < 0) expireTime = int.MaxValue; isFlowOkDuration = 0; /// Start the flow measurement uniCB.MeasureFlow(false, flowMeter.Idx1); opState = OpState.Wait4FlowMsrmntAndSendRVMove; } /// /// Run this operation /// /// /// Event.OpArgumentError /// Event.Starting /// Event.Busy /// Event.FlowReached /// Event.RegulValveTimeOut /// public Event Run() { log.DebugFormat("Op.Run() opState={0}", opState); if (StateMachine.Time > expireTime) return Event.RegulValveTimeOut; if (opState == OpState.Wait4FlowMsrmntAndSendRVMove) { if (flowMeter.DebugLevel == DebugMode.Simulate) return Event.FlowReached; if (!flowMeter.MsrmntAvailable) return Event.Starting; double flow1 = flowMeter.ReadFlow(); /// Set the targer flow/frequency range currentReqFlowLo = shrinkedTgtFlowLo; /// Default lower limit currentReqFlowHi = shrinkedTgtFlowHi; /// Default upper limit //if (flow1 >= targetFlowAve) currentReqFlowHi = targetFlowAve; /// Decrease upper limit //if (flow1 <= targetFlowAve) currentReqFlowLo = targetFlowAve; /// Increase lower limit if ((currentReqFlowLo >= currentReqFlowHi) || (currentReqFlowLo > maximalFlow) || (currentReqFlowHi <= 0)) { return Event.OpArgumentError; } if (StateMachine.Time > setFlowTime) { uniCB.SetFlow(false, regV.Idx1, flowMeter.NominalFreq * currentReqFlowLo / flowMeter.NominalFlow, flowMeter.NominalFreq * currentReqFlowHi / flowMeter.NominalFlow); log.InfoFormat("Run(): rv#={0} TARGET: flowLo={1} flowHi={2}", regV.Idx1, currentReqFlowLo, currentReqFlowHi); opState = OpState.SettingFlow; } return Event.Starting; } if (opState == OpState.ValveMoveToPosition1) { opState = OpState.ValveMoveToPosition2; return Event.Starting; } else if (opState == OpState.ValveMoveToPosition2) /// Move to position { /// Issues the appropriate ValveMove(...) command regV.MoveToPosition(false, targetPositionLo, targetPositionHi); opState = OpState.SettingPosition; return Event.Starting; } else if (opState == OpState.ValveMoveToPosition3) { opState = OpState.SettingPosition; return Event.Starting; } else if (opState == OpState.SettingPosition) { /// Repeated untill position is reached double rvPosition = regV.Position; if ((targetPositionLo <= rvPosition) && (rvPosition <= targetPositionHi)) { opState = OpState.Wait4FlowMsrmntAndSendRVMove; } return Event.Starting; } else if (opState == OpState.SettingFlow) { /// Regulation valve is setting flow to the required value /// double frequency = flowMeter.ReadFrequency(); double flow = flowMeter.ReadFlow(); if (msrdFlow != null && flow != 0) msrdFlow.Val = flow; if (currentReqFlowLo <= flow && flow <= currentReqFlowHi) { isFlowOkDuration++; /// Increment the flow within range duration if (isFlowOkDuration >= regV.FlowStableSec) { /// Flow is within range for sufficiently long time if (regV.StoredPositionReuse) { double rvPosition = regV.Position; /// Read the current position double storedPosition; if (regV.Dict.TryGetValue(targetFlowAve, out storedPosition)) { /// update the stored valve position StoreTargetPosition(targetFlowAve, (rvPosition + storedPosition) / 2.0); log.InfoFormat("Run(): rv#={0} reqFlow={1} pos={2}% stored={3} <--- Updating a stored position", regV.Idx1, targetFlowAve, rvPosition.ToString("F1"), storedPosition); } else { /// store the valve position StoreTargetPosition(targetFlowAve, rvPosition); log.InfoFormat("Run(): rv#={0} reqFlow={1} pos={2}% <--- Storing a new position", regV.Idx1, targetFlowAve, rvPosition.ToString("F1")); } } log.InfoFormat("flow = {0} m3/h (lo={1}, hi={2}, REACHED)", flow, currentReqFlowLo, currentReqFlowHi); opState = OpState.FlowReached; return Event.FlowReached; } else { log.InfoFormat("flow = {0} m3/h (lo={1}, hi={2}, FLOW_OK_TIMER={3}s)", flow, currentReqFlowLo, currentReqFlowHi, isFlowOkDuration); return Event.Busy; } } else { /// Flow is out of range isFlowOkDuration = 0; log.InfoFormat("flow = {0} m3/h (lo={1}, hi={2})", flow, currentReqFlowLo, currentReqFlowHi); return Event.Busy; } } else /// opState == OpState.FlowReached { log.InfoFormat("flow = {0} m3/h (lo={1}, hi={2}, REACHED)", flowMeter.ReadFlow(), currentReqFlowLo, currentReqFlowHi); return Event.FlowReached; } } /// Stop this operation public void Stop() { if (!leaveFlowControlRunning) { uniCB.StopFlowControl(false, regV.Idx1); /// Stop the flow measurement } opState = OpState.Idle; } } }