Patch applied - Add initial implementation for RegValveLowRegulTimeSaturation module

This commit introduces the `RegValveLowRegulTimeSaturation` module, including core components such as `RegValve`, `ChangeRegValvePositionOp`, `Factory`, `Handlers`, and configuration classes (`RegValveCfg`, `RegValveCfgCtrl`). It integrates `log4net` for logging and adheres to the established modular design architecture.
This commit is contained in:
Michal Buzik 2025-09-07 22:18:35 +02:00
parent bf4fb469da
commit 2aeaf42325
8 changed files with 1371 additions and 0 deletions

View File

@ -0,0 +1,70 @@
///
/// Copyright (c) 2021-2023 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using log4net;
using TBF.Rig.ControlBoard.Uni;
using TBF.Rig.GenericDevices;
namespace TBF.Rig.Uni.RegValveLowRegulTimeSaturation
{
public class ChangeRegValvePositionOp : IOperation
{
private static readonly ILog log = LogManager.GetLogger(typeof(ChangeRegValvePositionOp));
public override string ToString()
{
return string.Format("ChangeRegValvePositionOp({0},{1}s)", regV.Name, timePulseSec.ToString("F2"));
}
/// Set by the constructor
readonly UniCB uniCB;
readonly RegValve regV;
readonly int regulValveNr;
readonly double timePulseSec;
/// <summary>
/// Set required water flow.
/// Events: FlowSet, FlowTimeOut
/// </summary>
/// <param name="cb">Control board device</param>
/// <param name="rv">Regulation valve component</param>
/// <param name="posLoPct">Lower limit of the position to be achieved</param>
/// <param name="posHiPct">Upper limit of the position to be achieved</param>
/// <param name="timeout">Timeout in sec. for setting the flow</param>
/// <remarks>Only Elde.Valve flow are used, other flow on the lists are ignored</remarks>
public ChangeRegValvePositionOp(UniCB uniCB, RegValve regV, double timePulseSec)
{
this.uniCB = uniCB;
if (this.uniCB == null) throw new ArgumentNullException("ctrlBoard");
this.regV = regV as RegValve;
if (this.regV == null) throw new ArgumentNullException("regValve is null or not Uni");
regulValveNr = this.regV.Idx1;
this.timePulseSec = timePulseSec;
log.Debug(this.ToString());
}
/// <summary>Start this operation</summary>
public void Start()
{
uniCB.RegVlvIncrMove(false, regulValveNr, timePulseSec, this.regV.RegulationMinStep);
}
/// <summary>Run this operation</summary>
/// <returns>
/// Event.SetFlowDone
/// </returns>
public Event Run()
{
return Event.PositionReached;
}
/// <summary>Stop this operation</summary>
public void Stop()
{
}
}
}

View File

@ -0,0 +1,25 @@
///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
using System.Collections.Generic;
using TBF.Rig.Generic;
namespace TBF.Rig.Uni.RegValveLowRegulTimeSaturation
{
public class Factory : IComponentFactory
{
public string ClassName { get { return GetType().Namespace.Substring(12); } }
public override string ToString() { return ClassName; }
public IComponent DummyComponent() { return new RegValve(); }
public IComponent GetComponent(IComponentCfg cfg, IList<IComponent> components) { return new RegValve(cfg, components); }
public IComponentCfg DefaultConfig() { return new RegValveCfg("VR", this); }
public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component)
{
return ComponentCfgBase.CreateFromDbEntity(RegValveCfg.Serializer, component, this);
}
}
}

View File

@ -0,0 +1,36 @@
///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
using System;
using log4net;
namespace TBF.Rig.Uni.RegValveLowRegulTimeSaturation
{
static class Handlers
{
static readonly ILog log = LogManager.GetLogger(typeof(Handlers));
static Handlers()
{
}
/// <summary>
/// Called from the state machine when a procedure is selected and UI needs to be updated.
/// </summary>
public static void OnAdcChanged(object sender, CmdResponseArgs data)
{
if (AdcChangedHandler == null)
return;
try
{
AdcChangedHandler(sender, data);
}
catch (Exception e)
{
log.Error("AdcChangedHandler(...) failed", e);
}
}
public static event EventHandler<CmdResponseArgs> AdcChangedHandler;
}
}

View File

