/// /// Copyright (c) 2013-2015 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, reqFlowLo, reqFlowHi, pidCoef); } const int CoaxValveNr = 7; /// Coax. valve has number 7 /// Set by the constructor readonly ControlBoardDev controlBoard; readonly Elde.RegulValve.RegulValve regulValve; readonly int regulValveNr; readonly int flowMeterNr; readonly float reqFlowLo; readonly float reqFlowHi; readonly float reqFlowAve; readonly float pidCoef; readonly int timeout; readonly bool leaveMeasurementRunning; readonly float nominalFlow; /// /// Internal states of this operation /// enum OpState { Idle = 0, ValveMoveToPosition1, /// Wait 1 takt ValveMoveToPosition2, /// This is actual move to position ValveMoveToPosition3, /// Wait 1 takt ValveMoveToPosition4, /// Verify SettingPosition, ValveMoveToFlow1, /// Wait 1 takt ValveMoveToFlow2, /// This is actual move to flow ValveMoveToFlow3, /// Wait 1 takt ValveMoveToFlow4, /// Verify SettingFlow, FlowReached, SendCommandAgain, } OpState opState; float targetPositionLo; float targetPositionHi; int expireTime; FloatBox 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, float requiredFlowLo, float requiredFlowHi, float pidCoef, FloatBox measuredFlow, int timeout, bool leaveMeasurementRunning) { if (controlBoard == null) throw new ArgumentNullException("ctrlBoard"); this.controlBoard = controlBoard; regulValve = _regulValve as Elde.RegulValve.RegulValve; if (regulValve == null) throw new ArgumentNullException("regValve is null or not Elde"); 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; this.reqFlowLo = requiredFlowLo; this.reqFlowHi = requiredFlowHi; this.reqFlowAve = (reqFlowLo + reqFlowHi) / 2.0f; this.pidCoef = pidCoef; this.measuredFlow = measuredFlow; 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, float requiredFlowLo, float requiredFlowHi, float pidCoef, FloatBox measuredFlow, int timeout) : this(controlBoard, regValve, flowMeter, requiredFlowLo, requiredFlowHi, pidCoef, measuredFlow, timeout, false) { } /// /// Set required water flow - No timeout. /// Events: FlowSet /// public SetFlowOp(ControlBoardDev controlBoard, IRegulValve regValve, IFlowMeter flowMeter, float requiredFlowLo, float requiredFlowHi, float pidCoef, FloatBox measuredFlow) : this(controlBoard, regValve, flowMeter, requiredFlowLo, pidCoef, requiredFlowHi, measuredFlow, 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(float 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(float avgReqFlow, float actPosition) { if (!regulValve.Dict.ContainsKey(reqFlowAve)) { regulValve.Dict.Add(new KeyValuePair(avgReqFlow, actPosition)); } return; } /// Start this operation public void Start() { flowWithinBoundsTime = 0; if (regulValve.RegulValveCfg.StoredPositionReuse && FetchTargetPosition(reqFlowAve, out targetPositionLo, out targetPositionHi)) { log.InfoFormat("Start(): rv#={0} flowMtr#={1} TARGET: flowLo={2} flowHi={3} FETCHED: posLo={4} posHi={5}", regulValveNr, flowMeterNr, reqFlowLo, reqFlowHi, targetPositionLo, targetPositionHi); opState = OpState.ValveMoveToPosition1; } else { log.InfoFormat("Start(): rv#={0} flowMtr#={1} TARGET: flowLo={2} flowHi={3}", regulValveNr, flowMeterNr, reqFlowLo, reqFlowHi); 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, controlBoard.Route, int.MaxValue, int.MaxValue, tm, 0.0f, (int)pidCoef, 0, sd); } /// Run this operation /// /// Event.None, Event.FlowReached, Event.RegulValveTimeOut, Event.OpArgumentError /// public Event Run() { float rvPosition = controlBoard.RValvePosition(regulValveNr); float flowMtrFreq = controlBoard.ReferenceFreq; float flow = flowMtrFreq * nominalFlow / 2000.0f; if (measuredFlow != null) measuredFlow.Val = flow; log.InfoFormat("Run(): rv#={0} pos={1}% freq={2} flow={3} opState={4}", regulValveNr, rvPosition.ToString("F1"), flowMtrFreq, flow, opState); if (opState == OpState.SendCommandAgain) { flowWithinBoundsTime = 0; if (regulValve.RegulValveCfg.StoredPositionReuse && FetchTargetPosition(reqFlowAve, out targetPositionLo, out targetPositionHi)) { log.InfoFormat("Start(): rv#={0} flowMtr#={1} TARGET: flowLo={2} flowHi={3} FETCHED: posLo={4} posHi={5}", regulValveNr, flowMeterNr, reqFlowLo, reqFlowHi, targetPositionLo, targetPositionHi); opState = OpState.ValveMoveToPosition1; } else { log.InfoFormat("Start(): rv#={0} flowMtr#={1} TARGET: flowLo={2} flowHi={3}", regulValveNr, flowMeterNr, reqFlowLo, reqFlowHi); 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, controlBoard.Route, int.MaxValue, int.MaxValue, tm, 0.0f, (int)pidCoef, 0, 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) { opState = OpState.ValveMoveToPosition4; return Event.None; } else if (opState == OpState.ValveMoveToPosition4) /// Verify { if (((ulong)controlBoard.StatusP & (ulong)StatusP.RefPulsesMsrmnt) == 0) { opState = OpState.SendCommandAgain; return Event.None; } else if (regulValveNr == CoaxValveNr && ((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); opState = OpState.ValveMoveToPosition3; return Event.None; } else if (regulValveNr < 6 && controlBoard.RegulValveState(regulValveNr) != RegulValveState.DacValueRegul) { /// Repeat appropriate ValveMove(...) command controlBoard.ValveMove(regulValveNr, RegulValveMode.TargetPosition, new float[2] { 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 (reqFlowLo >= reqFlowHi || reqFlowLo > nominalFlow || reqFlowHi <= 0) { return Event.OpArgumentError; } log.InfoFormat("Run(): rv#={0} flowMtr#={1} TARGET: flowLo={2} flowHi={3}", regulValveNr, flowMeterNr, reqFlowLo, reqFlowHi); float freqLo = 2000.0f * reqFlowLo / nominalFlow; float freqHi = 2000.0f * reqFlowHi / nominalFlow; controlBoard.ValveMove(regulValveNr, RegulValveMode.TargetFrequency, new float[2] { freqLo, freqHi }, regulValve.StableTime); opState = OpState.ValveMoveToFlow3; return Event.None; } else if (opState == OpState.ValveMoveToFlow3) { opState = OpState.ValveMoveToFlow4; return Event.None; } else if (opState == OpState.ValveMoveToFlow4) { if (((ulong)controlBoard.StatusP & (ulong)StatusP.RefPulsesMsrmnt) == 0) { opState = OpState.SendCommandAgain; return Event.None; } else if (regulValveNr == CoaxValveNr && ((ulong)controlBoard.StatusP & (ulong)StatusP.CoaxRegValveBusy) == 0) { /// Done once, issues an appropriate ValveMove(...) command float freqLo = 2000.0f * reqFlowLo / nominalFlow; float freqHi = 2000.0f * reqFlowHi / nominalFlow; controlBoard.ValveMove(regulValveNr, RegulValveMode.TargetFrequency, new float[2] { (freqLo + freqHi) / 2.0f, (freqLo + freqHi) / 2.0f }, regulValve.StableTime); opState = OpState.ValveMoveToFlow3; return Event.None; } else if (regulValveNr < 6 && controlBoard.RegulValveState(regulValveNr) != RegulValveState.PwOrFreqRegul) { /// Done once, issues an appropriate ValveMove(...) command float freqLo = 2000.0f * reqFlowLo / nominalFlow; float freqHi = 2000.0f * reqFlowHi / nominalFlow; controlBoard.ValveMove(regulValveNr, RegulValveMode.TargetFrequency, new float[2] { 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 ((reqFlowLo <= flow) && (flow <= reqFlowHi)) { 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, reqFlowLo, reqFlowHi); opState = OpState.FlowReached; return Event.FlowReached; } else { log.InfoFormat("flow = {0} m3/h (lo={1}, hi={2}, FLOW_OK_TIMER={3}s)", flow, reqFlowLo, reqFlowHi, flowWithinBoundsTime); return Event.None; } } else if (StateMachine.Time > expireTime) { return Event.RegulValveTimeOut; } else { log.InfoFormat("flow = {0} m3/h (lo={1}, hi={2})", flow, reqFlowLo, reqFlowHi); flowWithinBoundsTime = 0; return Event.None; } } else /// opState == OpState.FlowReached { log.InfoFormat("flow = {0} m3/h (lo={1}, hi={2}, REACHED)", flow, reqFlowLo, reqFlowHi); 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, controlBoard.Route, 50000, 50000, tm, 0.0f, 20, 0, sd); } } }