///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using Common;
using log4net;
using Config.Entities;
using TBF.Rig.ControlBoard.Uni;
using TBF.Rig.GenericDevices;
using TBF.Boxes;
namespace TBF.Rig.Uni.RegValveLowRegulTimeSaturation
{
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})", regV.Name, shrinkedTgtFlowLo, shrinkedTgtFlowHi);
}
public const double RqrdFlowRangeRatio = 0.5;
///
/// Set by the constructor
///
readonly UniCB uniCB;
readonly RegValve regV;
readonly IFlowMeter flowMeter;
readonly double shrinkedTgtFlowLo;
readonly double shrinkedTgtFlowHi;
readonly double targetFlowAve;
readonly int timeout;
readonly int delay;
readonly bool leaveFlowControlRunning;
readonly double maximalFlow;
///
/// Internal state of this operation
///
enum OpState
{
Idle = 0,
ValveMoveToPosition1, /// Wait 1 takt
ValveMoveToPosition2, /// This is actual move to position
ValveMoveToPosition3, /// Wait 1 takt
SettingPosition,
Wait4FlowMsrmntAndSendRVMove, /// Wait until the flow measurement starts, then issue reg. valve move command
SettingFlow, /// Measure the flow and verify whether it is within limits sufficiently long time
FlowReached,
SendCommandAgain,
}
OpState opState;
double currentReqFlowLo;
double currentReqFlowHi;
double targetPositionLo;
double targetPositionHi;
int setFlowTime;
int expireTime;
DoubleBox msrdFlow;
int isFlowOkDuration;
///
/// 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]
/// DoubleBox for measured flow
/// Timeout for the flow setting in [s]
/// Flow setting starts after this delay 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(UniCB uniCB, IRegValve regValve, IFlowMeter flowMeter, double qFrom, double qTo, DoubleBox msrdFlow,
int timeout, int delay, bool leaveFlowControlRunning)
{
this.uniCB = uniCB;
if (this.uniCB == null) throw new ArgumentNullException("Control board is null or not Uni");
this.regV = regValve as RegValve;
if (this.regV == null) throw new ArgumentNullException(string.Format("{0} is not Uni.RegValve", regValve.Name));
this.flowMeter = flowMeter;
if (this.flowMeter == null) throw new ArgumentNullException("flowMeter");
maximalFlow = flowMeter.NominalFlow * 1.25;
double rqrdFlowLoBeforeCorr = qFrom - MeasurementCorrection.GetCorrection(qFrom, flowMeter.Corrections);
double rqrdFlowHiBeforeCorr = qTo - MeasurementCorrection.GetCorrection(qTo, flowMeter.Corrections);
targetFlowAve = (rqrdFlowLoBeforeCorr + rqrdFlowHiBeforeCorr) / 2;
shrinkedTgtFlowLo = (RqrdFlowRangeRatio * rqrdFlowLoBeforeCorr)
+ ((1 - RqrdFlowRangeRatio) * targetFlowAve); /// Move the lower limit 15% of the range up
shrinkedTgtFlowHi = (RqrdFlowRangeRatio * rqrdFlowHiBeforeCorr)
+ ((1 - RqrdFlowRangeRatio) * targetFlowAve); /// Move the upper limit 15% of the range down
this.msrdFlow = msrdFlow;
this.timeout = timeout;
this.delay = delay;
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, IFlowMeter flowMeter, double qFrom, double qTo, DoubleBox flowbox, int timeout, int delay)
: this(uniCB, regValve, flowMeter, qFrom, qTo, flowbox, timeout, delay, false)
{
}
///
/// Set required water flow - Do not leave the measurement running.
/// Events: FlowSet
///
public SetFlowOp(UniCB uniCB, IRegValve regValve, IFlowMeter flowMeter, double qFrom, double qTo, DoubleBox flowbox, int timeout)
: this(uniCB, regValve, flowMeter, qFrom, qTo, flowbox, timeout, 0, false)
{
}
///
/// Set required water flow - No timeout.
/// Events: FlowSet
///
public SetFlowOp(UniCB uniCB, IRegValve regValve, IFlowMeter flowMeter, double qFrom, double qTo, DoubleBox flowbox)
: this(uniCB, regValve, flowMeter, qFrom, qTo, flowbox, int.MaxValue, 0, 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 (regV.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;
positionHi = 0;
return false;
}
}
void StoreTargetPosition(double avgReqFlow, double actPosition)
{
if (!regV.Dict.ContainsKey(targetFlowAve))
{
regV.Dict.Add(new KeyValuePair(avgReqFlow, actPosition));
}
return;
}
/// Start this operation
public void Start()
{
log.InfoFormat("SetFlowOp:Start() rv#={0} flowMtr#={1} TARGET: flowLo={2} flowHi={3}",
regV.Idx1, flowMeter.Idx1, currentReqFlowLo, currentReqFlowHi);
/// Store the current time, etc.
setFlowTime = StateMachine.Time + delay;
expireTime = StateMachine.Time + timeout;
if (expireTime < 0) expireTime = int.MaxValue;
isFlowOkDuration = 0;
/// Start the flow measurement
uniCB.MeasureFlow(false, flowMeter.Idx1);
opState = OpState.Wait4FlowMsrmntAndSendRVMove;
}
///
/// Run this operation
///
///
/// Event.OpArgumentError
/// Event.Starting
/// Event.Busy
/// Event.FlowReached
/// Event.RegulValveTimeOut
///
public Event Run()
{
log.DebugFormat("Op.Run() opState={0}", opState);
if (StateMachine.Time > expireTime) return Event.RegulValveTimeOut;
if (opState == OpState.Wait4FlowMsrmntAndSendRVMove)
{
if (flowMeter.DebugLevel == DebugMode.Simulate) return Event.FlowReached;
if (!flowMeter.MsrmntAvailable) return Event.Starting;
double flow1 = flowMeter.ReadFlow();
/// Set the targer flow/frequency range
currentReqFlowLo = shrinkedTgtFlowLo; /// Default lower limit
currentReqFlowHi = shrinkedTgtFlowHi; /// Default upper limit
//if (flow1 >= targetFlowAve) currentReqFlowHi = targetFlowAve; /// Decrease upper limit
//if (flow1 <= targetFlowAve) currentReqFlowLo = targetFlowAve; /// Increase lower limit
if ((currentReqFlowLo >= currentReqFlowHi) || (currentReqFlowLo > maximalFlow) || (currentReqFlowHi <= 0))
{
return Event.OpArgumentError;
}
if (StateMachine.Time > setFlowTime)
{
uniCB.SetFlow(false, regV.Idx1, flowMeter.NominalFreq * currentReqFlowLo / flowMeter.NominalFlow,
flowMeter.NominalFreq * currentReqFlowHi / flowMeter.NominalFlow);
log.InfoFormat("Run(): rv#={0} TARGET: flowLo={1} flowHi={2}", regV.Idx1, currentReqFlowLo, currentReqFlowHi);
opState = OpState.SettingFlow;
}
return Event.Starting;
}
if (opState == OpState.ValveMoveToPosition1)
{
opState = OpState.ValveMoveToPosition2;
return Event.Starting;
}
else if (opState == OpState.ValveMoveToPosition2) /// Move to position
{
/// Issues the appropriate ValveMove(...) command
regV.MoveToPosition(false, targetPositionLo, targetPositionHi);
opState = OpState.SettingPosition;
return Event.Starting;
}
else if (opState == OpState.ValveMoveToPosition3)
{
opState = OpState.SettingPosition;
return Event.Starting;
}
else if (opState == OpState.SettingPosition)
{
/// Repeated untill position is reached
double rvPosition = regV.Position;
if ((targetPositionLo <= rvPosition) && (rvPosition <= targetPositionHi))
{
opState = OpState.Wait4FlowMsrmntAndSendRVMove;
}
return Event.Starting;
}
else if (opState == OpState.SettingFlow)
{
/// Regulation valve is setting flow to the required value
///
double frequency = flowMeter.ReadFrequency();
double flow = flowMeter.ReadFlow();
if (msrdFlow != null && flow != 0) msrdFlow.Val = flow;
if (currentReqFlowLo <= flow && flow <= currentReqFlowHi)
{
isFlowOkDuration++; /// Increment the flow within range duration
if (isFlowOkDuration >= regV.FlowStableSec)
{
/// Flow is within range for sufficiently long time
if (regV.StoredPositionReuse)
{
double rvPosition = regV.Position; /// Read the current position
double storedPosition;
if (regV.Dict.TryGetValue(targetFlowAve, out storedPosition))
{
/// update the stored valve position
StoreTargetPosition(targetFlowAve, (rvPosition + storedPosition) / 2.0);
log.InfoFormat("Run(): rv#={0} reqFlow={1} pos={2}% stored={3} <--- Updating a stored position",
regV.Idx1, targetFlowAve, rvPosition.ToString("F1"), storedPosition);
}
else
{
/// store the valve position
StoreTargetPosition(targetFlowAve, rvPosition);
log.InfoFormat("Run(): rv#={0} reqFlow={1} pos={2}% <--- Storing a new position",
regV.Idx1, targetFlowAve, 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, isFlowOkDuration);
return Event.Busy;
}
}
else
{
/// Flow is out of range
isFlowOkDuration = 0;
log.InfoFormat("flow = {0} m3/h (lo={1}, hi={2})", flow, currentReqFlowLo, currentReqFlowHi);
return Event.Busy;
}
}
else /// opState == OpState.FlowReached
{
log.InfoFormat("flow = {0} m3/h (lo={1}, hi={2}, REACHED)", flowMeter.ReadFlow(), currentReqFlowLo, currentReqFlowHi);
return Event.FlowReached;
}
}
/// Stop this operation
public void Stop()
{
if (!leaveFlowControlRunning)
{
uniCB.StopFlowControl(false, regV.Idx1); /// Stop the flow measurement
}
opState = OpState.Idle;
}
}
}