2 Commits
Author SHA1 Message Date
thomas.hilscher 2d43509afc Update Über page
Test / Run tests (push) Successful in 37s
2026-08-08 00:19:49 +02:00
thomas.hilscher 1da9354c67 Add auto updater 2026-08-08 00:09:06 +02:00
51 changed files with 1720 additions and 3708 deletions
+1 -29
View File
@@ -1,35 +1,7 @@
[plc] [plc]
port = COM1 port = COM1
sensor_delay_open = 0 ; seconds
sensor_delay_close = 2 ; seconds
[update] [update]
url = https://elwig.at/files/pamhagen-sysctrl/latest url = https://elwig.at/files/pamhagen-sysctrl/?format=json
auto = true 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
+55 -30
View File
@@ -12,6 +12,7 @@ namespace PamhagenSysCtrl {
public static Dispatcher? MainDispatcher; public static Dispatcher? MainDispatcher;
private readonly DispatcherTimer AutoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) }; private readonly DispatcherTimer AutoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) };
private bool IsCheckingForUpdates;
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");
@@ -34,60 +35,84 @@ 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");
Plant = new(Config.PlcPort); try {
Plant = new(Config.PlcPort);
await Plant.StartThread();
} catch (Exception exc) {
Plant?.Dispose();
Plant = null;
var str = $"Beim Aufbauen einer Verbindung zur SPS ist ein Fehler aufgetreten:\n\n{exc.GetType().Name}: {exc.Message}";
if (exc.InnerException != null) str += $"\n\n{exc.InnerException.GetType().Name}: {exc.InnerException.Message}";
MessageBox.Show(str, "SPS-Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
base.OnStartup(evt); base.OnStartup(evt);
if (Config.UpdateUrl != null && Config.UpdateAuto) { if (Config.UpdateAuto && Config.UpdateUrl != null) {
AutoUpdateTimer.Tick += async (_, _) => { AutoUpdateTimer.Tick += async (_, _) => await CheckForUpdates();
try {
await CheckForUpdates();
} catch { }
};
AutoUpdateTimer.Start(); AutoUpdateTimer.Start();
await Dispatcher.BeginInvoke(async () => { _ = Dispatcher.BeginInvoke(async () => {
await Task.Delay(1500); await Task.Delay(1500);
try { await CheckForUpdates();
await CheckForUpdates();
} catch { }
}); });
} }
} }
public static async Task CheckForUpdates(bool showResult = false) { public async Task CheckForUpdates(bool showResult = false) {
if (Config.UpdateUrl == null) return; if (IsCheckingForUpdates) {
foreach (Window w in Current.Windows) { if (showResult) {
if (w is UpdateDialog) return; MessageBox.Show("Es wird bereits nach Updates gesucht.", "Nach Updates suchen", MessageBoxButton.OK, MessageBoxImage.Information);
}
return;
}
if (Config.UpdateUrl == null) {
if (showResult) {
MessageBox.Show("Die automatische Update-Suche ist deaktiviert.", "Nach Updates suchen", MessageBoxButton.OK, MessageBoxImage.Information);
}
return;
} }
IsCheckingForUpdates = true;
try { try {
var latest = await UpdateService.GetLatestInstallerUrl(Config.UpdateUrl); using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(15));
if (latest.HasValue && new Version(latest.Value.Version) > Version) { var latest = await UpdateService.GetLatestInstallerAsync(Config.UpdateUrl, timeout.Token);
var dialog = new UpdateDialog(latest.Value.Version, latest.Value.Url, latest.Value.Size) { if (latest.Version > Version) {
Owner = Current.MainWindow, var dialog = new UpdateDialog(latest) {
Owner = MainWindow,
}; };
if (dialog.ShowDialog() == true) { if (dialog.ShowDialog() == true) {
Current.Shutdown(); Shutdown();
} }
} else if (showResult) { } else if (showResult) {
MessageBox.Show($"Die Anlagensteuerung ist auf dem aktuellen Stand.\n(Version {Version})", "Nach Updates suchen", MessageBoxButton.OK, MessageBoxImage.Information); MessageBox.Show(
$"Die Anlagensteuerung ist auf dem aktuellen Stand. (Version {Version})",
"Nach Updates suchen",
MessageBoxButton.OK,
MessageBoxImage.Information
);
} }
} catch (OperationCanceledException) { } catch (OperationCanceledException) {
if (showResult) { if (showResult) {
MessageBox.Show("Zeitüberschreitung beim Abrufen der Update-Informationen.", "Nach Updates suchen", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(
} else { "Zeitüberschreitung beim Abrufen der Update-Informationen.",
throw; "Nach Updates suchen",
MessageBoxButton.OK,
MessageBoxImage.Error
);
} }
} catch (Exception exc) { } catch (Exception exc) {
if (showResult) { if (showResult) {
MessageBox.Show($"Die Update-Informationen konnten nicht abgerufen werden:\n\n{exc.Message}", "Nach Updates suchen", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(
} else { $"Die Update-Informationen konnten nicht abgerufen werden:\n\n{exc.Message}",
throw; "Nach Updates suchen",
MessageBoxButton.OK,
MessageBoxImage.Error
);
} }
} finally {
IsCheckingForUpdates = false;
} }
} }
+2 -2
View File
@@ -4,7 +4,7 @@
ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True" ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True"
WindowStartupLocation="CenterOwner" WindowStartupLocation="CenterOwner"
Title="Update verfügbar - Anlagensteuerung Pamhagen" Title="Update verfügbar - Anlagensteuerung Pamhagen"
Height="220" Width="480" Height="220" Width="460"
Closed="OnClosed"> Closed="OnClosed">
<Grid Margin="20"> <Grid Margin="20">
<Grid.RowDefinitions> <Grid.RowDefinitions>
@@ -18,7 +18,7 @@
Version <Run x:Name="VersionText" FontWeight="Bold">0.0.0</Run> ist verfügbar.<LineBreak/> Version <Run x:Name="VersionText" FontWeight="Bold">0.0.0</Run> ist verfügbar.<LineBreak/>
Soll das Update heruntergeladen und installiert werden?<LineBreak/> Soll das Update heruntergeladen und installiert werden?<LineBreak/>
(ca. <Run x:Name="SizeText">0</Run> MB)<LineBreak/><LineBreak/> (ca. <Run x:Name="SizeText">0</Run> MB)<LineBreak/><LineBreak/>
<Run FontWeight="Bold">Hinweis:</Run> Die Anlagensteuerung wird für die Installation geschlossen. <Run FontWeight="Bold">Hinweis:</Run> Die Anlagensteuerung wird zur Installation geschlossen.
</TextBlock> </TextBlock>
<StackPanel x:Name="DownloadPanel" Grid.Row="0" VerticalAlignment="Center" Visibility="Collapsed"> <StackPanel x:Name="DownloadPanel" Grid.Row="0" VerticalAlignment="Center" Visibility="Collapsed">
+14 -10
View File
@@ -1,18 +1,17 @@
using PamhagenSysCtrl.Helpers; using PamhagenSysCtrl.Helpers;
using System.IO;
using System.Windows; using System.Windows;
namespace PamhagenSysCtrl.Dialogs { namespace PamhagenSysCtrl.Dialogs {
public partial class UpdateDialog : Window { public partial class UpdateDialog : Window {
private readonly string Url; private readonly UpdateInstaller Installer;
private readonly CancellationTokenSource Cancellation = new(); private readonly CancellationTokenSource Cancellation = new();
public UpdateDialog(string version, string url, long size) { public UpdateDialog(UpdateInstaller installer) {
Url = url; Installer = installer;
InitializeComponent(); InitializeComponent();
VersionText.Text = version; VersionText.Text = installer.Version.ToString();
SizeText.Text = Math.Ceiling(size / 1024d / 1024d).ToString("N0"); SizeText.Text = Math.Ceiling(installer.Size / 1024d / 1024d).ToString("N0");
} }
private void OnClosed(object? sender, EventArgs evt) { private void OnClosed(object? sender, EventArgs evt) {
@@ -27,15 +26,20 @@ namespace PamhagenSysCtrl.Dialogs {
try { try {
var progress = new Progress<double>(value => DownloadProgress.Value = value * 100); var progress = new Progress<double>(value => DownloadProgress.Value = value * 100);
var filename = Path.Combine(App.TempPath, $"PamhagenSysCtrl-{VersionText.Text}.msi"); var fileName = await UpdateService.DownloadInstallerAsync(Installer, App.TempPath, progress, Cancellation.Token);
await UpdateService.DownloadInstaller(Url, filename, progress, Cancellation.Token);
StatusText.Text = "Installer wird gestartet..."; StatusText.Text = "Installer wird gestartet...";
UpdateService.StartInstaller(filename); UpdateService.StartInstaller(fileName);
DialogResult = true; DialogResult = true;
} catch (OperationCanceledException) { } catch (OperationCanceledException) {
// Closing the dialog cancels an active download. // Closing the dialog cancels an active download.
} catch (Exception exc) { } catch (Exception exc) {
MessageBox.Show($"Das Update konnte nicht installiert werden:\n\n{exc.Message}", "Update installieren", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(
this,
$"Das Update konnte nicht installiert werden:\n\n{exc.Message}",
"Update installieren",
MessageBoxButton.OK,
MessageBoxImage.Error
);
Description.Visibility = Visibility.Visible; Description.Visibility = Visibility.Visible;
DownloadPanel.Visibility = Visibility.Collapsed; DownloadPanel.Visibility = Visibility.Collapsed;
InstallButton.IsEnabled = true; InstallButton.IsEnabled = true;
+5 -27
View File
@@ -9,16 +9,11 @@ namespace PamhagenSysCtrl.Helpers {
private readonly string FileName; private readonly string FileName;
public string PlcPort = "COM1"; public string PlcPort = "COM1";
public int PlcSensorDelayOpen = 0; public string? UpdateUrl = "https://elwig.at/files/pamhagen-sysctrl/?format=json";
public int PlcSensorDelayClose = 0; public bool UpdateAuto = true;
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();
} }
@@ -26,30 +21,13 @@ 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"); UpdateUrl = config["update:url"] ?? UpdateUrl;
PlcSensorDelayClose = int.Parse(config["plc:sensor_delay_close"] ?? "0"); var updateAuto = config["update:auto"];
UpdateUrl = config["update:url"]; UpdateAuto = updateAuto == null || TrueValues.Contains(updateAuto.ToLower());
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;
}
} }
} }
-15
View File
@@ -1,15 +0,0 @@
using System.Drawing;
namespace PamhagenSysCtrl.Helpers {
public static class Extensions {
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);
}
}
}
+265 -351
View File
@@ -18,102 +18,78 @@ 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 = -118; double mw2X = 180;
double mw1X = -94; double mw1X = 300;
double rebX = (mw1X + mw2X) / 2; double mpY = 380;
double rebY = -46; double ltg1Y = 510;
double mpY = -10; double ltg2Y = ltg1Y + 30;
double tanksY = -30; double ltg1X = 600;
double ltg1Y = 16; double ltg2X = ltg1X + 30;
double ltg2Y = ltg1Y + 6; double ltg6Y = 160;
double ltg1X = -34; double ltg7Y = ltg6Y - 30;
double ltg2X = ltg1X + 6; double tanksY = 280;
double ltg3Y = 30; double tValveY = ((tanksY - 50) + ltg6Y) / 2 + 5;
double ltg4Y = ltg3Y + 6; double wtX = ltg1X - 180;
double ltg5Y = tanksY + 36; double wt2Y = 225;
double ltg6Y = -54; double wt1Y = wt2Y + 135;
double ltg7Y = ltg6Y - 6; double x1X = wtX + 40;
double ltg8Y = ltg3Y - 6; double t1X = 735;
double ltg9Y = ltg4Y + 6; double t2X = t1X + 80;
double tValveY = ((tanksY - 10) + ltg6Y) / 2 + 1; double t3X = t2X + 80;
double wtX = ltg1X - 36; double t4X = t3X + 80 + 30;
double wt2Y = -41; double t5X = t4X + 80;
double wt1Y = wt2Y + 27; double t6X = t5X + 80 + 30;
double x1X = wtX + 8; double t7X = t6X + 80;
double t1X = -7; double t8X = t7X + 80;
double t2X = t1X + 16; double t9X = t8X + 80;
double t3X = t2X + 16; var prY = 720;
double t4X = t3X + 16 + 6; var pr1X = 730;
double t5X = t4X + 16; var pr2X = 920;
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 + 14, rebY - 20, () => App.Plant?.State.TroughAuger_IsActive ?? false); var trough = new EntryTrough("Auffangwanne", mw1X + 70, 100, () => App.Plant?.IsTroughAugerActive ?? false);
Nodes.Add(trough); Nodes.Add(trough);
Auger1 = new EncasedAuger("", rebX - 19, rebY, () => App.Plant?.State.Auger1_IsActive ?? false); Auger1 = new EncasedAuger("", (mw1X + mw2X) / 2 - 100, 200, () => App.Plant?.IsAuger1Active ?? false);
var auger0 = new EncasedAuger("Pressen-\nQuerschnecke", rebX - 39, rebY, () => App.Plant?.State.PressCrossAuger_IsActive ?? false, 180, clickable: false); var auger0 = new EncasedAuger("Pressen-\nQuerschnecke", 40, 200, () => App.Plant?.IsPresseQuerSchneckeActive ?? false, 180, clickable: false);
Auger3 = new EncasedAuger("", rebX - 23, rebY - 24, () => App.Plant?.State.Auger3_IsActive ?? false, 135); Auger3 = new EncasedAuger("", 120, 80, () => App.Plant?.IsAuger3Active ?? false, 135);
var belt = new ConveyorBelt("", rebX - 29, rebY - 8, () => App.Plant?.State.Auger2_IsActive ?? false); var belt = new ConveyorBelt("", 90, 160, () => App.Plant?.IsAuger2Active ?? 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", rebX, rebY, () => App.Plant?.State.Rebler_IsActive ?? false); var rebler = new Rebler("Rebler", (mw1X + mw2X) / 2, 200, () => App.Plant?.IsReblerActive ?? false);
Nodes.Add(rebler); Nodes.Add(rebler);
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); var switcher = new Switcher("", (mw1X + mw2X) / 2, 260, () => App.Plant?.TroughSelector ?? 0, () => App.Plant == null ? 0 : App.Plant.WantMW1Selected ? 1 : 2);
Nodes.Add(switcher); Nodes.Add(switcher);
MW1 = new Trough("MW1", mw1X, mpY - 14, () => App.Plant?.State.MW1_FillLevel ?? FillState.Unknown); MW1 = new Trough("MW1", mw1X, mpY - 70, () => App.Plant?.FillLevelMW1 ?? FillState.Unknown);
MW2 = new Trough("MW2", mw2X, mpY - 14, () => App.Plant?.State.MW2_FillLevel ?? FillState.Unknown); MW2 = new Trough("MW2", mw2X, mpY - 70, () => App.Plant?.FillLevelMW2 ?? FillState.Unknown);
MP1 = new Pump("MP1", mw1X, mpY, () => App.Plant?.State.MP1_State ?? default, () => App.Plant?.State.MP1_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x))); MP1 = new Pump("MP1", mw1X, mpY, () => App.Plant?.MP1 ?? default, () => App.Plant?.MP1HasClearance ?? 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))); MP2 = new Pump("MP2", mw2X, mpY, () => App.Plant?.MP2 ?? default, () => App.Plant?.MP2HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
var x1 = new PipeJoin(mw1X, mpY + 8); var x1 = new PipeJoin(mw1X, mpY + 40);
var x2 = new PipeJoin(mw2X, mpY + 8); var x2 = new PipeJoin(mw2X, mpY + 40);
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 + 16, 2); var v1 = CreateValve(1, mw1X, mpY + 70);
var v2 = CreateValve(2, mw2X, mpY + 16, 0); var v2 = CreateValve(2, mw2X, mpY + 70);
var v3 = CreateValve(3, mw2X + 6, mpY + 16, 2); var v3 = CreateValve(3, mw2X + 30, mpY + 70);
var v4 = CreateValve(4, mw1X - 6, mpY + 16, 0); var v4 = CreateValve(4, mw1X - 30, mpY + 70);
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, ltg1Y); var x3 = new PipeJoin(mw1X, mpY + 100);
var x4 = new PipeJoin(x1X, ltg1Y); var x4 = new PipeJoin(x1X, ltg1Y);
var x5 = new PipeJoin(mw1X - 6, ltg2Y); var x5 = new PipeJoin(mw2X, mpY + 130);
var x6 = new PipeJoin(x1X - 6, ltg2Y); var x6 = new PipeJoin(x1X - 30, ltg2Y);
CreatePipe(v1, x3, true); CreatePipe(v1, x3, true);
CreatePipe(v3, x3, true); CreatePipe(v3, x3, true);
@@ -122,50 +98,50 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(v4, x5, true); CreatePipe(v4, x5, true);
CreatePipe(x5, x6, true); CreatePipe(x5, x6, true);
V5 = CreateValve(5, x1X + 6, ltg1Y, 1); var v5 = CreateValve(5, x1X + 30, ltg1Y);
V6 = CreateValve(6, x1X + 6, ltg2Y + 8, 3); var v6 = CreateValve(6, x1X + 30, ltg2Y + 40);
V7 = CreateValve(7, x1X + 6, ltg2Y, 3); var v7 = CreateValve(7, x1X + 30, ltg2Y);
V8 = CreateValve(8, x1X - 6, ltg2Y + 8, 0); var v8 = CreateValve(8, x1X - 30, ltg2Y + 40);
var v9 = CreateValve(9, ltg2X, ltg5Y + 8, 2); var v9 = CreateValve(9, ltg2X, 500);
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: 6); CreatePipe(v7, v9, label: "Ltg. 2", labelOffsetX: 30);
var xv15 = new PipeJoin(ltg1X, wt1Y + 8); var xv15 = new PipeJoin(ltg1X, wt1Y + 40);
var xv11 = new PipeJoin(ltg2X, wt1Y + 14); var xv11 = new PipeJoin(ltg2X, wt1Y + 70);
var x7 = new PipeJoin(ltg2X, ltg5Y); var x7 = new PipeJoin(ltg2X, 460);
CreatePipe(V5, xv15, label: "Ltg. 1", labelOffsetX: 6); CreatePipe(v5, xv15, label: "Ltg. 1", labelOffsetX: 30);
CreatePipe(v9, x7); CreatePipe(v9, x7);
CreatePipe(x7, xv11); CreatePipe(x7, xv11);
var v10 = CreateValve(10, ltg2X + 8, ltg5Y, 3); var v10 = CreateValve(10, ltg2X + 40, 460);
var v11 = CreateValve(11, ltg1X - 6, wt1Y + 14, 3); var v11 = CreateValve(11, ltg1X - 30, wt1Y + 70);
var v12 = CreateValve(12, ltg1X, wt1Y, 0); var v12 = CreateValve(12, ltg1X, wt1Y);
var v13 = CreateValve(13, ltg2X, wt1Y, 2); var v13 = CreateValve(13, ltg2X, wt1Y);
var v14 = CreateValve(14, ltg1X - 6, wt1Y - 8, 3); var v14 = CreateValve(14, ltg1X - 30, wt1Y - 40);
var v15 = CreateValve(15, ltg1X - 6, wt1Y + 8, 1); var v15 = CreateValve(15, ltg1X - 30, wt1Y + 40);
var v16 = CreateValve(16, ltg1X - 6, wt1Y - 14, 1); var v16 = CreateValve(16, ltg1X - 30, wt1Y - 70);
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 - 14); var xv16 = new PipeJoin(ltg1X, wt1Y - 70);
var xv14 = new PipeJoin(ltg2X, wt1Y - 8); var xv14 = new PipeJoin(ltg2X, wt1Y - 40);
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 - 6, wt2Y - 14, 1); var v20 = CreateValve(20, ltg1X - 30, wt2Y - 70);
var v21 = CreateValve(21, ltg1X - 6, wt2Y - 8, 3); var v21 = CreateValve(21, ltg1X - 30, wt2Y - 40);
var xv20 = new PipeJoin(ltg1X, wt2Y - 14); var xv20 = new PipeJoin(ltg1X, wt2Y - 70);
var xv21 = new PipeJoin(ltg2X, wt2Y - 8); var xv21 = new PipeJoin(ltg2X, wt2Y - 40);
CreatePipe(xv16, xv20); CreatePipe(xv16, xv20);
CreatePipe(xv14, xv21); CreatePipe(xv14, xv21);
CreatePipe(v20, xv20); CreatePipe(v20, xv20);
@@ -173,15 +149,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 + 14, wt1Y, 2); var v17 = CreateValve(17, wtX + 70, wt1Y);
var v18 = CreateValve(18, wtX + 8, wt1Y + 8, 3); var v18 = CreateValve(18, wtX + 40, wt1Y + 40);
var v19 = CreateValve(19, wtX + 8, (wt1Y + wt2Y) / 2, 0); var v19 = CreateValve(19, wtX + 40, (wt1Y + wt2Y) / 2);
var xwt1 = new PipeJoin(wtX + 24, wt1Y + 8); var xwt1 = new PipeJoin(wtX + 120, wt1Y + 40);
var xwt2 = new PipeJoin(wtX + 14, wt1Y + 8); var xwt2 = new PipeJoin(wtX + 70, wt1Y + 40);
var xwt3 = new PipeJoin(wtX + 8, wt1Y - 8); var xwt3 = new PipeJoin(wtX + 40, wt1Y - 40);
var xwt4 = new PipeJoin(wtX + 24, wt1Y - 8); var xwt4 = new PipeJoin(wtX + 120, wt1Y - 40);
var xwt5 = new PipeJoin(wtX + 8, wt2Y + 8); var xwt5 = new PipeJoin(wtX + 40, wt2Y + 40);
var xwt6 = new PipeJoin(wtX + 24, wt2Y - 8); var xwt6 = new PipeJoin(wtX + 120, wt2Y - 40);
CreatePipe(v11, xwt1); CreatePipe(v11, xwt1);
CreatePipe(v15, xwt1); CreatePipe(v15, xwt1);
CreatePipe(xwt1, xwt2); CreatePipe(xwt1, xwt2);
@@ -200,141 +176,141 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(v19, xwt5); CreatePipe(v19, xwt5);
CreatePipe(xwt5, wt2); CreatePipe(xwt5, wt2);
var xv22 = new PipeJoin(t1X - 3, ltg7Y); var xv22 = new PipeJoin(t1X - 15, ltg7Y);
var xv23 = new PipeJoin(t1X + 3, ltg6Y); var xv23 = new PipeJoin(t1X + 15, ltg6Y);
var v22 = CreateValve(22, t1X - 3, tValveY, 0); var v22 = CreateValve(22, t1X - 15, tValveY);
var v23 = CreateValve(23, t1X + 3, tValveY, 2); var v23 = CreateValve(23, t1X + 15, tValveY);
Tank1 = CreateTank(1, t1X, tanksY, 35000); var t1 = CreateTank(1, t1X, tanksY, 35000);
CreatePipe(xv20, xv22, true, label: "Ltg. 7", labelOffsetX: 10); CreatePipe(xv20, xv22, true, label: "Ltg. 7", labelOffsetX: 50);
CreatePipe(xv21, xv23, true, label: "Ltg. 6", labelOffsetX: 4); CreatePipe(xv21, xv23, true, label: "Ltg. 6", labelOffsetX: 20);
CreatePipe(xv22, v22); CreatePipe(xv22, v22);
CreatePipe(xv23, v23); CreatePipe(xv23, v23);
CreateInlet(v22, Tank1); CreateInlet(v22, t1);
CreateInlet(v23, Tank1); CreateInlet(v23, t1);
var xv24 = new PipeJoin(t2X - 3, ltg7Y); var xv24 = new PipeJoin(t2X - 15, ltg7Y);
var xv25 = new PipeJoin(t2X + 3, ltg6Y); var xv25 = new PipeJoin(t2X + 15, ltg6Y);
var v24 = CreateValve(24, t2X - 3, tValveY, 0); var v24 = CreateValve(24, t2X - 15, tValveY);
var v25 = CreateValve(25, t2X + 3, tValveY, 2); var v25 = CreateValve(25, t2X + 15, tValveY);
Tank2 = CreateTank(2, t2X, tanksY, 35000); var t2 = 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, Tank2); CreateInlet(v24, t2);
CreateInlet(v25, Tank2); CreateInlet(v25, t2);
var xv26 = new PipeJoin(t3X - 3, ltg7Y); var xv26 = new PipeJoin(t3X - 15, ltg7Y);
var xv27 = new PipeJoin(t3X + 3, ltg6Y); var xv27 = new PipeJoin(t3X + 15, ltg6Y);
var v26 = CreateValve(26, t3X - 3, tValveY, 0); var v26 = CreateValve(26, t3X - 15, tValveY);
var v27 = CreateValve(27, t3X + 3, tValveY, 2); var v27 = CreateValve(27, t3X + 15, tValveY);
Tank3 = CreateTank(3, t3X, tanksY, 35000); var t3 = 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, Tank3); CreateInlet(v26, t3);
CreateInlet(v27, Tank3); CreateInlet(v27, t3);
var v28 = CreateValve(28, (t3X + t4X) / 2, ltg7Y, 1); var v28 = CreateValve(28, (t3X + t4X) / 2, ltg7Y);
var v29 = CreateValve(29, (t3X + t4X) / 2, ltg6Y, 3); var v29 = CreateValve(29, (t3X + t4X) / 2, ltg6Y);
CreatePipe(xv26, v28); CreatePipe(xv26, v28);
CreatePipe(xv27, v29); CreatePipe(xv27, v29);
var xv30 = new PipeJoin(t4X - 3, ltg7Y); var xv30 = new PipeJoin(t4X - 15, ltg7Y);
var xv31 = new PipeJoin(t4X + 3, ltg6Y); var xv31 = new PipeJoin(t4X + 15, ltg6Y);
var v30 = CreateValve(30, t4X - 3, tValveY, 0); var v30 = CreateValve(30, t4X - 15, tValveY);
var v31 = CreateValve(31, t4X + 3, tValveY, 2); var v31 = CreateValve(31, t4X + 15, tValveY);
Tank4 = CreateTank(4, t4X, tanksY, 35000); var t4 = 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, Tank4); CreateInlet(v30, t4);
CreateInlet(v31, Tank4); CreateInlet(v31, t4);
var xv32 = new PipeJoin(t5X - 3, ltg7Y); var xv32 = new PipeJoin(t5X - 15, ltg7Y);
var xv33 = new PipeJoin(t5X + 3, ltg6Y); var xv33 = new PipeJoin(t5X + 15, ltg6Y);
var v32 = CreateValve(32, t5X - 3, tValveY, 0); var v32 = CreateValve(32, t5X - 15, tValveY);
var v33 = CreateValve(33, t5X + 3, tValveY, 2); var v33 = CreateValve(33, t5X + 15, tValveY);
Tank5 = CreateTank(5, t5X, tanksY, 35000); var t5 = CreateTank(5, t5X, tanksY, 35000);
CreatePipe(xv30, xv32, label: "Ltg. 7", labelOffsetX: 6); CreatePipe(xv30, xv32, label: "Ltg. 7", labelOffsetX: 30);
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, Tank5); CreateInlet(v32, t5);
CreateInlet(v33, Tank5); CreateInlet(v33, t5);
var v34 = CreateValve(34, (t5X + t6X) / 2, ltg7Y, 1); var v34 = CreateValve(34, (t5X + t6X) / 2, ltg7Y);
var v35 = CreateValve(35, (t5X + t6X) / 2, ltg6Y, 3); var v35 = CreateValve(35, (t5X + t6X) / 2, ltg6Y);
CreatePipe(xv32, v34); CreatePipe(xv32, v34);
CreatePipe(xv33, v35); CreatePipe(xv33, v35);
var xv36 = new PipeJoin(t6X + 3, ltg6Y); var xv36 = new PipeJoin(t6X + 15, ltg6Y);
var xv37 = new PipeJoin(t6X - 3, ltg7Y); var xv37 = new PipeJoin(t6X - 15, ltg7Y);
var v36 = CreateValve(36, t6X + 3, tValveY, 2); var v36 = CreateValve(36, t6X + 15, tValveY);
var v37 = CreateValve(37, t6X - 3, tValveY, 0); var v37 = CreateValve(37, t6X - 15, tValveY);
Tank6 = CreateTank(6, t6X, tanksY, 35000); var t6 = 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, Tank6); CreateInlet(v36, t6);
CreateInlet(v37, Tank6); CreateInlet(v37, t6);
var xv38 = new PipeJoin(t7X + 3, ltg6Y); var xv38 = new PipeJoin(t7X + 15, ltg6Y);
var xv39 = new PipeJoin(t7X - 3, ltg7Y); var xv39 = new PipeJoin(t7X - 15, ltg7Y);
var v38 = CreateValve(38, t7X + 3, tValveY, 2); var v38 = CreateValve(38, t7X + 15, tValveY);
var v39 = CreateValve(39, t7X - 3, tValveY, 0); var v39 = CreateValve(39, t7X - 15, tValveY);
Tank7 = CreateTank(7, t7X, tanksY, 35000); var t7 = 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, Tank7); CreateInlet(v38, t7);
CreateInlet(v39, Tank7); CreateInlet(v39, t7);
var xv40 = new PipeJoin(t8X + 3, ltg6Y); var xv40 = new PipeJoin(t8X + 15, ltg6Y);
var xv41 = new PipeJoin(t8X - 3, ltg7Y); var xv41 = new PipeJoin(t8X - 15, ltg7Y);
var v40 = CreateValve(40, t8X + 3, tValveY, 2); var v40 = CreateValve(40, t8X + 15, tValveY);
var v41 = CreateValve(41, t8X - 3, tValveY, 0); var v41 = CreateValve(41, t8X - 15, tValveY);
Tank8 = CreateTank(8, t8X, tanksY, 35000); var t8 = 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: 6); CreatePipe(xv39, xv41, label: "Ltg. 7", labelOffsetX: 30);
CreatePipe(xv40, v40); CreatePipe(xv40, v40);
CreatePipe(xv41, v41); CreatePipe(xv41, v41);
CreateInlet(v40, Tank8); CreateInlet(v40, t8);
CreateInlet(v41, Tank8); CreateInlet(v41, t8);
var v42 = CreateValve(42, t9X + 3, tValveY, 2); var v42 = CreateValve(42, t9X + 15, tValveY);
var v43 = CreateValve(43, t9X - 3, tValveY, 0); var v43 = CreateValve(43, t9X - 15, tValveY);
Tank9 = CreateTank(9, t9X, tanksY, 35000); var t9 = CreateTank(9, t9X, tanksY, 35000);
CreatePipe(xv40, v42); CreatePipe(xv40, v42);
CreatePipe(xv41, v43); CreatePipe(xv41, v43);
CreateInlet(v42, Tank9); CreateInlet(v42, t9);
CreateInlet(v43, Tank9); CreateInlet(v43, t9);
var xp12 = new PipeJoin((t1X + t2X) / 2, tanksY + 16); var xp12 = new PipeJoin((t1X + t2X) / 2, tanksY + 80);
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))); P12 = new Pump("P1/2", (t1X + t2X) / 2, tanksY + 110, () => App.Plant?.P12 ?? default, () => App.Plant?.P12HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
CreateOutlet(Tank1, xp12); CreateOutlet(t1, xp12);
CreateOutlet(Tank2, xp12); CreateOutlet(t2, xp12);
CreatePipe(xp12, P12); CreatePipe(xp12, P12);
P3 = new Pump("P3", t3X, tanksY + 22, () => App.Plant?.State.P3_State ?? default, () => App.Plant?.State.P3_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x))); P3 = new Pump("P3", t3X, tanksY + 110, () => App.Plant?.P3 ?? default, () => App.Plant?.P3HasClearance ?? 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))); P4 = new Pump("P4", t4X, tanksY + 110, () => App.Plant?.P4 ?? default, () => App.Plant?.P4HasClearance ?? 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))); P5 = new Pump("P5", t5X, tanksY + 110, () => App.Plant?.P5 ?? default, () => App.Plant?.P5HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
CreateOutlet(Tank3, P3); CreateOutlet(t3, P3);
CreateOutlet(Tank4, P4); CreateOutlet(t4, P4);
CreateOutlet(Tank5, P5); CreateOutlet(t5, P5);
var v44 = CreateValve(44, (t1X + t2X) / 2, tanksY + 30, 2); var v44 = CreateValve(44, (t1X + t2X) / 2, tanksY + 150);
var v45 = CreateValve(45, t3X, tanksY + 30, 2); var v45 = CreateValve(45, t3X, tanksY + 150);
var v46 = CreateValve(46, (t3X + t4X) / 2, ltg5Y, 3); var v46 = CreateValve(46, (t3X + t4X) / 2, tanksY + 180);
var v47 = CreateValve(47, t4X, tanksY + 30, 2); var v47 = CreateValve(47, t4X, tanksY + 150);
var v48 = CreateValve(48, t5X, tanksY + 30, 2); var v48 = CreateValve(48, t5X, tanksY + 150);
var xv44 = new PipeJoin((t1X + t2X) / 2, tanksY + 36); var xv44 = new PipeJoin((t1X + t2X) / 2, tanksY + 180);
var xv45 = new PipeJoin(t3X, tanksY + 36); var xv45 = new PipeJoin(t3X, tanksY + 180);
var xv47 = new PipeJoin(t4X, tanksY + 36); var xv47 = new PipeJoin(t4X, tanksY + 180);
CreatePipe(P12, v44); CreatePipe(P12, v44);
CreatePipe(P3, v45); CreatePipe(P3, v45);
CreatePipe(P4, v47); CreatePipe(P4, v47);
@@ -349,45 +325,45 @@ namespace PamhagenSysCtrl.Helpers {
CreateTwoWayPipe(v46, xv45); CreateTwoWayPipe(v46, xv45);
CreateTwoWayPipe(xv45, xv44); CreateTwoWayPipe(xv45, xv44);
var xt6 = new PipeJoin(t6X, tanksY + 22); var xt6 = new PipeJoin(t6X, tanksY + 110);
var xt7 = new PipeJoin(t7X, tanksY + 22); var xt7 = new PipeJoin(t7X, tanksY + 110);
var xt8 = new PipeJoin(t8X, tanksY + 22); var xt8 = new PipeJoin(t8X, tanksY + 110);
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))); P6 = new Pump("P6", t6X, tanksY + 150, () => App.Plant?.P6 ?? default, () => App.Plant?.P6HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
CreateOutlet(Tank6, xt6); CreateOutlet(t6, xt6);
CreateOutlet(Tank7, xt7); CreateOutlet(t7, xt7);
CreateOutlet(Tank8, xt8); CreateOutlet(t8, xt8);
CreateOutlet(Tank9, xt8); CreateOutlet(t9, 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 + 44, 2); var v49 = CreateValve(49, t4X, tanksY + 220);
CreatePipe(xv47, v49); CreatePipe(xv47, v49);
var prX = (pr1X + pr2X) / 2; var prX = (pr1X + pr2X) / 2;
var pr1cX = prX - 3; var pr1cX = prX - 15;
var pr2cX = prX + 3; var pr2cX = prX + 15;
Press1 = new Press("Presse 1", pr1X, prY, 20000, () => FillState.Unknown); var press1 = new Press("Presse 1", pr1X, prY, 20000, () => FillState.Unknown);
Press2 = new Press("Presse 2", pr2X, prY, 15000, () => FillState.Unknown); var press2 = new Press("Presse 2", pr2X, prY, 15000, () => FillState.Unknown);
var v50 = CreateValve(50, pr2cX + 6, ltg3Y, 1); var v50 = CreateValve(50, pr2cX + 30, 580);
var v51 = CreateValve(51, pr1cX - 6, ltg3Y, 1); var v51 = CreateValve(51, pr1cX - 30, 580);
var v52 = CreateValve(52, pr1cX - 6, ltg4Y, 3); var v52 = CreateValve(52, pr1cX - 30, 610);
var v53 = CreateValve(53, pr2cX + 6, ltg4Y, 3); var v53 = CreateValve(53, pr2cX + 30, 610);
var v54 = CreateValve(54, pr2cX + 6, ltg8Y, 1); var v54 = CreateValve(54, pr2cX + 30, 550);
var v55 = CreateValve(55, pr1cX - 6, ltg8Y, 1); var v55 = CreateValve(55, pr1cX - 30, 550);
var v56 = CreateValve(56, pr1cX - 6, ltg9Y, 3); var v56 = CreateValve(56, pr1cX - 30, 640);
var v57 = CreateValve(57, pr2cX + 6, ltg9Y, 3); var v57 = CreateValve(57, pr2cX + 30, 640);
var xp1 = new PipeJoin(pr1cX - 14, ltg3Y); var xp1 = new PipeJoin(pr1cX - 70, 580);
var xp2 = new PipeJoin(pr1cX - 14, ltg4Y); var xp2 = new PipeJoin(pr1cX - 70, 610);
var xp3 = new PipeJoin(pr2cX + 14, ltg8Y); var xp3 = new PipeJoin(pr2cX + 70, 550);
var xp4 = new PipeJoin(pr2cX + 14, ltg9Y); var xp4 = new PipeJoin(pr2cX + 70, 640);
var xp5 = new PipeJoin(pr1cX, ltg3Y); var xp5 = new PipeJoin(pr1cX, 580);
var xp6 = new PipeJoin(pr1cX, ltg4Y); var xp6 = new PipeJoin(pr1cX, 610);
var xp7 = new PipeJoin(pr2cX, ltg8Y); var xp7 = new PipeJoin(pr2cX, 550);
var xp8 = new PipeJoin(pr2cX, ltg9Y); var xp8 = new PipeJoin(pr2cX, 640);
CreatePipe(V6, xp1, true, label: "Ltg. 3", labelOffsetX: 6); CreatePipe(v6, xp1, true, label: "Ltg. 3", labelOffsetX: 30);
CreatePipe(V8, xp2, true, label: "Ltg. 4", labelOffsetX: 18); CreatePipe(v8, xp2, true, label: "Ltg. 4", labelOffsetX: 90);
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);
@@ -410,149 +386,88 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(xp5, xp6); CreatePipe(xp5, xp6);
CreatePipe(xp7, xp8); CreatePipe(xp7, xp8);
var zv1 = new ExternalValve("ZV1", pr1cX, prY - 8, 0, () => App.Plant?.State.Press1_HasClearance ?? false); var zv1 = new CenterValve("ZV1", pr1cX, prY - 40, () => App.Plant?.Press1HasClearance ?? false);
var zv2 = new ExternalValve("ZV2", pr2cX, prY - 8, 2, () => App.Plant?.State.Press2_HasClearance ?? false); var zv2 = new CenterValve("ZV2", pr2cX, prY - 40, () => App.Plant?.Press2HasClearance ?? 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 ControlledValve CreateValve(int n, double x, double y, int o) { protected Valve CreateValve(int n, double x, double y) {
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))); return new(n, x, y, () => App.Plant?.IsValveOpen(n) ?? false, (v) => App.Plant?.ManualControlMode ?? false ? App.Plant.WantValveOpen(n) : SelectedPaths.Any(p => p.Hops.Any(h => h.Node == 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?.State.Tanks_FillLevel[n - 1] ?? FillState.Unknown); return new($"Tank {n}", x, y, capacity, () => App.Plant?.TankFillLevels[n - 1] ?? FillState.Unknown);
} }
private int PathCost(Path p) { public void TraverseSubgraph(INode start, Func<INode, bool> invalid, HashSet<IEdge>? _visited = null, Action<INode, bool, IEdge>? edgeAction = null, Action<Valve>? valveAction = null, bool reverse = false) {
return
1000 * p.Valves.Count() +
(p.InnerValves.Any(v => v.Nr == 15) && !p.InnerValves.Any(v => v.Nr == 16 || v.Nr == 20) ? 10 : 0) +
(p.InnerValves.Any(v => v.Nr == 11) && !p.InnerValves.Any(v => v.Nr == 14 || v.Nr == 21) ? 10 : 0) +
(p.InnerValves.Any(v => v.Nr == 6) ? 10 : 0) +
p.Bends;
}
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 ?? []; var visited = _visited ?? [];
if (start is Valve v) {
valveAction?.Invoke(v);
}
if (invalid(start)) {
return;
}
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;
yield return (start, edge, reverse || _visited == null, o); edgeAction?.Invoke(start, reverse || _visited == null, edge);
visited.Add(edge); visited.Add(edge);
foreach (var e in TraverseSubgraph(o, isBorder, visited, reverse || _visited == null)) TraverseSubgraph(o, invalid, visited, edgeAction, valveAction, 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;
yield return (start, edge, reverse, o); edgeAction?.Invoke(start, reverse, edge);
visited.Add(edge); visited.Add(edge);
foreach (var e in TraverseSubgraph(o, isBorder, visited, reverse)) TraverseSubgraph(o, invalid, visited, edgeAction, valveAction, reverse);
yield return e;
} }
} }
public void AddPath(Path path) { public void AddPath(Path path) {
_selectedPaths.Add(path); _selectedPaths.Add(path);
path.LockValves(); UpdateValvesAdjacientToPath(path, true, false);
if (App.Plant is PamhagenPlant plant) { if (App.Plant is PamhagenPlant plant) {
foreach (var v in path.InnerValves) plant.OpenV(v.Nr); foreach (var (_, node) in path.Hops) {
foreach (var v in path.BorderValves) plant.CloseV(v.Nr); if (node is Valve v) {
plant.OpenV(v.Nr);
}
}
} }
} }
public void RemovePath(Path path) { public void RemovePath(Path path) {
_selectedPaths.Remove(path); _selectedPaths.Remove(path);
path.UnlockValves(); UpdateValvesAdjacientToPath(path, false);
foreach (var p in _selectedPaths) UpdateValvesAdjacientToPath(p, true);
if (App.Plant is PamhagenPlant plant) { if (App.Plant is PamhagenPlant plant) {
foreach (var v in path.InnerValves) plant.CloseV(v.Nr); foreach (var (_, node) in path.Hops) {
if (node is Valve v) {
plant.CloseV(v.Nr);
}
}
} }
} }
public static void ColorPipesAdjacientToPath(Path path, Brush? color1, MotorState flowArrows, Brush? color2) { private void UpdateValvesAdjacientToPath(Path path, bool locked, bool? wantOpen = null) {
foreach (var (edge, node) in path.Hops) {
TraverseSubgraph(node, (n) => n is Valve || n is ISink || n is Pump, 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, MotorState flowArrows, Brush? color2) {
var color = color1; var color = color1;
var flowPathOffset = 0.0; var flowPathOffset = 0.0;
HashSet<IEdge> visited = []; HashSet<IEdge> visited = [];
@@ -566,28 +481,27 @@ namespace PamhagenSysCtrl.Helpers {
} }
visited.Add(edge); visited.Add(edge);
if (node is Pump) color = color2; if (node is Pump) color = color2;
foreach (var (_, e, _, _) in TraverseSubgraph(node, (n) => n is Valve || n is ISink || n is Pump, visited)) { TraverseSubgraph(node, (n) => n is Valve || n is ISink || n is Pump, visited, edgeAction: (s, r, e) => e.Highlight = color);
e.Highlight = color;
}
} }
} }
public static void ColorPipesByValves(INode start, Brush? color1, MotorState flowArrows, Brush? color2) { public void ColorPipesByValves(INode start, Brush? color1, MotorState flowArrows, Brush? color2) {
var flowPathOffset = 0.0; 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))) { TraverseSubgraph(start, (n) => n is ISink || n is Outlet || !((n as Valve)?.IsOpen ?? (n as CenterValve)?.IsOpen ?? true), null,
if (((e.Start as Valve)?.IsOpen ?? true) && ((e.End as Valve)?.IsOpen ?? true)) { edgeAction: (s, r, e) => {
e.Highlight = color1; if (((e.Start as Valve)?.IsOpen ?? true) && ((e.End as Valve)?.IsOpen ?? true)) {
if (flowArrows == MotorState.Forward || flowArrows == MotorState.Backward) { e.Highlight = color1;
var dir = e.Start == s; if (flowArrows == MotorState.Forward || flowArrows == MotorState.Backward) {
if (r) dir = !dir; var dir = e.Start == s;
if (flowArrows == MotorState.Backward) dir = !dir; if (r) dir = !dir;
e.Flow = (dir, flowPathOffset); if (flowArrows == MotorState.Backward) dir = !dir;
flowPathOffset += e.FlowLength; e.Flow = (dir, flowPathOffset);
flowPathOffset += e.FlowLength;
}
} else {
e.Highlight = color2;
} }
} else { });
e.Highlight = color2;
}
}
} }
} }
} }
+539 -294
View File
@@ -1,354 +1,599 @@
using PamhagenSysCtrl.Helpers.Pipeline;
using PamhagenSysCtrl.Helpers.Plc;
using System.IO; using System.IO;
using System.Windows; using System.Windows;
namespace PamhagenSysCtrl.Helpers { namespace PamhagenSysCtrl.Helpers {
public class PamhagenPlant : IDisposable { public class PamhagenPlant : IDisposable {
public enum ShortcutKey {
START_REBLER, STOP_REBLER, TOGGLE_REBLER, INDICATE_REBLER,
START_WASTE_DISPOSAL, STOP_WASTE_DISPOSAL, TOGGLE_WASTE_DISPOSAL, INDICATE_WASTE_DISPOSAL,
START_AUGER1, STOP_AUGER1, TOGGLE_AUGER1, INDICATE_AUGER1,
START_AUGER2, STOP_AUGER2, TOGGLE_AUGER2, INDICATE_AUGER2,
START_AUGER3, STOP_AUGER3, TOGGLE_AUGER3, INDICATE_AUGER3,
INDICATE_PRESS_CROSS_AUGER,
START_TROUGH_AUGER, STOP_TROUGH_AUGER, TOGGLE_TROUGH_AUGER, INDICATE_TROUGH_AUGER,
SELECT_MW1, INDICATE_MW1_SELECTED,
SELECT_MW2, INDICATE_MW2_SELECTED,
INDICATE_MW1, INDICATE_MW1_EMPTY, INDICATE_MW1_FILLED, INDICATE_MW1_HALF, INDICATE_MW1_FULL,
INDICATE_MW2, INDICATE_MW2_EMPTY, INDICATE_MW2_FILLED, INDICATE_MW2_HALF, INDICATE_MW2_FULL,
START_STIRRER_MW1, STOP_STIRRER_MW1, TOGGLE_STIRRER_MW1, INDICATE_STIRRER_MW1,
START_STIRRER_MW2, STOP_STIRRER_MW2, TOGGLE_STIRRER_MW2, INDICATE_STIRRER_MW2,
START_MP1, STOP_MP1, TOGGLE_MP1, INDICATE_MP1,
START_MP2, STOP_MP2, TOGGLE_MP2, INDICATE_MP2,
ENABLE_P12, DISABLE_P12, TOGGLE_P12, INDICATE_P12,
ENABLE_P3, DISABLE_P3, TOGGLE_P3, INDICATE_P3,
ENABLE_P4, DISABLE_P4, TOGGLE_P4, INDICATE_P4,
ENABLE_P5, DISABLE_P5, TOGGLE_P5, INDICATE_P5,
ENABLE_P6, DISABLE_P6, TOGGLE_P6, INDICATE_P6,
INDICATE_PRESS1_CLEARANCE,
INDICATE_PRESS2_CLEARANCE,
PATH_MW1_PRESS1, PATH_MW1_LTG3_PRESS1, PATH_MW1_LTG4_PRESS1,
PATH_MW1_PRESS2, PATH_MW1_LTG3_PRESS2, PATH_MW1_LTG4_PRESS2,
PATH_MW1_T1, PATH_MW1_LTG1_T1, PATH_MW1_LTG2_T1,
PATH_MW1_T2, PATH_MW1_LTG1_T2, PATH_MW1_LTG2_T2,
PATH_MW1_T3, PATH_MW1_LTG1_T3, PATH_MW1_LTG2_T3,
PATH_MW1_T4, PATH_MW1_LTG1_T4, PATH_MW1_LTG2_T4,
PATH_MW1_T5, PATH_MW1_LTG1_T5, PATH_MW1_LTG2_T5,
PATH_MW1_T6, PATH_MW1_LTG1_T6, PATH_MW1_LTG2_T6,
PATH_MW1_T7, PATH_MW1_LTG1_T7, PATH_MW1_LTG2_T7,
PATH_MW1_T8, PATH_MW1_LTG1_T8, PATH_MW1_LTG2_T8,
PATH_MW1_T9, PATH_MW1_LTG1_T9, PATH_MW1_LTG2_T9,
PATH_MW2_PRESS1, PATH_MW2_LTG3_PRESS1, PATH_MW2_LTG4_PRESS1,
PATH_MW2_PRESS2, PATH_MW2_LTG3_PRESS2, PATH_MW2_LTG4_PRESS2,
PATH_MW2_T1, PATH_MW2_LTG1_T1, PATH_MW2_LTG2_T1,
PATH_MW2_T2, PATH_MW2_LTG1_T2, PATH_MW2_LTG2_T2,
PATH_MW2_T3, PATH_MW2_LTG1_T3, PATH_MW2_LTG2_T3,
PATH_MW2_T4, PATH_MW2_LTG1_T4, PATH_MW2_LTG2_T4,
PATH_MW2_T5, PATH_MW2_LTG1_T5, PATH_MW2_LTG2_T5,
PATH_MW2_T6, PATH_MW2_LTG1_T6, PATH_MW2_LTG2_T6,
PATH_MW2_T7, PATH_MW2_LTG1_T7, PATH_MW2_LTG2_T7,
PATH_MW2_T8, PATH_MW2_LTG1_T8, PATH_MW2_LTG2_T8,
PATH_MW2_T9, PATH_MW2_LTG1_T9, PATH_MW2_LTG2_T9,
PATH_T12_PRESS1, PATH_T12_PRESS2, PATH_T12_T1, PATH_T12_T2, PATH_T12_T3, PATH_T12_T4, PATH_T12_T5, PATH_T12_T6, PATH_T12_T7, PATH_T12_T8, PATH_T12_T9,
PATH_T3_PRESS1, PATH_T3_PRESS2, PATH_T3_T1, PATH_T3_T2, PATH_T3_T3, PATH_T3_T4, PATH_T3_T5, PATH_T3_T6, PATH_T3_T7, PATH_T3_T8, PATH_T3_T9,
PATH_T4_PRESS1, PATH_T4_PRESS2, PATH_T4_T1, PATH_T4_T2, PATH_T4_T3, PATH_T4_T4, PATH_T4_T5, PATH_T4_T6, PATH_T4_T7, PATH_T4_T8, PATH_T4_T9,
PATH_T5_PRESS1, PATH_T5_PRESS2, PATH_T5_T1, PATH_T5_T2, PATH_T5_T3, PATH_T5_T4, PATH_T5_T5, PATH_T5_T6, PATH_T5_T7, PATH_T5_T8, PATH_T5_T9,
PATH_P6_PRESS1, PATH_P6_PRESS2,
}
protected PamhagenPlc? Plc; protected PamhagenPlc? Plc;
protected Xbe24Keyboard? Keyboard;
public PamhagenGraph Graph;
public bool IsPlcConnected => Plc != null;
public bool IsKeyboardConnected => Keyboard != null;
protected bool IsRunning = true; protected bool IsRunning = true;
protected Thread? ReconnectThread; protected Thread? BackgroundThread;
protected Thread? PlcThread;
public bool ManualControlMode { get; set; }
public bool OverridePathClearances { get; set; }
public bool OverrideDryRunClearances { get; set; }
public event EventHandler<PlantEventArgs>? Update; public event EventHandler<PlantEventArgs>? Update;
public readonly string PlcComPort; protected bool ActuatorsChanged = false;
public string? PlcConnectionError { get; private set; } protected PamhagenPlc.ActuatorStates Actuators;
public T1.Status? LastPlcStatus => Plc?.LastStatus; protected PamhagenPlc.SensorStates Sensors = new(default, default, default, default, default, default, default, [
public T1Error? LastPlcError => Plc?.LastError; FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown
public long? LastRttNs { get; private set; } ], default);
public PamhagenState State => Plc?.State ?? default; public bool IsValveOpen(int n) => !IsValveClosed(n);
public bool ManualControlMode { get => Plc?.ManualControlMode ?? false; set => Plc?.ManualControlMode = value; } public bool IsValveClosed(int n) => Sensors.GetDS(n);
public bool OverridePathClearances { get => Plc?.OverridePathClearances ?? false; set => Plc?.OverridePathClearances = value; } public bool WantValveClosed(int n) => !WantValveOpen(n);
public bool OverrideDryRunClearances { get => Plc?.OverrideDryRunClearances ?? false; set => Plc?.OverrideDryRunClearances = value; } public bool WantValveOpen(int n) => Actuators.GetV(n);
public PamhagenPlant(string comPort) { public bool AreValvesOpen(params int[] ns) => ns.All(IsValveOpen) && ns.All(WantValveOpen);
PlcComPort = comPort; public bool AreValvesClosed(params int[] ns) => ns.All(IsValveClosed) && ns.All(WantValveClosed);
Graph = new();
Update += UpdateKeyboardBacklights;
ReconnectThread = new Thread(new ParameterizedThreadStart(ReconnectLoop));
ReconnectThread.Start();
}
protected async void ReconnectLoop(object? parameters) { public int TroughSelector => Sensors.TroughSelector;
while (IsRunning) { public bool IsMWSelected => Sensors.TroughSelector == 1;
if (Plc == null) { public bool IsMW2Selected => Sensors.TroughSelector == 2;
PlcThread?.Join(); public FillState FillLevelMW1 => Sensors.FillingLevelMW1;
PlcThread = null; public FillState FillLevelMW2 => Sensors.FillingLevelMW2;
PamhagenPlc? tmp = null; public bool Press1HasClearance => Sensors.ClearancePress1;
try { public bool Press2HasClearance => Sensors.ClearancePress2;
tmp = new PamhagenPlc(PlcComPort, App.Config.PlcSensorDelayOpen * 1000, App.Config.PlcSensorDelayClose * 1000); public FillState FillLevelT1 => Sensors.FillingLevelTanks[0];
await tmp.Initialize(); public FillState FillLevelT2 => Sensors.FillingLevelTanks[1];
Plc = tmp; public FillState FillLevelT3 => Sensors.FillingLevelTanks[2];
PlcConnectionError = null; public FillState FillLevelT4 => Sensors.FillingLevelTanks[3];
PlcThread = new Thread(new ParameterizedThreadStart(PlcLoop)); public FillState FillLevelT5 => Sensors.FillingLevelTanks[4];
PlcThread.Start(); public FillState FillLevelT6 => Sensors.FillingLevelTanks[5];
} catch (Exception exc) { public FillState FillLevelT7 => Sensors.FillingLevelTanks[6];
tmp?.Dispose(); public FillState FillLevelT8 => Sensors.FillingLevelTanks[7];
PlcConnectionError = $"{exc.GetType().Name}: {exc.Message}"; public FillState FillLevelT9 => Sensors.FillingLevelTanks[8];
if (exc is FileNotFoundException) { public FillState[] TankFillLevels => Sensors.FillingLevelTanks;
// COM port does not exist - ignore public double GradationOe => Sensors.GradationOe;
PlcConnectionError = "Port existiert nicht"; public MotorState PresseQuerSchnecke => Sensors.PresseQuerSchnecke;
} else if (exc is UnauthorizedAccessException) { public bool IsPresseQuerSchneckeActive => PresseQuerSchnecke == MotorState.Forward;
// COM port exists, but is used by other process - ignore
PlcConnectionError = "Port wird bereits verwendet";
} else if (exc is TimeoutException) {
// COM port exists, but PLC not responding - ignore
PlcConnectionError = "Keine Rückmeldung von SPS";
} else {
var str = $"Beim Aufbauen einer Verbindung zur SPS ist ein Fehler aufgetreten:\n\n{exc.GetType().Name}: {exc.Message}";
if (exc.InnerException != null) str += $"\n\n{exc.InnerException.GetType().Name}: {exc.InnerException.Message}";
MessageBox.Show(str, "SPS-Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
RaiseUpdateEvent(new());
}
if (Keyboard == null) { public bool WantMW1Selected => Actuators.GetV(58);
try { public bool WantMW2Selected => !Actuators.GetV(58);
Keyboard = new();
Keyboard.Error += (s, e) => {
try {
Keyboard.Dispose();
} catch { }
Keyboard = null;
};
Keyboard.KeyDown += Keyboard_KeyDown;
Keyboard.KeysChanged += Keyboard_KeysChanged;
Keyboard.SetFashFrequency(250);
} catch { }
}
await Task.Delay(100); public bool IsTroughAugerActive => Actuators.TroughAuger;
} public bool IsReblerActive => Actuators.Rebler;
public bool IsStirrerMW1Active => Actuators.StirrerMW1;
public bool IsStirrerMW2Active => Actuators.StirrerMW2;
public bool IsAuger1Active => Actuators.Auger1;
public bool IsAuger2Active => Actuators.Auger2;
public bool IsAuger3Active => Actuators.Auger3;
public bool IsWasteDisposalActive => IsAuger1Active || IsAuger2Active || IsAuger3Active;
public bool IsGradationPumpActive => Actuators.GradationPump;
public bool IsMP1Active => Actuators.MP1 == MotorState.Forward || Actuators.MP1 == MotorState.Backward;
public bool IsMP2Active => Actuators.MP2 == MotorState.Forward || Actuators.MP2 == MotorState.Backward;
public bool IsP12Active => Actuators.P12 == MotorState.Forward;
public bool IsP3Active => Actuators.P3 == MotorState.Forward;
public bool IsP4Active => Actuators.P4 == MotorState.Forward;
public bool IsP5Active => Actuators.P5 == MotorState.Forward;
public bool IsP6Active => Actuators.P6 == MotorState.Forward;
public PamhagenPlant(string portName) {
Plc = new(portName);
Update += OnUpdate;
} }
protected virtual void RaiseUpdateEvent(PlantEventArgs evt) { protected virtual void RaiseUpdateEvent(PlantEventArgs evt) {
App.MainDispatcher?.BeginInvoke(() => Update?.Invoke(this, evt)); App.MainDispatcher?.BeginInvoke(() => Update?.Invoke(this, evt));
} }
protected void UpdateKeyboardBacklights(object? sender, PlantEventArgs evt) { protected static void OnUpdate(object? sender, PlantEventArgs evt) {
if (sender is not PamhagenPlant plant || plant.Keyboard is not Xbe24Keyboard k) return; if (sender is not PamhagenPlant p) return;
// TODO update all keys if (!p.ManualControlMode) {
if (p.IsPresseQuerSchneckeActive && !p.IsAuger2Active) p.StartAuger2();
if (p.IsReblerActive && !p.IsAuger1Active) p.StartAuger1();
if (p.IsAuger1Active && !p.IsAuger2Active) p.StartAuger2();
if (p.IsAuger2Active && !p.IsAuger3Active) p.StartAuger3();
if (p.IsTroughAugerActive && (!p.IsReblerActive || (p.IsMWSelected && p.FillLevelMW1 == FillState.Full) || (p.IsMW2Selected && p.FillLevelMW2 == FillState.Full)))
p.StopTroughAuger();
}
k.SetBacklight(ShortcutKey.START_REBLER, State.Rebler_IsActive ? Xbe24Keyboard.Green : Xbe24Keyboard.LightGreen); if (p.IsMP1Active && !p.MP1HasClearance) p.StopMP1();
k.SetBacklight(ShortcutKey.STOP_REBLER, !State.Rebler_IsActive ? Xbe24Keyboard.Red : Xbe24Keyboard.LightRed); if (p.IsMP2Active && !p.MP2HasClearance) p.StopMP2();
k.SetBacklight(ShortcutKey.START_TROUGH_AUGER, !State.TroughAuger_HasClearance ? k.IsKeyPressed(ShortcutKey.START_TROUGH_AUGER) ? Xbe24Keyboard.Orange : Xbe24Keyboard.LightRed : State.TroughAuger_IsActive ? Xbe24Keyboard.Green : Xbe24Keyboard.LightGreen); if (!p.ManualControlMode) {
k.SetBacklight(ShortcutKey.STOP_TROUGH_AUGER, !State.TroughAuger_IsActive ? Xbe24Keyboard.Red : Xbe24Keyboard.LightRed); if (p.MP1 == MotorState.Forward && p.FillLevelMW1 >= FillState.Half && !p.IsStirrerMW1Active) {
p.StartStirrerMW1();
k.SetBacklight(ShortcutKey.SELECT_MW2, State.MW2_WantSelected || State.MW2_IsSelected ? Xbe24Keyboard.Blue : Xbe24Keyboard.LightBlue, State.MW2_WantSelected && !State.MW2_IsSelected); } else if (p.IsStirrerMW1Active && (!p.IsMP1Active || p.FillLevelMW1 < FillState.Half)) {
k.SetBacklight(ShortcutKey.SELECT_MW1, State.MW1_WantSelected || State.MW1_IsSelected ? Xbe24Keyboard.Blue : Xbe24Keyboard.LightBlue, State.MW1_WantSelected && !State.MW1_IsSelected); p.StopStirrerMW1();
}
Dictionary<Pump, bool> pumpHighlightOff = []; if (p.MP2 == MotorState.Forward && p.FillLevelMW2 >= FillState.Half && !p.IsStirrerMW2Active) {
foreach (var (key, d) in Graph.PathShortcuts) { p.StartStirrerMW2();
if (App.Config.MapKey(key) is not int idx) } else if (p.IsStirrerMW2Active && (!p.IsMP2Active || p.FillLevelMW2 < FillState.Half)) {
continue; p.StopStirrerMW2();
var isActive = d.Pump.IsActive;
var paths = plant.Graph.SelectedPaths.Select(p => (Pipeline.Path?)p)
.Where(p => p?.Matches(d.Source, d.Intermediate, d.Sink) ?? false);
if (paths.FirstOrDefault() is Pipeline.Path path) {
var isReady = path.IsReady;
var isDefault = !path.Hops.SkipLast(1).Any(h => h.Node is not Valve && h.Node is not PipeJoin && h.Node is not Pump);
k.SetBacklight(idx, isActive ? isDefault ? Xbe24Keyboard.Cyan : Xbe24Keyboard.Purple : isDefault ? Xbe24Keyboard.Blue : Xbe24Keyboard.DarkPurple, !isReady);
} else {
var isFree = Graph.GetFreePath([d.Source, d.Sink], overrideStart: true) != null;
if (isActive && k.IsKeyPressed(idx)) pumpHighlightOff[d.Pump] = true;
k.SetBacklight(idx, isActive ? k.IsKeyPressed(idx) ? Xbe24Keyboard.Orange : Xbe24Keyboard.Neutral : isFree ? Xbe24Keyboard.LightBlue : k.IsKeyPressed(idx) ? Xbe24Keyboard.Orange : Xbe24Keyboard.Neutral);
} }
} }
if (!plant.State.MP2_HasPathClearance && plant.State.MP2_HasDryRunClearance && plant.State.MP2_WantsPathClearance) { if (p.IsP12Active && !p.P12HasClearance) p.StopP12();
k.SetBacklight(ShortcutKey.START_MP2, plant.State.MP2_WantActive ? Xbe24Keyboard.Green : Xbe24Keyboard.LightGreen, true); if (p.IsP3Active && !p.P3HasClearance) p.StopP3();
} else { if (p.IsP4Active && !p.P4HasClearance) p.StopP4();
k.SetBacklight(ShortcutKey.START_MP2, !State.MP2_HasClearance ? k.IsKeyPressed(ShortcutKey.START_MP2) ? Xbe24Keyboard.Orange : Xbe24Keyboard.LightRed : State.MP2_IsActive ? Xbe24Keyboard.Green : Xbe24Keyboard.LightGreen); if (p.IsP5Active && !p.P5HasClearance) p.StopP5();
} if (p.IsP6Active && !p.P6HasClearance) p.StopP6();
if (!plant.State.MP1_HasPathClearance && plant.State.MP1_HasDryRunClearance && plant.State.MP1_WantsPathClearance) { }
k.SetBacklight(ShortcutKey.START_MP1, plant.State.MP1_WantActive ? Xbe24Keyboard.Green : Xbe24Keyboard.LightGreen, true);
} else { public async Task StartThread() {
k.SetBacklight(ShortcutKey.START_MP1, !State.MP1_HasClearance ? k.IsKeyPressed(ShortcutKey.START_MP1) ? Xbe24Keyboard.Orange : Xbe24Keyboard.LightRed : State.MP1_IsActive ? Xbe24Keyboard.Green : Xbe24Keyboard.LightGreen); if (Plc == null) throw new Exception();
} await Plc.TestConnection();
k.SetBacklight(ShortcutKey.STOP_MP2, pumpHighlightOff.GetValueOrDefault(Graph.MP2) ? Xbe24Keyboard.Orange : !State.MP2_IsActive ? Xbe24Keyboard.Red : Xbe24Keyboard.LightRed); Actuators = await Plc.ReadOutputs();
k.SetBacklight(ShortcutKey.STOP_MP1, pumpHighlightOff.GetValueOrDefault(Graph.MP1) ? Xbe24Keyboard.Orange : !State.MP1_IsActive ? Xbe24Keyboard.Red : Xbe24Keyboard.LightRed); BackgroundThread = new Thread(new ParameterizedThreadStart(BackgroundLoop));
BackgroundThread.Start();
} }
public void Dispose() { public void Dispose() {
IsRunning = false; IsRunning = false;
ReconnectThread?.Join(); BackgroundThread?.Join();
PlcThread?.Join();
Plc?.Dispose(); Plc?.Dispose();
Keyboard?.Dispose();
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
protected async void PlcLoop(object? parameters) { protected async void BackgroundLoop(object? parameters) {
App.MainDispatcher?.Invoke(InitializePaths);
while (IsRunning && Plc != null) { while (IsRunning && Plc != null) {
var start = DateTime.Now;
try { try {
await Plc.Update(); if (ActuatorsChanged) {
LastRttNs = (long)(DateTime.Now - start).TotalNanoseconds; await Plc.WriteOutputs(Actuators);
ActuatorsChanged = false;
}
Sensors = await Plc.ReadInputs();
RaiseUpdateEvent(new()); RaiseUpdateEvent(new());
} catch (Exception exc) { } catch (Exception exc) {
LastRttNs = null;
var str = "Bei der SPS ist ein Fehler aufgetreten:\n\n" + exc.Message; var str = "Bei der SPS ist ein Fehler aufgetreten:\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message; if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "SPS-Fehler", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(str, "SPS-Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
if (exc is TimeoutException or IOException) { if (exc is TimeoutException or IOException) {
PlcConnectionError = $"{exc.GetType().Name}: {exc.Message}";
Plc?.Dispose(); Plc?.Dispose();
Plc = null; Plc = null;
IsRunning = false;
} else { } else {
await Task.Delay(1000); await Task.Delay(1000);
} }
} }
} }
if (Plc != null) {
try {
await Plc.Shutdown();
} catch (Exception exc) {
var str = "Beim Schließen der Verbindung zur SPS ist ein Fehler aufgetreten:\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "SPS-Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
RaiseUpdateEvent(new());
}
public void InitializePaths() {
var add = ReconstructSelectedPaths().ToList();
var remove = Graph.SelectedPaths.ToList();
foreach (var p in remove) Graph.RemovePath(p);
for (int i = 1; i <= 57; i++) {
App.Plant?.Plc?.CloseV(i);
}
foreach (var p in add) {
if (p.InnerValves.Any(v => v.IsLocked)) continue;
Graph.AddPath(p);
}
}
public IEnumerable<Pipeline.Path> ReconstructSelectedPaths() {
if (App.Plant is not PamhagenPlant p) yield break;
var sources = Graph.Nodes.Where(n => n is ISource).ToList();
var sinks = Graph.Nodes.Where(n => n is ISink).ToList();
foreach (var src in sources) {
var paths = new List<Pipeline.Path>();
foreach (var sink in sinks) {
paths.AddRange(Graph.GetPaths(src, sink, null, (n) => n is not ISink && (n is not ControlledValve v || State.Valve_WantOpen(v.Nr))));
}
if (paths.Count == 1)
yield return paths.First();
}
}
public void FastSelect(ISource source, INode? intermediate, ISink sink) {
if (Graph.SelectedPaths.Select(p => (Pipeline.Path?)p).FirstOrDefault(p => p?.Start == source, null) is Pipeline.Path p1) {
if (p1.HasActivePump)
throw new NoClearanceException("Pumpe aktiv");
Graph.RemovePath(p1);
if (p1.End == sink && (intermediate == null || p1.Contains(intermediate)))
return;
}
if (Graph.GetFreePath(intermediate == null ? [source, sink] : [source, intermediate, sink]) is Pipeline.Path p2) {
Graph.AddPath(p2);
}
}
private void Keyboard_KeyDown(object? sender, int keyIndex) {
if (Plc == null || App.Config.MapKey(keyIndex) is not ShortcutKey key) return;
try { try {
if (key == ShortcutKey.START_REBLER) { if (Plc != null)
Plc.StartRebler(); await Plc.WriteOutputs(new() { Valves = Actuators.Valves });
} else if (key == ShortcutKey.STOP_REBLER) { } catch (Exception exc) {
Plc.StopRebler(); var str = "Beim Schließen der Verbindung zur SPS ist ein Fehler aufgetreten:\n\n" + exc.Message;
} else if (key == ShortcutKey.START_TROUGH_AUGER) { if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
Plc.StartTroughAuger(); MessageBox.Show(str, "SPS-Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
} else if (key == ShortcutKey.STOP_TROUGH_AUGER) {
Plc.StopTroughAuger();
} else if (key == ShortcutKey.SELECT_MW2) {
Plc.SelectMW2();
} else if (key == ShortcutKey.SELECT_MW1) {
Plc.SelectMW1();
} else if (key == ShortcutKey.START_MP2) {
Plc.StartMP2Forward();
} else if (key == ShortcutKey.STOP_MP2) {
Plc.StopMP2();
} else if (key == ShortcutKey.START_MP1) {
Plc.StartMP1Forward();
} else if (key == ShortcutKey.STOP_MP1) {
Plc.StopMP1();
} else if (Graph.PathShortcuts.TryGetValue(key, out var path)) {
FastSelect(path.Source, path.Intermediate, path.Sink);
} else {
return;
}
RaiseUpdateEvent(new());
} catch (NoClearanceException) {
// Keyboard?.SetBacklight(keyIndex, Color.FromRgb(255, 0, 0));
} catch (Exception) {
// ignore
} }
} }
private void Keyboard_KeysChanged(object? sender, Xbe24Keyboard.KeyState state) { protected bool HasWTClearanceAA =>
UpdateKeyboardBacklights(this, new()); (AreValvesOpen(12) && AreValvesClosed(15, 16, 20)) || // bypass
(AreValvesOpen(15, 18, 16) && AreValvesClosed(11, 12, 14, 17, 19, 20)) || // WT1
(AreValvesOpen(15, 17, 20) && AreValvesClosed(11, 12, 18, 19, 21)) || // WT2
(AreValvesOpen(15, 18, 19, 20) && AreValvesClosed(11, 12, 14, 16, 17, 21)) || // WT1+WT2
false;
protected bool HasWTClearanceAB =>
(AreValvesOpen(15, 18, 14) && AreValvesClosed(11, 12, 13, 16, 17, 19, 21)) || // WT1
(AreValvesOpen(15, 17, 21) && AreValvesClosed(11, 12, 13, 18, 19, 20)) || // WT2
(AreValvesOpen(15, 18, 19, 21) && AreValvesClosed(11, 12, 13, 14, 16, 17, 20)) || // WT1+WT2
false;
protected bool HasWTClearanceBA =>
(AreValvesOpen(11, 18, 16) && AreValvesClosed(15, 12, 13, 14, 17, 19, 20)) || // WT1
(AreValvesOpen(11, 17, 20) && AreValvesClosed(15, 12, 13, 18, 19, 21)) || // WT2
(AreValvesOpen(11, 18, 19, 20) && AreValvesClosed(15, 12, 13, 14, 16, 17, 21)) || // WT1+WT2
false;
protected bool HasWTClearanceBB =>
(AreValvesOpen(13) && AreValvesClosed(11, 14, 21)) || // bypass
(AreValvesOpen(11, 18, 14) && AreValvesClosed(15, 13, 16, 17, 19, 21)) || // WT1
(AreValvesOpen(11, 17, 21) && AreValvesClosed(15, 13, 18, 19, 20)) || // WT2
(AreValvesOpen(11, 18, 19, 21) && AreValvesClosed(15, 13, 14, 16, 17, 20)) || // WT1+WT2
false;
protected bool HasT1ClearanceA => AreValvesOpen(22) && AreValvesClosed(24, 26, 28);
protected bool HasT1ClearanceB => AreValvesOpen(23) && AreValvesClosed(25, 27, 29);
protected bool HasT2ClearanceA => AreValvesOpen(24) && AreValvesClosed(22, 26, 28);
protected bool HasT2ClearanceB => AreValvesOpen(25) && AreValvesClosed(23, 27, 29);
protected bool HasT3ClearanceA => AreValvesOpen(26) && AreValvesClosed(22, 24, 28);
protected bool HasT3ClearanceB => AreValvesOpen(27) && AreValvesClosed(23, 25, 29);
protected bool HasT4ClearanceA => AreValvesOpen(28, 30) && AreValvesClosed(22, 24, 26, 32, 34);
protected bool HasT4ClearanceB => AreValvesOpen(29, 31) && AreValvesClosed(23, 25, 27, 33, 35);
protected bool HasT5ClearanceA => AreValvesOpen(28, 32) && AreValvesClosed(22, 24, 26, 30, 34);
protected bool HasT5ClearanceB => AreValvesOpen(29, 33) && AreValvesClosed(23, 25, 27, 31, 35);
protected bool HasT6ClearanceA => AreValvesOpen(28, 34, 37) && AreValvesClosed(22, 24, 26, 30, 32, 39, 41, 43);
protected bool HasT6ClearanceB => AreValvesOpen(29, 35, 36) && AreValvesClosed(23, 25, 27, 31, 33, 38, 40, 42);
protected bool HasT7ClearanceA => AreValvesOpen(28, 34, 39) && AreValvesClosed(22, 24, 26, 30, 32, 37, 41, 43);
protected bool HasT7ClearanceB => AreValvesOpen(29, 35, 38) && AreValvesClosed(23, 25, 27, 31, 33, 36, 40, 42);
protected bool HasT8ClearanceA => AreValvesOpen(28, 34, 41) && AreValvesClosed(22, 24, 26, 30, 32, 37, 39, 43);
protected bool HasT8ClearanceB => AreValvesOpen(29, 35, 40) && AreValvesClosed(23, 25, 27, 31, 33, 36, 38, 42);
protected bool HasT9ClearanceA => AreValvesOpen(28, 34, 43) && AreValvesClosed(22, 24, 26, 30, 32, 37, 39, 41);
protected bool HasT9ClearanceB => AreValvesOpen(29, 35, 42) && AreValvesClosed(23, 25, 27, 31, 33, 36, 38, 40);
protected bool HasClearanceTank1(bool a, bool b) =>
(a && ((HasWTClearanceAA && HasT1ClearanceA) || (HasWTClearanceAB && HasT1ClearanceB))) ||
(b && ((HasWTClearanceBA && HasT1ClearanceA) || (HasWTClearanceBB && HasT1ClearanceB)));
protected bool HasClearanceTank2(bool a, bool b) =>
(a && ((HasWTClearanceAA && HasT2ClearanceA) || (HasWTClearanceAB && HasT2ClearanceB))) ||
(b && ((HasWTClearanceBA && HasT2ClearanceA) || (HasWTClearanceBB && HasT2ClearanceB)));
protected bool HasClearanceTank3(bool a, bool b) =>
(a && ((HasWTClearanceAA && HasT3ClearanceA) || (HasWTClearanceAB && HasT3ClearanceB))) ||
(b && ((HasWTClearanceBA && HasT3ClearanceA) || (HasWTClearanceBB && HasT3ClearanceB)));
protected bool HasClearanceTank4(bool a, bool b) =>
(a && ((HasWTClearanceAA && HasT4ClearanceA) || (HasWTClearanceAB && HasT4ClearanceB))) ||
(b && ((HasWTClearanceBA && HasT4ClearanceA) || (HasWTClearanceBB && HasT4ClearanceB)));
protected bool HasClearanceTank5(bool a, bool b) =>
(a && ((HasWTClearanceAA && HasT5ClearanceA) || (HasWTClearanceAB && HasT5ClearanceB))) ||
(b && ((HasWTClearanceBA && HasT5ClearanceA) || (HasWTClearanceBB && HasT5ClearanceB)));
protected bool HasClearanceTank6(bool a, bool b) =>
(a && ((HasWTClearanceAA && HasT6ClearanceA) || (HasWTClearanceAB && HasT6ClearanceB))) ||
(b && ((HasWTClearanceBA && HasT6ClearanceA) || (HasWTClearanceBB && HasT6ClearanceB)));
protected bool HasClearanceTank7(bool a, bool b) =>
(a && ((HasWTClearanceAA && HasT7ClearanceA) || (HasWTClearanceAB && HasT7ClearanceB))) ||
(b && ((HasWTClearanceBA && HasT7ClearanceA) || (HasWTClearanceBB && HasT7ClearanceB)));
protected bool HasClearanceTank8(bool a, bool b) =>
(a && ((HasWTClearanceAA && HasT8ClearanceA) || (HasWTClearanceAB && HasT8ClearanceB))) ||
(b && ((HasWTClearanceBA && HasT8ClearanceA) || (HasWTClearanceBB && HasT8ClearanceB)));
protected bool HasClearanceTank9(bool a, bool b) =>
(a && ((HasWTClearanceAA && HasT9ClearanceA) || (HasWTClearanceAB && HasT9ClearanceB))) ||
(b && ((HasWTClearanceBA && HasT9ClearanceA) || (HasWTClearanceBB && HasT9ClearanceB)));
public bool MP1HasClearance => (OverrideDryRunClearances || FillLevelMW1 != FillState.Empty) && (
OverridePathClearances ||
(MP1HasClearancePress1 && Press1HasClearance) ||
(MP1HasClearancePress2 && Press2HasClearance) ||
(HasClearanceTank1(MP1HasClearanceTanksA, MP1HasClearanceTanksB) && FillLevelT1 != FillState.Full) ||
(HasClearanceTank2(MP1HasClearanceTanksA, MP1HasClearanceTanksB) && FillLevelT2 != FillState.Full) ||
(HasClearanceTank3(MP1HasClearanceTanksA, MP1HasClearanceTanksB) && FillLevelT3 != FillState.Full) ||
(HasClearanceTank4(MP1HasClearanceTanksA, MP1HasClearanceTanksB) && FillLevelT4 != FillState.Full) ||
(HasClearanceTank5(MP1HasClearanceTanksA, MP1HasClearanceTanksB) && FillLevelT5 != FillState.Full) ||
(HasClearanceTank6(MP1HasClearanceTanksA, MP1HasClearanceTanksB) && FillLevelT6 != FillState.Full) ||
(HasClearanceTank7(MP1HasClearanceTanksA, MP1HasClearanceTanksB) && FillLevelT7 != FillState.Full) ||
(HasClearanceTank8(MP1HasClearanceTanksA, MP1HasClearanceTanksB) && FillLevelT8 != FillState.Full) ||
(HasClearanceTank9(MP1HasClearanceTanksA, MP1HasClearanceTanksB) && FillLevelT9 != FillState.Full) ||
false);
protected bool MP1HasClearancePress1 =>
(AreValvesOpen(1, 6, 51) && AreValvesClosed(3, 4, 5, 50, 52, 53, 55)) ||
(AreValvesOpen(4, 8, 52) && AreValvesClosed(1, 2, 7, 50, 51, 53, 56));
protected bool MP1HasClearancePress2 =>
(AreValvesOpen(1, 6, 55) && AreValvesClosed(3, 4, 5, 51, 54, 56, 57)) ||
(AreValvesOpen(4, 8, 56) && AreValvesClosed(1, 2, 7, 52, 54, 55, 57));
protected bool MP1HasClearanceTanksA => AreValvesOpen(1, 5) && AreValvesClosed(3, 4, 6);
protected bool MP1HasClearanceTanksB => AreValvesOpen(4, 7, 9) && AreValvesClosed(1, 2, 8, 10);
public MotorState MP1 {
get => Actuators.MP1;
set => Actuators.MP1 = (value != MotorState.Halt && value != MotorState.Forward && value != MotorState.Backward) ? throw new ArgumentException("Invalid state for MP1") : value;
} }
public void OpenV(int n) => Plc?.OpenV(n); public bool MP2HasClearance => (OverrideDryRunClearances || FillLevelMW2 != FillState.Empty) && (
public void CloseV(int n) => Plc?.CloseV(n); OverridePathClearances ||
public void SelectMW1() => Plc?.SelectMW1(); (MP2HasClearancePress1 && Press1HasClearance) ||
public void SelectMW2() => Plc?.SelectMW2(); (MP2HasClearancePress2 && Press2HasClearance) ||
public void StartTroughAuger() => Plc?.StartTroughAuger(); (HasClearanceTank1(MP2HasClearanceTanksA, MP2HasClearanceTanksB) && FillLevelT1 != FillState.Full) ||
public void StopTroughAuger() => Plc?.StopTroughAuger(); (HasClearanceTank2(MP2HasClearanceTanksA, MP2HasClearanceTanksB) && FillLevelT2 != FillState.Full) ||
public void StartRebler() => Plc?.StartRebler(); (HasClearanceTank3(MP2HasClearanceTanksA, MP2HasClearanceTanksB) && FillLevelT3 != FillState.Full) ||
public void StopRebler() => Plc?.StopRebler(); (HasClearanceTank4(MP2HasClearanceTanksA, MP2HasClearanceTanksB) && FillLevelT4 != FillState.Full) ||
public void StartStirrerMW1() => Plc?.StartStirrerMW1(); (HasClearanceTank5(MP2HasClearanceTanksA, MP2HasClearanceTanksB) && FillLevelT5 != FillState.Full) ||
public void StopStirrerMW1() => Plc?.StopStirrerMW1(); (HasClearanceTank6(MP2HasClearanceTanksA, MP2HasClearanceTanksB) && FillLevelT6 != FillState.Full) ||
public void StartStirrerMW2() => Plc?.StartStirrerMW2(); (HasClearanceTank7(MP2HasClearanceTanksA, MP2HasClearanceTanksB) && FillLevelT7 != FillState.Full) ||
public void StopStirrerMW2() => Plc?.StopStirrerMW2(); (HasClearanceTank8(MP2HasClearanceTanksA, MP2HasClearanceTanksB) && FillLevelT8 != FillState.Full) ||
public void StartAuger1() => Plc?.StartAuger1(); (HasClearanceTank9(MP2HasClearanceTanksA, MP2HasClearanceTanksB) && FillLevelT9 != FillState.Full) ||
public void StopAuger1() => Plc?.StopAuger1(); false);
public void StartAuger2() => Plc?.StartAuger2(); protected bool MP2HasClearancePress1 =>
public void StopAuger2() => Plc?.StopAuger2(); (AreValvesOpen(2, 8, 52) && AreValvesClosed(3, 4, 7, 50, 51, 53, 56)) ||
public void StartAuger3() => Plc?.StartAuger3(); (AreValvesOpen(3, 6, 51) && AreValvesClosed(1, 2, 5, 50, 52, 53, 55));
public void StopAuger3() => Plc?.StopAuger3(); protected bool MP2HasClearancePress2 =>
public void StartMP1Forward() => Plc?.StartMP1Forward(); (AreValvesOpen(2, 8, 56) && AreValvesClosed(3, 4, 7, 52, 54, 55, 57)) ||
public void StartMP1Backward() => Plc?.StartMP1Backward(); (AreValvesOpen(3, 6, 55) && AreValvesClosed(2, 1, 5, 51, 54, 56, 57));
public void StopMP1() => Plc?.StopMP1(); protected bool MP2HasClearanceTanksA => AreValvesOpen(3, 5) && AreValvesClosed(1, 2, 6);
public void StartMP2Forward() => Plc?.StartMP2Forward(); protected bool MP2HasClearanceTanksB => AreValvesOpen(2, 7, 9) && AreValvesClosed(3, 4, 8, 10);
public void StartMP2Backward() => Plc?.StartMP2Backward();
public void StopMP2() => Plc?.StopMP2(); public MotorState MP2 {
public void EnableP12() => Plc?.EnableP12(); get => Actuators.MP2;
public void DisableP12() => Plc?.DisableP12(); set => Actuators.MP2 = (value != MotorState.Halt && value != MotorState.Forward && value != MotorState.Backward) ? throw new ArgumentException("Invalid state for MP2") : value;
public void EnableP3() => Plc?.EnableP3(); }
public void DisableP3() => Plc?.DisableP3();
public void EnableP4() => Plc?.EnableP4(); public bool P12HasClearance => (OverrideDryRunClearances || FillLevelT1 != FillState.Empty || FillLevelT2 != FillState.Empty) && (
public void DisableP4() => Plc?.DisableP4(); OverridePathClearances ||
public void EnableP5() => Plc?.EnableP5(); (P12HasClearancePress1 && Press1HasClearance) ||
public void DisableP5() => Plc?.DisableP5(); (P12HasClearancePress2 && Press2HasClearance) ||
public void EnableP6() => Plc?.EnableP6(); (HasClearanceTank1(false, P12HasClearanceTanksB) && FillLevelT1 != FillState.Full) ||
public void DisableP6() => Plc?.DisableP6(); (HasClearanceTank2(false, P12HasClearanceTanksB) && FillLevelT2 != FillState.Full) ||
(HasClearanceTank3(false, P12HasClearanceTanksB) && FillLevelT3 != FillState.Full) ||
(HasClearanceTank4(false, P12HasClearanceTanksB) && FillLevelT4 != FillState.Full) ||
(HasClearanceTank5(false, P12HasClearanceTanksB) && FillLevelT5 != FillState.Full) ||
(HasClearanceTank6(false, P12HasClearanceTanksB) && FillLevelT6 != FillState.Full) ||
(HasClearanceTank7(false, P12HasClearanceTanksB) && FillLevelT7 != FillState.Full) ||
(HasClearanceTank8(false, P12HasClearanceTanksB) && FillLevelT8 != FillState.Full) ||
(HasClearanceTank9(false, P12HasClearanceTanksB) && FillLevelT9 != FillState.Full) ||
false);
protected bool P12HasClearancePress1 => AreValvesOpen(44, 46, 49, 50) && AreValvesClosed(10, 45, 47, 48, 54, 51, 52, 53);
protected bool P12HasClearancePress2 => AreValvesOpen(44, 46, 49, 54) && AreValvesClosed(10, 45, 47, 48, 50, 55, 56, 57);
protected bool P12HasClearanceTanksB => AreValvesOpen(44, 10) && AreValvesClosed(9, 45, 46);
public MotorState P12 {
get => Actuators.P12;
set => Actuators.P12 = (value != MotorState.Halt && value != MotorState.Forward) ? throw new ArgumentException("Invalid state for P12") : value;
}
public bool P3HasClearance => (OverrideDryRunClearances || FillLevelT3 != FillState.Empty) && (
OverridePathClearances ||
(P3HasClearancePress1 && Press1HasClearance) ||
(P3HasClearancePress2 && Press2HasClearance) ||
(HasClearanceTank1(false, P3HasClearanceTanksB) && FillLevelT1 != FillState.Full) ||
(HasClearanceTank2(false, P3HasClearanceTanksB) && FillLevelT2 != FillState.Full) ||
(HasClearanceTank3(false, P3HasClearanceTanksB) && FillLevelT3 != FillState.Full) ||
(HasClearanceTank4(false, P3HasClearanceTanksB) && FillLevelT4 != FillState.Full) ||
(HasClearanceTank5(false, P3HasClearanceTanksB) && FillLevelT5 != FillState.Full) ||
(HasClearanceTank6(false, P3HasClearanceTanksB) && FillLevelT6 != FillState.Full) ||
(HasClearanceTank7(false, P3HasClearanceTanksB) && FillLevelT7 != FillState.Full) ||
(HasClearanceTank8(false, P3HasClearanceTanksB) && FillLevelT8 != FillState.Full) ||
(HasClearanceTank9(false, P3HasClearanceTanksB) && FillLevelT9 != FillState.Full) ||
false);
protected bool P3HasClearancePress1 => AreValvesOpen(45, 46, 49, 50) && AreValvesClosed(10, 44, 47, 48, 51, 52, 53, 54);
protected bool P3HasClearancePress2 => AreValvesOpen(45, 46, 49, 54) && AreValvesClosed(10, 44, 47, 48, 50, 55, 56, 57);
protected bool P3HasClearanceTanksB => AreValvesOpen(45, 10) && AreValvesClosed(9, 44, 46);
public MotorState P3 {
get => Actuators.P3;
set => Actuators.P3 = (value != MotorState.Halt && value != MotorState.Forward) ? throw new ArgumentException("Invalid state for P3") : value;
}
public bool P4HasClearance => (OverrideDryRunClearances || FillLevelT4 != FillState.Empty) && (
OverridePathClearances ||
(P4HasClearancePress1 && Press1HasClearance) ||
(P4HasClearancePress2 && Press2HasClearance) ||
(HasClearanceTank1(false, P4HasClearanceTanksB) && FillLevelT1 != FillState.Full) ||
(HasClearanceTank2(false, P4HasClearanceTanksB) && FillLevelT2 != FillState.Full) ||
(HasClearanceTank3(false, P4HasClearanceTanksB) && FillLevelT3 != FillState.Full) ||
(HasClearanceTank4(false, P4HasClearanceTanksB) && FillLevelT4 != FillState.Full) ||
(HasClearanceTank5(false, P4HasClearanceTanksB) && FillLevelT5 != FillState.Full) ||
(HasClearanceTank6(false, P4HasClearanceTanksB) && FillLevelT6 != FillState.Full) ||
(HasClearanceTank7(false, P4HasClearanceTanksB) && FillLevelT7 != FillState.Full) ||
(HasClearanceTank8(false, P4HasClearanceTanksB) && FillLevelT8 != FillState.Full) ||
(HasClearanceTank9(false, P4HasClearanceTanksB) && FillLevelT9 != FillState.Full) ||
false);
protected bool P4HasClearancePress1 => AreValvesOpen(47, 49, 50) && AreValvesClosed(46, 48, 51, 52, 53, 54);
protected bool P4HasClearancePress2 => AreValvesOpen(47, 49, 54) && AreValvesClosed(46, 48, 50, 55, 56, 57);
protected bool P4HasClearanceTanksB => AreValvesOpen(47, 46, 10) && AreValvesClosed(9, 44, 45, 48, 49);
public MotorState P4 {
get => Actuators.P4;
set => Actuators.P4 = (value != MotorState.Halt && value != MotorState.Forward) ? throw new ArgumentException("Invalid state for P4") : value;
}
public bool P5HasClearance => (OverrideDryRunClearances || FillLevelT5 != FillState.Empty) && (
OverridePathClearances ||
(P5HasClearancePress1 && Press1HasClearance) ||
(P5HasClearancePress2 && Press2HasClearance) ||
(HasClearanceTank1(false, P5HasClearanceTanksB) && FillLevelT1 != FillState.Full) ||
(HasClearanceTank2(false, P5HasClearanceTanksB) && FillLevelT2 != FillState.Full) ||
(HasClearanceTank3(false, P5HasClearanceTanksB) && FillLevelT3 != FillState.Full) ||
(HasClearanceTank4(false, P5HasClearanceTanksB) && FillLevelT4 != FillState.Full) ||
(HasClearanceTank5(false, P5HasClearanceTanksB) && FillLevelT5 != FillState.Full) ||
(HasClearanceTank6(false, P5HasClearanceTanksB) && FillLevelT6 != FillState.Full) ||
(HasClearanceTank7(false, P5HasClearanceTanksB) && FillLevelT7 != FillState.Full) ||
(HasClearanceTank8(false, P5HasClearanceTanksB) && FillLevelT8 != FillState.Full) ||
(HasClearanceTank9(false, P5HasClearanceTanksB) && FillLevelT9 != FillState.Full) ||
false);
protected bool P5HasClearancePress1 => AreValvesOpen(48, 49, 50) && AreValvesClosed(46, 47, 51, 52, 53, 54);
protected bool P5HasClearancePress2 => AreValvesOpen(48, 49, 54) && AreValvesClosed(46, 47, 50, 55, 56, 57);
protected bool P5HasClearanceTanksB => AreValvesOpen(48, 46, 10) && AreValvesClosed(9, 44, 45, 47, 49);
public MotorState P5 {
get => Actuators.P5;
set => Actuators.P5 = (value != MotorState.Halt && value != MotorState.Forward) ? throw new ArgumentException("Invalid state for P5") : value;
}
public bool P6HasClearance => OverridePathClearances ||
(AreValvesOpen(53) && AreValvesClosed(50, 51, 52, 57) && Press1HasClearance) ||
(AreValvesOpen(57) && AreValvesClosed(53, 54, 55, 56) && Press2HasClearance);
public MotorState P6 {
get => Actuators.P6;
set => Actuators.P6 = (value != MotorState.Halt && value != MotorState.Forward) ? throw new ArgumentException("Invalid state for P6") : value;
}
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);
ActuatorsChanged = true;
}
public void SelectMW1() {
Actuators.SetV(58, true);
ActuatorsChanged = true;
}
public void SelectMW2() {
Actuators.SetV(58, false);
ActuatorsChanged = true;
}
public void StartTroughAuger() {
if (!ManualControlMode && (!IsReblerActive || (IsMWSelected && FillLevelMW1 == FillState.Full) || (IsMW2Selected && FillLevelMW2 == FillState.Full)))
throw new NoClearanceException("Schnecke Auffangwanne hat keine Freigabe");
Actuators.TroughAuger = true;
ActuatorsChanged = true;
}
public void StopTroughAuger() {
Actuators.TroughAuger = false;
ActuatorsChanged = true;
}
public void StartRebler() {
Actuators.Rebler = true;
if (!ManualControlMode) StartAuger1();
ActuatorsChanged = true;
}
public void StopRebler() {
if (!ManualControlMode) StopTroughAuger();
Actuators.Rebler = false;
ActuatorsChanged = true;
}
public void StartStirrerMW1() {
Actuators.StirrerMW1 = true;
ActuatorsChanged = true;
}
public void StopStirrerMW1() {
Actuators.StirrerMW1 = false;
ActuatorsChanged = true;
}
public void StartStirrerMW2() {
Actuators.StirrerMW2 = true;
ActuatorsChanged = true;
}
public void StopStirrerMW2() {
Actuators.StirrerMW2 = false;
ActuatorsChanged = true;
}
public void StartAuger1() {
Actuators.Auger1 = true;
if (!ManualControlMode) StartAuger2();
ActuatorsChanged = true;
}
public void StopAuger1() {
if (!ManualControlMode && IsReblerActive) throw new NoClearanceException("Rebler aktiv");
Actuators.Auger1 = false;
ActuatorsChanged = true;
}
public void StartAuger2() {
Actuators.Auger2 = true;
if (!ManualControlMode) StartAuger3();
ActuatorsChanged = true;
}
public void StopAuger2() {
if (!ManualControlMode && IsPresseQuerSchneckeActive) throw new NoClearanceException("Presse-Querschnecke aktiv");
if (!ManualControlMode && IsAuger1Active) throw new NoClearanceException("Förderschnecke 1 aktiv");
Actuators.Auger2 = false;
ActuatorsChanged = true;
}
public void StartAuger3() {
Actuators.Auger3 = true;
ActuatorsChanged = true;
}
public void StopAuger3() {
if (!ManualControlMode && IsAuger2Active) throw new NoClearanceException("Förderschnecke 2 aktiv");
Actuators.Auger3 = false;
ActuatorsChanged = true;
}
public void StartGradationPump() {
Actuators.GradationPump = true;
ActuatorsChanged = true;
}
public void StopGradationPump() {
Actuators.GradationPump = false;
ActuatorsChanged = true;
}
public void StartMP1Forward() {
if (!MP1HasClearance) throw new NoClearanceException("MP1 hat keine Freigabe");
if (Actuators.MP1 == MotorState.Backward) throw new NoClearanceException("MP1 läuft bereits (zurück)");
Actuators.MP1 = MotorState.Forward;
if (FillLevelMW1 >= FillState.Half) StartStirrerMW1();
ActuatorsChanged = true;
}
public void StartMP1Backward() {
if (!MP1HasClearance) throw new NoClearanceException("MP1 hat keine Freigabe");
if (Actuators.MP1 == MotorState.Forward) throw new NoClearanceException("MP1 läuft bereits (vor)");
Actuators.MP1 = MotorState.Backward;
ActuatorsChanged = true;
}
public void StopMP1() {
Actuators.MP1 = MotorState.Halt;
if (!ManualControlMode) StopStirrerMW1();
ActuatorsChanged = true;
}
public void StartMP2Forward() {
if (!MP2HasClearance) throw new NoClearanceException("MP2 hat keine Freigabe");
if (Actuators.MP2 == MotorState.Backward) throw new NoClearanceException("MP2 läuft bereits (zurück)");
Actuators.MP2 = MotorState.Forward;
if (FillLevelMW2 >= FillState.Half) StartStirrerMW2();
ActuatorsChanged = true;
}
public void StartMP2Backward() {
if (!MP2HasClearance) throw new NoClearanceException("MP2 hat keine Freigabe");
if (Actuators.MP2 == MotorState.Forward) throw new NoClearanceException("MP2 läuft bereits (vor)");
Actuators.MP2 = MotorState.Backward;
ActuatorsChanged = true;
}
public void StopMP2() {
Actuators.MP2 = MotorState.Halt;
if (!ManualControlMode) StopStirrerMW1();
ActuatorsChanged = true;
}
public void StartP12() {
if (!P12HasClearance) throw new NoClearanceException("P1/2 hat keine Freigabe");
Actuators.P12 = MotorState.Forward;
ActuatorsChanged = true;
}
public void StopP12() {
Actuators.P12 = MotorState.Halt;
ActuatorsChanged = true;
}
public void StartP3() {
if (!P3HasClearance) throw new NoClearanceException("P3 hat keine Freigabe");
Actuators.P3 = MotorState.Forward;
ActuatorsChanged = true;
}
public void StopP3() {
Actuators.P3 = MotorState.Halt;
ActuatorsChanged = true;
}
public void StartP4() {
if (!P4HasClearance) throw new NoClearanceException("P4 hat keine Freigabe");
Actuators.P4 = MotorState.Forward;
ActuatorsChanged = true;
}
public void StopP4() {
Actuators.P4 = MotorState.Halt;
ActuatorsChanged = true;
}
public void StartP5() {
if (!P5HasClearance) throw new NoClearanceException("P5 hat keine Freigabe");
Actuators.P5 = MotorState.Forward;
ActuatorsChanged = true;
}
public void StopP5() {
Actuators.P5 = MotorState.Halt;
ActuatorsChanged = true;
}
public void StartP6() {
if (!P6HasClearance) throw new NoClearanceException("P6 hat keine Freigabe");
Actuators.P6 = MotorState.Forward;
ActuatorsChanged = true;
}
public void StopP6() {
Actuators.P6 = MotorState.Halt;
ActuatorsChanged = true;
}
} }
} }
@@ -1,18 +1,18 @@
using System.IO.Ports; using System.IO.Ports;
namespace PamhagenSysCtrl.Helpers.Plc { namespace PamhagenSysCtrl.Helpers {
public class LegacyPamhagenPlc : IDisposable { public class PamhagenPlc : IDisposable {
public record struct SensorStates( public record struct SensorStates(
long PressureSensors, long PressureSensors,
FillState FillLevelMW1, FillState FillingLevelMW1,
FillState FillLevelMW2, FillState FillingLevelMW2,
bool ClearancePress1, bool ClearancePress1,
bool ClearancePress2, bool ClearancePress2,
int TroughSelector, int TroughSelector,
double GradationOe, double GradationOe,
FillState[] FillLevelTanks, FillState[] FillingLevelTanks,
MotorState PressCrossAuger MotorState PresseQuerSchnecke
) { ) {
public readonly bool GetDS(int n) => (PressureSensors & (1L << (n - 1))) != 0; public readonly bool GetDS(int n) => (PressureSensors & (1L << (n - 1))) != 0;
} }
@@ -34,12 +34,8 @@ namespace PamhagenSysCtrl.Helpers.Plc {
protected readonly T1 Plc; protected readonly T1 Plc;
public string ComPort => Plc.PortName; public PamhagenPlc(string portName, Parity parity = Parity.Odd) {
public T1.Status LastStatus => Plc.LastStatus; Plc = new T1(portName, parity);
public T1Error LastError => Plc.LastError;
public LegacyPamhagenPlc(string comPort, Parity parity = Parity.Odd) {
Plc = new T1(comPort, parity);
} }
public void Dispose() { public void Dispose() {
@@ -47,28 +43,31 @@ namespace PamhagenSysCtrl.Helpers.Plc {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
protected static FillState Int3ToFillState(int n, bool bigEndian = true) { protected static FillState Int3ToFillState(int n) {
return return n switch {
(bigEndian && (n & 0x4) != 0) || (!bigEndian && (n & 0x1) != 0) ? FillState.Full : 7 => FillState.Full,
(bigEndian && (n & 0x2) != 0) || (!bigEndian && (n & 0x2) != 0) ? FillState.Half : 6 => FillState.Half,
(bigEndian && (n & 0x1) != 0) || (!bigEndian && (n & 0x4) != 0) ? FillState.Filled : 4 => FillState.Filled,
n == 0 ? FillState.Empty : 0 => FillState.Empty,
FillState.Invalid; _ => FillState.Invalid,
};
} }
protected static FillState Int2ToFillState(int n, bool bigEndian = true) { protected static FillState Int2ToFillState(int n) {
return return n switch {
(bigEndian && (n & 0x2) != 0) || (!bigEndian && (n & 0x1) != 0) ? FillState.Full : 2 => FillState.Full,
(bigEndian && (n & 0x1) != 0) || (!bigEndian && (n & 0x2) != 0) ? FillState.Filled : 0 => FillState.Filled,
n == 0 ? FillState.Empty : 1 => FillState.Empty,
FillState.Invalid; _ => FillState.Invalid,
};
} }
protected static FillState Int1ToFillState(int n) { protected static FillState Int1ToFillState(int n) {
return return n switch {
n == 1 ? FillState.Full : 1 => FillState.Full,
n == 0 ? FillState.Unknown : 0 => FillState.Unknown,
FillState.Invalid; _ => FillState.Invalid,
};
} }
protected static MotorState Int2ToPumpState(int n, bool inverse) { protected static MotorState Int2ToPumpState(int n, bool inverse) {
@@ -95,7 +94,7 @@ namespace PamhagenSysCtrl.Helpers.Plc {
throw new T1Exception($"Interner Fehler: {error} ({(int)error:0000})"); throw new T1Exception($"Interner Fehler: {error} ({(int)error:0000})");
} }
public async Task<(ushort RW010, ushort RW011, ushort RW012, ushort RW013, ushort RW014, ushort RW015, ushort RW016)> ReadInputRegisters() { protected 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
@@ -111,8 +110,8 @@ namespace PamhagenSysCtrl.Helpers.Plc {
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, false); // 111 011 001 000 var mw1 = Int3ToFillState((f1 ) & 0x7); // 111 011 001 000
var mw2 = Int3ToFillState((f1 >> 3) & 0x7, false); // 111 011 001 000 var mw2 = Int3ToFillState((f1 >> 3) & 0x7); // 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
@@ -130,11 +129,11 @@ namespace PamhagenSysCtrl.Helpers.Plc {
// -> 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 = Int1ToFillState(((f2 ) & 0x3) >> 1); // 1X 0X var t1 = Int2ToFillState((f2 ) & 0x3); // 10 01 00
var t2 = Int1ToFillState(((f2 >> 2) & 0x3) >> 1); // 1X 0X var t2 = Int2ToFillState((f2 >> 2) & 0x3); // 10 01 00
var t3 = Int1ToFillState(((f2 >> 4) & 0x3) >> 1); // 1X 0X var t3 = Int2ToFillState((f2 >> 4) & 0x3); // 10 01 00
var t4 = Int1ToFillState(((f2 >> 6) & 0x3) >> 1); // 1X 0X var t4 = Int2ToFillState((f2 >> 6) & 0x3); // 10 01 00
var t5 = Int1ToFillState(((f2 >> 8) & 0x3) >> 1); // 1X 0X var t5 = Int2ToFillState((f2 >> 8) & 0x3); // 10 01 00
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
+24 -38
View File
@@ -14,21 +14,17 @@ 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 (_storyboard == null) return; if (value) {
if (value && _storyboard.GetIsPaused()) { _storyboard?.Resume();
_storyboard.Resume(); } else {
} else if (!value && !_storyboard.GetIsPaused()) { _storyboard?.Pause();
_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;
@@ -39,51 +35,41 @@ 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);
_leftSlide = new DoubleAnimation { var slideLeft = 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(_leftSlide); _storyboard.Children.Add(slideLeft);
Storyboard.SetTargetProperty(_leftSlide, new PropertyPath(Canvas.LeftProperty)); Storyboard.SetTarget(slideLeft, _polyline);
_clipCanvas = new Canvas() { Storyboard.SetTargetProperty(slideLeft, new PropertyPath(Canvas.LeftProperty));
var clipCanvas = new Canvas() {
Width = Width,
Height = Height,
ClipToBounds = true, ClipToBounds = true,
RenderTransform = new RotateTransform(Angle, Width / 2, Height / 2),
}; };
_clipCanvas.Children.Add(_polyline); clipCanvas.SetValue(Canvas.TopProperty, CenterY - Height / 2);
canvas.Children.Add(_clipCanvas); clipCanvas.SetValue(Canvas.LeftProperty, CenterX - Width / 2);
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);
}
} }
} }
@@ -0,0 +1,70 @@
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;
}
}
}
@@ -1,51 +0,0 @@
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 = 6; public const double WIDTH = 30;
public const double HEIGHT = 24; public const double HEIGHT = 120;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -18,23 +18,22 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public bool IsActive => CallbackActive(); public bool IsActive => CallbackActive();
public bool IsAnimationActive { public bool IsAnimationActive {
get => !(_storyboard?.GetIsPaused() ?? true); get => _animationActive;
set { set {
if (_storyboard == null) return; _animationActive = value;
if (value && _storyboard.GetIsPaused()) { if (value) {
_storyboard.Resume(); _storyboard?.Resume();
} else if (!value && !_storyboard.GetIsPaused()) { } else {
_storyboard.Pause(); _storyboard?.Pause();
} }
} }
} }
protected Func<bool> CallbackActive; protected Func<bool> CallbackActive;
private Rectangle? _rect; private bool _animationActive;
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) {
@@ -48,63 +47,52 @@ 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);
_slideUp = new DoubleAnimation { var 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);
_clipCanvas = new Canvas() { Storyboard.SetTargetProperty(slideUp, new PropertyPath(Canvas.TopProperty));
var clipCanvas = new Canvas() {
Width = WIDTH - 15,
Height = HEIGHT - 8,
ClipToBounds = true, ClipToBounds = true,
}; };
_clipCanvas.Children.Add(_polyline); clipCanvas.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2 + 4);
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) {
@@ -114,11 +102,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 =
IsActive && isHovering && isPressed ? PamhagenBrushes.Red : IsAnimationActive && isHovering && isPressed ? PamhagenBrushes.Red :
IsActive && isHovering ? PamhagenBrushes.Red : IsAnimationActive && isHovering ? PamhagenBrushes.Red :
!IsActive && isHovering && isPressed ? PamhagenBrushes.Green : !IsAnimationActive && isHovering && isPressed ? PamhagenBrushes.Green :
!IsActive && isHovering ? PamhagenBrushes.Green : !IsAnimationActive && isHovering ? PamhagenBrushes.Green :
IsActive ? PamhagenBrushes.Green : IsAnimationActive ? 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 = 16; public const double WIDTH = 80;
public const double HEIGHT = 4; 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; }
@@ -29,9 +29,8 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
protected Func<bool> CallbackActive; protected Func<bool> CallbackActive;
private bool _animationActive; private bool _animationActive;
private Canvas? _canvas; private Shape? _inner;
private Rectangle? _inner; private Shape? _outer;
private Rectangle? _outer;
private Auger? _auger; private Auger? _auger;
private TextBlock? _text; private TextBlock? _text;
@@ -47,49 +46,41 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_canvas = new Canvas() { var c = new Canvas() {
RenderTransform = new RotateTransform(Angle), RenderTransform = new RotateTransform(Angle, WIDTH / 2, HEIGHT / 2),
}; };
_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);
_canvas.Children.Add(_outer); c.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2);
_canvas.Children.Add(_inner); _inner.SetValue(Canvas.TopProperty, 2.0);
canvas.Children.Add(_canvas); c.Children.Add(_outer);
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, 2.4, Angle); _auger = new Auger(CenterX, CenterY, WIDTH, 12, 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;
} }
+22 -47
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 = 30; public const double WIDTH = 150;
public const double HEIGHT = 12; public const double HEIGHT = 60;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -20,8 +20,9 @@ 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 && !_animationActive) { if (value) {
_doorTransform?.BeginAnimation( _doorTransform?.BeginAnimation(
RotateTransform.AngleProperty, RotateTransform.AngleProperty,
new DoubleAnimation { new DoubleAnimation {
@@ -29,7 +30,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 if (!value && _animationActive) { } else {
_doorTransform?.BeginAnimation( _doorTransform?.BeginAnimation(
RotateTransform.AngleProperty, RotateTransform.AngleProperty,
new DoubleAnimation { new DoubleAnimation {
@@ -38,23 +39,17 @@ 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 Polygon? _inner; private Shape? _inner;
private Polygon? _outer; private Shape? _outer;
private TextBlock? _text; private TextBlock? _text;
private Auger? _auger; private Auger? _auger;
private Line? _door; private Shape? _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) {
@@ -67,62 +62,42 @@ 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); _doorTransform = new RotateTransform(0, left, bottom - 24);
_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 - 2.2, CenterY + HEIGHT / 2 - 1.8, WIDTH - 5, 2.4); _auger = new(CenterX - 11, CenterY + HEIGHT / 2 - 9, WIDTH - 25, 12);
_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;
} }
@@ -1,29 +0,0 @@
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;
}
}
}
}
+21 -18
View File
@@ -7,40 +7,42 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
internal sealed class FlowArrows { internal sealed class FlowArrows {
public const double SPEED = 4; public const double SPEED = 20;
public const double SPACING = 3; public const double SPACING = 15;
public const double LENGTH = 1.4; public const double LENGTH = 7;
public const double WIDTH = 1.4; public const double WIDTH = 7;
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 { get; init; } public double Length => GetLength(_path[0], _path[1]) + GetLength(_path[1], _path[2]);
private FlowArrows(Point[] path) { private FlowArrows(Canvas canvas, 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, IEnumerable<Point> points) { public static FlowArrows Create(Canvas canvas, Line first, Line second) {
var arrows = new FlowArrows([.. points]); return new FlowArrows(canvas, [
canvas.Children.Add(arrows._layer); new Point(first.X1, first.Y1),
return arrows; new Point(first.X2, first.Y2),
new Point(second.X2, second.Y2),
]);
} }
public void Scale(double scale) { public static FlowArrows Create(Canvas canvas, Polyline path) {
_scale = scale; return new FlowArrows(canvas, [.. path.Points]);
} }
public void Update((bool TowardEnd, double PathOffset)? flow) { public void Update((bool TowardEnd, double PathOffset)? flow) {
@@ -50,6 +52,7 @@ 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;
@@ -66,7 +69,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;
@@ -142,9 +145,9 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
direction = v2; direction = v2;
} }
double ux = direction.X * _scale; double ux = direction.X;
double uy = direction.Y * _scale; double uy = direction.Y;
(arrow.RenderTransform as MatrixTransform)?.Matrix = new Matrix(ux, uy, -uy, ux, position.X * _scale, position.Y * _scale); (arrow.RenderTransform as MatrixTransform)?.Matrix = new Matrix(ux, uy, -uy, ux, position.X, position.Y);
} }
private void Stop() { private void Stop() {
+26 -79
View File
@@ -1,7 +1,5 @@
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 {
@@ -56,115 +54,64 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
return p; return p;
} }
public Path? GetPath(IEnumerable<INode> points, Func<INode, bool>? valid = null, Func<Path, int>? cost = null) { public Path? GetPath(IEnumerable<INode> points, Func<INode, bool>? valid = null) {
if (points.Count() < 2) return null; if (points.Count() < 2) return null;
var start = points.First(); var start = points.First();
List<Path> paths = [new(start, [])]; var path = new Path(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) {
var newPaths = new List<Path>(); return null;
foreach (var path in paths) { } else {
foreach (var subPath in GetPaths(start, end, visited: [.. path.Hops.Select(h => h.Node)], valid)) { path.Hops = [.. path.Hops, .. p.Hops];
newPaths.Add(new(path.Start, [.. path.Hops, .. subPath.Hops])); start = end;
}
} }
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 IEnumerable<Path> GetPaths(INode start, INode end, HashSet<INode>? visited = null, Func<INode, bool>? valid = null) { public Path? GetPath(INode start, INode end, Func<INode, bool>? valid = null, HashSet<INode>? visited = 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) {
yield return new(start, [(o, e)]); return new(start, [(o, e)]);
continue;
} else if (!valid(e) || visited.Contains(e)) { } else if (!valid(e) || visited.Contains(e)) {
continue; continue;
} }
foreach (var p in GetPaths(e, end, visited, valid)) { var p = GetPath(e, end, valid, visited);
yield return new(start, [(o, e), .. p.Hops]); if (p != null) {
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, double scale) { public void Draw(Canvas canvas) {
var bg = new Rectangle() { canvas.Children.Add(new Rectangle() {
Fill = Brushes.White, Fill = Brushes.White,
Width = 8000, Width = 5000,
Height = 8000, Height = 5000,
}; });
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 void Scale(double scale) { public ISet<INode> GetHover(double x, double y) {
foreach (var e in Edges) { return Nodes.Where(n => n.IsInside(x, y)).ToHashSet();
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(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 = 6; public const double WIDTH = 30;
public const double HEIGHT = 24; public const double HEIGHT = 120;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -28,30 +28,29 @@ 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,7 +14,6 @@ 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);
} }
} }
+50 -27
View File
@@ -15,15 +15,10 @@ 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; private Line? _outer1;
public double Y1 => Start.CenterY; private Line? _outer2;
public double X2 => Start.CenterX; private Line? _inner1;
public double Y2 => Orientation ? Sink.CenterY - 2 : Sink.TopY - 2; private Line? _inner2;
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) {
@@ -33,34 +28,62 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_outer = new Polyline() { var x1 = Start.CenterX;
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, Stroke = Brushes.Black,
StrokeStartLineCap = PenLineCap.Round, StrokeStartLineCap = PenLineCap.Round,
StrokeLineJoin = PenLineJoin.Round, StrokeEndLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Flat,
}; };
_inner = new Polyline() { _inner1 = new Line() {
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,
};
_outer2 = new Line() {
X1 = x2,
Y1 = y2,
X2 = x3,
Y2 = y3,
StrokeThickness = 10,
Stroke = Brushes.Black,
StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Flat, StrokeEndLineCap = PenLineCap.Flat,
}; };
canvas.Children.Add(_outer); _inner2 = new Line() {
canvas.Children.Add(_inner); X1 = x2,
_flowArrows = FlowArrows.Create(canvas, [new(X1, Y1), new(X2, Y2), new(X3, Y3)]); Y1 = y2,
} X2 = x3,
Y2 = y3,
public void Scale(double scale) { StrokeThickness = 6,
var border = Graph.ToBorder(scale); Stroke = Brushes.DarkGray,
_outer?.Points = [Graph.ToPx(X1, Y1, scale), Graph.ToPx(X2, Y2, scale), Graph.ToPx(X3, Y3, scale)]; StrokeStartLineCap = PenLineCap.Round,
_inner?.Points = [Graph.ToPx(X1, Y1, scale), Graph.ToPx(X2, Y2, scale), Graph.ToPx(X3, Y3, scale)]; StrokeEndLineCap = PenLineCap.Flat,
_inner?.StrokeThickness = Math.Round(1.2 * scale / 2) * 2; };
_outer?.StrokeThickness = Math.Round(1.2 * scale / 2) * 2 + border * 2; canvas.Children.Add(_outer1);
_flowArrows?.Scale(scale); canvas.Children.Add(_outer2);
canvas.Children.Add(_inner1);
canvas.Children.Add(_inner2);
_flowArrows = FlowArrows.Create(canvas, _inner1!, _inner2!);
} }
public void Update() { public void Update() {
_inner?.Stroke = Highlight ?? Brushes.DarkGray; _inner1?.Stroke = Highlight ?? Brushes.DarkGray;
_inner2?.Stroke = Highlight ?? Brushes.DarkGray;
_flowArrows?.Update(Flow); _flowArrows?.Update(Flow);
} }
} }
+52 -29
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 = 3.2; public const double POINT = 16;
public INode Start => Source; public INode Start => Source;
public INode End { get; set; } public INode End { get; set; }
@@ -17,17 +17,12 @@ 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 Polyline? _outer; private Line? _outer1;
private Polyline? _inner; private Line? _outer2;
private Line? _inner1;
private Line? _inner2;
public Outlet(ISource start, INode end) { public Outlet(ISource start, INode end) {
Source = start; Source = start;
@@ -36,41 +31,69 @@ 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,
}; };
_outer = new Polyline() { _outer1 = new Line() {
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,
}; };
_inner = new Polyline() { _inner1 = new Line() {
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,
}; };
canvas.Children.Add(_outer); _outer2 = new Line() {
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(_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);
_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() {
_inner?.Stroke = Highlight ?? Brushes.DarkGray; _inner1?.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);
} }
+4 -38
View File
@@ -1,43 +1,9 @@
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public record struct Path( public record struct Path(
INode Start, INode Start,
(IEdge Edge, INode Node)[] Hops IEnumerable<(IEdge Edge, INode Node)> Hops)
) { {
public readonly int Bends => Hops.Sum(h => (h.Node is not Valve && h.Node is not PipeJoin ? 1 : 0) + (h.Edge.Start.CenterX != h.Edge.End.CenterX && h.Edge.Start.CenterY != h.Edge.End.CenterY ? 1 : 0)); public readonly double 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);
}
}
} }
} }
+15 -20
View File
@@ -15,13 +15,6 @@ 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;
@@ -38,13 +31,23 @@ 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,
@@ -55,26 +58,18 @@ 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, [new(X1, Y1), new(X2, Y2), new(X3, Y3)]); _flowArrows = FlowArrows.Create(canvas, _inner);
}
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() {
+20 -23
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 = 3.6; public const double SIZE = 18;
public const double POINT = 0.8; public const double POINT = 4;
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 Polygon? _rect; private Shape? _rect;
public PipeJoin(double x, double y) { public PipeJoin(double x, double y) {
CenterX = x; CenterX = x;
@@ -26,22 +26,14 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_rect = new Polygon() { 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)));
Fill = Brushes.Black, 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)));
Stroke = 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)));
}; 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)));
canvas.Children.Add(_rect); 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)));
public void Scale(double scale) { 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 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)
@@ -78,10 +70,15 @@ 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?.Points = [.. points.Select(p => new Point(p.X * scale, p.Y * scale))]; _rect = new Polygon() {
_rect?.StrokeThickness = 2; Points = [..points],
_rect?.SetValue(Canvas.LeftProperty, (CenterX - SIZE / 2) * scale); Fill = Brushes.Black,
_rect?.SetValue(Canvas.TopProperty, (CenterY - SIZE / 2) * scale); Stroke = Brushes.Black,
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) {
+20 -20
View File
@@ -1,12 +1,13 @@
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 = 24; public const double DIAMETER = 120;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -23,8 +24,8 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
protected Func<FillState> CallbackFillState; protected Func<FillState> CallbackFillState;
private Polygon? _frame; private Shape? _frame;
private Ellipse? _circle; private Shape? _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) {
@@ -39,40 +40,39 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
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);
} }
+17 -24
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 = 4.8; 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; }
@@ -25,8 +25,8 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
protected Func<bool> CallbackClearance; protected Func<bool> CallbackClearance;
protected Func<Pump, bool> CallbackSelected; protected Func<Pump, bool> CallbackSelected;
private Ellipse? _circle; private Shape? _circle;
private Polyline? _triangle; private Shape? _triangle;
private TextBlock? _text; private TextBlock? _text;
public Pump(string label, double x, double y, Func<MotorState> cbState, 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) {
@@ -42,42 +42,35 @@ 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,
}; };
_triangle = new Polyline() { var dx = Math.Cos(Math.PI / 6) * (DIAMETER / 2 - 2);
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,
StrokeStartLineCap = PenLineCap.Square, StrokeThickness = 2,
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;
} }
+45 -49
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 = 24; public const double WIDTH = 120;
public const double HEIGHT = 12; public const double HEIGHT = 60;
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 Rectangle? _rect; private Shape? _rect;
private Polygon? _hopperOuter; private Shape? _hopperOuter;
private Polygon? _hopperInner; private Shape? _hopperInner;
private Polygon? _bottomInner; private Shape? _bottomInner;
private Polygon? _bottomOuter; private Shape? _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,56 +37,52 @@ 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,
}; };
_hopperOuter = new Polygon() {
Fill = Brushes.Black,
};
_hopperInner = new Polygon() {
Fill = Brushes.WhiteSmoke,
};
_bottomOuter = new Polygon() {
Fill = Brushes.Black,
};
_bottomInner = new Polygon() {
Fill = Brushes.WhiteSmoke,
};
_text = new() {
Text = Label,
TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};
canvas.Children.Add(_hopperOuter);
canvas.Children.Add(_bottomOuter);
canvas.Children.Add(_bottomInner);
canvas.Children.Add(_rect);
canvas.Children.Add(_hopperInner);
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 left = CenterX - WIDTH / 2;
var right = CenterX + WIDTH / 2; var right = CenterX + WIDTH / 2;
var top = CenterY - HEIGHT / 2; var top = CenterY - HEIGHT / 2;
var bottom = CenterY + HEIGHT / 2; var bottom = CenterY + HEIGHT / 2;
var hx = right - 4; var hx = right - 20;
var h = 3; var h = 15;
var w = 5; var w = 25;
_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)]; _hopperOuter = new Polygon() {
_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)]; Points = [new(hx - w, top - h), new(hx - w + 5, top + 2), new(hx + w - 5, top + 2), new(hx + w, top - h)],
_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)]; Fill = Brushes.Black,
_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)]; };
_hopperInner = new Polygon() {
_text?.FontSize = 12; 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)],
_text?.Width = WIDTH * scale; Fill = Brushes.WhiteSmoke,
_text?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale); };
_text?.SetValue(Canvas.TopProperty, (CenterY * scale) - 8); _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,
};
_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,
};
_text = new() {
Text = Label,
FontSize = 12,
Width = WIDTH,
TextAlignment = TextAlignment.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(_bottomOuter);
canvas.Children.Add(_rect);
canvas.Children.Add(_hopperInner);
canvas.Children.Add(_bottomInner);
canvas.Children.Add(_text);
} }
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
+10 -23
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 = 20; public const double WIDTH = 100;
public const double HEIGHT = 10; public const double HEIGHT = 50;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -34,15 +34,21 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_mainTransform = new RotateTransform(0); _mainTransform = new RotateTransform(0, CenterX, CenterY);
_shadowTransform = new RotateTransform(0); _shadowTransform = new RotateTransform(0, CenterX, CenterY);
_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,
@@ -52,25 +58,6 @@ 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;
} }
+11 -15
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 = 12; public const double WIDTH = 60;
public const double HEIGHT = 20; public const double HEIGHT = 100;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -40,31 +40,27 @@ 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 = 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;
} }
+14 -18
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 = 20; public const double WIDTH = 100;
public const double HEIGHT = 12; public const double HEIGHT = 60;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -35,37 +35,33 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
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);
} }
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) * scale);
_text?.SetValue(Canvas.TopProperty, (CenterY * scale) - 10);
}
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;
} }
+47 -105
View File
@@ -4,143 +4,85 @@ using System.Windows.Media;
using System.Windows.Shapes; using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public abstract class Valve : INode { public class Valve : INode {
public const double LENGTH = 4.8; public const double DIAMETER = 24;
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 abstract string Label { get; } public int Nr { get; set; }
public string Label => $"V{Nr}";
public Brush? Highlight { get; set; } public Brush? Highlight { get; set; }
public int Orientation { get; set; } public bool IsLocked { get; set; }
public bool IsOpen => CallbackIsOpen();
public bool IsOpen => CallbackIsOpen() == true; public bool WantOpen => CallbackWantOpen(this);
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;
protected Rectangle? _background; private Shape? _circle;
protected Polygon? _triangles; private TextBlock? _text;
protected Polyline? _topLine;
protected Polyline? _bottomLine;
protected ((Point Top, Point Bottom) Closed, (Point Top, Point Bottom) Open) _middlePoints;
protected TextBlock? _text;
public Valve(double x, double y, int orientation, Func<bool?> cbOpen) { public Valve(int nr, double x, double y, Func<bool> cbOpen, Func<Valve, bool> cbWant) {
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) {
_background = new Rectangle() { _circle = new Ellipse() {
Fill = Brushes.White, Height = DIAMETER,
}; Width = DIAMETER,
_triangles = new Polygon() {
Fill = Brushes.DarkGray, Fill = Brushes.DarkGray,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
}; };
_topLine = new Polyline() { _circle.SetValue(Canvas.LeftProperty, CenterX - DIAMETER / 2);
Stroke = Brushes.Black, _circle.SetValue(Canvas.TopProperty, CenterY - DIAMETER / 2);
};
_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,
}; };
canvas.Children.Add(_background); _text.SetValue(Canvas.LeftProperty, CenterX - DIAMETER / 2 + 0.5);
canvas.Children.Add(_triangles); _text.SetValue(Canvas.TopProperty, CenterY - DIAMETER / 2 + 5.5);
canvas.Children.Add(_topLine); canvas.Children.Add(_circle);
canvas.Children.Add(_bottomLine);
canvas.Children.Add(_text); canvas.Children.Add(_text);
} }
public void Scale(double scale) {
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 bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
if (Orientation == 0 || Orientation == 2) { return Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= DIAMETER / 2;
return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= LENGTH / 2;
} else {
return Math.Abs(x - CenterX) <= LENGTH / 2 && Math.Abs(y - CenterY) <= WIDTH / 2;
}
} }
public abstract void Update(bool isHovering, bool isPressed); public void Update(bool isHovering, bool isPressed) {
if (App.Plant?.ManualControlMode ?? false) {
_circle?.Fill =
!WantOpen && !IsOpen && !isHovering ? PamhagenBrushes.Red :
!WantOpen && !IsOpen ? PamhagenBrushes.Green :
!WantOpen && IsOpen ? PamhagenBrushes.DimRed :
WantOpen && !IsOpen ? PamhagenBrushes.DimGreen :
WantOpen && IsOpen && !isHovering ? PamhagenBrushes.Green :
WantOpen && IsOpen ? PamhagenBrushes.Red :
Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray);
} else {
_circle?.Fill =
IsLocked && !WantOpen && !IsOpen ? PamhagenBrushes.DimOrange :
IsLocked && !WantOpen && IsOpen ? PamhagenBrushes.Red :
IsLocked && WantOpen && !IsOpen ? PamhagenBrushes.Yellow :
IsLocked && WantOpen && IsOpen ? PamhagenBrushes.Green :
Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray);
}
}
} }
} }
-347
View File
@@ -1,347 +0,0 @@
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 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;
public PamhagenPlc(string comPort, int sensorDelayOpenMs, int sensorDelayCloseMs, Parity parity = Parity.Odd) {
Plc = new(comPort, parity);
ConsiderLastSensorsOpen = sensorDelayOpenMs / 125 + 1;
ConsiderLastSensorsClose = sensorDelayCloseMs / 125 + 1;
LastSensors = new LegacyPamhagenPlc.SensorStates[Math.Max(ConsiderLastSensorsOpen, ConsiderLastSensorsClose)];
}
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);
}
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;
State = new(LastSensors, this);
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.MP1_State == MotorState.Forward && State.MW1_FillLevel >= FillState.Half && !State.MW1_Stirrer_IsActive) {
StartStirrerMW1();
} else if (State.MW2_Stirrer_IsActive && (!State.MP1_IsActive || State.MW1_FillLevel < FillState.Half)) {
StopStirrerMW1();
}
if (State.MP2_State == MotorState.Forward && State.MW2_FillLevel >= FillState.Half && !State.MW2_Stirrer_IsActive) {
StartStirrerMW2();
} else if (State.MW2_Stirrer_IsActive && (!State.MP2_IsActive || State.MW2_FillLevel < FillState.Half)) {
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() {
Actuators.StirrerMW1 = true;
}
public void StopStirrerMW1() {
Actuators.StirrerMW1 = false;
}
public void StartStirrerMW2() {
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 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 (State.MW1_FillLevel >= FillState.Half) StartStirrerMW1();
}
public void StartMP1Backward() {
MP1_WantActive = false;
if (!State.MP1_HasClearance) throw new NoClearanceException("MP1 hat keine Freigabe");
if (Actuators.MP1 == MotorState.Forward) throw new NoClearanceException("MP1 läuft bereits (vor)");
Actuators.MP1 = MotorState.Backward;
}
public void StopMP1() {
MP1_WantActive = false;
Actuators.MP1 = MotorState.Halt;
if (!ManualControlMode) StopStirrerMW1();
}
public void StartMP2Forward() {
if (!State.MP2_HasClearance) {
if (!State.MP2_HasDryRunClearance || !State.MP2_WantsPathClearance)
throw new NoClearanceException("MP2 hat keine Freigabe");
MP2_WantActive = true;
return;
}
MP2_WantActive = false;
if (Actuators.MP2 == MotorState.Backward) throw new NoClearanceException("MP2 läuft bereits (zurück)");
Actuators.MP2 = MotorState.Forward;
if (State.MW2_FillLevel >= FillState.Half) StartStirrerMW2();
}
public void StartMP2Backward() {
MP2_WantActive = false;
if (!State.MP2_HasClearance) throw new NoClearanceException("MP2 hat keine Freigabe");
if (Actuators.MP2 == MotorState.Forward) throw new NoClearanceException("MP2 läuft bereits (vor)");
Actuators.MP2 = MotorState.Backward;
}
public void StopMP2() {
MP2_WantActive = false;
Actuators.MP2 = MotorState.Halt;
if (!ManualControlMode) StopStirrerMW1();
}
public void EnableP12() {
if (!State.P12_HasClearance) {
if (!State.P12_HasDryRunClearance || !State.P12_WantsPathClearance)
throw new NoClearanceException("P1/2 hat keine Freigabe");
P12_WantActive = true;
return;
}
P12_WantActive = false;
if (!State.P12_HasClearance) throw new NoClearanceException("P1/2 hat keine Freigabe");
Actuators.P12 = MotorState.Forward;
}
public void DisableP12() {
P12_WantActive = false;
Actuators.P12 = MotorState.Halt;
}
public void EnableP3() {
if (!State.P3_HasClearance) {
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;
}
}
}
@@ -1,342 +0,0 @@
namespace PamhagenSysCtrl.Helpers.Plc {
public readonly record struct PamhagenState {
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 FillState MW1_FillLevel => Sensors.FillLevelMW1;
public FillState MW2_FillLevel => Sensors.FillLevelMW2;
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 => Sensors.FillLevelTanks[0];
public FillState T2_FillLevel => Sensors.FillLevelTanks[1];
public FillState T3_FillLevel => Sensors.FillLevelTanks[2];
public FillState T4_FillLevel => Sensors.FillLevelTanks[3];
public FillState T5_FillLevel => Sensors.FillLevelTanks[4];
public FillState T6_FillLevel => Sensors.FillLevelTanks[5];
public FillState T7_FillLevel => Sensors.FillLevelTanks[6];
public FillState T8_FillLevel => Sensors.FillLevelTanks[7];
public FillState T9_FillLevel => Sensors.FillLevelTanks[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 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.MP1_WantActive;
public bool MP2_WantActive => Plc.MP2_WantActive;
public bool P12_WantActive => Plc.P12_WantActive;
public bool P3_WantActive => Plc.P3_WantActive;
public bool P4_WantActive => Plc.P4_WantActive;
public bool P5_WantActive => Plc.P5_WantActive;
public bool P6_WantActive => Plc.P6_WantActive;
public bool MP1_HasPathClearance => OverridePathClearances || MP1_PathClearance;
public bool MP2_HasPathClearance => OverridePathClearances || MP2_PathClearance;
public bool P12_HasPathClearance => OverridePathClearances || P12_PathClearance;
public bool P3_HasPathClearance => OverridePathClearances || P3_PathClearance;
public bool P4_HasPathClearance => OverridePathClearances || P4_PathClearance;
public bool P5_HasPathClearance => OverridePathClearances || P5_PathClearance;
public bool P6_HasPathClearance => OverridePathClearances || P6_PathClearance;
public bool MP1_HasDryRunClearance => OverrideDryRunClearances || MP1_DryRunClearance;
public bool MP2_HasDryRunClearance => OverrideDryRunClearances || MP2_DryRunClearance;
public bool P12_HasDryRunClearance => OverrideDryRunClearances || P12_DryRunClearance;
public bool P3_HasDryRunClearance => OverrideDryRunClearances || P3_DryRunClearance;
public bool P4_HasDryRunClearance => OverrideDryRunClearances || P4_DryRunClearance;
public bool P5_HasDryRunClearance => OverrideDryRunClearances || P5_DryRunClearance;
public bool P6_HasDryRunClearance => OverrideDryRunClearances || P6_DryRunClearance;
public bool MP1_HasClearance => MP1_HasDryRunClearance && MP1_HasPathClearance;
public bool MP2_HasClearance => MP2_HasDryRunClearance && MP2_HasPathClearance;
public bool P12_HasClearance => P12_HasDryRunClearance && P12_HasPathClearance;
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 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);
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))
);
}
}
}
-144
View File
@@ -1,144 +0,0 @@
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,
};
}
}
}
@@ -2,7 +2,7 @@ using System.IO;
using System.IO.Ports; using System.IO.Ports;
using System.Text; using System.Text;
namespace PamhagenSysCtrl.Helpers.Plc { namespace PamhagenSysCtrl.Helpers {
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,16 +10,12 @@ namespace PamhagenSysCtrl.Helpers.Plc {
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 = 500, ReadTimeout = 1000,
WriteTimeout = 500, WriteTimeout = 1000,
DtrEnable = true, DtrEnable = true,
RtsEnable = true, RtsEnable = true,
Encoding = Encoding.ASCII, Encoding = Encoding.ASCII,
@@ -51,7 +47,7 @@ namespace PamhagenSysCtrl.Helpers.Plc {
Serial.WriteLine(line.ToString()); Serial.WriteLine(line.ToString());
} }
protected async Task<(string Command, string Data)> ReceiveResponse(string? expectedCmd = null) { protected async Task<(string, string)> 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)");
@@ -101,16 +97,14 @@ namespace PamhagenSysCtrl.Helpers.Plc {
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");
LastStatus = new Status((Mode)(res[3] - '0'), ((res[0] - '0') << 4) | (res[1] - '0')); return 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);
LastError = Enum.IsDefined(typeof(T1Error), code) ? (T1Error)code : T1Error.UNKNOWN_ERROR; return 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) {
+73
View File
@@ -0,0 +1,73 @@
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,
};
}
}
}
@@ -1,4 +1,4 @@
namespace PamhagenSysCtrl.Helpers.Plc { namespace PamhagenSysCtrl.Helpers {
public class T1Exception(string msg) : Exception(msg) { public class T1Exception(string msg) : Exception(msg) {
} }
} }
+25 -28
View File
@@ -4,42 +4,37 @@ using System.Net.Http;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
namespace PamhagenSysCtrl.Helpers { namespace PamhagenSysCtrl.Helpers {
public sealed record UpdateInstaller(Version Version, Uri Url, long Size);
public static class UpdateService { public static class UpdateService {
public static async Task<(string Version, string Url, long Size)?> GetLatestInstallerUrl(string feedUrl) { private static readonly HttpClient HttpClient = new() {
try { Timeout = Timeout.InfiniteTimeSpan,
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()); public static async Task<UpdateInstaller> GetLatestInstallerAsync(string feedUrl, CancellationToken cancellationToken = default) {
var latest = json!["data"]!.AsArray()[^1]!; using var response = await HttpClient.GetAsync(feedUrl, cancellationToken);
return ((string)latest["version"]!, (string)latest["url"]!, (long)latest["size"]!); response.EnsureSuccessStatusCode();
} catch {
return null; var json = JsonNode.Parse(await response.Content.ReadAsStringAsync(cancellationToken));
} var latest = json!["data"]!.AsArray()[^1]!;
return new(
new Version((string)latest["version"]!),
new Uri((string)latest["url"]!),
(long)latest["size"]!
);
} }
public static async Task DownloadInstaller(string url, string filename, IProgress<double>? progress = null, CancellationToken cancellationToken = default) { public static async Task<string> DownloadInstallerAsync(UpdateInstaller installer, string targetDirectory, IProgress<double>? progress = null, CancellationToken cancellationToken = default) {
Directory.CreateDirectory(targetDirectory);
var fileName = Path.Combine(targetDirectory, $"PamhagenSysCtrl-{installer.Version}.msi");
try { try {
using var client = new HttpClient() { using var response = await HttpClient.GetAsync(installer.Url, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
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(); response.EnsureSuccessStatusCode();
var contentLength = response.Content.Headers.ContentLength; var contentLength = response.Content.Headers.ContentLength;
await using (var destination = new FileStream(filename, FileMode.Create)) { await using (var destination = new FileStream(fileName, FileMode.Create)) {
await using var source = await response.Content.ReadAsStreamAsync(cancellationToken); await using var source = await response.Content.ReadAsStreamAsync(cancellationToken);
var buffer = new byte[81920]; var buffer = new byte[81920];
long downloaded = 0; long downloaded = 0;
@@ -54,8 +49,9 @@ namespace PamhagenSysCtrl.Helpers {
} }
progress?.Report(1); progress?.Report(1);
return fileName;
} catch { } catch {
File.Delete(filename); File.Delete(fileName);
throw; throw;
} }
} }
@@ -73,5 +69,6 @@ namespace PamhagenSysCtrl.Helpers {
throw new InvalidOperationException("Der Installer konnte nicht gestartet werden."); throw new InvalidOperationException("Der Installer konnte nicht gestartet werden.");
} }
} }
} }
} }
-134
View File
@@ -1,134 +0,0 @@
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 DefaltBacklight = Color.FromArgb(3, 3, 3);
public static readonly Color Neutral = Color.FromArgb(4, 4, 4);
public static readonly Color Red = Color.FromArgb(255, 0, 0);
public static readonly Color Green = Color.FromArgb(0, 255, 0);
public static readonly Color Blue = Color.FromArgb(0, 0, 255);
public static readonly Color Yellow = Color.FromArgb(255, 255, 0);
public static readonly Color Magenta = Color.FromArgb(255, 0, 255);
public static readonly Color Cyan = Color.FromArgb(0, 255, 255);
public static readonly Color Orange = Color.FromArgb(255, 32, 0);
public static readonly Color DarkPurple = Color.FromArgb(128, 0, 128);
public static readonly Color Purple = Color.FromArgb(255, 64, 255);
public static readonly Color LightRed = Color.FromArgb(32, 4, 4);
public static readonly Color LightGreen = Color.FromArgb(4, 32, 4);
public static readonly Color LightBlue = Color.FromArgb(4, 4, 32);
protected readonly PIEDevice XbeDevice;
private KeyState LastKeyState = new(new byte[4]);
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.
+3 -6
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.4</Version> <Version>0.0.1</Version>
<Product>Anlagensteuerung Pamhagen</Product> <Product>Anlagensteuerung Pamhagen</Product>
<AssemblyTitle>Anlagensteuerung Pamhagen</AssemblyTitle> <AssemblyTitle>Anlagensteuerung Pamhagen</AssemblyTitle>
<AssemblyName>PamhagenSysCtrl</AssemblyName> <AssemblyName>PamhagenSysCtrl</AssemblyName>
@@ -16,11 +16,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.11" /> <PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.10" />
<PackageReference Include="System.IO.Ports" Version="10.0.11" /> <PackageReference Include="System.IO.Ports" Version="10.0.10" />
<Reference Include="PIEHidNetCore">
<HintPath>PIEHidNetCore.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
</Project> </Project>
+6 -7
View File
@@ -2,26 +2,25 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Über - Anlagensteuerung Pamhagen" Title="Über - Anlagensteuerung Pamhagen"
Height="340" Width="560" ResizeMode="NoResize" Height="300" Width="560" ResizeMode="NoResize"
WindowStartupLocation="CenterOwner"> WindowStartupLocation="CenterOwner">
<Grid> <Grid>
<TextBlock Margin="20,10" FontSize="12"> <TextBlock Margin="20,10" FontSize="12">
<Bold>Produkt:</Bold> Anlagensteuerung Pamhagen<LineBreak/> <Bold>Produkt:</Bold> Anlagensteuerung Pamhagen<LineBreak/>
<Bold>Beschreibung:</Bold> Schnittstelle zu einer SPS und grafische Benutzeroberfläche für die<LineBreak/> <Bold>Beschreibung:</Bold> Steuerung und Visualisierung der Anlage in Pamhagen<LineBreak/>
<Bold Foreground="Transparent">Beschreibung:</Bold> Steuerung der Anlage<LineBreak/> <Bold>Typ:</Bold> Industrielle Anlagensteuerung<LineBreak/>
<Bold>Typ:</Bold> HMI- und SPS-Steuerungssoftware<LineBreak/>
<Bold>Version:</Bold> <Run x:Name="VersionText">0.0.0</Run><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>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>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>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>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/> <Hyperlink NavigateUri="https://git.necronda.net/winzer/pamhagen-sysctrl" RequestNavigate="Hyperlink_RequestNavigate">git.necronda.net/winzer/pamhagen-sysctrl</Hyperlink><LineBreak/>
<Bold>Entwicklungszeitraum:</Bold> 2026<LineBreak/> <Bold>Entwicklungszeitraum:</Bold> 2026<LineBreak/>
<LineBreak/> <LineBreak/>
<Bold>Verwendete Technologien:</Bold><LineBreak/> <Bold>Verwendete Technologien:</Bold><LineBreak/>
Programmiersprache: C#<LineBreak/> Programmiersprache: C#<LineBreak/>
Framework: Windows Presentation Framework (WPF)<LineBreak/> Framework: Windows Presentation Framework (WPF)<LineBreak/>
SPS-Schnittstelle: Serielle Verbindung<LineBreak/> Kommunikation: Serielle Schnittstelle<LineBreak/>
Paketierung: <Hyperlink NavigateUri="https://www.firegiant.com/wixtoolset/" RequestNavigate="Hyperlink_RequestNavigate">WiX Toolset</Hyperlink> Paketierung: <Hyperlink NavigateUri="https://www.firegiant.com/wixtoolset/" RequestNavigate="Hyperlink_RequestNavigate">WiX Toolset</Hyperlink>
</TextBlock> </TextBlock>
</Grid> </Grid>
@@ -29,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.State.MW1_WantSelected) { if (App.Plant.WantMW1Selected) {
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.State.Valve_WantOpen(i)) { } else if (App.Plant.WantValveOpen(i)) {
App.Plant.CloseV(i); App.Plant.CloseV(i);
} else { } else {
App.Plant.OpenV(i); App.Plant.OpenV(i);
@@ -60,8 +60,8 @@ namespace PamhagenSysCtrl {
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.State.Valve_WantClosed(i); var target = plant.WantValveClosed(i);
var actual = plant.State.Valve_IsClosed(i); var actual = plant.IsValveClosed(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;
@@ -69,97 +69,98 @@ namespace PamhagenSysCtrl {
b.Background = actual ? Brushes.MistyRose : Brushes.MintCream; b.Background = actual ? Brushes.MistyRose : Brushes.MintCream;
} }
ReblerButton.BorderBrush = plant.State.Rebler_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; ReblerButton.BorderBrush = plant.IsReblerActive ? Brushes.DarkGreen : Brushes.DarkRed;
ReblerButton.Foreground = plant.State.Rebler_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; ReblerButton.Foreground = plant.IsReblerActive ? Brushes.DarkGreen : Brushes.DarkRed;
ReblerButton.Background = plant.State.Rebler_IsActive ? Brushes.MintCream: Brushes.MistyRose; ReblerButton.Background = plant.IsReblerActive ? Brushes.MintCream: Brushes.MistyRose;
TroughAugerButton.BorderBrush = plant.State.TroughAuger_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; TroughAugerButton.BorderBrush = plant.IsTroughAugerActive ? Brushes.DarkGreen : Brushes.DarkRed;
TroughAugerButton.Foreground = plant.State.TroughAuger_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; TroughAugerButton.Foreground = plant.IsTroughAugerActive ? Brushes.DarkGreen : Brushes.DarkRed;
TroughAugerButton.Background = plant.State.TroughAuger_IsActive ? Brushes.MintCream : Brushes.MistyRose; TroughAugerButton.Background = plant.IsTroughAugerActive ? Brushes.MintCream : Brushes.MistyRose;
StirrerMW1Button.BorderBrush = plant.State.MW1_Stirrer_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; StirrerMW1Button.BorderBrush = plant.IsStirrerMW1Active ? Brushes.DarkGreen : Brushes.DarkRed;
StirrerMW1Button.Foreground = plant.State.MW1_Stirrer_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; StirrerMW1Button.Foreground = plant.IsStirrerMW1Active ? Brushes.DarkGreen : Brushes.DarkRed;
StirrerMW1Button.Background = plant.State.MW1_Stirrer_IsActive ? Brushes.MintCream : Brushes.MistyRose; StirrerMW1Button.Background = plant.IsStirrerMW1Active ? Brushes.MintCream : Brushes.MistyRose;
StirrerMW2Button.BorderBrush = plant.State.MW2_Stirrer_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; StirrerMW2Button.BorderBrush = plant.IsStirrerMW2Active ? Brushes.DarkGreen : Brushes.DarkRed;
StirrerMW2Button.Foreground = plant.State.MW2_Stirrer_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; StirrerMW2Button.Foreground = plant.IsStirrerMW2Active ? Brushes.DarkGreen : Brushes.DarkRed;
StirrerMW2Button.Background = plant.State.MW2_Stirrer_IsActive ? Brushes.MintCream : Brushes.MistyRose; StirrerMW2Button.Background = plant.IsStirrerMW2Active ? Brushes.MintCream : Brushes.MistyRose;
Auger1Button.BorderBrush = plant.State.Auger1_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; Auger1Button.BorderBrush = plant.IsAuger1Active ? Brushes.DarkGreen : Brushes.DarkRed;
Auger1Button.Foreground = plant.State.Auger1_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; Auger1Button.Foreground = plant.IsAuger1Active ? Brushes.DarkGreen : Brushes.DarkRed;
Auger1Button.Background = plant.State.Auger1_IsActive ? Brushes.MintCream : Brushes.MistyRose; Auger1Button.Background = plant.IsAuger1Active ? Brushes.MintCream : Brushes.MistyRose;
Auger2Button.BorderBrush = plant.State.Auger2_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; Auger2Button.BorderBrush = plant.IsAuger2Active ? Brushes.DarkGreen : Brushes.DarkRed;
Auger2Button.Foreground = plant.State.Auger2_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; Auger2Button.Foreground = plant.IsAuger2Active ? Brushes.DarkGreen : Brushes.DarkRed;
Auger2Button.Background = plant.State.Auger2_IsActive ? Brushes.MintCream : Brushes.MistyRose; Auger2Button.Background = plant.IsAuger2Active ? Brushes.MintCream : Brushes.MistyRose;
Auger3Button.BorderBrush = plant.State.Auger3_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; Auger3Button.BorderBrush = plant.IsAuger3Active ? Brushes.DarkGreen : Brushes.DarkRed;
Auger3Button.Foreground = plant.State.Auger3_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; Auger3Button.Foreground = plant.IsAuger3Active ? Brushes.DarkGreen : Brushes.DarkRed;
Auger3Button.Background = plant.State.Auger3_IsActive ? Brushes.MintCream : Brushes.MistyRose; Auger3Button.Background = plant.IsAuger3Active ? Brushes.MintCream : Brushes.MistyRose;
MP1BackwardButton.BorderBrush = plant.State.MP1_State == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray; MP1BackwardButton.BorderBrush = plant.MP1 == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray;
MP1BackwardButton.Foreground = plant.State.MP1_State == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray; MP1BackwardButton.Foreground = plant.MP1 == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray;
MP1BackwardButton.Background = plant.State.MP1_State == MotorState.Backward ? Brushes.MintCream : Brushes.WhiteSmoke; MP1BackwardButton.Background = plant.MP1 == MotorState.Backward ? Brushes.MintCream : Brushes.WhiteSmoke;
MP1HaltButton.BorderBrush = plant.State.MP1_State == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray; MP1HaltButton.BorderBrush = plant.MP1 == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray;
MP1HaltButton.Foreground = plant.State.MP1_State == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray; MP1HaltButton.Foreground = plant.MP1 == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray;
MP1HaltButton.Background = plant.State.MP1_State == MotorState.Halt ? Brushes.MistyRose : Brushes.WhiteSmoke; MP1HaltButton.Background = plant.MP1 == MotorState.Halt ? Brushes.MistyRose : Brushes.WhiteSmoke;
MP1ForwardButton.BorderBrush = plant.State.MP1_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray; MP1ForwardButton.BorderBrush = plant.MP1 == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray;
MP1ForwardButton.Foreground = plant.State.MP1_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray; MP1ForwardButton.Foreground = plant.MP1 == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray;
MP1ForwardButton.Background = plant.State.MP1_State == MotorState.Forward ? Brushes.MintCream: Brushes.WhiteSmoke; MP1ForwardButton.Background = plant.MP1 == MotorState.Forward ? Brushes.MintCream: Brushes.WhiteSmoke;
MP2BackwardButton.BorderBrush = plant.State.MP2_State == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray; MP2BackwardButton.BorderBrush = plant.MP2 == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray;
MP2BackwardButton.Foreground = plant.State.MP2_State == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray; MP2BackwardButton.Foreground = plant.MP2 == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray;
MP2BackwardButton.Background = plant.State.MP2_State == MotorState.Backward ? Brushes.MintCream : Brushes.WhiteSmoke; MP2BackwardButton.Background = plant.MP2 == MotorState.Backward ? Brushes.MintCream : Brushes.WhiteSmoke;
MP2HaltButton.BorderBrush = plant.State.MP2_State == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray; MP2HaltButton.BorderBrush = plant.MP2 == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray;
MP2HaltButton.Foreground = plant.State.MP2_State == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray; MP2HaltButton.Foreground = plant.MP2 == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray;
MP2HaltButton.Background = plant.State.MP2_State == MotorState.Halt ? Brushes.MistyRose : Brushes.WhiteSmoke; MP2HaltButton.Background = plant.MP2 == MotorState.Halt ? Brushes.MistyRose : Brushes.WhiteSmoke;
MP2ForwardButton.BorderBrush = plant.State.MP2_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray; MP2ForwardButton.BorderBrush = plant.MP2 == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray;
MP2ForwardButton.Foreground = plant.State.MP2_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray; MP2ForwardButton.Foreground = plant.MP2 == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray;
MP2ForwardButton.Background = plant.State.MP2_State == MotorState.Forward ? Brushes.MintCream : Brushes.WhiteSmoke; MP2ForwardButton.Background = plant.MP2 == MotorState.Forward ? Brushes.MintCream : Brushes.WhiteSmoke;
P12Button.BorderBrush = plant.State.P12_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P12Button.BorderBrush = plant.P12 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P12Button.Foreground = plant.State.P12_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P12Button.Foreground = plant.P12 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P12Button.Background = plant.State.P12_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P12Button.Background = plant.P12 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
P3Button.BorderBrush = plant.State.P3_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P3Button.BorderBrush = plant.P3 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P3Button.Foreground = plant.State.P3_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P3Button.Foreground = plant.P3 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P3Button.Background = plant.State.P3_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P3Button.Background = plant.P3 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
P4Button.BorderBrush = plant.State.P4_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P4Button.BorderBrush = plant.P4 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P4Button.Foreground = plant.State.P4_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P4Button.Foreground = plant.P4 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P4Button.Background = plant.State.P4_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P4Button.Background = plant.P4 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
P5Button.BorderBrush = plant.State.P5_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P5Button.BorderBrush = plant.P5 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P5Button.Foreground = plant.State.P5_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P5Button.Foreground = plant.P5 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P5Button.Background = plant.State.P5_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P5Button.Background = plant.P5 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
P6Button.BorderBrush = plant.State.P6_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P6Button.BorderBrush = plant.P6 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P6Button.Foreground = plant.State.P6_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P6Button.Foreground = plant.P6 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P6Button.Background = plant.State.P6_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P6Button.Background = plant.P6 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
Status.Text = $""" Status.Text = $"""
Presse Querschnecke: {plant.State.PressCrossAuger_State} Gradation °Oe: {plant.GradationOe:N2}
Presse Querschnecke: {plant.PresseQuerSchnecke}
Füllstände: Füllstände:
Maischewanne 1: {plant.State.MW2_FillLevel} (ausgewählt: {plant.State.MW1_WantSelected}, Status: {plant.State.MW1_IsSelected}) Maischewanne 1: {plant.FillLevelMW1} (ausgewählt: {plant.WantMW1Selected}, Status: {plant.IsMWSelected})
Maischewanne 2: {plant.State.MW2_FillLevel} (ausgewählt: {plant.State.MW2_WantSelected}, Status: {plant.State.MW2_IsSelected}) Maischewanne 2: {plant.FillLevelMW2} (ausgewählt: {plant.WantMW2Selected}, Status: {plant.IsMW2Selected})
Presse 1: {plant.State.Press1_HasClearance} Presse 1: {plant.Press1HasClearance}
Presse 2: {plant.State.Press2_HasClearance} Presse 2: {plant.Press2HasClearance}
Tank 1: {plant.State.T1_FillLevel} Tank 1: {plant.FillLevelT1}
Tank 2: {plant.State.T2_FillLevel} Tank 2: {plant.FillLevelT2}
Tank 3: {plant.State.T3_FillLevel} Tank 3: {plant.FillLevelT3}
Tank 4: {plant.State.T4_FillLevel} Tank 4: {plant.FillLevelT4}
Tank 5: {plant.State.T5_FillLevel} Tank 5: {plant.FillLevelT5}
Tank 6: {plant.State.T6_FillLevel} Tank 6: {plant.FillLevelT6}
Tank 7: {plant.State.T7_FillLevel} Tank 7: {plant.FillLevelT7}
Tank 8: {plant.State.T8_FillLevel} Tank 8: {plant.FillLevelT8}
Tank 9: {plant.State.T9_FillLevel} Tank 9: {plant.FillLevelT9}
"""; """;
} }
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.State.TroughAuger_IsActive) { if (App.Plant.IsTroughAugerActive) {
App.Plant.StopTroughAuger(); App.Plant.StopTroughAuger();
} else { } else {
App.Plant.StartTroughAuger(); App.Plant.StartTroughAuger();
@@ -172,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.State.Rebler_IsActive) { if (App.Plant.IsReblerActive) {
App.Plant.StopRebler(); App.Plant.StopRebler();
} else { } else {
App.Plant.StartRebler(); App.Plant.StartRebler();
@@ -185,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.State.MW1_Stirrer_IsActive) { if (App.Plant.IsStirrerMW1Active) {
App.Plant.StopStirrerMW1(); App.Plant.StopStirrerMW1();
} else { } else {
App.Plant.StartStirrerMW1(); App.Plant.StartStirrerMW1();
@@ -198,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.State.MW2_Stirrer_IsActive) { if (App.Plant.IsStirrerMW2Active) {
App.Plant.StopStirrerMW2(); App.Plant.StopStirrerMW2();
} else { } else {
App.Plant.StartStirrerMW2(); App.Plant.StartStirrerMW2();
@@ -211,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.State.Auger1_IsActive) { if (App.Plant.IsAuger1Active) {
App.Plant.StopAuger1(); App.Plant.StopAuger1();
} else { } else {
App.Plant.StartAuger1(); App.Plant.StartAuger1();
@@ -224,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.State.Auger2_IsActive) { if (App.Plant.IsAuger2Active) {
App.Plant.StopAuger2(); App.Plant.StopAuger2();
} else { } else {
App.Plant.StartAuger2(); App.Plant.StartAuger2();
@@ -237,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.State.Auger3_IsActive) { if (App.Plant.IsAuger3Active) {
App.Plant.StopAuger3(); App.Plant.StopAuger3();
} else { } else {
App.Plant.StartAuger3(); App.Plant.StartAuger3();
@@ -305,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.State.P12_IsActive) { if (App.Plant.P12 == MotorState.Forward) {
App.Plant.DisableP12(); App.Plant.StopP12();
} else { } else {
App.Plant.EnableP12(); App.Plant.StartP12();
} }
} 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);
@@ -318,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.State.P3_IsActive) { if (App.Plant.P3 == MotorState.Forward) {
App.Plant.DisableP3(); App.Plant.StopP3();
} else { } else {
App.Plant.EnableP3(); App.Plant.StartP3();
} }
} 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);
@@ -331,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.State.P4_IsActive) { if (App.Plant.P4 == MotorState.Forward) {
App.Plant.DisableP4(); App.Plant.StopP4();
} else { } else {
App.Plant.EnableP4(); App.Plant.StartP4();
} }
} 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);
@@ -344,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.State.P5_IsActive) { if (App.Plant.P5 == MotorState.Forward) {
App.Plant.DisableP5(); App.Plant.StopP5();
} else { } else {
App.Plant.EnableP5(); App.Plant.StartP5();
} }
} 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);
@@ -357,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.State.P6_IsActive) { if (App.Plant.P6 == MotorState.Forward) {
App.Plant.DisableP6(); App.Plant.StopP6();
} else { } else {
App.Plant.EnableP6(); App.Plant.StartP6();
} }
} 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);
+6 -204
View File
@@ -5,9 +5,7 @@
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"
Closed="OnClosed" Closed="OnClosed"
SizeChanged="OnSizeChanged" Title="Anlagensteuerung Pamhagen" Height="950" Width="1600" MinWidth="1500" MinHeight="950">
Title="Anlagensteuerung Pamhagen"
Height="800" Width="1400" MinWidth="1000" MinHeight="500">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="19"/> <RowDefinition Height="19"/>
@@ -52,7 +50,7 @@
<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 Header="Nach Updates suchen..." Click="Menu_Help_CheckForUpdates_Click">
<MenuItem.Icon> <MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE895;"/> <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE895;"/>
</MenuItem.Icon> </MenuItem.Icon>
@@ -61,210 +59,14 @@
</Menu> </Menu>
<Grid Grid.Row="1" ClipToBounds="True"> <Grid Grid.Row="1" ClipToBounds="True">
<Canvas x:Name="SchemeCanvas" VerticalAlignment="Center" HorizontalAlignment="Center" SnapsToDevicePixels="True" Grid.ColumnSpan="2" Margin="25,0,0,0" <Canvas x:Name="SchemeCanvas" Width="1550" Height="900" VerticalAlignment="Center" HorizontalAlignment="Center" SnapsToDevicePixels="True"
MouseMove="SchemeCanvas_MouseMove" MouseWheel="SchemeCanvas_MouseWheel" MouseMove="SchemeCanvas_MouseMove" MouseLeftButtonDown="SchemeCanvas_MouseLeftButtonDown" MouseLeftButtonUp="SchemeCanvas_MouseLeftButtonUp" Grid.ColumnSpan="2" Margin="25,0,0,0">
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>
+49 -358
View File
@@ -1,77 +1,24 @@
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 const double _minMove = 50; private readonly PamhagenGraph Graph;
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 double Scale { get; set; } = 5;
public double RoundedScale => Math.Round(Scale * 2) / 2;
public Path? HoveringPath {
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;
}
}
}
protected Dictionary<Button, (ISource Source, Pump Pump, ISink Sink)> FastSelectPaths;
public PlantSchemeWindow() { public PlantSchemeWindow() {
InitializeComponent(); InitializeComponent();
Menu_Help_CheckForUpdates.IsEnabled = App.Config.UpdateUrl != null; App.Plant?.Update += OnUpdate;
Graph.Draw(SchemeCanvas, RoundedScale); Graph = new();
FastSelectPaths = new Dictionary<Button, (ISource, Pump, ISink)> { Graph.Draw(SchemeCanvas);
[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) { private void OnClosed(object sender, EventArgs evt) {
@@ -97,8 +44,6 @@ namespace PamhagenSysCtrl.Windows {
} }
Menu_Settings_OverridePathClearances.IsChecked = false; Menu_Settings_OverridePathClearances.IsChecked = false;
Menu_Settings_OverrideDryRunClearances.IsChecked = false; Menu_Settings_OverrideDryRunClearances.IsChecked = false;
App.Plant?.InitializePaths();
} }
OnUpdate(null, null); OnUpdate(null, null);
} }
@@ -131,7 +76,9 @@ namespace PamhagenSysCtrl.Windows {
} }
private async void Menu_Help_CheckForUpdates_Click(object sender, RoutedEventArgs evt) { private async void Menu_Help_CheckForUpdates_Click(object sender, RoutedEventArgs evt) {
await App.CheckForUpdates(true); if (Application.Current is App app) {
await app.CheckForUpdates(true);
}
} }
private void Menu_Help_About_Click(object sender, RoutedEventArgs evt) { private void Menu_Help_About_Click(object sender, RoutedEventArgs evt) {
@@ -141,202 +88,18 @@ namespace PamhagenSysCtrl.Windows {
window.Show(); 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);
}
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) { private void SchemeCanvas_MouseMove(object sender, MouseEventArgs evt) {
var pos = evt.GetPosition(SchemeCanvas); var p = evt.GetPosition(SchemeCanvas);
if (_lastMousePos is Point p) { UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed);
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 pos = evt.GetPosition(SchemeCanvas); var p = evt.GetPosition(SchemeCanvas);
_lastMousePos = pos; _lastSelected = UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed);
_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) {
@@ -344,7 +107,7 @@ namespace PamhagenSysCtrl.Windows {
} }
foreach (var c in clicked) { foreach (var c in clicked) {
if (App.Plant?.ManualControlMode ?? false) { if (App.Plant?.ManualControlMode ?? false) {
if (c is ControlledValve v) { if (c is Valve v) {
if (v.WantOpen) { if (v.WantOpen) {
App.Plant.CloseV(v.Nr); App.Plant.CloseV(v.Nr);
} else { } else {
@@ -354,16 +117,17 @@ namespace PamhagenSysCtrl.Windows {
} else { } else {
if (_lastSource == null && Graph.SelectedPaths.Any(p => p.Start == c)) { if (_lastSource == null && Graph.SelectedPaths.Any(p => p.Start == c)) {
var path = Graph.SelectedPaths.First(p => p.Start == c); var path = Graph.SelectedPaths.First(p => p.Start == c);
if (!path.HasActivePump) { if (!path.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false)) {
Graph.RemovePath(path); Graph.RemovePath(path);
} }
} else if (_lastSource != null && c is ISink sink) { } else if (_lastSource != null && c is ISink sink) {
if (Graph.GetFreePath([_lastSource, .. _path, sink]) is Path path) { var res = Graph.GetPath([_lastSource, .. _path, sink], (n) => n is not ISink && (n is not Valve v || !v.IsLocked));
if (res is Path path) {
Graph.AddPath(path); Graph.AddPath(path);
_lastSource = null; _lastSource = null;
} }
} else if (_lastSource != null) { } else if (_lastSource != null) {
var path = Graph.GetFreePath([_lastSource, .. _path, c]); var path = Graph.GetPath([_lastSource, .. _path, c], (n) => n is not ISink && (n is not Valve v || !v.IsLocked));
if (path != null) { if (path != null) {
_path.Add(c); _path.Add(c);
} }
@@ -388,7 +152,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?.State.MW1_WantSelected ?? false) { if (App.Plant?.WantMW1Selected ?? false) {
App.Plant?.SelectMW2(); App.Plant?.SelectMW2();
} else { } else {
App.Plant?.SelectMW1(); App.Plant?.SelectMW1();
@@ -428,33 +192,33 @@ namespace PamhagenSysCtrl.Windows {
} }
} else if (pump == Graph.P12) { } else if (pump == Graph.P12) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.DisableP12(); App.Plant?.StopP12();
} else { } else {
App.Plant?.EnableP12(); App.Plant?.StartP12();
} }
} else if (pump == Graph.P3) { } else if (pump == Graph.P3) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.DisableP3(); App.Plant?.StopP3();
} else { } else {
App.Plant?.EnableP3(); App.Plant?.StartP3();
} }
} else if (pump == Graph.P4) { } else if (pump == Graph.P4) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.DisableP4(); App.Plant?.StopP4();
} else { } else {
App.Plant?.EnableP4(); App.Plant?.StartP4();
} }
} else if (pump == Graph.P5) { } else if (pump == Graph.P5) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.DisableP5(); App.Plant?.StopP5();
} else { } else {
App.Plant?.EnableP5(); App.Plant?.StartP5();
} }
} else if (pump == Graph.P6) { } else if (pump == Graph.P6) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.DisableP6(); App.Plant?.StopP6();
} else { } else {
App.Plant?.EnableP6(); App.Plant?.StartP6();
} }
} }
} }
@@ -465,30 +229,6 @@ namespace PamhagenSysCtrl.Windows {
UpdateScheme(pos, evt.LeftButton == MouseButtonState.Pressed); UpdateScheme(pos, evt.LeftButton == MouseButtonState.Pressed);
} }
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) { private ISet<INode> UpdateScheme(Point pos, bool down) {
foreach (var e in Graph.Edges) { foreach (var e in Graph.Edges) {
e.Highlight = null; e.Highlight = null;
@@ -500,43 +240,35 @@ namespace PamhagenSysCtrl.Windows {
} }
} }
SetFastSelectButton(FastSelectButton_EntryTrough_Start, App.Plant?.State.TroughAuger_IsActive ?? false ? 1 : 0, !(App.Plant?.State.TroughAuger_HasClearance ?? false)); _hover = Graph.GetHover(pos.X, pos.Y);
SetFastSelectButton(FastSelectButton_EntryTrough_Stop, App.Plant?.State.TroughAuger_IsActive ?? false ? 0 : 2);
SetFastSelectButton(FastSelectButton_MW1, App.Plant?.State.MW1_IsSelected?? false ? 1 : 0);
SetFastSelectButton(FastSelectButton_MW2, App.Plant?.State.MW2_IsSelected ?? false ? 1 : 0);
SetFastSelectButton(FastSelectButton_MP1_Forward, App.Plant?.State.MP1_State == MotorState.Forward ? 1 : 0, !(App.Plant?.State.MP1_HasClearance ?? false));
SetFastSelectButton(FastSelectButton_MP1_Stop, App.Plant?.State.MP1_State == MotorState.Halt ? 2 : 0);
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.X, pos.Y, RoundedScale);
var hoverButtons = FastSelectPaths.Where(b => b.Key.IsMouseOver).ToDictionary();
Path? hoverPathDeactivate = null;
if (App.Plant?.ManualControlMode ?? false) { if (App.Plant?.ManualControlMode ?? false) {
foreach (var src in Graph.Nodes.Where(n => n is Pump)) { foreach (var src in Graph.Nodes.Where(n => n is Pump)) {
PamhagenGraph.ColorPipesByValves(src, PamhagenBrushes.Green, (src as Pump)?.State ?? default, PamhagenBrushes.DimOrange); Graph.ColorPipesByValves(src, PamhagenBrushes.Green, (src as Pump)?.State ?? default, PamhagenBrushes.DimOrange);
} }
MP1_Target.Text = "Vor";
MP2_Target.Text = "Vor";
MP1_Target.FontSize = 16;
MP2_Target.FontSize = 16;
} else { } else {
foreach (var p in Graph.SelectedPaths) { foreach (var p in Graph.SelectedPaths) {
PamhagenGraph.ColorPipesAdjacientToPath(p, PamhagenBrushes.Green, p.Pumps.Select(p => p.State).FirstOrDefault(n => n != MotorState.Halt), PamhagenBrushes.DimOrange); Graph.ColorPipesAdjacientToPath(p, PamhagenBrushes.Green, p.Hops.Select(h => (h.Node as Pump)?.State ?? default).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 (_lastSource != null && (_hover.Count > 0 || _path.Count > 0)) {
if (path1.HasActivePump) { List<INode> points = [_lastSource, .. _path];
foreach (var p in path1.Pumps) p.Highlight = PamhagenBrushes.Red; if (_hover.Count > 0 && !points.Contains(_hover.First())) {
} else { points.Add(_hover.First());
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 (points.Count >= 2) {
if (path2.HasActivePump) { var res = Graph.GetPath(points, (n) => n is not ISink && (n is not Valve v || !v.IsLocked));
foreach (var p in path2.Pumps) p.Highlight = PamhagenBrushes.Red; if (res is Path path) {
Graph.ColorPipesAdjacientToPath(path, PamhagenBrushes.Green, default, PamhagenBrushes.DimOrange);
}
}
} else if (_lastSource == null && _hover.Count > 0 && Graph.SelectedPaths.Any(p => p.Start == _hover.First())) {
var path = Graph.SelectedPaths.First(p => p.Start == _hover.First());
var pumpActive = path.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false);
if (pumpActive) {
foreach (var pump in path.Hops.Select(h => h.Node as Pump).Where(p => p != null)) {
pump?.Highlight = PamhagenBrushes.Red;
}
} else { } else {
hoverPathDeactivate = path2; Graph.ColorPipesAdjacientToPath(path, PamhagenBrushes.Red, default, null);
PamhagenGraph.ColorPipesAdjacientToPath(path2, PamhagenBrushes.Red, default, null);
} }
} }
if (_lastSource != null) { if (_lastSource != null) {
@@ -544,54 +276,13 @@ namespace PamhagenSysCtrl.Windows {
edge.Highlight = PamhagenBrushes.Green; edge.Highlight = PamhagenBrushes.Green;
} }
} }
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 {
MP1_Target.Text = "Vor";
}
if (Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == Graph.MW2) is Path pmw2) {
MP2_Target.Text = "\u2b9e " + string.Join(" \u2b9e ", pmw2.Hops
.Select(h => h.Node is Valve || h.Node is Pump ? null : h.Node.Label)
.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 (App.Plant is PamhagenPlant p) { if (sender is not PamhagenPlant plant) return;
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);
} }
} }
+1 -41
View File
@@ -1,42 +1,2 @@
Anlagensteuerung Pamhagen # Anlagensteuerung Pamhangen
=========================
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
@@ -1,233 +0,0 @@
#!/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
@@ -1,216 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 12 KiB