From fb6f461b8e2721409733b5c205c63e568397b038 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/PIEHidNetCore.dll | Bin 0 -> 26112 bytes PamhagenSysCtrl/PamhagenSysCtrl.csproj | 3 + .../Windows/PlantSchemeWindow.xaml.cs | 241 +++++++++++++++--- 3 files changed, 210 insertions(+), 34 deletions(-) create mode 100644 PamhagenSysCtrl/PIEHidNetCore.dll 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..b614164 100644 --- a/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs +++ b/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs @@ -1,5 +1,6 @@ using PamhagenSysCtrl.Helpers; using PamhagenSysCtrl.Helpers.Pipeline; +using PIEHidNetCore; using System.Diagnostics; using System.Windows; using System.Windows.Controls; @@ -7,10 +8,15 @@ using System.Windows.Input; using System.Windows.Media; namespace PamhagenSysCtrl.Windows { - public partial class PlantSchemeWindow : Window { + public partial class PlantSchemeWindow : Window, PIEDataHandler, PIEErrorHandler { private const double _minMove = 50; + private PIEDevice[]? devices; + private PIEDevice? xbe24; + private byte[]? lastData; + private Color[] lastColor = new Color[24]; + private readonly PamhagenGraph Graph; private Point? _lastMousePos; @@ -45,37 +51,198 @@ namespace PamhagenSysCtrl.Windows { } } - protected Dictionary FastSelectPaths; + protected Dictionary FastSelectPaths; 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), + FastSelectPaths = new Dictionary { + [FastSelectButton_MW1_Press1] = (12, Graph.MW1, Graph.MP1, Graph.Press1), + [FastSelectButton_MW2_Press1] = (18, Graph.MW2, Graph.MP2, Graph.Press1), + [FastSelectButton_MW1_Press2] = (13, Graph.MW1, Graph.MP1, Graph.Press2), + [FastSelectButton_MW2_Press2] = (19, Graph.MW2, Graph.MP2, Graph.Press2), + [FastSelectButton_MW1_Tank1] = (14, Graph.MW1, Graph.MP1, Graph.Tank1), + [FastSelectButton_MW2_Tank1] = (20, Graph.MW2, Graph.MP2, Graph.Tank1), + [FastSelectButton_MW1_Tank2] = (15, Graph.MW1, Graph.MP1, Graph.Tank2), + [FastSelectButton_MW2_Tank2] = (21, Graph.MW2, Graph.MP2, Graph.Tank2), + [FastSelectButton_MW1_Tank3] = (16, Graph.MW1, Graph.MP1, Graph.Tank3), + [FastSelectButton_MW2_Tank3] = (22, Graph.MW2, Graph.MP2, Graph.Tank3), + [FastSelectButton_MW1_Tank4] = (17, Graph.MW1, Graph.MP1, Graph.Tank4), + [FastSelectButton_MW2_Tank4] = (23, Graph.MW2, Graph.MP2, Graph.Tank4), + [FastSelectButton_MW1_Tank5] = (-1, Graph.MW1, Graph.MP1, Graph.Tank5), + [FastSelectButton_MW2_Tank5] = (-1, Graph.MW2, Graph.MP2, Graph.Tank5), }; App.Plant?.Update += OnUpdate; InitializePaths(); + + devices = PIEDevice.EnumeratePIE(); + + if (devices.Length > 0) { + foreach (var device in devices) { + // XBE-24 uses the Consumer HID usage page. + if (device.HidUsagePage == 0x0C && + device.WriteLength > 1) { + xbe24 = device; + break; + } + } + + if (xbe24 == null) { + return; + } + + // Open the HID interface. + xbe24.SetupInterface(); + + // Receive input reports. + xbe24.SetDataCallback(this); + + // Receive device errors, e.g. unplugging. + xbe24.SetErrorCallback(this); + + lastData = new byte[xbe24.ReadLength]; + + MessageBox.Show($"Connected: {xbe24.ProductString}"); + } } private void OnClosed(object sender, EventArgs evt) { + xbe24?.CloseInterface(); + xbe24 = null; + Application.Current.Shutdown(); } + public void HandlePIEHidData(byte[] data, PIEDevice sourceDevice, int error) { + if (xbe24 == null || sourceDevice != xbe24) + return; + Dispatcher.Invoke(() => { + ProcessXbe24Data(data); + }); + } + + public void HandlePIEHidError(PIEDevice sourceDevice, int error) { + if (xbe24 == null || sourceDevice != xbe24) + return; + Dispatcher.Invoke(() => { + MessageBox.Show($"XBE-24: {error}", "XBE-24", MessageBoxButton.OK, MessageBoxImage.Error); + }); + } + + private void ProcessXbe24Data(byte[] data) { + if (xbe24 == null || lastData == null) + return; + + // XBE-24 button data starts at byte 3. + // + // There are 4 button bytes: + // + // byte 3 -> buttons 0-5 + // byte 4 -> buttons 6-11 + // byte 5 -> buttons 12-17 + // byte 6 -> buttons 18-23 + + for (int column = 0; column < 4; column++) { + for (int row = 0; row < 6; row++) { + int button = column * 6 + row; + byte mask = (byte)(1 << row); + + bool nowPressed = + (data[3 + column] & mask) != 0; + + bool previouslyPressed = + (lastData[3 + column] & mask) != 0; + + // Button was just pressed. + if (nowPressed && !previouslyPressed) { + OnButtonPressed(button); + } + + // Button was just released. + if (!nowPressed && previouslyPressed) { + OnButtonReleased(button); + } + } + } + + Array.Copy(data, lastData, xbe24.ReadLength); + } + + private void OnButtonPressed(int button) { + // Your application logic goes here. + // 0 6 12 18 + // 1 7 13 19 + // 2 8 14 20 + // 3 9 15 21 + // 4 10 16 22 + // 5 11 17 23 + + switch (button) { + case 2: FastSelectButton_EntryTroughStop_Click(null, null); break; + case 8: FastSelectButton_EntryTroughStart_Click(null, null); break; + case 3: FastSelectButton_MW2_Click(null, null); break; + case 9: FastSelectButton_MW1_Click(null, null); break; + case 4: + if (FastSelectButton_MP2_Forward.IsEnabled) { + FastSelectButton_MP2_Forward_Click(null, null); + } else { + SetXbeLight(4, Color.FromRgb(255, 0, 0)); + } + break; + case 5: if (FastSelectButton_MP2_Stop.IsEnabled) FastSelectButton_MP2_Stop_Click(null, null); break; + case 10: + if (FastSelectButton_MP1_Forward.IsEnabled) { + FastSelectButton_MP1_Forward_Click(null, null); + } else { + SetXbeLight(10, Color.FromRgb(255, 0, 0)); + } + break; + case 11: if (FastSelectButton_MP1_Stop.IsEnabled) FastSelectButton_MP1_Stop_Click(null, null); break; + case 12: FastSelectButton_Click(FastSelectButton_MW1_Press1, null); break; + case 13: FastSelectButton_Click(FastSelectButton_MW1_Press2, null); break; + case 14: FastSelectButton_Click(FastSelectButton_MW1_Tank1, null); break; + case 15: FastSelectButton_Click(FastSelectButton_MW1_Tank2, null); break; + case 16: FastSelectButton_Click(FastSelectButton_MW1_Tank3, null); break; + case 17: FastSelectButton_Click(FastSelectButton_MW1_Tank4, null); break; + case 18: FastSelectButton_Click(FastSelectButton_MW2_Press1, null); break; + case 19: FastSelectButton_Click(FastSelectButton_MW2_Press2, null); break; + case 20: FastSelectButton_Click(FastSelectButton_MW2_Tank1, null); break; + case 21: FastSelectButton_Click(FastSelectButton_MW2_Tank2, null); break; + case 22: FastSelectButton_Click(FastSelectButton_MW2_Tank3, null); break; + case 23: FastSelectButton_Click(FastSelectButton_MW2_Tank4, null); break; + } + } + + private void OnButtonReleased(int button) { + // Your application logic goes here. + } + + private void SetXbeLight(byte button, Color col, bool flashing = false) { + if (xbe24 == null || lastColor[button] == col) + return; + + byte[] data = new byte[xbe24.WriteLength]; + for (byte i = 0; i < 2; i++) { + data[0] = 0; + data[1] = 0xA5; // Set RGB LED + data[2] = button; + data[3] = i; // upper + lower LED + data[4] = col.R; // Red + data[5] = col.G; // Green + data[6] = col.B; // Blue + data[7] = flashing ? (byte)1 : (byte)0; + + int result; + do { + result = xbe24.WriteData(data); + } while (result == 404); + } + + lastColor[button] = col; + } + private void Menu_Settings_ManualControl_Changed(object sender, RoutedEventArgs evt) { App.Plant?.ManualControlMode = Menu_Settings_ManualControl.IsChecked; if (Menu_Settings_ManualControl.IsChecked) { @@ -211,7 +378,7 @@ 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; @@ -470,28 +637,32 @@ namespace PamhagenSysCtrl.Windows { UpdateScheme(pos, evt.LeftButton == MouseButtonState.Pressed); } - private static void SetFastSelectButton(Button b, int m, bool disabled = false) { + private void SetFastSelectButton(Button b, byte? keyIdx, int m, bool disabled = false) { b.IsEnabled = !disabled; if (m == 1) { b.Background = Brushes.MintCream; b.BorderBrush = Brushes.DarkGreen; b.Foreground = Brushes.DarkGreen; + if (keyIdx is byte k) SetXbeLight(k, Color.FromRgb(0, 255, 0)); } else if (m == 2) { b.Background = Brushes.MistyRose; b.BorderBrush = Brushes.DarkRed; b.Foreground = Brushes.DarkRed; + if (keyIdx is byte k) SetXbeLight(k, Color.FromRgb(255, 0, 0)); } else { b.Background = Brushes.LightGray; b.BorderBrush = Brushes.DimGray; b.Foreground = Brushes.Black; + if (keyIdx is byte k) SetXbeLight(k, disabled ? Color.FromRgb(8, 8, 8) : Color.FromRgb(255, 64, 64)); } } - private void UpdateFastSelectPathButton(IEnumerable paths, Path? hoverPathDeactivate, Button b) { + private void UpdateFastSelectPathButton(IEnumerable paths, Path? hoverPathDeactivate, Button b, byte? keyIdx) { var e = FastSelectPaths[b]; - SetFastSelectButton(b, + SetFastSelectButton(b, keyIdx, 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) { @@ -507,17 +678,16 @@ namespace PamhagenSysCtrl.Windows { } } - SetFastSelectButton(FastSelectButton_EntryTrough_Start, App.Plant?.IsTroughAugerActive ?? false ? 1 : 0, !(App.Plant?.TroughAugerHasClearance ?? false)); - 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); - } + SetFastSelectButton(FastSelectButton_EntryTrough_Start, 8, App.Plant?.IsTroughAugerActive ?? false ? 1 : 0, !(App.Plant?.TroughAugerHasClearance ?? false)); + SetFastSelectButton(FastSelectButton_EntryTrough_Stop, 2, App.Plant?.IsTroughAugerActive ?? false ? 0 : 2); + SetFastSelectButton(FastSelectButton_MW1, 9, App.Plant?.IsMW1Selected ?? false ? 1 : 0); + SetFastSelectButton(FastSelectButton_MW2, 3, App.Plant?.IsMW2Selected ?? false ? 1 : 0); + + SetFastSelectButton(FastSelectButton_MP1_Forward, 10, App.Plant?.MP1 == MotorState.Forward ? 1 : 0, !(App.Plant?.MP1HasClearance ?? false)); + SetFastSelectButton(FastSelectButton_MP1_Stop, 11, App.Plant?.MP1 == MotorState.Halt ? 2 : 0); + + SetFastSelectButton(FastSelectButton_MP2_Forward, 4, App.Plant?.MP2 == MotorState.Forward ? 1 : 0, !(App.Plant?.MP2HasClearance ?? false)); + SetFastSelectButton(FastSelectButton_MP2_Stop, 5, App.Plant?.MP2 == MotorState.Halt ? 2 : 0); _hover = Graph.GetHover(pos.X, pos.Y, RoundedScale); var hoverButtons = FastSelectPaths.Where(b => b.Key.IsMouseOver).ToDictionary(); @@ -584,10 +754,13 @@ namespace PamhagenSysCtrl.Windows { MP2_Target.FontSize = len2 > 24 ? 8 : len2 > 20 ? 10 : len2 > 16 ? 12 : len2 > 12 ? 14 : 16; } + foreach (var b in FastSelectPaths) { + //SetFastSelectButton(b.Key, b.Value.KeyIdx == -1 ? null : (byte)b.Value.KeyIdx, 0, false); + } 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, b.Value.KeyIdx == -1 ? null : (byte)b.Value.KeyIdx); } Graph.Update(_hover, down);