/// /// Copyright (c) 2017-2018 Sensus Slovensko a.s. /// using System; using System.Collections.Generic; using log4net; using TBF.BenchControl.GenericDevices; using TBF.Boxes; namespace TBF.BenchControl.Elde.RegulValveCoax { 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, finalReqFlowLo, finalReqFlowHi); } /// Set by the constructor readonly ControlBoardDev controlBoard; readonly Elde.RegulValveCoax.RegulValve regulValve; readonly int regulValveNr; readonly int flowMeterNr; readonly double finalReqFlowLo; readonly double finalReqFlowHi; 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 expireTime; DoubleBox measuredFlow; 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 measuredFlow, bool reTryCmds, int timeout, bool leaveMeasurementRunning) { if (controlBoard == null) throw new ArgumentNullException("ctrlBoard"); this.controlBoard = controlBoard; regulValve = _regulValve as Elde.RegulValveCoax.RegulValve; if (regulValve == null) throw new ArgumentNullException(string.Format("{0} is not Elde.RegulValveCoax.", _regulValve.Name)); regulValveNr = this.regulValve.Idx1; if (flowMeter is Elde.FlowMeter.FlowMeter) { this.flowMeterNr = (flowMeter as Elde.FlowMeter.FlowMeter).Idx1; } else if (flowMeter is Elde.FlowMeterTwins.FlowMeter) { this.flowMeterNr = 0; } else { throw new ArgumentNullException("flowMeter is null or not Elde"); } nominalFlow = flowMeter.NominalFlow; maximalFlow = nominalFlow * 1.25; this.reqFlowAve = (requiredFlowLo + requiredFlowHi) / 2; this.finalReqFlowLo = (0.7 * requiredFlowLo) + (0.3 * this.reqFlowAve); /// Move the lower limit 15% of the range up this.finalReqFlowHi = (0.7 * requiredFlowHi) + (0.3 * this.reqFlowAve); /// Move the upper limit 15% of the range down this.measuredFlow = measuredFlow; 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 measuredFlow, bool reTryCmds, int timeout) : this(controlBoard, regValve, flowMeter, requiredFlowLo, requiredFlowHi, measuredFlow, reTryCmds, timeout, false) { } /// /// Set required water flow - No timeout. /// Events: FlowSet /// public SetFlowOp(ControlBoardDev controlBoard, IRegulValve regValve, IFlowMeter flowMeter, double requiredFlowLo, double requiredFlowHi, DoubleBox measuredFlow, bool reTryCmds) : this(controlBoard, regValve, flowMeter, requiredFlowLo, requiredFlowHi, measuredFlow, 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() { double flowMtrFreq = controlBoard.ReferenceFreq; currentReqFlowLo = reqFlowAve; currentReqFlowHi = reqFlowAve; /// if (flowMtrFreq != 0) { double flow = flowMtrFreq * nominalFlow / 2000.0; if (measuredFlow != null) measuredFlow.Val = flow; if (flow >= reqFlowAve) currentReqFlowLo = finalReqFlowLo; if (flow <= reqFlowAve) currentReqFlowHi = finalReqFlowHi; } else { currentReqFlowLo = finalReqFlowLo; /// Unexpected case when flowMtrFreq==0 currentReqFlowHi = finalReqFlowHi; } flowWithinBoundsTime = 0; if (regulValve.RegulValveCfg.StoredPositionReuse && FetchTargetPosition(reqFlowAve, out targetPositionLo, out targetPositionHi)) { log.WarnFormat("Start() rv#={0} flowMtr#={1} TARGET: flowLo={2} flowHi={3} FETCHED: posLo={4} posHi={5}", regulValveNr, flowMeterNr, currentReqFlowLo, currentReqFlowHi, targetPositionLo, targetPositionHi); opState = OpState.ValveMoveToPosition1; } else { log.WarnFormat("Start() rv#={0} flowMtr#={1} TARGET: flowLo={2} flowHi={3}", regulValveNr, flowMeterNr, currentReqFlowLo, currentReqFlowHi); opState = OpState.ValveMoveToFlow1; } expireTime = StateMachine.Time + timeout; if (expireTime < 0) expireTime = int.MaxValue; TestMethods tm = 0; StopDevs sd = 0; // '_regulValve.RegulValveCfg.PidCoef' replaced by a casted operation parameter 'pidCoef' controlBoard.SendCommand(Command.Start, flowMeterNr, int.MaxValue, int.MaxValue, tm, sd); } /// Run this operation /// /// Event.None, Event.FlowReached, Event.RegulValveTimeOut, Event.OpArgumentError /// public Event Run() { float rvPosition = controlBoard.RValvePosition(regulValveNr); double flowMtrFreq = controlBoard.ReferenceFreq; double flow = flowMtrFreq * nominalFlow / 2000.0; if (flowMtrFreq != 0) { if (measuredFlow != null) measuredFlow.Val = flow; if (flow >= reqFlowAve) currentReqFlowLo = finalReqFlowLo; if (flow <= reqFlowAve) currentReqFlowHi = finalReqFlowHi; } log.InfoFormat("Run() rv#={0} pos={1}% freq={2} flow={3} curFlLo={4} curFlHi={5} state={6}", regulValveNr, rvPosition.ToString("F1"), flowMtrFreq.ToString("F1"), flow.ToString("F3"), currentReqFlowLo.ToString("F3"), currentReqFlowHi.ToString("F3"), opState); 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, flowMeterNr, currentReqFlowLo.ToString("F3"), currentReqFlowHi.ToString("F3"), targetPositionLo, targetPositionHi); opState = OpState.ValveMoveToPosition1; } else { log.InfoFormat(" Run() rv#={0} flowMtr#={1} TARGET: flowLo={2} flowHi={3}", regulValveNr, flowMeterNr, currentReqFlowLo.ToString("F3"), currentReqFlowHi.ToString("F3")); opState = OpState.ValveMoveToFlow1; } expireTime = StateMachine.Time + timeout; if (expireTime < 0) expireTime = int.MaxValue; TestMethods tm = 0; StopDevs sd = 0; // '_regulValve.RegulValveCfg.PidCoef' replaced by a casted operation parameter 'pidCoef' controlBoard.SendCommand(Command.Start, flowMeterNr, int.MaxValue, int.MaxValue, tm, sd); log.DebugFormat(" return Event.None"); return Event.None; } else if (opState == OpState.ValveMoveToPosition1) { opState = OpState.ValveMoveToPosition2; log.DebugFormat(" return Event.None"); 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) / 2, (targetPositionLo + targetPositionHi) / 2 }, regulValve.StableTime); opState = OpState.SettingPosition; log.DebugFormat(" return Event.None"); return Event.None; } else if (opState == OpState.ValveMoveToPosition3) { if (reTryCommands) { opState = OpState.CheckStatePosition; } else { opState = OpState.SettingPosition; } log.DebugFormat(" return Event.None"); return Event.None; } else if (opState == OpState.CheckStatePosition) /// Verify { if (((ulong)controlBoard.StatusP & (ulong)StatusP.RefPulsesMsrmnt) == 0) { opState = OpState.SendCommandAgain; log.DebugFormat(" return Event.None"); return Event.None; } else if (((ulong)controlBoard.StatusP & (ulong)StatusP.CoaxRegValveBusy) == 0) { /// Repeat appropriate ValveMove(...) command controlBoard.ValveMove(regulValveNr, RegulValveMode.TargetPosition, new float[2] { (targetPositionLo + targetPositionHi) / 2, (targetPositionLo + targetPositionHi) / 2 }, regulValve.StableTime); log.InfoFormat(" Run() ValveMove({0}, Position, {1}, {2}, {3})", regulValveNr, (targetPositionLo + targetPositionHi) / 2, (targetPositionLo + targetPositionHi) / 2, regulValve.StableTime); opState = OpState.ValveMoveToPosition3; log.DebugFormat(" return Event.None"); return Event.None; } else { opState = OpState.SettingPosition; log.DebugFormat(" return Event.None"); return Event.None; } } else if (opState == OpState.SettingPosition) { /// Repeated untill position is reached if ((targetPositionLo <= rvPosition) && (rvPosition <= targetPositionHi)) { opState = OpState.ValveMoveToFlow1; } log.DebugFormat(" return Event.None"); return Event.None; } else if (opState == OpState.ValveMoveToFlow1) { opState = OpState.ValveMoveToFlow2; log.DebugFormat(" return Event.None"); 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)) { log.DebugFormat(" return Event.OpArgumentError"); return Event.OpArgumentError; } double freqLo = 2000.0 * currentReqFlowLo / nominalFlow; double freqHi = 2000.0 * currentReqFlowHi / nominalFlow; controlBoard.ValveMove(regulValveNr, RegulValveMode.TargetFrequency, new float[2] { (float)((freqLo + freqHi) / 2), (float)((freqLo + freqHi) / 2) }, regulValve.StableTime); log.WarnFormat(" Run() ValveMove({0}, Freq., {1}, {2}, {3}) flowMtr#={4} TARGET: flowLo={5} flowHi={6}", regulValveNr, (freqLo + freqHi) / 2, (freqLo + freqHi) / 2, regulValve.StableTime, flowMeterNr, currentReqFlowLo.ToString("F3"), currentReqFlowHi.ToString("F3")); opState = OpState.ValveMoveToFlow3; log.DebugFormat(" return Event.None"); return Event.None; } else if (opState == OpState.ValveMoveToFlow3) { if (reTryCommands) { opState = OpState.CheckStateFlow; } else { opState = OpState.SettingFlow; } log.DebugFormat(" return Event.None"); return Event.None; } else if (opState == OpState.CheckStateFlow) /// Verify the flow setting { if (((ulong)controlBoard.StatusP & (ulong)StatusP.CoaxRegValveBusy) == 0) { /// 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 + freqHi) / 2), (float)((freqLo + freqHi) / 2) }, regulValve.StableTime); log.WarnFormat(" Run() ValveMove({0}, Frequency, {1}, {2}, {3}) flowMtr#={4} TARGET: flowLo={5} flowHi={6}", regulValveNr, (freqLo + freqHi) / 2, (freqLo + freqHi) / 2, regulValve.StableTime, flowMeterNr, currentReqFlowLo.ToString("F3"), currentReqFlowHi.ToString("F3")); opState = OpState.ValveMoveToFlow3; log.DebugFormat(" return Event.None"); return Event.None; } else { opState = OpState.SettingFlow; log.DebugFormat(" return Event.None"); 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.ToString("F3"), currentReqFlowLo.ToString("F3"), currentReqFlowHi.ToString("F3")); opState = OpState.FlowReached; log.DebugFormat(" return Event.FlowReached"); return Event.FlowReached; } else { log.InfoFormat(" flow = {0} m3/h (lo={1}, hi={2}, FLOW_OK_TIMER={3}s)", flow.ToString("F3"), currentReqFlowLo, currentReqFlowHi, flowWithinBoundsTime); log.DebugFormat(" return Event.None"); return Event.None; } } else if (StateMachine.Time > expireTime) { log.DebugFormat(" return Event.RegulValveTimeOut"); return Event.RegulValveTimeOut; } else { log.InfoFormat(" flow = {0} m3/h (lo={1}, hi={2})", flow.ToString("F3"), currentReqFlowLo, currentReqFlowHi); flowWithinBoundsTime = 0; log.DebugFormat(" return Event.None"); return Event.None; } } else /// opState == OpState.FlowReached { log.InfoFormat(" flow = {0} m3/h (lo={1}, hi={2}, REACHED)", flow.ToString("F3"), currentReqFlowLo, currentReqFlowHi); log.DebugFormat(" return Event.FlowReached"); 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, flowMeterNr, 50000, 50000, tm, sd); log.WarnFormat("Stop() SendCommand(Stop, {0}, ...)", flowMeterNr); } } }