@ -0,0 +1,243 @@
///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using log4net;
using SchematicDrawing;
using TBF.Rig.ControlBoard.Uni;
using TBF.Boxes;
using SharedComponents;
namespace TBF.Rig.Uni.RegValveLowRegulTimeSaturation
{
public class RegValve : ComponentBase, Generic.IDevice, GenericDevices.IRegValve, IDrawingItCmpntWithSetpoint
{
private static readonly ILog log = LogManager.GetLogger(typeof(RegValve));
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
public readonly RegValveCfg regValveCfg;
public IDrawingItem DrawingItem { get { return regValveCfg as IDrawingItem; } }
IDictionary<double, double> dict;
public IDictionary<double, double> Dict { get { return dict; } }
public readonly UniCB UniCB;
public ValveCategory Category { get { return regValveCfg.Category; } }
public int Idx1 { get { return regValveCfg.Idx1; } } /// 1 .. 8
public int DacValueClosed { get { return regValveCfg.AdcValueClosed; } } /// 0 .. 1023
public int DacValueOpen { get { return regValveCfg.AdcValueOpen; } } /// 0 .. 1023
public int StableTime { get { return regValveCfg.StableTime; } } /// 0=200ms, step=50ms, max. 1500ms (max.26)
public int FlowStableSec { get { return regValveCfg.FlowStableSec; } } /// 0 .. 60 sec
public bool StoredPositionReuse { get { return regValveCfg.StoredPositionReuse; } }
public int RegulationMinStep { get { return regValveCfg.RegulationMinStep; } } /// low regulation saturation time ...0=0, 1=50, 2=100, 3=150, 4=200, 5=250, 6=300 [ms]
///
public bool IsCoax { get { return false; } }
public RegValveState RegValveState
{
get
{
switch (UniCB.Data.RegVStatus[Idx1 - 1])
{
default:
case 0: return ControlBoard.Uni.RegValveState.Idle;
case 2: return ControlBoard.Uni.RegValveState.PwOrFreqRegul;
case 3: return ControlBoard.Uni.RegValveState.DacValueRegul;
}
}
}
/// Position 0.0 .. 100.0 in %
public double Position
{
get
{
int denominator = DacValueOpen - DacValueClosed;
if (denominator == 0) denominator = 1;
return 100.0 * Convert.ToDouble(Math.Max(0, Math.Min(denominator, (int)UniCB.AnalogInput(adcChannel) - DacValueClosed)))
/ Convert.ToDouble(denominator);
}
}
///
public double SetpointVal
{
get { return Position; }
set { TargetRegValvePosition = value; }
}
///
public void IncreaseSetpoint()
{
IncrementalMovement(true, 0.5);
}
///
public void DecreaseSetpoint()
{
IncrementalMovement(true, -0.5);
}
int adcChannel;
public double TargetRegValvePosition;
/// <summary>
/// Incremental movement of the regulation valve.
/// </summary>
/// <param name="time">Time in seconds, positive value opens the reg. valve</param>
public void IncrementalMovement(bool isFromUI, double time)
{
UniCB.RegVlvIncrMove(isFromUI, Idx1, time, RegulationMinStep);
}
/// <summary>
/// Move a regulation valve to specfied position
/// </summary>
/// <param name="rvId">Reg. valve ID</param>
/// <param name="position">Reg. valve position (0 .. 1.0)</param>
public void MoveToPosition(bool isFromUI, double posPctLo, double posPctHi = -1)
{
int adcDiff = Math.Abs(regValveCfg.AdcValueOpen - regValveCfg.AdcValueClosed);
int adcLower = Math.Min(regValveCfg.AdcValueOpen, regValveCfg.AdcValueClosed);
int targetAdcValLo = Math.Max(0, Convert.ToInt32(Math.Round((posPctLo * adcDiff / 100.0) + adcLower)));
int targetAdcValHi = (posPctHi < 0) ? -1 : Math.Min(1023, Convert.ToInt32(Math.Round((posPctHi * adcDiff / 100.0) + adcLower)));
UniCB.RegVlvMoveToPos(isFromUI, Idx1, targetAdcValLo, targetAdcValHi, RegulationMinStep);
}
#region Configuration Change Handling
public static void OnCfgChange(object sender, CfgChangeArgs args)
{
if (CfgChangeHandler == null) return;
try { CfgChangeHandler(sender, args); }
catch (Exception e) { log.Error("CfgChangeHandler(...) failed", e); }
}
public static event EventHandler<CfgChangeArgs> CfgChangeHandler;
public override void StartChangeHandler()
{
CfgChangeHandler += delegate(object sender, CfgChangeArgs args)
{
RegValveCfg tmpcfg = args.Cfg as RegValveCfg;
if (tmpcfg != null && tmpcfg.Name.Equals(Name))
{
if (args.Command == CfgChangeCmd.CfgChange)
{
regValveCfg.StableTimeMs = tmpcfg.StableTimeMs;
regValveCfg.FlowStableSec = tmpcfg.FlowStableSec;
regValveCfg.RegulationMinStep = tmpcfg.RegulationMinStep;
}
else if (args.Command == CfgChangeCmd.RVOpenStep)
{
IncrementalMovement(true, 0.5); /// true = command is comming from the UI
}
else if (args.Command == CfgChangeCmd.RVCloseStep)
{
IncrementalMovement(true, -0.5); /// true = command is comming from the UI
}
else if (args.Command == CfgChangeCmd.GetAdc1 || args.Command == CfgChangeCmd.GetAdc2)
{
short adcValue = UniCB.AnalogInput(adcChannel);
RegValveCfgCtrl.OnCmdResponse(this, new CmdResponseArgs(args.Command, Idx1, adcValue));
}
}
};
}
#endregion Configuration Change Handling
public RegValve() { }
public RegValve(Generic.IComponentCfg cfg, IList<Generic.IComponent> components)
: base(cfg)
{
regValveCfg = cfg as RegValveCfg;
this.UniCB = TbfComponents.FindComponent(cfg.ParentName, components) as UniCB;
if (this.UniCB == null) throw new Exception(string.Format("Cannot find {0} (a parent of {1})", cfg.ParentName, Name));
}
public override void Initialize()
{
adcChannel = regValveCfg.Idx1 - 1;
this.dict = new Dictionary<double, double>();
TBF.UiBridge.Bridge.SetpointChangeHandler += delegate(object sndr, TBF.UiBridge.SetpointChangeArgs args)
{
if (args.Name == Name)
{
if (args.Increase) IncreaseSetpoint(); else DecreaseSetpoint();
}
};
log.FatalFormat("{0} initialized: {1}", Name, this);
}
///
/// IDevice interface implementation
///
public void RunDeviceBefore()
{
Int16 adcValue = UniCB.AnalogInput(adcChannel);
Handlers.OnAdcChanged(this, new CmdResponseArgs(CfgChangeCmd.GetAdc, Idx1, adcValue));
}
public void RunDeviceAfter() { }
public void StopDevice() { }
public void StopDevice2() { }
/// <summary>
/// Operation to set the water flow within tolerances
/// Events: Event.None, Event.FlowReached, Event.FlowTimeOut
/// </summary>
/// <param name="flowMeter">Flowmeter component</param>
/// <param name="requiredFlowLo">Lower limit of the required flow [m3/h]</param>
/// <param name="requiredFlowHi">Higher limit of the required flow [m3/h]</param>
/// <param name="pidCoef">PID coefficient (float)</param>
/// <param name="measuredFlow">Measured flow [m3/h]</param>
/// <param name="timeout">Timeout in [s]</param>
/// <returns>SetFlowOp instance</returns>
public IOperation SetFlowOp(GenericDevices.IFlowMeter flowMeter, double requiredFlowLo, double requiredFlowHi, DoubleBox measuredFlow, int timeout, int delay)
{
return new SetFlowOp(UniCB, this, flowMeter, requiredFlowLo, requiredFlowHi, measuredFlow, timeout, delay);
}
/// <summary>
/// Operation to set the water flow within tolerances. Leave the measurement running.
/// Events: Event.None, Event.FlowReached, Event.FlowTimeOut
/// </summary>
/// <param name="flowMeter">Flowmeter component</param>
/// <param name="requiredFlowLo">Lower limit of the required flow [m3/h]</param>
/// <param name="requiredFlowHi">Higher limit of the required flow [m3/h]</param>
/// <param name="measuredFlow">Measured flow [m3/h]</param>
/// <param name="timeout">Timeout in [s]</param>
/// <returns>SetFlowOp instance</returns>
public IOperation SetFlowAndMeasureOp(GenericDevices.IFlowMeter flowMeter, double requiredFlowLo, double requiredFlowHi,
DoubleBox measuredFlow, int timeout, float filterConstant)
{
return new SetFlowOp(UniCB, this, flowMeter, requiredFlowLo, requiredFlowHi, measuredFlow, timeout, 0, true);
}
/// <summary>
/// Operation to set the regulation valve to a required position
/// Events: Event.None
/// </summary>
/// <param name="flowLo">Lower limit of the required valve position in [%]</param>
/// <param name="flowLo">Higher limit of the required valve position in [%]</param>
/// <returns>SetPositionOp instance</returns>
public IOperation SetRegValvePositionOp(double pctLo, double pctHi, int timeoutMs)
{
return new SetRegValvePositionOp(UniCB, this, pctLo, pctHi, timeoutMs);
}
public IOperation ChangeRegValvePositionOp(double timePulseSec)
{
return new ChangeRegValvePositionOp(UniCB, this, timePulseSec);
}
}
}

