Move path logic into Path class
Test / Run tests (push) Successful in 43s

This commit is contained in:
2026-10-15 23:45:32 +02:00
parent c005378368
commit d8c2292b24
9 changed files with 345 additions and 218 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
using System.Windows.Media;
using System.Drawing;
namespace PamhagenSysCtrl.Helpers {
public static class Extensions {
+45 -73
View File
@@ -90,8 +90,8 @@ namespace PamhagenSysCtrl.Helpers {
MW1 = new Trough("MW1", mw1X, mpY - 14, () => App.Plant?.State.MW1_FillLevel ?? FillState.Unknown);
MW2 = new Trough("MW2", mw2X, mpY - 14, () => App.Plant?.State.MW2_FillLevel ?? FillState.Unknown);
MP1 = new Pump("MP1", mw1X, mpY, () => App.Plant?.State.MP1_State ?? default, () => App.Plant?.State.MP1_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
MP2 = new Pump("MP2", mw2X, mpY, () => App.Plant?.State.MP2_State ?? default, () => App.Plant?.State.MP2_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
MP1 = new Pump("MP1", mw1X, mpY, () => App.Plant?.State.MP1_State ?? default, () => App.Plant?.State.MP1_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x)));
MP2 = new Pump("MP2", mw2X, mpY, () => App.Plant?.State.MP2_State ?? default, () => App.Plant?.State.MP2_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x)));
var x1 = new PipeJoin(mw1X, mpY + 8);
var x2 = new PipeJoin(mw2X, mpY + 8);
@@ -315,14 +315,14 @@ namespace PamhagenSysCtrl.Helpers {
CreateInlet(v43, Tank9);
var xp12 = new PipeJoin((t1X + t2X) / 2, tanksY + 16);
P12 = new Pump("P1/2", (t1X + t2X) / 2, tanksY + 22, () => App.Plant?.State.P12_State ?? default, () => App.Plant?.State.P12_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
P12 = new Pump("P1/2", (t1X + t2X) / 2, tanksY + 22, () => App.Plant?.State.P12_State ?? default, () => App.Plant?.State.P12_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x)));
CreateOutlet(Tank1, xp12);
CreateOutlet(Tank2, xp12);
CreatePipe(xp12, P12);
P3 = new Pump("P3", t3X, tanksY + 22, () => App.Plant?.State.P3_State ?? default, () => App.Plant?.State.P3_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
P4 = new Pump("P4", t4X, tanksY + 22, () => App.Plant?.State.P4_State ?? default, () => App.Plant?.State.P4_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
P5 = new Pump("P5", t5X, tanksY + 22, () => App.Plant?.State.P5_State ?? default, () => App.Plant?.State.P5_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
P3 = new Pump("P3", t3X, tanksY + 22, () => App.Plant?.State.P3_State ?? default, () => App.Plant?.State.P3_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x)));
P4 = new Pump("P4", t4X, tanksY + 22, () => App.Plant?.State.P4_State ?? default, () => App.Plant?.State.P4_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x)));
P5 = new Pump("P5", t5X, tanksY + 22, () => App.Plant?.State.P5_State ?? default, () => App.Plant?.State.P5_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x)));
CreateOutlet(Tank3, P3);
CreateOutlet(Tank4, P4);
CreateOutlet(Tank5, P5);
@@ -352,7 +352,7 @@ namespace PamhagenSysCtrl.Helpers {
var xt6 = new PipeJoin(t6X, tanksY + 22);
var xt7 = new PipeJoin(t7X, tanksY + 22);
var xt8 = new PipeJoin(t8X, tanksY + 22);
P6 = new Pump("P6", t6X, tanksY + 30, () => App.Plant?.State.P6_State ?? default, () => App.Plant?.State.P6_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
P6 = new Pump("P6", t6X, tanksY + 30, () => App.Plant?.State.P6_State ?? default, () => App.Plant?.State.P6_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x)));
CreateOutlet(Tank6, xt6);
CreateOutlet(Tank7, xt7);
CreateOutlet(Tank8, xt8);
@@ -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.Hops.Any(h => h.Node == v)));
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)));
}
protected static Tank CreateTank(int n, double x, double y, int capacity) {
@@ -497,10 +497,10 @@ namespace PamhagenSysCtrl.Helpers {
private int PathCost(Path p) {
return
1000 * p.Hops.Count(h => h.Node is Valve) +
(p.Hops.Any(h => h.Node is ControlledValve v && v.Nr == 15) && !p.Hops.Any(h => h.Node is ControlledValve v && (v.Nr == 16 || v.Nr == 20)) ? 10 : 0) +
(p.Hops.Any(h => h.Node is ControlledValve v && v.Nr == 11) && !p.Hops.Any(h => h.Node is ControlledValve v && (v.Nr == 14 || v.Nr == 21)) ? 10 : 0) +
(p.Hops.Any(h => h.Node is ControlledValve v && v.Nr == 6) ? 10 : 0) +
1000 * p.Valves.Count() +
(p.InnerValves.Any(v => v.Nr == 15) && !p.InnerValves.Any(v => v.Nr == 16 || v.Nr == 20) ? 10 : 0) +
(p.InnerValves.Any(v => v.Nr == 11) && !p.InnerValves.Any(v => v.Nr == 14 || v.Nr == 21) ? 10 : 0) +
(p.InnerValves.Any(v => v.Nr == 6) ? 10 : 0) +
p.Bends;
}
@@ -512,76 +512,47 @@ namespace PamhagenSysCtrl.Helpers {
}
}
public void TraverseSubgraph(INode start, Func<INode, bool> invalid, HashSet<IEdge>? _visited = null, Action<INode, bool, IEdge>? edgeAction = null, Action<ControlledValve>? valveAction = null, bool reverse = false) {
var visited = _visited ?? [];
if (start is ControlledValve v) {
valveAction?.Invoke(v);
}
if (invalid(start)) {
return;
}
public static IEnumerable<(INode Start, IEdge Edge, bool EdgeReverse, INode End)> TraverseSubgraph(INode start, Func<INode, bool> isBorder, HashSet<IEdge>? _visited = null, bool reverse = false) {
if (isBorder(start))
yield break;
var visited = _visited ?? [];
foreach (var edge in start.Inputs) {
if (visited.Contains(edge)) continue;
var o = edge.IsTwoWay && edge.Start == start ? edge.End : edge.Start;
edgeAction?.Invoke(start, reverse || _visited == null, edge);
yield return (start, edge, reverse || _visited == null, o);
visited.Add(edge);
TraverseSubgraph(o, invalid, visited, edgeAction, valveAction, reverse || _visited == null);
foreach (var e in TraverseSubgraph(o, isBorder, visited, reverse || _visited == null))
yield return e;
}
foreach (var edge in start.Outputs) {
if (visited.Contains(edge)) continue;
var o = edge.IsTwoWay && edge.End == start ? edge.Start : edge.End;
edgeAction?.Invoke(start, reverse, edge);
yield return (start, edge, reverse, o);
visited.Add(edge);
TraverseSubgraph(o, invalid, visited, edgeAction, valveAction, reverse);
foreach (var e in TraverseSubgraph(o, isBorder, visited, reverse))
yield return e;
}
}
public void AddPath(Path path) {
_selectedPaths.Add(path);
UpdateValvesAdjacientToPath(path, true, false);
path.LockValves();
if (App.Plant is PamhagenPlant plant) {
foreach (var (_, node) in path.Hops) {
if (node is ControlledValve v) {
plant.OpenV(v.Nr);
}
}
foreach (var v in path.InnerValves) plant.OpenV(v.Nr);
foreach (var v in path.BorderValves) plant.CloseV(v.Nr);
}
}
public void RemovePath(Path path) {
_selectedPaths.Remove(path);
UpdateValvesAdjacientToPath(path, false);
foreach (var p in _selectedPaths) UpdateValvesAdjacientToPath(p, true);
path.UnlockValves();
if (App.Plant is PamhagenPlant plant) {
foreach (var (_, node) in path.Hops) {
if (node is ControlledValve v) {
plant.CloseV(v.Nr);
}
}
foreach (var v in path.InnerValves) plant.CloseV(v.Nr);
}
}
private void UpdateValvesAdjacientToPath(Path path, bool locked, bool? wantOpen = null) {
foreach (var (edge, node) in path.Hops) {
TraverseSubgraph(node, (n) => n is ControlledValve || n is ISink || n is Pump, valveAction: (v) => {
if (locked) {
v.LockedForPaths.Add(path);
} else {
v.LockedForPaths.Remove(path);
}
if (wantOpen.HasValue) {
if (wantOpen.Value) {
App.Plant?.OpenV(v.Nr);
} else {
App.Plant?.CloseV(v.Nr);
}
}
});
}
}
public void ColorPipesAdjacientToPath(Path path, Brush? color1, MotorState flowArrows, Brush? color2) {
public static void ColorPipesAdjacientToPath(Path path, Brush? color1, MotorState flowArrows, Brush? color2) {
var color = color1;
var flowPathOffset = 0.0;
HashSet<IEdge> visited = [];
@@ -595,27 +566,28 @@ namespace PamhagenSysCtrl.Helpers {
}
visited.Add(edge);
if (node is Pump) color = color2;
TraverseSubgraph(node, (n) => n is Valve || n is ISink || n is Pump, visited, edgeAction: (s, r, e) => e.Highlight = color);
foreach (var (_, e, _, _) in TraverseSubgraph(node, (n) => n is Valve || n is ISink || n is Pump, visited)) {
e.Highlight = color;
}
}
}
public void ColorPipesByValves(INode start, Brush? color1, MotorState flowArrows, Brush? color2) {
public static void ColorPipesByValves(INode start, Brush? color1, MotorState flowArrows, Brush? color2) {
var flowPathOffset = 0.0;
TraverseSubgraph(start, (n) => n is ISink || n is Outlet || !((n as Valve)?.IsOpen ?? (n as ExternalValve)?.IsOpen ?? true), null,
edgeAction: (s, r, e) => {
if (((e.Start as Valve)?.IsOpen ?? true) && ((e.End as Valve)?.IsOpen ?? true)) {
e.Highlight = color1;
if (flowArrows == MotorState.Forward || flowArrows == MotorState.Backward) {
var dir = e.Start == s;
if (r) dir = !dir;
if (flowArrows == MotorState.Backward) dir = !dir;
e.Flow = (dir, flowPathOffset);
flowPathOffset += e.FlowLength;
}
} else {
e.Highlight = color2;
foreach (var (s, e, r, _) in TraverseSubgraph(start, (n) => n is ISink || n is Outlet || !((n as Valve)?.IsOpen ?? (n as ExternalValve)?.IsOpen ?? true))) {
if (((e.Start as Valve)?.IsOpen ?? true) && ((e.End as Valve)?.IsOpen ?? true)) {
e.Highlight = color1;
if (flowArrows == MotorState.Forward || flowArrows == MotorState.Backward) {
var dir = e.Start == s;
if (r) dir = !dir;
if (flowArrows == MotorState.Backward) dir = !dir;
e.Flow = (dir, flowPathOffset);
flowPathOffset += e.FlowLength;
}
});
} else {
e.Highlight = color2;
}
}
}
}
}
+33 -54
View File
@@ -2,7 +2,6 @@ using PamhagenSysCtrl.Helpers.Pipeline;
using PamhagenSysCtrl.Helpers.Plc;
using System.IO;
using System.Windows;
using System.Windows.Media;
namespace PamhagenSysCtrl.Helpers {
public class PamhagenPlant : IDisposable {
@@ -128,6 +127,12 @@ namespace PamhagenSysCtrl.Helpers {
if (Keyboard == null) {
try {
Keyboard = new();
Keyboard.Error += (s, e) => {
try {
Keyboard.Dispose();
} catch { }
Keyboard = null;
};
Keyboard.KeyDown += Keyboard_KeyDown;
Keyboard.KeysChanged += Keyboard_KeysChanged;
Keyboard.SetFashFrequency(250);
@@ -147,63 +152,45 @@ namespace PamhagenSysCtrl.Helpers {
// TODO update all keys
k.SetBacklight(ShortcutKey.START_REBLER, State.Rebler_IsActive ? Color.FromRgb(0, 255, 0) : State.Auger1_IsActive ? Color.FromRgb(255, 255, 0) : State.WasteDisposal_IsActive ? Color.FromRgb(32, 32, 4) : Color.FromRgb(4, 32, 4));
k.SetBacklight(ShortcutKey.STOP_REBLER, !State.Rebler_IsActive ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4));
k.SetBacklight(ShortcutKey.START_REBLER, State.Rebler_IsActive ? Xbe24Keyboard.Green : Xbe24Keyboard.LightGreen);
k.SetBacklight(ShortcutKey.STOP_REBLER, !State.Rebler_IsActive ? Xbe24Keyboard.Red : Xbe24Keyboard.LightRed);
k.SetBacklight(ShortcutKey.START_TROUGH_AUGER, !State.TroughAuger_HasClearance ? k.IsKeyPressed(ShortcutKey.START_TROUGH_AUGER) ? Color.FromRgb(255, 32, 0) : Color.FromRgb(32, 4, 4) : State.TroughAuger_IsActive ? Color.FromRgb(0, 255, 0) : Color.FromRgb(4, 32, 4));
k.SetBacklight(ShortcutKey.STOP_TROUGH_AUGER, !State.TroughAuger_IsActive ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4));
k.SetBacklight(ShortcutKey.START_TROUGH_AUGER, !State.TroughAuger_HasClearance ? k.IsKeyPressed(ShortcutKey.START_TROUGH_AUGER) ? Xbe24Keyboard.Orange : Xbe24Keyboard.LightRed : State.TroughAuger_IsActive ? Xbe24Keyboard.Green : Xbe24Keyboard.LightGreen);
k.SetBacklight(ShortcutKey.STOP_TROUGH_AUGER, !State.TroughAuger_IsActive ? Xbe24Keyboard.Red : Xbe24Keyboard.LightRed);
k.SetBacklight(ShortcutKey.SELECT_MW2, State.MW2_WantSelected || State.MW2_IsSelected ? Color.FromRgb(0, 0, 255) : Color.FromRgb(4, 4, 16), State.MW2_WantSelected && !State.MW2_IsSelected);
k.SetBacklight(ShortcutKey.SELECT_MW1, State.MW1_WantSelected || State.MW1_IsSelected ? Color.FromRgb(0, 0, 255) : Color.FromRgb(4, 4, 16), State.MW1_WantSelected && !State.MW1_IsSelected);
k.SetBacklight(ShortcutKey.SELECT_MW2, State.MW2_WantSelected || State.MW2_IsSelected ? Xbe24Keyboard.Blue : Xbe24Keyboard.LightBlue, State.MW2_WantSelected && !State.MW2_IsSelected);
k.SetBacklight(ShortcutKey.SELECT_MW1, State.MW1_WantSelected || State.MW1_IsSelected ? Xbe24Keyboard.Blue : Xbe24Keyboard.LightBlue, State.MW1_WantSelected && !State.MW1_IsSelected);
bool prepMP1 = false, prepMP2 = false, prepP12 = false, prepP3 = false, prepP4 = false, prepP5 = false, prepP6 = false;
bool hiOffMP1 = false, hiOffMP2 = false, hiOffP12 = false, hiOffP3 = false, hiOffP4 = false, hiOffP5 = false, hiOffP6 = false;
Dictionary<Pump, bool> pumpHighlightOff = [];
foreach (var (key, d) in Graph.PathShortcuts) {
if (App.Config.MapKey(key) is not int idx)
continue;
var isRunning = d.Pump.IsActive;
// TODO consider intermediate
if (plant.Graph.SelectedPaths.Select(p => (Pipeline.Path?)p).FirstOrDefault(p => p?.Start == d.Source && p?.Hops.Last().Node == d.Sink) is Pipeline.Path path) {
// TODO also account for valves guarding path
var isReady = path.Hops.All(h => h.Node is not ControlledValve v || v.IsOpen == v.WantOpen);
if (!isReady) {
if (d.Pump == Graph.MP1) prepMP1 = true;
if (d.Pump == Graph.MP2) prepMP2 = true;
if (d.Pump == Graph.P12) prepP12 = true;
if (d.Pump == Graph.P3) prepP3 = true;
if (d.Pump == Graph.P4) prepP4 = true;
if (d.Pump == Graph.P5) prepP5 = true;
if (d.Pump == Graph.P6) prepP6 = true;
}
var isActive = d.Pump.IsActive;
var paths = plant.Graph.SelectedPaths.Select(p => (Pipeline.Path?)p)
.Where(p => p?.Matches(d.Source, d.Intermediate, d.Sink) ?? false);
if (paths.FirstOrDefault() is Pipeline.Path path) {
var isReady = path.IsReady;
var isDefault = !path.Hops.SkipLast(1).Any(h => h.Node is not Valve && h.Node is not PipeJoin && h.Node is not Pump);
k.SetBacklight(idx, isRunning ? isDefault ? Color.FromRgb(0, 255, 255) : Color.FromRgb(255, 64, 255) : isDefault ? Color.FromRgb(0, 0, 255) : Color.FromRgb(128, 0, 128), !isReady);
k.SetBacklight(idx, isActive ? isDefault ? Xbe24Keyboard.Cyan : Xbe24Keyboard.Purple : isDefault ? Xbe24Keyboard.Blue : Xbe24Keyboard.DarkPurple, !isReady);
} else {
var isFree = Graph.GetFreePath([d.Source, d.Sink], overrideStart: true) != null;
if (isRunning && k.IsKeyPressed(idx)) {
if (d.Pump == Graph.MP1) hiOffMP1 = true;
if (d.Pump == Graph.MP2) hiOffMP2 = true;
if (d.Pump == Graph.P12) hiOffP12 = true;
if (d.Pump == Graph.P3) hiOffP3 = true;
if (d.Pump == Graph.P4) hiOffP4 = true;
if (d.Pump == Graph.P5) hiOffP5 = true;
if (d.Pump == Graph.P6) hiOffP6 = true;
}
k.SetBacklight(idx, isRunning ? k.IsKeyPressed(idx) ? Color.FromRgb(255, 16, 0) : Color.FromRgb(4, 4, 4) : isFree ? Color.FromRgb(8, 8, 32) : k.IsKeyPressed(idx) ? Color.FromRgb(255, 32, 0) : Color.FromRgb(4, 4, 4));
if (isActive && k.IsKeyPressed(idx)) pumpHighlightOff[d.Pump] = true;
k.SetBacklight(idx, isActive ? k.IsKeyPressed(idx) ? Xbe24Keyboard.Orange : Xbe24Keyboard.Neutral : isFree ? Xbe24Keyboard.LightBlue : k.IsKeyPressed(idx) ? Xbe24Keyboard.Orange : Xbe24Keyboard.Neutral);
}
}
if (!plant.State.MP2_HasPathClearance && plant.State.MP2_HasDryRunClearance && prepMP2) {
k.SetBacklight(ShortcutKey.START_MP2, Color.FromRgb(4, 32, 4), true);
if (!plant.State.MP2_HasPathClearance && plant.State.MP2_HasDryRunClearance && plant.State.MP2_WantsPathClearance) {
k.SetBacklight(ShortcutKey.START_MP2, plant.State.MP2_WantActive ? Xbe24Keyboard.Green : Xbe24Keyboard.LightGreen, true);
} else {
k.SetBacklight(ShortcutKey.START_MP2, !State.MP2_HasClearance ? k.IsKeyPressed(ShortcutKey.START_MP2) ? Color.FromRgb(255, 32, 0) : Color.FromRgb(32, 4, 4) : State.MP2_IsActive ? Color.FromRgb(0, 255, 0) : Color.FromRgb(4, 32, 4));
k.SetBacklight(ShortcutKey.START_MP2, !State.MP2_HasClearance ? k.IsKeyPressed(ShortcutKey.START_MP2) ? Xbe24Keyboard.Orange : Xbe24Keyboard.LightRed : State.MP2_IsActive ? Xbe24Keyboard.Green : Xbe24Keyboard.LightGreen);
}
if (!plant.State.MP1_HasPathClearance && plant.State.MP1_HasDryRunClearance && prepMP1) {
k.SetBacklight(ShortcutKey.START_MP1, Color.FromRgb(4, 32, 4), true);
if (!plant.State.MP1_HasPathClearance && plant.State.MP1_HasDryRunClearance && plant.State.MP1_WantsPathClearance) {
k.SetBacklight(ShortcutKey.START_MP1, plant.State.MP1_WantActive ? Xbe24Keyboard.Green : Xbe24Keyboard.LightGreen, true);
} else {
k.SetBacklight(ShortcutKey.START_MP1, !State.MP1_HasClearance ? k.IsKeyPressed(ShortcutKey.START_MP1) ? Color.FromRgb(255, 32, 0) : Color.FromRgb(32, 4, 4) : State.MP1_IsActive ? Color.FromRgb(0, 255, 0) : Color.FromRgb(4, 32, 4));
k.SetBacklight(ShortcutKey.START_MP1, !State.MP1_HasClearance ? k.IsKeyPressed(ShortcutKey.START_MP1) ? Xbe24Keyboard.Orange : Xbe24Keyboard.LightRed : State.MP1_IsActive ? Xbe24Keyboard.Green : Xbe24Keyboard.LightGreen);
}
k.SetBacklight(ShortcutKey.STOP_MP2, hiOffMP2 ? Color.FromRgb(255, 32, 0) : !State.MP2_IsActive ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4));
k.SetBacklight(ShortcutKey.STOP_MP1, hiOffMP1 ? Color.FromRgb(255, 32, 0) : !State.MP1_IsActive ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4));
k.SetBacklight(ShortcutKey.STOP_MP2, pumpHighlightOff.GetValueOrDefault(Graph.MP2) ? Xbe24Keyboard.Orange : !State.MP2_IsActive ? Xbe24Keyboard.Red : Xbe24Keyboard.LightRed);
k.SetBacklight(ShortcutKey.STOP_MP1, pumpHighlightOff.GetValueOrDefault(Graph.MP1) ? Xbe24Keyboard.Orange : !State.MP1_IsActive ? Xbe24Keyboard.Red : Xbe24Keyboard.LightRed);
}
public void Dispose() {
@@ -257,7 +244,7 @@ namespace PamhagenSysCtrl.Helpers {
App.Plant?.Plc?.CloseV(i);
}
foreach (var p in add) {
if (p.Hops.Any(h => h.Node is ControlledValve v && v.IsLocked)) continue;
if (p.InnerValves.Any(v => v.IsLocked)) continue;
Graph.AddPath(p);
}
}
@@ -278,10 +265,10 @@ namespace PamhagenSysCtrl.Helpers {
public void FastSelect(ISource source, INode? intermediate, ISink sink) {
if (Graph.SelectedPaths.Select(p => (Pipeline.Path?)p).FirstOrDefault(p => p?.Start == source, null) is Pipeline.Path p1) {
if (p1.Hops.Any(h => h.Node is Pump p && p.IsActive))
if (p1.HasActivePump)
throw new NoClearanceException("Pumpe aktiv");
Graph.RemovePath(p1);
if (p1.Hops.Last().Node == sink && (intermediate == null || p1.Hops.Any(h => h.Node == intermediate)))
if (p1.End == sink && (intermediate == null || p1.Contains(intermediate)))
return;
}
if (Graph.GetFreePath(intermediate == null ? [source, sink] : [source, intermediate, sink]) is Pipeline.Path p2) {
@@ -295,15 +282,7 @@ namespace PamhagenSysCtrl.Helpers {
if (key == ShortcutKey.START_REBLER) {
Plc.StartRebler();
} else if (key == ShortcutKey.STOP_REBLER) {
if (State.Rebler_IsActive) {
Plc.StopRebler();
} else {
Plc.StopAuger1();
if (!State.PressCrossAuger_IsActive) {
Plc.StopAuger2();
Plc.StopAuger3();
}
}
Plc.StopRebler();
} else if (key == ShortcutKey.START_TROUGH_AUGER) {
Plc.StartTroughAuger();
} else if (key == ShortcutKey.STOP_TROUGH_AUGER) {
+37 -3
View File
@@ -1,9 +1,43 @@
namespace PamhagenSysCtrl.Helpers.Pipeline {
public record struct Path(
INode Start,
IEnumerable<(IEdge Edge, INode Node)> Hops)
{
INode Start,
IEnumerable<(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));
public readonly INode End => Hops.Last().Node;
public readonly IEnumerable<INode> Nodes => [Start, .. Hops.Select(h => h.Node)];
public readonly bool Contains(INode node) => Nodes.Any(h => h == node);
public readonly bool Matches(INode start, INode end) => Matches(start, null, end);
public readonly bool Matches(INode start, INode? intermediate, INode end) => Start == start && End == end && (intermediate == null || Nodes.Any(n => n == intermediate));
public readonly IEnumerable<Pump> Pumps => Nodes.Select(n => n as Pump).Where(p => p != null).Cast<Pump>();
public readonly IEnumerable<Valve> Valves => Nodes.Select(n => n as Valve).Where(v => v != null).Cast<Valve>();
private readonly IEnumerable<ControlledValve> InnerAndBorderValves => Nodes
.SelectMany(node => PamhagenGraph.TraverseSubgraph(node, (n) => n is ControlledValve || n is ISink || n is Pump))
.Select(e => e.End as ControlledValve)
.Where(v => v != null)
.Cast<ControlledValve>()
.Distinct();
public readonly IEnumerable<ControlledValve> InnerValves => Nodes.Select(n => n as ControlledValve).Where(v => v != null).Cast<ControlledValve>();
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 void LockValves() {
foreach (var v in InnerAndBorderValves) {
v.LockedForPaths.Add(this);
}
}
public readonly void UnlockValves() {
foreach (var v in InnerAndBorderValves) {
v.LockedForPaths.Remove(this);
}
}
}
}
+89 -17
View File
@@ -10,6 +10,14 @@ namespace PamhagenSysCtrl.Helpers.Plc {
public bool OverridePathClearances { get => _overridePathClearnaces; set { _overridePathClearnaces = value; UpdateInterlocks(); } }
public bool OverrideDryRunClearances { get => _overrideDryRunClearances; set { _overrideDryRunClearances = value; UpdateInterlocks(); } }
internal bool MP1_WantActive { get; private set; }
internal bool MP2_WantActive { get; private set; }
internal bool P12_WantActive { get; private set; }
internal bool P3_WantActive { get; private set; }
internal bool P4_WantActive { get; private set; }
internal bool P5_WantActive { get; private set; }
internal bool P6_WantActive { get; private set; }
protected LegacyPamhagenPlc Plc;
public PamhagenState State { get; private set; }
@@ -17,9 +25,9 @@ namespace PamhagenSysCtrl.Helpers.Plc {
public T1.Status? LastStatus => Plc?.LastStatus;
public T1Error? LastError => Plc?.LastError;
protected LegacyPamhagenPlc.ActuatorStates Actuators;
protected LegacyPamhagenPlc.ActuatorStates LastActuators;
protected LegacyPamhagenPlc.SensorStates Sensors = new(default, default, default, default, default, default, default, [
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);
@@ -37,7 +45,7 @@ namespace PamhagenSysCtrl.Helpers.Plc {
Actuators = await Plc.ReadOutputs();
LastActuators = Actuators;
Sensors = await Plc.ReadInputs();
State = new(Sensors, () => Actuators, OverridePathClearances, OverrideDryRunClearances);
State = new(Sensors, this);
}
public async Task Shutdown() {
@@ -52,7 +60,7 @@ namespace PamhagenSysCtrl.Helpers.Plc {
await Plc.WriteOutputs(LastActuators);
}
Sensors = await Plc.ReadInputs();
State = new(Sensors, () => Actuators, OverridePathClearances, OverrideDryRunClearances);
State = new(Sensors, this);
UpdateInterlocks();
}
@@ -73,6 +81,19 @@ namespace PamhagenSysCtrl.Helpers.Plc {
if (State.MP1_IsActive && !State.MP1_HasClearance) StopMP1();
if (State.MP2_IsActive && !State.MP2_HasClearance) StopMP2();
if (State.P12_IsActive && !State.P12_HasClearance) DisableP12();
if (State.P3_IsActive && !State.P3_HasClearance) DisableP3();
if (State.P4_IsActive && !State.P4_HasClearance) DisableP4();
if (State.P5_IsActive && !State.P5_HasClearance) DisableP5();
if (State.P6_IsActive && !State.P6_HasClearance) DisableP6();
if (MP1_WantActive && State.MP1_HasClearance) StartMP1Forward();
if (MP2_WantActive && State.MP2_HasClearance) StartMP2Forward();
if (P12_WantActive && State.P12_HasClearance) EnableP12();
if (P3_WantActive && State.P3_HasClearance) EnableP3();
if (P4_WantActive && State.P4_HasClearance) EnableP4();
if (P5_WantActive && State.P5_HasClearance) EnableP5();
if (P6_WantActive && State.P6_HasClearance) EnableP6();
if (!ManualControlMode) {
if (State.MP1_State == MotorState.Forward && State.MW1_FillLevel >= FillState.Half && !State.MW1_Stirrer_IsActive) {
@@ -86,12 +107,6 @@ namespace PamhagenSysCtrl.Helpers.Plc {
StopStirrerMW2();
}
}
if (State.P12_IsActive && !State.P12_HasClearance) DisableP12();
if (State.P3_IsActive && !State.P3_HasClearance) DisableP3();
if (State.P4_IsActive && !State.P4_HasClearance) DisableP4();
if (State.P5_IsActive && !State.P5_HasClearance) DisableP5();
if (State.P6_IsActive && !State.P6_HasClearance) DisableP6();
}
public void OpenV(int n) => SetV(n, true);
@@ -128,6 +143,7 @@ namespace PamhagenSysCtrl.Helpers.Plc {
public void StopRebler() {
if (!ManualControlMode) StopTroughAuger();
Actuators.Rebler = false;
if (!ManualControlMode) StopAuger1();
}
public void StartStirrerMW1() {
@@ -154,6 +170,8 @@ namespace PamhagenSysCtrl.Helpers.Plc {
public void StopAuger1() {
if (!ManualControlMode && State.Rebler_IsActive) throw new NoClearanceException("Rebler aktiv");
Actuators.Auger1 = false;
if (!ManualControlMode && !State.PressCrossAuger_IsActive)
StopAuger2();
}
public void StartAuger2() {
@@ -165,6 +183,8 @@ namespace PamhagenSysCtrl.Helpers.Plc {
if (!ManualControlMode && State.PressCrossAuger_IsActive) throw new NoClearanceException("Presse-Querschnecke aktiv");
if (!ManualControlMode && State.Auger1_IsActive) throw new NoClearanceException("Förderschnecke 1 aktiv");
Actuators.Auger2 = false;
if (!ManualControlMode)
StopAuger3();
}
public void StartAuger3() {
@@ -185,83 +205,135 @@ namespace PamhagenSysCtrl.Helpers.Plc {
}
public void StartMP1Forward() {
if (!State.MP1_HasClearance) throw new NoClearanceException("MP1 hat keine Freigabe");
if (!State.MP1_HasClearance) {
if (!State.MP1_HasDryRunClearance || !State.MP1_WantsPathClearance)
throw new NoClearanceException("MP1 hat keine Freigabe");
MP1_WantActive = true;
return;
}
MP1_WantActive = false;
if (Actuators.MP1 == MotorState.Backward) throw new NoClearanceException("MP1 läuft bereits (zurück)");
Actuators.MP1 = MotorState.Forward;
if (State.MW1_FillLevel >= FillState.Half) StartStirrerMW1();
}
public void StartMP1Backward() {
MP1_WantActive = false;
if (!State.MP1_HasClearance) throw new NoClearanceException("MP1 hat keine Freigabe");
if (Actuators.MP1 == MotorState.Forward) throw new NoClearanceException("MP1 läuft bereits (vor)");
Actuators.MP1 = MotorState.Backward;
}
public void StopMP1() {
MP1_WantActive = false;
Actuators.MP1 = MotorState.Halt;
if (!ManualControlMode) StopStirrerMW1();
}
public void StartMP2Forward() {
if (!State.MP2_HasClearance) throw new NoClearanceException("MP2 hat keine Freigabe");
if (!State.MP2_HasClearance) {
if (!State.MP2_HasDryRunClearance || !State.MP2_WantsPathClearance)
throw new NoClearanceException("MP2 hat keine Freigabe");
MP2_WantActive = true;
return;
}
MP2_WantActive = false;
if (Actuators.MP2 == MotorState.Backward) throw new NoClearanceException("MP2 läuft bereits (zurück)");
Actuators.MP2 = MotorState.Forward;
if (State.MW2_FillLevel >= FillState.Half) StartStirrerMW2();
}
public void StartMP2Backward() {
MP2_WantActive = false;
if (!State.MP2_HasClearance) throw new NoClearanceException("MP2 hat keine Freigabe");
if (Actuators.MP2 == MotorState.Forward) throw new NoClearanceException("MP2 läuft bereits (vor)");
Actuators.MP2 = MotorState.Backward;
}
public void StopMP2() {
MP2_WantActive = false;
Actuators.MP2 = MotorState.Halt;
if (!ManualControlMode) StopStirrerMW1();
}
public void EnableP12() {
if (!State.P12_HasClearance) {
if (!State.P12_HasDryRunClearance || !State.P12_WantsPathClearance)
throw new NoClearanceException("P1/2 hat keine Freigabe");
P12_WantActive = true;
return;
}
P12_WantActive = false;
if (!State.P12_HasClearance) throw new NoClearanceException("P1/2 hat keine Freigabe");
Actuators.P12 = MotorState.Forward;
}
public void DisableP12() {
P12_WantActive = false;
Actuators.P12 = MotorState.Halt;
}
public void EnableP3() {
if (!State.P3_HasClearance) throw new NoClearanceException("P3 hat keine Freigabe");
if (!State.P3_HasClearance) {
if (!State.P3_HasDryRunClearance || !State.P3_WantsPathClearance)
throw new NoClearanceException("P3 hat keine Freigabe");
P3_WantActive = true;
return;
}
P3_WantActive = false;
Actuators.P3 = MotorState.Forward;
}
public void DisableP3() {
P3_WantActive = false;
Actuators.P3 = MotorState.Halt;
}
public void EnableP4() {
if (!State.P4_HasClearance) throw new NoClearanceException("P4 hat keine Freigabe");
if (!State.P4_HasClearance) {
if (!State.P4_HasDryRunClearance || !State.P4_WantsPathClearance)
throw new NoClearanceException("P4 hat keine Freigabe");
P4_WantActive = true;
return;
}
P4_WantActive = false;
Actuators.P4 = MotorState.Forward;
}
public void DisableP4() {
P4_WantActive = false;
Actuators.P4 = MotorState.Halt;
}
public void EnableP5() {
if (!State.P5_HasClearance) throw new NoClearanceException("P5 hat keine Freigabe");
if (!State.P5_HasClearance) {
if (!State.P5_HasDryRunClearance || !State.P5_WantsPathClearance)
throw new NoClearanceException("P5 hat keine Freigabe");
P5_WantActive = true;
return;
}
P5_WantActive = false;
Actuators.P5 = MotorState.Forward;
}
public void DisableP5() {
P5_WantActive = false;
Actuators.P5 = MotorState.Halt;
}
public void EnableP6() {
if (!State.P6_HasClearance) throw new NoClearanceException("P6 hat keine Freigabe");
if (!State.P6_HasClearance) {
if (!State.P6_HasDryRunClearance || !State.P6_WantsPathClearance)
throw new NoClearanceException("P6 hat keine Freigabe");
P6_WantActive = true;
return;
}
P6_WantActive = false;
Actuators.P6 = MotorState.Forward;
}
public void DisableP6() {
P6_WantActive = false;
Actuators.P6 = MotorState.Halt;
}
}
+107 -33
View File
@@ -2,8 +2,12 @@ namespace PamhagenSysCtrl.Helpers.Plc {
public readonly record struct PamhagenState {
private readonly LegacyPamhagenPlc.SensorStates Sensors;
private readonly Func<LegacyPamhagenPlc.ActuatorStates> ActuatorsCb;
private LegacyPamhagenPlc.ActuatorStates Actuators { get => ActuatorsCb == null ? default : ActuatorsCb(); }
private readonly PamhagenPlc Plc;
private LegacyPamhagenPlc.ActuatorStates Actuators => Plc == null ? default : Plc.Actuators;
public bool ManualControlMode => Plc != null && Plc.ManualControlMode;
public bool OverridePathClearances => Plc != null && Plc.OverridePathClearances;
public bool OverrideDryRunClearances => Plc != null && Plc.OverrideDryRunClearances;
public long Valves => Actuators.Valves;
public long PressureSensors => Sensors.PressureSensors;
@@ -57,20 +61,51 @@ namespace PamhagenSysCtrl.Helpers.Plc {
public bool Press2_HasClearance => Sensors.ClearancePress2;
public bool TroughAuger_HasClearance => Rebler_IsActive && !(MW1_IsSelected && MW1_FillLevel == FillState.Full) && !(MW2_IsSelected && MW2_FillLevel == FillState.Full);
public readonly bool MP1_HasPathClearance { get; }
public readonly bool MP2_HasPathClearance { get; }
public readonly bool P12_HasPathClearance { get; }
public readonly bool P3_HasPathClearance { get; }
public readonly bool P4_HasPathClearance { get; }
public readonly bool P5_HasPathClearance { get; }
public readonly bool P6_HasPathClearance { get; }
public readonly bool MP1_HasDryRunClearance { get; }
public readonly bool MP2_HasDryRunClearance { get; }
public readonly bool P12_HasDryRunClearance { get; }
public readonly bool P3_HasDryRunClearance { get; }
public readonly bool P4_HasDryRunClearance { get; }
public readonly bool P5_HasDryRunClearance { get; }
public readonly bool P6_HasDryRunClearance { get; }
private readonly bool MP1_PathClearance;
private readonly bool MP2_PathClearance;
private readonly bool P12_PathClearance;
private readonly bool P3_PathClearance;
private readonly bool P4_PathClearance;
private readonly bool P5_PathClearance;
private readonly bool P6_PathClearance;
private readonly bool MP1_DryRunClearance;
private readonly bool MP2_DryRunClearance;
private readonly bool P12_DryRunClearance;
private readonly bool P3_DryRunClearance;
private readonly bool P4_DryRunClearance;
private readonly bool P5_DryRunClearance;
private readonly bool P6_DryRunClearance;
public readonly bool MP1_WantsPathClearance { get; }
public readonly bool MP2_WantsPathClearance { get; }
public readonly bool P12_WantsPathClearance { get; }
public readonly bool P3_WantsPathClearance { get; }
public readonly bool P4_WantsPathClearance { get; }
public readonly bool P5_WantsPathClearance { get; }
public readonly bool P6_WantsPathClearance { get; }
public bool MP1_WantActive => Plc.MP1_WantActive;
public bool MP2_WantActive => Plc.MP2_WantActive;
public bool P12_WantActive => Plc.P12_WantActive;
public bool P3_WantActive => Plc.P3_WantActive;
public bool P4_WantActive => Plc.P4_WantActive;
public bool P5_WantActive => Plc.P5_WantActive;
public bool P6_WantActive => Plc.P6_WantActive;
public bool MP1_HasPathClearance => OverridePathClearances || MP1_PathClearance;
public bool MP2_HasPathClearance => OverridePathClearances || MP2_PathClearance;
public bool P12_HasPathClearance => OverridePathClearances || P12_PathClearance;
public bool P3_HasPathClearance => OverridePathClearances || P3_PathClearance;
public bool P4_HasPathClearance => OverridePathClearances || P4_PathClearance;
public bool P5_HasPathClearance => OverridePathClearances || P5_PathClearance;
public bool P6_HasPathClearance => OverridePathClearances || P6_PathClearance;
public bool MP1_HasDryRunClearance => OverrideDryRunClearances || MP1_DryRunClearance;
public bool MP2_HasDryRunClearance => OverrideDryRunClearances || MP2_DryRunClearance;
public bool P12_HasDryRunClearance => OverrideDryRunClearances || P12_DryRunClearance;
public bool P3_HasDryRunClearance => OverrideDryRunClearances || P3_DryRunClearance;
public bool P4_HasDryRunClearance => OverrideDryRunClearances || P4_DryRunClearance;
public bool P5_HasDryRunClearance => OverrideDryRunClearances || P5_DryRunClearance;
public bool P6_HasDryRunClearance => OverrideDryRunClearances || P6_DryRunClearance;
public bool MP1_HasClearance => MP1_HasDryRunClearance && MP1_HasPathClearance;
public bool MP2_HasClearance => MP2_HasDryRunClearance && MP2_HasPathClearance;
public bool P12_HasClearance => P12_HasDryRunClearance && P12_HasPathClearance;
@@ -79,25 +114,33 @@ 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, Func<LegacyPamhagenPlc.ActuatorStates> actuatorsCb, bool overridePathClearances = false, bool overrideDryRunClearances = false) {
public PamhagenState(LegacyPamhagenPlc.SensorStates sensors, PamhagenPlc plc) {
Sensors = sensors;
ActuatorsCb = actuatorsCb;
Plc = plc;
MP1_HasPathClearance = overridePathClearances || HasPathClearance(this, Paths.MP1);
MP2_HasPathClearance = overridePathClearances || HasPathClearance(this, Paths.MP2);
P12_HasPathClearance = overridePathClearances || HasPathClearance(this, Paths.P12);
P3_HasPathClearance = overridePathClearances || HasPathClearance(this, Paths.P3);
P4_HasPathClearance = overridePathClearances || HasPathClearance(this, Paths.P4);
P5_HasPathClearance = overridePathClearances || HasPathClearance(this, Paths.P5);
P6_HasPathClearance = overridePathClearances || HasPathClearance(this, Paths.P6);
MP1_WantsPathClearance = WantsPathClearnace(this, Paths.MP1);
MP2_WantsPathClearance = WantsPathClearnace(this, Paths.MP2);
P12_WantsPathClearance = WantsPathClearnace(this, Paths.P12);
P3_WantsPathClearance = WantsPathClearnace(this, Paths.P3);
P4_WantsPathClearance = WantsPathClearnace(this, Paths.P4);
P5_WantsPathClearance = WantsPathClearnace(this, Paths.P5);
P6_WantsPathClearance = WantsPathClearnace(this, Paths.P6);
MP1_HasDryRunClearance = overrideDryRunClearances || MW1_FillLevel != FillState.Empty;
MP2_HasDryRunClearance = overrideDryRunClearances || MW2_FillLevel != FillState.Empty;
P12_HasDryRunClearance = overrideDryRunClearances || T1_FillLevel != FillState.Empty || T2_FillLevel != FillState.Empty;
P3_HasDryRunClearance = overrideDryRunClearances || T3_FillLevel != FillState.Empty;
P4_HasDryRunClearance = overrideDryRunClearances || T4_FillLevel != FillState.Empty;
P5_HasDryRunClearance = overrideDryRunClearances || T5_FillLevel != FillState.Empty;
P6_HasDryRunClearance = true;
MP1_PathClearance = HasPathClearance(this, Paths.MP1);
MP2_PathClearance = HasPathClearance(this, Paths.MP2);
P12_PathClearance = HasPathClearance(this, Paths.P12);
P3_PathClearance = HasPathClearance(this, Paths.P3);
P4_PathClearance = HasPathClearance(this, Paths.P4);
P5_PathClearance = HasPathClearance(this, Paths.P5);
P6_PathClearance = HasPathClearance(this, Paths.P6);
MP1_DryRunClearance = MW1_FillLevel != FillState.Empty;
MP2_DryRunClearance = MW2_FillLevel != FillState.Empty;
P12_DryRunClearance = T1_FillLevel != FillState.Empty || T2_FillLevel != FillState.Empty;
P3_DryRunClearance = T3_FillLevel != FillState.Empty;
P4_DryRunClearance = T4_FillLevel != FillState.Empty;
P5_DryRunClearance = T5_FillLevel != FillState.Empty;
P6_DryRunClearance = true;
}
public bool Valve_IsClosed(int n) => Sensors.GetDS(n);
@@ -124,10 +167,18 @@ namespace PamhagenSysCtrl.Helpers.Plc {
return masks.Any(m => state.Valves == m && ~state.PressureSensors == m);
}
private static bool HasAnyClearance(PamhagenState state, IEnumerable<ValveStateMask> masks) {
private static bool HasAnyPathClearance(PamhagenState state, IEnumerable<ValveStateMask> masks) {
return masks.Any(m => state.Valves >= m && ~state.PressureSensors >= m);
}
private static bool WantsSinglePathClearance(PamhagenState state, IEnumerable<ValveStateMask> masks) {
return masks.Any(m => state.Valves == m);
}
private static bool WantsAnyPathClearance(PamhagenState state, IEnumerable<ValveStateMask> masks) {
return masks.Any(m => state.Valves >= m);
}
private static bool HasPathClearance(PamhagenState state, (ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) masks) {
return
(HasSinglePathClearance(state, masks.Press1) && state.Press1_HasClearance) ||
@@ -144,6 +195,22 @@ namespace PamhagenSysCtrl.Helpers.Plc {
false;
}
private static bool WantsPathClearnace(PamhagenState state, (ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) masks) {
return
(WantsSinglePathClearance(state, masks.Press1) && state.Press1_HasClearance) ||
(WantsSinglePathClearance(state, masks.Press2) && state.Press2_HasClearance) ||
(WantsSinglePathClearance(state, masks.Tanks[0]) && state.T1_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[1]) && state.T2_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[2]) && state.T3_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[3]) && state.T4_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[4]) && state.T5_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[5]) && state.T6_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[6]) && state.T7_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[7]) && state.T8_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[8]) && state.T9_FillLevel != FillState.Full) ||
false;
}
private static bool HasPathClearance(PamhagenState state, (ValveStateMask[] Press1, ValveStateMask[] Press2) masks) {
return
(HasSinglePathClearance(state, masks.Press1) && state.Press1_HasClearance) ||
@@ -151,6 +218,13 @@ namespace PamhagenSysCtrl.Helpers.Plc {
false;
}
private static bool WantsPathClearnace(PamhagenState state, (ValveStateMask[] Press1, ValveStateMask[] Press2) masks) {
return
(WantsSinglePathClearance(state, masks.Press1) && state.Press1_HasClearance) ||
(WantsSinglePathClearance(state, masks.Press2) && state.Press2_HasClearance) ||
false;
}
private static readonly (
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) MP1,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) MP2,
+20 -4
View File
@@ -1,6 +1,6 @@
using PIEHidNetCore;
using System.Drawing;
using System.IO;
using System.Windows.Media;
namespace PamhagenSysCtrl.Helpers {
public class Xbe24Keyboard : IDisposable, PIEDataHandler, PIEErrorHandler {
@@ -11,6 +11,21 @@ namespace PamhagenSysCtrl.Helpers {
public readonly bool GetKey(int n) => (Rows[n / 6] & (1 << (n % 6))) != 0;
}
public static readonly Color DefaltBacklight = Color.FromArgb(3, 3, 3);
public static readonly Color Neutral = Color.FromArgb(4, 4, 4);
public static readonly Color Red = Color.FromArgb(255, 0, 0);
public static readonly Color Green = Color.FromArgb(0, 255, 0);
public static readonly Color Blue = Color.FromArgb(0, 0, 255);
public static readonly Color Yellow = Color.FromArgb(255, 255, 0);
public static readonly Color Magenta = Color.FromArgb(255, 0, 255);
public static readonly Color Cyan = Color.FromArgb(0, 255, 255);
public static readonly Color Orange = Color.FromArgb(255, 32, 0);
public static readonly Color DarkPurple = Color.FromArgb(128, 0, 128);
public static readonly Color Purple = Color.FromArgb(255, 64, 255);
public static readonly Color LightRed = Color.FromArgb(32, 4, 4);
public static readonly Color LightGreen = Color.FromArgb(4, 32, 4);
public static readonly Color LightBlue = Color.FromArgb(4, 4, 32);
protected readonly PIEDevice XbeDevice;
private KeyState LastKeyState = new(new byte[4]);
@@ -21,6 +36,7 @@ namespace PamhagenSysCtrl.Helpers {
public event EventHandler<int>? KeyDown;
public event EventHandler<int>? KeyUp;
public event EventHandler<KeyState>? KeysChanged;
public event EventHandler<int>? Error;
public Xbe24Keyboard(int pid = 0x0555) {
foreach (var device in PIEDevice.EnumeratePIE()) {
@@ -40,12 +56,12 @@ namespace PamhagenSysCtrl.Helpers {
if ((res = XbeDevice.SetErrorCallback(this)) != 0)
throw new IOException($"Unable to SetErrorCallback for XBE keyboard: {res}");
SetBacklight(Color.FromRgb(1, 1, 1));
SetBacklight(DefaltBacklight);
}
public void Dispose() {
GC.SuppressFinalize(this);
SetBacklight(Color.FromRgb(1, 1, 1));
SetBacklight(DefaltBacklight);
XbeDevice?.CloseInterface();
}
@@ -61,7 +77,7 @@ namespace PamhagenSysCtrl.Helpers {
public void HandlePIEHidError(PIEDevice sourceDevice, int error) {
if (sourceDevice != XbeDevice)
return;
// TODO
Error?.Invoke(this, error);
}
protected void HandleKeyStateChange(KeyState current, KeyState last) {
@@ -27,7 +27,7 @@ namespace PamhagenSysCtrl.Windows {
public Path? HoveringPath {
get {
foreach (var b in FastSelectPaths.Where(b => b.Key.IsMouseOver)) {
if (Graph.SelectedPaths.Any(p => p.Start == b.Value.Source && p.Hops.Last().Node == b.Value.Sink))
if (Graph.SelectedPaths.Any(p => p.Matches(b.Value.Source, b.Value.Sink)))
continue;
return Graph.GetFreePath([b.Value.Source, b.Value.Sink], true);
}
@@ -354,7 +354,7 @@ namespace PamhagenSysCtrl.Windows {
} else {
if (_lastSource == null && Graph.SelectedPaths.Any(p => p.Start == c)) {
var path = Graph.SelectedPaths.First(p => p.Start == c);
if (!path.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false)) {
if (!path.HasActivePump) {
Graph.RemovePath(path);
}
} else if (_lastSource != null && c is ISink sink) {
@@ -485,7 +485,7 @@ namespace PamhagenSysCtrl.Windows {
private void UpdateFastSelectPathButton(IEnumerable<Path> paths, Path? hoverPathDeactivate, Button b) {
var e = FastSelectPaths[b];
SetFastSelectButton(b,
paths.Any(p => p.Start == e.Source && p.Hops.Last().Node == e.Sink) ? (hoverPathDeactivate?.Start == e.Source && hoverPathDeactivate?.Hops.Last().Node == e.Sink) ? 2 : 1 : 0,
paths.Any(p => p.Matches(e.Source, e.Sink)) ? (hoverPathDeactivate?.Matches(e.Source, e.Sink) ?? false) ? 2 : 1 : 0,
e.Pump.IsActive || Graph.GetFreePath([e.Source, e.Sink], overrideStart: true) == null);
}
@@ -514,7 +514,7 @@ namespace PamhagenSysCtrl.Windows {
Path? hoverPathDeactivate = null;
if (App.Plant?.ManualControlMode ?? false) {
foreach (var src in Graph.Nodes.Where(n => n is Pump)) {
Graph.ColorPipesByValves(src, PamhagenBrushes.Green, (src as Pump)?.State ?? default, PamhagenBrushes.DimOrange);
PamhagenGraph.ColorPipesByValves(src, PamhagenBrushes.Green, (src as Pump)?.State ?? default, PamhagenBrushes.DimOrange);
}
MP1_Target.Text = "Vor";
MP2_Target.Text = "Vor";
@@ -522,27 +522,21 @@ namespace PamhagenSysCtrl.Windows {
MP2_Target.FontSize = 16;
} else {
foreach (var p in Graph.SelectedPaths) {
Graph.ColorPipesAdjacientToPath(p, PamhagenBrushes.Green, p.Hops.Select(h => (h.Node as Pump)?.State ?? default).FirstOrDefault(n => n != MotorState.Halt), PamhagenBrushes.DimOrange);
PamhagenGraph.ColorPipesAdjacientToPath(p, PamhagenBrushes.Green, p.Pumps.Select(p => p.State).FirstOrDefault(n => n != MotorState.Halt), PamhagenBrushes.DimOrange);
}
if (_lastSource == null && _hover.Count > 0 && Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == _hover.First()) is Path path1) {
var pumpActive = path1.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false);
if (pumpActive) {
foreach (var pump in path1.Hops.Select(h => h.Node as Pump).Where(p => p != null)) {
pump?.Highlight = PamhagenBrushes.Red;
}
if (path1.HasActivePump) {
foreach (var p in path1.Pumps) p.Highlight = PamhagenBrushes.Red;
} else {
hoverPathDeactivate = path1;
Graph.ColorPipesAdjacientToPath(path1, PamhagenBrushes.Red, default, null);
PamhagenGraph.ColorPipesAdjacientToPath(path1, PamhagenBrushes.Red, default, null);
}
} else if (hoverButtons.Count != 0 && Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == hoverButtons.First().Value.Source) is Path path2) {
var pumpActive = path2.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false);
if (pumpActive) {
foreach (var pump in path2.Hops.Select(h => h.Node as Pump).Where(p => p != null)) {
pump?.Highlight = PamhagenBrushes.Red;
}
if (path2.HasActivePump) {
foreach (var p in path2.Pumps) p.Highlight = PamhagenBrushes.Red;
} else {
hoverPathDeactivate = path2;
Graph.ColorPipesAdjacientToPath(path2, PamhagenBrushes.Red, default, null);
PamhagenGraph.ColorPipesAdjacientToPath(path2, PamhagenBrushes.Red, default, null);
}
}
if (_lastSource != null) {
@@ -551,7 +545,7 @@ namespace PamhagenSysCtrl.Windows {
}
}
if (HoveringPath is Path hoveringPath) {
Graph.ColorPipesAdjacientToPath(hoveringPath, PamhagenBrushes.Green, default, PamhagenBrushes.DimOrange);
PamhagenGraph.ColorPipesAdjacientToPath(hoveringPath, PamhagenBrushes.Green, default, PamhagenBrushes.DimOrange);
}
if (Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == Graph.MW1) is Path pmw1) {
@@ -600,19 +594,5 @@ namespace PamhagenSysCtrl.Windows {
}
UpdateScheme(Mouse.GetPosition(SchemeCanvas), Mouse.LeftButton == MouseButtonState.Pressed);
}
public IEnumerable<Path> ReconstructSelectedPaths() {
if (App.Plant is not PamhagenPlant p) yield break;
var sources = Graph.Nodes.Where(n => n is ISource).ToList();
var sinks = Graph.Nodes.Where(n => n is ISink).ToList();
foreach (var src in sources) {
var paths = new List<Path>();
foreach (var sink in sinks) {
paths.AddRange(Graph.GetPaths(src, sink, null, (n) => n is not ISink && (n is not ControlledValve v || p.State.Valve_WantOpen(v.Nr))));
}
if (paths.Count == 1)
yield return paths.First();
}
}
}
}