41 Commits
Author SHA1 Message Date
lorenz.stechauner 16f697c017 Fix/Remove error message boxes
Test / Run tests (push) Successful in 16s
2026-11-06 14:26:48 +01:00
lorenz.stechauner f784f7871a Fix hovering with IsMouseOver
Test / Run tests (push) Successful in 16s
2026-11-06 14:17:34 +01:00
lorenz.stechauner eaafc47804 Fix crash in ManualControlWindow 2026-11-06 14:17:00 +01:00
lorenz.stechauner e626ae1910 Add stirrers to graph
Test / Run tests (push) Successful in 14s
2026-11-06 14:02:56 +01:00
lorenz.stechauner 1e31dafce5 Xbe24Keyboard: Update colors
Test / Run tests (push) Successful in 15s
2026-11-06 10:19:24 +01:00
lorenz.stechauner 50f6174af1 Implement remaining XBE-24 and stirrer keys
Test / Run tests (push) Successful in 35s
2026-11-06 09:49:23 +01:00
lorenz.stechauner ed4bbd09bd ManualControlWindow: Small fixes 2026-11-06 09:38:28 +01:00
lorenz.stechauner d0c328d97e Add sensor delay for opening and closing valves
Test / Run tests (push) Successful in 43s
2026-10-16 10:26:17 +02:00
lorenz.stechauner d8c2292b24 Move path logic into Path class
Test / Run tests (push) Successful in 43s
2026-10-15 23:45:32 +02:00
lorenz.stechauner c005378368 Allow configuration of XBE-24 keyboard layout
Test / Run tests (push) Successful in 37s
2026-10-14 16:38:23 +02:00
lorenz.stechauner 4b9bcc6ad3 Add ReconnectThread to reconnect to PLC and Keyboard on the fly
Test / Run tests (push) Successful in 26s
2026-10-13 17:26:35 +02:00
lorenz.stechauner c3f4acaf07 Indicate Valve status with top/bottom lines
Test / Run tests (push) Successful in 16s
2026-10-13 15:31:40 +02:00
lorenz.stechauner 49bdd5d4c0 Refactor PamhagenPlc
Test / Run tests (push) Successful in 36s
2026-10-13 12:45:33 +02:00
lorenz.stechauner 6f076919c7 Add XBE-24 key icons 2026-10-10 22:19:58 +02:00
lorenz.stechauner 947d581c11 Add PLC simulator 2026-10-10 22:19:40 +02:00
lorenz.stechauner 334f201a32 Add support for XBE-24 keyboard
Test / Run tests (push) Successful in 36s
2026-10-10 22:16:50 +02:00
lorenz.stechauner 24beafd9e5 Add status bar 2026-10-10 22:16:14 +02:00
lorenz.stechauner 3d24c65758 Pipeline: Change Valve symbol and add abstract Valve
Test / Run tests (push) Successful in 15s
2026-09-12 19:54:49 +02:00
lorenz.stechauner f4ef8d2f16 Pipeline/Pump: Change triangle to ISO symbol 2026-09-12 19:50:51 +02:00
lorenz.stechauner 3c97d974b5 Bump version to 0.0.4
Test / Run tests (push) Successful in 14s
Deploy / Build and Deploy (push) Successful in 2m40s
2026-08-19 10:11:04 +02:00
lorenz.stechauner 2bad9b8a0b Update dependencies 2026-08-19 10:10:07 +02:00
lorenz.stechauner 0b6b05551b Show only one UpdateDialog at a time
Test / Run tests (push) Successful in 14s
2026-08-19 10:09:06 +02:00
lorenz.stechauner d303f0767a Switch start/stop buttons for entry trough
Test / Run tests (push) Successful in 35s
2026-08-17 01:08:18 +02:00
lorenz.stechauner a18f253fe4 Switch start/stop buttons for MP1/2
Test / Run tests (push) Successful in 32s
2026-08-16 22:45:49 +02:00
lorenz.stechauner 7542b70595 Add T1Error names and descriptions 2026-08-14 13:51:18 +02:00
lorenz.stechauner f2a3ad3423 Reconstruct selected paths
Test / Run tests (push) Successful in 13s
2026-08-14 10:53:42 +02:00
lorenz.stechauner f251994c25 Add scaling to PlantSchemeWindow
Test / Run tests (push) Successful in 13s
2026-08-14 09:59:16 +02:00
lorenz.stechauner 2dee3a9ae9 Rework updating process a bit
Test / Run tests (push) Successful in 13s
2026-08-12 15:01:59 +02:00
lorenz.stechauner 4e82f164c9 Update README and AboutWindow 2026-08-12 14:45:58 +02:00
thomas.hilscher 77f9ae167c Update Über page 2026-08-12 11:38:09 +02:00
thomas.hilscher eb0ec60ed2 Add auto updater 2026-08-12 11:37:07 +02:00
lorenz.stechauner 9d4ae11de8 Ignore empty sensors in tanks
Test / Run tests (push) Successful in 33s
2026-08-12 11:24:14 +02:00
lorenz.stechauner d507a7486e Bump version to 0.0.3
Test / Run tests (push) Successful in 14s
Deploy / Build and Deploy (push) Successful in 2m34s
2026-08-11 14:50:41 +02:00
lorenz.stechauner ece46846b4 Add buttons for controlling pumps
Test / Run tests (push) Successful in 34s
2026-08-11 12:59:08 +02:00
lorenz.stechauner 03c91fb55a Enhance path finding using path cost 2026-08-11 02:01:08 +02:00
lorenz.stechauner f0accc8df8 Add fast select buttons
Test / Run tests (push) Successful in 14s
2026-08-11 00:26:27 +02:00
lorenz.stechauner 3983393e10 Bump version to 0.0.2
Test / Run tests (push) Successful in 32s
Deploy / Build and Deploy (push) Successful in 2m35s
2026-08-10 19:37:59 +02:00
lorenz.stechauner c82adcc054 Handle windows for manual control correctly
Test / Run tests (push) Successful in 13s
2026-08-07 16:15:00 +02:00
lorenz.stechauner 28aaa6b1f3 Use ManualControlMode to disable automatic behaviour
Test / Run tests (push) Successful in 13s
2026-08-07 16:03:56 +02:00
lorenz.stechauner 0ceab368ce Add Handbetrieb support in scheme window
Test / Run tests (push) Successful in 33s
2026-08-07 15:55:22 +02:00
lorenz.stechauner 7c3233361f Add automatic stirrers
Test / Run tests (push) Successful in 14s
2026-08-07 13:08:45 +02:00
57 changed files with 4430 additions and 1571 deletions

No files matched your search

