diff --git a/Installer/Files/config.ini b/Installer/Files/config.ini index 28ec8cb..8fa0152 100644 --- a/Installer/Files/config.ini +++ b/Installer/Files/config.ini @@ -1,6 +1,8 @@ [plc] port = COM1 +sensor_delay_open = 0 ; seconds +sensor_delay_close = 2 ; seconds [update] url = https://elwig.at/files/pamhagen-sysctrl/latest diff --git a/PamhagenSysCtrl/Helpers/Config.cs b/PamhagenSysCtrl/Helpers/Config.cs index 8764a99..1a62160 100644 --- a/PamhagenSysCtrl/Helpers/Config.cs +++ b/PamhagenSysCtrl/Helpers/Config.cs @@ -9,6 +9,8 @@ namespace PamhagenSysCtrl.Helpers { private readonly string FileName; public string PlcPort = "COM1"; + public int PlcSensorDelayOpen = 0; + public int PlcSensorDelayClose = 0; public string? UpdateUrl; public bool UpdateAuto; @@ -24,6 +26,8 @@ namespace PamhagenSysCtrl.Helpers { try { var config = new ConfigurationBuilder().AddIniFile(FileName).Build(); PlcPort = config["plc:port"] ?? "COM1"; + PlcSensorDelayOpen = int.Parse(config["plc:sensor_delay_open"] ?? "0"); + PlcSensorDelayClose = int.Parse(config["plc:sensor_delay_close"] ?? "0"); UpdateUrl = config["update:url"]; UpdateAuto = TrueValues.Contains(config["update:auto"]?.ToLower()); var xbe24 = config.GetSection("xbe24"); diff --git a/PamhagenSysCtrl/Helpers/PamhagenGraph.cs b/PamhagenSysCtrl/Helpers/PamhagenGraph.cs index a611fee..d5851df 100644 --- a/PamhagenSysCtrl/Helpers/PamhagenGraph.cs +++ b/PamhagenSysCtrl/Helpers/PamhagenGraph.cs @@ -488,7 +488,7 @@ namespace PamhagenSysCtrl.Helpers { } protected ControlledValve CreateValve(int n, double x, double y, int o) { - return new(n, x, y, o, () => App.Plant?.State.Valve_IsOpen(n) ?? false, (v) => App.Plant?.ManualControlMode ?? false ? App.Plant.State.Valve_WantOpen(n) : SelectedPaths.Any(p => p.Contains(v))); + return new(n, x, y, o, () => App.Plant?.State.Valve_OpenState(n), (v) => App.Plant?.ManualControlMode ?? false ? App.Plant.State.Valve_WantOpen(n) : SelectedPaths.Any(p => p.Contains(v))); } protected static Tank CreateTank(int n, double x, double y, int capacity) { diff --git a/PamhagenSysCtrl/Helpers/PamhagenPlant.cs b/PamhagenSysCtrl/Helpers/PamhagenPlant.cs index 1c3d24f..60c616c 100644 --- a/PamhagenSysCtrl/Helpers/PamhagenPlant.cs +++ b/PamhagenSysCtrl/Helpers/PamhagenPlant.cs @@ -97,7 +97,7 @@ namespace PamhagenSysCtrl.Helpers { PlcThread = null; PamhagenPlc? tmp = null; try { - tmp = new PamhagenPlc(PlcComPort); + tmp = new PamhagenPlc(PlcComPort, App.Config.PlcSensorDelayOpen * 1000, App.Config.PlcSensorDelayClose * 1000); await tmp.Initialize(); Plc = tmp; PlcConnectionError = null; diff --git a/PamhagenSysCtrl/Helpers/Pipeline/ControlledValve.cs b/PamhagenSysCtrl/Helpers/Pipeline/ControlledValve.cs index e57ecf6..e3710b9 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/ControlledValve.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/ControlledValve.cs @@ -13,7 +13,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { protected Func CallbackWantOpen; - public ControlledValve(int nr, double x, double y, int orientation, Func cbOpen, Func cbWant) : + public ControlledValve(int nr, double x, double y, int orientation, Func cbOpen, Func cbWant) : base(x, y, orientation, cbOpen) { Nr = nr; CallbackWantOpen = cbWant; @@ -23,9 +23,9 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { if (App.Plant?.ManualControlMode ?? false) { _text?.Visibility = Visibility.Visible; _triangles?.Fill = - !WantOpen && !IsOpen && !isHovering ? PamhagenBrushes.Red : - !WantOpen && !IsOpen ? PamhagenBrushes.Green : - !WantOpen && IsOpen ? PamhagenBrushes.DimRed : + !WantOpen && IsClosed && !isHovering ? PamhagenBrushes.Red : + !WantOpen && IsClosed ? PamhagenBrushes.Green : + !WantOpen && !IsClosed ? PamhagenBrushes.DimRed : WantOpen && !IsOpen ? PamhagenBrushes.DimGreen : WantOpen && IsOpen && !isHovering ? PamhagenBrushes.Green : WantOpen && IsOpen ? PamhagenBrushes.Red : @@ -33,13 +33,13 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { } else { _text?.Visibility = isHovering ? Visibility.Visible : Visibility.Hidden; _triangles?.Fill = - IsLocked && !WantOpen && !IsOpen ? PamhagenBrushes.DimOrange : - IsLocked && !WantOpen && IsOpen ? PamhagenBrushes.Red : + IsLocked && !WantOpen && IsClosed ? PamhagenBrushes.DimOrange : + IsLocked && !WantOpen && !IsClosed ? PamhagenBrushes.Red : IsLocked && WantOpen && !IsOpen ? PamhagenBrushes.Yellow : IsLocked && WantOpen && IsOpen ? PamhagenBrushes.Green : Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray); } - if (IsOpen) { + if (!IsClosed) { _topLine?.Points[1] = _middlePoints.Open.Top; _bottomLine?.Points[1] = _middlePoints.Open.Bottom; } else { diff --git a/PamhagenSysCtrl/Helpers/Pipeline/ExternalValve.cs b/PamhagenSysCtrl/Helpers/Pipeline/ExternalValve.cs index b3e2f28..dd0ef9f 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/ExternalValve.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/ExternalValve.cs @@ -6,7 +6,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { public override string Label { get; } - public ExternalValve(string label, double x, double y, int orientation, Func cbOpen) : + public ExternalValve(string label, double x, double y, int orientation, Func cbOpen) : base(x, y, orientation, cbOpen) { Label = label; } @@ -17,7 +17,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { !IsOpen ? PamhagenBrushes.Red : IsOpen ? PamhagenBrushes.Green : Highlight ?? Brushes.DarkGray; - if (IsOpen) { + if (!IsClosed) { _topLine?.Points[1] = _middlePoints.Open.Top; _bottomLine?.Points[1] = _middlePoints.Open.Bottom; } else { diff --git a/PamhagenSysCtrl/Helpers/Pipeline/Path.cs b/PamhagenSysCtrl/Helpers/Pipeline/Path.cs index 763494b..c548b37 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/Path.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/Path.cs @@ -1,7 +1,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { public record struct Path( INode Start, - IEnumerable<(IEdge Edge, INode Node)> Hops + (IEdge Edge, INode Node)[] Hops ) { public readonly int Bends => Hops.Sum(h => (h.Node is not Valve && h.Node is not PipeJoin ? 1 : 0) + (h.Edge.Start.CenterX != h.Edge.End.CenterX && h.Edge.Start.CenterY != h.Edge.End.CenterY ? 1 : 0)); public readonly double Length => Hops.Sum(h => Math.Abs(h.Edge.Start.CenterX - h.Edge.End.CenterX) + Math.Abs(h.Edge.Start.CenterY - h.Edge.End.CenterY)); @@ -26,7 +26,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { public readonly IEnumerable BorderValves => InnerAndBorderValves.Except(InnerValves); public readonly bool HasActivePump => Nodes.Any(n => n is Pump p && p.IsActive); - public readonly bool IsReady => InnerValves.All(v => v.IsOpen && v.WantOpen && v.IsLocked) && BorderValves.All(v => !v.IsOpen && !v.WantOpen && v.IsLocked); + public readonly bool IsReady => InnerValves.All(v => v.IsOpen && v.WantOpen && v.IsLocked) && BorderValves.All(v => v.IsClosed && !v.WantOpen && v.IsLocked); public readonly void LockValves() { foreach (var v in InnerAndBorderValves) { diff --git a/PamhagenSysCtrl/Helpers/Pipeline/Valve.cs b/PamhagenSysCtrl/Helpers/Pipeline/Valve.cs index bf3e98b..2e92526 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/Valve.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/Valve.cs @@ -16,11 +16,13 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { public int Orientation { get; set; } - public bool IsOpen => CallbackIsOpen(); + public bool IsOpen => CallbackIsOpen() == true; + public bool IsClosed => CallbackIsOpen() == false; + public bool IsTransitioning => CallbackIsOpen() == null; public ISet Inputs { get; init; } public ISet Outputs { get; init; } - protected Func CallbackIsOpen; + protected Func CallbackIsOpen; protected Rectangle? _background; protected Polygon? _triangles; @@ -29,7 +31,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { protected ((Point Top, Point Bottom) Closed, (Point Top, Point Bottom) Open) _middlePoints; protected TextBlock? _text; - public Valve(double x, double y, int orientation, Func cbOpen) { + public Valve(double x, double y, int orientation, Func cbOpen) { CenterX = x; CenterY = y; CallbackIsOpen = cbOpen; diff --git a/PamhagenSysCtrl/Helpers/Plc/PamhagenPlc.cs b/PamhagenSysCtrl/Helpers/Plc/PamhagenPlc.cs index 570fc58..550d822 100644 --- a/PamhagenSysCtrl/Helpers/Plc/PamhagenPlc.cs +++ b/PamhagenSysCtrl/Helpers/Plc/PamhagenPlc.cs @@ -27,12 +27,16 @@ namespace PamhagenSysCtrl.Helpers.Plc { internal LegacyPamhagenPlc.ActuatorStates Actuators; internal LegacyPamhagenPlc.ActuatorStates LastActuators; - internal LegacyPamhagenPlc.SensorStates Sensors = new(default, default, default, default, default, default, default, [ - FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown - ], default); + internal LegacyPamhagenPlc.SensorStates[] LastSensors = []; - public PamhagenPlc(string comPort, Parity parity = Parity.Odd) { + internal readonly int ConsiderLastSensorsOpen; + internal readonly int ConsiderLastSensorsClose; + + public PamhagenPlc(string comPort, int sensorDelayOpenMs, int sensorDelayCloseMs, Parity parity = Parity.Odd) { Plc = new(comPort, parity); + ConsiderLastSensorsOpen = sensorDelayOpenMs / 125 + 1; + ConsiderLastSensorsClose = sensorDelayCloseMs / 125 + 1; + LastSensors = new LegacyPamhagenPlc.SensorStates[Math.Max(ConsiderLastSensorsOpen, ConsiderLastSensorsClose)]; } public void Dispose() { @@ -44,8 +48,9 @@ namespace PamhagenSysCtrl.Helpers.Plc { await Plc.TestConnection(); Actuators = await Plc.ReadOutputs(); LastActuators = Actuators; - Sensors = await Plc.ReadInputs(); - State = new(Sensors, this); + var sensors = await Plc.ReadInputs(); + LastSensors[0] = sensors; + State = new(LastSensors, this); } public async Task Shutdown() { @@ -59,8 +64,10 @@ namespace PamhagenSysCtrl.Helpers.Plc { LastActuators = Actuators; await Plc.WriteOutputs(LastActuators); } - Sensors = await Plc.ReadInputs(); - State = new(Sensors, this); + var sensors = await Plc.ReadInputs(); + Array.Copy(LastSensors, 0, LastSensors, 1, LastSensors.Length - 1); + LastSensors[0] = sensors; + State = new(LastSensors, this); UpdateInterlocks(); } diff --git a/PamhagenSysCtrl/Helpers/Plc/PamhagenState.cs b/PamhagenSysCtrl/Helpers/Plc/PamhagenState.cs index 6c36a72..efd065d 100644 --- a/PamhagenSysCtrl/Helpers/Plc/PamhagenState.cs +++ b/PamhagenSysCtrl/Helpers/Plc/PamhagenState.cs @@ -10,7 +10,8 @@ namespace PamhagenSysCtrl.Helpers.Plc { public bool OverrideDryRunClearances => Plc != null && Plc.OverrideDryRunClearances; public long Valves => Actuators.Valves; - public long PressureSensors => Sensors.PressureSensors; + public readonly long OpenValves { get; } + public readonly long ClosedValves { get; } public bool MW1_IsSelected => Sensors.TroughSelector == 1; public bool MW2_IsSelected => Sensors.TroughSelector == 2; @@ -114,10 +115,13 @@ namespace PamhagenSysCtrl.Helpers.Plc { public bool P5_HasClearance => P4_HasDryRunClearance && P5_HasPathClearance; public bool P6_HasClearance => P6_HasDryRunClearance && P6_HasPathClearance; - public PamhagenState(LegacyPamhagenPlc.SensorStates sensors, PamhagenPlc plc) { - Sensors = sensors; + public PamhagenState(IEnumerable sensors, PamhagenPlc plc) { + Sensors = sensors.First(); Plc = plc; + OpenValves = sensors.Take(Plc.ConsiderLastSensorsOpen).Aggregate(-1L, (m, s) => m & ~s.PressureSensors); + ClosedValves = sensors.Take(Plc.ConsiderLastSensorsClose).Aggregate(-1L, (m, s) => m & s.PressureSensors); + MP1_WantsPathClearance = WantsPathClearnace(this, Paths.MP1); MP2_WantsPathClearance = WantsPathClearnace(this, Paths.MP2); P12_WantsPathClearance = WantsPathClearnace(this, Paths.P12); @@ -143,8 +147,10 @@ namespace PamhagenSysCtrl.Helpers.Plc { P6_DryRunClearance = true; } - public bool Valve_IsClosed(int n) => Sensors.GetDS(n); - public bool Valve_IsOpen(int n) => !Valve_IsClosed(n); + public bool Valve_IsClosed(int n) => (ClosedValves & (1L << (n - 1))) != 0; + public bool Valve_IsTransitioning(int n) => !Valve_IsClosed(n) && !Valve_IsOpen(n); + public bool Valve_IsOpen(int n) => (OpenValves & (1L << (n - 1))) != 0; + public bool? Valve_OpenState(int n) => Valve_IsOpen(n) ? true : Valve_IsClosed(n) ? false : null; public bool Valve_WantOpen(int n) => Actuators.GetV(n); public bool Valve_WantClosed(int n) => !Valve_WantOpen(n); @@ -164,11 +170,11 @@ namespace PamhagenSysCtrl.Helpers.Plc { } private static bool HasSinglePathClearance(PamhagenState state, IEnumerable masks) { - return masks.Any(m => state.Valves == m && ~state.PressureSensors == m); + return masks.Any(m => state.Valves == m && state.OpenValves == m && ~state.ClosedValves == m); } private static bool HasAnyPathClearance(PamhagenState state, IEnumerable masks) { - return masks.Any(m => state.Valves >= m && ~state.PressureSensors >= m); + return masks.Any(m => state.Valves >= m && state.OpenValves >= m && ~state.ClosedValves >= m); } private static bool WantsSinglePathClearance(PamhagenState state, IEnumerable masks) {