View File

@ -0,0 +1,108 @@
///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using Config.Entities;
using SchematicDrawing;
using TBF.Rig.Generic;
namespace TBF.Rig.Uni.RegValveLowRegulTimeSaturation
{
public class RegValveCfg : ComponentCfgBase, IChildComponentCfg, IDrawingItemWithSetpoint
{
public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(RegValveCfg) })[0];
public override XmlSerializer GetSerializer() { return Serializer; }
public IComponentCfgCtrl GetControl(IList<Component> cmpntEntities)
{
return new RegValveCfgCtrl();
}
///
/// Serialized parameters
///
public int Idx1; /// 1..8
public ValveCategory Category; /// Feeding, Output or All
public int AdcValueClosed; /// 0..1023
public int AdcValueOpen; /// 0..1023
public int StableTimeMs; /// 200..1500 ms, step is 50 ms
public int FlowStableSec; /// 0..60 sec
public bool StoredPositionReuse; /// true = store and re-use previous regulation valve positions
public int RegulationMinStep { get; set; } /// low regulation saturation time ...0=0, 1=50, 2=100, 3=150, 4=200, 5=250, 6=300 [ms]
public string SetpFormat { get; set; }
public Common.Unit SetpUnit { get; set; }
/// Schematic drawing info
public Shape Shape { get; set; }
public int X { get; set; }
public int Y { get; set; }
public Sz Sz { get; set; }
public Orient Orient { get; set; }
public bool Flip { get; set; }
public int LblX { get; set; }
public int LblY { get; set; }
public Orient LblOrient { get; set; }
public int SetpX { get; set; }
public int SetpY { get; set; }
public Orient SetpOrient { get; set; }
[XmlIgnore]
public IList<GNode> GNodes { get; set; }
/// <summary>
/// Stabilization time when setting the flow: 0=200ms, step 50ms, max. 1.5 sec.
/// </summary>
public int StableTime { get { return (StableTimeMs - 200) / 50; } }
/// Private parameterless constructor invoked by all other (public) constructors
RegValveCfg()
{
GNodes = new List<GNode>();
}
public RegValveCfg(string name, IComponentFactory factory)
: this()
{
Shape = Shape.RegV;
Sz = Sz.M;
SetpFormat = "{0:F0} %";
SetpUnit = Common.Unit.Pct;
Name = name;
Factory = factory;
ParentName = "UniCB";
Category = ValveCategory.Output;
Idx1 = 1;
AdcValueClosed = 100;
AdcValueOpen = 500;
StableTimeMs = 500;
FlowStableSec = 5;
StoredPositionReuse = false;
RegulationMinStep = 0;
LblX = 38;
SetpX = 38;
SetpY = 22;
}
public string ToString(int i)
{
return string.Format("Name={0} ({1}), Idx1={2}, Cat.={3}, ADC-Closed={4}, ADC-Open={5}, StabTm={6}ms, FlowStable={7}s, PosReuse={8}",
Name,
(string.IsNullOrEmpty(ParentName) ? "-" : ParentName),
Idx1,
Category,
AdcValueClosed,
AdcValueOpen,
StableTimeMs,
FlowStableSec,
StoredPositionReuse,
RegulationMinStep);
}
}
}

View File

@ -0,0 +1,394 @@
///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
using System;
using System.Linq;
using System.Windows.Forms;
using log4net;
using Common;
using Config.Entities;
using TBF.Rig.Generic;
using TBF.UI.Bench.Components;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
namespace TBF.Rig.Uni.RegValveLowRegulTimeSaturation
{
public partial class RegValveCfgCtrl : UserControl, IComponentCfgCtrl
{
static readonly ILog log = LogManager.GetLogger(typeof(RegValveCfgCtrl));
ComponentParametersDlg parent;
public bool ShowMore { get { return true; } }
RegValveCfg config;
public IComponentCfg Config
{
get { return config as IComponentCfg; }
set
{
config = value as RegValveCfg;
Redraw();
}
}
public RegValveCfgCtrl()
{
InitializeComponent();
}
private void RegulationValveCfgCtrl_Load(object sender, EventArgs e)
{
parent = ParentForm as ComponentParametersDlg;
if (parent == null) return;
if (parent.CmpntEntities != null)
{
foreach (var c in parent.CmpntEntities.Where(x => x.ClassName == "ControlBoard.Uni"))
{
parentNameComboBox.Items.Add(c.Name);
}
}
categoryComboBox.Items.Add(ValveCategory.Feeding.ToString());
categoryComboBox.Items.Add(ValveCategory.Output.ToString());
if (config != null)
{
adc1 = config.AdcValueClosed;
pct1 = 0;
adc2 = config.AdcValueOpen;
pct2 = 100;
}
for (Common.Unit units = 0; units < global::Common.Unit.Count; units++)
{
if (global::Common.Units.IsQuantity(units, Quantity.Error))
{
unitsComboBox.Items.Add(units.ToString());
}
}
regulMinStepsComboBox.Items.Add("0");
regulMinStepsComboBox.Items.Add("50");
regulMinStepsComboBox.Items.Add("100");
regulMinStepsComboBox.Items.Add("150");
regulMinStepsComboBox.Items.Add("200");
regulMinStepsComboBox.Items.Add("250");
regulMinStepsComboBox.Items.Add("300");
Redraw();
StartResponseHandler();
}
public void Closing()
{
StopResponseHandler();
}
void Redraw()
{
if (config == null) return; /// Control was not loaded, settings were not changed
classNameLabel.Text = config.Factory.ClassName;
nameTextBox.Text = config.Name;
parentNameComboBox.Text = string.IsNullOrEmpty(config.ParentName) ? "---" : config.ParentName;
categoryComboBox.Text = config.Category.ToString();
positionTextBox.Text = config.Idx1.ToString();
adcClosedTextBox.Text = config.AdcValueClosed.ToString();
adcOpenTextBox.Text = config.AdcValueOpen.ToString();
stableTimeTextBox.Text = config.StableTimeMs.ToString();
flowStableSecTextBox.Text = config.FlowStableSec.ToString();
storedPosReuseCheckBox.Checked = config.StoredPositionReuse;
formatTextBox.Text = config.SetpFormat;
unitsComboBox.Text = config.SetpUnit.ToString();
regulMinStepsComboBox.Text = (config.RegulationMinStep * 50).ToString();
adcTextBox1.Text = adc1.ToString();
adcTextBox2.Text = adc2.ToString();
pctTextBox1.Text = pct1.ToString();
pctTextBox2.Text = pct2.ToString();
}
public void Unlock()
{
nameTextBox.Enabled = true;
parentNameComboBox.Enabled = true;
categoryComboBox.Enabled = true;
positionTextBox.Enabled = true;
adcClosedTextBox.Enabled = true;
adcOpenTextBox.Enabled = true;
stableTimeTextBox.Enabled = true;
flowStableSecTextBox.Enabled = true;
storedPosReuseCheckBox.Enabled = true;
formatTextBox.Enabled = true;
unitsComboBox.Enabled = true;
regulMinStepsComboBox.Enabled = true;
}
public CfgUpdateFlags VerifyCfg(ref string message)
{
CfgUpdateFlags flags = CfgUpdateFlags.None;
int dummy;
if (!parentNameComboBox.Items.Contains(parentNameComboBox.Text))
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Invalid 'Parent Name'";
}
if (!categoryComboBox.Text.Equals(ValveCategory.Feeding.ToString()) &&
!categoryComboBox.Text.Equals(ValveCategory.Output.ToString()))
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Invalid 'Category'";
}
if (!int.TryParse(positionTextBox.Text, out dummy) || dummy < 1 || dummy > 8)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "'Position' should be between 1 and 8";
}
if (!int.TryParse(adcClosedTextBox.Text, out dummy) || dummy < 0 || dummy > 1023)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "'ADC when Closed' should be between 0 and 1023";
}
if (!int.TryParse(adcOpenTextBox.Text, out dummy) || dummy < 0 || dummy > 1023)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "'ADC when Open' should be between 0 and 1023";
}
if (!int.TryParse(stableTimeTextBox.Text, out dummy) || dummy < 200 || dummy > 1500 || (dummy % 50) != 0)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "'Stable time' should be between 200 and 1500 ms in 50 ms steps";
}
if (!int.TryParse(flowStableSecTextBox.Text, out dummy) || dummy < 0 || dummy > 60)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "'Flow stable' should be between 0 and 60 sec";
}
if (!unitsComboBox.Items.Contains(unitsComboBox.Text))
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Invalid 'Units'";
}
return flags;
}
public CfgUpdateFlags UpdateCfg()
{
CfgUpdateFlags flags = CfgUpdateFlags.None;
if (config == null) return CfgUpdateFlags.Error; /// Control was not loaded, settings were not changed
///
if (!config.Name.Equals(nameTextBox.Text))
{
config.Name = nameTextBox.Text;
flags |= CfgUpdateFlags.RestartRqrd;
}
string parentname = parentNameComboBox.Text.Equals("---") ? string.Empty : parentNameComboBox.Text;
if (!config.ParentName.Equals(parentname))
{
config.ParentName = parentname;
flags |= CfgUpdateFlags.RestartRqrd;
}
ValveCategory newCategory;
if (categoryComboBox.Text.Equals(ValveCategory.Feeding.ToString())) newCategory = ValveCategory.Feeding;
else if (categoryComboBox.Text.Equals(ValveCategory.Output.ToString())) newCategory = ValveCategory.Output;
else newCategory = ValveCategory.None;
if (newCategory != config.Category)
{
config.Category = newCategory;
flags |= CfgUpdateFlags.RestartRqrd;
}
int tmp = int.Parse(positionTextBox.Text);
if (config.Idx1 != tmp)
{
config.Idx1 = tmp;
flags |= CfgUpdateFlags.RestartRqrd;
}
tmp = int.Parse(adcClosedTextBox.Text);
if (config.AdcValueClosed != tmp)
{
config.AdcValueClosed = tmp;
flags |= CfgUpdateFlags.RestartRqrd;
}
tmp = int.Parse(adcOpenTextBox.Text);
if (config.AdcValueOpen != tmp)
{
config.AdcValueOpen = tmp;
flags |= CfgUpdateFlags.RestartRqrd;
}
tmp = int.Parse(stableTimeTextBox.Text);
if (config.StableTimeMs != tmp)
{
config.StableTimeMs = tmp;
flags |= CfgUpdateFlags.AnyChange;
}
tmp = int.Parse(flowStableSecTextBox.Text);
if (config.FlowStableSec != tmp)
{
config.FlowStableSec = tmp;
flags |= CfgUpdateFlags.AnyChange;
}
tmp = regulMinStepsComboBox.SelectedIndex;
if (config.RegulationMinStep != tmp)
{
config.RegulationMinStep = tmp;
flags |= CfgUpdateFlags.AnyChange;
}
bool btmp = storedPosReuseCheckBox.Checked;
if (config.StoredPositionReuse != btmp)
{
config.StoredPositionReuse = btmp;
flags |= CfgUpdateFlags.RestartRqrd;
}
if ((flags & CfgUpdateFlags.AnyChange) != 0)
{
RegValve.OnCfgChange(this, new CfgChangeArgs(CfgChangeCmd.CfgChange, config));
}
config.SetpFormat = formatTextBox.Text;
config.SetpUnit = global::Common.Unit.None;
for (Common.Unit units = 0; units < global::Common.Unit.Count; units++)
{
if (unitsComboBox.Text == units.ToString())
{
config.SetpUnit = units;
break;
}
}
return flags;
}
int adc1;
int pct1;
int adc2;
int pct2;
private void openButton_Click(object sender, EventArgs e)
{
RegValve.OnCfgChange(this, new CfgChangeArgs(CfgChangeCmd.RVOpenStep, config));
}
private void closeButton_Click(object sender, EventArgs e)
{
RegValve.OnCfgChange(this, new CfgChangeArgs(CfgChangeCmd.RVCloseStep, config));
}
//
private void getButton1_Click(object sender, EventArgs e)
{
RegValve.OnCfgChange(this, new CfgChangeArgs(CfgChangeCmd.GetAdc1, config));
}
private void getButton2_Click(object sender, EventArgs e)
{
RegValve.OnCfgChange(this, new CfgChangeArgs(CfgChangeCmd.GetAdc2, config));
}
private void setButton_Click(object sender, EventArgs e)
{
int tmpPct1;
int tmpPct2;
if ((int.TryParse(pctTextBox1.Text, out tmpPct1) && (tmpPct1 >= 0) && (tmpPct1 <= 100)) &&
(int.TryParse(pctTextBox2.Text, out tmpPct2) && (tmpPct2 >= 0) && (tmpPct2 <= 100)))
{
pct1 = tmpPct1;
pct2 = tmpPct2;
UpdateValveOpenClose();
}
}
void UpdateValveOpenClose()
{
if ((adc1 == adc2) || (pct1 == pct2)) return;
double factor = (float)(adc2 - adc1) / (float)(pct2 - pct1);
int adcClosed = (int)((float)(0 - pct1) * factor + (float)adc1 + 0.5f);
int adcOpen = (int)((float)(100 - pct1) * factor + (float)adc1 + 0.5f);
if ((adcClosed >= 0) && (adcOpen >= 0))
{
adcClosedTextBox.Text = adcClosed.ToString();
adcOpenTextBox.Text = adcOpen.ToString();
}
}
private void regulMinStepsComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
if (regulMinStepsComboBox.SelectedItem != null)
{
//config.RegulationMinStep = regulMinStepsComboBox.SelectedIndex;
}
}
#region Configuration Change Handling
public static void OnCmdResponse(object sender, CmdResponseArgs args)
{
if (CmdResponseHandler == null) return;
try { CmdResponseHandler(sender, args); }
catch (Exception e) { log.Error("CmdResponseHandler(...) failed", e); }
}
public static event EventHandler<CmdResponseArgs> CmdResponseHandler;
public void StartResponseHandler()
{
Handlers.AdcChangedHandler += delegate(object sndr, CmdResponseArgs args)
{
if (InvokeRequired) { Invoke(new EventHandler<CmdResponseArgs>(OnAdcChanged), sndr, args); }
else OnAdcChanged(sndr, args);
};
CmdResponseHandler += delegate(object sender, CmdResponseArgs args)
{
if (args.Command == CfgChangeCmd.GetAdc1)
{
adc1 = args.Response;
adcTextBox1.Text = adc1.ToString();
}
else if (args.Command == CfgChangeCmd.GetAdc2)
{
adc2 = args.Response;
adcTextBox2.Text = adc2.ToString();
}
};
}
public void StopResponseHandler()
{
CmdResponseHandler = null;
}
void OnAdcChanged(object sender, CmdResponseArgs data)
{
if (data.Command == CfgChangeCmd.GetAdc && data.Id == config.Idx1)
{
adcTextBox.Text = data.Response.ToString();
}
}
#endregion Configuration Change Handling
}
}

View File

@ -0,0 +1,340 @@
///
/// 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;
/// <summary>
/// Set required water flow. Conditionally leave the measurement running.
/// Events: FlowSet, FlowTimeOut
/// </summary>
/// <param name="uniCB">Control board device</param>
/// <param name="regValve">Regulation valve component</param>
/// <param name="flowMeter">Flowmeter component</param>
/// <param name="qFrom">Lower limit of the flow to be achieved in [m3/h]</param>
/// <param name="qTo">Upper limit of the flow to be achieved in [m3/h]</param>
/// <param name="msrdFlow">DoubleBox for measured flow</param>
/// <param name="timeout">Timeout for the flow setting in [s]</param>
/// <param name="delay">Flow setting starts after this delay in [s]</param>
/// <param name="leaveFlowControlRunning">true = Leave the measurement running after op. stop</param>
/// <remarks>Only Elde.Valve flow are used, other flow on the lists are ignored</remarks>
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());
}
/// <summary>
/// Set required water flow - Do not leave the measurement running.
/// Events: FlowSet
/// </summary>
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)
{
}
/// <summary>
/// Set required water flow - Do not leave the measurement running.
/// Events: FlowSet
/// </summary>
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)
{
}
/// <summary>
/// Set required water flow - No timeout.
/// Events: FlowSet
/// </summary>
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)
{
}
/// <summary>
/// Fetch target position limits from the dictionary or return false
/// </summary>
/// <param name="avgReqFlow">Average of the flow targer range (input)</param>
/// <param name="positionLo">Target valve position low limit (output)</param>
/// <param name="positionHi">Target valve position high limit (output)</param>
/// <returns>true when positions for the target flow are stored in the memory, otherwise return false</returns>
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<double, double>(avgReqFlow, actPosition));
}
return;
}
/// <summary>Start this operation</summary>
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;
}
/// <summary>
/// Run this operation
/// </summary>
/// <returns>
/// Event.OpArgumentError
/// Event.Starting
/// Event.Busy
/// Event.FlowReached
/// Event.RegulValveTimeOut
/// </returns>
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;
}
}
/// <summary>Stop this operation</summary>
public void Stop()
{
if (!leaveFlowControlRunning)
{
uniCB.StopFlowControl(false, regV.Idx1); /// Stop the flow measurement
}
opState = OpState.Idle;
}
}
}

