Add sensor delay for opening and closing valves
Test / Run tests (push) Successful in 43s

This commit is contained in:
2026-10-16 10:26:17 +02:00
parent d8c2292b24
commit d0c328d97e
10 changed files with 52 additions and 31 deletions
+4
View File
@@ -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");
+1 -1
View File
@@ -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) {
+1 -1
View File
@@ -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;
@@ -13,7 +13,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
protected Func<Valve, bool> CallbackWantOpen;
public ControlledValve(int nr, double x, double y, int orientation, Func<bool> cbOpen, Func<Valve, bool> cbWant) :
public ControlledValve(int nr, double x, double y, int orientation, Func<bool?> cbOpen, Func<Valve, bool> 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 {
@@ -6,7 +6,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public override string Label { get; }
public ExternalValve(string label, double x, double y, int orientation, Func<bool> cbOpen) :
public ExternalValve(string label, double x, double y, int orientation, Func<bool?> 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 {
+2 -2
View File
@@ -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<ControlledValve> 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) {
+5 -3
View File
@@ -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<IEdge> Inputs { get; init; }
public ISet<IEdge> Outputs { get; init; }
protected Func<bool> CallbackIsOpen;
protected Func<bool?> 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<bool> cbOpen) {
public Valve(double x, double y, int orientation, Func<bool?> cbOpen) {
CenterX = x;
CenterY = y;
CallbackIsOpen = cbOpen;
+15 -8
View File
@@ -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();
}
+13 -7
View File
@@ -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<LegacyPamhagenPlc.SensorStates> 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<ValveStateMask> 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<ValveStateMask> 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<ValveStateMask> masks) {