/// /// Copyright (c) 2013-2017 Sensus Metering Systems /// using System; using System.Collections.Generic; using log4net; using TBF.BenchControl.GenericDevices; using TBF.Boxes; namespace TBF.BenchControl.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); } /// Set by the constructor readonly ControlBoardDev controlBoard; readonly Elde.RegulValve.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, IRegulValve 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 Elde.RegulValve.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; this.reqFlowAve = (requiredFlowLo + requiredFlowHi) / 2; this.requiredFlowLo = (0.7 * requiredFlowLo) + (0.3 * this.reqFlowAve); /// Move the lower limit 15% of the range up this.requiredFlowHi = (0.7 * requiredFlowHi) + (0.3 * this.reqFlowAve); /// Move the upper limit 15% 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, IRegulValve 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, IRegulValve 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; } } }