///
/// 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;
}
}
}