diff --git a/Installer/Files/config.ini b/Installer/Files/config.ini index 32a31fd..28ec8cb 100644 --- a/Installer/Files/config.ini +++ b/Installer/Files/config.ini @@ -24,7 +24,7 @@ c2 = STOP_TROUGH_AUGER c3 = SELECT_MW1 c4 = TOGGLE_STIRRER_MW1 c5 = START_MP1 -c6 = STOP_MP2 +c6 = STOP_MP1 d1 = PATH_MW1_PRESS1 d2 = PATH_MW1_PRESS2 d3 = PATH_MW1_T1 diff --git a/PamhagenSysCtrl/Helpers/Extensions.cs b/PamhagenSysCtrl/Helpers/Extensions.cs index 11dafea..73de93c 100644 --- a/PamhagenSysCtrl/Helpers/Extensions.cs +++ b/PamhagenSysCtrl/Helpers/Extensions.cs @@ -1,4 +1,4 @@ -using System.Windows.Media; +using System.Drawing; namespace PamhagenSysCtrl.Helpers { public static class Extensions { diff --git a/PamhagenSysCtrl/Helpers/PamhagenGraph.cs b/PamhagenSysCtrl/Helpers/PamhagenGraph.cs index 97c334c..a611fee 100644 --- a/PamhagenSysCtrl/Helpers/PamhagenGraph.cs +++ b/PamhagenSysCtrl/Helpers/PamhagenGraph.cs @@ -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 invalid, HashSet? _visited = null, Action? edgeAction = null, Action? 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 isBorder, HashSet? _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 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; + } + } } } } diff --git a/PamhagenSysCtrl/Helpers/PamhagenPlant.cs b/PamhagenSysCtrl/Helpers/PamhagenPlant.cs index 8ff7014..1c3d24f 100644 --- a/PamhagenSysCtrl/Helpers/PamhagenPlant.cs +++ b/PamhagenSysCtrl/Helpers/PamhagenPlant.cs @@ -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 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) { diff --git a/PamhagenSysCtrl/Helpers/Pipeline/Path.cs b/PamhagenSysCtrl/Helpers/Pipeline/Path.cs index bc0292a..763494b 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/Path.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/Path.cs @@ -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 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 Pumps => Nodes.Select(n => n as Pump).Where(p => p != null).Cast(); + public readonly IEnumerable Valves => Nodes.Select(n => n as Valve).Where(v => v != null).Cast(); + + private readonly IEnumerable 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() + .Distinct(); + public readonly IEnumerable InnerValves => Nodes.Select(n => n as ControlledValve).Where(v => v != null).Cast(); + 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 void LockValves() { + foreach (var v in InnerAndBorderValves) { + v.LockedForPaths.Add(this); + } + } + + public readonly void UnlockValves() { + foreach (var v in InnerAndBorderValves) { + v.LockedForPaths.Remove(this); + } + } } } diff --git a/PamhagenSysCtrl/Helpers/Plc/PamhagenPlc.cs b/PamhagenSysCtrl/Helpers/Plc/PamhagenPlc.cs index ff3ca0b..570fc58 100644 --- a/PamhagenSysCtrl/Helpers/Plc/PamhagenPlc.cs +++ b/PamhagenSysCtrl/Helpers/Plc/PamhagenPlc.cs @@ -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; } } diff --git a/PamhagenSysCtrl/Helpers/Plc/PamhagenState.cs b/PamhagenSysCtrl/Helpers/Plc/PamhagenState.cs index 86f5bb2..6c36a72 100644 --- a/PamhagenSysCtrl/Helpers/Plc/PamhagenState.cs +++ b/PamhagenSysCtrl/Helpers/Plc/PamhagenState.cs @@ -2,8 +2,12 @@ namespace PamhagenSysCtrl.Helpers.Plc { public readonly record struct PamhagenState { private readonly LegacyPamhagenPlc.SensorStates Sensors; - private readonly Func 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 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 masks) { + private static bool HasAnyPathClearance(PamhagenState state, IEnumerable masks) { return masks.Any(m => state.Valves >= m && ~state.PressureSensors >= m); } + private static bool WantsSinglePathClearance(PamhagenState state, IEnumerable masks) { + return masks.Any(m => state.Valves == m); + } + + private static bool WantsAnyPathClearance(PamhagenState state, IEnumerable 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, diff --git a/PamhagenSysCtrl/Helpers/Xbe24Keyboard.cs b/PamhagenSysCtrl/Helpers/Xbe24Keyboard.cs index 8668c5a..3732af6 100644 --- a/PamhagenSysCtrl/Helpers/Xbe24Keyboard.cs +++ b/PamhagenSysCtrl/Helpers/Xbe24Keyboard.cs @@ -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? KeyDown; public event EventHandler? KeyUp; public event EventHandler? KeysChanged; + public event EventHandler? 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) { diff --git a/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs b/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs index 4e840e0..e0371b7 100644 --- a/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs +++ b/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs @@ -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 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 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(); - 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(); - } - } } }