diff --git a/PamhagenSysCtrl/PIEHidNetCore.dll b/PamhagenSysCtrl/PIEHidNetCore.dll new file mode 100644 index 0000000..3dab6e1 Binary files /dev/null and b/PamhagenSysCtrl/PIEHidNetCore.dll differ diff --git a/PamhagenSysCtrl/PamhagenSysCtrl.csproj b/PamhagenSysCtrl/PamhagenSysCtrl.csproj index 02df3e7..213dda0 100644 --- a/PamhagenSysCtrl/PamhagenSysCtrl.csproj +++ b/PamhagenSysCtrl/PamhagenSysCtrl.csproj @@ -18,6 +18,9 @@ + + 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);