From 2aeaf42325eb0590f8d195d191db59d39de06dd1 Mon Sep 17 00:00:00 2001 From: Michal Buzik Date: Sun, 7 Sep 2025 22:18:35 +0200 Subject: [PATCH] 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. --- .../ChangeRegValvePositionOp.cs | 70 ++++ .../RegValveLowRegulTimeSaturation/Factory.cs | 25 ++ .../Handlers.cs | 36 ++ .../RegValve.cs | 243 +++++++++++ .../RegValveCfg.cs | 108 +++++ .../RegValveCfgCtrl.cs | 394 ++++++++++++++++++ .../SetFlowOp.cs | 340 +++++++++++++++ .../SetRegValvePositionOp.cs | 155 +++++++ 8 files changed, 1371 insertions(+) create mode 100644 TBF/Rig/Uni/RegValveLowRegulTimeSaturation/ChangeRegValvePositionOp.cs create mode 100644 TBF/Rig/Uni/RegValveLowRegulTimeSaturation/Factory.cs create mode 100644 TBF/Rig/Uni/RegValveLowRegulTimeSaturation/Handlers.cs create mode 100644 TBF/Rig/Uni/RegValveLowRegulTimeSaturation/RegValve.cs create mode 100644 TBF/Rig/Uni/RegValveLowRegulTimeSaturation/RegValveCfg.cs create mode 100644 TBF/Rig/Uni/RegValveLowRegulTimeSaturation/RegValveCfgCtrl.cs create mode 100644 TBF/Rig/Uni/RegValveLowRegulTimeSaturation/SetFlowOp.cs create mode 100644 TBF/Rig/Uni/RegValveLowRegulTimeSaturation/SetRegValvePositionOp.cs diff --git a/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/ChangeRegValvePositionOp.cs b/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/ChangeRegValvePositionOp.cs new file mode 100644 index 000000000..0a398fcde --- /dev/null +++ b/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/ChangeRegValvePositionOp.cs @@ -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; + + /// + /// Set required water flow. + /// Events: FlowSet, FlowTimeOut + /// + /// Control board device + /// Regulation valve component + /// Lower limit of the position to be achieved + /// Upper limit of the position to be achieved + /// Timeout in sec. for setting the flow + /// Only Elde.Valve flow are used, other flow on the lists are ignored + 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()); + } + + /// Start this operation + public void Start() + { + uniCB.RegVlvIncrMove(false, regulValveNr, timePulseSec, this.regV.RegulationMinStep); + } + + /// Run this operation + /// + /// Event.SetFlowDone + /// + public Event Run() + { + return Event.PositionReached; + } + + /// Stop this operation + public void Stop() + { + } + } +} diff --git a/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/Factory.cs b/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/Factory.cs new file mode 100644 index 000000000..52d05f2f8 --- /dev/null +++ b/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/Factory.cs @@ -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 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); + } + } +} diff --git a/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/Handlers.cs b/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/Handlers.cs new file mode 100644 index 000000000..1aa203913 --- /dev/null +++ b/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/Handlers.cs @@ -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() + { + } + + /// + /// Called from the state machine when a procedure is selected and UI needs to be updated. + /// + 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 AdcChangedHandler; + } +} diff --git a/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/RegValve.cs b/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/RegValve.cs new file mode 100644 index 000000000..d43935b5e --- /dev/null +++ b/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/RegValve.cs @@ -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 dict; + public IDictionary 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; + + + /// + /// Incremental movement of the regulation valve. + /// + /// Time in seconds, positive value opens the reg. valve + public void IncrementalMovement(bool isFromUI, double time) + { + UniCB.RegVlvIncrMove(isFromUI, Idx1, time, RegulationMinStep); + } + + /// + /// Move a regulation valve to specfied position + /// + /// Reg. valve ID + /// Reg. valve position (0 .. 1.0) + 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 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 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(); + + 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() { } + + + /// + /// Operation to set the water flow within tolerances + /// Events: Event.None, Event.FlowReached, Event.FlowTimeOut + /// + /// Flowmeter component + /// Lower limit of the required flow [m3/h] + /// Higher limit of the required flow [m3/h] + /// PID coefficient (float) + /// Measured flow [m3/h] + /// Timeout in [s] + /// SetFlowOp instance + 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); + } + + /// + /// Operation to set the water flow within tolerances. Leave the measurement running. + /// Events: Event.None, Event.FlowReached, Event.FlowTimeOut + /// + /// Flowmeter component + /// Lower limit of the required flow [m3/h] + /// Higher limit of the required flow [m3/h] + /// Measured flow [m3/h] + /// Timeout in [s] + /// SetFlowOp instance + 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); + } + + /// + /// Operation to set the regulation valve to a required position + /// Events: Event.None + /// + /// Lower limit of the required valve position in [%] + /// Higher limit of the required valve position in [%] + /// SetPositionOp instance + 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); + } + } +} diff --git a/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/RegValveCfg.cs b/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/RegValveCfg.cs new file mode 100644 index 000000000..9b24c1b3f --- /dev/null +++ b/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/RegValveCfg.cs @@ -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 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 GNodes { get; set; } + + /// + /// Stabilization time when setting the flow: 0=200ms, step 50ms, max. 1.5 sec. + /// + public int StableTime { get { return (StableTimeMs - 200) / 50; } } + + /// Private parameterless constructor invoked by all other (public) constructors + RegValveCfg() + { + GNodes = new List(); + } + + 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); + } + } +} diff --git a/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/RegValveCfgCtrl.cs b/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/RegValveCfgCtrl.cs new file mode 100644 index 000000000..98e37457a --- /dev/null +++ b/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/RegValveCfgCtrl.cs @@ -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 CmdResponseHandler; + + public void StartResponseHandler() + { + Handlers.AdcChangedHandler += delegate(object sndr, CmdResponseArgs args) + { + if (InvokeRequired) { Invoke(new EventHandler(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 + } +} diff --git a/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/SetFlowOp.cs b/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/SetFlowOp.cs new file mode 100644 index 000000000..8eb540601 --- /dev/null +++ b/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/SetFlowOp.cs @@ -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; + + + /// + /// 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; + } + } +} diff --git a/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/SetRegValvePositionOp.cs b/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/SetRegValvePositionOp.cs new file mode 100644 index 000000000..071a9c163 --- /dev/null +++ b/TBF/Rig/Uni/RegValveLowRegulTimeSaturation/SetRegValvePositionOp.cs @@ -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; + + /// + /// Set required water flow. + /// Events: FlowSet, FlowTimeOut + /// + /// Control board device + /// Regulation valve component + /// Lower limit of the position to be achieved + /// Upper limit of the position to be achieved + /// Timeout in sec. for setting the flow + /// Only Elde.Valve flow are used, other flow on the lists are ignored + 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()); + } + + /// + /// Set required water flow - no timeout. + /// Events: FlowSet + /// + public SetRegValvePositionOp(UniCB cBoard, RegValve regValve, float posLoPct, float posHiPct) + : this(cBoard, regValve, posLoPct, posHiPct, int.MaxValue) + { + } + + /// Start this operation + 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; + } + + /// + /// Run this operation + /// + /// + /// Event.None . . . . . . . busy adjusting position + /// Event.PositionReached . . position reached + /// Event.OpArgumentError . . invalid required position + /// + 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; + } + + /// Stop this operation + public void Stop() + { + } + } +}