+36
View File
@@ -1,3 +1,39 @@
[plc] [plc]
port = COM1 port = COM1
; sensor delay in seconds
sensor_delay_open = 0
sensor_delay_close = 2
sensor_delay_mw = 10
; from which filling level on does the stirrer start (or stop when below) automatically when pumping
auto_stirrer = Half
[update]
url = https://elwig.at/files/pamhagen-sysctrl/latest
auto = true
[xbe24]
a1 = PATH_MW2_PRESS1
a2 = PATH_MW2_PRESS2
a3 = PATH_MW2_T1
a4 = PATH_MW2_T2
a5 = PATH_MW2_T3
a6 = PATH_MW2_T4
b1 = START_REBLER
b2 = STOP_REBLER
b3 = SELECT_MW2
b4 = TOGGLE_STIRRER_MW2
b5 = START_MP2
b6 = STOP_MP2
c1 = START_TROUGH_AUGER
c2 = STOP_TROUGH_AUGER
c3 = SELECT_MW1
c4 = TOGGLE_STIRRER_MW1
c5 = START_MP1
c6 = STOP_MP1
d1 = PATH_MW1_PRESS1
d2 = PATH_MW1_PRESS2
d3 = PATH_MW1_T1
d4 = PATH_MW1_T2
d5 = PATH_MW1_T3
d6 = PATH_MW1_T4
+54 -10
View File
@@ -1,3 +1,4 @@
using PamhagenSysCtrl.Dialogs;
using PamhagenSysCtrl.Helpers; using PamhagenSysCtrl.Helpers;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
@@ -10,6 +11,8 @@ namespace PamhagenSysCtrl {
public static PamhagenPlant? Plant; public static PamhagenPlant? Plant;
public static Dispatcher? MainDispatcher; public static Dispatcher? MainDispatcher;
private readonly DispatcherTimer AutoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) };
public static readonly string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Anlagensteuerung Pamhagen"); public static readonly string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Anlagensteuerung Pamhagen");
public static readonly string ConfigPath = Path.Combine(DataPath, "config.ini"); public static readonly string ConfigPath = Path.Combine(DataPath, "config.ini");
public static readonly string InstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Anlagensteuerung Pamhagen"); public static readonly string InstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Anlagensteuerung Pamhagen");
@@ -31,20 +34,61 @@ namespace PamhagenSysCtrl {
} }
protected override async void OnStartup(StartupEventArgs evt) { protected override async void OnStartup(StartupEventArgs evt) {
if (Config == null)
return;
Version = new Version(typeof(App).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Split('+')[0] ?? "0.0.0"); Version = new Version(typeof(App).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Split('+')[0] ?? "0.0.0");
try { Plant = new(Config.PlcPort);
Plant = new(Config.PlcPort); base.OnStartup(evt);
await Plant.StartThread();
} catch (Exception exc) { if (Config.UpdateUrl != null && Config.UpdateAuto) {
Plant?.Dispose(); AutoUpdateTimer.Tick += async (_, _) => {
Plant = null; try {
var str = $"Beim Aufbauen einer Verbindung zur SPS ist ein Fehler aufgetreten:\n\n{exc.GetType().Name}: {exc.Message}"; await CheckForUpdates();
if (exc.InnerException != null) str += $"\n\n{exc.InnerException.GetType().Name}: {exc.InnerException.Message}"; } catch { }
MessageBox.Show(str, "SPS-Fehler", MessageBoxButton.OK, MessageBoxImage.Error); };
AutoUpdateTimer.Start();
await Dispatcher.BeginInvoke(async () => {
await Task.Delay(1500);
try {
await CheckForUpdates();
} catch { }
});
}
}
public static async Task CheckForUpdates(bool showResult = false) {
if (Config.UpdateUrl == null) return;
foreach (Window w in Current.Windows) {
if (w is UpdateDialog) return;
} }
base.OnStartup(evt); try {
var latest = await UpdateService.GetLatestInstallerUrl(Config.UpdateUrl);
if (latest.HasValue && new Version(latest.Value.Version) > Version) {
var dialog = new UpdateDialog(latest.Value.Version, latest.Value.Url, latest.Value.Size) {
Owner = Current.MainWindow,
};
if (dialog.ShowDialog() == true) {
Current.Shutdown();
}
} else if (showResult) {
MessageBox.Show($"Die Anlagensteuerung ist auf dem aktuellen Stand.\n(Version {Version})", "Nach Updates suchen", MessageBoxButton.OK, MessageBoxImage.Information);
}
} catch (OperationCanceledException) {
if (showResult) {
MessageBox.Show("Zeitüberschreitung beim Abrufen der Update-Informationen.", "Nach Updates suchen", MessageBoxButton.OK, MessageBoxImage.Error);
} else {
throw;
}
} catch (Exception exc) {
if (showResult) {
MessageBox.Show($"Die Update-Informationen konnten nicht abgerufen werden:\n\n{exc.Message}", "Nach Updates suchen", MessageBoxButton.OK, MessageBoxImage.Error);
} else {
throw;
}
}
} }
protected async void Application_Exit(object sender, ExitEventArgs evt) { protected async void Application_Exit(object sender, ExitEventArgs evt) {
+37
View File
@@ -0,0 +1,37 @@
<Window x:Class="PamhagenSysCtrl.Dialogs.UpdateDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True"
WindowStartupLocation="CenterOwner"
Title="Update verfügbar - Anlagensteuerung Pamhagen"
Height="220" Width="480"
Closed="OnClosed">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock x:Name="Description" FontSize="14" TextAlignment="Center"
HorizontalAlignment="Center" VerticalAlignment="Center">
Version <Run x:Name="VersionText" FontWeight="Bold">0.0.0</Run> ist verfügbar.<LineBreak/>
Soll das Update heruntergeladen und installiert werden?<LineBreak/>
(ca. <Run x:Name="SizeText">0</Run> MB)<LineBreak/><LineBreak/>
<Run FontWeight="Bold">Hinweis:</Run> Die Anlagensteuerung wird für die Installation geschlossen.
</TextBlock>
<StackPanel x:Name="DownloadPanel" Grid.Row="0" VerticalAlignment="Center" Visibility="Collapsed">
<TextBlock x:Name="StatusText" Text="Update wird heruntergeladen..."
FontSize="14" TextAlignment="Center" Margin="0,0,0,10"/>
<ProgressBar x:Name="DownloadProgress" Height="24" Minimum="0" Maximum="100"/>
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,18,0,0">
<Button x:Name="InstallButton" Content="Installieren" IsDefault="True"
Width="110" Height="30" Margin="0,0,12,0" Click="InstallButton_Click"/>
<Button x:Name="CancelButton" Content="Abbrechen" IsCancel="True"
Width="110" Height="30"/>
</StackPanel>
</Grid>
</Window>
@@ -0,0 +1,45 @@
using PamhagenSysCtrl.Helpers;
using System.IO;
using System.Windows;
namespace PamhagenSysCtrl.Dialogs {
public partial class UpdateDialog : Window {
private readonly string Url;
private readonly CancellationTokenSource Cancellation = new();
public UpdateDialog(string version, string url, long size) {
Url = url;
InitializeComponent();
VersionText.Text = version;
SizeText.Text = Math.Ceiling(size / 1024d / 1024d).ToString("N0");
}
private void OnClosed(object? sender, EventArgs evt) {
Cancellation.Cancel();
Cancellation.Dispose();
}
private async void InstallButton_Click(object sender, RoutedEventArgs evt) {
Description.Visibility = Visibility.Collapsed;
DownloadPanel.Visibility = Visibility.Visible;
InstallButton.IsEnabled = false;
try {
var progress = new Progress<double>(value => DownloadProgress.Value = value * 100);
var filename = Path.Combine(App.TempPath, $"PamhagenSysCtrl-{VersionText.Text}.msi");
await UpdateService.DownloadInstaller(Url, filename, progress, Cancellation.Token);
StatusText.Text = "Installer wird gestartet...";
UpdateService.StartInstaller(filename);
DialogResult = true;
} catch (OperationCanceledException) {
// Closing the dialog cancels an active download.
} catch (Exception exc) {
MessageBox.Show($"Das Update konnte nicht installiert werden:\n\n{exc.Message}", "Update installieren", MessageBoxButton.OK, MessageBoxImage.Error);
Description.Visibility = Visibility.Visible;
DownloadPanel.Visibility = Visibility.Collapsed;
InstallButton.IsEnabled = true;
}
}
}
}
+31
View File
@@ -9,9 +9,18 @@ namespace PamhagenSysCtrl.Helpers {
private readonly string FileName; private readonly string FileName;
public string PlcPort = "COM1"; public string PlcPort = "COM1";
public int PlcSensorDelayOpen = 0;
public int PlcSensorDelayClose = 0;
public int PlcSensorDelayMW = 0;
public FillState PlcAutoStirrer = FillState.Half;
public string? UpdateUrl;
public bool UpdateAuto;
public PamhagenPlant.ShortcutKey?[] Xbe24KeyMap;
public Config(string filename) { public Config(string filename) {
FileName = filename; FileName = filename;
Xbe24KeyMap = new PamhagenPlant.ShortcutKey?[24];
Read(); Read();
} }
@@ -19,10 +28,32 @@ namespace PamhagenSysCtrl.Helpers {
try { try {
var config = new ConfigurationBuilder().AddIniFile(FileName).Build(); var config = new ConfigurationBuilder().AddIniFile(FileName).Build();
PlcPort = config["plc:port"] ?? "COM1"; PlcPort = config["plc:port"] ?? "COM1";
PlcSensorDelayOpen = int.Parse(config["plc:sensor_delay_open"] ?? "0");
PlcSensorDelayClose = int.Parse(config["plc:sensor_delay_close"] ?? "0");
PlcSensorDelayMW = int.Parse(config["plc:sensor_delay_mw"] ?? "0");
PlcAutoStirrer = Enum.Parse<FillState>(config["plc:auto_stirrer"] ?? "Half");
UpdateUrl = config["update:url"];
UpdateAuto = TrueValues.Contains(config["update:auto"]?.ToLower());
var xbe24 = config.GetSection("xbe24");
for (char c = 'a'; c <= 'd'; c++) {
for (int r = 1; r <= 6; r++) {
int n = (c - 'a') * 6 + r - 1;
var s = xbe24[$"{c}{r}"]?.ToUpper();
Xbe24KeyMap[n] = s == null ? null : Enum.Parse<PamhagenPlant.ShortcutKey>(s);
}
}
} catch (Exception exc) { } catch (Exception exc) {
MessageBox.Show($"Die Konfigurationsdatei konnte nicht gelesen werden:\n\n{exc.Message}", "Konfigurationsdatei lesen", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"Die Konfigurationsdatei konnte nicht gelesen werden:\n\n{exc.Message}", "Konfigurationsdatei lesen", MessageBoxButton.OK, MessageBoxImage.Error);
Application.Current.Shutdown(); Application.Current.Shutdown();
} }
} }
public PamhagenPlant.ShortcutKey? MapKey(int n) {
return n < 0 || Xbe24KeyMap.Length < n ? null : Xbe24KeyMap[n];
}
public int? MapKey(PamhagenPlant.ShortcutKey key) {
return Xbe24KeyMap.IndexOf(key) is int n ? n == -1 ? null : n : null;
}
} }
} }
+19
View File
@@ -0,0 +1,19 @@
using System.Drawing;
namespace PamhagenSysCtrl.Helpers {
public static class Extensions {
public static void SetBacklight(this Xbe24Keyboard k, PamhagenPlant.ShortcutKey key, (Color Color, bool Flashing) state) {
k.SetBacklight(key, state.Color, state.Flashing);
}
public static void SetBacklight(this Xbe24Keyboard k, PamhagenPlant.ShortcutKey key, Color color, bool flashing = false) {
if (App.Config.MapKey(key) is int n)
k.SetBacklight(n, color, flashing);
}
public static bool IsKeyPressed(this Xbe24Keyboard k, PamhagenPlant.ShortcutKey key) {
return App.Config.MapKey(key) is int n && k.IsKeyPressed(n);
}
}
}
+1 -1
View File
@@ -1,3 +1,3 @@
namespace PamhagenSysCtrl.Helpers { namespace PamhagenSysCtrl.Helpers {
public enum FillState { Empty = 0, Filled = 1, Half = 2, Full = 3, Invalid = -1, Unknown = -2 } public enum FillState { Unknown = 0, Empty = 1, Filled = 2, Quarter = 3, Half = 4, ThreeQuarter = 5, AlmostFull = 6, Full = 7, Invalid = -1 }
} }
+21 -4
View File
@@ -5,16 +5,21 @@ namespace PamhagenSysCtrl.Helpers {
public const double TRANSITION = 1.0 / 4096; public const double TRANSITION = 1.0 / 4096;
public static readonly SolidColorBrush Green = new SolidColorBrush(Color.FromRgb(0x20, 0xE0, 0x20)); public static readonly SolidColorBrush Green = new(Color.FromRgb(0x20, 0xE0, 0x20));
public static readonly SolidColorBrush DimOrange = new SolidColorBrush(Color.FromRgb(0xFF, 0xC0, 0x80)); public static readonly SolidColorBrush DimGreen = new(Color.FromRgb(0xC0, 0xE0, 0xC0));
public static readonly SolidColorBrush Yellow = new SolidColorBrush(Color.FromRgb(0xFF, 0xE0, 0x10)); public static readonly SolidColorBrush DimOrange = new(Color.FromRgb(0xFF, 0xC0, 0x80));
public static readonly SolidColorBrush Red = new SolidColorBrush(Color.FromRgb(0xFF, 0x20, 0x20)); public static readonly SolidColorBrush Yellow = new(Color.FromRgb(0xFF, 0xE0, 0x10));
public static readonly SolidColorBrush Red = new(Color.FromRgb(0xFF, 0x20, 0x20));
public static readonly SolidColorBrush DimRed = new(Color.FromRgb(0xFF, 0xC0, 0xC0));
public static Brush ForFillState(FillState state, Color background) { public static Brush ForFillState(FillState state, Color background) {
return state switch { return state switch {
FillState.Empty => GetEmpty(background), FillState.Empty => GetEmpty(background),
FillState.Filled => GetFilled(background), FillState.Filled => GetFilled(background),
FillState.Quarter => GetQuarter(background),
FillState.Half => GetHalf(background), FillState.Half => GetHalf(background),
FillState.ThreeQuarter => GetThreeQuarter(background),
FillState.AlmostFull => GetAlmostFull(background),
FillState.Full => GetFull(background), FillState.Full => GetFull(background),
_ => GetEmpty(background), _ => GetEmpty(background),
}; };
@@ -32,10 +37,22 @@ namespace PamhagenSysCtrl.Helpers {
return new LinearGradientBrush(background, Green.Color, new(0.5, 0.875 - TRANSITION), new(0.5, 0.875 + TRANSITION)); return new LinearGradientBrush(background, Green.Color, new(0.5, 0.875 - TRANSITION), new(0.5, 0.875 + TRANSITION));
} }
public static Brush GetQuarter(Color background) {
return new LinearGradientBrush(background, Green.Color, new(0.5, 0.75 - TRANSITION), new(0.5, 0.75 + TRANSITION));
}
public static Brush GetHalf(Color background) { public static Brush GetHalf(Color background) {
return new LinearGradientBrush(background, Green.Color, new(0.5, 0.5 - TRANSITION), new(0.5, 0.5 + TRANSITION)); return new LinearGradientBrush(background, Green.Color, new(0.5, 0.5 - TRANSITION), new(0.5, 0.5 + TRANSITION));
} }
public static Brush GetThreeQuarter(Color background) {
return new LinearGradientBrush(background, Green.Color, new(0.5, 0.25 - TRANSITION), new(0.5, 0.25 + TRANSITION));
}
public static Brush GetAlmostFull(Color background) {
return new LinearGradientBrush(background, Green.Color, new(0.5, 0.125 - TRANSITION), new(0.5, 0.125 + TRANSITION));
}
public static Brush GetFull(Color background) { public static Brush GetFull(Color background) {
return new LinearGradientBrush(background, Red.Color, new(0.5, 0.0625 - TRANSITION), new(0.5, 0.0625 + TRANSITION)); return new LinearGradientBrush(background, Red.Color, new(0.5, 0.0625 - TRANSITION), new(0.5, 0.0625 + TRANSITION));
} }
+361 -251
View File
@@ -18,78 +18,102 @@ namespace PamhagenSysCtrl.Helpers {
public readonly Pump P6; public readonly Pump P6;
public readonly EncasedAuger Auger1; public readonly EncasedAuger Auger1;
public readonly EncasedAuger Auger3; public readonly EncasedAuger Auger3;
public readonly Press Press1;
public readonly Press Press2;
public readonly Tank Tank1;
public readonly Tank Tank2;
public readonly Tank Tank3;
public readonly Tank Tank4;
public readonly Tank Tank5;
public readonly Tank Tank6;
public readonly Tank Tank7;
public readonly Tank Tank8;
public readonly Tank Tank9;
public readonly Valve V5;
public readonly Valve V6;
public readonly Valve V7;
public readonly Valve V8;
public readonly Dictionary<PamhagenPlant.ShortcutKey, (ISource Source, Pump Pump, INode? Intermediate, ISink Sink)> PathShortcuts;
public PamhagenGraph() { public PamhagenGraph() {
double mw2X = 180; double mw2X = -118;
double mw1X = 300; double mw1X = -94;
double mpY = 380; double rebX = (mw1X + mw2X) / 2;
double ltg1Y = 510; double rebY = -46;
double ltg2Y = ltg1Y + 30; double mpY = -10;
double ltg1X = 600; double tanksY = -30;
double ltg2X = ltg1X + 30; double ltg1Y = 16;
double ltg6Y = 160; double ltg2Y = ltg1Y + 6;
double ltg7Y = ltg6Y - 30; double ltg1X = -34;
double tanksY = 280; double ltg2X = ltg1X + 6;
double tValveY = ((tanksY - 50) + ltg6Y) / 2 + 5; double ltg3Y = 30;
double wtX = ltg1X - 180; double ltg4Y = ltg3Y + 6;
double wt2Y = 225; double ltg5Y = tanksY + 36;
double wt1Y = wt2Y + 135; double ltg6Y = -54;
double x1X = wtX + 40; double ltg7Y = ltg6Y - 6;
double t1X = 735; double ltg8Y = ltg3Y - 6;
double t2X = t1X + 80; double ltg9Y = ltg4Y + 6;
double t3X = t2X + 80; double tValveY = ((tanksY - 10) + ltg6Y) / 2 + 1;
double t4X = t3X + 80 + 30; double wtX = ltg1X - 36;
double t5X = t4X + 80; double wt2Y = -41;
double t6X = t5X + 80 + 30; double wt1Y = wt2Y + 27;
double t7X = t6X + 80; double x1X = wtX + 8;
double t8X = t7X + 80; double t1X = -7;
double t9X = t8X + 80; double t2X = t1X + 16;
var prY = 720; double t3X = t2X + 16;
var pr1X = 730; double t4X = t3X + 16 + 6;
var pr2X = 920; double t5X = t4X + 16;
double t6X = t5X + 16 + 6;
double t7X = t6X + 16;
double t8X = t7X + 16;
double t9X = t8X + 16;
var prY = 58;
var pr1X = -15;
var pr2X = 23;
var trough = new EntryTrough("Auffangwanne", mw1X + 70, 100, () => App.Plant?.IsTroughAugerActive ?? false); var trough = new EntryTrough("Auffangwanne", mw1X + 14, rebY - 20, () => App.Plant?.State.TroughAuger_IsActive ?? false);
Nodes.Add(trough); Nodes.Add(trough);
Auger1 = new EncasedAuger("", (mw1X + mw2X) / 2 - 100, 200, () => App.Plant?.IsAuger1Active ?? false); Auger1 = new EncasedAuger("", rebX - 19, rebY, () => App.Plant?.State.Auger1_IsActive ?? false);
var auger0 = new EncasedAuger("Pressen-\nQuerschnecke", 40, 200, () => App.Plant?.IsPresseQuerSchneckeActive ?? false, 180, clickable: false); var auger0 = new EncasedAuger("Pressen-\nQuerschnecke", rebX - 39, rebY, () => App.Plant?.State.PressCrossAuger_IsActive ?? false, 180, clickable: false);
Auger3 = new EncasedAuger("", 120, 80, () => App.Plant?.IsAuger3Active ?? false, 135); Auger3 = new EncasedAuger("", rebX - 23, rebY - 24, () => App.Plant?.State.Auger3_IsActive ?? false, 135);
var belt = new ConveyorBelt("", 90, 160, () => App.Plant?.IsAuger2Active ?? false); var belt = new ConveyorBelt("", rebX - 29, rebY - 8, () => App.Plant?.State.Auger2_IsActive ?? false);
Nodes.Add(belt); Nodes.Add(belt);
Nodes.Add(Auger1); Nodes.Add(Auger1);
Nodes.Add(auger0); Nodes.Add(auger0);
Nodes.Add(Auger3); Nodes.Add(Auger3);
var rebler = new Rebler("Rebler", (mw1X + mw2X) / 2, 200, () => App.Plant?.IsReblerActive ?? false); var rebler = new Rebler("Rebler", rebX, rebY, () => App.Plant?.State.Rebler_IsActive ?? false);
Nodes.Add(rebler); Nodes.Add(rebler);
var switcher = new Switcher("", (mw1X + mw2X) / 2, 260, () => App.Plant?.TroughSelector ?? 0, () => App.Plant == null ? 0 : App.Plant.WantMW1Selected ? 1 : 2); var switcher = new Switcher("", rebX, rebY + 12, () => App.Plant?.State.MW1_IsSelected == true ? 1 : App.Plant?.State.MW2_IsSelected == true ? 2 : 0, () => App.Plant?.State.MW1_WantSelected == true ? 1 : App.Plant?.State.MW2_WantSelected == true ? 2 : 0);
Nodes.Add(switcher); Nodes.Add(switcher);
MW1 = new Trough("MW1", mw1X, mpY - 70, () => App.Plant?.FillLevelMW1 ?? FillState.Unknown); MW1 = new Trough("MW1", mw1X, mpY - 14, true, () => App.Plant?.State.MW1_FillLevel ?? FillState.Unknown, () => App.Plant?.State.MW1_Stirrer_IsActive ?? false);
MW2 = new Trough("MW2", mw2X, mpY - 70, () => App.Plant?.FillLevelMW2 ?? FillState.Unknown); MW2 = new Trough("MW2", mw2X, mpY - 14, false, () => App.Plant?.State.MW2_FillLevel ?? FillState.Unknown, () => App.Plant?.State.MW2_Stirrer_IsActive ?? false);
MP1 = new Pump("MP1", mw1X, mpY, () => App.Plant?.IsMP1Active ?? false, () => App.Plant?.MP1HasClearance ?? 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?.IsMP2Active ?? false, () => App.Plant?.MP2HasClearance ?? 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.Contains(x)));
var x1 = new PipeJoin(mw1X, mpY + 40); var x1 = new PipeJoin(mw1X, mpY + 8);
var x2 = new PipeJoin(mw2X, mpY + 40); var x2 = new PipeJoin(mw2X, mpY + 8);
CreateOutlet(MW1, MP1); CreateOutlet(MW1, MP1);
CreateOutlet(MW2, MP2); CreateOutlet(MW2, MP2);
CreatePipe(MP1, x1); CreatePipe(MP1, x1);
CreatePipe(MP2, x2); CreatePipe(MP2, x2);
var v1 = CreateValve(1, mw1X, mpY + 70); var v1 = CreateValve(1, mw1X, mpY + 16, 2);
var v2 = CreateValve(2, mw2X, mpY + 70); var v2 = CreateValve(2, mw2X, mpY + 16, 0);
var v3 = CreateValve(3, mw2X + 30, mpY + 70); var v3 = CreateValve(3, mw2X + 6, mpY + 16, 2);
var v4 = CreateValve(4, mw1X - 30, mpY + 70); var v4 = CreateValve(4, mw1X - 6, mpY + 16, 0);
CreatePipe(x1, v1); CreatePipe(x1, v1);
CreatePipe(x1, v4); CreatePipe(x1, v4);
CreatePipe(x2, v2); CreatePipe(x2, v2);
CreatePipe(x2, v3); CreatePipe(x2, v3);
var x3 = new PipeJoin(mw1X, mpY + 100); var x3 = new PipeJoin(mw1X, ltg1Y);
var x4 = new PipeJoin(x1X, ltg1Y); var x4 = new PipeJoin(x1X, ltg1Y);
var x5 = new PipeJoin(mw2X, mpY + 130); var x5 = new PipeJoin(mw1X - 6, ltg2Y);
var x6 = new PipeJoin(x1X - 30, ltg2Y); var x6 = new PipeJoin(x1X - 6, ltg2Y);
CreatePipe(v1, x3, true); CreatePipe(v1, x3, true);
CreatePipe(v3, x3, true); CreatePipe(v3, x3, true);
@@ -98,50 +122,50 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(v4, x5, true); CreatePipe(v4, x5, true);
CreatePipe(x5, x6, true); CreatePipe(x5, x6, true);
var v5 = CreateValve(5, x1X + 30, ltg1Y); V5 = CreateValve(5, x1X + 6, ltg1Y, 1);
var v6 = CreateValve(6, x1X + 30, ltg2Y + 40); V6 = CreateValve(6, x1X + 6, ltg2Y + 8, 3);
var v7 = CreateValve(7, x1X + 30, ltg2Y); V7 = CreateValve(7, x1X + 6, ltg2Y, 3);
var v8 = CreateValve(8, x1X - 30, ltg2Y + 40); V8 = CreateValve(8, x1X - 6, ltg2Y + 8, 0);
var v9 = CreateValve(9, ltg2X, 500); var v9 = CreateValve(9, ltg2X, ltg5Y + 8, 2);
CreatePipe(x4, v5); CreatePipe(x4, V5);
CreatePipe(x4, v6, true); CreatePipe(x4, V6, true);
CreatePipe(x6, v7); CreatePipe(x6, V7);
CreatePipe(x6, v8); CreatePipe(x6, V8);
CreatePipe(v7, v9, label: "Ltg. 2", labelOffsetX: 30); CreatePipe(V7, v9, label: "Ltg. 2", labelOffsetX: 6);
var xv15 = new PipeJoin(ltg1X, wt1Y + 40); var xv15 = new PipeJoin(ltg1X, wt1Y + 8);
var xv11 = new PipeJoin(ltg2X, wt1Y + 70); var xv11 = new PipeJoin(ltg2X, wt1Y + 14);
var x7 = new PipeJoin(ltg2X, 460); var x7 = new PipeJoin(ltg2X, ltg5Y);
CreatePipe(v5, xv15, label: "Ltg. 1", labelOffsetX: 30); CreatePipe(V5, xv15, label: "Ltg. 1", labelOffsetX: 6);
CreatePipe(v9, x7); CreatePipe(v9, x7);
CreatePipe(x7, xv11); CreatePipe(x7, xv11);
var v10 = CreateValve(10, ltg2X + 40, 460); var v10 = CreateValve(10, ltg2X + 8, ltg5Y, 3);
var v11 = CreateValve(11, ltg1X - 30, wt1Y + 70); var v11 = CreateValve(11, ltg1X - 6, wt1Y + 14, 3);
var v12 = CreateValve(12, ltg1X, wt1Y); var v12 = CreateValve(12, ltg1X, wt1Y, 0);
var v13 = CreateValve(13, ltg2X, wt1Y); var v13 = CreateValve(13, ltg2X, wt1Y, 2);
var v14 = CreateValve(14, ltg1X - 30, wt1Y - 40); var v14 = CreateValve(14, ltg1X - 6, wt1Y - 8, 3);
var v15 = CreateValve(15, ltg1X - 30, wt1Y + 40); var v15 = CreateValve(15, ltg1X - 6, wt1Y + 8, 1);
var v16 = CreateValve(16, ltg1X - 30, wt1Y - 70); var v16 = CreateValve(16, ltg1X - 6, wt1Y - 14, 1);
CreatePipe(v10, x7); CreatePipe(v10, x7);
CreatePipe(xv11, v11); CreatePipe(xv11, v11);
CreatePipe(xv15, v15); CreatePipe(xv15, v15);
CreatePipe(xv15, v12); CreatePipe(xv15, v12);
CreatePipe(xv11, v13); CreatePipe(xv11, v13);
var xv16 = new PipeJoin(ltg1X, wt1Y - 70); var xv16 = new PipeJoin(ltg1X, wt1Y - 14);
var xv14 = new PipeJoin(ltg2X, wt1Y - 40); var xv14 = new PipeJoin(ltg2X, wt1Y - 8);
CreatePipe(v12, xv16); CreatePipe(v12, xv16);
CreatePipe(v13, xv14); CreatePipe(v13, xv14);
CreatePipe(v16, xv16); CreatePipe(v16, xv16);
CreatePipe(v14, xv14); CreatePipe(v14, xv14);
var v20 = CreateValve(20, ltg1X - 30, wt2Y - 70); var v20 = CreateValve(20, ltg1X - 6, wt2Y - 14, 1);
var v21 = CreateValve(21, ltg1X - 30, wt2Y - 40); var v21 = CreateValve(21, ltg1X - 6, wt2Y - 8, 3);
var xv20 = new PipeJoin(ltg1X, wt2Y - 70); var xv20 = new PipeJoin(ltg1X, wt2Y - 14);
var xv21 = new PipeJoin(ltg2X, wt2Y - 40); var xv21 = new PipeJoin(ltg2X, wt2Y - 8);
CreatePipe(xv16, xv20); CreatePipe(xv16, xv20);
CreatePipe(xv14, xv21); CreatePipe(xv14, xv21);
CreatePipe(v20, xv20); CreatePipe(v20, xv20);
@@ -149,15 +173,15 @@ namespace PamhagenSysCtrl.Helpers {
var wt1 = new HeatExchanger("WT Most", wtX, wt1Y); var wt1 = new HeatExchanger("WT Most", wtX, wt1Y);
var wt2 = new HeatExchanger("WT Warmwasser", wtX, wt2Y); var wt2 = new HeatExchanger("WT Warmwasser", wtX, wt2Y);
var v17 = CreateValve(17, wtX + 70, wt1Y); var v17 = CreateValve(17, wtX + 14, wt1Y, 2);
var v18 = CreateValve(18, wtX + 40, wt1Y + 40); var v18 = CreateValve(18, wtX + 8, wt1Y + 8, 3);
var v19 = CreateValve(19, wtX + 40, (wt1Y + wt2Y) / 2); var v19 = CreateValve(19, wtX + 8, (wt1Y + wt2Y) / 2, 0);
var xwt1 = new PipeJoin(wtX + 120, wt1Y + 40); var xwt1 = new PipeJoin(wtX + 24, wt1Y + 8);
var xwt2 = new PipeJoin(wtX + 70, wt1Y + 40); var xwt2 = new PipeJoin(wtX + 14, wt1Y + 8);
var xwt3 = new PipeJoin(wtX + 40, wt1Y - 40); var xwt3 = new PipeJoin(wtX + 8, wt1Y - 8);
var xwt4 = new PipeJoin(wtX + 120, wt1Y - 40); var xwt4 = new PipeJoin(wtX + 24, wt1Y - 8);
var xwt5 = new PipeJoin(wtX + 40, wt2Y + 40); var xwt5 = new PipeJoin(wtX + 8, wt2Y + 8);
var xwt6 = new PipeJoin(wtX + 120, wt2Y - 40); var xwt6 = new PipeJoin(wtX + 24, wt2Y - 8);
CreatePipe(v11, xwt1); CreatePipe(v11, xwt1);
CreatePipe(v15, xwt1); CreatePipe(v15, xwt1);
CreatePipe(xwt1, xwt2); CreatePipe(xwt1, xwt2);
@@ -176,141 +200,141 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(v19, xwt5); CreatePipe(v19, xwt5);
CreatePipe(xwt5, wt2); CreatePipe(xwt5, wt2);
var xv22 = new PipeJoin(t1X - 15, ltg7Y); var xv22 = new PipeJoin(t1X - 3, ltg7Y);
var xv23 = new PipeJoin(t1X + 15, ltg6Y); var xv23 = new PipeJoin(t1X + 3, ltg6Y);
var v22 = CreateValve(22, t1X - 15, tValveY); var v22 = CreateValve(22, t1X - 3, tValveY, 0);
var v23 = CreateValve(23, t1X + 15, tValveY); var v23 = CreateValve(23, t1X + 3, tValveY, 2);
var t1 = CreateTank(1, t1X, tanksY, 35000); Tank1 = CreateTank(1, t1X, tanksY, 35000);
CreatePipe(xv20, xv22, true, label: "Ltg. 7", labelOffsetX: 50); CreatePipe(xv20, xv22, true, label: "Ltg. 7", labelOffsetX: 10);
CreatePipe(xv21, xv23, true, label: "Ltg. 6", labelOffsetX: 20); CreatePipe(xv21, xv23, true, label: "Ltg. 6", labelOffsetX: 4);
CreatePipe(xv22, v22); CreatePipe(xv22, v22);
CreatePipe(xv23, v23); CreatePipe(xv23, v23);
CreateInlet(v22, t1); CreateInlet(v22, Tank1);
CreateInlet(v23, t1); CreateInlet(v23, Tank1);
var xv24 = new PipeJoin(t2X - 15, ltg7Y); var xv24 = new PipeJoin(t2X - 3, ltg7Y);
var xv25 = new PipeJoin(t2X + 15, ltg6Y); var xv25 = new PipeJoin(t2X + 3, ltg6Y);
var v24 = CreateValve(24, t2X - 15, tValveY); var v24 = CreateValve(24, t2X - 3, tValveY, 0);
var v25 = CreateValve(25, t2X + 15, tValveY); var v25 = CreateValve(25, t2X + 3, tValveY, 2);
var t2 = CreateTank(2, t2X, tanksY, 35000); Tank2 = CreateTank(2, t2X, tanksY, 35000);
CreatePipe(xv22, xv24); CreatePipe(xv22, xv24);
CreatePipe(xv23, xv25); CreatePipe(xv23, xv25);
CreatePipe(xv24, v24); CreatePipe(xv24, v24);
CreatePipe(xv25, v25); CreatePipe(xv25, v25);
CreateInlet(v24, t2); CreateInlet(v24, Tank2);
CreateInlet(v25, t2); CreateInlet(v25, Tank2);
var xv26 = new PipeJoin(t3X - 15, ltg7Y); var xv26 = new PipeJoin(t3X - 3, ltg7Y);
var xv27 = new PipeJoin(t3X + 15, ltg6Y); var xv27 = new PipeJoin(t3X + 3, ltg6Y);
var v26 = CreateValve(26, t3X - 15, tValveY); var v26 = CreateValve(26, t3X - 3, tValveY, 0);
var v27 = CreateValve(27, t3X + 15, tValveY); var v27 = CreateValve(27, t3X + 3, tValveY, 2);
var t3 = CreateTank(3, t3X, tanksY, 35000); Tank3 = CreateTank(3, t3X, tanksY, 35000);
CreatePipe(xv24, xv26); CreatePipe(xv24, xv26);
CreatePipe(xv25, xv27); CreatePipe(xv25, xv27);
CreatePipe(xv26, v26); CreatePipe(xv26, v26);
CreatePipe(xv27, v27); CreatePipe(xv27, v27);
CreateInlet(v26, t3); CreateInlet(v26, Tank3);
CreateInlet(v27, t3); CreateInlet(v27, Tank3);
var v28 = CreateValve(28, (t3X + t4X) / 2, ltg7Y); var v28 = CreateValve(28, (t3X + t4X) / 2, ltg7Y, 1);
var v29 = CreateValve(29, (t3X + t4X) / 2, ltg6Y); var v29 = CreateValve(29, (t3X + t4X) / 2, ltg6Y, 3);
CreatePipe(xv26, v28); CreatePipe(xv26, v28);
CreatePipe(xv27, v29); CreatePipe(xv27, v29);
var xv30 = new PipeJoin(t4X - 15, ltg7Y); var xv30 = new PipeJoin(t4X - 3, ltg7Y);
var xv31 = new PipeJoin(t4X + 15, ltg6Y); var xv31 = new PipeJoin(t4X + 3, ltg6Y);
var v30 = CreateValve(30, t4X - 15, tValveY); var v30 = CreateValve(30, t4X - 3, tValveY, 0);
var v31 = CreateValve(31, t4X + 15, tValveY); var v31 = CreateValve(31, t4X + 3, tValveY, 2);
var t4 = CreateTank(4, t4X, tanksY, 35000); Tank4 = CreateTank(4, t4X, tanksY, 35000);
CreatePipe(v28, xv30); CreatePipe(v28, xv30);
CreatePipe(v29, xv31); CreatePipe(v29, xv31);
CreatePipe(xv30, v30); CreatePipe(xv30, v30);
CreatePipe(xv31, v31); CreatePipe(xv31, v31);
CreateInlet(v30, t4); CreateInlet(v30, Tank4);
CreateInlet(v31, t4); CreateInlet(v31, Tank4);
var xv32 = new PipeJoin(t5X - 15, ltg7Y); var xv32 = new PipeJoin(t5X - 3, ltg7Y);
var xv33 = new PipeJoin(t5X + 15, ltg6Y); var xv33 = new PipeJoin(t5X + 3, ltg6Y);
var v32 = CreateValve(32, t5X - 15, tValveY); var v32 = CreateValve(32, t5X - 3, tValveY, 0);
var v33 = CreateValve(33, t5X + 15, tValveY); var v33 = CreateValve(33, t5X + 3, tValveY, 2);
var t5 = CreateTank(5, t5X, tanksY, 35000); Tank5 = CreateTank(5, t5X, tanksY, 35000);
CreatePipe(xv30, xv32, label: "Ltg. 7", labelOffsetX: 30); CreatePipe(xv30, xv32, label: "Ltg. 7", labelOffsetX: 6);
CreatePipe(xv31, xv33, label: "Ltg. 6", labelOffsetX: 0); CreatePipe(xv31, xv33, label: "Ltg. 6", labelOffsetX: 0);
CreatePipe(xv32, v32); CreatePipe(xv32, v32);
CreatePipe(xv33, v33); CreatePipe(xv33, v33);
CreateInlet(v32, t5); CreateInlet(v32, Tank5);
CreateInlet(v33, t5); CreateInlet(v33, Tank5);
var v34 = CreateValve(34, (t5X + t6X) / 2, ltg7Y); var v34 = CreateValve(34, (t5X + t6X) / 2, ltg7Y, 1);
var v35 = CreateValve(35, (t5X + t6X) / 2, ltg6Y); var v35 = CreateValve(35, (t5X + t6X) / 2, ltg6Y, 3);
CreatePipe(xv32, v34); CreatePipe(xv32, v34);
CreatePipe(xv33, v35); CreatePipe(xv33, v35);
var xv36 = new PipeJoin(t6X + 15, ltg6Y); var xv36 = new PipeJoin(t6X + 3, ltg6Y);
var xv37 = new PipeJoin(t6X - 15, ltg7Y); var xv37 = new PipeJoin(t6X - 3, ltg7Y);
var v36 = CreateValve(36, t6X + 15, tValveY); var v36 = CreateValve(36, t6X + 3, tValveY, 2);
var v37 = CreateValve(37, t6X - 15, tValveY); var v37 = CreateValve(37, t6X - 3, tValveY, 0);
var t6 = CreateTank(6, t6X, tanksY, 35000); Tank6 = CreateTank(6, t6X, tanksY, 35000);
CreatePipe(v34, xv37); CreatePipe(v34, xv37);
CreatePipe(v35, xv36); CreatePipe(v35, xv36);
CreatePipe(xv37, v37); CreatePipe(xv37, v37);
CreatePipe(xv36, v36); CreatePipe(xv36, v36);
CreateInlet(v36, t6); CreateInlet(v36, Tank6);
CreateInlet(v37, t6); CreateInlet(v37, Tank6);
var xv38 = new PipeJoin(t7X + 15, ltg6Y); var xv38 = new PipeJoin(t7X + 3, ltg6Y);
var xv39 = new PipeJoin(t7X - 15, ltg7Y); var xv39 = new PipeJoin(t7X - 3, ltg7Y);
var v38 = CreateValve(38, t7X + 15, tValveY); var v38 = CreateValve(38, t7X + 3, tValveY, 2);
var v39 = CreateValve(39, t7X - 15, tValveY); var v39 = CreateValve(39, t7X - 3, tValveY, 0);
var t7 = CreateTank(7, t7X, tanksY, 35000); Tank7 = CreateTank(7, t7X, tanksY, 35000);
CreatePipe(xv37, xv39); CreatePipe(xv37, xv39);
CreatePipe(xv36, xv38); CreatePipe(xv36, xv38);
CreatePipe(xv38, v38); CreatePipe(xv38, v38);
CreatePipe(xv39, v39); CreatePipe(xv39, v39);
CreateInlet(v38, t7); CreateInlet(v38, Tank7);
CreateInlet(v39, t7); CreateInlet(v39, Tank7);
var xv40 = new PipeJoin(t8X + 15, ltg6Y); var xv40 = new PipeJoin(t8X + 3, ltg6Y);
var xv41 = new PipeJoin(t8X - 15, ltg7Y); var xv41 = new PipeJoin(t8X - 3, ltg7Y);
var v40 = CreateValve(40, t8X + 15, tValveY); var v40 = CreateValve(40, t8X + 3, tValveY, 2);
var v41 = CreateValve(41, t8X - 15, tValveY); var v41 = CreateValve(41, t8X - 3, tValveY, 0);
var t8 = CreateTank(8, t8X, tanksY, 35000); Tank8 = CreateTank(8, t8X, tanksY, 35000);
CreatePipe(xv38, xv40, label: "Ltg. 6", labelOffsetX: 0); CreatePipe(xv38, xv40, label: "Ltg. 6", labelOffsetX: 0);
CreatePipe(xv39, xv41, label: "Ltg. 7", labelOffsetX: 30); CreatePipe(xv39, xv41, label: "Ltg. 7", labelOffsetX: 6);
CreatePipe(xv40, v40); CreatePipe(xv40, v40);
CreatePipe(xv41, v41); CreatePipe(xv41, v41);
CreateInlet(v40, t8); CreateInlet(v40, Tank8);
CreateInlet(v41, t8); CreateInlet(v41, Tank8);
var v42 = CreateValve(42, t9X + 15, tValveY); var v42 = CreateValve(42, t9X + 3, tValveY, 2);
var v43 = CreateValve(43, t9X - 15, tValveY); var v43 = CreateValve(43, t9X - 3, tValveY, 0);
var t9 = CreateTank(9, t9X, tanksY, 35000); Tank9 = CreateTank(9, t9X, tanksY, 35000);
CreatePipe(xv40, v42); CreatePipe(xv40, v42);
CreatePipe(xv41, v43); CreatePipe(xv41, v43);
CreateInlet(v42, t9); CreateInlet(v42, Tank9);
CreateInlet(v43, t9); CreateInlet(v43, Tank9);
var xp12 = new PipeJoin((t1X + t2X) / 2, tanksY + 80); var xp12 = new PipeJoin((t1X + t2X) / 2, tanksY + 16);
P12 = new Pump("P1/2", (t1X + t2X) / 2, tanksY + 110, () => App.Plant?.IsP12Active ?? false, () => App.Plant?.P12HasClearance ?? 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(t1, xp12); CreateOutlet(Tank1, xp12);
CreateOutlet(t2, xp12); CreateOutlet(Tank2, xp12);
CreatePipe(xp12, P12); CreatePipe(xp12, P12);
P3 = new Pump("P3", t3X, tanksY + 110, () => App.Plant?.IsP3Active ?? false, () => App.Plant?.P3HasClearance ?? 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 + 110, () => App.Plant?.IsP4Active ?? false, () => App.Plant?.P4HasClearance ?? 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.Contains(x)));
P5 = new Pump("P5", t5X, tanksY + 110, () => App.Plant?.IsP5Active ?? false, () => App.Plant?.P5HasClearance ?? 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.Contains(x)));
CreateOutlet(t3, P3); CreateOutlet(Tank3, P3);
CreateOutlet(t4, P4); CreateOutlet(Tank4, P4);
CreateOutlet(t5, P5); CreateOutlet(Tank5, P5);
var v44 = CreateValve(44, (t1X + t2X) / 2, tanksY + 150); var v44 = CreateValve(44, (t1X + t2X) / 2, tanksY + 30, 2);
var v45 = CreateValve(45, t3X, tanksY + 150); var v45 = CreateValve(45, t3X, tanksY + 30, 2);
var v46 = CreateValve(46, (t3X + t4X) / 2, tanksY + 180); var v46 = CreateValve(46, (t3X + t4X) / 2, ltg5Y, 3);
var v47 = CreateValve(47, t4X, tanksY + 150); var v47 = CreateValve(47, t4X, tanksY + 30, 2);
var v48 = CreateValve(48, t5X, tanksY + 150); var v48 = CreateValve(48, t5X, tanksY + 30, 2);
var xv44 = new PipeJoin((t1X + t2X) / 2, tanksY + 180); var xv44 = new PipeJoin((t1X + t2X) / 2, tanksY + 36);
var xv45 = new PipeJoin(t3X, tanksY + 180); var xv45 = new PipeJoin(t3X, tanksY + 36);
var xv47 = new PipeJoin(t4X, tanksY + 180); var xv47 = new PipeJoin(t4X, tanksY + 36);
CreatePipe(P12, v44); CreatePipe(P12, v44);
CreatePipe(P3, v45); CreatePipe(P3, v45);
CreatePipe(P4, v47); CreatePipe(P4, v47);
@@ -325,45 +349,45 @@ namespace PamhagenSysCtrl.Helpers {
CreateTwoWayPipe(v46, xv45); CreateTwoWayPipe(v46, xv45);
CreateTwoWayPipe(xv45, xv44); CreateTwoWayPipe(xv45, xv44);
var xt6 = new PipeJoin(t6X, tanksY + 110); var xt6 = new PipeJoin(t6X, tanksY + 22);
var xt7 = new PipeJoin(t7X, tanksY + 110); var xt7 = new PipeJoin(t7X, tanksY + 22);
var xt8 = new PipeJoin(t8X, tanksY + 110); var xt8 = new PipeJoin(t8X, tanksY + 22);
P6 = new Pump("P6", t6X, tanksY + 150, () => App.Plant?.IsP6Active ?? false, () => App.Plant?.P6HasClearance ?? 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(t6, xt6); CreateOutlet(Tank6, xt6);
CreateOutlet(t7, xt7); CreateOutlet(Tank7, xt7);
CreateOutlet(t8, xt8); CreateOutlet(Tank8, xt8);
CreateOutlet(t9, xt8); CreateOutlet(Tank9, xt8);
CreatePipe(xt8, xt7); CreatePipe(xt8, xt7);
CreatePipe(xt7, xt6); CreatePipe(xt7, xt6);
CreatePipe(xt6, P6); CreatePipe(xt6, P6);
var v49 = CreateValve(49, t4X, tanksY + 220); var v49 = CreateValve(49, t4X, tanksY + 44, 2);
CreatePipe(xv47, v49); CreatePipe(xv47, v49);
var prX = (pr1X + pr2X) / 2; var prX = (pr1X + pr2X) / 2;
var pr1cX = prX - 15; var pr1cX = prX - 3;
var pr2cX = prX + 15; var pr2cX = prX + 3;
var press1 = new Press("Presse 1", pr1X, prY, 20000, () => FillState.Unknown); Press1 = new Press("Presse 1", pr1X, prY, 20000, () => FillState.Unknown, () => App.Plant?.State.Press1_HasClearance ?? false);
var press2 = new Press("Presse 2", pr2X, prY, 15000, () => FillState.Unknown); Press2 = new Press("Presse 2", pr2X, prY, 15000, () => FillState.Unknown, () => App.Plant?.State.Press2_HasClearance ?? false);
var v50 = CreateValve(50, pr2cX + 30, 580); var v50 = CreateValve(50, pr2cX + 6, ltg3Y, 1);
var v51 = CreateValve(51, pr1cX - 30, 580); var v51 = CreateValve(51, pr1cX - 6, ltg3Y, 1);
var v52 = CreateValve(52, pr1cX - 30, 610); var v52 = CreateValve(52, pr1cX - 6, ltg4Y, 3);
var v53 = CreateValve(53, pr2cX + 30, 610); var v53 = CreateValve(53, pr2cX + 6, ltg4Y, 3);
var v54 = CreateValve(54, pr2cX + 30, 550); var v54 = CreateValve(54, pr2cX + 6, ltg8Y, 1);
var v55 = CreateValve(55, pr1cX - 30, 550); var v55 = CreateValve(55, pr1cX - 6, ltg8Y, 1);
var v56 = CreateValve(56, pr1cX - 30, 640); var v56 = CreateValve(56, pr1cX - 6, ltg9Y, 3);
var v57 = CreateValve(57, pr2cX + 30, 640); var v57 = CreateValve(57, pr2cX + 6, ltg9Y, 3);
var xp1 = new PipeJoin(pr1cX - 70, 580); var xp1 = new PipeJoin(pr1cX - 14, ltg3Y);
var xp2 = new PipeJoin(pr1cX - 70, 610); var xp2 = new PipeJoin(pr1cX - 14, ltg4Y);
var xp3 = new PipeJoin(pr2cX + 70, 550); var xp3 = new PipeJoin(pr2cX + 14, ltg8Y);
var xp4 = new PipeJoin(pr2cX + 70, 640); var xp4 = new PipeJoin(pr2cX + 14, ltg9Y);
var xp5 = new PipeJoin(pr1cX, 580); var xp5 = new PipeJoin(pr1cX, ltg3Y);
var xp6 = new PipeJoin(pr1cX, 610); var xp6 = new PipeJoin(pr1cX, ltg4Y);
var xp7 = new PipeJoin(pr2cX, 550); var xp7 = new PipeJoin(pr2cX, ltg8Y);
var xp8 = new PipeJoin(pr2cX, 640); var xp8 = new PipeJoin(pr2cX, ltg9Y);
CreatePipe(v6, xp1, true, label: "Ltg. 3", labelOffsetX: 30); CreatePipe(V6, xp1, true, label: "Ltg. 3", labelOffsetX: 6);
CreatePipe(v8, xp2, true, label: "Ltg. 4", labelOffsetX: 90); CreatePipe(V8, xp2, true, label: "Ltg. 4", labelOffsetX: 18);
CreatePipe(v49, xp3, true, label: "Ltg. 8"); CreatePipe(v49, xp3, true, label: "Ltg. 8");
CreatePipe(P6, xp4, true, label: "Ltg. 9"); CreatePipe(P6, xp4, true, label: "Ltg. 9");
CreatePipe(xp1, v51, true); CreatePipe(xp1, v51, true);
@@ -386,97 +410,183 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(xp5, xp6); CreatePipe(xp5, xp6);
CreatePipe(xp7, xp8); CreatePipe(xp7, xp8);
var zv1 = new CenterValve("ZV1", pr1cX, prY - 40, () => App.Plant?.Press1HasClearance ?? false); var zv1 = new ExternalValve("ZV1", pr1cX, prY - 8, 0, () => App.Plant?.State.Press1_HasClearance ?? false);
var zv2 = new CenterValve("ZV2", pr2cX, prY - 40, () => App.Plant?.Press2HasClearance ?? false); var zv2 = new ExternalValve("ZV2", pr2cX, prY - 8, 2, () => App.Plant?.State.Press2_HasClearance ?? false);
CreatePipe(xp6, zv1, true); CreatePipe(xp6, zv1, true);
CreatePipe(xp8, zv2, true); CreatePipe(xp8, zv2, true);
CreateInlet(zv1, press1, true); CreateInlet(zv1, Press1, true);
CreateInlet(zv2, press2, true); CreateInlet(zv2, Press2, true);
PathShortcuts = [];
foreach (var key in Enum.GetValues<PamhagenPlant.ShortcutKey>()) {
var n = Enum.GetName(key);
if (n?.StartsWith("PATH_") != true) continue;
ISource src;
Pump p;
INode? inter = null;
ISink sink;
if (n.StartsWith("PATH_MW1_")) {
src = MW1;
p = MP1;
} else if (n.StartsWith("PATH_MW2_")) {
src = MW2;
p = MP2;
} else if (n.StartsWith("PATH_T12_")) {
src = Tank1;
p = P12;
} else if (n.StartsWith("PATH_T3_")) {
src = Tank3;
p = P3;
} else if (n.StartsWith("PATH_T4_")) {
src = Tank4;
p = P4;
} else if (n.StartsWith("PATH_T5_")) {
src = Tank5;
p = P5;
} else if (n.StartsWith("PATH_P6_")) {
src = Tank6;
p = P6;
} else {
continue;
}
if (n.Contains("_LTG1_")) {
inter = V5;
} else if (n.Contains("_LTG2_")) {
inter = V7;
} else if (n.Contains("_LTG3_")) {
inter = V6;
} else if (n.Contains("_LTG4_")) {
inter = V8;
}
if (n.EndsWith("_PRESS1")) {
sink = Press1;
} else if (n.EndsWith("_PRESS2")) {
sink = Press2;
} else if (n.EndsWith("_T1")) {
sink = Tank1;
} else if (n.EndsWith("_T2")) {
sink = Tank2;
} else if (n.EndsWith("_T3")) {
sink = Tank3;
} else if (n.EndsWith("_T4")) {
sink = Tank4;
} else if (n.EndsWith("_T5")) {
sink = Tank5;
} else if (n.EndsWith("_T6")) {
sink = Tank6;
} else if (n.EndsWith("_T7")) {
sink = Tank7;
} else if (n.EndsWith("_T8")) {
sink = Tank8;
} else if (n.EndsWith("_T9")) {
sink = Tank9;
} else {
continue;
}
PathShortcuts[key] = (src, p, inter, sink);
}
} }
protected Valve CreateValve(int n, double x, double y) { protected ControlledValve CreateValve(int n, double x, double y, int o) {
return new(n, x, y, () => App.Plant?.IsValveOpen(n) ?? false, (v) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == v))); return new(n, x, y, o, () => App.Plant?.State.Valve_OpenState(n), (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) { protected static Tank CreateTank(int n, double x, double y, int capacity) {
return new($"Tank {n}", x, y, capacity, () => App.Plant?.TankFillLevels[n - 1] ?? FillState.Unknown); return new($"Tank {n}", x, y, capacity, () => App.Plant?.State.Tanks_FillLevel[n - 1] ?? FillState.Unknown, () => true);
} }
public void TraverseSubgraph(INode start, HashSet<IEdge>? visited = null, Action<IEdge>? edgeAction = null, Action<Valve>? valveAction = null) { private int PathCost(Path p) {
visited ??= []; return
if (start is Valve v) { 1000 * p.Valves.Count() +
valveAction?.Invoke(v); (p.InnerValves.Any(v => v.Nr == 15) && !p.InnerValves.Any(v => v.Nr == 16 || v.Nr == 20) ? 10 : 0) +
return; (p.InnerValves.Any(v => v.Nr == 11) && !p.InnerValves.Any(v => v.Nr == 14 || v.Nr == 21) ? 10 : 0) +
} else if (start is ISink || start is Pump) { (p.InnerValves.Any(v => v.Nr == 6) ? 10 : 0) +
return; p.Bends;
}
public Path? GetFreePath(IEnumerable<INode> points, bool overrideStart = false) {
if (overrideStart) {
return GetPath(points, (n) => n is not ISink && (n is not ControlledValve v || !v.LockedForPaths.Any(p => p.Start != points.First())), PathCost);
} else {
return GetPath(points, (n) => n is not ISink && (n is not ControlledValve v || !v.IsLocked), PathCost);
} }
}
public static IEnumerable<(INode Start, IEdge Edge, bool EdgeReverse, INode End)> TraverseSubgraph(INode start, Func<INode, bool> isBorder, HashSet<IEdge>? _visited = null, bool reverse = false) {
if (isBorder(start))
yield break;
var visited = _visited ?? [];
foreach (var edge in start.Inputs) { foreach (var edge in start.Inputs) {
if (visited.Contains(edge)) continue; if (visited.Contains(edge)) continue;
var o = edge.IsTwoWay && edge.Start == start ? edge.End : edge.Start; var o = edge.IsTwoWay && edge.Start == start ? edge.End : edge.Start;
edgeAction?.Invoke(edge); yield return (start, edge, reverse || _visited == null, o);
visited.Add(edge); visited.Add(edge);
TraverseSubgraph(o, visited, edgeAction, valveAction); foreach (var e in TraverseSubgraph(o, isBorder, visited, reverse || _visited == null))
yield return e;
} }
foreach (var edge in start.Outputs) { foreach (var edge in start.Outputs) {
if (visited.Contains(edge)) continue; if (visited.Contains(edge)) continue;
var o = edge.IsTwoWay && edge.End == start ? edge.Start : edge.End; var o = edge.IsTwoWay && edge.End == start ? edge.Start : edge.End;
edgeAction?.Invoke(edge); yield return (start, edge, reverse, o);
visited.Add(edge); visited.Add(edge);
TraverseSubgraph(o, visited, edgeAction, valveAction); foreach (var e in TraverseSubgraph(o, isBorder, visited, reverse))
yield return e;
} }
} }
public void AddPath(Path path) { public void AddPath(Path path) {
_selectedPaths.Add(path); _selectedPaths.Add(path);
UpdateValvesAdjacientToPath(path, true, false); path.LockValves();
if (App.Plant is PamhagenPlant plant) { if (App.Plant is PamhagenPlant plant) {
foreach (var (_, node) in path.Hops) { foreach (var v in path.InnerValves) plant.OpenV(v.Nr);
if (node is Valve v) { foreach (var v in path.BorderValves) plant.CloseV(v.Nr);
plant.OpenV(v.Nr);
}
}
} }
} }
public void RemovePath(Path path) { public void RemovePath(Path path) {
_selectedPaths.Remove(path); _selectedPaths.Remove(path);
UpdateValvesAdjacientToPath(path, false); path.UnlockValves();
foreach (var p in _selectedPaths) UpdateValvesAdjacientToPath(p, true);
if (App.Plant is PamhagenPlant plant) { if (App.Plant is PamhagenPlant plant) {
foreach (var (_, node) in path.Hops) { foreach (var v in path.InnerValves) plant.CloseV(v.Nr);
if (node is Valve v) {
plant.CloseV(v.Nr);
}
}
} }
} }
private void UpdateValvesAdjacientToPath(Path path, bool locked, bool? wantOpen = null) { public static void ColorPipesAdjacientToPath(Path path, Brush? color1, MotorState flowArrows, Brush? color2) {
foreach (var (edge, node) in path.Hops) {
TraverseSubgraph(node, valveAction: (v) => {
v.IsLocked = locked;
if (wantOpen.HasValue) {
if (wantOpen.Value) {
App.Plant?.OpenV(v.Nr);
} else {
App.Plant?.CloseV(v.Nr);
}
}
});
}
}
public void ColorPipesAdjacientToPath(Path path, Brush? color1, bool flowArrows, Brush? color2) {
var color = color1; var color = color1;
var flowPathOffset = 0.0; var flowPathOffset = 0.0;
HashSet<IEdge> visited = []; HashSet<IEdge> visited = [];
foreach (var (edge, node) in path.Hops) { foreach (var (edge, node) in path.Hops) {
edge.Highlight = color1; edge.Highlight = color1;
edge.Flow = flowArrows ? (edge.End == node, flowPathOffset) : null; if (flowArrows > MotorState.Halt) {
if (edge.Flow != null) flowPathOffset += edge.FlowLength; var dir = edge.End == node;
if (flowArrows == MotorState.Backward) dir = !dir;
edge.Flow = (dir, flowPathOffset);
flowPathOffset += edge.FlowLength;
}
visited.Add(edge); visited.Add(edge);
if (node is Pump) color = color2; if (node is Pump) color = color2;
TraverseSubgraph(node, visited, edgeAction: (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 static void ColorPipesByValves(INode start, Brush? color1, MotorState flowArrows, Brush? color2) {
var flowPathOffset = 0.0;
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;
}
} }
} }
} }
File diff suppressed because it is too large. Load diff
+38 -24
View File
@@ -14,17 +14,21 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public double Angle { get; set; } public double Angle { get; set; }
public bool IsAnimationActive { public bool IsAnimationActive {
get => !(_storyboard?.GetIsPaused() ?? true);
set { set {
if (value) { if (_storyboard == null) return;
_storyboard?.Resume(); if (value && _storyboard.GetIsPaused()) {
} else { _storyboard.Resume();
_storyboard?.Pause(); } else if (!value && !_storyboard.GetIsPaused()) {
_storyboard.Pause();
} }
} }
} }
private Polyline? _polyline; private Polyline? _polyline;
private Storyboard? _storyboard; private Storyboard? _storyboard;
private DoubleAnimation? _leftSlide;
private Canvas? _clipCanvas;
public Auger(double x, double y, double width, double height, double angle = 0) { public Auger(double x, double y, double width, double height, double angle = 0) {
CenterX = x; CenterX = x;
@@ -35,41 +39,51 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
var d = Height / 4;
int n = (int)(Width / d / 4) + 2;
_polyline = new Polyline() { _polyline = new Polyline() {
Points = [.. Enumerable.Range(0, n).SelectMany(i => new List<Point>() {
new(i * 4 * d, Height / 2), new((i * 4 + 1) * d, 0),
new((i * 4 + 2) * d, Height / 2), new((i * 4 + 3) * d, Height)
}), new(n * 4 * d, Height / 2), new(0, Height / 2)],
Fill = Brushes.Transparent, Fill = Brushes.Transparent,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
}; };
_polyline.SetValue(Canvas.LeftProperty, 0.0); _polyline.SetValue(Canvas.LeftProperty, 0.0);
var slideLeft = new DoubleAnimation { _leftSlide = new DoubleAnimation {
From = 0, From = 0,
To = -d * 4,
Duration = new Duration(TimeSpan.FromSeconds(1)), Duration = new Duration(TimeSpan.FromSeconds(1)),
RepeatBehavior = RepeatBehavior.Forever, RepeatBehavior = RepeatBehavior.Forever,
}; };
Storyboard.SetTarget(_leftSlide, _polyline);
_storyboard = new Storyboard(); _storyboard = new Storyboard();
_storyboard.Children.Add(slideLeft); _storyboard.Children.Add(_leftSlide);
Storyboard.SetTarget(slideLeft, _polyline); Storyboard.SetTargetProperty(_leftSlide, new PropertyPath(Canvas.LeftProperty));
Storyboard.SetTargetProperty(slideLeft, new PropertyPath(Canvas.LeftProperty)); _clipCanvas = new Canvas() {
var clipCanvas = new Canvas() {
Width = Width,
Height = Height,
ClipToBounds = true, ClipToBounds = true,
RenderTransform = new RotateTransform(Angle, Width / 2, Height / 2),
}; };
clipCanvas.SetValue(Canvas.TopProperty, CenterY - Height / 2); _clipCanvas.Children.Add(_polyline);
clipCanvas.SetValue(Canvas.LeftProperty, CenterX - Width / 2); canvas.Children.Add(_clipCanvas);
clipCanvas.Children.Add(_polyline);
canvas.Children.Add(clipCanvas);
_storyboard.Begin(); _storyboard.Begin();
_storyboard.Pause(); _storyboard.Pause();
} }
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
var d = Height / 4 * scale;
int n = (int)(Width * scale / d / 4) + 2;
_polyline?.Points = [.. Enumerable.Range(0, n).SelectMany(i => new List<Point>() {
new(i * 4 * d, Height / 2 * scale), new((i * 4 + 1) * d, 0),
new((i * 4 + 2) * d, Height / 2 * scale), new((i * 4 + 3) * d, Height * scale)
}), new(n * 4 * d, Height / 2 * scale), new(0, Height / 2 * scale)];
_polyline?.StrokeThickness = border;
var active = IsAnimationActive;
_leftSlide?.To = -d * 4;
_storyboard?.Stop();
_storyboard?.Begin();
if (!active) _storyboard?.Pause();
_clipCanvas?.Width = Width * scale;
_clipCanvas?.Height = Height * scale;
_clipCanvas?.RenderTransform = new RotateTransform(Angle, Width / 2 * scale, Height / 2 * scale);
_clipCanvas?.SetValue(Canvas.TopProperty, (CenterY - Height / 2) * scale);
_clipCanvas?.SetValue(Canvas.LeftProperty,( CenterX - Width / 2) * scale);
}
} }
} }
@@ -1,70 +0,0 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline {
public class CenterValve : INode {
public const double DIAMETER = 24;
public double CenterX { get; set; }
public double CenterY { get; set; }
public string Label { get; set; }
public Brush? Highlight { get; set; }
public bool IsOpen => CallbackIsOpen();
public ISet<IEdge> Inputs { get; init; }
public ISet<IEdge> Outputs { get; init; }
protected Func<bool> CallbackIsOpen;
private Shape? _circle;
private TextBlock? _text;
public CenterValve(string label, double x, double y, Func<bool> cbOpen) {
CenterX = x;
CenterY = y;
Label = label;
CallbackIsOpen = cbOpen;
Inputs = new HashSet<IEdge>();
Outputs = new HashSet<IEdge>();
}
public void Draw(Canvas canvas) {
_circle = new Ellipse() {
Height = DIAMETER,
Width = DIAMETER,
Fill = Brushes.DarkGray,
Stroke = Brushes.Black,
StrokeThickness = 2,
};
_circle.SetValue(Canvas.LeftProperty, CenterX - DIAMETER / 2);
_circle.SetValue(Canvas.TopProperty, CenterY - DIAMETER / 2);
_text = new() {
Text = Label,
FontSize = 10,
Width = DIAMETER,
Height = DIAMETER,
TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
SnapsToDevicePixels = true,
};
_text.SetValue(Canvas.LeftProperty, CenterX - DIAMETER / 2 + 0.5);
_text.SetValue(Canvas.TopProperty, CenterY - DIAMETER / 2 + 5.5);
canvas.Children.Add(_circle);
canvas.Children.Add(_text);
}
public bool IsInside(double x, double y) {
return Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= DIAMETER / 2;
}
public void Update(bool isHovering, bool isPressed) {
_circle?.Fill =
!IsOpen ? PamhagenBrushes.Red :
IsOpen ? PamhagenBrushes.Green :
Highlight ?? Brushes.DarkGray;
}
}
}
@@ -0,0 +1,51 @@
using System.Windows;
using System.Windows.Media;
namespace PamhagenSysCtrl.Helpers.Pipeline {
public class ControlledValve : Valve {
public int Nr { get; set; }
public override string Label => $"V{Nr}";
public readonly ISet<Path> LockedForPaths = new HashSet<Path>();
public bool IsLocked => LockedForPaths.Any();
public bool WantOpen => CallbackWantOpen(this);
protected Func<Valve, bool> CallbackWantOpen;
public ControlledValve(int nr, double x, double y, int orientation, Func<bool?> cbOpen, Func<Valve, bool> cbWant) :
base(x, y, orientation, cbOpen) {
Nr = nr;
CallbackWantOpen = cbWant;
}
public override void Update(bool isHovering, bool isPressed) {
if (App.Plant?.ManualControlMode ?? false) {
_text?.Visibility = Visibility.Visible;
_triangles?.Fill =
!WantOpen && IsClosed && !isHovering ? PamhagenBrushes.Red :
!WantOpen && IsClosed ? PamhagenBrushes.Green :
!WantOpen && !IsClosed ? PamhagenBrushes.DimRed :
WantOpen && !IsOpen ? PamhagenBrushes.DimGreen :
WantOpen && IsOpen && !isHovering ? PamhagenBrushes.Green :
WantOpen && IsOpen ? PamhagenBrushes.Red :
Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray);
} else {
_text?.Visibility = isHovering ? Visibility.Visible : Visibility.Hidden;
_triangles?.Fill =
IsLocked && !WantOpen && IsClosed ? PamhagenBrushes.DimOrange :
IsLocked && !WantOpen && !IsClosed ? PamhagenBrushes.Red :
IsLocked && WantOpen && !IsOpen ? PamhagenBrushes.Yellow :
IsLocked && WantOpen && IsOpen ? PamhagenBrushes.Green :
Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray);
}
if (!IsClosed) {
_topLine?.Points[1] = _middlePoints.Open.Top;
_bottomLine?.Points[1] = _middlePoints.Open.Bottom;
} else {
_topLine?.Points[1] = _middlePoints.Closed.Top;
_bottomLine?.Points[1] = _middlePoints.Closed.Bottom;
}
}
}
}
@@ -7,8 +7,8 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class ConveyorBelt : INode { public class ConveyorBelt : INode {
public const double WIDTH = 30; public const double WIDTH = 6;
public const double HEIGHT = 120; public const double HEIGHT = 24;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -18,22 +18,23 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public bool IsActive => CallbackActive(); public bool IsActive => CallbackActive();
public bool IsAnimationActive { public bool IsAnimationActive {
get => _animationActive; get => !(_storyboard?.GetIsPaused() ?? true);
set { set {
_animationActive = value; if (_storyboard == null) return;
if (value) { if (value && _storyboard.GetIsPaused()) {
_storyboard?.Resume(); _storyboard.Resume();
} else { } else if (!value && !_storyboard.GetIsPaused()) {
_storyboard?.Pause(); _storyboard.Pause();
} }
} }
} }
protected Func<bool> CallbackActive; protected Func<bool> CallbackActive;
private bool _animationActive; private Rectangle? _rect;
private Shape? _rect;
private Polyline? _polyline; private Polyline? _polyline;
private DoubleAnimation? _slideUp;
private Canvas? _clipCanvas;
private Storyboard? _storyboard; private Storyboard? _storyboard;
public ConveyorBelt(string label, double x, double y, Func<bool> cbActive) { public ConveyorBelt(string label, double x, double y, Func<bool> cbActive) {
@@ -47,52 +48,63 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_rect = new Rectangle() { _rect = new Rectangle() {
Width = WIDTH,
Height = HEIGHT,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
StrokeLineJoin = PenLineJoin.Round, StrokeLineJoin = PenLineJoin.Round,
}; };
_rect.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_rect.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2);
var d = 15;
int n = (int)(HEIGHT / d) + 2;
_polyline = new Polyline() { _polyline = new Polyline() {
Points = [.. Enumerable.Range(0, n).SelectMany(i => new List<Point>() {
new(-WIDTH, i * d), new(WIDTH, i * d), new(-WIDTH, i * d)
})],
Fill = Brushes.Transparent, Fill = Brushes.Transparent,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2, StrokeThickness = 2,
}; };
_polyline.SetValue(Canvas.TopProperty, 0.0); _polyline.SetValue(Canvas.TopProperty, 0.0);
var slideUp = new DoubleAnimation { _slideUp = new DoubleAnimation {
From = 0, From = 0,
To = -d,
Duration = new Duration(TimeSpan.FromSeconds(1)), Duration = new Duration(TimeSpan.FromSeconds(1)),
RepeatBehavior = RepeatBehavior.Forever, RepeatBehavior = RepeatBehavior.Forever,
}; };
Storyboard.SetTargetProperty(_slideUp, new PropertyPath(Canvas.TopProperty));
_storyboard = new Storyboard(); _storyboard = new Storyboard();
_storyboard.Children.Add(slideUp); _storyboard.Children.Add(_slideUp);
Storyboard.SetTarget(slideUp, _polyline); Storyboard.SetTarget(_slideUp, _polyline);
Storyboard.SetTargetProperty(slideUp, new PropertyPath(Canvas.TopProperty)); _clipCanvas = new Canvas() {
var clipCanvas = new Canvas() {
Width = WIDTH - 15,
Height = HEIGHT - 8,
ClipToBounds = true, ClipToBounds = true,
}; };
clipCanvas.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2 + 4); _clipCanvas.Children.Add(_polyline);
clipCanvas.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2 + 7.5);
clipCanvas.Children.Add(_polyline);
canvas.Children.Add(_rect); canvas.Children.Add(_rect);
canvas.Children.Add(clipCanvas); canvas.Children.Add(_clipCanvas);
_storyboard.Begin(); _storyboard.Begin();
_storyboard.Pause(); _storyboard.Pause();
}
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_rect?.Width = WIDTH * scale;
_rect?.Height = HEIGHT * scale;
_rect?.StrokeThickness = border;
_rect?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale);
_rect?.SetValue(Canvas.TopProperty, (CenterY - HEIGHT / 2) * scale);
var d = Math.Round(3 * scale);
int n = (int)(HEIGHT * scale / d) + 2;
_polyline?.Points = [.. Enumerable.Range(0, n).SelectMany(i => new List<Point>() {
new(-WIDTH * scale, i * d), new(WIDTH * scale, i * d), new(-WIDTH * scale, i * d)
})];
_polyline?.StrokeThickness = border;
var active = IsAnimationActive;
_slideUp?.To = -d;
_storyboard?.Stop();
_storyboard?.Begin();
if (!active) _storyboard?.Pause();
_clipCanvas?.Width = (WIDTH - 2.2) * scale - border * 2;
_clipCanvas?.Height = (HEIGHT - 0.8) * scale - border * 2;
_clipCanvas?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2 + 1.1) * scale + border);
_clipCanvas?.SetValue(Canvas.TopProperty, (CenterY - HEIGHT / 2 + 0.4) * scale + border);
} }
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
@@ -102,11 +114,11 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public void Update(bool isHovering, bool isPressed) { public void Update(bool isHovering, bool isPressed) {
IsAnimationActive = IsActive; IsAnimationActive = IsActive;
_rect?.Fill = _rect?.Fill =
IsAnimationActive && isHovering && isPressed ? PamhagenBrushes.Red : IsActive && isHovering && isPressed ? PamhagenBrushes.Red :
IsAnimationActive && isHovering ? PamhagenBrushes.Red : IsActive && isHovering ? PamhagenBrushes.Red :
!IsAnimationActive && isHovering && isPressed ? PamhagenBrushes.Green : !IsActive && isHovering && isPressed ? PamhagenBrushes.Green :
!IsAnimationActive && isHovering ? PamhagenBrushes.Green : !IsActive && isHovering ? PamhagenBrushes.Green :
IsAnimationActive ? PamhagenBrushes.Green : IsActive ? PamhagenBrushes.Green :
Brushes.WhiteSmoke; Brushes.WhiteSmoke;
} }
} }
@@ -6,8 +6,8 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class EncasedAuger : INode { public class EncasedAuger : INode {
public const double WIDTH = 80; public const double WIDTH = 16;
public const double HEIGHT = 20; public const double HEIGHT = 4;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -29,8 +29,9 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
protected Func<bool> CallbackActive; protected Func<bool> CallbackActive;
private bool _animationActive; private bool _animationActive;
private Shape? _inner; private Canvas? _canvas;
private Shape? _outer; private Rectangle? _inner;
private Rectangle? _outer;
private Auger? _auger; private Auger? _auger;
private TextBlock? _text; private TextBlock? _text;
@@ -46,41 +47,49 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
var c = new Canvas() { _canvas = new Canvas() {
RenderTransform = new RotateTransform(Angle, WIDTH / 2, HEIGHT / 2), RenderTransform = new RotateTransform(Angle),
}; };
_outer = new Rectangle() { _outer = new Rectangle() {
Width = WIDTH,
Height = HEIGHT,
Fill = Brushes.Black, Fill = Brushes.Black,
}; };
_inner = new Rectangle() { _inner = new Rectangle() {
Width = WIDTH,
Height = HEIGHT - 4,
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
}; };
c.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
c.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2); _canvas.Children.Add(_outer);
_inner.SetValue(Canvas.TopProperty, 2.0); _canvas.Children.Add(_inner);
c.Children.Add(_outer); canvas.Children.Add(_canvas);
c.Children.Add(_inner);
canvas.Children.Add(c);
_text = new() { _text = new() {
Text = Label, Text = Label,
Width = WIDTH,
FontSize = 12,
TextAlignment = TextAlignment.Center, TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
}; };
_text.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_text.SetValue(Canvas.TopProperty, CenterY + HEIGHT / 2);
canvas.Children.Add(_text); canvas.Children.Add(_text);
_auger = new Auger(CenterX, CenterY, WIDTH, 12, Angle); _auger = new Auger(CenterX, CenterY, WIDTH, 2.4, Angle);
_auger.Draw(canvas); _auger.Draw(canvas);
} }
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_canvas?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale);
_canvas?.SetValue(Canvas.TopProperty, (CenterY - HEIGHT / 2) * scale);
_canvas?.RenderTransform = new RotateTransform(Angle, WIDTH / 2 * scale, HEIGHT / 2 * scale);
_outer?.Width = WIDTH * scale;
_outer?.Height = HEIGHT * scale;
_inner?.Width = WIDTH * scale;
_inner?.Height = HEIGHT * scale - border * 2;
_inner?.SetValue(Canvas.TopProperty, border);
_text?.FontSize = 12;
_text?.Width = WIDTH * scale;
_text?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale);
_text?.SetValue(Canvas.TopProperty, (CenterY + HEIGHT / 2) * scale);
_auger?.Scale(scale);
}
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
return Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= WIDTH / 2; return Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= WIDTH / 2;
} }
+47 -22
View File
@@ -7,8 +7,8 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
class EntryTrough : INode { class EntryTrough : INode {
public const double WIDTH = 150; public const double WIDTH = 30;
public const double HEIGHT = 60; public const double HEIGHT = 12;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -20,9 +20,8 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public bool IsAnimationActive { public bool IsAnimationActive {
get => _animationActive; get => _animationActive;
set { set {
_animationActive = value;
_auger?.IsAnimationActive = value; _auger?.IsAnimationActive = value;
if (value) { if (value && !_animationActive) {
_doorTransform?.BeginAnimation( _doorTransform?.BeginAnimation(
RotateTransform.AngleProperty, RotateTransform.AngleProperty,
new DoubleAnimation { new DoubleAnimation {
@@ -30,7 +29,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
To = 60, To = 60,
Duration = TimeSpan.FromSeconds(Math.Abs(_doorTransform.Angle - 60) / 60.0) Duration = TimeSpan.FromSeconds(Math.Abs(_doorTransform.Angle - 60) / 60.0)
}); });
} else { } else if (!value && _animationActive) {
_doorTransform?.BeginAnimation( _doorTransform?.BeginAnimation(
RotateTransform.AngleProperty, RotateTransform.AngleProperty,
new DoubleAnimation { new DoubleAnimation {
@@ -39,17 +38,23 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
Duration = TimeSpan.FromSeconds(Math.Abs(_doorTransform.Angle - 0) / 60.0) Duration = TimeSpan.FromSeconds(Math.Abs(_doorTransform.Angle - 0) / 60.0)
}); });
} }
_animationActive = value;
} }
} }
public double Top => CenterY - HEIGHT / 2;
public double Right => CenterX + WIDTH / 2;
public double Bottom => CenterY + HEIGHT / 2;
public double Left => CenterX - WIDTH / 2;
protected Func<bool> CallbackActive; protected Func<bool> CallbackActive;
private bool _animationActive; private bool _animationActive;
private Shape? _inner; private Polygon? _inner;
private Shape? _outer; private Polygon? _outer;
private TextBlock? _text; private TextBlock? _text;
private Auger? _auger; private Auger? _auger;
private Shape? _door; private Line? _door;
private RotateTransform? _doorTransform; private RotateTransform? _doorTransform;
public EntryTrough(string label, double x, double y, Func<bool> cbActive) { public EntryTrough(string label, double x, double y, Func<bool> cbActive) {
@@ -62,42 +67,62 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
var top = CenterY - HEIGHT / 2;
var right = CenterX + WIDTH / 2;
var bottom = CenterY + HEIGHT / 2;
var left = CenterX - WIDTH / 2;
_outer = new Polygon() { _outer = new Polygon() {
Points = [new(left, top), new(right, top), new(right - 20, bottom), new(left, bottom), new(left, bottom - 20), new(left + 12.5, bottom - 20)],
Fill = Brushes.Black, Fill = Brushes.Black,
}; };
_inner = new Polygon() { _inner = new Polygon() {
Points = [new(left + 2, top), new(right - 2, top), new(right - 20 - 2, bottom - 2), new(left, bottom - 2), new(left, bottom - 18), new(left + 15.5, bottom - 18)],
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
}; };
_doorTransform = new RotateTransform(0, left, bottom - 24); _doorTransform = new RotateTransform(0);
_door = new Line() { _door = new Line() {
X1 = left, Y1 = bottom - 24, X2 = left, Y2 = bottom + 4,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 4,
RenderTransform = _doorTransform, RenderTransform = _doorTransform,
}; };
_text = new() { _text = new() {
Text = Label, Text = Label,
FontSize = 12,
Width = WIDTH,
TextAlignment = TextAlignment.Center, TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
}; };
_text.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_text.SetValue(Canvas.TopProperty, CenterY - 7.5 - 8);
canvas.Children.Add(_outer); canvas.Children.Add(_outer);
canvas.Children.Add(_inner); canvas.Children.Add(_inner);
canvas.Children.Add(_text); canvas.Children.Add(_text);
canvas.Children.Add(_door); canvas.Children.Add(_door);
_auger = new(CenterX - 11, CenterY + HEIGHT / 2 - 9, WIDTH - 25, 12); _auger = new(CenterX - 2.2, CenterY + HEIGHT / 2 - 1.8, WIDTH - 5, 2.4);
_auger.Draw(canvas); _auger.Draw(canvas);
} }
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_outer?.Points = [
new(Left * scale, Top * scale),
new(Right * scale, Top * scale),
new((Right - 4) * scale, Bottom * scale),
new(Left * scale, Bottom * scale),
new(Left * scale, (Bottom - 4) * scale),
new((Left + 2.5) * scale, (Bottom - 4) * scale),
];
_inner?.Points = [
new(Left * scale + border, Top * scale),
new(Right * scale - border, Top * scale),
new((Right - 4) * scale - border, Bottom * scale - border),
new(Left * scale, Bottom * scale - border),
new(Left * scale, (Bottom - 3.6) * scale),
new((Left + 3.1) * scale, (Bottom - 3.6) * scale),
];
_door?.X1 = Left * scale;
_door?.Y1 = (Bottom - 4.8) * scale;
_door?.X2 = Left * scale;
_door?.Y2 = (Bottom + 0.8) * scale;
_door?.StrokeThickness = border * 2;
_doorTransform?.CenterX = Left * scale;
_doorTransform?.CenterY = (Bottom - 4.8) * scale;
_text?.FontSize = 12;
_text?.Width = WIDTH * scale;
_text?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale);
_text?.SetValue(Canvas.TopProperty, (CenterY - 1.5) * scale - 8);
_auger?.Scale(scale);
}
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2; return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2;
} }
@@ -0,0 +1,29 @@
using System.Windows;
using System.Windows.Media;
namespace PamhagenSysCtrl.Helpers.Pipeline {
public class ExternalValve : Valve {
public override string Label { get; }
public ExternalValve(string label, double x, double y, int orientation, Func<bool?> cbOpen) :
base(x, y, orientation, cbOpen) {
Label = label;
}
public override void Update(bool isHovering, bool isPressed) {
_text?.Visibility = isHovering || (App.Plant?.ManualControlMode ?? false) ? Visibility.Visible : Visibility.Hidden;
_triangles?.Fill =
!IsOpen ? PamhagenBrushes.Red :
IsOpen ? PamhagenBrushes.Green :
Highlight ?? Brushes.DarkGray;
if (!IsClosed) {
_topLine?.Points[1] = _middlePoints.Open.Top;
_bottomLine?.Points[1] = _middlePoints.Open.Bottom;
} else {
_topLine?.Points[1] = _middlePoints.Closed.Top;
_bottomLine?.Points[1] = _middlePoints.Closed.Bottom;
}
}
}
}
+18 -21
View File
@@ -7,42 +7,40 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
internal sealed class FlowArrows { internal sealed class FlowArrows {
public const double SPEED = 20; public const double SPEED = 4;
public const double SPACING = 15; public const double SPACING = 3;
public const double LENGTH = 7; public const double LENGTH = 1.4;
public const double WIDTH = 7; public const double WIDTH = 1.4;
private static readonly Stopwatch Clock = Stopwatch.StartNew(); private static readonly Stopwatch Clock = Stopwatch.StartNew();
private double _scale = 5;
private readonly Canvas _layer; private readonly Canvas _layer;
private readonly Point[] _path; private readonly Point[] _path;
private Point[] _points = []; private Point[] _points = [];
private double _length;
private double _pathOffset; private double _pathOffset;
private bool _isActive; private bool _isActive;
public double Length => GetLength(_path[0], _path[1]) + GetLength(_path[1], _path[2]); public double Length { get; init; }
private FlowArrows(Canvas canvas, Point[] path) { private FlowArrows(Point[] path) {
_path = path; _path = path;
Length = _path.Length <= 1 ? 0 : Enumerable.Range(0, _path.Length - 1).Sum(i => GetLength(_path[i], _path[i + 1]));
_layer = new Canvas() { _layer = new Canvas() {
IsHitTestVisible = false, IsHitTestVisible = false,
Visibility = Visibility.Collapsed, Visibility = Visibility.Collapsed,
}; };
_layer.Unloaded += (_, _) => Stop(); _layer.Unloaded += (_, _) => Stop();
canvas.Children.Add(_layer);
} }
public static FlowArrows Create(Canvas canvas, Line first, Line second) { public static FlowArrows Create(Canvas canvas, IEnumerable<Point> points) {
return new FlowArrows(canvas, [ var arrows = new FlowArrows([.. points]);
new Point(first.X1, first.Y1), canvas.Children.Add(arrows._layer);
new Point(first.X2, first.Y2), return arrows;
new Point(second.X2, second.Y2),
]);
} }
public static FlowArrows Create(Canvas canvas, Polyline path) { public void Scale(double scale) {
return new FlowArrows(canvas, [.. path.Points]); _scale = scale;
} }
public void Update((bool TowardEnd, double PathOffset)? flow) { public void Update((bool TowardEnd, double PathOffset)? flow) {
@@ -52,7 +50,6 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
_points = flow.Value.TowardEnd ? _path : [.. _path.Reverse()]; _points = flow.Value.TowardEnd ? _path : [.. _path.Reverse()];
_length = GetLength(_points[0], _points[1]) + GetLength(_points[1], _points[2]);
_pathOffset = flow.Value.PathOffset; _pathOffset = flow.Value.PathOffset;
if (!_isActive) { if (!_isActive) {
CompositionTarget.Rendering += OnRendering; CompositionTarget.Rendering += OnRendering;
@@ -69,7 +66,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
private void Render() { private void Render() {
var distance = ((Clock.Elapsed.TotalSeconds * SPEED - _pathOffset) % SPACING + SPACING) % SPACING; var distance = ((Clock.Elapsed.TotalSeconds * SPEED - _pathOffset) % SPACING + SPACING) % SPACING;
var arrowIndex = 0; var arrowIndex = 0;
while (distance <= _length) { while (distance <= Length) {
var arrow = GetArrow(arrowIndex++); var arrow = GetArrow(arrowIndex++);
SetPosition(arrow, distance); SetPosition(arrow, distance);
arrow.Visibility = Visibility.Visible; arrow.Visibility = Visibility.Visible;
@@ -145,9 +142,9 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
direction = v2; direction = v2;
} }
double ux = direction.X; double ux = direction.X * _scale;
double uy = direction.Y; double uy = direction.Y * _scale;
(arrow.RenderTransform as MatrixTransform)?.Matrix = new Matrix(ux, uy, -uy, ux, position.X, position.Y); (arrow.RenderTransform as MatrixTransform)?.Matrix = new Matrix(ux, uy, -uy, ux, position.X * _scale, position.Y * _scale);
} }
private void Stop() { private void Stop() {
+83 -26
View File
@@ -1,5 +1,7 @@
using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Shapes; using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
@@ -54,64 +56,119 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
return p; return p;
} }
public Path? GetPath(IEnumerable<INode> points, Func<INode, bool>? valid = null) { public Path? GetPath(IEnumerable<INode> points, Func<INode, bool>? valid = null, Func<Path, int>? cost = null) {
if (points.Count() < 2) return null; if (points.Count() < 2) return null;
var start = points.First(); var start = points.First();
var path = new Path(start, []); List<Path> paths = [new(start, [])];
foreach (var end in points.Skip(1)) { foreach (var end in points.Skip(1)) {
if (valid != null && start != points.First() && !valid(start)) { if (valid != null && start != points.First() && !valid(start))
return null; return null;
} else if (GetPath(start, end, valid, visited: [.. path.Hops.Select(h => h.Node)]) is not Path p) {
return null; var newPaths = new List<Path>();
} else { foreach (var path in paths) {
path.Hops = [.. path.Hops, .. p.Hops]; foreach (var subPath in GetPaths(start, end, visited: [.. path.Hops.Select(h => h.Node)], valid)) {
start = end; newPaths.Add(new(path.Start, [.. path.Hops, .. subPath.Hops]));
}
} }
if (newPaths.Count == 0)
return null;
paths = newPaths;
start = end;
}
if (paths.Count == 0) {
return null;
} else {
cost ??= p => 0;
return paths.OrderBy(cost).First();
} }
return path;
} }
public Path? GetPath(INode start, INode end, Func<INode, bool>? valid = null, HashSet<INode>? visited = null) { public IEnumerable<Path> GetPaths(INode start, INode end, HashSet<INode>? visited = null, Func<INode, bool>? valid = null) {
if (!Nodes.Contains(start) || !Nodes.Contains(end)) throw new ArgumentException("Start/end node not contained in graph"); if (!Nodes.Contains(start) || !Nodes.Contains(end)) throw new ArgumentException("Start/end node not contained in graph");
valid ??= a => true; valid ??= a => true;
visited = [.. visited ?? [], start]; visited = [.. visited ?? [], start];
List<(IEdge, INode)> hops = []; List<(IEdge, INode)> hops = [];
Path? path = null;
foreach (var o in start.Outputs) { foreach (var o in start.Outputs) {
var e = o.IsTwoWay && o.End == start ? o.Start : o.End; var e = o.IsTwoWay && o.End == start ? o.Start : o.End;
if (e == end) { if (e == end) {
return new(start, [(o, e)]); yield return new(start, [(o, e)]);
continue;
} else if (!valid(e) || visited.Contains(e)) { } else if (!valid(e) || visited.Contains(e)) {
continue; continue;
} }
var p = GetPath(e, end, valid, visited); foreach (var p in GetPaths(e, end, visited, valid)) {
if (p != null) { yield return new(start, [(o, e), .. p.Hops]);
var p2 = new Path(start, [(o, e), .. p.Value.Hops]);
if (path == null || p2.Bends < path.Value.Bends || (p2.Bends == path.Value.Bends && p2.Length < path.Value.Length)) {
path = p2;
}
} }
} }
return path != null ? new(start, path.Value.Hops) : null;
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas, double scale) {
canvas.Children.Add(new Rectangle() { var bg = new Rectangle() {
Fill = Brushes.White, Fill = Brushes.White,
Width = 5000, Width = 8000,
Height = 5000, Height = 8000,
}); };
SetPos(bg, -4000, -4000, 1);
canvas.Children.Add(bg);
foreach (var e in Edges) { foreach (var e in Edges) {
e.Draw(canvas); e.Draw(canvas);
e.Scale(scale);
} }
foreach (var n in Nodes) { foreach (var n in Nodes) {
n.Draw(canvas); n.Draw(canvas);
n.Scale(scale);
} }
} }
public ISet<INode> GetHover(double x, double y) { public void Scale(double scale) {
return Nodes.Where(n => n.IsInside(x, y)).ToHashSet(); foreach (var e in Edges) {
e.Scale(scale);
}
foreach (var n in Nodes) {
n.Scale(scale);
}
}
public static double ToPx(double v, double scale) {
return Math.Round(v * scale);
}
public static Point ToPx(double x, double y, double scale, double dxpx = 0, double dypx = 0, bool round = true) {
var p = new Point(x * scale + dxpx, y * scale + dypx);
return round ? new(Math.Round(p.X), Math.Round(p.Y)) : p;
}
public static Point ToPx(Point p, double scale) {
return ToPx(p.X, p.Y, scale);
}
public static double ToBorder(double scale) {
return scale > 10 ? 4 : scale > 7.5 ? 3 : scale > 4 ? 2 : 1;
}
public static void SetPos(FrameworkElement? s, double x, double y, double scale, double dxpx = 0, double dypx = 0) {
s?.SetValue(Canvas.LeftProperty, ToPx(x, scale) + dxpx);
s?.SetValue(Canvas.TopProperty, ToPx(y, scale) + dypx);
}
public static void SetWH(Shape? s, double w, double h, double scale) {
s?.Width = ToPx(w, scale);
s?.Height = ToPx(h, scale);
}
public static void SetCenter(Shape? s, double cx, double cy, double w, double h, double scale) {
Graph.SetWH(s, w, h, scale);
Graph.SetPos(s, cx - w / 2, cy - h / 2, scale);
}
public ISet<INode> GetHover(Point? p, double scale) {
return p is Point v ? GetHover(v.X, v.Y, scale) : new HashSet<INode>();
}
public ISet<INode> GetHover(double x, double y, double scale) {
return Nodes.Where(n => n.IsInside(x / scale, y / scale)).ToHashSet();
} }
public void Update(ISet<INode> hovering, bool isPressed) { public void Update(ISet<INode> hovering, bool isPressed) {
@@ -6,8 +6,8 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class HeatExchanger : INode { public class HeatExchanger : INode {
public const double WIDTH = 30; public const double WIDTH = 6;
public const double HEIGHT = 120; public const double HEIGHT = 24;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -28,29 +28,30 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_rect = new Rectangle() { _rect = new Rectangle() {
Width = WIDTH,
Height = HEIGHT,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
}; };
_text = new() { _text = new() {
Text = Label, Text = Label,
FontSize = 12,
Width = HEIGHT,
TextAlignment = TextAlignment.Center, TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
RenderTransform = new RotateTransform(270), RenderTransform = new RotateTransform(270),
SnapsToDevicePixels = true, SnapsToDevicePixels = true,
}; };
_rect.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_rect.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2);
_text.SetValue(Canvas.LeftProperty, CenterX - 9);
_text.SetValue(Canvas.TopProperty, CenterY + HEIGHT / 2);
canvas.Children.Add(_rect); canvas.Children.Add(_rect);
canvas.Children.Add(_text); canvas.Children.Add(_text);
} }
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
Graph.SetWH(_rect, WIDTH, HEIGHT, scale);
Graph.SetPos(_rect, CenterX - WIDTH / 2, CenterY - HEIGHT / 2, scale);
_rect?.StrokeThickness = border;
_text?.FontSize = 12;
_text?.Width = Math.Round(HEIGHT * scale);
Graph.SetPos(_text, CenterX, CenterY + HEIGHT / 2, scale, dxpx: -9);
}
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2; return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2;
} }
@@ -14,6 +14,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public double FlowLength { get; } public double FlowLength { get; }
public void Draw(Canvas canvas); public void Draw(Canvas canvas);
public void Scale(double scale);
public void Update(); public void Update();
} }
} }
+1 -1
View File
@@ -10,8 +10,8 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public ISet<IEdge> Outputs { get; } public ISet<IEdge> Outputs { get; }
public void Draw(Canvas canvas); public void Draw(Canvas canvas);
public void Scale(double scale);
public void Update(bool isHovering, bool isPressed); public void Update(bool isHovering, bool isPressed);
public bool IsInside(double x, double y); public bool IsInside(double x, double y);
} }
} }
@@ -2,6 +2,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public interface ISink : INode { public interface ISink : INode {
public double TopY { get; } public double TopY { get; }
public bool IsFull { get; } public bool IsFull { get; }
public bool HasFillingClearance { get; }
public int? CapacityLiters { get; } public int? CapacityLiters { get; }
} }
} }
+26 -49
View File
@@ -15,10 +15,15 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public (bool TowardEnd, double PathOffset)? Flow { get; set; } public (bool TowardEnd, double PathOffset)? Flow { get; set; }
public double FlowLength => _flowArrows?.Length ?? 0; public double FlowLength => _flowArrows?.Length ?? 0;
private Line? _outer1; public double X1 => Start.CenterX;
private Line? _outer2; public double Y1 => Start.CenterY;
private Line? _inner1; public double X2 => Start.CenterX;
private Line? _inner2; public double Y2 => Orientation ? Sink.CenterY - 2 : Sink.TopY - 2;
public double X3 => Orientation ? Sink.CenterX : Start.CenterX + (Start.CenterX == End.CenterX ? 0 : Start.CenterX > End.CenterX ? -4 : 4);
public double Y3 => Orientation ? Sink.CenterY - 2 : Sink.TopY + 4;
private Polyline? _outer;
private Polyline? _inner;
private FlowArrows? _flowArrows; private FlowArrows? _flowArrows;
public Inlet(INode start, ISink end, bool orientation = false) { public Inlet(INode start, ISink end, bool orientation = false) {
@@ -28,62 +33,34 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
var x1 = Start.CenterX; _outer = new Polyline() {
var y1 = Start.CenterY;
var x2 = Start.CenterX;
var y2 = Orientation ? Sink.CenterY - 10 : Sink.TopY - 10;
var x3 = Orientation ? Sink.CenterX : Start.CenterX + (Start.CenterX == End.CenterX ? 0 : Start.CenterX > End.CenterX ? -20 : 20);
var y3 = Orientation ? Sink.CenterY - 10 : Sink.TopY + 20;
_outer1 = new Line() {
X1 = x1,
Y1 = y1,
X2 = x2,
Y2 = y2,
StrokeThickness = 10,
Stroke = Brushes.Black,
StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round,
};
_inner1 = new Line() {
X1 = x1,
Y1 = y1,
X2 = x2,
Y2 = y2,
StrokeThickness = 6,
Stroke = Brushes.DarkGray,
StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round,
};
_outer2 = new Line() {
X1 = x2,
Y1 = y2,
X2 = x3,
Y2 = y3,
StrokeThickness = 10,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeStartLineCap = PenLineCap.Round, StrokeStartLineCap = PenLineCap.Round,
StrokeLineJoin = PenLineJoin.Round,
StrokeEndLineCap = PenLineCap.Flat, StrokeEndLineCap = PenLineCap.Flat,
}; };
_inner2 = new Line() { _inner = new Polyline() {
X1 = x2,
Y1 = y2,
X2 = x3,
Y2 = y3,
StrokeThickness = 6,
Stroke = Brushes.DarkGray, Stroke = Brushes.DarkGray,
StrokeStartLineCap = PenLineCap.Round, StrokeStartLineCap = PenLineCap.Round,
StrokeLineJoin = PenLineJoin.Round,
StrokeEndLineCap = PenLineCap.Flat, StrokeEndLineCap = PenLineCap.Flat,
}; };
canvas.Children.Add(_outer1); canvas.Children.Add(_outer);
canvas.Children.Add(_outer2); canvas.Children.Add(_inner);
canvas.Children.Add(_inner1); _flowArrows = FlowArrows.Create(canvas, [new(X1, Y1), new(X2, Y2), new(X3, Y3)]);
canvas.Children.Add(_inner2); }
_flowArrows = FlowArrows.Create(canvas, _inner1!, _inner2!);
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_outer?.Points = [Graph.ToPx(X1, Y1, scale), Graph.ToPx(X2, Y2, scale), Graph.ToPx(X3, Y3, scale)];
_inner?.Points = [Graph.ToPx(X1, Y1, scale), Graph.ToPx(X2, Y2, scale), Graph.ToPx(X3, Y3, scale)];
_inner?.StrokeThickness = Math.Round(1.2 * scale / 2) * 2;
_outer?.StrokeThickness = Math.Round(1.2 * scale / 2) * 2 + border * 2;
_flowArrows?.Scale(scale);
} }
public void Update() { public void Update() {
_inner1?.Stroke = Highlight ?? Brushes.DarkGray; _inner?.Stroke = Highlight ?? Brushes.DarkGray;
_inner2?.Stroke = Highlight ?? Brushes.DarkGray;
_flowArrows?.Update(Flow); _flowArrows?.Update(Flow);
} }
} }
+29 -52
View File
@@ -5,7 +5,7 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Outlet : IEdge { public class Outlet : IEdge {
public const double POINT = 16; public const double POINT = 3.2;
public INode Start => Source; public INode Start => Source;
public INode End { get; set; } public INode End { get; set; }
@@ -17,12 +17,17 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public (bool TowardEnd, double PathOffset)? Flow { get; set; } public (bool TowardEnd, double PathOffset)? Flow { get; set; }
public double FlowLength => _flowArrows?.Length ?? 0; public double FlowLength => _flowArrows?.Length ?? 0;
public double X1 => Start.CenterX;
public double Y1 => Source.BottomY;
public double X2 => Orientation ? Start.CenterX : End.CenterX;
public double Y2 => Orientation ? End.CenterY : Start.CenterY;
public double X3 => End.CenterX;
public double Y3 => End.CenterY;
private Polygon? _hopper; private Polygon? _hopper;
private FlowArrows? _flowArrows; private FlowArrows? _flowArrows;
private Line? _outer1; private Polyline? _outer;
private Line? _outer2; private Polyline? _inner;
private Line? _inner1;
private Line? _inner2;
public Outlet(ISource start, INode end) { public Outlet(ISource start, INode end) {
Source = start; Source = start;
@@ -31,69 +36,41 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
var x1 = Start.CenterX ;
var y1 = Source.BottomY;
var x2 = Orientation ? Start.CenterX : End.CenterX;
var y2 = Orientation ? End.CenterY : Start.CenterY;
var x3 = End.CenterX;
var y3 = End.CenterY;
_hopper = new Polygon() { _hopper = new Polygon() {
Points = [new(x1 - POINT, y1 - 2), new(x1 + POINT, y1 - 2), new(x1, y1 + POINT)],
Fill = Brushes.DarkGray, Fill = Brushes.DarkGray,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
}; };
_outer1 = new Line() { _outer = new Polyline() {
X1 = x1,
Y1 = y1,
X2 = x2,
Y2 = y2,
StrokeThickness = 10,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeStartLineCap = PenLineCap.Round, StrokeStartLineCap = PenLineCap.Round,
StrokeLineJoin = PenLineJoin.Round,
StrokeEndLineCap = PenLineCap.Round, StrokeEndLineCap = PenLineCap.Round,
}; };
_inner1 = new Line() { _inner = new Polyline() {
X1 = x1,
Y1 = y1,
X2 = x2,
Y2 = y2,
StrokeThickness = 6,
Stroke = Brushes.DarkGray, Stroke = Brushes.DarkGray,
StrokeStartLineCap = PenLineCap.Round, StrokeStartLineCap = PenLineCap.Round,
StrokeLineJoin = PenLineJoin.Round,
StrokeEndLineCap = PenLineCap.Round, StrokeEndLineCap = PenLineCap.Round,
}; };
_outer2 = new Line() { canvas.Children.Add(_outer);
X1 = x2,
Y1 = y2,
X2 = x3,
Y2 = y3,
StrokeThickness = 10,
Stroke = Brushes.Black,
StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round,
};
_inner2 = new Line() {
X1 = x2,
Y1 = y2,
X2 = x3,
Y2 = y3,
StrokeThickness = 6,
Stroke = Brushes.DarkGray,
StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round,
};
canvas.Children.Add(_outer1);
canvas.Children.Add(_outer2);
canvas.Children.Add(_hopper); canvas.Children.Add(_hopper);
canvas.Children.Add(_inner1); canvas.Children.Add(_inner);
canvas.Children.Add(_inner2); _flowArrows = FlowArrows.Create(canvas, [new(X1, Y1), new(X2, Y2), new(X3, Y3)]);
_flowArrows = FlowArrows.Create(canvas, _inner1!, _inner2!); }
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_hopper?.Points = [Graph.ToPx(X1 - POINT, Y1, scale, dypx: -border * 2), Graph.ToPx(X1 + POINT, Y1, scale, dypx: -border * 2), Graph.ToPx(X1, Y1 + POINT, scale)];
_hopper?.StrokeThickness = border;
_outer?.Points = [Graph.ToPx(X1, Y1, scale), Graph.ToPx(X2, Y2, scale), Graph.ToPx(X3, Y3, scale)];
_inner?.Points = [Graph.ToPx(X1, Y1, scale), Graph.ToPx(X2, Y2, scale), Graph.ToPx(X3, Y3, scale)];
_inner?.StrokeThickness = Math.Round(1.2 * scale / 2) * 2;
_outer?.StrokeThickness = Math.Round(1.2 * scale / 2) * 2 + border * 2;
_flowArrows?.Scale(scale);
} }
public void Update() { public void Update() {
_inner1?.Stroke = Highlight ?? Brushes.DarkGray; _inner?.Stroke = Highlight ?? Brushes.DarkGray;
_inner2?.Stroke = Highlight ?? Brushes.DarkGray;
_hopper?.Fill = Highlight ?? Brushes.DarkGray; _hopper?.Fill = Highlight ?? Brushes.DarkGray;
_flowArrows?.Update(Flow); _flowArrows?.Update(Flow);
} }
+38 -4
View File
@@ -1,9 +1,43 @@
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public record struct Path( public record struct Path(
INode Start, INode Start,
IEnumerable<(IEdge Edge, INode Node)> Hops) (IEdge Edge, INode Node)[] Hops
{ ) {
public readonly double 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 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 double Length => Hops.Sum(h => Math.Abs(h.Edge.Start.CenterX - h.Edge.End.CenterX) + Math.Abs(h.Edge.Start.CenterY - h.Edge.End.CenterY));
public readonly INode End => Hops.Last().Node;
public readonly IEnumerable<INode> Nodes => [Start, .. Hops.Select(h => h.Node)];
public readonly bool Contains(INode node) => Nodes.Any(h => h == node);
public readonly bool Matches(INode start, INode end) => Matches(start, null, end);
public readonly bool Matches(INode start, INode? intermediate, INode end) => Start == start && End == end && (intermediate == null || Nodes.Any(n => n == intermediate));
public readonly IEnumerable<Pump> Pumps => Nodes.Select(n => n as Pump).Where(p => p != null).Cast<Pump>();
public readonly IEnumerable<Valve> Valves => Nodes.Select(n => n as Valve).Where(v => v != null).Cast<Valve>();
private readonly IEnumerable<ControlledValve> InnerAndBorderValves => Nodes
.SelectMany(node => PamhagenGraph.TraverseSubgraph(node, (n) => n is ControlledValve || n is ISink || n is Pump))
.Select(e => e.End as ControlledValve)
.Where(v => v != null)
.Cast<ControlledValve>()
.Distinct();
public readonly IEnumerable<ControlledValve> InnerValves => Nodes.Select(n => n as ControlledValve).Where(v => v != null).Cast<ControlledValve>();
public readonly IEnumerable<ControlledValve> BorderValves => InnerAndBorderValves.Except(InnerValves);
public readonly bool HasActivePump => Nodes.Any(n => n is Pump p && p.IsActive);
public readonly bool IsReady => InnerValves.All(v => v.IsOpen && v.WantOpen && v.IsLocked) && BorderValves.All(v => v.IsClosed && !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);
}
}
} }
} }
+20 -15
View File
@@ -15,6 +15,13 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public (bool TowardEnd, double PathOffset)? Flow { get; set; } public (bool TowardEnd, double PathOffset)? Flow { get; set; }
public double FlowLength => _flowArrows?.Length ?? 0; public double FlowLength => _flowArrows?.Length ?? 0;
public double X1 => Start.CenterX;
public double Y1 => Start.CenterY;
public double X2 => Orientation ? Start.CenterX : End.CenterX;
public double Y2 => Orientation ? End.CenterY : Start.CenterY;
public double X3 => End.CenterX;
public double Y3 => End.CenterY;
private Polyline? _outer; private Polyline? _outer;
private Polyline? _inner; private Polyline? _inner;
private TextBlock? _text; private TextBlock? _text;
@@ -31,23 +38,13 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
var x1 = Start.CenterX;
var y1 = Start.CenterY;
var x2 = Orientation ? Start.CenterX : End.CenterX;
var y2 = Orientation ? End.CenterY : Start.CenterY;
var x3 = End.CenterX;
var y3 = End.CenterY;
_outer = new Polyline() { _outer = new Polyline() {
Points = [new(x1, y1), new(x2, y2), new(x3, y3)],
StrokeThickness = 10,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeStartLineCap = PenLineCap.Round, StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round, StrokeEndLineCap = PenLineCap.Round,
StrokeLineJoin = PenLineJoin.Round, StrokeLineJoin = PenLineJoin.Round,
}; };
_inner = new Polyline() { _inner = new Polyline() {
Points = [new(x1, y1), new(x2, y2), new(x3, y3)],
StrokeThickness = 6,
Stroke = Brushes.DarkGray, Stroke = Brushes.DarkGray,
StrokeStartLineCap = PenLineCap.Round, StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round, StrokeEndLineCap = PenLineCap.Round,
@@ -58,18 +55,26 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
if (Label != null) { if (Label != null) {
_text = new() { _text = new() {
Text = Label, Text = Label,
FontSize = 12,
Width = 50,
TextAlignment = TextAlignment.Center, TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Bottom, VerticalAlignment = VerticalAlignment.Bottom,
HorizontalAlignment = HorizontalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center,
SnapsToDevicePixels = true, SnapsToDevicePixels = true,
}; };
_text.SetValue(Canvas.LeftProperty, _textOffsetX != null ? (Orientation ? x2 : x1) + _textOffsetX : (Orientation ? (x2 + x3) / 2 : (x1 + x2) / 2) - 25);
_text.SetValue(Canvas.TopProperty, y2 - 22);
canvas.Children.Add(_text); canvas.Children.Add(_text);
} }
_flowArrows = FlowArrows.Create(canvas, _inner); _flowArrows = FlowArrows.Create(canvas, [new(X1, Y1), new(X2, Y2), new(X3, Y3)]);
}
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_outer?.Points = [Graph.ToPx(X1, Y1, scale), Graph.ToPx(X2, Y2, scale), Graph.ToPx(X3, Y3, scale)];
_inner?.Points = [Graph.ToPx(X1, Y1, scale), Graph.ToPx(X2, Y2, scale), Graph.ToPx(X3, Y3, scale)];
_inner?.StrokeThickness = Math.Round(1.2 * scale / 2) * 2;
_outer?.StrokeThickness = Math.Round(1.2 * scale / 2) * 2 + border * 2;
_text?.FontSize = 12;
_text?.Width = 10 * scale;
Graph.SetPos(_text, _textOffsetX != null ? (Orientation ? X2 : X1) + _textOffsetX.Value : (Orientation ? (X2 + X3) / 2 : (X1 + X2) / 2) - 5, Y2 - 1.4, scale, dypx: -16);
_flowArrows?.Scale(scale);
} }
public void Update() { public void Update() {
+23 -20
View File
@@ -6,8 +6,8 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class PipeJoin : INode { public class PipeJoin : INode {
public const double SIZE = 18; public const double SIZE = 3.6;
public const double POINT = 4; public const double POINT = 0.8;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -16,7 +16,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public ISet<IEdge> Inputs { get; init; } public ISet<IEdge> Inputs { get; init; }
public ISet<IEdge> Outputs { get; init; } public ISet<IEdge> Outputs { get; init; }
private Shape? _rect; private Polygon? _rect;
public PipeJoin(double x, double y) { public PipeJoin(double x, double y) {
CenterX = x; CenterX = x;
@@ -26,14 +26,22 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
var it = Inputs.Any(e => (e.Start.CenterY < CenterY && (!e.Orientation || e.Start.CenterX == CenterX)) || (e.IsTwoWay && e.End.CenterY < CenterY && (!e.Orientation || e.End.CenterX == CenterX))); _rect = new Polygon() {
var ot = Outputs.Any(e => (e.End.CenterY < CenterY && ( e.Orientation || e.End.CenterX == CenterX)) || (e.IsTwoWay && e.Start.CenterY < CenterY && ( e.Orientation || e.Start.CenterX == CenterX))); Fill = Brushes.Black,
var ir = Inputs.Any(e => (e.Start.CenterX > CenterX && ( e.Orientation || e.Start.CenterY == CenterY)) || (e.IsTwoWay && e.End.CenterX > CenterX && ( e.Orientation || e.End.CenterY == CenterY))); Stroke = Brushes.Black,
var or = Outputs.Any(e => (e.End.CenterX > CenterX && (!e.Orientation || e.End.CenterY == CenterY)) || (e.IsTwoWay && e.Start.CenterX > CenterX && (!e.Orientation || e.Start.CenterY == CenterY))); };
var ib = Inputs.Any(e => (e.Start.CenterY > CenterY && (!e.Orientation || e.Start.CenterX == CenterX)) || (e.IsTwoWay && e.End.CenterY > CenterY && (!e.Orientation || e.End.CenterX == CenterX))); canvas.Children.Add(_rect);
var ob = Outputs.Any(e => (e.End.CenterY > CenterY && ( e.Orientation || e.End.CenterX == CenterX)) || (e.IsTwoWay && e.Start.CenterY > CenterY && ( e.Orientation || e.Start.CenterX == CenterX))); }
var il = Inputs.Any(e => (e.Start.CenterX < CenterX && ( e.Orientation || e.Start.CenterY == CenterY)) || (e.IsTwoWay && e.End.CenterX < CenterX && ( e.Orientation || e.End.CenterY == CenterY)));
var ol = Outputs.Any(e => (e.End.CenterX < CenterX && (!e.Orientation || e.End.CenterY == CenterY)) || (e.IsTwoWay && e.Start.CenterX < CenterX && (!e.Orientation || e.Start.CenterY == CenterY))); public void Scale(double scale) {
var it = Inputs.Any(e => (e.Start.CenterY < CenterY && (!e.Orientation || e.Start.CenterX == CenterX)) || (e.IsTwoWay && e.End.CenterY < CenterY && (!e.Orientation || e.End.CenterX == CenterX)));
var ot = Outputs.Any(e => (e.End.CenterY < CenterY && (e.Orientation || e.End.CenterX == CenterX)) || (e.IsTwoWay && e.Start.CenterY < CenterY && (e.Orientation || e.Start.CenterX == CenterX)));
var ir = Inputs.Any(e => (e.Start.CenterX > CenterX && (e.Orientation || e.Start.CenterY == CenterY)) || (e.IsTwoWay && e.End.CenterX > CenterX && (e.Orientation || e.End.CenterY == CenterY)));
var or = Outputs.Any(e => (e.End.CenterX > CenterX && (!e.Orientation || e.End.CenterY == CenterY)) || (e.IsTwoWay && e.Start.CenterX > CenterX && (!e.Orientation || e.Start.CenterY == CenterY)));
var ib = Inputs.Any(e => (e.Start.CenterY > CenterY && (!e.Orientation || e.Start.CenterX == CenterX)) || (e.IsTwoWay && e.End.CenterY > CenterY && (!e.Orientation || e.End.CenterX == CenterX)));
var ob = Outputs.Any(e => (e.End.CenterY > CenterY && (e.Orientation || e.End.CenterX == CenterX)) || (e.IsTwoWay && e.Start.CenterY > CenterY && (e.Orientation || e.Start.CenterX == CenterX)));
var il = Inputs.Any(e => (e.Start.CenterX < CenterX && (e.Orientation || e.Start.CenterY == CenterY)) || (e.IsTwoWay && e.End.CenterX < CenterX && (e.Orientation || e.End.CenterY == CenterY)));
var ol = Outputs.Any(e => (e.End.CenterX < CenterX && (!e.Orientation || e.End.CenterY == CenterY)) || (e.IsTwoWay && e.Start.CenterX < CenterX && (!e.Orientation || e.Start.CenterY == CenterY)));
var points = new List<Point> { var points = new List<Point> {
new(0, 0) new(0, 0)
@@ -70,15 +78,10 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
points.AddRange([new(0, SIZE / 2 + POINT), new(POINT, SIZE / 2), new(0, SIZE / 2 - POINT)]); points.AddRange([new(0, SIZE / 2 + POINT), new(POINT, SIZE / 2), new(0, SIZE / 2 - POINT)]);
} }
_rect = new Polygon() { _rect?.Points = [.. points.Select(p => new Point(p.X * scale, p.Y * scale))];
Points = [..points], _rect?.StrokeThickness = 2;
Fill = Brushes.Black, _rect?.SetValue(Canvas.LeftProperty, (CenterX - SIZE / 2) * scale);
Stroke = Brushes.Black, _rect?.SetValue(Canvas.TopProperty, (CenterY - SIZE / 2) * scale);
StrokeThickness = 2,
};
_rect.SetValue(Canvas.LeftProperty, CenterX - SIZE / 2);
_rect.SetValue(Canvas.TopProperty, CenterY - SIZE / 2);
canvas.Children.Add(_rect);
} }
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
+24 -21
View File
@@ -1,13 +1,12 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Shapes; using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Press : INode, ISink { public class Press : INode, ISink {
public const double DIAMETER = 120; public const double DIAMETER = 24;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -19,60 +18,64 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public double TopY => CenterY - DIAMETER / 2; public double TopY => CenterY - DIAMETER / 2;
public FillState FillState => CallbackFillState(); public FillState FillState => CallbackFillState();
public bool IsFull => FillState == FillState.Full; public bool IsFull => FillState == FillState.Full;
public bool HasFillingClearance => CallbackFillingClearance();
public int? CapacityLiters { get; set; } public int? CapacityLiters { get; set; }
protected Func<FillState> CallbackFillState; protected Func<FillState> CallbackFillState;
protected Func<bool> CallbackFillingClearance;
private Shape? _frame; private Polygon? _frame;
private Shape? _circle; private Ellipse? _circle;
private TextBlock? _text; private TextBlock? _text;
public Press(string label, double x, double y, int capacityLiters, Func<FillState> cbFillState) { public Press(string label, double x, double y, int capacityLiters, Func<FillState> cbFillState, Func<bool> cbFillingClearance) {
CenterX = x; CenterX = x;
CenterY = y; CenterY = y;
Label = label; Label = label;
CapacityLiters = capacityLiters; CapacityLiters = capacityLiters;
CallbackFillState = cbFillState; CallbackFillState = cbFillState;
CallbackFillingClearance = cbFillingClearance;
Inputs = new HashSet<IEdge>(); Inputs = new HashSet<IEdge>();
Outputs = new HashSet<IEdge>(); Outputs = new HashSet<IEdge>();
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_circle = new Ellipse() { _circle = new Ellipse() {
Width = DIAMETER,
Height = DIAMETER,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
}; };
_frame = new Polygon() { _frame = new Polygon() {
Points = [
new(CenterX - DIAMETER / 2 - 10, CenterY), new(CenterX + DIAMETER / 2 + 10, CenterY),
new(CenterX + DIAMETER / 2 + 10, CenterY + DIAMETER / 2 + 30), new(CenterX + DIAMETER / 2 - 20, CenterY + DIAMETER / 2 + 30),
new(CenterX + DIAMETER / 2 - 20, CenterY + DIAMETER / 2 + 10), new(CenterX - DIAMETER / 2 + 20, CenterY + DIAMETER / 2 + 10),
new(CenterX - DIAMETER / 2 + 20, CenterY + DIAMETER / 2 + 30), new(CenterX - DIAMETER / 2 - 10, CenterY + DIAMETER / 2 + 30)
],
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
}; };
_text = new() { _text = new() {
Text = CapacityLiters.HasValue ? $"{Label}\n{CapacityLiters:N0}" : Label, Text = CapacityLiters.HasValue ? $"{Label}\n{CapacityLiters:N0}" : Label,
FontSize = 14,
Width = DIAMETER,
TextAlignment = TextAlignment.Center, TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
}; };
_circle.SetValue(Canvas.LeftProperty, CenterX - DIAMETER / 2);
_circle.SetValue(Canvas.TopProperty, CenterY - DIAMETER / 2);
_text.SetValue(Canvas.LeftProperty, CenterX - DIAMETER / 2);
_text.SetValue(Canvas.TopProperty, CenterY - (CapacityLiters.HasValue ? 20 : 10));
canvas.Children.Add(_frame); canvas.Children.Add(_frame);
canvas.Children.Add(_circle); canvas.Children.Add(_circle);
canvas.Children.Add(_text); canvas.Children.Add(_text);
} }
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_circle?.StrokeThickness = border;
Graph.SetCenter(_circle, CenterX, CenterY, DIAMETER, DIAMETER, scale);
_frame?.Points = [
Graph.ToPx(CenterX - DIAMETER / 2 - 2, CenterY, scale), Graph.ToPx(CenterX + DIAMETER / 2 + 2, CenterY, scale),
Graph.ToPx(CenterX + DIAMETER / 2 + 2, CenterY + DIAMETER / 2 + 6, scale), Graph.ToPx(CenterX + DIAMETER / 2 - 4, CenterY + DIAMETER / 2 + 6, scale),
Graph.ToPx(CenterX + DIAMETER / 2 - 4, CenterY + DIAMETER / 2 + 2, scale), Graph.ToPx(CenterX - DIAMETER / 2 + 4, CenterY + DIAMETER / 2 + 2, scale),
Graph.ToPx(CenterX - DIAMETER / 2 + 4, CenterY + DIAMETER / 2 + 6, scale), Graph.ToPx(CenterX - DIAMETER / 2 - 2, CenterY + DIAMETER / 2 + 6, scale),
];
_frame?.StrokeThickness = border;
_text?.FontSize = 14;
_text?.Width = DIAMETER * scale;
_text?.SetValue(Canvas.LeftProperty, (CenterX - DIAMETER / 2) * scale);
_text?.SetValue(Canvas.TopProperty, (CenterY * scale) - (CapacityLiters.HasValue ? 20 : 10));
}
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
return Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= DIAMETER / 2 || (Math.Abs(x - CenterX) <= DIAMETER / 2 + 10 && y >= CenterY && y <= CenterY + DIAMETER / 2 + 30); return Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= DIAMETER / 2 || (Math.Abs(x - CenterX) <= DIAMETER / 2 + 10 && y >= CenterY && y <= CenterY + DIAMETER / 2 + 30);
} }
+45 -28
View File
@@ -6,7 +6,7 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Pump : INode { public class Pump : INode {
public const double DIAMETER = 24; public const double DIAMETER = 4.8;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -16,23 +16,24 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public ISet<IEdge> Outputs { get; init; } public ISet<IEdge> Outputs { get; init; }
public Brush? Highlight { get; set; } public Brush? Highlight { get; set; }
public bool IsActive => CallbackActive(); public bool IsActive => State == MotorState.Forward || State == MotorState.Backward;
public MotorState State => CallbackState();
public bool HasClearance => CallbackClearance(); public bool HasClearance => CallbackClearance();
public bool IsSelected => CallbackSelected(this); public bool IsSelected => CallbackSelected(this);
protected Func<bool> CallbackActive; protected Func<MotorState> CallbackState;
protected Func<bool> CallbackClearance; protected Func<bool> CallbackClearance;
protected Func<Pump, bool> CallbackSelected; protected Func<Pump, bool> CallbackSelected;
private Shape? _circle; private Ellipse? _circle;
private Shape? _triangle; private Polyline? _triangle;
private TextBlock? _text; private TextBlock? _text;
public Pump(string label, double x, double y, Func<bool> cbActive, Func<bool> cbClearance, Func<Pump, bool> cbSelected) { public Pump(string label, double x, double y, Func<MotorState> cbState, Func<bool> cbClearance, Func<Pump, bool> cbSelected) {
CenterX = x; CenterX = x;
CenterY = y; CenterY = y;
Label = label; Label = label;
CallbackActive = cbActive; CallbackState = cbState;
CallbackClearance = cbClearance; CallbackClearance = cbClearance;
CallbackSelected = cbSelected; CallbackSelected = cbSelected;
Inputs = new HashSet<IEdge>(); Inputs = new HashSet<IEdge>();
@@ -41,47 +42,63 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_circle = new Ellipse() { _circle = new Ellipse() {
Height = DIAMETER,
Width = DIAMETER,
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
}; };
var dx = Math.Cos(Math.PI / 6) * (DIAMETER / 2 - 2); _triangle = new Polyline() {
var dy = Math.Sin(Math.PI / 6) * (DIAMETER / 2 - 2);
_triangle = new Polygon() {
Points = [new(CenterX - dx, CenterY - dy), new(CenterX + dx, CenterY - dy), new(CenterX, CenterY + DIAMETER / 2 - 2)],
Fill = Brushes.Transparent,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2, StrokeStartLineCap = PenLineCap.Square,
StrokeLineJoin = PenLineJoin.Round,
StrokeEndLineCap = PenLineCap.Square,
}; };
_text = new() { _text = new() {
Text = Label, Text = Label,
FontSize = 14,
TextAlignment = TextAlignment.Left, TextAlignment = TextAlignment.Left,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
}; };
_circle.SetValue(Canvas.LeftProperty, CenterX - DIAMETER / 2);
_circle.SetValue(Canvas.TopProperty, CenterY - DIAMETER / 2);
_text.SetValue(Canvas.LeftProperty, CenterX + DIAMETER / 2 + 2);
_text.SetValue(Canvas.TopProperty, CenterY - 10);
canvas.Children.Add(_circle); canvas.Children.Add(_circle);
canvas.Children.Add(_triangle); canvas.Children.Add(_triangle);
canvas.Children.Add(_text); canvas.Children.Add(_text);
} }
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_circle?.StrokeThickness = border;
Graph.SetCenter(_circle, CenterX, CenterY, DIAMETER, DIAMETER, scale);
var dx = Math.Cos(Math.PI / 9.5) * (DIAMETER / 2);
var dy = Math.Sin(Math.PI / 9.5) * (DIAMETER / 2);
_triangle?.Points = [
Graph.ToPx(CenterX - dx, CenterY - dy, scale, dxpx: border, dypx: border / 2, round: false),
Graph.ToPx(CenterX, CenterY + DIAMETER / 2, scale, dypx: -border, round: false),
Graph.ToPx(CenterX + dx, CenterY - dy, scale, dxpx: -border, dypx: border / 2, round: false),
];
_triangle?.StrokeThickness = border;
_text?.FontSize = 14;
_text?.SetValue(Canvas.LeftProperty, (CenterX + DIAMETER / 2) * scale + 2);
_text?.SetValue(Canvas.TopProperty, CenterY * scale - 10);
}
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
return Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= DIAMETER / 2; return Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= DIAMETER / 2;
} }
public void Update(bool isHovering, bool isPresed) { public void Update(bool isHovering, bool isPresed) {
_circle?.Fill = Highlight ?? ( if (App.Plant?.ManualControlMode ?? false) {
IsActive && isHovering ? PamhagenBrushes.Red : _circle?.Fill = Highlight ?? (
IsActive ? PamhagenBrushes.Green : IsActive && isHovering ? PamhagenBrushes.Red :
HasClearance && IsSelected && isHovering ? PamhagenBrushes.Green : IsActive ? PamhagenBrushes.Green :
HasClearance && IsSelected ? PamhagenBrushes.Yellow : HasClearance && isHovering ? PamhagenBrushes.Green :
IsSelected ? PamhagenBrushes.Red : HasClearance ? PamhagenBrushes.Yellow :
isHovering ? Brushes.LightGray : Brushes.WhiteSmoke); PamhagenBrushes.Red);
} else {
_circle?.Fill = Highlight ?? (
IsActive && isHovering ? PamhagenBrushes.Red :
IsActive ? PamhagenBrushes.Green :
HasClearance && IsSelected && isHovering ? PamhagenBrushes.Green :
HasClearance && IsSelected ? PamhagenBrushes.Yellow :
IsSelected ? PamhagenBrushes.Red :
isHovering ? Brushes.LightGray : Brushes.WhiteSmoke);
}
} }
} }
} }
+31 -27
View File
@@ -6,8 +6,8 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Rebler : INode { public class Rebler : INode {
public const double WIDTH = 120; public const double WIDTH = 24;
public const double HEIGHT = 60; public const double HEIGHT = 12;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -20,11 +20,11 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
protected Func<bool> CallbackActive; protected Func<bool> CallbackActive;
private TextBlock? _text; private TextBlock? _text;
private Shape? _rect; private Rectangle? _rect;
private Shape? _hopperOuter; private Polygon? _hopperOuter;
private Shape? _hopperInner; private Polygon? _hopperInner;
private Shape? _bottomInner; private Polygon? _bottomInner;
private Shape? _bottomOuter; private Polygon? _bottomOuter;
public Rebler(string label, double x, double y, Func<bool> cbActive) { public Rebler(string label, double x, double y, Func<bool> cbActive) {
CenterX = x; CenterX = x;
@@ -37,54 +37,58 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_rect = new Rectangle() { _rect = new Rectangle() {
Width = WIDTH,
Height = HEIGHT,
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2, StrokeThickness = 2,
}; };
var left = CenterX - WIDTH / 2;
var right = CenterX + WIDTH / 2;
var top = CenterY - HEIGHT / 2;
var bottom = CenterY + HEIGHT / 2;
var hx = right - 20;
var h = 15;
var w = 25;
_hopperOuter = new Polygon() { _hopperOuter = new Polygon() {
Points = [new(hx - w, top - h), new(hx - w + 5, top + 2), new(hx + w - 5, top + 2), new(hx + w, top - h)],
Fill = Brushes.Black, Fill = Brushes.Black,
}; };
_hopperInner = new Polygon() { _hopperInner = new Polygon() {
Points = [new(hx - w + 2, top - h), new(hx - w + 5 + 2, top + 2), new(hx + w - 5 - 2, top + 2), new(hx + w - 2, top - h)],
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
}; };
_bottomOuter = new Polygon() { _bottomOuter = new Polygon() {
Points = [new(left + 20, bottom), new(right - 20, bottom), new(right - 15, bottom + 10), new(left + 15, bottom + 10)],
Fill = Brushes.Black, Fill = Brushes.Black,
}; };
_bottomInner = new Polygon() { _bottomInner = new Polygon() {
Points = [new(left + 20 + 2, bottom), new(right - 20 - 2, bottom), new(right - 15 - 2, bottom + 10), new(left + 15 + 2, bottom + 10)],
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
}; };
_text = new() { _text = new() {
Text = Label, Text = Label,
FontSize = 12,
Width = WIDTH,
TextAlignment = TextAlignment.Center, TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
}; };
_rect.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_rect.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2);
_text.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_text.SetValue(Canvas.TopProperty, CenterY - 8);
canvas.Children.Add(_hopperOuter); canvas.Children.Add(_hopperOuter);
canvas.Children.Add(_bottomOuter); canvas.Children.Add(_bottomOuter);
canvas.Children.Add(_bottomInner);
canvas.Children.Add(_rect); canvas.Children.Add(_rect);
canvas.Children.Add(_hopperInner); canvas.Children.Add(_hopperInner);
canvas.Children.Add(_bottomInner);
canvas.Children.Add(_text); canvas.Children.Add(_text);
} }
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
Graph.SetCenter(_rect, CenterX, CenterY, WIDTH, HEIGHT, scale);
_rect?.StrokeThickness = border;
var left = CenterX - WIDTH / 2;
var right = CenterX + WIDTH / 2;
var top = CenterY - HEIGHT / 2;
var bottom = CenterY + HEIGHT / 2;
var hx = right - 4;
var h = 3;
var w = 5;
_hopperOuter?.Points = [Graph.ToPx(hx - w, top - h, scale), Graph.ToPx(hx - w + 1, top, scale, dypx: border), Graph.ToPx(hx + w - 1, top, scale, dypx: border), Graph.ToPx(hx + w, top - h, scale)];
_hopperInner?.Points = [Graph.ToPx(hx - w, top - h, scale, dxpx: border), Graph.ToPx(hx - w + 1, top, scale, dxpx: border, dypx: border), Graph.ToPx(hx + w - 1, top, scale, dxpx: -border, dypx: border), Graph.ToPx(hx + w, top - h, scale, dxpx: -border)];
_bottomOuter?.Points = [Graph.ToPx(left + 5, bottom - 1, scale), Graph.ToPx(right - 5, bottom - 1, scale), Graph.ToPx(right - 3, bottom + 2, scale), Graph.ToPx(left + 3, bottom + 2, scale)];
_bottomInner?.Points = [Graph.ToPx(left + 5, bottom - 1, scale, dxpx: border), Graph.ToPx(right - 5, bottom - 1, scale, dxpx: -border), Graph.ToPx(right - 3, bottom + 2, scale, dxpx: -border), Graph.ToPx(left + 3, bottom + 2, scale, dxpx: border)];
_text?.FontSize = 12;
_text?.Width = WIDTH * scale;
_text?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale);
_text?.SetValue(Canvas.TopProperty, (CenterY * scale) - 8);
}
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2; return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2;
} }
@@ -0,0 +1,76 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Stirrer {
public double CenterX { get; set; }
public double CenterY { get; set; }
public double Radius { get; set; }
public bool IsRotatingClockwise { get; set; }
public bool IsAnimationActive {
get => !(_storyboard?.GetIsPaused() ?? true);
set {
if (_storyboard == null) return;
if (value && _storyboard.GetIsPaused()) {
_storyboard.Resume();
} else if (!value && !_storyboard.GetIsPaused()) {
_storyboard.Pause();
}
}
}
private Polyline? _polyline;
private Storyboard? _storyboard;
private RotateTransform? _transform;
private DoubleAnimation? _rotate;
public Stirrer(double x, double y, double radius, bool clockwise = true) {
CenterX = x;
CenterY = y;
Radius = radius;
IsRotatingClockwise = clockwise;
}
public void Draw(Canvas canvas) {
_transform = new RotateTransform(0);
_polyline = new Polyline() {
Stroke = Brushes.Black,
RenderTransform = _transform,
StrokeLineJoin = PenLineJoin.Round,
};
_rotate = new DoubleAnimation {
From = 0,
To = IsRotatingClockwise ? 360 : -360,
Duration = new Duration(TimeSpan.FromSeconds(4.5)),
RepeatBehavior = RepeatBehavior.Forever,
};
Storyboard.SetTarget(_rotate, _polyline);
_storyboard = new Storyboard();
_storyboard.Children.Add(_rotate);
Storyboard.SetTargetProperty(_rotate, new PropertyPath("RenderTransform.Angle"));
canvas.Children.Add(_polyline);
_storyboard.Begin();
_storyboard.Pause();
}
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
var dx = Radius * Math.Cos(double.DegreesToRadians(30));
var dy = Radius * Math.Sin(double.DegreesToRadians(30));
_polyline?.Points = [
Graph.ToPx(CenterX, CenterY, scale, round: false),
Graph.ToPx(CenterX, CenterY - Radius, scale, round: false), Graph.ToPx(CenterX, CenterY, scale, round: false),
Graph.ToPx(CenterX + dx, CenterY + dy, scale, round: false), Graph.ToPx(CenterX, CenterY, scale, round: false),
Graph.ToPx(CenterX - dx, CenterY + dy, scale, round: false), Graph.ToPx(CenterX, CenterY, scale, round: false)];
_polyline?.StrokeThickness = border * 1.5;
_transform?.CenterX = Graph.ToPx(CenterX, scale);
_transform?.CenterY = Graph.ToPx(CenterY, scale);
}
}
}
+23 -10
View File
@@ -6,8 +6,8 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Switcher : INode { public class Switcher : INode {
public const double WIDTH = 100; public const double WIDTH = 20;
public const double HEIGHT = 50; public const double HEIGHT = 10;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -34,21 +34,15 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_mainTransform = new RotateTransform(0, CenterX, CenterY); _mainTransform = new RotateTransform(0);
_shadowTransform = new RotateTransform(0, CenterX, CenterY); _shadowTransform = new RotateTransform(0);
_main = new Line() { _main = new Line() {
X1 = CenterX - WIDTH / 2, Y1 = CenterY,
X2 = CenterX + WIDTH / 2, Y2 = CenterY,
StrokeThickness = 5,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeStartLineCap = PenLineCap.Square, StrokeStartLineCap = PenLineCap.Square,
StrokeEndLineCap = PenLineCap.Square, StrokeEndLineCap = PenLineCap.Square,
RenderTransform = _mainTransform, RenderTransform = _mainTransform,
}; };
_shadow = new Line() { _shadow = new Line() {
X1 = CenterX - WIDTH / 2, Y1 = CenterY,
X2 = CenterX + WIDTH / 2, Y2 = CenterY,
StrokeThickness = 5,
Stroke = Brushes.DarkGray, Stroke = Brushes.DarkGray,
StrokeStartLineCap = PenLineCap.Square, StrokeStartLineCap = PenLineCap.Square,
StrokeEndLineCap = PenLineCap.Square, StrokeEndLineCap = PenLineCap.Square,
@@ -58,6 +52,25 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
canvas.Children.Add(_main); canvas.Children.Add(_main);
} }
public void Scale(double scale) {
_mainTransform?.CenterX = CenterX * scale;
_mainTransform?.CenterY = CenterY * scale;
_shadowTransform?.CenterX = CenterX * scale;
_shadowTransform?.CenterY = CenterY * scale;
_main?.X1 = (CenterX - WIDTH / 2) * scale;
_main?.Y1 = CenterY * scale;
_main?.X2 = (CenterX + WIDTH / 2) * scale;
_main?.Y2 = CenterY * scale;
_main?.StrokeThickness = 1 * scale;
_shadow?.X1 = (CenterX - WIDTH / 2) * scale;
_shadow?.Y1 = CenterY * scale;
_shadow?.X2 = (CenterX + WIDTH / 2) * scale;
_shadow?.Y2 = CenterY * scale;
_shadow?.StrokeThickness = 1 * scale;
}
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2; return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2;
} }
+19 -12
View File
@@ -6,8 +6,8 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Tank : INode, ISink, ISource { public class Tank : INode, ISink, ISource {
public const double WIDTH = 60; public const double WIDTH = 12;
public const double HEIGHT = 100; public const double HEIGHT = 20;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -17,6 +17,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public FillState FillState => CallbackFillState(); public FillState FillState => CallbackFillState();
public bool IsFull => FillState == FillState.Full; public bool IsFull => FillState == FillState.Full;
public bool IsFilled => FillState != FillState.Empty; public bool IsFilled => FillState != FillState.Empty;
public bool HasFillingClearance => CallbackFillingClearance();
public ISet<IEdge> Inputs { get; init; } public ISet<IEdge> Inputs { get; init; }
public ISet<IEdge> Outputs { get; init; } public ISet<IEdge> Outputs { get; init; }
@@ -24,43 +25,49 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public int? CapacityLiters { get; set; } public int? CapacityLiters { get; set; }
protected Func<FillState> CallbackFillState; protected Func<FillState> CallbackFillState;
protected Func<bool> CallbackFillingClearance;
private Shape? _rect; private Shape? _rect;
private TextBlock? _text; private TextBlock? _text;
public Tank(string label, double x, double y, int capacityLiters, Func<FillState> cbFillState) { public Tank(string label, double x, double y, int capacityLiters, Func<FillState> cbFillState, Func<bool> cbFillingClearance) {
CenterX = x; CenterX = x;
CenterY = y; CenterY = y;
Label = label; Label = label;
CapacityLiters = capacityLiters; CapacityLiters = capacityLiters;
CallbackFillState = cbFillState; CallbackFillState = cbFillState;
CallbackFillingClearance = cbFillingClearance;
Inputs = new HashSet<IEdge>(); Inputs = new HashSet<IEdge>();
Outputs = new HashSet<IEdge>(); Outputs = new HashSet<IEdge>();
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_rect = new Rectangle() { _rect = new Rectangle() {
Width = WIDTH,
Height = HEIGHT,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
}; };
_text = new() { _text = new() {
Text = CapacityLiters.HasValue ? $"{Label}\n{CapacityLiters:N0}" : Label, Text = CapacityLiters.HasValue ? $"{Label}\n{CapacityLiters:N0}" : Label,
FontSize = 12,
Width = WIDTH,
TextAlignment = TextAlignment.Center, TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
}; };
_rect.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_rect.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2);
_text.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_text.SetValue(Canvas.TopProperty, CenterY - (CapacityLiters.HasValue ? 16 : 8));
canvas.Children.Add(_rect); canvas.Children.Add(_rect);
canvas.Children.Add(_text); canvas.Children.Add(_text);
} }
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_rect?.Height = Graph.ToPx(HEIGHT, scale);
_rect?.Width = Graph.ToPx(WIDTH, scale);
_rect?.StrokeThickness = border;
_rect?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale);
_rect?.SetValue(Canvas.TopProperty, (CenterY - HEIGHT / 2) * scale);
_text?.Width = Graph.ToPx(WIDTH, scale);
_text?.FontSize = 12;
_text?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale);
_text?.SetValue(Canvas.TopProperty, (CenterY * scale) - (CapacityLiters.HasValue ? 16 : 8));
}
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2; return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2;
} }
+29 -15
View File
@@ -6,8 +6,8 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Trough : INode, ISource { public class Trough : INode, ISource {
public const double WIDTH = 100; public const double WIDTH = 20;
public const double HEIGHT = 60; public const double HEIGHT = 12;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -15,51 +15,64 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public string Label { get; set; } public string Label { get; set; }
public FillState FillState => CallbackFillState(); public FillState FillState => CallbackFillState();
public bool IsFilled => FillState != FillState.Empty; public bool IsFilled => FillState != FillState.Empty;
public bool IsStirrerActive => CallbackStirrerActive();
public ISet<IEdge> Inputs { get; init; } public ISet<IEdge> Inputs { get; init; }
public ISet<IEdge> Outputs { get; init; } public ISet<IEdge> Outputs { get; init; }
protected Func<FillState> CallbackFillState; protected Func<FillState> CallbackFillState;
protected Func<bool> CallbackStirrerActive;
private Shape? _inner; private Shape? _inner;
private Shape? _outer; private Shape? _outer;
private TextBlock? _text; private TextBlock? _text;
private Stirrer _stirrer;
private readonly bool _stirrerLeft;
public Trough(string label, double x, double y, Func<FillState> cbFillState) { public Trough(string label, double x, double y, bool stirrerLeft, Func<FillState> cbFillState, Func<bool> cbStirrerActive) {
CenterX = x; CenterX = x;
CenterY = y; CenterY = y;
Label = label; Label = label;
CallbackFillState = cbFillState; CallbackFillState = cbFillState;
CallbackStirrerActive = cbStirrerActive;
Inputs = new HashSet<IEdge>(); Inputs = new HashSet<IEdge>();
Outputs = new HashSet<IEdge>(); Outputs = new HashSet<IEdge>();
_stirrer = new(x + (stirrerLeft ? -4 : 4), y, 4, stirrerLeft);
_stirrerLeft = stirrerLeft;
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_outer = new Rectangle() { _outer = new Rectangle() {
Width = WIDTH,
Height = HEIGHT,
Fill = Brushes.Black, Fill = Brushes.Black,
}; };
_inner = new Rectangle() { _inner = new Rectangle() {
Width = WIDTH - 4,
Height = HEIGHT - 2,
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
}; };
_text = new() { _text = new() {
Text = Label, Text = Label,
FontSize = 14,
Width = WIDTH,
TextAlignment = TextAlignment.Center, TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
}; };
_outer.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_outer.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2);
_inner.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2 + 2);
_inner.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2);
_text.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_text.SetValue(Canvas.TopProperty, CenterY - 10);
canvas.Children.Add(_outer); canvas.Children.Add(_outer);
canvas.Children.Add(_inner); canvas.Children.Add(_inner);
canvas.Children.Add(_text); canvas.Children.Add(_text);
_stirrer.Draw(canvas);
}
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_outer?.Width = WIDTH * scale;
_outer?.Height = HEIGHT * scale;
_inner?.Width = WIDTH * scale - border * 2;
_inner?.Height = HEIGHT * scale - border;
_text?.Width = WIDTH * scale;
_text?.FontSize = 14;
_outer?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale);
_outer?.SetValue(Canvas.TopProperty, (CenterY - HEIGHT / 2) * scale);
_inner?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale + border);
_inner?.SetValue(Canvas.TopProperty, (CenterY - HEIGHT / 2) * scale);
_text?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2 + (_stirrerLeft ? 5 : -5)) * scale);
_text?.SetValue(Canvas.TopProperty, (CenterY * scale) - 10);
_stirrer.Scale(scale);
} }
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
@@ -72,6 +85,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
isHovering ? Brushes.LightGray : isHovering ? Brushes.LightGray :
Brushes.WhiteSmoke Brushes.WhiteSmoke
); );
_stirrer.IsAnimationActive = IsStirrerActive;
} }
} }
} }
+105 -36
View File
@@ -4,74 +4,143 @@ using System.Windows.Media;
using System.Windows.Shapes; using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Valve : INode { public abstract class Valve : INode {
public const double DIAMETER = 24; public const double LENGTH = 4.8;
public const double WIDTH = 2.6;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
public int Nr { get; set; } public abstract string Label { get; }
public string Label => $"V{Nr}";
public Brush? Highlight { get; set; } public Brush? Highlight { get; set; }
public bool IsLocked { get; set; } public int Orientation { get; set; }
public bool IsOpen => CallbackIsOpen();
public bool WantOpen => CallbackWantOpen(this); public bool IsOpen => CallbackIsOpen() == true;
public bool IsClosed => CallbackIsOpen() == false;
public bool IsTransitioning => CallbackIsOpen() == null;
public ISet<IEdge> Inputs { get; init; } public ISet<IEdge> Inputs { get; init; }
public ISet<IEdge> Outputs { get; init; } public ISet<IEdge> Outputs { get; init; }
protected Func<bool> CallbackIsOpen; protected Func<bool?> CallbackIsOpen;
protected Func<Valve, bool> CallbackWantOpen;
private Shape? _circle; protected Rectangle? _background;
private TextBlock? _text; protected Polygon? _triangles;
protected Polyline? _topLine;
protected Polyline? _bottomLine;
protected ((Point Top, Point Bottom) Closed, (Point Top, Point Bottom) Open) _middlePoints;
protected TextBlock? _text;
public Valve(int nr, double x, double y, Func<bool> cbOpen, Func<Valve, bool> cbWant) { public Valve(double x, double y, int orientation, Func<bool?> cbOpen) {
CenterX = x; CenterX = x;
CenterY = y; CenterY = y;
Nr = nr;
CallbackIsOpen = cbOpen; CallbackIsOpen = cbOpen;
CallbackWantOpen = cbWant;
Inputs = new HashSet<IEdge>(); Inputs = new HashSet<IEdge>();
Outputs = new HashSet<IEdge>(); Outputs = new HashSet<IEdge>();
Orientation = orientation;
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_circle = new Ellipse() { _background = new Rectangle() {
Height = DIAMETER, Fill = Brushes.White,
Width = DIAMETER, };
_triangles = new Polygon() {
Fill = Brushes.DarkGray, Fill = Brushes.DarkGray,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
}; };
_circle.SetValue(Canvas.LeftProperty, CenterX - DIAMETER / 2); _topLine = new Polyline() {
_circle.SetValue(Canvas.TopProperty, CenterY - DIAMETER / 2); Stroke = Brushes.Black,
};
_bottomLine = new Polyline() {
Stroke = Brushes.Black,
};
_text = new() { _text = new() {
Text = Label, Text = Label,
FontSize = 10,
Width = DIAMETER,
Height = DIAMETER,
TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
SnapsToDevicePixels = true, SnapsToDevicePixels = true,
}; };
_text.SetValue(Canvas.LeftProperty, CenterX - DIAMETER / 2 + 0.5); canvas.Children.Add(_background);
_text.SetValue(Canvas.TopProperty, CenterY - DIAMETER / 2 + 5.5); canvas.Children.Add(_triangles);
canvas.Children.Add(_circle); canvas.Children.Add(_topLine);
canvas.Children.Add(_bottomLine);
canvas.Children.Add(_text); canvas.Children.Add(_text);
} }
public bool IsInside(double x, double y) { public void Scale(double scale) {
return Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= DIAMETER / 2; var border = Graph.ToBorder(scale);
if (Orientation == 0 || Orientation == 2) {
Graph.SetCenter(_background, CenterX, CenterY, LENGTH / 2, LENGTH, scale);
_triangles?.Points = [
Graph.ToPx(CenterX - WIDTH / 2, CenterY - LENGTH / 2, scale),
Graph.ToPx(CenterX + WIDTH / 2, CenterY + LENGTH / 2, scale),
Graph.ToPx(CenterX - WIDTH / 2, CenterY + LENGTH / 2, scale),
Graph.ToPx(CenterX + WIDTH / 2, CenterY - LENGTH / 2, scale),
];
_topLine?.Points = [
Graph.ToPx(CenterX - WIDTH / 2, CenterY - LENGTH / 4, scale),
Graph.ToPx(CenterX - WIDTH / 4, CenterY, scale),
Graph.ToPx(CenterX - WIDTH / 2, CenterY + LENGTH / 4, scale),
];
_bottomLine?.Points = [
Graph.ToPx(CenterX + WIDTH / 2, CenterY - LENGTH / 4, scale),
Graph.ToPx(CenterX + WIDTH / 4, CenterY, scale),
Graph.ToPx(CenterX + WIDTH / 2, CenterY + LENGTH / 4, scale),
];
if (_topLine != null && _bottomLine != null)
_middlePoints = ((_topLine.Points[1], _bottomLine.Points[1]),
(Graph.ToPx(CenterX - WIDTH / 3, CenterY, scale), Graph.ToPx(CenterX + WIDTH / 3, CenterY, scale)));
} else {
Graph.SetCenter(_background, CenterX, CenterY, LENGTH, LENGTH / 2, scale);
_triangles?.Points = [
Graph.ToPx(CenterX - LENGTH / 2, CenterY - WIDTH / 2, scale),
Graph.ToPx(CenterX + LENGTH / 2, CenterY + WIDTH / 2, scale),
Graph.ToPx(CenterX + LENGTH / 2, CenterY - WIDTH / 2, scale),
Graph.ToPx(CenterX - LENGTH / 2, CenterY + WIDTH / 2, scale),
];
_topLine?.Points = [
Graph.ToPx(CenterX - LENGTH / 4, CenterY - WIDTH / 2, scale),
Graph.ToPx(CenterX, CenterY - WIDTH / 4, scale),
Graph.ToPx(CenterX + LENGTH / 4, CenterY - WIDTH / 2, scale),
];
_bottomLine?.Points = [
Graph.ToPx(CenterX - LENGTH / 4, CenterY + WIDTH / 2, scale),
Graph.ToPx(CenterX, CenterY + WIDTH / 4, scale),
Graph.ToPx(CenterX + LENGTH / 4, CenterY + WIDTH / 2, scale),
];
if (_topLine != null && _bottomLine != null)
_middlePoints = ((_topLine.Points[1], _bottomLine.Points[1]),
(Graph.ToPx(CenterX, CenterY - WIDTH / 3, scale), Graph.ToPx(CenterX, CenterY + WIDTH / 3, scale)));
}
_triangles?.StrokeThickness = border;
_topLine?.StrokeThickness = border;
_bottomLine?.StrokeThickness = border;
_text?.Width = 40;
_text?.Height = 16;
_text?.FontSize = 12;
_text?.TextAlignment = Orientation == 0 ? TextAlignment.Right : Orientation == 2 ? TextAlignment.Left : TextAlignment.Center;
if (Orientation == 0) {
_text?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 1.5) * scale - 40);
_text?.SetValue(Canvas.TopProperty, CenterY * scale - 8);
} else if (Orientation == 2) {
_text?.SetValue(Canvas.LeftProperty, (CenterX + WIDTH / 1.5) * scale);
_text?.SetValue(Canvas.TopProperty, CenterY * scale - 8 );
} else if (Orientation == 1) {
_text?.SetValue(Canvas.LeftProperty, CenterX * scale - 20);
_text?.SetValue(Canvas.TopProperty, (CenterY - WIDTH / 2) * scale - 16);
} else {
_text?.SetValue(Canvas.LeftProperty, CenterX * scale - 20);
_text?.SetValue(Canvas.TopProperty, (CenterY + WIDTH / 2) * scale);
}
} }
public void Update(bool isHovering, bool isPressed) { public bool IsInside(double x, double y) {
_circle?.Fill = if (Orientation == 0 || Orientation == 2) {
IsLocked && !WantOpen && !IsOpen ? PamhagenBrushes.DimOrange : return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= LENGTH / 2;
IsLocked && !WantOpen && IsOpen ? PamhagenBrushes.Red : } else {
IsLocked && WantOpen && !IsOpen ? PamhagenBrushes.Yellow : return Math.Abs(x - CenterX) <= LENGTH / 2 && Math.Abs(y - CenterY) <= WIDTH / 2;
IsLocked && WantOpen && IsOpen ? PamhagenBrushes.Green : }
Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray);
} }
public abstract void Update(bool isHovering, bool isPressed);
} }
} }
@@ -1,18 +1,18 @@
using System.IO.Ports; using System.IO.Ports;
namespace PamhagenSysCtrl.Helpers { namespace PamhagenSysCtrl.Helpers.Plc {
public class PamhagenPlc : IDisposable { public class LegacyPamhagenPlc : IDisposable {
public record struct SensorStates( public record struct SensorStates(
long PressureSensors, long PressureSensors,
FillState FillingLevelMW1, FillState FillLevelMW1,
FillState FillingLevelMW2, FillState FillLevelMW2,
bool ClearancePress1, bool ClearancePress1,
bool ClearancePress2, bool ClearancePress2,
int TroughSelector, int TroughSelector,
double GradationOe, double GradationOe,
FillState[] FillingLevelTanks, FillState[] FillLevelTanks,
MotorState PresseQuerSchnecke MotorState PressCrossAuger
) { ) {
public readonly bool GetDS(int n) => (PressureSensors & (1L << (n - 1))) != 0; public readonly bool GetDS(int n) => (PressureSensors & (1L << (n - 1))) != 0;
} }
@@ -34,8 +34,12 @@ namespace PamhagenSysCtrl.Helpers {
protected readonly T1 Plc; protected readonly T1 Plc;
public PamhagenPlc(string portName, Parity parity = Parity.Odd) { public string ComPort => Plc.PortName;
Plc = new T1(portName, parity); public T1.Status LastStatus => Plc.LastStatus;
public T1Error LastError => Plc.LastError;
public LegacyPamhagenPlc(string comPort, Parity parity = Parity.Odd) {
Plc = new T1(comPort, parity);
} }
public void Dispose() { public void Dispose() {
@@ -43,31 +47,28 @@ namespace PamhagenSysCtrl.Helpers {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
protected static FillState Int3ToFillState(int n) { protected static FillState Int3ToFillState(int n, bool bigEndian = true) {
return n switch { return
7 => FillState.Full, (bigEndian && (n & 0x4) != 0) || (!bigEndian && (n & 0x1) != 0) ? FillState.AlmostFull :
6 => FillState.Half, (bigEndian && (n & 0x2) != 0) || (!bigEndian && (n & 0x2) != 0) ? FillState.Half :
4 => FillState.Filled, (bigEndian && (n & 0x1) != 0) || (!bigEndian && (n & 0x4) != 0) ? FillState.Filled :
0 => FillState.Empty, n == 0 ? FillState.Empty :
_ => FillState.Invalid, FillState.Invalid;
};
} }
protected static FillState Int2ToFillState(int n) { protected static FillState Int2ToFillState(int n, bool bigEndian = true) {
return n switch { return
2 => FillState.Full, (bigEndian && (n & 0x2) != 0) || (!bigEndian && (n & 0x1) != 0) ? FillState.Full :
0 => FillState.Filled, (bigEndian && (n & 0x1) != 0) || (!bigEndian && (n & 0x2) != 0) ? FillState.Filled :
1 => FillState.Empty, n == 0 ? FillState.Empty :
_ => FillState.Invalid, FillState.Invalid;
};
} }
protected static FillState Int1ToFillState(int n) { protected static FillState Int1ToFillState(int n) {
return n switch { return
1 => FillState.Full, n == 1 ? FillState.Full :
0 => FillState.Unknown, n == 0 ? FillState.Unknown :
_ => FillState.Invalid, FillState.Invalid;
};
} }
protected static MotorState Int2ToPumpState(int n, bool inverse) { protected static MotorState Int2ToPumpState(int n, bool inverse) {
@@ -94,7 +95,7 @@ namespace PamhagenSysCtrl.Helpers {
throw new T1Exception($"Interner Fehler: {error} ({(int)error:0000})"); throw new T1Exception($"Interner Fehler: {error} ({(int)error:0000})");
} }
protected async Task<(ushort RW010, ushort RW011, ushort RW012, ushort RW013, ushort RW014, ushort RW015, ushort RW016)> ReadInputRegisters() { public async Task<(ushort RW010, ushort RW011, ushort RW012, ushort RW013, ushort RW014, ushort RW015, ushort RW016)> ReadInputRegisters() {
var d = await Plc.DataRead("RW010", 7); var d = await Plc.DataRead("RW010", 7);
// RW010, RW011, RW012, RW013 Drucksensoren // RW010, RW011, RW012, RW013 Drucksensoren
// RW014 Füllstände A1, A2, Presse1, Presse2, Wannenselekt // RW014 Füllstände A1, A2, Presse1, Presse2, Wannenselekt
@@ -110,8 +111,8 @@ namespace PamhagenSysCtrl.Helpers {
long pressure = (long)r1 | ((long)r2 << 16) | ((long)r3 << 32) | ((long)r4 << 48); long pressure = (long)r1 | ((long)r2 << 16) | ((long)r3 << 32) | ((long)r4 << 48);
pressure = (pressure & 0x01FFFFFFFFFFFFFF) | ((pressure & 0x0600000000000000) << 1); pressure = (pressure & 0x01FFFFFFFFFFFFFF) | ((pressure & 0x0600000000000000) << 1);
var mw1 = Int3ToFillState((f1 ) & 0x7); // 111 011 001 000 var mw1 = Int3ToFillState((f1 ) & 0x7, false); // 111 011 001 000
var mw2 = Int3ToFillState((f1 >> 3) & 0x7); // 111 011 001 000 var mw2 = Int3ToFillState((f1 >> 3) & 0x7, false); // 111 011 001 000
var p1 = ((f1 >> 6) & 0x1) == 0; // 1 0 var p1 = ((f1 >> 6) & 0x1) == 0; // 1 0
var p2 = ((f1 >> 7) & 0x1) == 0; // 1 0 var p2 = ((f1 >> 7) & 0x1) == 0; // 1 0
var wp = ((f1 >> 8) & 0x3); // 10 01 var wp = ((f1 >> 8) & 0x3); // 10 01
@@ -129,11 +130,11 @@ namespace PamhagenSysCtrl.Helpers {
// -> factor: 0,03125 + (offset) 25 Oe // -> factor: 0,03125 + (offset) 25 Oe
double oe = v * 0.03125 + 25; double oe = v * 0.03125 + 25;
var t1 = Int2ToFillState((f2 ) & 0x3); // 10 01 00 var t1 = Int1ToFillState(((f2 ) & 0x3) >> 1); // 1X 0X
var t2 = Int2ToFillState((f2 >> 2) & 0x3); // 10 01 00 var t2 = Int1ToFillState(((f2 >> 2) & 0x3) >> 1); // 1X 0X
var t3 = Int2ToFillState((f2 >> 4) & 0x3); // 10 01 00 var t3 = Int1ToFillState(((f2 >> 4) & 0x3) >> 1); // 1X 0X
var t4 = Int2ToFillState((f2 >> 6) & 0x3); // 10 01 00 var t4 = Int1ToFillState(((f2 >> 6) & 0x3) >> 1); // 1X 0X
var t5 = Int2ToFillState((f2 >> 8) & 0x3); // 10 01 00 var t5 = Int1ToFillState(((f2 >> 8) & 0x3) >> 1); // 1X 0X
var t6 = Int1ToFillState((f2 >> 10) & 0x1); // 1 0 var t6 = Int1ToFillState((f2 >> 10) & 0x1); // 1 0
var t7 = Int1ToFillState((f2 >> 11) & 0x1); // 1 0 var t7 = Int1ToFillState((f2 >> 11) & 0x1); // 1 0
var t8 = Int1ToFillState((f2 >> 12) & 0x1); // 1 0 var t8 = Int1ToFillState((f2 >> 12) & 0x1); // 1 0
+365
View File
@@ -0,0 +1,365 @@
using System.IO.Ports;
namespace PamhagenSysCtrl.Helpers.Plc {
public class PamhagenPlc : IDisposable {
private bool _manualControlMode = false;
private bool _overridePathClearnaces = false;
private bool _overrideDryRunClearances = false;
public bool ManualControlMode { get => _manualControlMode; set { _manualControlMode = value; UpdateInterlocks(); } }
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; }
public PamhagenState LastState { get; private set; }
public string? ComPort => Plc?.ComPort;
public T1.Status? LastStatus => Plc?.LastStatus;
public T1Error? LastError => Plc?.LastError;
internal LegacyPamhagenPlc.ActuatorStates Actuators;
internal LegacyPamhagenPlc.ActuatorStates LastActuators;
internal LegacyPamhagenPlc.SensorStates[] LastSensors = [];
internal readonly int ConsiderLastSensorsOpen;
internal readonly int ConsiderLastSensorsClose;
internal readonly int ConsiderLastSensorsMW;
internal readonly FillState AutoStirrer;
public PamhagenPlc(string comPort, int sensorDelayOpenMs, int sensorDelayCloseMs, int sensorDelayMwMs, FillState autoStirrer = FillState.Half, Parity parity = Parity.Odd) {
Plc = new(comPort, parity);
ConsiderLastSensorsOpen = sensorDelayOpenMs / 125 + 1;
ConsiderLastSensorsClose = sensorDelayCloseMs / 125 + 1;
ConsiderLastSensorsMW = sensorDelayMwMs / 125 + 1;
AutoStirrer = autoStirrer;
LastSensors = new LegacyPamhagenPlc.SensorStates[Math.Max(Math.Max(ConsiderLastSensorsOpen, ConsiderLastSensorsClose), ConsiderLastSensorsMW)];
}
public void Dispose() {
GC.SuppressFinalize(this);
Plc.Dispose();
}
public async Task Initialize() {
await Plc.TestConnection();
Actuators = await Plc.ReadOutputs();
LastActuators = Actuators;
var sensors = await Plc.ReadInputs();
LastSensors[0] = sensors;
State = new(LastSensors, this);
LastState = State;
}
public async Task Shutdown() {
await Plc.WriteOutputs(new() { Valves = Actuators.Valves });
}
public async Task Update() {
UpdateInterlocks();
if (Actuators != LastActuators) {
LastActuators = Actuators;
await Plc.WriteOutputs(LastActuators);
}
var sensors = await Plc.ReadInputs();
Array.Copy(LastSensors, 0, LastSensors, 1, LastSensors.Length - 1);
LastSensors[0] = sensors;
var current = new PamhagenState(LastSensors, this);
LastState = State;
State = current;
UpdateInterlocks();
}
protected void UpdateInterlocks() {
if (!ManualControlMode) {
if (State.PressCrossAuger_IsActive && !State.Auger2_IsActive) StartAuger2();
if (State.Rebler_IsActive && !State.Auger1_IsActive) StartAuger1();
if (State.Auger1_IsActive && !State.Auger2_IsActive) StartAuger2();
if (State.Auger2_IsActive && !State.Auger3_IsActive) StartAuger3();
if (State.TroughAuger_IsActive && !State.TroughAuger_HasClearance)
StopTroughAuger();
if (!State.PressCrossAuger_IsActive && !State.Auger1_IsActive) {
StopAuger2();
StopAuger3();
}
}
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.MW1_Stirrer_IsActive && (State.MW1_FillLevel == FillState.Empty || (LastState.MW1_FillLevel >= AutoStirrer && State.MW1_FillLevel < AutoStirrer))) {
StopStirrerMW1();
}
if (State.MW2_Stirrer_IsActive && (State.MW2_FillLevel == FillState.Empty || (LastState.MW2_FillLevel >= AutoStirrer && State.MW2_FillLevel < AutoStirrer))) {
StopStirrerMW2();
}
}
}
public void OpenV(int n) => SetV(n, true);
public void CloseV(int n) => SetV(n, false);
protected void SetV(int n, bool val) {
if (n < 1 || n > 57) throw new ArgumentException("Invalid value for n: 1 <= #V <= 57");
Actuators.SetV(n, val);
}
public void SelectMW1() {
Actuators.SetV(58, true);
}
public void SelectMW2() {
Actuators.SetV(58, false);
}
public void StartTroughAuger() {
if (!ManualControlMode && !State.TroughAuger_HasClearance)
throw new NoClearanceException("Schnecke Auffangwanne hat keine Freigabe");
Actuators.TroughAuger = true;
}
public void StopTroughAuger() {
Actuators.TroughAuger = false;
}
public void StartRebler() {
Actuators.Rebler = true;
if (!ManualControlMode) StartAuger1();
}
public void StopRebler() {
if (!ManualControlMode) StopTroughAuger();
Actuators.Rebler = false;
if (!ManualControlMode) StopAuger1();
}
public void StartStirrerMW1() {
if (!ManualControlMode && State.MW1_FillLevel == FillState.Empty) throw new NoClearanceException("MW1 ist leer");
Actuators.StirrerMW1 = true;
}
public void StopStirrerMW1() {
Actuators.StirrerMW1 = false;
}
public void StartStirrerMW2() {
if (!ManualControlMode && State.MW2_FillLevel == FillState.Empty) throw new NoClearanceException("MW2 ist leer");
Actuators.StirrerMW2 = true;
}
public void StopStirrerMW2() {
Actuators.StirrerMW2 = false;
}
public void StartAuger1() {
Actuators.Auger1 = true;
if (!ManualControlMode) StartAuger2();
}
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() {
Actuators.Auger2 = true;
if (!ManualControlMode) StartAuger3();
}
public void StopAuger2() {
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() {
Actuators.Auger3 = true;
}
public void StopAuger3() {
if (!ManualControlMode && State.Auger2_IsActive) throw new NoClearanceException("Förderschnecke 2 aktiv");
Actuators.Auger3 = false;
}
public void StartWasteDisposal() {
StartAuger1();
StartAuger2();
StartAuger3();
}
public void StopWasteDisposal() {
StopAuger1();
StopAuger2();
StopAuger3();
}
public void StartGradationPump() {
Actuators.GradationPump = true;
}
public void StopGradationPump() {
Actuators.GradationPump = false;
}
public void StartMP1Forward() {
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 (!ManualControlMode && State.MW1_FillLevel >= AutoStirrer) 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) {
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 (!ManualControlMode && State.MW2_FillLevel >= AutoStirrer) 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) StopStirrerMW2();
}
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) {
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) {
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) {
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) {
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;
}
}
}
@@ -0,0 +1,372 @@
namespace PamhagenSysCtrl.Helpers.Plc {
public readonly record struct PamhagenState {
public readonly record struct PumpState(
MotorState State, bool IsActive,
bool HasClearance, bool HasPathClearance, bool HasDryRunClearance,
bool WantsPathClearance, bool WantActive);
private readonly LegacyPamhagenPlc.SensorStates Sensors;
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 readonly long OpenValves { get; }
public readonly long ClosedValves { get; }
public bool MW1_IsSelected => Sensors.TroughSelector == 1;
public bool MW2_IsSelected => Sensors.TroughSelector == 2;
public bool MW1_WantSelected => Actuators.GetV(58);
public bool MW2_WantSelected => !Actuators.GetV(58);
public readonly FillState MW1_FillLevel { get; }
public readonly FillState MW2_FillLevel { get; }
public FillState[] Tanks_FillLevel => Sensors.FillLevelTanks ?? [FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown];
public FillState T1_FillLevel => Tanks_FillLevel[0];
public FillState T2_FillLevel => Tanks_FillLevel[1];
public FillState T3_FillLevel => Tanks_FillLevel[2];
public FillState T4_FillLevel => Tanks_FillLevel[3];
public FillState T5_FillLevel => Tanks_FillLevel[4];
public FillState T6_FillLevel => Tanks_FillLevel[5];
public FillState T7_FillLevel => Tanks_FillLevel[6];
public FillState T8_FillLevel => Tanks_FillLevel[7];
public FillState T9_FillLevel => Tanks_FillLevel[8];
public MotorState PressCrossAuger_State => Sensors.PressCrossAuger;
public bool PressCrossAuger_IsActive => PressCrossAuger_State == MotorState.Forward;
public bool TroughAuger_IsActive => Actuators.TroughAuger;
public bool Rebler_IsActive => Actuators.Rebler;
public bool MW1_Stirrer_IsActive => Actuators.StirrerMW1;
public bool MW2_Stirrer_IsActive => Actuators.StirrerMW2;
public bool Auger1_IsActive => Actuators.Auger1;
public bool Auger2_IsActive => Actuators.Auger2;
public bool Auger3_IsActive => Actuators.Auger3;
public bool WasteDisposal_IsActive => Auger1_IsActive || Auger2_IsActive || Auger3_IsActive;
public bool WasteDisposal_IsConsistent => (Auger1_IsActive && Auger2_IsActive && Auger3_IsActive) || (!Auger1_IsActive && !Auger2_IsActive && !Auger3_IsActive);
public MotorState MP1_State => Actuators.MP1;
public MotorState MP2_State => Actuators.MP2;
public MotorState P12_State => Actuators.P12;
public MotorState P3_State => Actuators.P3;
public MotorState P4_State => Actuators.P4;
public MotorState P5_State => Actuators.P5;
public MotorState P6_State => Actuators.P6;
public bool MP1_IsActive => Actuators.MP1 == MotorState.Forward || Actuators.MP1 == MotorState.Backward;
public bool MP2_IsActive => Actuators.MP2 == MotorState.Forward || Actuators.MP2 == MotorState.Backward;
public bool P12_IsActive => Actuators.P12 == MotorState.Forward;
public bool P3_IsActive => Actuators.P3 == MotorState.Forward;
public bool P4_IsActive => Actuators.P4 == MotorState.Forward;
public bool P5_IsActive => Actuators.P5 == MotorState.Forward;
public bool P6_IsActive => Actuators.P6 == MotorState.Forward;
public bool Press1_HasClearance => Sensors.ClearancePress1;
public bool Press2_HasClearance => Sensors.ClearancePress2;
public bool TroughAuger_HasClearance => Rebler_IsActive && !(MW1_IsSelected && MW1_FillLevel == FillState.Full) && !(MW2_IsSelected && MW2_FillLevel == FillState.Full);
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 != null && Plc.MP1_WantActive;
public bool MP2_WantActive => Plc != null && Plc.MP2_WantActive;
public bool P12_WantActive => Plc != null && Plc.P12_WantActive;
public bool P3_WantActive => Plc != null && Plc.P3_WantActive;
public bool P4_WantActive => Plc != null && Plc.P4_WantActive;
public bool P5_WantActive => Plc != null && Plc.P5_WantActive;
public bool P6_WantActive => Plc != null && 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;
public bool P3_HasClearance => P3_HasDryRunClearance && P3_HasPathClearance;
public bool P4_HasClearance => P4_HasDryRunClearance && P4_HasPathClearance;
public bool P5_HasClearance => P4_HasDryRunClearance && P5_HasPathClearance;
public bool P6_HasClearance => P6_HasDryRunClearance && P6_HasPathClearance;
public PumpState MP1 => new(MP1_State, MP1_IsActive, MP1_HasClearance, MP1_HasPathClearance, MP1_HasDryRunClearance, MP1_WantsPathClearance, MP1_WantActive);
public PumpState MP2 => new(MP2_State, MP2_IsActive, MP2_HasClearance, MP2_HasPathClearance, MP2_HasDryRunClearance, MP2_WantsPathClearance, MP2_WantActive);
public PumpState P12 => new(P12_State, P12_IsActive, P12_HasClearance, P12_HasPathClearance, P12_HasDryRunClearance, P12_WantsPathClearance, P12_WantActive);
public PumpState P3 => new(P3_State, P3_IsActive, P3_HasClearance, P3_HasPathClearance, P3_HasDryRunClearance, P3_WantsPathClearance, P3_WantActive);
public PumpState P4 => new(P4_State, P4_IsActive, P4_HasClearance, P4_HasPathClearance, P4_HasDryRunClearance, P4_WantsPathClearance, P4_WantActive);
public PumpState P5 => new(P5_State, P5_IsActive, P5_HasClearance, P5_HasPathClearance, P5_HasDryRunClearance, P5_WantsPathClearance, P5_WantActive);
public PumpState P6 => new(P6_State, P6_IsActive, P6_HasClearance, P6_HasPathClearance, P6_HasDryRunClearance, P6_WantsPathClearance, P6_WantActive);
public PamhagenState(IEnumerable<LegacyPamhagenPlc.SensorStates> sensors, PamhagenPlc plc) {
Sensors = sensors.First();
Plc = plc;
OpenValves = sensors.Take(Plc.ConsiderLastSensorsOpen).Aggregate(-1L, (m, s) => m & ~s.PressureSensors);
ClosedValves = sensors.Take(Plc.ConsiderLastSensorsClose).Aggregate(-1L, (m, s) => m & s.PressureSensors);
var mw1 = sensors.Take(Plc.ConsiderLastSensorsMW).Select(s => s.FillLevelMW1).Where(s => s != FillState.Unknown).Distinct().ToArray();
var mw2 = sensors.Take(Plc.ConsiderLastSensorsMW).Select(s => s.FillLevelMW2).Where(s => s != FillState.Unknown).Distinct().ToArray();
MW1_FillLevel =
mw1.Contains(FillState.Invalid) ? FillState.Invalid :
mw1.Length == 1 && mw1[0] == FillState.Empty ? mw1[0] :
mw1.Length == 1 ? mw1[0] + 1 :
mw1.Length >= 2 ? mw1[0] > mw1[1] ? mw1[0] : mw1[1] :
FillState.Unknown;
MW2_FillLevel =
mw2.Contains(FillState.Invalid) ? FillState.Invalid :
mw2.Length == 1 && mw2[0] == FillState.Empty ? mw2[0] :
mw2.Length == 1 ? mw2[0] + 1 :
mw2.Length >= 2 ? mw2[0] > mw2[1] ? mw2[0] : mw2[1] :
FillState.Unknown;
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_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) => (ClosedValves & (1L << (n - 1))) != 0;
public bool Valve_IsTransitioning(int n) => !Valve_IsClosed(n) && !Valve_IsOpen(n);
public bool Valve_IsOpen(int n) => (OpenValves & (1L << (n - 1))) != 0;
public bool? Valve_OpenState(int n) => Valve_IsOpen(n) ? true : Valve_IsClosed(n) ? false : null;
public bool Valve_WantOpen(int n) => Actuators.GetV(n);
public bool Valve_WantClosed(int n) => !Valve_WantOpen(n);
private record struct ValveStates(int[] Open, int[] Closed) {
public readonly ValveStateMask ToMask() => new(Open.Aggregate(0L, (m, n) => m | (1L << (n - 1))), Open.Concat(Closed).Aggregate(0L, (m, n) => m | (1L << (n - 1))));
public static ValveStates operator +(ValveStates a, ValveStates b) {
return new([.. a.Open, .. b.Open], [.. a.Closed, .. b.Closed]);
}
}
private record struct ValveStateMask(long States, long Mask) {
public static ValveStateMask[] From(IEnumerable<ValveStates> states) => [.. states.Select(s => s.ToMask())];
public static bool operator ==(long a, ValveStateMask b) => (a & b.Mask) == b.States;
public static bool operator >=(long a, ValveStateMask b) => (a & b.States) == b.States;
public static bool operator !=(long a, ValveStateMask b) => !(a == b);
public static bool operator <=(long a, ValveStateMask b) => !(a >= b);
}
private static bool HasSinglePathClearance(PamhagenState state, IEnumerable<ValveStateMask> masks) {
return masks.Any(m => state.Valves == m && state.OpenValves == m && ~state.ClosedValves == m);
}
private static bool HasAnyPathClearance(PamhagenState state, IEnumerable<ValveStateMask> masks) {
return masks.Any(m => state.Valves >= m && state.OpenValves >= m && ~state.ClosedValves >= m);
}
private static bool WantsSinglePathClearance(PamhagenState state, IEnumerable<ValveStateMask> masks) {
return masks.Any(m => state.Valves == m);
}
private static bool WantsAnyPathClearance(PamhagenState state, IEnumerable<ValveStateMask> masks) {
return masks.Any(m => state.Valves >= m);
}
private static bool HasPathClearance(PamhagenState state, (ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) masks) {
return
(HasSinglePathClearance(state, masks.Press1) && state.Press1_HasClearance) ||
(HasSinglePathClearance(state, masks.Press2) && state.Press2_HasClearance) ||
(HasSinglePathClearance(state, masks.Tanks[0]) && state.T1_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[1]) && state.T2_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[2]) && state.T3_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[3]) && state.T4_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[4]) && state.T5_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[5]) && state.T6_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[6]) && state.T7_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[7]) && state.T8_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[8]) && state.T9_FillLevel != FillState.Full) ||
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) ||
(HasSinglePathClearance(state, masks.Press2) && state.Press2_HasClearance) ||
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,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) P12,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) P3,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) P4,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) P5,
(ValveStateMask[] Press1, ValveStateMask[] Press2) P6
) Paths;
static PamhagenState() {
ValveStates[] wt_aa = [
new([12], [15, 16, 20]), // bypass
new([15, 18, 16], [11, 12, 14, 17, 19, 20]), // WT1
new([15, 17, 20], [11, 12, 18, 19, 21]), // WT2
new([15, 18, 19, 20], [11, 12, 14, 16, 17, 21]), // WT1+WT2
];
ValveStates[] wt_ab = [
new([15, 18, 14], [11, 12, 13, 16, 17, 19, 21]), // WT1
new([15, 17, 21], [11, 12, 13, 18, 19, 20]), // WT2
new([15, 18, 19, 21], [11, 12, 13, 14, 16, 17, 20]), // WT1+WT2
];
ValveStates[] wt_ba = [
new([11, 18, 16], [15, 12, 13, 14, 17, 19, 20]), // WT1
new([11, 17, 20], [15, 12, 13, 18, 19, 21]), // WT2
new([11, 18, 19, 20], [15, 12, 13, 14, 16, 17, 21]), // WT1+WT2
];
ValveStates[] wt_bb = [
new([13], [11, 14, 21]), // bypass
new([11, 18, 14], [15, 13, 16, 17, 19, 21]), // WT1
new([11, 17, 21], [15, 13, 18, 19, 20]), // WT2
new([11, 18, 19, 21], [15, 13, 14, 16, 17, 20]), // WT1+WT2
];
ValveStates[][] tanks = [
[new([22], [24, 26, 28]), new([23], [25, 27, 29])],
[new([24], [22, 26, 28]), new([25], [23, 27, 29])],
[new([26], [22, 24, 28]), new([27], [23, 25, 29])],
[new([28, 30], [22, 24, 26, 32, 34]), new([29, 31], [23, 25, 27, 33, 35])],
[new([28, 32], [22, 24, 26, 30, 34]), new([29, 33], [23, 25, 27, 31, 35])],
[new([28, 34, 37], [22, 24, 26, 30, 32, 39, 41, 43]), new([29, 35, 36], [23, 25, 27, 31, 33, 38, 40, 42])],
[new([28, 34, 39], [22, 24, 26, 30, 32, 37, 41, 43]), new([29, 35, 38], [23, 25, 27, 31, 33, 36, 40, 42])],
[new([28, 34, 41], [22, 24, 26, 30, 32, 37, 39, 43]), new([29, 35, 40], [23, 25, 27, 31, 33, 36, 38, 42])],
[new([28, 34, 43], [22, 24, 26, 30, 32, 37, 39, 41]), new([29, 35, 42], [23, 25, 27, 31, 33, 36, 38, 40])],
];
ValveStates[] mp1_pr1 = [
new([1, 6, 51], [3, 4, 5, 50, 52, 53, 55]),
new([4, 8, 52], [1, 2, 7, 50, 51, 53, 56]),
];
ValveStates[] mp1_pr2 = [
new([1, 6, 55], [3, 4, 5, 51, 54, 56, 57]),
new([4, 8, 56], [1, 2, 7, 52, 54, 55, 57]),
];
ValveStates mp1_wt_a = new([1, 5], [3, 4, 6]);
ValveStates mp1_wt_b = new([4, 7, 9], [1, 2, 8, 10]);
ValveStates[] mp1_a = [.. wt_aa.Select(m => mp1_wt_a + m), .. wt_ba.Select(m => mp1_wt_b + m)];
ValveStates[] mp1_b = [.. wt_ab.Select(m => mp1_wt_a + m), .. wt_bb.Select(m => mp1_wt_b + m)];
ValveStates[][] mp1_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. mp1_a.Select(m => m + t[0]), .. mp1_b.Select(m => m + t[1])])];
ValveStates[] mp2_pr1 = [
new([2, 8, 52], [3, 4, 7, 50, 51, 53, 56]),
new([3, 6, 51], [1, 2, 5, 50, 52, 53, 55]),
];
ValveStates[] mp2_pr2 = [
new([2, 8, 56], [3, 4, 7, 52, 54, 55, 57]),
new([3, 6, 55], [2, 1, 5, 51, 54, 56, 57]),
];
ValveStates mp2_wt_a = new([3, 5], [1, 2, 6]);
ValveStates mp2_wt_b = new([2, 7, 9], [3, 4, 8, 10]);
ValveStates[] mp2_a = [.. wt_aa.Select(m => mp2_wt_a + m), .. wt_ba.Select(m => mp2_wt_b + m)];
ValveStates[] mp2_b = [.. wt_ab.Select(m => mp2_wt_a + m), .. wt_bb.Select(m => mp2_wt_b + m)];
ValveStates[][] mp2_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. mp2_a.Select(m => m + t[0]), .. mp2_b.Select(m => m + t[1])])];
ValveStates[] p12_pr1 = [new([44, 46, 49, 50], [10, 45, 47, 48, 54, 51, 52, 53])];
ValveStates[] p12_pr2 = [new([44, 46, 49, 54], [10, 45, 47, 48, 50, 55, 56, 57])];
ValveStates[] p12_b = [new([44, 10], [9, 45, 46])];
ValveStates[][] p12_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. p12_b.Select(m => m + t[1])])];
ValveStates[] p3_pr1 = [new([45, 46, 49, 50], [10, 44, 47, 48, 51, 52, 53, 54])];
ValveStates[] p3_pr2 = [new([45, 46, 49, 54], [10, 44, 47, 48, 50, 55, 56, 57])];
ValveStates[] p3_b = [new([45, 10], [9, 44, 46])];
ValveStates[][] p3_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. p3_b.Select(m => m + t[1])])];
ValveStates[] p4_pr1 = [new([47, 49, 50], [46, 48, 51, 52, 53, 54])];
ValveStates[] p4_pr2 = [new([47, 49, 54], [46, 48, 50, 55, 56, 57])];
ValveStates[] p4_b = [new([47, 46, 10], [9, 44, 45, 48, 49])];
ValveStates[][] p4_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. p4_b.Select(m => m + t[1])])];
ValveStates[] p5_pr1 = [new([48, 49, 50], [46, 47, 51, 52, 53, 54])];
ValveStates[] p5_pr2 = [new([48, 49, 54], [46, 47, 50, 55, 56, 57])];
ValveStates[] p5_b = [new([48, 46, 10], [9, 44, 45, 47, 49])];
ValveStates[][] p5_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. p5_b.Select(m => m + t[1])])];
ValveStates[] p6_pr1 = [new([53], [50, 51, 52, 57])];
ValveStates[] p6_pr2 = [new([57], [53, 54, 55, 56])];
Paths = (
(ValveStateMask.From(mp1_pr1), ValveStateMask.From(mp1_pr2), [.. mp1_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(mp2_pr1), ValveStateMask.From(mp2_pr2), [.. mp2_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p12_pr1), ValveStateMask.From(p12_pr2), [.. p12_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p3_pr1), ValveStateMask.From(p3_pr2), [.. p3_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p4_pr1), ValveStateMask.From(p4_pr2), [.. p4_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p5_pr1), ValveStateMask.From(p5_pr2), [.. p5_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p6_pr1), ValveStateMask.From(p6_pr2))
);
}
}
}
@@ -2,7 +2,7 @@ using System.IO;
using System.IO.Ports; using System.IO.Ports;
using System.Text; using System.Text;
namespace PamhagenSysCtrl.Helpers { namespace PamhagenSysCtrl.Helpers.Plc {
public class T1 : IDisposable { public class T1 : IDisposable {
public enum Mode { HALT = 1, RUN = 2, RUN_F = 3, HOLD = 4, ERROR = 6 } public enum Mode { HALT = 1, RUN = 2, RUN_F = 3, HOLD = 4, ERROR = 6 }
@@ -10,12 +10,16 @@ namespace PamhagenSysCtrl.Helpers {
protected SerialPort Serial; protected SerialPort Serial;
public string PortName => Serial.PortName;
public Status LastStatus { get; private set; }
public T1Error LastError { get; private set; }
public T1(string portName, Parity parity = Parity.Odd) { public T1(string portName, Parity parity = Parity.Odd) {
Serial = new SerialPort(portName, 9600, parity, 8, StopBits.One) { Serial = new SerialPort(portName, 9600, parity, 8, StopBits.One) {
Handshake = Handshake.None, Handshake = Handshake.None,
NewLine = "\r", NewLine = "\r",
ReadTimeout = 1000, ReadTimeout = 500,
WriteTimeout = 1000, WriteTimeout = 500,
DtrEnable = true, DtrEnable = true,
RtsEnable = true, RtsEnable = true,
Encoding = Encoding.ASCII, Encoding = Encoding.ASCII,
@@ -47,7 +51,7 @@ namespace PamhagenSysCtrl.Helpers {
Serial.WriteLine(line.ToString()); Serial.WriteLine(line.ToString());
} }
protected async Task<(string, string)> ReceiveResponse(string? expectedCmd = null) { protected async Task<(string Command, string Data)> ReceiveResponse(string? expectedCmd = null) {
var line = Serial.ReadLine(); var line = Serial.ReadLine();
if (line.Length < 8 || !line.StartsWith("(A") || !line.EndsWith(')') || line[^4] != '&') { if (line.Length < 8 || !line.StartsWith("(A") || !line.EndsWith(')') || line[^4] != '&') {
throw new FormatException("Invalid response from PLC (T1)"); throw new FormatException("Invalid response from PLC (T1)");
@@ -97,14 +101,16 @@ namespace PamhagenSysCtrl.Helpers {
public async Task<Status> StatusRead() { public async Task<Status> StatusRead() {
await SendCommand("ST", checksum: false); await SendCommand("ST", checksum: false);
var (_, res) = await ReceiveResponse("ST"); var (_, res) = await ReceiveResponse("ST");
return new Status((Mode)(res[3] - '0'), ((res[0] - '0') << 4) | (res[1] - '0')); LastStatus = new Status((Mode)(res[3] - '0'), ((res[0] - '0') << 4) | (res[1] - '0'));
return LastStatus;
} }
public async Task<T1Error> ErrorStatusRead() { public async Task<T1Error> ErrorStatusRead() {
await SendCommand("ER", checksum: false); await SendCommand("ER", checksum: false);
var (_, res) = await ReceiveResponse("ER"); var (_, res) = await ReceiveResponse("ER");
var code = Convert.ToInt32(res, 10); var code = Convert.ToInt32(res, 10);
return Enum.IsDefined(typeof(T1Error), code) ? (T1Error)code : T1Error.UNKNOWN_ERROR; LastError = Enum.IsDefined(typeof(T1Error), code) ? (T1Error)code : T1Error.UNKNOWN_ERROR;
return LastError;
} }
public Task<ushort[]> DataRead(string register, int length) { public Task<ushort[]> DataRead(string register, int length) {
+144
View File
@@ -0,0 +1,144 @@
namespace PamhagenSysCtrl.Helpers.Plc {
public enum T1Error {
UNKNOWN_ERROR = -1,
NO_ERROR = 0,
SYSTEM_POWER_ON = 10,
SYSTEM_POWER_OFF = 11,
RAM_CHECK_ERROR = 20,
PROGRAM_BCC_ERROR = 21,
BATTERY_VOLTAGE_DROP = 22,
EEPROM_BCC_ERROR = 23,
EEPROM_WARNING = 26,
SYSTEM_RAM_CHECK_ERROR = 30,
SYSTEM_ROM_BCC_ERROR = 31,
PERIPHERAL_LSI_ERROR = 32,
CLOCK_CALENDAR_CHECK_ERROR = 33,
ILLEGAL_SYSTEM_INTERRUPT = 34,
WD_TIMER_ERROR = 35,
IO_BUS_ERROR = 40,
IO_MISMATCH = 41,
IO_NO_ANSWER = 42,
IO_PARITY_ERROR = 43,
ILLEGAL_IO_REGISTER = 46,
COMMUNICATION_BUSY = 51,
FORMAT_ERROR = 52,
SCAN_TIME_OVER = 64,
NO_END_IRET_ERROR = 80,
PAIR_INSTRUCTION_ERROR = 81,
OPERAND_ERROR = 82,
INVALID_PROGRAM = 83,
NO_SUBROUTINE_ENTRY = 86,
NO_RET_ERROR = 87,
SUBROUTINE_NESTING_ERROR = 88,
LOOP_NESTING_ERROR = 89,
INVALID_FUNCTION_INSTRUCTION = 98,
PASSWORD_PROTECT = 106,
ILLEGAL_INSTRUCTION = 110,
REGISTER_ADDRESS_ERROR = 111,
BOUNDARY_ERROR = 112,
MEMORY_FULL = 113,
MODE_MISMATCH = 114,
REGISTER_ADDRESS_SIZE_ERROR = 115,
DUPLICATE_ENTRY_NR = 121,
}
public static class T1ErrorExtension {
public static bool IsError(this T1Error error) {
return error switch {
T1Error.NO_ERROR => false,
T1Error.SYSTEM_POWER_ON => false,
T1Error.SYSTEM_POWER_OFF => false,
_ => true,
};
}
public static string GetName(this T1Error error) {
return error switch {
T1Error.UNKNOWN_ERROR => "Unknown error",
T1Error.NO_ERROR => "No error",
T1Error.SYSTEM_POWER_ON => "System power on",
T1Error.SYSTEM_POWER_OFF => "System power off",
T1Error.RAM_CHECK_ERROR => "RAM check error",
T1Error.PROGRAM_BCC_ERROR => "Program BCC error",
T1Error.BATTERY_VOLTAGE_DROP => "Battery voltage drop",
T1Error.EEPROM_BCC_ERROR => "EEPROM BCC error",
T1Error.EEPROM_WARNING => "EEPROM warning",
T1Error.SYSTEM_RAM_CHECK_ERROR => "System RAM check error",
T1Error.SYSTEM_ROM_BCC_ERROR => "System ROM BCC error",
T1Error.PERIPHERAL_LSI_ERROR => "Peripheral LSI error",
T1Error.CLOCK_CALENDAR_CHECK_ERROR => "Clock-calendar check error",
T1Error.ILLEGAL_SYSTEM_INTERRUPT => "Illegal system interrupt",
T1Error.WD_TIMER_ERROR => "WD timer error",
T1Error.IO_BUS_ERROR => "I/O bus error",
T1Error.IO_MISMATCH => "I/O mismatch",
T1Error.IO_NO_ANSWER => "I/O no answer",
T1Error.IO_PARITY_ERROR => "I/O parity error",
T1Error.ILLEGAL_IO_REGISTER => "Illegal I/O register",
T1Error.COMMUNICATION_BUSY => "Communication busy",
T1Error.FORMAT_ERROR => "Format error",
T1Error.SCAN_TIME_OVER => "Scan time over",
T1Error.NO_END_IRET_ERROR => "No END/IRET error",
T1Error.PAIR_INSTRUCTION_ERROR => "Pair instruction error",
T1Error.OPERAND_ERROR => "Operand error",
T1Error.INVALID_PROGRAM => "Invalid program",
T1Error.NO_SUBROUTINE_ENTRY => "No subroutine entry",
T1Error.NO_RET_ERROR => "No RET error",
T1Error.SUBROUTINE_NESTING_ERROR => "Subroutine nesting error",
T1Error.LOOP_NESTING_ERROR => "Loop nesting error",
T1Error.INVALID_FUNCTION_INSTRUCTION => "Invalid function instruction",
T1Error.PASSWORD_PROTECT => "Password protect",
T1Error.ILLEGAL_INSTRUCTION => "Illegal instruction",
T1Error.REGISTER_ADDRESS_ERROR => "Register address error",
T1Error.BOUNDARY_ERROR => "Boundary error",
T1Error.MEMORY_FULL => "Memory full",
T1Error.MODE_MISMATCH => "Mode mismatch",
T1Error.REGISTER_ADDRESS_SIZE_ERROR => "Register address/size error",
T1Error.DUPLICATE_ENTRY_NR => "Duplicate entry No.",
_ => error.ToString(),
};
}
public static string? GetDescription(this T1Error error) {
return error switch {
T1Error.NO_ERROR => "No error recorded",
T1Error.SYSTEM_POWER_ON => "Power on (no error)",
T1Error.SYSTEM_POWER_OFF => "Power off (no error)",
T1Error.RAM_CHECK_ERROR => "RAM read/write error has been detected",
T1Error.PROGRAM_BCC_ERROR => "Program BCC (memory check code) error has been detected",
T1Error.BATTERY_VOLTAGE_DROP => "Data invalidity of RAM (back-up area) has been detected",
T1Error.EEPROM_BCC_ERROR => "BCC error of built-in EEPROM has been detected",
T1Error.EEPROM_WARNING => "EEPROM write operation has exceeded 100,000 times",
T1Error.SYSTEM_RAM_CHECK_ERROR => "System RAM read/write error has been detected",
T1Error.SYSTEM_ROM_BCC_ERROR => "BCC error of system ROM has been detected",
T1Error.PERIPHERAL_LSI_ERROR => "CPU hardware error has been detected",
T1Error.CLOCK_CALENDAR_CHECK_ERROR => "Invalid clock-calendar data has been detected",
T1Error.ILLEGAL_SYSTEM_INTERRUPT => "Unregistered interrupt has occurred",
T1Error.WD_TIMER_ERROR => "Watchdog timer error has occurred",
T1Error.IO_BUS_ERROR => "I/O bus error has been detected",
T1Error.IO_MISMATCH => "Registered I/O allocation table and actual I/O configuration are not identical",
T1Error.IO_NO_ANSWER => "No response from I/O module has been received",
T1Error.IO_PARITY_ERROR => "I/O bus parity error has been detected",
T1Error.ILLEGAL_IO_REGISTER => "Excess I/O register allocation has been detected",
T1Error.COMMUNICATION_BUSY => "The T1/T1S is busy in processing for other peripheral communications",
T1Error.FORMAT_ERROR => "Received request is invalid (detected by the T1/T1S)",
T1Error.SCAN_TIME_OVER => "Scan time has exceeded 200 ms",
T1Error.NO_END_IRET_ERROR => "END or IRET instruction has not been programmed",
T1Error.PAIR_INSTRUCTION_ERROR => "Illegal combination of pair instructions has been programmed",
T1Error.OPERAND_ERROR => "Illegal operand has been detected",
T1Error.INVALID_PROGRAM => "Program abnormality has been detected",
T1Error.NO_SUBROUTINE_ENTRY => "Subroutine corresponding to CALL instruction has not been programmed",
T1Error.NO_RET_ERROR => "RET (subroutine return) instruction has not been in a subroutine",
T1Error.SUBROUTINE_NESTING_ERROR => "CALL instruction has been programmed in a subroutine (subroutine nesting)",
T1Error.LOOP_NESTING_ERROR => "Nesting of FOR-NEXT loop has been programmed",
T1Error.INVALID_FUNCTION_INSTRUCTION => "Function instruction which is not supported by T1/T1S has been programmed",
T1Error.PASSWORD_PROTECT => "Requested operation is protected by password",
T1Error.ILLEGAL_INSTRUCTION => "Illegal instruction has been detected",
T1Error.REGISTER_ADDRESS_ERROR => "Excess register address range has been programmed",
T1Error.BOUNDARY_ERROR => "Illegal register address is designated by index modification",
T1Error.MEMORY_FULL => "Program memory is insufficient for the requested command",
T1Error.MODE_MISMATCH => "Received command is invalid in the current T1/T1S operation mode",
T1Error.REGISTER_ADDRESS_SIZE_ERROR => "Specified register range exceeds the limit",
T1Error.DUPLICATE_ENTRY_NR => "Multiple subroutines which has same subroutine number have been programmed",
_ => null,
};
}
}
}
@@ -1,4 +1,4 @@
namespace PamhagenSysCtrl.Helpers { namespace PamhagenSysCtrl.Helpers.Plc {
public class T1Exception(string msg) : Exception(msg) { public class T1Exception(string msg) : Exception(msg) {
} }
} }
-73
View File
@@ -1,73 +0,0 @@
namespace PamhagenSysCtrl.Helpers {
public enum T1Error {
UNKNOWN_ERROR = -1,
NO_ERROR = 0,
SYSTEM_POWER_ON = 10,
SYSTEM_POWER_OFF = 11,
RAM_CHECK_ERROR = 20,
PROGRAM_BCC_ERROR = 21,
BATTERY_VOLTAGE_DROP = 22,
EEPROM_BCC_ERROR = 23,
EEPROM_WARNING = 26,
SYSTEM_RAM_CHECK_ERROR = 30,
SYSTEM_ROM_BCC_ERROR = 31,
PERIPHERAL_LSI_ERROR = 32,
CLOCK_CALENDAR_CHECK_ERROR = 33,
ILLEGAL_SYSTEM_INTERRUPT = 34,
WD_TIMER_ERROR = 35,
IO_BUS_ERROR = 40,
IO_MISMATCH = 41,
IO_NO_ANSWER = 42,
IO_PARITY_ERROR = 43,
ILLEGAL_IO_REGISTER = 46,
COMMUNICATION_BUSY = 51,
FORMAT_ERROR = 52,
SCAN_TIME_OVER = 64,
NO_END_IRET_ERROR = 80,
PAIR_INSTRUCTION_ERROR = 81,
OPERAND_ERROR = 82,
INVALID_PROGRAM = 83,
NO_SUBROUTINE_ENTRY = 86,
NO_RET_ERROR = 87,
SUBROUTINE_NESTING_ERROR = 88,
LOOP_NESTING_ERROR = 89,
INVALID_FUNCTION_INSTRUCTION = 98,
PASSWORD_PROTECT = 106,
ILLEGAL_INSTRUCTION = 110,
REGISTER_ADDRESS_ERROR = 111,
BOUNDARY_ERROR = 112,
MEMORY_FULL = 113,
MODE_MISMATCH = 114,
REGISTER_ADDRESS_SIZE_ERROR = 115,
DUPLICATE_ENTRY_NR = 121,
}
public static class T1ErrorExtension {
public static bool IsError(this T1Error error) {
return error switch {
T1Error.NO_ERROR => false,
T1Error.SYSTEM_POWER_ON => false,
T1Error.SYSTEM_POWER_OFF => false,
_ => true,
};
}
public static string GetName(this T1Error error) {
return error switch {
T1Error.NO_ERROR => "No error",
T1Error.SYSTEM_POWER_ON => "System power on",
T1Error.SYSTEM_POWER_OFF => "System power off",
// TODO
_ => "Unknown error",
};
}
public static string? GetDescription(this T1Error error) {
return error switch {
T1Error.NO_ERROR => "No error recorded",
T1Error.SYSTEM_POWER_ON => "Power on (no error)",
T1Error.SYSTEM_POWER_OFF => "Power off (no error)",
// TODO
_ => null,
};
}
}
}
+77
View File
@@ -0,0 +1,77 @@
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Text.Json.Nodes;
namespace PamhagenSysCtrl.Helpers {
public static class UpdateService {
public static async Task<(string Version, string Url, long Size)?> GetLatestInstallerUrl(string feedUrl) {
try {
using var client = new HttpClient() {
Timeout = TimeSpan.FromSeconds(5),
};
client.DefaultRequestHeaders.UserAgent.Clear();
client.DefaultRequestHeaders.UserAgent.ParseAdd($"PamhagenSysCtrl/{App.Version} ({Environment.MachineName}, {Environment.OSVersion})");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new("application/json"));
using var res = await client.GetAsync(feedUrl);
if (!res.IsSuccessStatusCode)
return null;
var json = JsonNode.Parse(await res.Content.ReadAsStringAsync());
var latest = json!["data"]!.AsArray()[^1]!;
return ((string)latest["version"]!, (string)latest["url"]!, (long)latest["size"]!);
} catch {
return null;
}
}
public static async Task DownloadInstaller(string url, string filename, IProgress<double>? progress = null, CancellationToken cancellationToken = default) {
try {
using var client = new HttpClient() {
Timeout = TimeSpan.FromSeconds(5),
};
client.DefaultRequestHeaders.UserAgent.Clear();
client.DefaultRequestHeaders.UserAgent.ParseAdd($"PamhagenSysCtrl/{App.Version} ({Environment.MachineName}, {Environment.OSVersion})");
client.DefaultRequestHeaders.Accept.Clear();
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();
var contentLength = response.Content.Headers.ContentLength;
await using (var destination = new FileStream(filename, FileMode.Create)) {
await using var source = await response.Content.ReadAsStreamAsync(cancellationToken);
var buffer = new byte[81920];
long downloaded = 0;
int read;
while ((read = await source.ReadAsync(buffer, cancellationToken)) != 0) {
await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken);
downloaded += read;
if (contentLength.HasValue) {
progress?.Report((double)downloaded / contentLength.Value);
}
}
}
progress?.Report(1);
} catch {
File.Delete(filename);
throw;
}
}
public static void StartInstaller(string fileName) {
var startInfo = new ProcessStartInfo {
FileName = "msiexec.exe",
UseShellExecute = true,
Verb = "runas",
};
startInfo.ArgumentList.Add("/i");
startInfo.ArgumentList.Add(Path.GetFullPath(fileName));
if (Process.Start(startInfo) == null) {
throw new InvalidOperationException("Der Installer konnte nicht gestartet werden.");
}
}
}
}
+135
View File
@@ -0,0 +1,135 @@
using PIEHidNetCore;
using System.Drawing;
using System.IO;
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;
}
public static readonly Color Off = Color.FromArgb(0, 0, 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(128, 128, 0);
public static readonly Color Magenta = Color.FromArgb(128, 0, 128);
public static readonly Color Cyan = Color.FromArgb(0, 128, 128);
public static readonly Color Orange = Color.FromArgb(255, 32, 0);
public static readonly Color Purple = Color.FromArgb(192, 32, 192);
public static readonly Color Lime = Color.FromArgb(64, 255, 4);
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]);
private readonly (Color Color, bool Flashing)[] LastStates = new (Color, bool)[48];
public bool IsKeyPressed(int keyIndex) => LastKeyState.GetKey(keyIndex);
public event EventHandler<int>? KeyDown;
public event EventHandler<int>? KeyUp;
public event EventHandler<KeyState>? KeysChanged;
public event EventHandler<int>? Error;
public Xbe24Keyboard(int pid = 0x0555) {
foreach (var device in PIEDevice.EnumeratePIE()) {
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}");
SetBacklight(DefaltBacklight);
}
public void Dispose() {
GC.SuppressFinalize(this);
SetBacklight(DefaltBacklight);
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;
Error?.Invoke(this, error);
}
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)]);
}
}
}
Binary file not shown.
+6 -3
View File
@@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework> <TargetFramework>net10.0-windows</TargetFramework>
<Version>0.0.1</Version> <Version>0.0.4</Version>
<Product>Anlagensteuerung Pamhagen</Product> <Product>Anlagensteuerung Pamhagen</Product>
<AssemblyTitle>Anlagensteuerung Pamhagen</AssemblyTitle> <AssemblyTitle>Anlagensteuerung Pamhagen</AssemblyTitle>
<AssemblyName>PamhagenSysCtrl</AssemblyName> <AssemblyName>PamhagenSysCtrl</AssemblyName>
@@ -16,8 +16,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.10" /> <PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.11" />
<PackageReference Include="System.IO.Ports" Version="10.0.10" /> <PackageReference Include="System.IO.Ports" Version="10.0.11" />
<Reference Include="PIEHidNetCore">
<HintPath>PIEHidNetCore.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
</Project> </Project>
+28
View File
@@ -0,0 +1,28 @@
<Window x:Class="PamhagenSysCtrl.Windows.AboutWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Über - Anlagensteuerung Pamhagen"
Height="340" Width="560" ResizeMode="NoResize"
WindowStartupLocation="CenterOwner">
<Grid>
<TextBlock Margin="20,10" FontSize="12">
<Bold>Produkt:</Bold> Anlagensteuerung Pamhagen<LineBreak/>
<Bold>Beschreibung:</Bold> Schnittstelle zu einer SPS und grafische Benutzeroberfläche für die<LineBreak/>
<Bold Foreground="Transparent">Beschreibung:</Bold> Steuerung der Anlage<LineBreak/>
<Bold>Typ:</Bold> HMI- und SPS-Steuerungssoftware<LineBreak/>
<Bold>Version:</Bold> <Run x:Name="VersionText">0.0.0</Run><LineBreak/>
<Bold>Lizenz:</Bold> <Hyperlink NavigateUri="https://www.gnu.org/licenses/gpl-3.0.html" RequestNavigate="Hyperlink_RequestNavigate">GNU General Public License 3.0 (GPLv3)</Hyperlink><LineBreak/>
<Bold>Entwickler:</Bold> Lorenz Stechauner, Thomas Hilscher<LineBreak/>
<Bold>Kontakt:</Bold> <Hyperlink NavigateUri="mailto:lorenz.stechauner@necronda.net" RequestNavigate="Hyperlink_RequestNavigate">lorenz.stechauner@necronda.net</Hyperlink>, <Hyperlink NavigateUri="mailto:thomas.hilscher@gmail.com" RequestNavigate="Hyperlink_RequestNavigate">thomas.hilscher@gmail.com</Hyperlink><LineBreak/>
<Bold>Quellcode:</Bold> <Hyperlink NavigateUri="C:\Program Files\Anlagensteuerung Pamhagen\src" RequestNavigate="Hyperlink_RequestNavigate_Explorer">C:\Program Files\Anlagensteuerung Pamhagen\src</Hyperlink>,<LineBreak/>
<Bold Foreground="Transparent">Quellcode:</Bold> <Hyperlink NavigateUri="https://git.necronda.net/winzer/pamhagen-sysctrl" RequestNavigate="Hyperlink_RequestNavigate">https://git.necronda.net/winzer/pamhagen-sysctrl</Hyperlink><LineBreak/>
<Bold>Entwicklungszeitraum:</Bold> 2026<LineBreak/>
<LineBreak/>
<Bold>Verwendete Technologien:</Bold><LineBreak/>
Programmiersprache: C#<LineBreak/>
Framework: Windows Presentation Framework (WPF)<LineBreak/>
SPS-Schnittstelle: Serielle Verbindung<LineBreak/>
Paketierung: <Hyperlink NavigateUri="https://www.firegiant.com/wixtoolset/" RequestNavigate="Hyperlink_RequestNavigate">WiX Toolset</Hyperlink>
</TextBlock>
</Grid>
</Window>
@@ -0,0 +1,25 @@
using System.Diagnostics;
using System.Windows;
using System.Windows.Navigation;
namespace PamhagenSysCtrl.Windows {
public partial class AboutWindow : Window {
public AboutWindow() {
InitializeComponent();
VersionText.Text = App.Version.ToString();
}
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs evt) {
try {
Process.Start(new ProcessStartInfo(evt.Uri.AbsoluteUri) { UseShellExecute = true });
} catch { }
}
private void Hyperlink_RequestNavigate_Explorer(object sender, RequestNavigateEventArgs evt) {
try {
Process.Start("explorer.exe", evt.Uri.AbsoluteUri);
} catch { }
}
}
}
@@ -5,13 +5,14 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PamhagenSysCtrl" xmlns:local="clr-namespace:PamhagenSysCtrl"
mc:Ignorable="d" mc:Ignorable="d"
Closed="OnClosed"
Title="Handbetrieb - Anlagensteuerung Pamhagen" Height="550" Width="860"> Title="Handbetrieb - Anlagensteuerung Pamhagen" Height="550" Width="860">
<Grid> <Grid>
<Grid x:Name="ValvePanel" Margin="5,5,5,5"> <Grid x:Name="ValvePanel" Margin="5,5,5,5">
</Grid> </Grid>
<TextBlock x:Name="Status" FontSize="14" <TextBlock x:Name="Status" FontSize="14"
Margin="350,50,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"/> Margin="350,10,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<Grid x:Name="ActuatorPanel" Margin="350,350,5,5"> <Grid x:Name="ActuatorPanel" Margin="350,350,5,5">
@@ -1,4 +1,5 @@
using PamhagenSysCtrl.Helpers; using PamhagenSysCtrl.Helpers;
using PamhagenSysCtrl.Windows;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
@@ -28,14 +29,14 @@ namespace PamhagenSysCtrl {
if (App.Plant == null || sender is not Button b) return; if (App.Plant == null || sender is not Button b) return;
var i = int.Parse($"{b.Content}"[1..]); var i = int.Parse($"{b.Content}"[1..]);
if (i == 58) { if (i == 58) {
if (App.Plant.WantMW1Selected) { if (App.Plant.State.MW1_WantSelected) {
App.Plant.SelectMW2(); App.Plant.SelectMW2();
} else { } else {
App.Plant.SelectMW1(); App.Plant.SelectMW1();
} }
} else if (i > 57) { } else if (i > 57) {
return; return;
} else if (App.Plant.WantValveOpen(i)) { } else if (App.Plant.State.Valve_WantOpen(i)) {
App.Plant.CloseV(i); App.Plant.CloseV(i);
} else { } else {
App.Plant.OpenV(i); App.Plant.OpenV(i);
@@ -47,12 +48,20 @@ namespace PamhagenSysCtrl {
App.Plant?.Update += OnUpdate; App.Plant?.Update += OnUpdate;
} }
private void OnClosed(object sender, EventArgs evt) {
foreach (var win in Application.Current.Windows) {
if (win is PlantSchemeWindow w) {
w.Menu_Settings_ManualControl.IsChecked = false;
}
}
}
public void OnUpdate(object? sender, PlantEventArgs evt) { public void OnUpdate(object? sender, PlantEventArgs evt) {
if (sender is not PamhagenPlant plant) return; if (sender is not PamhagenPlant plant) return;
for (int i = 1; i <= 58; i++) { for (int i = 1; i <= 58; i++) {
var target = plant.WantValveClosed(i); var target = plant.State.Valve_WantClosed(i);
var actual = plant.IsValveClosed(i); var actual = plant.State.Valve_IsClosed(i);
if (ValvePanel.Children[i - 1] is not Button b) if (ValvePanel.Children[i - 1] is not Button b)
continue; continue;
b.BorderBrush = target ? Brushes.DarkRed : Brushes.DarkGreen; b.BorderBrush = target ? Brushes.DarkRed : Brushes.DarkGreen;
@@ -60,98 +69,98 @@ namespace PamhagenSysCtrl {
b.Background = actual ? Brushes.MistyRose : Brushes.MintCream; b.Background = actual ? Brushes.MistyRose : Brushes.MintCream;
} }
ReblerButton.BorderBrush = plant.IsReblerActive ? Brushes.DarkGreen : Brushes.DarkRed; ReblerButton.BorderBrush = plant.State.Rebler_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
ReblerButton.Foreground = plant.IsReblerActive ? Brushes.DarkGreen : Brushes.DarkRed; ReblerButton.Foreground = plant.State.Rebler_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
ReblerButton.Background = plant.IsReblerActive ? Brushes.MintCream: Brushes.MistyRose; ReblerButton.Background = plant.State.Rebler_IsActive ? Brushes.MintCream: Brushes.MistyRose;
TroughAugerButton.BorderBrush = plant.IsTroughAugerActive ? Brushes.DarkGreen : Brushes.DarkRed; TroughAugerButton.BorderBrush = plant.State.TroughAuger_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
TroughAugerButton.Foreground = plant.IsTroughAugerActive ? Brushes.DarkGreen : Brushes.DarkRed; TroughAugerButton.Foreground = plant.State.TroughAuger_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
TroughAugerButton.Background = plant.IsTroughAugerActive ? Brushes.MintCream : Brushes.MistyRose; TroughAugerButton.Background = plant.State.TroughAuger_IsActive ? Brushes.MintCream : Brushes.MistyRose;
StirrerMW1Button.BorderBrush = plant.IsStirrerMW1Active ? Brushes.DarkGreen : Brushes.DarkRed; StirrerMW1Button.BorderBrush = plant.State.MW1_Stirrer_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
StirrerMW1Button.Foreground = plant.IsStirrerMW1Active ? Brushes.DarkGreen : Brushes.DarkRed; StirrerMW1Button.Foreground = plant.State.MW1_Stirrer_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
StirrerMW1Button.Background = plant.IsStirrerMW1Active ? Brushes.MintCream : Brushes.MistyRose; StirrerMW1Button.Background = plant.State.MW1_Stirrer_IsActive ? Brushes.MintCream : Brushes.MistyRose;
StirrerMW2Button.BorderBrush = plant.IsStirrerMW2Active ? Brushes.DarkGreen : Brushes.DarkRed; StirrerMW2Button.BorderBrush = plant.State.MW2_Stirrer_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
StirrerMW2Button.Foreground = plant.IsStirrerMW2Active ? Brushes.DarkGreen : Brushes.DarkRed; StirrerMW2Button.Foreground = plant.State.MW2_Stirrer_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
StirrerMW2Button.Background = plant.IsStirrerMW2Active ? Brushes.MintCream : Brushes.MistyRose; StirrerMW2Button.Background = plant.State.MW2_Stirrer_IsActive ? Brushes.MintCream : Brushes.MistyRose;
Auger1Button.BorderBrush = plant.IsAuger1Active ? Brushes.DarkGreen : Brushes.DarkRed; Auger1Button.BorderBrush = plant.State.Auger1_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
Auger1Button.Foreground = plant.IsAuger1Active ? Brushes.DarkGreen : Brushes.DarkRed; Auger1Button.Foreground = plant.State.Auger1_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
Auger1Button.Background = plant.IsAuger1Active ? Brushes.MintCream : Brushes.MistyRose; Auger1Button.Background = plant.State.Auger1_IsActive ? Brushes.MintCream : Brushes.MistyRose;
Auger2Button.BorderBrush = plant.IsAuger2Active ? Brushes.DarkGreen : Brushes.DarkRed; Auger2Button.BorderBrush = plant.State.Auger2_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
Auger2Button.Foreground = plant.IsAuger2Active ? Brushes.DarkGreen : Brushes.DarkRed; Auger2Button.Foreground = plant.State.Auger2_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
Auger2Button.Background = plant.IsAuger2Active ? Brushes.MintCream : Brushes.MistyRose; Auger2Button.Background = plant.State.Auger2_IsActive ? Brushes.MintCream : Brushes.MistyRose;
Auger3Button.BorderBrush = plant.IsAuger3Active ? Brushes.DarkGreen : Brushes.DarkRed; Auger3Button.BorderBrush = plant.State.Auger3_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
Auger3Button.Foreground = plant.IsAuger3Active ? Brushes.DarkGreen : Brushes.DarkRed; Auger3Button.Foreground = plant.State.Auger3_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
Auger3Button.Background = plant.IsAuger3Active ? Brushes.MintCream : Brushes.MistyRose; Auger3Button.Background = plant.State.Auger3_IsActive ? Brushes.MintCream : Brushes.MistyRose;
MP1BackwardButton.BorderBrush = plant.MP1 == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray; MP1BackwardButton.BorderBrush = plant.State.MP1_State == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray;
MP1BackwardButton.Foreground = plant.MP1 == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray; MP1BackwardButton.Foreground = plant.State.MP1_State == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray;
MP1BackwardButton.Background = plant.MP1 == MotorState.Backward ? Brushes.MintCream : Brushes.WhiteSmoke; MP1BackwardButton.Background = plant.State.MP1_State == MotorState.Backward ? Brushes.MintCream : Brushes.WhiteSmoke;
MP1HaltButton.BorderBrush = plant.MP1 == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray; MP1HaltButton.BorderBrush = plant.State.MP1_State == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray;
MP1HaltButton.Foreground = plant.MP1 == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray; MP1HaltButton.Foreground = plant.State.MP1_State == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray;
MP1HaltButton.Background = plant.MP1 == MotorState.Halt ? Brushes.MistyRose : Brushes.WhiteSmoke; MP1HaltButton.Background = plant.State.MP1_State == MotorState.Halt ? Brushes.MistyRose : Brushes.WhiteSmoke;
MP1ForwardButton.BorderBrush = plant.MP1 == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray; MP1ForwardButton.BorderBrush = plant.State.MP1_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray;
MP1ForwardButton.Foreground = plant.MP1 == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray; MP1ForwardButton.Foreground = plant.State.MP1_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray;
MP1ForwardButton.Background = plant.MP1 == MotorState.Forward ? Brushes.MintCream: Brushes.WhiteSmoke; MP1ForwardButton.Background = plant.State.MP1_State == MotorState.Forward ? Brushes.MintCream: Brushes.WhiteSmoke;
MP2BackwardButton.BorderBrush = plant.MP2 == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray; MP2BackwardButton.BorderBrush = plant.State.MP2_State == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray;
MP2BackwardButton.Foreground = plant.MP2 == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray; MP2BackwardButton.Foreground = plant.State.MP2_State == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray;
MP2BackwardButton.Background = plant.MP2 == MotorState.Backward ? Brushes.MintCream : Brushes.WhiteSmoke; MP2BackwardButton.Background = plant.State.MP2_State == MotorState.Backward ? Brushes.MintCream : Brushes.WhiteSmoke;
MP2HaltButton.BorderBrush = plant.MP2 == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray; MP2HaltButton.BorderBrush = plant.State.MP2_State == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray;
MP2HaltButton.Foreground = plant.MP2 == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray; MP2HaltButton.Foreground = plant.State.MP2_State == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray;
MP2HaltButton.Background = plant.MP2 == MotorState.Halt ? Brushes.MistyRose : Brushes.WhiteSmoke; MP2HaltButton.Background = plant.State.MP2_State == MotorState.Halt ? Brushes.MistyRose : Brushes.WhiteSmoke;
MP2ForwardButton.BorderBrush = plant.MP2 == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray; MP2ForwardButton.BorderBrush = plant.State.MP2_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray;
MP2ForwardButton.Foreground = plant.MP2 == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray; MP2ForwardButton.Foreground = plant.State.MP2_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray;
MP2ForwardButton.Background = plant.MP2 == MotorState.Forward ? Brushes.MintCream : Brushes.WhiteSmoke; MP2ForwardButton.Background = plant.State.MP2_State == MotorState.Forward ? Brushes.MintCream : Brushes.WhiteSmoke;
P12Button.BorderBrush = plant.P12 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P12Button.BorderBrush = plant.State.P12_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P12Button.Foreground = plant.P12 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P12Button.Foreground = plant.State.P12_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P12Button.Background = plant.P12 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P12Button.Background = plant.State.P12_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
P3Button.BorderBrush = plant.P3 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P3Button.BorderBrush = plant.State.P3_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P3Button.Foreground = plant.P3 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P3Button.Foreground = plant.State.P3_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P3Button.Background = plant.P3 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P3Button.Background = plant.State.P3_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
P4Button.BorderBrush = plant.P4 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P4Button.BorderBrush = plant.State.P4_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P4Button.Foreground = plant.P4 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P4Button.Foreground = plant.State.P4_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P4Button.Background = plant.P4 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P4Button.Background = plant.State.P4_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
P5Button.BorderBrush = plant.P5 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P5Button.BorderBrush = plant.State.P5_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P5Button.Foreground = plant.P5 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P5Button.Foreground = plant.State.P5_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P5Button.Background = plant.P5 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P5Button.Background = plant.State.P5_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
P6Button.BorderBrush = plant.P6 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P6Button.BorderBrush = plant.State.P6_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P6Button.Foreground = plant.P6 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P6Button.Foreground = plant.State.P6_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P6Button.Background = plant.P6 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P6Button.Background = plant.State.P6_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
Status.Text = $""" Status.Text = $"""
Gradation °Oe: {plant.GradationOe:N2} Presse Querschnecke: {plant.State.PressCrossAuger_State}
Presse Querschnecke: {plant.PresseQuerSchnecke} Freigaben:
Füllstände: Presse 1: {plant.State.Press1_HasClearance}
Maischewanne 1: {plant.FillLevelMW1} (ausgewählt: {plant.WantMW1Selected}, Status: {plant.IsMWSelected}) Presse 2: {plant.State.Press2_HasClearance}
Maischewanne 2: {plant.FillLevelMW2} (ausgewählt: {plant.WantMW2Selected}, Status: {plant.IsMW2Selected}) Füllstände:
Presse 1: {plant.Press1HasClearance} Maischewanne 1: {plant.State.MW1_FillLevel} (ausgewählt-soll: {plant.State.MW1_WantSelected}, ausgewählt-ist: {plant.State.MW1_IsSelected})
Presse 2: {plant.Press2HasClearance} Maischewanne 2: {plant.State.MW2_FillLevel} (ausgewählt-soll: {plant.State.MW2_WantSelected}, ausgewählt-ist: {plant.State.MW2_IsSelected})
Tank 1: {plant.FillLevelT1} Tank 1: {plant.State.T1_FillLevel}
Tank 2: {plant.FillLevelT2} Tank 2: {plant.State.T2_FillLevel}
Tank 3: {plant.FillLevelT3} Tank 3: {plant.State.T3_FillLevel}
Tank 4: {plant.FillLevelT4} Tank 4: {plant.State.T4_FillLevel}
Tank 5: {plant.FillLevelT5} Tank 5: {plant.State.T5_FillLevel}
Tank 6: {plant.FillLevelT6} Tank 6: {plant.State.T6_FillLevel}
Tank 7: {plant.FillLevelT7} Tank 7: {plant.State.T7_FillLevel}
Tank 8: {plant.FillLevelT8} Tank 8: {plant.State.T8_FillLevel}
Tank 9: {plant.FillLevelT9} Tank 9: {plant.State.T9_FillLevel}
"""; """;
} }
private void TroughAugerButton_Click(object? sender, RoutedEventArgs? evt) { private void TroughAugerButton_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.IsTroughAugerActive) { if (App.Plant.State.TroughAuger_IsActive) {
App.Plant.StopTroughAuger(); App.Plant.StopTroughAuger();
} else { } else {
App.Plant.StartTroughAuger(); App.Plant.StartTroughAuger();
@@ -164,7 +173,7 @@ namespace PamhagenSysCtrl {
private void ReblerButton_Click(object? sender, RoutedEventArgs? evt) { private void ReblerButton_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.IsReblerActive) { if (App.Plant.State.Rebler_IsActive) {
App.Plant.StopRebler(); App.Plant.StopRebler();
} else { } else {
App.Plant.StartRebler(); App.Plant.StartRebler();
@@ -177,7 +186,7 @@ namespace PamhagenSysCtrl {
private void StirrerMW1Button_Click(object? sender, RoutedEventArgs? evt) { private void StirrerMW1Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.IsStirrerMW1Active) { if (App.Plant.State.MW1_Stirrer_IsActive) {
App.Plant.StopStirrerMW1(); App.Plant.StopStirrerMW1();
} else { } else {
App.Plant.StartStirrerMW1(); App.Plant.StartStirrerMW1();
@@ -190,7 +199,7 @@ namespace PamhagenSysCtrl {
private void StirrerMW2Button_Click(object? sender, RoutedEventArgs? evt) { private void StirrerMW2Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.IsStirrerMW2Active) { if (App.Plant.State.MW2_Stirrer_IsActive) {
App.Plant.StopStirrerMW2(); App.Plant.StopStirrerMW2();
} else { } else {
App.Plant.StartStirrerMW2(); App.Plant.StartStirrerMW2();
@@ -203,7 +212,7 @@ namespace PamhagenSysCtrl {
private void Auger1Button_Click(object? sender, RoutedEventArgs? evt) { private void Auger1Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.IsAuger1Active) { if (App.Plant.State.Auger1_IsActive) {
App.Plant.StopAuger1(); App.Plant.StopAuger1();
} else { } else {
App.Plant.StartAuger1(); App.Plant.StartAuger1();
@@ -216,7 +225,7 @@ namespace PamhagenSysCtrl {
private void Auger2Button_Click(object? sender, RoutedEventArgs? evt) { private void Auger2Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.IsAuger2Active) { if (App.Plant.State.Auger2_IsActive) {
App.Plant.StopAuger2(); App.Plant.StopAuger2();
} else { } else {
App.Plant.StartAuger2(); App.Plant.StartAuger2();
@@ -229,7 +238,7 @@ namespace PamhagenSysCtrl {
private void Auger3Button_Click(object? sender, RoutedEventArgs? evt) { private void Auger3Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.IsAuger3Active) { if (App.Plant.State.Auger3_IsActive) {
App.Plant.StopAuger3(); App.Plant.StopAuger3();
} else { } else {
App.Plant.StartAuger3(); App.Plant.StartAuger3();
@@ -297,10 +306,10 @@ namespace PamhagenSysCtrl {
private void P12Button_Click(object? sender, RoutedEventArgs? evt) { private void P12Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.P12 == MotorState.Forward) { if (App.Plant.State.P12_IsActive) {
App.Plant.StopP12(); App.Plant.DisableP12();
} else { } else {
App.Plant.StartP12(); App.Plant.EnableP12();
} }
} catch (NoClearanceException exc) { } catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
@@ -310,10 +319,10 @@ namespace PamhagenSysCtrl {
private void P3Button_Click(object? sender, RoutedEventArgs? evt) { private void P3Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.P3 == MotorState.Forward) { if (App.Plant.State.P3_IsActive) {
App.Plant.StopP3(); App.Plant.DisableP3();
} else { } else {
App.Plant.StartP3(); App.Plant.EnableP3();
} }
} catch (NoClearanceException exc) { } catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
@@ -323,10 +332,10 @@ namespace PamhagenSysCtrl {
private void P4Button_Click(object? sender, RoutedEventArgs? evt) { private void P4Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.P4 == MotorState.Forward) { if (App.Plant.State.P4_IsActive) {
App.Plant.StopP4(); App.Plant.DisableP4();
} else { } else {
App.Plant.StartP4(); App.Plant.EnableP4();
} }
} catch (NoClearanceException exc) { } catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
@@ -336,10 +345,10 @@ namespace PamhagenSysCtrl {
private void P5Button_Click(object? sender, RoutedEventArgs? evt) { private void P5Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.P5 == MotorState.Forward) { if (App.Plant.State.P5_IsActive) {
App.Plant.StopP5(); App.Plant.DisableP5();
} else { } else {
App.Plant.StartP5(); App.Plant.EnableP5();
} }
} catch (NoClearanceException exc) { } catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
@@ -349,10 +358,10 @@ namespace PamhagenSysCtrl {
private void P6Button_Click(object? sender, RoutedEventArgs? evt) { private void P6Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.P6 == MotorState.Forward) { if (App.Plant.State.P6_IsActive) {
App.Plant.StopP6(); App.Plant.DisableP6();
} else { } else {
App.Plant.StartP6(); App.Plant.EnableP6();
} }
} catch (NoClearanceException exc) { } catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
+219 -12
View File
@@ -4,7 +4,10 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PamhagenSysCtrl.Windows" xmlns:local="clr-namespace:PamhagenSysCtrl.Windows"
Title="Anlagensteuerung Pamhagen" Height="950" Width="1600" MinWidth="1500" MinHeight="950"> Closed="OnClosed"
SizeChanged="OnSizeChanged"
Title="Anlagensteuerung Pamhagen"
Height="800" Width="1400" MinWidth="1000" MinHeight="500">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="19"/> <RowDefinition Height="19"/>
@@ -14,20 +17,23 @@
<Menu Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="LightGray" Background="White"> <Menu Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="LightGray" Background="White">
<MenuItem Header="Einstellungen"> <MenuItem Header="Einstellungen">
<MenuItem x:Name="Menu_Settings_ManualControl" Header="Handbetrieb" <MenuItem x:Name="Menu_Settings_ManualControl" Header="Handbetrieb" IsCheckable="True"
Click="Menu_Settings_ManualControl_Click" IsCheckable="True"> Checked="Menu_Settings_ManualControl_Changed"
Unchecked="Menu_Settings_ManualControl_Changed">
<MenuItem.Icon> <MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xF271;"/> <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xF271;"/>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem x:Name="Menu_Settings_OverridePathClearances" Header="Pfad-Freigaben überbrücken" <MenuItem x:Name="Menu_Settings_OverridePathClearances" Header="Pfad-Freigaben überbrücken" IsCheckable="True"
Click="Menu_Settings_OverridePathClearances_Click" IsCheckable="True"> Checked="Menu_Settings_OverridePathClearances_Changed"
Unchecked="Menu_Settings_OverridePathClearances_Changed">
<MenuItem.Icon> <MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE785;"/> <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE785;"/>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem x:Name="Menu_Settings_OverrideDryRunClearances" Header="Trockenlauf-Freigaben überbrücken" <MenuItem x:Name="Menu_Settings_OverrideDryRunClearances" Header="Trockenlauf-Freigaben überbrücken" IsCheckable="True"
Click="Menu_Settings_OverrideDryRunClearances_Click" IsCheckable="True"/> Checked="Menu_Settings_OverrideDryRunClearances_Changed"
Unchecked="Menu_Settings_OverrideDryRunClearances_Changed"/>
<Separator/> <Separator/>
<MenuItem x:Name="Menu_Settings_Config" Header="Konfigurationsdatei öffnen..." Click="Menu_Settings_Config_Click"> <MenuItem x:Name="Menu_Settings_Config" Header="Konfigurationsdatei öffnen..." Click="Menu_Settings_Config_Click">
<MenuItem.Icon> <MenuItem.Icon>
@@ -41,23 +47,224 @@
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
<MenuItem Header="Hilfe"> <MenuItem Header="Hilfe">
<MenuItem Header="Über"> <MenuItem Header="Über" Click="Menu_Help_About_Click">
<MenuItem.Icon> <MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE946;"/> <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE946;"/>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem x:Name="Menu_Help_CheckForUpdates" Header="Nach Updates suchen..." Click="Menu_Help_CheckForUpdates_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE895;"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem> </MenuItem>
</Menu> </Menu>
<Grid Grid.Row="1" ClipToBounds="True"> <Grid Grid.Row="1" ClipToBounds="True">
<Canvas x:Name="SchemeCanvas" Width="1550" Height="900" VerticalAlignment="Center" HorizontalAlignment="Center" SnapsToDevicePixels="True" <Canvas x:Name="SchemeCanvas" VerticalAlignment="Center" HorizontalAlignment="Center" SnapsToDevicePixels="True" Grid.ColumnSpan="2" Margin="25,0,0,0"
MouseMove="SchemeCanvas_MouseMove" MouseLeftButtonDown="SchemeCanvas_MouseLeftButtonDown" MouseLeftButtonUp="SchemeCanvas_MouseLeftButtonUp" Grid.ColumnSpan="2" Margin="25,0,0,0"> MouseMove="SchemeCanvas_MouseMove" MouseWheel="SchemeCanvas_MouseWheel"
MouseLeftButtonDown="SchemeCanvas_MouseLeftButtonDown" MouseLeftButtonUp="SchemeCanvas_MouseLeftButtonUp">
</Canvas> </Canvas>
<Border Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Bottom" Padding="10,10,20,20" Margin="0,0,-11,-11"
Background="WhiteSmoke"
BorderBrush="Gray" BorderThickness="1" CornerRadius="10">
<Grid x:Name="FastSelectButtonsRight">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="160"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="5"/>
<RowDefinition Height="50"/>
<RowDefinition Height="5"/>
<RowDefinition Height="50"/>
<RowDefinition Height="5"/>
<RowDefinition Height="50"/>
<RowDefinition Height="5"/>
<RowDefinition Height="50"/>
<RowDefinition Height="5"/>
<RowDefinition Height="50"/>
<RowDefinition Height="5"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Button x:Name="FastSelectButton_MW1_Press1" Grid.Column="0" Grid.Row="0"
Content="MW1 &#x2B9E; Presse 1" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW2_Press1" Grid.Column="2" Grid.Row="0"
Content="MW2 &#x2B9E; Presse 1" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW1_Press2" Grid.Column="0" Grid.Row="2"
Content="MW1 &#x2B9E; Presse 2" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW2_Press2" Grid.Column="2" Grid.Row="2"
Content="MW2 &#x2B9E; Presse 2" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW1_Tank1" Grid.Column="0" Grid.Row="4"
Content="MW1 &#x2B9E; Tank 1" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW2_Tank1" Grid.Column="2" Grid.Row="4"
Content="MW2 &#x2B9E; Tank 1" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW1_Tank2" Grid.Column="0" Grid.Row="6"
Content="MW1 &#x2B9E; Tank 2" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW2_Tank2" Grid.Column="2" Grid.Row="6"
Content="MW2 &#x2B9E; Tank 2" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW1_Tank3" Grid.Column="0" Grid.Row="8"
Content="MW1 &#x2B9E; Tank 3" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW2_Tank3" Grid.Column="2" Grid.Row="8"
Content="MW2 &#x2B9E; Tank 3" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW1_Tank4" Grid.Column="0" Grid.Row="10"
Content="MW1 &#x2B9E; Tank 4" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW2_Tank4" Grid.Column="2" Grid.Row="10"
Content="MW2 &#x2B9E; Tank 4" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW1_Tank5" Grid.Column="0" Grid.Row="12"
Content="MW1 &#x2B9E; Tank 5" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW2_Tank5" Grid.Column="2" Grid.Row="12"
Content="MW2 &#x2B9E; Tank 5" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
</Grid>
</Border>
<Border HorizontalAlignment="Left" VerticalAlignment="Bottom" Padding="60,15,15,30" Margin="-11,0,0,-11"
Background="WhiteSmoke"
BorderBrush="Gray" BorderThickness="1" CornerRadius="10">
<Grid x:Name="FastSelectButtonsLeft">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="2.5"/>
<ColumnDefinition Width="2.5"/>
<ColumnDefinition Width="160"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition Height="5"/>
<RowDefinition Height="50"/>
<RowDefinition Height="5"/>
<RowDefinition Height="80"/>
<RowDefinition Height="80"/>
</Grid.RowDefinitions>
<Button x:Name="FastSelectButton_EntryTrough_Stop" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2"
FontSize="14" FontWeight="Bold"
Click="FastSelectButton_EntryTroughStop_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2">
<TextBlock TextAlignment="Center">Auffangwanne<LineBreak/><Run x:Name="FastSelectButton_EntryTrough_Stop_Run" FontSize="18">Schnecke Stop</Run></TextBlock>
</Button>
<Button x:Name="FastSelectButton_EntryTrough_Start" Grid.Column="2" Grid.Row="0" Grid.ColumnSpan="2"
FontSize="14" FontWeight="Bold"
Click="FastSelectButton_EntryTroughStart_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2">
<TextBlock TextAlignment="Center">Auffangwanne<LineBreak/><Run x:Name="FastSelectButton_EntryTrough_Start_Run" FontSize="18">Schnecke Start</Run></TextBlock>
</Button>
<Button x:Name="FastSelectButton_MW2" Grid.Column="0" Grid.Row="2"
Content="MW2 &#x2B9C;" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_MW2_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW1" Grid.Column="3" Grid.Row="2"
Content="&#x2B9E; MW1" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_MW1_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MP2_Forward" Grid.Column="0" Grid.Row="4"
FontSize="16" FontWeight="Bold"
Click="FastSelectButton_MP2_Forward_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2">
<TextBlock TextAlignment="Center"><Run x:Name="FastSelectButton_MP2_Forward_Run" FontSize="24">MP2</Run><LineBreak/><Run x:Name="MP2_Target">Vor</Run></TextBlock>
</Button>
<Button x:Name="FastSelectButton_MP2_Stop" Grid.Column="0" Grid.Row="5"
FontSize="16" FontWeight="Bold"
Click="FastSelectButton_MP2_Stop_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2">
<TextBlock TextAlignment="Center"><Run x:Name="FastSelectButton_MP2_Stop_Run" FontSize="24">MP2</Run><LineBreak/>Stop</TextBlock>
</Button>
<Button x:Name="FastSelectButton_MP1_Forward" Grid.Column="3" Grid.Row="4"
FontSize="16" FontWeight="Bold"
Click="FastSelectButton_MP1_Forward_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2">
<TextBlock TextAlignment="Center"><Run x:Name="FastSelectButton_MP1_Forward_Run" FontSize="24">MP1</Run><LineBreak/><Run x:Name="MP1_Target">Vor</Run></TextBlock>
</Button>
<Button x:Name="FastSelectButton_MP1_Stop" Grid.Column="3" Grid.Row="5"
FontSize="16" FontWeight="Bold"
Click="FastSelectButton_MP1_Stop_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2">
<TextBlock TextAlignment="Center"><Run x:Name="FastSelectButton_MP1_Stop_Run" FontSize="24">MP1</Run><LineBreak/>Stop</TextBlock>
</Button>
</Grid>
</Border>
</Grid> </Grid>
<StatusBar Grid.Row="2" BorderThickness="0,1,0,0" BorderBrush="Gray"> <StatusBar Grid.Row="2" BorderThickness="0,1,0,0" BorderBrush="Gray">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem>
<TextBlock>
SPS-Verbindung: <Run x:Name="Status_Connection" Text="-"/>
</TextBlock>
</StatusBarItem>
<Separator Grid.Column="1"/>
<StatusBarItem Grid.Column="2">
<TextBlock>
SPS-Status: <Run x:Name="Status_Plc" Text="-"/>
</TextBlock>
</StatusBarItem>
<Separator Grid.Column="3"/>
<StatusBarItem Grid.Column="4">
<TextBlock>
Druckluft: <Run x:Name="Status_CompressedAir" Text="-"/>
</TextBlock>
</StatusBarItem>
<Separator Grid.Column="5"/>
<StatusBarItem Grid.Column="6">
<TextBlock>
Ventile offen: <Run x:Name="Status_ValvesOpen" Text="-"/>
</TextBlock>
</StatusBarItem>
</StatusBar> </StatusBar>
</Grid> </Grid>
</Window> </Window>
+415 -67
View File
@@ -1,27 +1,85 @@
using PamhagenSysCtrl.Helpers; using PamhagenSysCtrl.Helpers;
using PamhagenSysCtrl.Helpers.Pipeline; using PamhagenSysCtrl.Helpers.Pipeline;
using PamhagenSysCtrl.Helpers.Plc;
using System.Diagnostics; using System.Diagnostics;
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media;
namespace PamhagenSysCtrl.Windows { namespace PamhagenSysCtrl.Windows {
public partial class PlantSchemeWindow : Window { public partial class PlantSchemeWindow : Window {
private readonly PamhagenGraph Graph; private const double _minMove = 50;
private static PamhagenGraph Graph => App.Plant!.Graph;
private Point? _lastMousePos;
private bool _mouseDragging;
private ISource? _lastSource; private ISource? _lastSource;
private List<INode> _path = []; private List<INode> _path = [];
private ISet<INode>? _lastSelected; private ISet<INode>? _lastSelected;
private ISet<INode>? _hover; private ISet<INode>? _hover;
public PlantSchemeWindow() { public double Scale { get; set; } = 5;
InitializeComponent(); public double RoundedScale => Math.Round(Scale * 2) / 2;
App.Plant?.Update += OnUpdate;
Graph = new(); public Path? HoveringPath {
Graph.Draw(SchemeCanvas); get {
foreach (var b in FastSelectPaths.Where(b => b.Key.IsMouseOver)) {
if (Graph.SelectedPaths.Any(p => p.Matches(b.Value.Source, b.Value.Sink)))
continue;
return Graph.GetFreePath([b.Value.Source, b.Value.Sink], true);
}
if (_lastSource == null || _hover == null || !(_hover.Count > 0 || _path.Count > 0))
return null;
List<INode> points = [_lastSource, .. _path];
if (_hover.Count > 0 && !points.Contains(_hover.First())) {
points.Add(_hover.First());
}
if (points.Count >= 2) {
return Graph.GetFreePath(points);
} else {
return null;
}
}
} }
private void Menu_Settings_ManualControl_Click(object sender, RoutedEventArgs evt) { protected Dictionary<Button, (ISource Source, Pump Pump, ISink Sink)> FastSelectPaths;
public PlantSchemeWindow() {
InitializeComponent();
Menu_Help_CheckForUpdates.IsEnabled = App.Config.UpdateUrl != null;
Graph.Draw(SchemeCanvas, RoundedScale);
FastSelectPaths = new Dictionary<Button, (ISource, Pump, ISink)> {
[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),
};
if (App.Plant != null) {
Status_Connection.Text = $"Nein ({App.Plant.PlcComPort})";
App.Plant.Update += OnUpdate;
}
}
private void OnClosed(object sender, EventArgs evt) {
Application.Current.Shutdown();
}
private void Menu_Settings_ManualControl_Changed(object sender, RoutedEventArgs evt) {
App.Plant?.ManualControlMode = Menu_Settings_ManualControl.IsChecked;
if (Menu_Settings_ManualControl.IsChecked) { if (Menu_Settings_ManualControl.IsChecked) {
foreach (var win in Application.Current.Windows) { foreach (var win in Application.Current.Windows) {
if (win is ManualControlWindow w) { if (win is ManualControlWindow w) {
@@ -37,15 +95,20 @@ namespace PamhagenSysCtrl.Windows {
w.Close(); w.Close();
} }
} }
Menu_Settings_OverridePathClearances.IsChecked = false;
Menu_Settings_OverrideDryRunClearances.IsChecked = false;
App.Plant?.InitializePaths();
} }
OnUpdate(null, null);
} }
private void Menu_Settings_OverridePathClearances_Click(object sender, RoutedEventArgs evt) { private void Menu_Settings_OverridePathClearances_Changed(object sender, RoutedEventArgs evt) {
App.Plant?.OverridePathClearances = Menu_Settings_OverridePathClearances.IsChecked; App.Plant?.OverridePathClearances = Menu_Settings_OverridePathClearances.IsChecked;
OnUpdate(null, null); OnUpdate(null, null);
} }
private void Menu_Settings_OverrideDryRunClearances_Click(object sender, RoutedEventArgs evt) { private void Menu_Settings_OverrideDryRunClearances_Changed(object sender, RoutedEventArgs evt) {
App.Plant?.OverrideDryRunClearances = Menu_Settings_OverrideDryRunClearances.IsChecked; App.Plant?.OverrideDryRunClearances = Menu_Settings_OverrideDryRunClearances.IsChecked;
OnUpdate(null, null); OnUpdate(null, null);
} }
@@ -67,45 +130,249 @@ namespace PamhagenSysCtrl.Windows {
} catch { } } catch { }
} }
private void SchemeCanvas_MouseMove(object sender, MouseEventArgs evt) { private async void Menu_Help_CheckForUpdates_Click(object sender, RoutedEventArgs evt) {
var p = evt.GetPosition(SchemeCanvas); await App.CheckForUpdates(true);
}
private void Menu_Help_About_Click(object sender, RoutedEventArgs evt) {
var window = new AboutWindow {
Owner = this,
};
window.Show();
}
private void FastSelectButton_EntryTroughStart_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.StartTroughAuger();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_EntryTroughStop_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.StopTroughAuger();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_MW1_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.SelectMW1();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_MW2_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.SelectMW2();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_MP1_Forward_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.StartMP1Forward();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_MP1_Stop_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.StopMP1();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_MP2_Forward_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.StartMP2Forward();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_MP2_Stop_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.StopMP2();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_Enter(object sender, MouseEventArgs evt) {
UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed); UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed);
} }
private void FastSelectButton_Leave(object sender, MouseEventArgs evt) {
UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed);
}
private void FastSelectButton_Click(object sender, RoutedEventArgs? evt) {
if (sender is not Button b || !FastSelectPaths.TryGetValue(b, out var val))
return;
App.Plant?.FastSelect(val.Source, null, val.Sink);
OnUpdate(null, null);
_lastSource = null;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs evt) {
var rx = evt.NewSize.Width / 1550.0;
var ry = evt.NewSize.Height / 900.0;
var r = Math.Min(rx, ry);
Scale = r * 5;
SchemeCanvas.Margin = new();
Graph.Scale(RoundedScale);
if (RoundedScale < 4) {
(FastSelectButtonsLeft.Parent as Border)?.Visibility = Visibility.Hidden;
(FastSelectButtonsRight.Parent as Border)?.Visibility = Visibility.Hidden;
} else if (RoundedScale <= 5) {
(FastSelectButtonsLeft.Parent as Border)?.Visibility = Visibility.Visible;
FastSelectButtonsLeft.ColumnDefinitions[0].Width = new(120);
FastSelectButtonsLeft.ColumnDefinitions[3].Width = new(120);
FastSelectButtonsLeft.RowDefinitions[0].Height = new(45);
FastSelectButtonsLeft.RowDefinitions[2].Height = new(40);
FastSelectButtonsLeft.RowDefinitions[4].Height = new(55);
FastSelectButtonsLeft.RowDefinitions[5].Height = new(55);
(FastSelectButtonsRight.Parent as Border)?.Visibility = Visibility.Visible;
foreach (var col in FastSelectButtonsRight.ColumnDefinitions) {
if (col.Width.Value <= 5) continue;
col.Width = new(120);
}
foreach (var row in FastSelectButtonsRight.RowDefinitions) {
if (row.Height.Value <= 5) continue;
row.Height = new(40);
}
foreach (var b in FastSelectPaths.Keys) {
b.FontSize = 14;
}
FastSelectButton_EntryTrough_Start.FontSize = 12;
FastSelectButton_EntryTrough_Start_Run.FontSize = 14;
FastSelectButton_EntryTrough_Stop.FontSize = 12;
FastSelectButton_EntryTrough_Stop_Run.FontSize = 14;
FastSelectButton_MW1.FontSize = 14;
FastSelectButton_MW2.FontSize = 14;
FastSelectButton_MP1_Stop.FontSize = 14;
FastSelectButton_MP1_Stop_Run.FontSize = 18;
FastSelectButton_MP1_Forward.FontSize = 14;
FastSelectButton_MP1_Forward_Run.FontSize = 18;
FastSelectButton_MP2_Stop.FontSize = 14;
FastSelectButton_MP2_Stop_Run.FontSize = 18;
FastSelectButton_MP2_Forward.FontSize = 14;
FastSelectButton_MP2_Forward_Run.FontSize = 18;
} else {
(FastSelectButtonsLeft.Parent as Border)?.Visibility = Visibility.Visible;
FastSelectButtonsLeft.ColumnDefinitions[0].Width = new(160);
FastSelectButtonsLeft.ColumnDefinitions[3].Width = new(160);
FastSelectButtonsLeft.RowDefinitions[0].Height = new(60);
FastSelectButtonsLeft.RowDefinitions[2].Height = new(50);
FastSelectButtonsLeft.RowDefinitions[4].Height = new(80);
FastSelectButtonsLeft.RowDefinitions[5].Height = new(80);
(FastSelectButtonsRight.Parent as Border)?.Visibility = Visibility.Visible;
foreach (var col in FastSelectButtonsRight.ColumnDefinitions) {
if (col.Width.Value <= 5) continue;
col.Width = new(160);
}
foreach (var row in FastSelectButtonsRight.RowDefinitions) {
if (row.Height.Value <= 5) continue;
row.Height = new(50);
}
foreach (var b in FastSelectPaths.Keys) {
b.FontSize = 16;
}
FastSelectButton_EntryTrough_Start.FontSize = 14;
FastSelectButton_EntryTrough_Start_Run.FontSize = 18;
FastSelectButton_EntryTrough_Stop.FontSize = 14;
FastSelectButton_EntryTrough_Stop_Run.FontSize = 18;
FastSelectButton_MW1.FontSize = 16;
FastSelectButton_MW2.FontSize = 16;
FastSelectButton_MP1_Stop.FontSize = 16;
FastSelectButton_MP1_Stop_Run.FontSize = 24;
FastSelectButton_MP1_Forward.FontSize = 16;
FastSelectButton_MP1_Forward_Run.FontSize = 24;
FastSelectButton_MP2_Stop.FontSize = 16;
FastSelectButton_MP2_Stop_Run.FontSize = 24;
FastSelectButton_MP2_Forward.FontSize = 16;
FastSelectButton_MP2_Forward_Run.FontSize = 24;
}
UpdateScheme(Mouse.GetPosition(SchemeCanvas), Mouse.LeftButton == MouseButtonState.Pressed);
}
private void SchemeCanvas_MouseWheel(object sender, MouseWheelEventArgs evt) {
Scale = Math.Max(1, Math.Min(20, Scale + evt.Delta / 500.0));
Graph.Scale(RoundedScale);
UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed);
}
private void SchemeCanvas_MouseMove(object sender, MouseEventArgs evt) {
var pos = evt.GetPosition(SchemeCanvas);
if (_lastMousePos is Point p) {
if (!_mouseDragging) {
var dx = p.X - pos.X;
var dy = p.Y - pos.Y;
_mouseDragging = Math.Sqrt(dx * dx + dy * dy) >= _minMove;
}
if (_mouseDragging) {
var dx = SchemeCanvas.Margin.Right - SchemeCanvas.Margin.Left + p.X - pos.X;
var dy = SchemeCanvas.Margin.Bottom - SchemeCanvas.Margin.Top + p.Y - pos.Y;
SchemeCanvas.Margin = new(dx > 0 ? 0 : -dx, dy > 0 ? 0 : -dy, dx < 0 ? 0 : dx, dy < 0 ? 0 : dy);
}
}
UpdateScheme(pos, evt.LeftButton == MouseButtonState.Pressed);
}
private void SchemeCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs evt) { private void SchemeCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs evt) {
var p = evt.GetPosition(SchemeCanvas); var pos = evt.GetPosition(SchemeCanvas);
_lastSelected = UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed); _lastMousePos = pos;
_mouseDragging = false;
_lastSelected = UpdateScheme(pos, evt.LeftButton == MouseButtonState.Pressed);
} }
private void SchemeCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs evt) { private void SchemeCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs evt) {
var pos = evt.GetPosition(SchemeCanvas); var pos = evt.GetPosition(SchemeCanvas);
_lastMousePos = null;
_mouseDragging = false;
var curSelected = UpdateScheme(pos, evt.LeftButton == MouseButtonState.Pressed); var curSelected = UpdateScheme(pos, evt.LeftButton == MouseButtonState.Pressed);
var clicked = curSelected.Intersect(_lastSelected ?? new HashSet<INode>()).ToList(); var clicked = curSelected.Intersect(_lastSelected ?? new HashSet<INode>()).ToList();
if (clicked.Count == 0) { if (clicked.Count == 0) {
_lastSource = null; _lastSource = null;
} }
foreach (var c in clicked) { foreach (var c in clicked) {
if (_lastSource == null && Graph.SelectedPaths.Any(p => p.Start == c)) { if (App.Plant?.ManualControlMode ?? false) {
var path = Graph.SelectedPaths.First(p => p.Start == c); if (c is ControlledValve v) {
if (!path.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false)) { if (v.WantOpen) {
Graph.RemovePath(path); App.Plant.CloseV(v.Nr);
} else {
App.Plant.OpenV(v.Nr);
}
} }
} else if (_lastSource != null && c is ISink sink) { } else {
var res = Graph.GetPath([_lastSource, .._path, sink], (n) => n is not ISink && (n is not Valve v || !v.IsLocked)); if (_lastSource == null && Graph.SelectedPaths.Any(p => p.Start == c)) {
if (res is Path path) { var path = Graph.SelectedPaths.First(p => p.Start == c);
Graph.AddPath(path); if (!path.HasActivePump) {
Graph.RemovePath(path);
}
} else if (_lastSource != null && c is ISink sink) {
if (Graph.GetFreePath([_lastSource, .. _path, sink]) is Path path) {
Graph.AddPath(path);
_lastSource = null;
}
} else if (_lastSource != null) {
var path = Graph.GetFreePath([_lastSource, .. _path, c]);
if (path != null) {
_path.Add(c);
}
} else if (c is ISource source) {
_lastSource = source;
_path = [];
} else {
_lastSource = null; _lastSource = null;
} }
} else if (_lastSource != null) {
var path = Graph.GetPath([_lastSource, .. _path, c], (n) => n is not ISink && (n is not Valve v || !v.IsLocked));
if (path != null) {
_path.Add(c);
}
} else if (c is ISource source) {
_lastSource = source;
_path = [];
} else {
_lastSource = null;
} }
try { try {
if (c is EntryTrough trough) { if (c is EntryTrough trough) {
@@ -121,7 +388,7 @@ namespace PamhagenSysCtrl.Windows {
App.Plant?.StartRebler(); App.Plant?.StartRebler();
} }
} else if (c is Switcher switcher) { } else if (c is Switcher switcher) {
if (App.Plant?.WantMW1Selected ?? false) { if (App.Plant?.State.MW1_WantSelected ?? false) {
App.Plant?.SelectMW2(); App.Plant?.SelectMW2();
} else { } else {
App.Plant?.SelectMW1(); App.Plant?.SelectMW1();
@@ -161,33 +428,33 @@ namespace PamhagenSysCtrl.Windows {
} }
} else if (pump == Graph.P12) { } else if (pump == Graph.P12) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.StopP12(); App.Plant?.DisableP12();
} else { } else {
App.Plant?.StartP12(); App.Plant?.EnableP12();
} }
} else if (pump == Graph.P3) { } else if (pump == Graph.P3) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.StopP3(); App.Plant?.DisableP3();
} else { } else {
App.Plant?.StartP3(); App.Plant?.EnableP3();
} }
} else if (pump == Graph.P4) { } else if (pump == Graph.P4) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.StopP4(); App.Plant?.DisableP4();
} else { } else {
App.Plant?.StartP4(); App.Plant?.EnableP4();
} }
} else if (pump == Graph.P5) { } else if (pump == Graph.P5) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.StopP5(); App.Plant?.DisableP5();
} else { } else {
App.Plant?.StartP5(); App.Plant?.EnableP5();
} }
} else if (pump == Graph.P6) { } else if (pump == Graph.P6) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.StopP6(); App.Plant?.DisableP6();
} else { } else {
App.Plant?.StartP6(); App.Plant?.EnableP6();
} }
} }
} }
@@ -198,7 +465,32 @@ namespace PamhagenSysCtrl.Windows {
UpdateScheme(pos, evt.LeftButton == MouseButtonState.Pressed); UpdateScheme(pos, evt.LeftButton == MouseButtonState.Pressed);
} }
private ISet<INode> UpdateScheme(Point pos, bool down) { private static void SetFastSelectButton(Button b, int m, bool disabled = false) {
b.IsEnabled = !disabled;
if (m == 1) {
b.Background = Brushes.MintCream;
b.BorderBrush = Brushes.DarkGreen;
b.Foreground = Brushes.DarkGreen;
} else if (m == 2) {
b.Background = Brushes.MistyRose;
b.BorderBrush = Brushes.DarkRed;
b.Foreground = Brushes.DarkRed;
} else {
b.Background = Brushes.LightGray;
b.BorderBrush = Brushes.DimGray;
b.Foreground = Brushes.Black;
}
}
private void UpdateFastSelectPathButton(IEnumerable<Path> paths, Path? hoverPathDeactivate, Button b) {
var e = FastSelectPaths[b];
SetFastSelectButton(b,
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);
}
private ISet<INode> UpdateScheme(Point? pos, bool down) {
if (!SchemeCanvas.IsMouseOver) pos = null;
foreach (var e in Graph.Edges) { foreach (var e in Graph.Edges) {
e.Highlight = null; e.Highlight = null;
e.Flow = null; e.Flow = null;
@@ -208,43 +500,99 @@ namespace PamhagenSysCtrl.Windows {
p.Highlight = null; p.Highlight = null;
} }
} }
foreach (var p in Graph.SelectedPaths) {
Graph.ColorPipesAdjacientToPath(p, PamhagenBrushes.Green, p.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false), PamhagenBrushes.DimOrange); SetFastSelectButton(FastSelectButton_EntryTrough_Start, App.Plant?.State.TroughAuger_IsActive ?? false ? 1 : 0, !(App.Plant?.State.TroughAuger_HasClearance ?? false));
} SetFastSelectButton(FastSelectButton_EntryTrough_Stop, App.Plant?.State.TroughAuger_IsActive ?? false ? 0 : 2);
_hover = Graph.GetHover(pos.X, pos.Y); SetFastSelectButton(FastSelectButton_MW1, App.Plant?.State.MW1_IsSelected?? false ? 1 : 0);
if (_lastSource != null && (_hover.Count > 0 || _path.Count > 0)) { SetFastSelectButton(FastSelectButton_MW2, App.Plant?.State.MW2_IsSelected ?? false ? 1 : 0);
List<INode> points = [_lastSource, .. _path]; SetFastSelectButton(FastSelectButton_MP1_Forward, App.Plant?.State.MP1_State == MotorState.Forward ? 1 : 0, !(App.Plant?.State.MP1_HasClearance ?? false));
if (_hover.Count > 0 && !points.Contains(_hover.First())) { SetFastSelectButton(FastSelectButton_MP1_Stop, App.Plant?.State.MP1_State == MotorState.Halt ? 2 : 0);
points.Add(_hover.First()); SetFastSelectButton(FastSelectButton_MP2_Forward, App.Plant?.State.MP2_State == MotorState.Forward ? 1 : 0, !(App.Plant?.State.MP2_HasClearance ?? false));
SetFastSelectButton(FastSelectButton_MP2_Stop, App.Plant?.State.MP2_State == MotorState.Halt ? 2 : 0);
_hover = Graph.GetHover(pos, RoundedScale);
var hoverButtons = FastSelectPaths.Where(b => b.Key.IsMouseOver).ToDictionary();
Path? hoverPathDeactivate = null;
if (App.Plant?.ManualControlMode ?? false) {
foreach (var src in Graph.Nodes.Where(n => n is Pump)) {
PamhagenGraph.ColorPipesByValves(src, PamhagenBrushes.Green, (src as Pump)?.State ?? default, PamhagenBrushes.DimOrange);
} }
if (points.Count >= 2) { MP1_Target.Text = "Vor";
var res = Graph.GetPath(points, (n) => n is not ISink && (n is not Valve v || !v.IsLocked)); MP2_Target.Text = "Vor";
if (res is Path path) { MP1_Target.FontSize = 16;
Graph.ColorPipesAdjacientToPath(path, PamhagenBrushes.Green, false, PamhagenBrushes.DimOrange); MP2_Target.FontSize = 16;
} else {
foreach (var p in Graph.SelectedPaths) {
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) {
if (path1.HasActivePump) {
foreach (var p in path1.Pumps) p.Highlight = PamhagenBrushes.Red;
} else {
hoverPathDeactivate = path1;
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) {
if (path2.HasActivePump) {
foreach (var p in path2.Pumps) p.Highlight = PamhagenBrushes.Red;
} else {
hoverPathDeactivate = path2;
PamhagenGraph.ColorPipesAdjacientToPath(path2, PamhagenBrushes.Red, default, null);
} }
} }
} else if (_lastSource == null && _hover.Count > 0 && Graph.SelectedPaths.Any(p => p.Start == _hover.First())) { if (_lastSource != null) {
var path = Graph.SelectedPaths.First(p => p.Start == _hover.First()); foreach (var edge in _lastSource.Outputs) {
var pumpActive = path.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false); edge.Highlight = PamhagenBrushes.Green;
if (pumpActive) {
foreach (var pump in path.Hops.Select(h => h.Node as Pump).Where(p => p != null)) {
pump?.Highlight = PamhagenBrushes.Red;
} }
}
if (HoveringPath is Path hoveringPath) {
PamhagenGraph.ColorPipesAdjacientToPath(hoveringPath, PamhagenBrushes.Green, default, PamhagenBrushes.DimOrange);
}
if (Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == Graph.MW1) is Path pmw1) {
MP1_Target.Text = "\u2b9e " + string.Join(" \u2b9e ", pmw1.Hops
.Select(h => h.Node is Valve || h.Node is Pump ? null : h.Node.Label)
.Where(l => !string.IsNullOrWhiteSpace(l)));
} else { } else {
Graph.ColorPipesAdjacientToPath(path, PamhagenBrushes.Red, false, null); MP1_Target.Text = "Vor";
} }
} if (Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == Graph.MW2) is Path pmw2) {
if (_lastSource != null) { MP2_Target.Text = "\u2b9e " + string.Join(" \u2b9e ", pmw2.Hops
foreach (var edge in _lastSource.Outputs) { .Select(h => h.Node is Valve || h.Node is Pump ? null : h.Node.Label)
edge.Highlight = PamhagenBrushes.Green; .Where(l => !string.IsNullOrWhiteSpace(l)));
} else {
MP2_Target.Text = "Vor";
} }
var len1 = MP1_Target.Text.Length;
var len2 = MP2_Target.Text.Length;
MP1_Target.FontSize = len1 > 24 ? 8 : len1 > 20 ? 10 : len1 > 16 ? 12 : len1 > 12 ? 14 : 16;
MP2_Target.FontSize = len2 > 24 ? 8 : len2 > 20 ? 10 : len2 > 16 ? 12 : len2 > 12 ? 14 : 16;
} }
var paths = Graph.SelectedPaths;
if (HoveringPath is Path hp) paths = paths.Append(hp);
foreach (var b in FastSelectPaths) {
UpdateFastSelectPathButton(paths, hoverPathDeactivate, b.Key);
}
Graph.Update(_hover, down); Graph.Update(_hover, down);
return _hover; return _hover;
} }
public void OnUpdate(object? sender, PlantEventArgs? evt) { public void OnUpdate(object? sender, PlantEventArgs? evt) {
if (sender is not PamhagenPlant plant) return; if (App.Plant is PamhagenPlant p) {
if (p.IsPlcConnected) {
Status_Connection.Text = $"Ja ({p.PlcComPort}" + (p.LastRttNs != null ? $", {p.LastRttNs / 1000000.0:N0} ms" : "") + ")";
Status_Plc.Text = $"{p.LastPlcStatus?.Mode} ({p.LastPlcStatus?.Flags:X3}) / {p.LastPlcError?.GetName()} ({(int?)p.LastPlcError:0000})";
Status_CompressedAir.Text = Enumerable.Range(1, 57).Any(p.State.Valve_IsClosed) ? "Ein" : Enumerable.Range(1, 57).Any(p.State.Valve_WantClosed) ? "Aus" : "?";
Status_ValvesOpen.Text = Enumerable.Range(1, 57).Count(p.State.Valve_IsOpen) + " / " + Enumerable.Range(1, 57).Count(p.State.Valve_WantOpen) + " / 57";
} else {
Status_Connection.Text = p.PlcConnectionError is string err ? $"Nein ({p.PlcComPort}, {err})" : $"Nein ({p.PlcComPort})";
Status_Plc.Text = "-";
Status_CompressedAir.Text = "-";
Status_ValvesOpen.Text = "-";
}
}
UpdateScheme(Mouse.GetPosition(SchemeCanvas), Mouse.LeftButton == MouseButtonState.Pressed); UpdateScheme(Mouse.GetPosition(SchemeCanvas), Mouse.LeftButton == MouseButtonState.Pressed);
} }
} }
+41 -1
View File
@@ -1,2 +1,42 @@
# Anlagensteuerung Pamhangen Anlagensteuerung Pamhagen
=========================
Schnittstelle zur SPS und grafische Benutzeroberfläche.
Winzerkeller Seewinkel, Pamhagen.
About
=====
**Product:** Anlagensteuerung Pamhagen
**Description:** Interface to a LPC and graphical user interface for controlling the plant/system
**Type:** HMI and PLC Control Software
**Version:** 0.0.4 ([Changelog](./CHANGELOG.md))
**License:** [GNU General Public License 3.0 (GPLv3)](./LICENSE)
**Source code:** https://git.necronda.net/winzer/pamhagen-sysctrl
**Developement period:** 2026
**Technology Stack:**
Language: C#
Framework: Windows Presentation Framework (WPF)
LPC-Interface: Serial connection
Packaging: [WiX Toolset](https://www.firegiant.com/wixtoolset/)
Über
====
**Produkt:** Anlagensteuerung Pamhagen
**Beschreibung:** Schnittstelle zu einer SPS und grafische Benutzeroberfläche für die Steuerung der Anlage
**Typ:** HMI- und SPS-Steuerungssoftware
**Version:** 0.0.4 ([Änderungsprotokoll](./CHANGELOG.md))
**Lizenz:** [GNU General Public License 3.0 (GPLv3)](./LICENSE)
**Quellcode:** https://git.necronda.net/winzer/pamhagen-sysctrl
**Entwicklungszeitraum:** 2026
**Verwendete Technologien:**
Programmiersprache: C#
Framework: Windows Presentation Framework (WPF)
SPS-Schnittstelle: Serielle Verbindung
Paketierung: [WiX Toolset](https://www.firegiant.com/wixtoolset/)
+233
View File
@@ -0,0 +1,233 @@
#!/bin/env python3
import socket
import time
import random
import argparse
mw1 = 0
mw2 = 0
t1 = 0
t2 = 0
t3 = 0
t4 = 0
t5 = 0
t6 = 0
t7 = 0
t8 = 0
t9 = 0
pr1 = 0
pr2 = 0
mw1_max = 5000
mw2_max = 5000
t_max = 35000
pr1_max = 20000
pr2_max = 15000
ENTRY_RATE = 20 / 20
PUMP_RATE = 5 / 20
rw = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
def are_valves_open(valves, *ns):
mask = 0
for n in ns:
mask |= 1 << (n - 1)
return (~valves & mask) == mask
def send_cmd(cmd: str, data: str) -> None:
msg = f'(A01{cmd}{data}&'
chk = sum(c for c in msg.encode('ascii')) & 0xFF
msg += f'{chk:02X})\r'
time.sleep(len(msg) * 8 / 9600 * 2)
print(f'-> {msg}')
client.send(msg.encode('ascii'))
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('socket')
args = parser.parse_args()
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client.connect(args.socket)
while True:
line = b''
while b'\r' not in line:
line += client.recv(256)
cmds = line.split(b'\r')
line = cmds[-1]
cmds = [c.decode('ascii') for c in cmds[:-1]]
for cmd in cmds:
print(f'<- {cmd}')
time.sleep(len(cmd) * 8 / 9600 * 2)
if not cmd.startswith('(A01') or not cmd.endswith(')') or len(cmd) < 7:
send_cmd('CE', '02')
continue
c, d = cmd[4:6], cmd[6:-1]
if cmd[-4] == '&':
chk1 = int(cmd[-3:-1], 16)
chk2 = sum(c for c in cmd[:-3].encode('ascii')) & 0xFF
if chk1 != chk2:
send_cmd('CE', '03')
continue
d = d[:-3]
if c == 'TS':
send_cmd('TS', d)
elif c == 'ST':
send_cmd('ST', '0002')
elif c == 'DR':
# FIXME
d = d.split(',')
s = int(d[0][2:])
l = int(d[1])
send_cmd('DR', ''.join(f'{r:04X}' for r in rw[s:s+l]))
elif c == 'DW':
# FIXME
d = d.split(',')
s = int(d[0][2:])
l = int(d[1])
for i in range(l):
rw[s + i] = int(d[2 + i], 16)
send_cmd('ST', '0002')
else:
send_cmd('CE', '01')
# tick - valves
for i in range(0, 60):
want_open = rw[i // 16] & (1 << (i % 16)) == 0
is_open = rw[10 + i // 16] & (1 << (i % 16)) != 0
if want_open and not is_open:
if random.random() > 0.875:
rw[10 + i // 16] |= 1 << (i % 16)
elif not want_open and is_open:
if random.random() > 0.875:
rw[10 + i // 16] &= ~(1 << (i % 16))
# tick - wannen
want_a1_selected = (rw[5] & 0x0004) != 0
want_a2_selected = not want_a1_selected
is_a1_selected = (rw[14] & 0x0200) != 0
is_a2_selected = (rw[14] & 0x0100) != 0
if is_a1_selected and not want_a1_selected:
rw[14] &= 0xFDFF
elif not is_a1_selected and want_a1_selected:
if random.random() > 0.875:
rw[14] |= 0x0200
if is_a2_selected and not want_a2_selected:
rw[14] &= 0xFEFF
elif not is_a2_selected and want_a2_selected:
if random.random() > 0.875:
rw[14] |= 0x0100
# tick - pipeline
valves = (rw[13] << 48) | (rw[12] << 32) | (rw[11] << 16) | rw[10]
wannen_schnecke = (rw[5] & 0x1) != 0
mp1 = (rw[4] & 0x002) != 0
mp2 = (rw[4] & 0x004) != 0
p12 = (rw[4] & 0x010) != 0
p3 = (rw[4] & 0x020) != 0
p4 = (rw[4] & 0x040) != 0
p5 = (rw[4] & 0x080) != 0
p6 = (rw[4] & 0x100) != 0
if wannen_schnecke and is_a1_selected:
mw1 += ENTRY_RATE
elif wannen_schnecke and is_a2_selected:
mw2 += ENTRY_RATE
if mp1 and mw1 > 0:
mw1 -= PUMP_RATE
if are_valves_open(valves, 1, 6, 51) or are_valves_open(valves, 4, 8, 52):
pr1 += PUMP_RATE
elif are_valves_open(valves, 1, 6, 55) or are_valves_open(valves, 4, 8, 56):
pr2 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 22) or are_valves_open(valves, 4, 7, 9, 13, 23):
t1 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 24) or are_valves_open(valves, 4, 7, 9, 13, 25):
t2 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 26) or are_valves_open(valves, 4, 7, 9, 13, 27):
t3 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 28, 30) or are_valves_open(valves, 4, 7, 9, 13, 29, 31):
t4 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 28, 32) or are_valves_open(valves, 4, 7, 9, 13, 29, 33):
t5 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 28, 34, 37) or are_valves_open(valves, 4, 7, 9, 13, 29, 35, 36):
t6 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 28, 34, 39) or are_valves_open(valves, 4, 7, 9, 13, 29, 35, 38):
t7 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 28, 34, 41) or are_valves_open(valves, 4, 7, 9, 13, 29, 35, 40):
t8 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 28, 34, 43) or are_valves_open(valves, 4, 7, 9, 13, 29, 35, 42):
t9 += PUMP_RATE
if mp2 and mw2 > 0:
mw2 -= PUMP_RATE
if are_valves_open(valves, 2, 8, 52) or are_valves_open(valves, 3, 6, 51):
pr1 += PUMP_RATE
elif are_valves_open(valves, 2, 8, 56) or are_valves_open(valves, 3, 6, 55):
pr2 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 22) or are_valves_open(valves, 2, 7, 9, 13, 23):
t1 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 24) or are_valves_open(valves, 2, 7, 9, 13, 25):
t2 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 26) or are_valves_open(valves, 2, 7, 9, 13, 27):
t3 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 28, 30) or are_valves_open(valves, 2, 7, 9, 13, 29, 31):
t4 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 28, 32) or are_valves_open(valves, 2, 7, 9, 13, 29, 33):
t5 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 28, 34, 37) or are_valves_open(valves, 2, 7, 9, 13, 29, 35, 36):
t6 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 28, 34, 39) or are_valves_open(valves, 2, 7, 9, 13, 29, 35, 38):
t7 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 28, 34, 41) or are_valves_open(valves, 2, 7, 9, 13, 29, 35, 40):
t8 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 28, 34, 43) or are_valves_open(valves, 2, 7, 9, 13, 29, 35, 42):
t9 += PUMP_RATE
if p6 and (t6 > 0 or t7 > 0 or t8 > 0 or t9 > 0):
if t6 > 0:
t6 -= PUMP_RATE
if t7 > 0:
t7 -= PUMP_RATE
if t8 > 0:
t8 -= PUMP_RATE
if t9 > 0:
t9 -= PUMP_RATE
if are_valves_open(valves, 53):
pr1 += PUMP_RATE
elif are_valves_open(valves, 57):
pr2 += PUMP_RATE
# tick - fill levels
rw[14] &= 0xFF00
rw[14] |= 0x0004 if mw1 > 50 else 0
rw[14] |= 0x0002 if mw1 > mw1_max / 2 else 0
rw[14] |= 0x0001 if mw1 > mw1_max * 0.875 else 0
rw[14] |= 0x0020 if mw2 > 50 else 0
rw[14] |= 0x0010 if mw2 > mw2_max / 2 else 0
rw[14] |= 0x0008 if mw2 > mw2_max * 0.875 else 0
rw[14] |= 0x0040 if pr1 > pr1_max * 0.875 else 0
rw[14] |= 0x0080 if pr2 > pr2_max * 0.875 else 0
rw[16] &= 0xC000
rw[16] |= 0x0001 if t1 > 50 else 0
rw[16] |= 0x0002 if t1 > t_max * 0.875 else 0
rw[16] |= 0x0004 if t2 > 50 else 0
rw[16] |= 0x0008 if t2 > t_max * 0.875 else 0
rw[16] |= 0x0010 if t3 > 50 else 0
rw[16] |= 0x0020 if t3 > t_max * 0.875 else 0
rw[16] |= 0x0040 if t4 > 50 else 0
rw[16] |= 0x0080 if t4 > t_max * 0.875 else 0
rw[16] |= 0x0100 if t5 > 50 else 0
rw[16] |= 0x0200 if t5 > t_max * 0.875 else 0
rw[16] |= 0x0400 if t6 > t_max * 0.875 else 0
rw[16] |= 0x0800 if t7 > t_max * 0.875 else 0
rw[16] |= 0x1000 if t8 > t_max * 0.875 else 0
rw[16] |= 0x2000 if t9 > t_max * 0.875 else 0
print(mw1, mw2, t1, t2, t3, t4, t5, t6, t7, t8, t9, pr1, pr2)
BIN
View File
Binary file not shown.
+216
View File
@@ -0,0 +1,216 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="78mm" height="117mm" viewBox="0 0 780 1170">
<title>XBE-24 Key Legend Pamhagen</title>
<defs>
<path id="key" d="M 5,5 l 150,0 l 0,130 q -75,35 -150,0 Z"
stroke-width="10" stroke="#808080" paint-order="stroke"/>
<path id="start" d="M 0,-40 l 40,40 l -40,40 l -40,-40 Z M 0,-20 l 0,40 Z"
stroke-width="7" stroke="#004000" fill-opacity="0"
stroke-linejoin="round"/>
<path id="stop" d="M 0,40 a 40,40 180 0,0 0,-80 a 40,40 180 1,0 0,80 m 0,-5 l -30.310889,-52.5 l 60.621778,0 Z"
stroke-width="7" stroke="#400000" fill-opacity="0"/>
<path id="on-off" d="M 0,40 a 40,40 180 0,0 0,-80 a 40,40 180 1,0 0,80 Z M 0,-20 l 0,40 Z"
stroke-width="7" stroke="#000000" fill-opacity="0"
stroke-linejoin="round"/>
<path id="arrow-down" d="M 0,-35 l 15,0 l 0,50 l 10,0 l -25,25 l -25,-25 l 10,0 l 0,-50 Z"
fill="#000040"/>
<use id="arrow-left-down" href="#arrow-down" transform="rotate(30)"/>
<path id="arrow-from-mw1" d="M -70,-20 l 80,0 l 0,30 l 10,0 l -20,20 l -20,-20 l 10,0 l 0,-10 l -60,0 Z"
fill="#000040"/>
<use id="arrow-from-mw2" href="#arrow-from-mw1" transform="scale(-1,1)"/>
<path id="press" d="M 35,0 l 10,0 l 0,55 l -20,0 l 0,-10 l -50,0 l 0,10 l -20,0 l 0,-55 l 10,0 a 35,35 180 0,1 70,0 a 35,35 180 0,1 -70,0"
stroke-width="5" stroke="#000000" fill-opacity="0"/>
<!--path id="tank" d="M 35,-32 l 0,90 l -10,0 l 0,-23 l 10,-9 l -30,25 l -10,0 l -30,-25 l 10,9 l 0,23 l -10,0 l 0,-90 Z"
stroke-width="5" stroke="#000000" fill-opacity="0"/-->
<path id="tank" d="M 35,-32 l 0,90 l -10,0 l 0,-23 l -20,16 l -10,0 l -20,-16 l 0,23 l -10,0 l 0,-90 Z"
stroke-width="5" stroke="#000000" fill-opacity="0"/>
</defs>
<rect x="0" y="0" width="780" height="1170" stroke="#000000" fill="#FFFFFF" stroke-width="5"/>
<g id="k0">
<use href="#key" x="25" y="30" fill="#E8E8FF"/>
<use href="#press" x="105" y="110"/>
<text x="105" y="110" font-size="35" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">P1</text>
<use href="#arrow-from-mw2" x="105" y="60"/>
<text x="148" y="50" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k1">
<use href="#key" x="25" y="220" fill="#E8E8FF"/>
<use href="#press" x="105" y="300"/>
<text x="105" y="300" font-size="35" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">P2</text>
<use href="#arrow-from-mw2" x="105" y="250"/>
<text x="148" y="240" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k2">
<use href="#key" x="25" y="410" fill="#E8E8FF"/>
<use href="#tank" x="105" y="490"/>
<text x="105" y="490" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT1</text>
<use href="#arrow-from-mw2" x="105" y="440"/>
<text x="148" y="430" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k3">
<use href="#key" x="25" y="600" fill="#E8E8FF"/>
<use href="#tank" x="105" y="680"/>
<text x="105" y="680" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT2</text>
<use href="#arrow-from-mw2" x="105" y="630"/>
<text x="148" y="620" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k4">
<use href="#key" x="25" y="790" fill="#E8E8FF"/>
<use href="#tank" x="105" y="870"/>
<text x="105" y="870" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT3</text>
<use href="#arrow-from-mw2" x="105" y="820"/>
<text x="148" y="810" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k5">
<use href="#key" x="25" y="980" fill="#E8E8FF"/>
<use href="#tank" x="105" y="1060"/>
<text x="105" y="1060" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT4</text>
<use href="#arrow-from-mw2" x="105" y="1010"/>
<text x="148" y="1000" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k6">
<use href="#key" x="215" y="30" fill="#E8FFE8"/>
<use href="#start" x="295" y="85"/>
<text x="295" y="150" font-size="40" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">Rebler</text>
</g>
<g id="k7">
<use href="#key" x="215" y="220" fill="#FFE8E8"/>
<use href="#stop" x="295" y="275"/>
<text x="295" y="340" font-size="40" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">Rebler</text>
</g>
<g id="k8">
<use href="#key" x="215" y="410" fill="#E8E8FF"/>
<use href="#arrow-left-down" x="300" y="465"/>
<text x="295" y="530" font-size="45" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k9">
<use href="#key" x="215" y="600" fill="#FFFFFF"/>
<use href="#on-off" x="295" y="655"/>
<text x="295" y="710" font-size="25" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;"><tspan>Rührer</tspan><tspan font-size="30" dx="-2.5em" dy="0.875em">MW2</tspan></text>
</g>
<g id="k10">
<use href="#key" x="215" y="790" fill="#E8FFE8"/>
<use href="#start" x="295" y="845"/>
<text x="295" y="913" font-size="45" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">MP2</text>
</g>
<g id="k11">
<use href="#key" x="215" y="980" fill="#FFE8E8"/>
<use href="#stop" x="295" y="1035"/>
<text x="295" y="1103" font-size="45" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">MP2</text>
</g>
<g id="k12">
<use href="#key" x="405" y="30" fill="#E8FFE8"/>
<use href="#start" x="485" y="85"/>
<text x="485" y="140" font-size="25" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;"><tspan>Trog-</tspan><tspan dx="-3.5em" dy="0.875em">schnecke</tspan></text>
</g>
<g id="k13">
<use href="#key" x="405" y="220" fill="#FFE8E8"/>
<use href="#stop" x="485" y="275"/>
<text x="485" y="330" font-size="25" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;"><tspan>Trog-</tspan><tspan dx="-3.5em" dy="0.875em">schnecke</tspan></text>
</g>
<g id="k14">
<use href="#key" x="405" y="410" fill="#E8E8FF"/>
<use href="#arrow-down" x="485" y="465"/>
<text x="485" y="530" font-size="45" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
<g id="k15">
<use href="#key" x="405" y="600" fill="#FFFFFF"/>
<use href="#on-off" x="485" y="655"/>
<text x="485" y="710" font-size="25" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;"><tspan>Rührer</tspan><tspan font-size="30" dx="-2.5em" dy="0.875em">MW1</tspan></text>
</g>
<g id="k16">
<use href="#key" x="405" y="790" fill="#E8FFE8"/>
<use href="#start" x="485" y="845"/>
<text x="485" y="913" font-size="45" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">MP1</text>
</g>
<g id="k17">
<use href="#key" x="405" y="980" fill="#FFE8E8"/>
<use href="#stop" x="485" y="1035"/>
<text x="485" y="1103" font-size="45" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">MP1</text>
</g>
<g id="k18">
<use href="#key" x="595" y="30" fill="#E8E8FF"/>
<use href="#press" x="675" y="110"/>
<text x="675" y="110" font-size="35" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">P1</text>
<use href="#arrow-from-mw1" x="675" y="60"/>
<text x="632" y="50" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
<g id="k19">
<use href="#key" x="595" y="220" fill="#E8E8FF"/>
<use href="#press" x="675" y="300"/>
<text x="675" y="300" font-size="35" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">P2</text>
<use href="#arrow-from-mw1" x="675" y="250"/>
<text x="632" y="240" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
<g id="k20">
<use href="#key" x="595" y="410" fill="#E8E8FF"/>
<use href="#tank" x="675" y="490"/>
<text x="675" y="490" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT1</text>
<use href="#arrow-from-mw1" x="675" y="440"/>
<text x="632" y="430" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
<g id="k21">
<use href="#key" x="595" y="600" fill="#E8E8FF"/>
<use href="#tank" x="675" y="680"/>
<text x="675" y="680" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT2</text>
<use href="#arrow-from-mw1" x="675" y="630"/>
<text x="632" y="620" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
<g id="k22">
<use href="#key" x="595" y="790" fill="#E8E8FF"/>
<use href="#tank" x="675" y="870"/>
<text x="675" y="870" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT3</text>
<use href="#arrow-from-mw1" x="675" y="820"/>
<text x="632" y="810" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
<g id="k23">
<use href="#key" x="595" y="980" fill="#E8E8FF"/>
<use href="#tank" x="675" y="1060"/>
<text x="675" y="1060" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT4</text>
<use href="#arrow-from-mw1" x="675" y="1010"/>
<text x="632" y="1000" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB