/// /// 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}, PID={3})", regulValve.Name, requiredFlowLo, requiredFlowHi, pidCoef); } /// 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 float pidCoef; readonly int[] filters; readonly int shortPulses; 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, float pidCoef, int[] filters, int shortPulses, 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 this.pidCoef = pidCoef; this.filters = filters; this.shortPulses = shortPulses; 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, float pidCoef, int[] filters, int shortPulses, DoubleBox flowbox, bool reTryCmds, int timeout) : this(controlBoard, regValve, flowMeter, requiredFlowLo, requiredFlowHi, pidCoef, filters, shortPulses, flowbox, reTryCmds, timeout, false) { } /// /// Set required water flow - No timeout. /// Events: FlowSet /// public SetFlowOp(ControlBoardDev controlBoard, IRegulValve regValve, IFlowMeter flowMeter, double requiredFlowLo, double requiredFlowHi, float pidCoef, int[] filters, int shortPulses, DoubleBox flowbox, bool reTryCmds) : this(controlBoard, regValve, flowMeter, requiredFlowLo, requiredFlowHi, pidCoef, filters, shortPulses, 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; } TestMethods tm = 0; StopDevs sd = 0; // '_regulValve.RegulValveCfg.PidCoef' replaced by a casted operation parameter 'pidCoef' controlBoard.SendCommand(Command.Start, flowMeter.Idx1, controlBoard.Route, int.MaxValue, int.MaxValue, tm, filters, (int)pidCoef, shortPulses, sd); } /// Run this operation /// /// Event.None, Event.FlowReached, Event.RegulValveTimeOut, Event.OpArgumentError /// 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; } TestMethods tm = 0; StopDevs sd = 0; // '_regulValve.RegulValveCfg.PidCoef' replaced by a casted operation parameter 'pidCoef' controlBoard.SendCommand(Command.Start, flowMeter.Idx1, controlBoard.Route, int.MaxValue, int.MaxValue, tm, filters, (int)pidCoef, shortPulses, sd); return Event.None; } else if (opState == OpState.ValveMoveToPosition1) { opState = OpState.ValveMoveToPosition2; return Event.None; } 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.None; } else if (opState == OpState.ValveMoveToPosition3) { if (reTryCommands) { opState = OpState.CheckStatePosition; } else { opState = OpState.SettingPosition; } return Event.None; } else if (opState == OpState.CheckStatePosition) /// Verify { if (((ulong)controlBoard.StatusP & (ulong)StatusP.RefPulsesMsrmnt) == 0) { opState = OpState.SendCommandAgain; return Event.None; } 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.None; } else { opState = OpState.SettingPosition; return Event.None; } } else if (opState == OpState.SettingPosition) { /// Repeated untill position is reached if ((targetPositionLo <= rvPosition) && (rvPosition <= targetPositionHi)) { opState = OpState.ValveMoveToFlow1; } return Event.None; } else if (opState == OpState.ValveMoveToFlow1) { opState = OpState.ValveMoveToFlow2; return Event.None; } 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.None; } else if (opState == OpState.ValveMoveToFlow3) { if (reTryCommands) { opState = OpState.CheckStateFlow; } else { opState = OpState.SettingFlow; } return Event.None; } 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.None; } 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.None; } else { opState = OpState.SettingFlow; return Event.None; } } else if (opState == OpState.SettingFlow) { /// /// Repeated untill the required flow is reached /// if ((currentReqFlowLo <= flow) && (flow <= currentReqFlowHi)) { if (flowWithinBoundsTime++ >= regulValve.FlowStableSec) { 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.None; } } 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.None; } } 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() { opState = OpState.Idle; if (leaveMeasurementRunning) return; /// Stop measurement TestMethods tm = 0; StopDevs sd = StopDevs.RegValveRegulation; controlBoard.SendCommand(Command.Stop, flowMeter.Idx1, controlBoard.Route, 50000, 50000, tm, filters, (int)pidCoef, shortPulses, sd); } } }