From 4071cc2f2c1bbc6d77ba53694e453cd142c8842f Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Wed, 9 Sep 2026 17:38:55 +0200 Subject: [PATCH] [WIP] XBE-24 --- PamhagenSysCtrl/App.xaml.cs | 8 +- PamhagenSysCtrl/Helpers/PamhagenPlant.cs | 205 +++++++++++++++++- PamhagenSysCtrl/Helpers/Xbe24Keyboard.cs | 114 ++++++++++ PamhagenSysCtrl/PIEHidNetCore.dll | Bin 0 -> 26112 bytes PamhagenSysCtrl/PamhagenSysCtrl.csproj | 3 + .../Windows/PlantSchemeWindow.xaml.cs | 79 ++----- 6 files changed, 343 insertions(+), 66 deletions(-) create mode 100644 PamhagenSysCtrl/Helpers/Xbe24Keyboard.cs create mode 100644 PamhagenSysCtrl/PIEHidNetCore.dll diff --git a/PamhagenSysCtrl/App.xaml.cs b/PamhagenSysCtrl/App.xaml.cs index d5d1b79..d250919 100644 --- a/PamhagenSysCtrl/App.xaml.cs +++ b/PamhagenSysCtrl/App.xaml.cs @@ -36,15 +36,11 @@ namespace PamhagenSysCtrl { protected override async void OnStartup(StartupEventArgs evt) { Version = new Version(typeof(App).GetTypeInfo().Assembly.GetCustomAttribute()?.InformationalVersion.Split('+')[0] ?? "0.0.0"); + Plant = new(Config.PlcPort); try { - Plant = new(Config.PlcPort); await Plant.StartThread(); } catch (Exception exc) { - Plant?.Dispose(); - Plant = null; - var str = $"Beim Aufbauen einer Verbindung zur SPS ist ein Fehler aufgetreten:\n\n{exc.GetType().Name}: {exc.Message}"; - if (exc.InnerException != null) str += $"\n\n{exc.InnerException.GetType().Name}: {exc.InnerException.Message}"; - MessageBox.Show(str, "SPS-Fehler", MessageBoxButton.OK, MessageBoxImage.Error); + // TODO } base.OnStartup(evt); diff --git a/PamhagenSysCtrl/Helpers/PamhagenPlant.cs b/PamhagenSysCtrl/Helpers/PamhagenPlant.cs index a2d633e..24c051e 100644 --- a/PamhagenSysCtrl/Helpers/PamhagenPlant.cs +++ b/PamhagenSysCtrl/Helpers/PamhagenPlant.cs @@ -1,10 +1,15 @@ +using PamhagenSysCtrl.Helpers.Pipeline; using System.IO; using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; namespace PamhagenSysCtrl.Helpers { public class PamhagenPlant : IDisposable { protected PamhagenPlc? Plc; + protected Xbe24Keyboard? Keyboard; + public PamhagenGraph Graph; protected bool IsRunning = true; protected Thread? BackgroundThread; @@ -78,8 +83,25 @@ namespace PamhagenSysCtrl.Helpers { public bool IsP6Active => Actuators.P6 == MotorState.Forward; public PamhagenPlant(string portName) { - Plc = new(portName); + try { + Plc = new(portName); + } catch (Exception exc) { + var str = $"Beim Aufbauen einer Verbindung zur SPS ist ein Fehler aufgetreten:\n\n{exc.GetType().Name}: {exc.Message}"; + if (exc.InnerException != null) str += $"\n\n{exc.InnerException.GetType().Name}: {exc.InnerException.Message}"; + MessageBox.Show(str, "SPS-Fehler", MessageBoxButton.OK, MessageBoxImage.Error); + } + Graph = new(); + + try { + Keyboard = new(); + Keyboard.KeyDown += Keyboard_KeyDown; + Keyboard.KeysChanged += Keyboard_KeysChanged; + Keyboard.SetFashFrequency(250); + Keyboard.SetBacklight(Color.FromRgb(4, 4, 4)); + } catch { } + Update += OnUpdate; + Update += UpdateKeyboardBacklights; } protected virtual void RaiseUpdateEvent(PlantEventArgs evt) { @@ -96,6 +118,10 @@ namespace PamhagenSysCtrl.Helpers { if (p.IsAuger2Active && !p.IsAuger3Active) p.StartAuger3(); if (p.IsTroughAugerActive && !p.TroughAugerHasClearance) p.StopTroughAuger(); + if (!p.IsPresseQuerSchneckeActive && !p.IsAuger1Active) { + p.StopAuger2(); + p.StopAuger3(); + } } if (p.IsMP1Active && !p.MP1HasClearance) p.StopMP1(); @@ -121,6 +147,66 @@ namespace PamhagenSysCtrl.Helpers { if (p.IsP6Active && !p.P6HasClearance) p.StopP6(); } + protected void UpdateKeyboardBacklights(object? sender, PlantEventArgs evt) { + if (sender is not PamhagenPlant plant || plant.Keyboard is not Xbe24Keyboard k) return; + + k.SetBacklight(0, plant.IsReblerActive ? Color.FromRgb(0, 255, 0) : plant.IsAuger1Active ? Color.FromRgb(255, 255, 0) : plant.IsAuger2Active || plant.IsAuger3Active ? Color.FromRgb(32, 32, 4) : Color.FromRgb(4, 32, 4)); + k.SetBacklight(1, !plant.IsReblerActive ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4)); + + k.SetBacklight(6, !plant.TroughAugerHasClearance ? k.IsKeyPressed(6) ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4) : plant.IsTroughAugerActive ? Color.FromRgb(0, 255, 0) : Color.FromRgb(4, 32, 4)); + k.SetBacklight(7, !plant.IsTroughAugerActive ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4)); + + k.SetBacklight(2, plant.WantMW2Selected || plant.IsMW2Selected ? Color.FromRgb(0, 0, 255) : Color.FromRgb(4, 4, 16), plant.WantMW2Selected && !plant.IsMW2Selected); + k.SetBacklight(8, plant.WantMW1Selected || plant.IsMW1Selected ? Color.FromRgb(0, 0, 255) : Color.FromRgb(4, 4, 16), plant.WantMW1Selected && !plant.IsMW1Selected); + + var buttons = new Dictionary { + [12] = (Graph.MW1, Graph.MP1, Graph.Press1), + [18] = (Graph.MW2, Graph.MP2, Graph.Press1), + [13] = (Graph.MW1, Graph.MP1, Graph.Press2), + [19] = (Graph.MW2, Graph.MP2, Graph.Press2), + [14] = (Graph.MW1, Graph.MP1, Graph.Tank1), + [20] = (Graph.MW2, Graph.MP2, Graph.Tank1), + [15] = (Graph.MW1, Graph.MP1, Graph.Tank2), + [21] = (Graph.MW2, Graph.MP2, Graph.Tank2), + [16] = (Graph.MW1, Graph.MP1, Graph.Tank3), + [22] = (Graph.MW2, Graph.MP2, Graph.Tank3), + [17] = (Graph.MW1, Graph.MP1, Graph.Tank4), + [23] = (Graph.MW2, Graph.MP2, Graph.Tank4), + }; + + bool preparingMP1 = false, preparingMP2 = false; + bool highlightOffMP1 = false, highlightOffMP2 = false; + foreach (var (idx, d) in buttons) { + var isRunning = d.Pump.IsActive; + 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 + var isReady = path.Hops.All(h => h.Node is not Valve v || v.IsOpen == v.WantOpen); + if (!isReady && d.Pump == Graph.MP1) preparingMP1 = true; + if (!isReady && d.Pump == Graph.MP2) preparingMP2 = true; + var isDefault = !path.Hops.SkipLast(1).Any(h => h.Node is not Valve && h.Node is not PipeJoin && h.Node is not Pump && h.Node is not CenterValve); + 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); + } else { + var isFree = Graph.GetFreePath([d.Source, d.Sink], overrideStart: true) != null; + if (isRunning && k.IsKeyPressed(idx) && d.Pump == Graph.MP1) highlightOffMP1 = true; + if (isRunning && k.IsKeyPressed(idx) && d.Pump == Graph.MP2) highlightOffMP2 = true; + k.SetBacklight(idx, isRunning ? k.IsKeyPressed(idx) ? Color.FromRgb(255, 0, 0) : Color.FromRgb(4, 4, 4) : isFree ? Color.FromRgb(8, 8, 32) : k.IsKeyPressed(idx) ? Color.FromRgb(255, 0, 0) : Color.FromRgb(4, 4, 4)); + } + } + + if (!plant.MP2HasPathClearance && plant.MP2HasDryRunClearance && preparingMP2) { + k.SetBacklight(4, Color.FromRgb(4, 32, 4), true); + } else { + k.SetBacklight(4, !plant.MP2HasClearance ? k.IsKeyPressed(4) ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4) : plant.IsMP2Active ? Color.FromRgb(0, 255, 0) : Color.FromRgb(4, 32, 4)); + } + if (!plant.MP1HasPathClearance && plant.MP1HasDryRunClearance && preparingMP1) { + k.SetBacklight(10, Color.FromRgb(4, 32, 4), true); + } else { + k.SetBacklight(10, !plant.MP1HasClearance ? k.IsKeyPressed(10) ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4) : plant.IsMP1Active ? Color.FromRgb(0, 255, 0) : Color.FromRgb(4, 32, 4)); + } + k.SetBacklight(5, highlightOffMP2 ? Color.FromRgb(255, 64, 0) : !plant.IsMP2Active ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4)); + k.SetBacklight(11, highlightOffMP1 ? Color.FromRgb(255, 64, 0) : !plant.IsMP1Active ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4)); + } + public async Task StartThread() { if (Plc == null) throw new Exception(); await Plc.TestConnection(); @@ -133,6 +219,7 @@ namespace PamhagenSysCtrl.Helpers { IsRunning = false; BackgroundThread?.Join(); Plc?.Dispose(); + Keyboard?.Dispose(); GC.SuppressFinalize(this); } @@ -171,6 +258,114 @@ namespace PamhagenSysCtrl.Helpers { } } + public void InitializePaths() { + var add = ReconstructSelectedPaths().ToList(); + var remove = Graph.SelectedPaths.ToList(); + foreach (var p in remove) Graph.RemovePath(p); + for (int i = 1; i <= 57; i++) { + App.Plant?.CloseV(i); + } + foreach (var p in add) { + if (p.Hops.Any(h => h.Node is Valve v && v.IsLocked)) continue; + Graph.AddPath(p); + } + } + + 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 Valve v || p.WantValveOpen(v.Nr)))); + } + if (paths.Count == 1) + yield return paths.First(); + } + } + + public void FastSelect(ISource source, 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)) + throw new NoClearanceException("Pumpe aktiv"); + Graph.RemovePath(p1); + if (p1.Hops.Last().Node == sink) + return; + } + if (Graph.GetFreePath([source, sink]) is Pipeline.Path p2) { + Graph.AddPath(p2); + } + } + + private void Keyboard_KeyDown(object? sender, int keyIndex) { + App.MainDispatcher?.BeginInvoke(() => { + try { + if (keyIndex == 0) { + StartRebler(); + } else if (keyIndex == 1) { + if (IsReblerActive) { + StopRebler(); + } else { + StopAuger1(); + if (!IsPresseQuerSchneckeActive) { + StopAuger2(); + StopAuger3(); + } + } + } else if (keyIndex == 6) { + StartTroughAuger(); + } else if (keyIndex == 7) { + StopTroughAuger(); + } else if (keyIndex == 2) { + SelectMW2(); + } else if (keyIndex == 8) { + SelectMW1(); + } else if (keyIndex == 4) { + StartMP2Forward(); + } else if (keyIndex == 5) { + StopMP2(); + } else if (keyIndex == 10) { + StartMP1Forward(); + } else if (keyIndex == 11) { + StopMP1(); + } else if (keyIndex == 12) { + FastSelect(Graph.MW1, Graph.Press1); + } else if (keyIndex == 13) { + FastSelect(Graph.MW1, Graph.Press2); + } else if (keyIndex == 14) { + FastSelect(Graph.MW1, Graph.Tank1); + } else if (keyIndex == 15) { + FastSelect(Graph.MW1, Graph.Tank2); + } else if (keyIndex == 16) { + FastSelect(Graph.MW1, Graph.Tank3); + } else if (keyIndex == 17) { + FastSelect(Graph.MW1, Graph.Tank4); + } else if (keyIndex == 18) { + FastSelect(Graph.MW2, Graph.Press1); + } else if (keyIndex == 19) { + FastSelect(Graph.MW2, Graph.Press2); + } else if (keyIndex == 20) { + FastSelect(Graph.MW2, Graph.Tank1); + } else if (keyIndex == 21) { + FastSelect(Graph.MW2, Graph.Tank2); + } else if (keyIndex == 22) { + FastSelect(Graph.MW2, Graph.Tank3); + } else if (keyIndex == 23) { + FastSelect(Graph.MW2, Graph.Tank4); + } + } catch (NoClearanceException) { + // Keyboard?.SetBacklight(keyIndex, Color.FromRgb(255, 0, 0)); + } catch (Exception) { + // ignore + } + }); + } + + private void Keyboard_KeysChanged(object? sender, Xbe24Keyboard.KeyState state) { + RaiseUpdateEvent(new()); + } + protected bool HasWTClearanceAA => (AreValvesOpen(12) && AreValvesClosed(15, 16, 20)) || // bypass (AreValvesOpen(15, 18, 16) && AreValvesClosed(11, 12, 14, 17, 19, 20)) || // WT1 @@ -241,7 +436,9 @@ namespace PamhagenSysCtrl.Helpers { (a && ((HasWTClearanceAA && HasT9ClearanceA) || (HasWTClearanceAB && HasT9ClearanceB))) || (b && ((HasWTClearanceBA && HasT9ClearanceA) || (HasWTClearanceBB && HasT9ClearanceB))); - public bool MP1HasClearance => (OverrideDryRunClearances || FillLevelMW1 != FillState.Empty) && ( + public bool MP1HasClearance => MP1HasDryRunClearance && MP1HasPathClearance; + public bool MP1HasDryRunClearance => (OverrideDryRunClearances || FillLevelMW1 != FillState.Empty); + public bool MP1HasPathClearance => ( OverridePathClearances || (MP1HasClearancePress1 && Press1HasClearance) || (MP1HasClearancePress2 && Press2HasClearance) || @@ -269,7 +466,9 @@ namespace PamhagenSysCtrl.Helpers { set => Actuators.MP1 = (value != MotorState.Halt && value != MotorState.Forward && value != MotorState.Backward) ? throw new ArgumentException("Invalid state for MP1") : value; } - public bool MP2HasClearance => (OverrideDryRunClearances || FillLevelMW2 != FillState.Empty) && ( + public bool MP2HasClearance => MP2HasDryRunClearance && MP2HasPathClearance; + public bool MP2HasDryRunClearance => (OverrideDryRunClearances || FillLevelMW2 != FillState.Empty); + public bool MP2HasPathClearance => ( OverridePathClearances || (MP2HasClearancePress1 && Press1HasClearance) || (MP2HasClearancePress2 && Press2HasClearance) || diff --git a/PamhagenSysCtrl/Helpers/Xbe24Keyboard.cs b/PamhagenSysCtrl/Helpers/Xbe24Keyboard.cs new file mode 100644 index 0000000..0f41513 --- /dev/null +++ b/PamhagenSysCtrl/Helpers/Xbe24Keyboard.cs @@ -0,0 +1,114 @@ +using PIEHidNetCore; +using System.IO; +using System.Windows.Media; + +namespace PamhagenSysCtrl.Helpers { + public class Xbe24Keyboard : IDisposable, PIEDataHandler, PIEErrorHandler { + + public enum LedBank { UPPER = 0, LOWER = 1 } + + public record struct KeyState(byte[] Rows) { + public readonly bool GetKey(int n) => (Rows[n / 6] & (1 << (n % 6))) != 0; + } + + protected readonly PIEDevice XbeDevice; + + private KeyState LastKeyState = new(new byte[4]); + private readonly (Color Color, bool Flashing)[] LastStates = new (Color, bool)[48]; + + public bool IsKeyPressed(int keyIndex) => LastKeyState.GetKey(keyIndex); + + public event EventHandler? KeyDown; + public event EventHandler? KeyUp; + public event EventHandler? KeysChanged; + + public Xbe24Keyboard(int pid = 0x0555) { + foreach (var device in PIEDevice.EnumeratePIE()) { + if (device.Pid == pid && device.HidUsagePage == 0x0C && device.HidUsage == 1 && device.WriteLength > 1) { + XbeDevice = device; + break; + } + } + if (XbeDevice == null) + throw new ArgumentException($"No XBE keyboard with id 05f3:{pid:x4}"); + + int res; + if ((res = XbeDevice.SetupInterface()) != 0) + throw new IOException($"Unable to SetupInterface for XBE keyboard: {res}"); + if ((res = XbeDevice.SetDataCallback(this)) != 0) + throw new IOException($"Unable to SetDataCallback for XBE keyboard: {res}"); + if ((res = XbeDevice.SetErrorCallback(this)) != 0) + throw new IOException($"Unable to SetErrorCallback for XBE keyboard: {res}"); + } + + public void Dispose() { + XbeDevice?.CloseInterface(); + } + + public void HandlePIEHidData(byte[] data, PIEDevice sourceDevice, int error) { + if (sourceDevice != XbeDevice) + return; + var current = new KeyState(data[3..8]); + var last = LastKeyState; + LastKeyState = current; + HandleKeyStateChange(current, last); + } + + public void HandlePIEHidError(PIEDevice sourceDevice, int error) { + if (sourceDevice != XbeDevice) + return; + // TODO + } + + protected void HandleKeyStateChange(KeyState current, KeyState last) { + for (int i = 0; i < 24; i++) { + if (!last.GetKey(i) && current.GetKey(i)) { + KeyDown?.Invoke(this, i); + } else if (last.GetKey(i) && !current.GetKey(i)) { + KeyUp?.Invoke(this, i); + } + } + KeysChanged?.Invoke(this, current); + } + + protected void SendCommand(byte command, byte[] payload) { + byte[] data = new byte[XbeDevice.WriteLength]; + data[1] = command; + Array.Copy(payload, 0, data, 2, Math.Min(payload.Length, data.Length - 2)); + + int res; + do { + res = XbeDevice.WriteData(data); + } while (res == 404); + if (res != 0) + throw new IOException($"Unable to WriteData for XBE keyboard: {res}"); + } + + public void SetBacklight(int keyIndex, Color color, bool flashing = false) { + SetBacklight(keyIndex, LedBank.UPPER, color, flashing); + SetBacklight(keyIndex, LedBank.LOWER, color, flashing); + } + + public void SetBacklight(int keyIndex, LedBank bank, Color color, bool flashing = false) { + if (LastStates[keyIndex * 2 + (int)bank] == (color, flashing)) + return; + SendCommand(165, [(byte)keyIndex, (byte)bank, color.R, color.G, color.B, (byte)(flashing ? 1 : 0)]); + LastStates[keyIndex * 2 + (int)bank] = (color, flashing); + } + + public void SetBacklight(Color color) { + SetBacklight(LedBank.UPPER, color); + SetBacklight(LedBank.LOWER, color); + } + + public void SetBacklight(LedBank bank, Color color) { + SendCommand(166, [(byte)bank, color.R, color.G, color.B]); + for (int i = 0; i < LastStates.Length; i++) + LastStates[i] = (color, false); + } + + public void SetFashFrequency(int ms) { + SendCommand(180, [(byte)(ms / 16)]); + } + } +} diff --git a/PamhagenSysCtrl/PIEHidNetCore.dll b/PamhagenSysCtrl/PIEHidNetCore.dll new file mode 100644 index 0000000000000000000000000000000000000000..3dab6e14c14d089e76697a5806c981cd9e221e09 GIT binary patch literal 26112 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 + + PIEHidNetCore.dll + diff --git a/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs b/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs index 2eec3fb..ee02a54 100644 --- a/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs +++ b/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs @@ -11,7 +11,7 @@ namespace PamhagenSysCtrl.Windows { private const double _minMove = 50; - private readonly PamhagenGraph Graph; + private static PamhagenGraph Graph => App.Plant!.Graph; private Point? _lastMousePos; private bool _mouseDragging; @@ -50,26 +50,25 @@ namespace PamhagenSysCtrl.Windows { public PlantSchemeWindow() { InitializeComponent(); Menu_Help_CheckForUpdates.IsEnabled = App.Config.UpdateUrl != null; - Graph = new(); Graph.Draw(SchemeCanvas, RoundedScale); FastSelectPaths = new Dictionary { [FastSelectButton_MW1_Press1] = (Graph.MW1, Graph.MP1, Graph.Press1), [FastSelectButton_MW2_Press1] = (Graph.MW2, Graph.MP2, Graph.Press1), [FastSelectButton_MW1_Press2] = (Graph.MW1, Graph.MP1, Graph.Press2), [FastSelectButton_MW2_Press2] = (Graph.MW2, Graph.MP2, Graph.Press2), - [FastSelectButton_MW1_Tank1] = (Graph.MW1, Graph.MP1, Graph.Tank1), - [FastSelectButton_MW2_Tank1] = (Graph.MW2, Graph.MP2, Graph.Tank1), - [FastSelectButton_MW1_Tank2] = (Graph.MW1, Graph.MP1, Graph.Tank2), - [FastSelectButton_MW2_Tank2] = (Graph.MW2, Graph.MP2, Graph.Tank2), - [FastSelectButton_MW1_Tank3] = (Graph.MW1, Graph.MP1, Graph.Tank3), - [FastSelectButton_MW2_Tank3] = (Graph.MW2, Graph.MP2, Graph.Tank3), - [FastSelectButton_MW1_Tank4] = (Graph.MW1, Graph.MP1, Graph.Tank4), - [FastSelectButton_MW2_Tank4] = (Graph.MW2, Graph.MP2, Graph.Tank4), - [FastSelectButton_MW1_Tank5] = (Graph.MW1, Graph.MP1, Graph.Tank5), - [FastSelectButton_MW2_Tank5] = (Graph.MW2, Graph.MP2, Graph.Tank5), + [FastSelectButton_MW1_Tank1] = (Graph.MW1, Graph.MP1, Graph.Tank1), + [FastSelectButton_MW2_Tank1] = (Graph.MW2, Graph.MP2, Graph.Tank1), + [FastSelectButton_MW1_Tank2] = (Graph.MW1, Graph.MP1, Graph.Tank2), + [FastSelectButton_MW2_Tank2] = (Graph.MW2, Graph.MP2, Graph.Tank2), + [FastSelectButton_MW1_Tank3] = (Graph.MW1, Graph.MP1, Graph.Tank3), + [FastSelectButton_MW2_Tank3] = (Graph.MW2, Graph.MP2, Graph.Tank3), + [FastSelectButton_MW1_Tank4] = (Graph.MW1, Graph.MP1, Graph.Tank4), + [FastSelectButton_MW2_Tank4] = (Graph.MW2, Graph.MP2, Graph.Tank4), + [FastSelectButton_MW1_Tank5] = (Graph.MW1, Graph.MP1, Graph.Tank5), + [FastSelectButton_MW2_Tank5] = (Graph.MW2, Graph.MP2, Graph.Tank5), }; App.Plant?.Update += OnUpdate; - InitializePaths(); + App.Plant?.InitializePaths(); } private void OnClosed(object sender, EventArgs evt) { @@ -96,7 +95,7 @@ namespace PamhagenSysCtrl.Windows { Menu_Settings_OverridePathClearances.IsChecked = false; Menu_Settings_OverrideDryRunClearances.IsChecked = false; - InitializePaths(); + App.Plant?.InitializePaths(); } OnUpdate(null, null); } @@ -211,19 +210,12 @@ namespace PamhagenSysCtrl.Windows { UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed); } - private void FastSelectButton_Click(object sender, RoutedEventArgs evt) { + private void FastSelectButton_Click(object sender, RoutedEventArgs? evt) { if (sender is not Button b || !FastSelectPaths.TryGetValue(b, out var val)) return; - - if (Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == val.Source, null) is Path p1) { - Graph.RemovePath(p1); - if (p1.Hops.Last().Node == val.Sink) - return; - } - if (Graph.GetFreePath([val.Source, val.Sink]) is Path p2) { - Graph.AddPath(p2); - _lastSource = null; - } + App.Plant?.FastSelect(val.Source, val.Sink); + OnUpdate(null, null); + _lastSource = null; } private void OnSizeChanged(object sender, SizeChangedEventArgs evt) { @@ -492,6 +484,7 @@ namespace PamhagenSysCtrl.Windows { 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, e.Pump.IsActive || Graph.GetFreePath([e.Source, e.Sink], overrideStart: true) == null); + } private ISet UpdateScheme(Point pos, bool down) { @@ -511,13 +504,12 @@ namespace PamhagenSysCtrl.Windows { SetFastSelectButton(FastSelectButton_EntryTrough_Stop, App.Plant?.IsTroughAugerActive ?? false ? 0 : 2); SetFastSelectButton(FastSelectButton_MW1, App.Plant?.IsMW1Selected ?? false ? 1 : 0); SetFastSelectButton(FastSelectButton_MW2, App.Plant?.IsMW2Selected ?? false ? 1 : 0); + SetFastSelectButton(FastSelectButton_MP1_Forward, App.Plant?.MP1 == MotorState.Forward ? 1 : 0, !(App.Plant?.MP1HasClearance ?? false)); SetFastSelectButton(FastSelectButton_MP1_Stop, App.Plant?.MP1 == MotorState.Halt ? 2 : 0); + SetFastSelectButton(FastSelectButton_MP2_Forward, App.Plant?.MP2 == MotorState.Forward ? 1 : 0, !(App.Plant?.MP2HasClearance ?? false)); SetFastSelectButton(FastSelectButton_MP2_Stop, App.Plant?.MP2 == MotorState.Halt ? 2 : 0); - foreach (var b in FastSelectPaths) { - SetFastSelectButton(b.Key, 0, false); - } _hover = Graph.GetHover(pos.X, pos.Y, RoundedScale); var hoverButtons = FastSelectPaths.Where(b => b.Key.IsMouseOver).ToDictionary(); @@ -586,8 +578,8 @@ namespace PamhagenSysCtrl.Windows { var paths = Graph.SelectedPaths; if (HoveringPath is Path hp) paths = paths.Append(hp); - foreach (var b in FastSelectPaths.Keys) { - UpdateFastSelectPathButton(paths, hoverPathDeactivate, b); + foreach (var b in FastSelectPaths) { + UpdateFastSelectPathButton(paths, hoverPathDeactivate, b.Key); } Graph.Update(_hover, down); @@ -603,32 +595,5 @@ namespace PamhagenSysCtrl.Windows { } UpdateScheme(Mouse.GetPosition(SchemeCanvas), Mouse.LeftButton == MouseButtonState.Pressed); } - - public void InitializePaths() { - var add = ReconstructSelectedPaths().ToList(); - var remove = Graph.SelectedPaths.ToList(); - foreach (var p in remove) Graph.RemovePath(p); - for (int i = 1; i <= 57; i++) { - App.Plant?.CloseV(i); - } - foreach (var p in add) { - if (p.Hops.Any(h => h.Node is Valve v && v.IsLocked)) continue; - Graph.AddPath(p); - } - } - - 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 Valve v || p.WantValveOpen(v.Nr)))); - } - if (paths.Count == 1) - yield return paths.First(); - } - } } }