///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using log4net;
using TBF.Rig.ControlBoard.Uni;
using TBF.Rig.GenericDevices;
using TBF.Boxes;
namespace TBF.Rig.Uni.RegValveTandem
{
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})", regValve.Name, finalReqFlowLo, finalReqFlowHi);
}
/// Set by the constructor
readonly UniCB uniCB;
readonly RegValve.RegValve regValve;
readonly RegValve.RegValve fixedRV;
readonly double fixedPctLo;
readonly double fixedPctHi;
readonly int flowMeterNr;
readonly double finalReqFlowLo;
readonly double finalReqFlowHi;
readonly double reqFlowAve;
readonly int timeout;
readonly bool leaveFlowControlRunning;
readonly double nominalFlow;
readonly double maximalFlow;
/// Process values
enum OpState
{
Idle = 0,
IssueSetPositionCommand,
SettingPosition,
IssueSetPositionCommand_FixedRV,
SettingPosition_FixedRV,
IssueSetFlowCommand,
SettingFlow,
FlowReached,
}
OpState opState;
double currentReqFlowLo;
double currentReqFlowHi;
double targetPositionLo;
double 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
/// Regulation valve set to a fixed position
/// Lower limit of the fixed position of the fixed reg.v.
/// Upper limit of the fixed position of the fixed reg.v.
/// 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(TBF.Rig.ControlBoard.IControlBoard cb, IRegValve regV, IRegValve fxdRV, float fxdPctLo, float fxdPctHi,
IFlowMeter flowMeter, double requiredFlowLo, double requiredFlowHi, DoubleBox measuredFlow,
int timeout, bool leaveFlowControlRunning)
{
this.uniCB = cb as UniCB;
if (uniCB == null) throw new ArgumentNullException("ctrlBoard is not Uni");
regValve = regV as TBF.Rig.Uni.RegValve.RegValve;
if (regValve == null) throw new ArgumentNullException("regValve is null or not Uni");
fixedRV = fxdRV as TBF.Rig.Uni.RegValve.RegValve;
if (fixedRV == null) throw new ArgumentNullException("regValve is null or not Uni");
this.fixedPctLo = fxdPctLo;
this.fixedPctHi = fxdPctHi;
if (flowMeter is Uni.FlowMeter.FlowMeter)
{
this.flowMeterNr = (flowMeter as Uni.FlowMeter.FlowMeter).Idx1;
this.nominalFlow = flowMeter.NominalFlow;
}
else if (flowMeter is Uni.FlowMetersInParallel.FlowMeter)
{
this.flowMeterNr = 0;
this.nominalFlow = flowMeter.NominalFlow;
}
else
{
throw new ArgumentNullException("flowMeter is null or not Uni");
}
nominalFlow = flowMeter.NominalFlow;
maximalFlow = flowMeter.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.timeout = timeout;
this.leaveFlowControlRunning = leaveFlowControlRunning;
log.Debug(this.ToString());
}
///
/// Set required water flow - Do not leave the measurement running.
/// Events: FlowSet
///
public SetFlowOp(UniCB uniCB, IRegValve regValve, IRegValve fixedRV, float fixedPctLo, float fixedPctHi,
IFlowMeter flowMeter, double requiredFlowLo, double requiredFlowHi, DoubleBox measuredFlow, int timeout)
: this(uniCB, regValve, fixedRV, fixedPctLo, fixedPctHi, flowMeter, requiredFlowLo, requiredFlowHi, measuredFlow, timeout, false)
{
}
///
/// Set required water flow - No timeout.
/// Events: FlowSet
///
public SetFlowOp(UniCB uniCB, IRegValve regValve, IRegValve fixedRV, float fixedPctLo, float fixedPctHi,
IFlowMeter flowMeter, double requiredFlowLo, double requiredFlowHi, DoubleBox measuredFlow)
: this(uniCB, regValve, fixedRV, fixedPctLo, fixedPctHi, flowMeter, requiredFlowLo, 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(double avgReqFlow, out double positionLo, out double positionHi)
{
double targetPosition;
if (regValve.Dict.TryGetValue(avgReqFlow, out targetPosition))
{
positionLo = Math.Max(targetPosition * 0.95, 0.0);
positionHi = Math.Min(targetPosition * 1.05, 100.0);
return true;
}
else
{
positionLo = 0.0;
positionHi = 0.0;
return false;
}
}
void StoreTargetPosition(double avgReqFlow, double actPosition)
{
if (!regValve.Dict.ContainsKey(reqFlowAve))
{
regValve.Dict.Add(new KeyValuePair(avgReqFlow, actPosition));
}
return;
}
/// Start this operation
public void Start()
{
double flowMtrFreq = uniCB.RefFrequency;
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 (regValve.StoredPositionReuse && FetchTargetPosition(reqFlowAve, out targetPositionLo, out targetPositionHi))
{
log.InfoFormat("Start(): rv#={0} flowMtr#={1} TARGET: flowLo={2} flowHi={3} FETCHED: posLo={4} posHi={5}",
regValve.Idx1, flowMeterNr, currentReqFlowLo, currentReqFlowHi, targetPositionLo, targetPositionHi);
opState = OpState.IssueSetPositionCommand;
}
else
{
log.InfoFormat("Start(): rv#={0} flowMtr#={1} TARGET: flowLo={2} flowHi={3}",
regValve.Idx1, flowMeterNr, currentReqFlowLo, currentReqFlowHi);
opState = OpState.IssueSetPositionCommand_FixedRV;
}
expireTime = StateMachine.Time + timeout;
if (expireTime < 0) expireTime = int.MaxValue;
/// Start flow measurement
uniCB.MeasureFlow(false, flowMeterNr);
}
/// Run this operation
///
/// Event.None, Event.FlowReached, Event.RegulValveTimeOut, Event.OpArgumentError
///
public Event Run()
{
double rvPosition = regValve.Position;
double fixedRVPosition = fixedRV.Position;
double flowMtrFreq = uniCB.RefFrequency;
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} opState={4}",
regValve.Idx1, rvPosition.ToString("F1"), flowMtrFreq, flow, opState);
if (opState == OpState.IssueSetPositionCommand)
{
///
/// Done once, issues an appropriate ValveMove(...) command
///
uniCB.RegVlvMoveToPos(false, regValve.Idx1, targetPositionLo, targetPositionHi);
opState = OpState.SettingPosition;
return Event.None;
}
else if (opState == OpState.SettingPosition)
{
///
/// Repeated untill position is reached
///
if ((targetPositionLo <= rvPosition) && (rvPosition <= targetPositionHi))
{
opState = OpState.IssueSetPositionCommand_FixedRV;
}
return Event.None;
}
else if (opState == OpState.IssueSetPositionCommand_FixedRV)
{
///
/// Done once, issues an appropriate ValveMove(...) command
///
uniCB.RegVlvMoveToPos(false, fixedRV.Idx1, fixedPctLo, fixedPctHi);
opState = OpState.SettingPosition_FixedRV;
return Event.None;
}
else if (opState == OpState.SettingPosition_FixedRV)
{
///
/// Repeated untill position is reached
///
if ((fixedPctLo <= fixedRVPosition) && (fixedRVPosition <= fixedPctHi))
{
opState = OpState.IssueSetFlowCommand;
}
return Event.None;
}
else if (opState == OpState.IssueSetFlowCommand)
{
///
/// 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}",
regValve.Idx1, flowMeterNr, currentReqFlowLo, currentReqFlowHi);
double freqLo = 2000.0 * currentReqFlowLo / nominalFlow;
double freqHi = 2000.0 * currentReqFlowHi / nominalFlow;
uniCB.SetFlow(false, regValve.Idx1, freqLo, freqHi);
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++ >= regValve.FlowStableSec)
{
if (regValve.StoredPositionReuse)
{
double storedPosition;
if (regValve.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",
regValve.Idx1, 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",
regValve.Idx1, reqFlowAve, rvPosition.ToString("F1"));
}
}
opState = OpState.FlowReached;
return Event.FlowReached;
}
else
{
return Event.None;
}
}
else if (StateMachine.Time > expireTime)
{
return Event.RegulValveTimeOut;
}
else
{
flowWithinBoundsTime = 0;
return Event.None;
}
}
else /// opState == OpState.FlowReached
{
return Event.FlowReached;
}
}
/// Start this operation
public void Stop()
{
opState = OpState.Idle;
if (leaveFlowControlRunning) return;
uniCB.StopFlowControl(false, regValve.Idx1); /// Stop measurement
}
}
}