View File

@ -0,0 +1,155 @@
///
/// Copyright (c) 2021-2022 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using log4net;
using TBF.Rig.ControlBoard.Uni;
using TBF.Rig.GenericDevices;
namespace TBF.Rig.Uni.RegValveLowRegulTimeSaturation
{
public class SetRegValvePositionOp : IOperation
{
private static readonly ILog log = LogManager.GetLogger(typeof(SetRegValvePositionOp));
public override string ToString()
{
return string.Format("SetRegValvePositionOp({0},{1},{2})", regV.Name, posLoPct, posHiPct);
}
///
/// Internal states of this operation
///
enum OpState
{
Idle = 0,
MoveToPosition, /// Send commend to move to position
CheckState, /// Check RV state by reading statos word
MovingToPosition, /// Command passed OK, RV is moving to a position
}
OpState opState;
/// Set by the constructor
readonly UniCB uniCB;
readonly RegValve regV;
readonly int regValveNr;
readonly double posLoPct;
readonly double posHiPct;
readonly int timeout;
/// Process values
int expireTime;
bool positionReached;
/// <summary>
/// Set required water flow.
/// Events: FlowSet, FlowTimeOut
/// </summary>
/// <param name="uniCB">Control board device</param>
/// <param name="_regulValve">Regulation valve component</param>
/// <param name="posLoPct">Lower limit of the position to be achieved</param>
/// <param name="posHiPct">Upper limit of the position to be achieved</param>
/// <param name="timeout">Timeout in sec. for setting the flow</param>
/// <remarks>Only Elde.Valve flow are used, other flow on the lists are ignored</remarks>
public SetRegValvePositionOp(UniCB uniCB, RegValve regV, double posLoPct, double posHiPct, int timeout)
{
this.uniCB = uniCB;
if (this.uniCB == null) throw new ArgumentNullException("ctrlBoard");
this.regV = regV as RegValve;
if (this.regV == null) throw new ArgumentNullException("regValve is null or not Uni");
this.regValveNr = this.regV.Idx1;
this.posLoPct = posLoPct;
this.posHiPct = posHiPct;
this.timeout = timeout;
log.Debug(this.ToString());
}
/// <summary>
/// Set required water flow - no timeout.
/// Events: FlowSet
/// </summary>
public SetRegValvePositionOp(UniCB cBoard, RegValve regValve, float posLoPct, float posHiPct)
: this(cBoard, regValve, posLoPct, posHiPct, int.MaxValue)
{
}
/// <summary>Start this operation</summary>
public void Start()
{
positionReached = false;
expireTime = StateMachine.Time + timeout;
log.InfoFormat("Start(): {0} reqPosLo={1}%, reqPosHi={2}%", regV.Name, posLoPct.ToString("F1"), posHiPct.ToString("F1"));
opState = OpState.MoveToPosition;
}
/// <summary>
/// Run this operation
/// </summary>
/// <returns>
/// Event.None . . . . . . . busy adjusting position
/// Event.PositionReached . . position reached
/// Event.OpArgumentError . . invalid required position
/// </returns>
public Event Run()
{
if (opState == OpState.MoveToPosition)
{
if (posLoPct >= posHiPct || posLoPct > 100.0 || posHiPct < 0)
{
return Event.OpArgumentError;
}
regV.MoveToPosition(false, posLoPct, posHiPct);
opState = OpState.CheckState;
log.WarnFormat("Run(): RV#={0}, state={1}, MoveToPosition(false, {2:D1}, {3:D1}) issued", regValveNr, opState, posLoPct, posHiPct);
return Event.None;
}
if (positionReached) return Event.PositionReached;
double positionPct = regV.Position;
log.WarnFormat("Run(): RV#={0}, state={1}, pos={2}%", regValveNr, opState, positionPct.ToString("F1"));
if (opState == OpState.CheckState)
{
if (posLoPct <= positionPct && positionPct <= posHiPct)
{
positionReached = true;
return Event.PositionReached;
}
else if (regV.RegValveState != RegValveState.DacValueRegul)
{
opState = OpState.MoveToPosition; /// Resend move command again in the next run
return Event.None;
}
else
{
opState = OpState.MovingToPosition;
return Event.None;
}
}
else if (StateMachine.Time > expireTime)
{
return Event.RegulValveTimeOut;
}
else if (opState == OpState.MovingToPosition)
{
if (posLoPct <= positionPct && positionPct <= posHiPct)
{
positionReached = true;
return Event.PositionReached;
}
return Event.None;
}
return Event.None;
}
/// <summary>Stop this operation</summary>
public void Stop()
{
}
}
}