/// /// Copyright (c) 2013-2021 Sensus Slovensko a.s. /// using System; using System.Collections.Generic; using log4net; using Config.Entities; using TBF.Rig.GenericDevices; using TBF.Boxes; namespace TBF.Rig.Elde.RegulValve { 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})", regulValve.Name, requiredFlowLo, requiredFlowHi); } public const double RqrdFlowRangeRatio = 0.5; /// Set by the constructor readonly ControlBoardDev controlBoard; readonly RegulValve regulValve; readonly int regulValveNr; readonly IFlowMeter flowMeter; readonly double requiredFlowLo; readonly double requiredFlowHi; readonly double reqFlowAve; readonly int timeout; readonly bool leaveMeasurementRunning; readonly bool reTryCommands; readonly double nominalFlow; readonly double maximalFlow; /// /// Internal states of this operation /// enum OpState { Idle = 0, ValveMoveToPosition1, /// Wait 1 takt ValveMoveToPosition2, /// This is actual move to position ValveMoveToPosition3, /// Wait 1 takt CheckStatePosition, /// Verify SettingPosition, ValveMoveToFlow1, /// Wait 1 takt ValveMoveToFlow2, /// This is actual move to flow ValveMoveToFlow3, /// Wait 1 takt CheckStateFlow, /// Verify SettingFlow, FlowReached, SendCommandAgain, } OpState opState; double currentReqFlowLo; double currentReqFlowHi; float targetPositionLo; float targetPositionHi; int startTime; int expireTime; DoubleBox flowBox; int flowWithinBoundsTime; /// /// 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] /// PID coefficient (float) /// Timeout for the flow setting 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(ControlBoardDev controlBoard, IRegValve regulValve, IFlowMeter flowMeter, double requiredFlowLo, double requiredFlowHi, DoubleBox flowBox, bool reTryCmds, int timeout, bool leaveMeasurementRunning) { if (controlBoard == null) throw new ArgumentNullException("ctrlBoard"); this.controlBoard = controlBoard; this.regulValve = regulValve as RegulValve; if (this.regulValve == null) throw new ArgumentNullException("regValve is null or not Elde"); regulValveNr = this.regulValve.Idx1; if (flowMeter == null) throw new ArgumentNullException("flowMeter"); this.flowMeter = flowMeter; nominalFlow = flowMeter.NominalFlow; maximalFlow = nominalFlow * 1.25; double rdrdFlowLoBeforeCorr = requiredFlowLo - MeasurementCorrection.GetCorrection(requiredFlowLo, flowMeter.Corrections); double rdrdFlowHiBeforeCorr = requiredFlowHi - MeasurementCorrection.GetCorrection(requiredFlowHi, flowMeter.Corrections); this.reqFlowAve = (rdrdFlowLoBeforeCorr + rdrdFlowHiBeforeCorr) / 2; this.requiredFlowLo = (RqrdFlowRangeRatio * rdrdFlowLoBeforeCorr) + ((1 - RqrdFlowRangeRatio) * this.reqFlowAve); /// Move the lower limit a bit of the range up this.requiredFlowHi = (RqrdFlowRangeRatio * rdrdFlowHiBeforeCorr) + ((1 - RqrdFlowRangeRatio) * this.reqFlowAve); /// Move the upper limit a bit of the range down if (flowBox == null) throw new ArgumentNullException("flowBox"); this.flowBox = flowBox; this.reTryCommands = reTryCmds; this.timeout = timeout; this.leaveMeasurementRunning = leaveMeasurementRunning; log.Debug(this.ToString()); } /// /// Set required water flow - Do not leave the measurement running. /// Events: FlowSet /// public SetFlowOp(ControlBoardDev controlBoard, IRegValve regValve, IFlowMeter flowMeter, double requiredFlowLo, double requiredFlowHi, DoubleBox flowbox, bool reTryCmds, int timeout) : this(controlBoard, regValve, flowMeter, requiredFlowLo, requiredFlowHi, flowbox, reTryCmds, timeout, false) { } /// /// Set required water flow - No timeout. /// Events: FlowSet /// public SetFlowOp(ControlBoardDev controlBoard, IRegValve regValve, IFlowMeter flowMeter, double requiredFlowLo, double requiredFlowHi, DoubleBox flowbox, bool reTryCmds) : this(controlBoard, regValve, flowMeter, requiredFlowLo, requiredFlowHi, flowbox, reTryCmds, int.MaxValue, 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 float positionLo, out float positionHi) { float targetPosition; if (regulValve.Dict.TryGetValue(avgReqFlow, out targetPosition)) { positionLo = Math.Max(targetPosition * 0.95f, 0.0f); positionHi = Math.Min(targetPosition * 1.05f, 100.0f); return true; } else { positionLo = 0.0f; positionHi = 0.0f; return false; } } void StoreTargetPosition(double avgReqFlow, float actPosition) { if (!regulValve.Dict.ContainsKey(reqFlowAve)) { regulValve.Dict.Add(new KeyValuePair(avgReqFlow, actPosition)); } return; } /// Start this operation public void Start() { startTime = StateMachine.Time; expireTime = StateMachine.Time + timeout; if (expireTime < 0) expireTime = int.MaxValue; double flowMtrFreq = flowMeter.ReadFrequency(); double flow = flowMeter.ReadFlow(); currentReqFlowLo = requiredFlowLo; /// Default lower limit currentReqFlowHi = requiredFlowHi; /// Default upper limit //if (flow != 0) //{ // flowBox.Val = flow; // if (flow >= reqFlowAve) currentReqFlowHi = reqFlowAve; /// Decrease upper limit // if (flow <= reqFlowAve) currentReqFlowLo = reqFlowAve; /// Increase lower limit //} flowWithinBoundsTime = 0; if (regulValve.RegulValveCfg.StoredPositionReuse && FetchTargetPosition(reqFlowAve, out targetPositionLo, out targetPositionHi)) { log.InfoFormat("SetFlowOp:Start() rv#={0} flowMtr#={1} TARGET: flowLo={2} flowHi={3} FETCHED: posLo={4} posHi={5}", regulValveNr, flowMeter.Idx1, currentReqFlowLo, currentReqFlowHi, targetPositionLo, targetPositionHi); opState = OpState.ValveMoveToPosition1; } else { log.InfoFormat("SetFlowOp:Start() rv#={0} flowMtr#={1} TARGET: flowLo={2} flowHi={3}", regulValveNr, flowMeter.Idx1, currentReqFlowLo, currentReqFlowHi); opState = OpState.ValveMoveToFlow1; } /// Start flow measurement controlBoard.SendCommand(Command.Start, flowMeter.Idx1, int.MaxValue, int.MaxValue, 0, 0); /// TestMethods=0, StopDevs=0 } /// Run this operation /// /// Event.OpArgumentError /// Event.Starting /// Event.Busy /// Event.FlowReached /// Event.RegulValveTimeOut /// public Event Run() { float rvPosition = controlBoard.RValvePosition(regulValveNr); double flowMtrFreq = flowMeter.ReadFrequency(); double flow = flowMeter.ReadFlow(); if (flow != 0) { flowBox.Val = flow; } log.InfoFormat("SetFlowOp:Run(T={0}) rv#={1} pos={2}% freq={3} flow={4} opState={5}", StateMachine.Time - startTime, regulValveNr, rvPosition.ToString("F1"), flowMtrFreq, flow, opState); if (StateMachine.Time > expireTime) { return Event.RegulValveTimeOut; } else if (opState == OpState.SendCommandAgain) { flowWithinBoundsTime = 0; if (regulValve.RegulValveCfg.StoredPositionReuse && FetchTargetPosition(reqFlowAve, out targetPositionLo, out targetPositionHi)) { log.InfoFormat("Run(): rv#={0} flowMtr#={1} TARGET: flowLo={2} flowHi={3} FETCHED: posLo={4} posHi={5}", regulValveNr, flowMeter.Idx1, currentReqFlowLo, currentReqFlowHi, targetPositionLo, targetPositionHi); opState = OpState.ValveMoveToPosition1; } else { log.InfoFormat("Run(): rv#={0} flowMtr#={1} TARGET: flowLo={2} flowHi={3}", regulValveNr, flowMeter.Idx1, currentReqFlowLo, currentReqFlowHi); opState = OpState.ValveMoveToFlow1; } /// Start flow measurement controlBoard.SendCommand(Command.Start, flowMeter.Idx1, int.MaxValue, int.MaxValue, 0, 0); /// TestMethods=0, StopDevs=0 return Event.Starting; } else if (opState == OpState.ValveMoveToPosition1) { opState = OpState.ValveMoveToPosition2; return Event.Starting; } else if (opState == OpState.ValveMoveToPosition2) /// Move to position { /// Issues the appropriate ValveMove(...) command controlBoard.ValveMove(regulValveNr, RegulValveMode.TargetPosition, new float[2] { targetPositionLo, targetPositionHi }, regulValve.StableTime); opState = OpState.SettingPosition; return Event.Starting; } else if (opState == OpState.ValveMoveToPosition3) { if (reTryCommands) { opState = OpState.CheckStatePosition; } else { opState = OpState.SettingPosition; } return Event.Starting; } else if (opState == OpState.CheckStatePosition) /// Verify { if (((ulong)controlBoard.StatusP & (ulong)StatusP.RefPulsesMsrmnt) == 0) { opState = OpState.SendCommandAgain; return Event.Starting; } else if (controlBoard.RegulValveState(regulValveNr) != RegulValveState.DacValueRegul) { /// Repeat appropriate ValveMove(...) command controlBoard.ValveMove(regulValveNr, RegulValveMode.TargetPosition, new float[2] { targetPositionLo, targetPositionHi }, regulValve.StableTime); log.InfoFormat("SetFlowOp: ValveMove({0}, Position, {1}, {2}, {3})", regulValveNr, targetPositionLo, targetPositionHi, regulValve.StableTime); opState = OpState.ValveMoveToPosition3; return Event.Starting; } else { opState = OpState.SettingPosition; return Event.Starting; } } else if (opState == OpState.SettingPosition) { /// Repeated untill position is reached if ((targetPositionLo <= rvPosition) && (rvPosition <= targetPositionHi)) { opState = OpState.ValveMoveToFlow1; } return Event.Starting; } else if (opState == OpState.ValveMoveToFlow1) { opState = OpState.ValveMoveToFlow2; return Event.Starting; } else if (opState == OpState.ValveMoveToFlow2) /// Move to flow { /// /// Done once, issues an appropriate ValveMove(...) command /// if ((currentReqFlowLo >= currentReqFlowHi) || (currentReqFlowLo > maximalFlow) || (currentReqFlowHi <= 0)) { return Event.OpArgumentError; } log.InfoFormat("Run(): rv#={0} flowMtr#={1} TARGET: flowLo={2} flowHi={3}", regulValveNr, flowMeter.Idx1, currentReqFlowLo, currentReqFlowHi); double freqLo = 2000.0 * currentReqFlowLo / nominalFlow; double freqHi = 2000.0 * currentReqFlowHi / nominalFlow; controlBoard.ValveMove(regulValveNr, RegulValveMode.TargetFrequency, new float[2] { (float)freqLo, (float)freqHi }, regulValve.StableTime); opState = OpState.ValveMoveToFlow3; return Event.Starting; } else if (opState == OpState.ValveMoveToFlow3) { if (reTryCommands) { opState = OpState.CheckStateFlow; } else { opState = OpState.SettingFlow; } return Event.Starting; } else if (opState == OpState.CheckStateFlow) /// Verify the flow setting { if ( ((ulong)controlBoard.StatusP & (ulong)StatusP.RefPulsesMsrmnt) == 0 || (controlBoard.RegulValveState(regulValveNr) & RegulValveState.PwOrFreqRegul) != RegulValveState.PwOrFreqRegul) { opState = OpState.SendCommandAgain; return Event.Starting; } else if (controlBoard.RegulValveState(regulValveNr) != RegulValveState.PwOrFreqRegul) { /// Done once, issues an appropriate ValveMove(...) command double freqLo = 2000.0 * currentReqFlowLo / nominalFlow; double freqHi = 2000.0 * currentReqFlowHi / nominalFlow; controlBoard.ValveMove(regulValveNr, RegulValveMode.TargetFrequency, new float[2] { (float)freqLo, (float)freqHi }, regulValve.StableTime); log.InfoFormat("SetFlowOp: ValveMove({0}, Frequency, {1}, {2}, {3})", regulValveNr, freqLo, freqHi, regulValve.StableTime); opState = OpState.ValveMoveToFlow3; return Event.Starting; } else { opState = OpState.SettingFlow; return Event.Busy; } } else if (opState == OpState.SettingFlow) { /// Regulation valve is setting flow to the required value if ((currentReqFlowLo <= flow) && (flow <= currentReqFlowHi)) { /// Flow is within range if (flowWithinBoundsTime++ >= regulValve.FlowStableSec) { /// Flow is within range for sufficiently long time if (regulValve.RegulValveCfg.StoredPositionReuse) { float storedPosition; if (regulValve.Dict.TryGetValue(reqFlowAve, out storedPosition)) { /// update the stored valve position StoreTargetPosition(reqFlowAve, (rvPosition + storedPosition) / 2.0f); log.InfoFormat("Run(): rv#={0} reqFlow={1} pos={2}% stored={3} <--- Updating a stored position", regulValveNr, reqFlowAve, rvPosition.ToString("F1"), storedPosition); } else { /// store the valve position StoreTargetPosition(reqFlowAve, rvPosition); log.InfoFormat("Run(): rv#={0} reqFlow={1} pos={2}% <--- Storing a new position", regulValveNr, reqFlowAve, 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, flowWithinBoundsTime); return Event.Busy; } } else if (StateMachine.Time > expireTime) { return Event.RegulValveTimeOut; } else { log.InfoFormat("flow = {0} m3/h (lo={1}, hi={2})", flow, currentReqFlowLo, currentReqFlowHi); flowWithinBoundsTime = 0; return Event.Busy; } } else /// opState == OpState.FlowReached { log.InfoFormat("flow = {0} m3/h (lo={1}, hi={2}, REACHED)", flow, currentReqFlowLo, currentReqFlowHi); return Event.FlowReached; } } /// Start this operation public void Stop() { if (!leaveMeasurementRunning) { /// Stop measurement controlBoard.SendCommand(Command.Stop, flowMeter.Idx1, 50000, 50000, 0, StopDevs.RegValveRegulation); /// TestMethods=0 } opState = OpState.Idle; } } }