24 Commits
Author SHA1 Message Date
lorenz.stechauner 2dee3a9ae9 Rework updating process a bit
Test / Run tests (push) Successful in 13s
2026-08-12 15:01:59 +02:00
lorenz.stechauner 4e82f164c9 Update README and AboutWindow 2026-08-12 14:45:58 +02:00
thomas.hilscherandlorenz.stechauner 77f9ae167c Update Über page 2026-08-12 11:38:09 +02:00
thomas.hilscherandlorenz.stechauner eb0ec60ed2 Add auto updater 2026-08-12 11:37:07 +02:00
lorenz.stechauner 9d4ae11de8 Ignore empty sensors in tanks
Test / Run tests (push) Successful in 33s
2026-08-12 11:24:14 +02:00
lorenz.stechauner d507a7486e Bump version to 0.0.3
Deploy / Build and Deploy (push) Successful in 2m34s
Test / Run tests (push) Successful in 14s
2026-08-11 14:50:41 +02:00
lorenz.stechauner ece46846b4 Add buttons for controlling pumps
Test / Run tests (push) Successful in 34s
2026-08-11 12:59:08 +02:00
lorenz.stechauner 03c91fb55a Enhance path finding using path cost 2026-08-11 02:01:08 +02:00
lorenz.stechauner f0accc8df8 Add fast select buttons
Test / Run tests (push) Successful in 14s
2026-08-11 00:26:27 +02:00
lorenz.stechauner 3983393e10 Bump version to 0.0.2
Deploy / Build and Deploy (push) Successful in 2m35s
Test / Run tests (push) Successful in 32s
2026-08-10 19:37:59 +02:00
lorenz.stechauner c82adcc054 Handle windows for manual control correctly
Test / Run tests (push) Successful in 13s
2026-08-07 16:15:00 +02:00
lorenz.stechauner 28aaa6b1f3 Use ManualControlMode to disable automatic behaviour
Test / Run tests (push) Successful in 13s
2026-08-07 16:03:56 +02:00
lorenz.stechauner 0ceab368ce Add Handbetrieb support in scheme window
Test / Run tests (push) Successful in 33s
2026-08-07 15:55:22 +02:00
lorenz.stechauner 7c3233361f Add automatic stirrers
Test / Run tests (push) Successful in 14s
2026-08-07 13:08:45 +02:00
lorenz.stechauner e45c398c68 Update manual control invocation
Test / Run tests (push) Successful in 13s
2026-08-07 12:58:55 +02:00
lorenz.stechauner d3dea3e9a0 Add ability to override clearances 2026-08-07 12:48:51 +02:00
lorenz.stechauner dbcf807154 Add pipe labels
Test / Run tests (push) Successful in 13s
2026-08-07 12:30:11 +02:00
lorenz.stechauner 96bb51807d Rearrange graph
Test / Run tests (push) Successful in 15s
2026-08-07 11:49:38 +02:00
lorenz.stechauner 4f8c401cae Adjust clearances to new pipe schema
Test / Run tests (push) Successful in 32s
2026-08-07 10:30:37 +02:00
lorenz.stechauner 06dcdd8569 Add CenterValves to presses
Test / Run tests (push) Successful in 14s
2026-08-07 01:10:29 +02:00
lorenz.stechauner e6b97ccf33 Fix belts/augers
Test / Run tests (push) Successful in 32s
2026-08-06 22:08:44 +02:00
lorenz.stechauner b85d1d8afd Update press and tank capacity 2026-08-06 21:47:16 +02:00
lorenz.stechauner b485bcfb19 deploy.yaml: Shorten Gitea release name 2026-08-06 10:04:51 +02:00
thomas.hilscher 122ac8efd7 Change assembly title to show correct name in task manager
Test / Run tests (push) Successful in 33s
2026-08-05 20:07:37 +02:00
29 changed files with 1374 additions and 386 deletions
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
- name: Create release
uses: akkuman/gitea-release-action@v1
with:
name: Anlagensteuerung Pamhagen ${{ env.APP_VERSION }}
name: Anl.-Strg. Pamhagen ${{ env.APP_VERSION }}
body: "**[Changelog](src/branch/main/CHANGELOG.md#v${{ env.APP_VERSION }})**"
files: |-
Installer/bin/Release/de-AT/PamhagenSysCtrl-${{ env.APP_VERSION }}.msi
+4
View File
@@ -1,3 +1,7 @@
[plc]
port = COM1
[update]
url = https://elwig.at/files/pamhagen-sysctrl/latest
auto = true
+48
View File
@@ -1,3 +1,4 @@
using PamhagenSysCtrl.Dialogs;
using PamhagenSysCtrl.Helpers;
using System.IO;
using System.Reflection;
@@ -10,6 +11,8 @@ namespace PamhagenSysCtrl {
public static PamhagenPlant? Plant;
public static Dispatcher? MainDispatcher;
private readonly DispatcherTimer AutoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) };
public static readonly string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Anlagensteuerung Pamhagen");
public static readonly string ConfigPath = Path.Combine(DataPath, "config.ini");
public static readonly string InstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Anlagensteuerung Pamhagen");
@@ -45,6 +48,51 @@ namespace PamhagenSysCtrl {
}
base.OnStartup(evt);
if (Config.UpdateUrl != null && Config.UpdateAuto) {
AutoUpdateTimer.Tick += async (_, _) => {
try {
await CheckForUpdates();
} catch { }
};
AutoUpdateTimer.Start();
await Dispatcher.BeginInvoke(async () => {
await Task.Delay(1500);
try {
await CheckForUpdates();
} catch { }
});
}
}
public static async Task CheckForUpdates(bool showResult = false) {
if (Config.UpdateUrl == null) return;
try {
var latest = await UpdateService.GetLatestInstallerUrl(Config.UpdateUrl);
if (latest.HasValue && new Version(latest.Value.Version) > Version) {
var dialog = new UpdateDialog(latest.Value.Version, latest.Value.Url, latest.Value.Size) {
Owner = Current.MainWindow,
};
if (dialog.ShowDialog() == true) {
Current.Shutdown();
}
} else if (showResult) {
MessageBox.Show($"Die Anlagensteuerung ist auf dem aktuellen Stand.\n(Version {Version})", "Nach Updates suchen", MessageBoxButton.OK, MessageBoxImage.Information);
}
} catch (OperationCanceledException) {
if (showResult) {
MessageBox.Show("Zeitüberschreitung beim Abrufen der Update-Informationen.", "Nach Updates suchen", MessageBoxButton.OK, MessageBoxImage.Error);
} else {
throw;
}
} catch (Exception exc) {
if (showResult) {
MessageBox.Show($"Die Update-Informationen konnten nicht abgerufen werden:\n\n{exc.Message}", "Nach Updates suchen", MessageBoxButton.OK, MessageBoxImage.Error);
} else {
throw;
}
}
}
protected async void Application_Exit(object sender, ExitEventArgs evt) {
+37
View File
@@ -0,0 +1,37 @@
<Window x:Class="PamhagenSysCtrl.Dialogs.UpdateDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True"
WindowStartupLocation="CenterOwner"
Title="Update verfügbar - Anlagensteuerung Pamhagen"
Height="220" Width="480"
Closed="OnClosed">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock x:Name="Description" FontSize="14" TextAlignment="Center"
HorizontalAlignment="Center" VerticalAlignment="Center">
Version <Run x:Name="VersionText" FontWeight="Bold">0.0.0</Run> ist verfügbar.<LineBreak/>
Soll das Update heruntergeladen und installiert werden?<LineBreak/>
(ca. <Run x:Name="SizeText">0</Run> MB)<LineBreak/><LineBreak/>
<Run FontWeight="Bold">Hinweis:</Run> Die Anlagensteuerung wird für die Installation geschlossen.
</TextBlock>
<StackPanel x:Name="DownloadPanel" Grid.Row="0" VerticalAlignment="Center" Visibility="Collapsed">
<TextBlock x:Name="StatusText" Text="Update wird heruntergeladen..."
FontSize="14" TextAlignment="Center" Margin="0,0,0,10"/>
<ProgressBar x:Name="DownloadProgress" Height="24" Minimum="0" Maximum="100"/>
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,18,0,0">
<Button x:Name="InstallButton" Content="Installieren" IsDefault="True"
Width="110" Height="30" Margin="0,0,12,0" Click="InstallButton_Click"/>
<Button x:Name="CancelButton" Content="Abbrechen" IsCancel="True"
Width="110" Height="30"/>
</StackPanel>
</Grid>
</Window>
@@ -0,0 +1,45 @@
using PamhagenSysCtrl.Helpers;
using System.IO;
using System.Windows;
namespace PamhagenSysCtrl.Dialogs {
public partial class UpdateDialog : Window {
private readonly string Url;
private readonly CancellationTokenSource Cancellation = new();
public UpdateDialog(string version, string url, long size) {
Url = url;
InitializeComponent();
VersionText.Text = version;
SizeText.Text = Math.Ceiling(size / 1024d / 1024d).ToString("N0");
}
private void OnClosed(object? sender, EventArgs evt) {
Cancellation.Cancel();
Cancellation.Dispose();
}
private async void InstallButton_Click(object sender, RoutedEventArgs evt) {
Description.Visibility = Visibility.Collapsed;
DownloadPanel.Visibility = Visibility.Visible;
InstallButton.IsEnabled = false;
try {
var progress = new Progress<double>(value => DownloadProgress.Value = value * 100);
var filename = Path.Combine(App.TempPath, $"PamhagenSysCtrl-{VersionText.Text}.msi");
await UpdateService.DownloadInstaller(Url, filename, progress, Cancellation.Token);
StatusText.Text = "Installer wird gestartet...";
UpdateService.StartInstaller(filename);
DialogResult = true;
} catch (OperationCanceledException) {
// Closing the dialog cancels an active download.
} catch (Exception exc) {
MessageBox.Show($"Das Update konnte nicht installiert werden:\n\n{exc.Message}", "Update installieren", MessageBoxButton.OK, MessageBoxImage.Error);
Description.Visibility = Visibility.Visible;
DownloadPanel.Visibility = Visibility.Collapsed;
InstallButton.IsEnabled = true;
}
}
}
}
+5 -1
View File
@@ -9,6 +9,8 @@ namespace PamhagenSysCtrl.Helpers {
private readonly string FileName;
public string PlcPort = "COM1";
public string? UpdateUrl;
public bool UpdateAuto;
public Config(string filename) {
FileName = filename;
@@ -19,9 +21,11 @@ namespace PamhagenSysCtrl.Helpers {
try {
var config = new ConfigurationBuilder().AddIniFile(FileName).Build();
PlcPort = config["plc:port"] ?? "COM1";
UpdateUrl = config["update:url"];
UpdateAuto = TrueValues.Contains(config["update:auto"]?.ToLower());
} catch (Exception exc) {
MessageBox.Show($"Die Konfigurationsdatei konnte nicht gelesen werden:\n\n{exc.Message}", "Konfigurationsdatei lesen", MessageBoxButton.OK, MessageBoxImage.Error);
App.Current.Shutdown();
Application.Current.Shutdown();
}
}
}
+6 -4
View File
@@ -5,10 +5,12 @@ namespace PamhagenSysCtrl.Helpers {
public const double TRANSITION = 1.0 / 4096;
public static readonly SolidColorBrush Green = new SolidColorBrush(Color.FromRgb(0x20, 0xE0, 0x20));
public static readonly SolidColorBrush DimOrange = new SolidColorBrush(Color.FromRgb(0xFF, 0xC0, 0x80));
public static readonly SolidColorBrush Yellow = new SolidColorBrush(Color.FromRgb(0xFF, 0xE0, 0x10));
public static readonly SolidColorBrush Red = new SolidColorBrush(Color.FromRgb(0xFF, 0x20, 0x20));
public static readonly SolidColorBrush Green = new(Color.FromRgb(0x20, 0xE0, 0x20));
public static readonly SolidColorBrush DimGreen = new(Color.FromRgb(0xC0, 0xE0, 0xC0));
public static readonly SolidColorBrush DimOrange = new(Color.FromRgb(0xFF, 0xC0, 0x80));
public static readonly SolidColorBrush Yellow = new(Color.FromRgb(0xFF, 0xE0, 0x10));
public static readonly SolidColorBrush Red = new(Color.FromRgb(0xFF, 0x20, 0x20));
public static readonly SolidColorBrush DimRed = new(Color.FromRgb(0xFF, 0xC0, 0xC0));
public static Brush ForFillState(FillState state, Color background) {
return state switch {
+197 -124
View File
@@ -17,7 +17,18 @@ namespace PamhagenSysCtrl.Helpers {
public readonly Pump P5;
public readonly Pump P6;
public readonly EncasedAuger Auger1;
public readonly EncasedAuger Auger2;
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 PamhagenGraph() {
double mw2X = 180;
@@ -34,6 +45,7 @@ namespace PamhagenSysCtrl.Helpers {
double wtX = ltg1X - 180;
double wt2Y = 225;
double wt1Y = wt2Y + 135;
double x1X = wtX + 40;
double t1X = 735;
double t2X = t1X + 80;
double t3X = t2X + 80;
@@ -43,21 +55,21 @@ namespace PamhagenSysCtrl.Helpers {
double t7X = t6X + 80;
double t8X = t7X + 80;
double t9X = t8X + 80;
var prY = 780;
var pr1X = 750;
var pr2X = 900;
var prY = 720;
var pr1X = 730;
var pr2X = 920;
var trough = new EntryTrough("Auffangwanne", mw1X + 70, 100, () => App.Plant?.IsTroughAugerActive ?? false);
Nodes.Add(trough);
Auger1 = new EncasedAuger("", (mw1X + mw2X) / 2 - 100, 200, () => App.Plant?.IsAuger1Active ?? false);
var auger3 = new EncasedAuger("Pressen-\nQuerschnecke", 40, 200, () => App.Plant?.IsPresseQuerSchneckeActive ?? false, 180, clickable: false);
Auger2 = new EncasedAuger("", 120, 80, () => App.Plant?.IsAuger2Active ?? false, 135);
var belt = new ConveyorBelt("", 90, 160, () => App.Plant?.IsBeltActive ?? false);
var auger0 = new EncasedAuger("Pressen-\nQuerschnecke", 40, 200, () => App.Plant?.IsPresseQuerSchneckeActive ?? false, 180, clickable: false);
Auger3 = new EncasedAuger("", 120, 80, () => App.Plant?.IsAuger3Active ?? false, 135);
var belt = new ConveyorBelt("", 90, 160, () => App.Plant?.IsAuger2Active ?? false);
Nodes.Add(belt);
Nodes.Add(Auger1);
Nodes.Add(auger3);
Nodes.Add(Auger2);
Nodes.Add(auger0);
Nodes.Add(Auger3);
var rebler = new Rebler("Rebler", (mw1X + mw2X) / 2, 200, () => App.Plant?.IsReblerActive ?? false);
Nodes.Add(rebler);
var switcher = new Switcher("", (mw1X + mw2X) / 2, 260, () => App.Plant?.TroughSelector ?? 0, () => App.Plant == null ? 0 : App.Plant.WantMW1Selected ? 1 : 2);
@@ -65,8 +77,8 @@ namespace PamhagenSysCtrl.Helpers {
MW1 = new Trough("MW1", mw1X, mpY - 70, () => App.Plant?.FillLevelMW1 ?? FillState.Unknown);
MW2 = new Trough("MW2", mw2X, mpY - 70, () => App.Plant?.FillLevelMW2 ?? FillState.Unknown);
MP1 = new Pump("MP1", mw1X, mpY, () => App.Plant?.IsMP1Active ?? false, () => App.Plant?.MP1HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
MP2 = new Pump("MP2", mw2X, mpY, () => App.Plant?.IsMP2Active ?? false, () => App.Plant?.MP2HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
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?.MP2 ?? default, () => App.Plant?.MP2HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
var x1 = new PipeJoin(mw1X, mpY + 40);
var x2 = new PipeJoin(mw2X, mpY + 40);
@@ -75,20 +87,20 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(MP1, x1);
CreatePipe(MP2, x2);
var v1 = CreateValve(1, mw1X, mpY + 70);
var v2 = CreateValve(2, mw2X, mpY + 70);
var v3 = CreateValve(3, mw2X + 30, mpY + 70);
var v4 = CreateValve(4, mw1X - 30, mpY + 70);
var v1 = CreateValve(1, mw1X, mpY + 80);
var v2 = CreateValve(2, mw2X, mpY + 80);
var v3 = CreateValve(3, mw2X + 30, mpY + 80);
var v4 = CreateValve(4, mw1X - 30, mpY + 80);
CreatePipe(x1, v1);
CreatePipe(x1, v4);
CreatePipe(x2, v2);
CreatePipe(x2, v3);
var x3 = new PipeJoin(mw1X, mpY + 100);
var x4 = new PipeJoin(mw1X, ltg1Y);
var x5 = new PipeJoin(mw2X, mpY + 130);
var x6 = new PipeJoin(mw2X, ltg2Y);
var x3 = new PipeJoin(mw1X, ltg1Y);
var x4 = new PipeJoin(x1X, ltg1Y);
var x5 = new PipeJoin(mw1X - 30, ltg2Y);
var x6 = new PipeJoin(x1X - 30, ltg2Y);
CreatePipe(v1, x3, true);
CreatePipe(v3, x3, true);
@@ -97,23 +109,23 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(v4, x5, true);
CreatePipe(x5, x6, true);
var v5 = CreateValve(5, mw1X + 50, ltg1Y);
var v6 = CreateValve(6, mw1X, ltg2Y + 40);
var v7 = CreateValve(7, (mw2X + mw1X) / 2, ltg2Y);
var v8 = CreateValve(8, mw2X, ltg2Y + 40);
var v5 = CreateValve(5, x1X + 30, ltg1Y);
var v6 = CreateValve(6, x1X + 30, ltg2Y + 40);
var v7 = CreateValve(7, x1X + 30, ltg2Y);
var v8 = CreateValve(8, x1X - 30, ltg2Y + 40);
var v9 = CreateValve(9, ltg2X, 500);
CreatePipe(x4, v5);
CreatePipe(x4, v6);
CreatePipe(x4, v6, true);
CreatePipe(x6, v7);
CreatePipe(x6, v8);
CreatePipe(v7, v9);
CreatePipe(v7, v9, label: "Ltg. 2", labelOffsetX: 30);
var xv15 = new PipeJoin(ltg1X, wt1Y + 40);
var xv11 = new PipeJoin(ltg2X, wt1Y + 70);
var x7 = new PipeJoin(ltg2X, 460);
CreatePipe(v5, xv15);
CreatePipe(v5, xv15, label: "Ltg. 1", labelOffsetX: 30);
CreatePipe(v9, x7);
CreatePipe(x7, xv11);
@@ -179,37 +191,37 @@ namespace PamhagenSysCtrl.Helpers {
var xv23 = new PipeJoin(t1X + 15, ltg6Y);
var v22 = CreateValve(22, t1X - 15, tValveY);
var v23 = CreateValve(23, t1X + 15, tValveY);
var t1 = CreateTank(1, t1X, tanksY, 100000);
CreatePipe(xv20, xv22, true);
CreatePipe(xv21, xv23, true);
Tank1 = CreateTank(1, t1X, tanksY, 35000);
CreatePipe(xv20, xv22, true, label: "Ltg. 7", labelOffsetX: 50);
CreatePipe(xv21, xv23, true, label: "Ltg. 6", labelOffsetX: 20);
CreatePipe(xv22, v22);
CreatePipe(xv23, v23);
CreateInlet(v22, t1);
CreateInlet(v23, t1);
CreateInlet(v22, Tank1);
CreateInlet(v23, Tank1);
var xv24 = new PipeJoin(t2X - 15, ltg7Y);
var xv25 = new PipeJoin(t2X + 15, ltg6Y);
var v24 = CreateValve(24, t2X - 15, tValveY);
var v25 = CreateValve(25, t2X + 15, tValveY);
var t2 = CreateTank(2, t2X, tanksY, 100000);
Tank2 = CreateTank(2, t2X, tanksY, 35000);
CreatePipe(xv22, xv24);
CreatePipe(xv23, xv25);
CreatePipe(xv24, v24);
CreatePipe(xv25, v25);
CreateInlet(v24, t2);
CreateInlet(v25, t2);
CreateInlet(v24, Tank2);
CreateInlet(v25, Tank2);
var xv26 = new PipeJoin(t3X - 15, ltg7Y);
var xv27 = new PipeJoin(t3X + 15, ltg6Y);
var v26 = CreateValve(26, t3X - 15, tValveY);
var v27 = CreateValve(27, t3X + 15, tValveY);
var t3 = CreateTank(3, t3X, tanksY, 100000);
Tank3 = CreateTank(3, t3X, tanksY, 35000);
CreatePipe(xv24, xv26);
CreatePipe(xv25, xv27);
CreatePipe(xv26, v26);
CreatePipe(xv27, v27);
CreateInlet(v26, t3);
CreateInlet(v27, t3);
CreateInlet(v26, Tank3);
CreateInlet(v27, Tank3);
var v28 = CreateValve(28, (t3X + t4X) / 2, ltg7Y);
var v29 = CreateValve(29, (t3X + t4X) / 2, ltg6Y);
@@ -220,25 +232,25 @@ namespace PamhagenSysCtrl.Helpers {
var xv31 = new PipeJoin(t4X + 15, ltg6Y);
var v30 = CreateValve(30, t4X - 15, tValveY);
var v31 = CreateValve(31, t4X + 15, tValveY);
var t4 = CreateTank(4, t4X, tanksY, 100000);
Tank4 = CreateTank(4, t4X, tanksY, 35000);
CreatePipe(v28, xv30);
CreatePipe(v29, xv31);
CreatePipe(xv30, v30);
CreatePipe(xv31, v31);
CreateInlet(v30, t4);
CreateInlet(v31, t4);
CreateInlet(v30, Tank4);
CreateInlet(v31, Tank4);
var xv32 = new PipeJoin(t5X - 15, ltg7Y);
var xv33 = new PipeJoin(t5X + 15, ltg6Y);
var v32 = CreateValve(32, t5X - 15, tValveY);
var v33 = CreateValve(33, t5X + 15, tValveY);
var t5 = CreateTank(5, t5X, tanksY, 100000);
CreatePipe(xv30, xv32);
CreatePipe(xv31, xv33);
Tank5 = CreateTank(5, t5X, tanksY, 35000);
CreatePipe(xv30, xv32, label: "Ltg. 7", labelOffsetX: 30);
CreatePipe(xv31, xv33, label: "Ltg. 6", labelOffsetX: 0);
CreatePipe(xv32, v32);
CreatePipe(xv33, v33);
CreateInlet(v32, t5);
CreateInlet(v33, t5);
CreateInlet(v32, Tank5);
CreateInlet(v33, Tank5);
var v34 = CreateValve(34, (t5X + t6X) / 2, ltg7Y);
var v35 = CreateValve(35, (t5X + t6X) / 2, ltg6Y);
@@ -249,58 +261,58 @@ namespace PamhagenSysCtrl.Helpers {
var xv37 = new PipeJoin(t6X - 15, ltg7Y);
var v36 = CreateValve(36, t6X + 15, tValveY);
var v37 = CreateValve(37, t6X - 15, tValveY);
var t6 = CreateTank(6, t6X, tanksY, 100000);
Tank6 = CreateTank(6, t6X, tanksY, 35000);
CreatePipe(v34, xv37);
CreatePipe(v35, xv36);
CreatePipe(xv37, v37);
CreatePipe(xv36, v36);
CreateInlet(v36, t6);
CreateInlet(v37, t6);
CreateInlet(v36, Tank6);
CreateInlet(v37, Tank6);
var xv38 = new PipeJoin(t7X + 15, ltg6Y);
var xv39 = new PipeJoin(t7X - 15, ltg7Y);
var v38 = CreateValve(38, t7X + 15, tValveY);
var v39 = CreateValve(39, t7X - 15, tValveY);
var t7 = CreateTank(7, t7X, tanksY, 100000);
Tank7 = CreateTank(7, t7X, tanksY, 35000);
CreatePipe(xv37, xv39);
CreatePipe(xv36, xv38);
CreatePipe(xv38, v38);
CreatePipe(xv39, v39);
CreateInlet(v38, t7);
CreateInlet(v39, t7);
CreateInlet(v38, Tank7);
CreateInlet(v39, Tank7);
var xv40 = new PipeJoin(t8X + 15, ltg6Y);
var xv41 = new PipeJoin(t8X - 15, ltg7Y);
var v40 = CreateValve(40, t8X + 15, tValveY);
var v41 = CreateValve(41, t8X - 15, tValveY);
var t8 = CreateTank(8, t8X, tanksY, 100000);
CreatePipe(xv38, xv40);
CreatePipe(xv39, xv41);
Tank8 = CreateTank(8, t8X, tanksY, 35000);
CreatePipe(xv38, xv40, label: "Ltg. 6", labelOffsetX: 0);
CreatePipe(xv39, xv41, label: "Ltg. 7", labelOffsetX: 30);
CreatePipe(xv40, v40);
CreatePipe(xv41, v41);
CreateInlet(v40, t8);
CreateInlet(v41, t8);
CreateInlet(v40, Tank8);
CreateInlet(v41, Tank8);
var v42 = CreateValve(42, t9X + 15, tValveY);
var v43 = CreateValve(43, t9X - 15, tValveY);
var t9 = CreateTank(9, t9X, tanksY, 100000);
Tank9 = CreateTank(9, t9X, tanksY, 35000);
CreatePipe(xv40, v42);
CreatePipe(xv41, v43);
CreateInlet(v42, t9);
CreateInlet(v43, t9);
CreateInlet(v42, Tank9);
CreateInlet(v43, Tank9);
var xp12 = new PipeJoin((t1X + t2X) / 2, tanksY + 80);
P12 = new Pump("P1/2", (t1X + t2X) / 2, tanksY + 110, () => App.Plant?.IsP12Active ?? false, () => App.Plant?.P12HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
CreateOutlet(t1, xp12);
CreateOutlet(t2, xp12);
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(Tank2, xp12);
CreatePipe(xp12, P12);
P3 = new Pump("P3", t3X, tanksY + 110, () => App.Plant?.IsP3Active ?? false, () => App.Plant?.P3HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
P4 = new Pump("P4", t4X, tanksY + 110, () => App.Plant?.IsP4Active ?? false, () => App.Plant?.P4HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
P5 = new Pump("P5", t5X, tanksY + 110, () => App.Plant?.IsP5Active ?? false, () => App.Plant?.P5HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
CreateOutlet(t3, P3);
CreateOutlet(t4, P4);
CreateOutlet(t5, P5);
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 + 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 + 110, () => App.Plant?.P5 ?? default, () => App.Plant?.P5HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
CreateOutlet(Tank3, P3);
CreateOutlet(Tank4, P4);
CreateOutlet(Tank5, P5);
var v44 = CreateValve(44, (t1X + t2X) / 2, tanksY + 150);
var v45 = CreateValve(45, t3X, tanksY + 150);
@@ -318,7 +330,7 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(v45, xv45);
CreatePipe(v47, xv47);
CreatePipe(xv44, v10);
CreatePipe(xv44, v10, label: "Ltg. 5");
CreatePipe(v48, xv47, true);
CreateTwoWayPipe(xv47, v46);
CreateTwoWayPipe(v46, xv45);
@@ -327,11 +339,11 @@ namespace PamhagenSysCtrl.Helpers {
var xt6 = new PipeJoin(t6X, tanksY + 110);
var xt7 = new PipeJoin(t7X, tanksY + 110);
var xt8 = new PipeJoin(t8X, tanksY + 110);
P6 = new Pump("P6", t6X, tanksY + 150, () => App.Plant?.IsP6Active ?? false, () => App.Plant?.P6HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
CreateOutlet(t6, xt6);
CreateOutlet(t7, xt7);
CreateOutlet(t8, xt8);
CreateOutlet(t9, xt8);
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(Tank7, xt7);
CreateOutlet(Tank8, xt8);
CreateOutlet(Tank9, xt8);
CreatePipe(xt8, xt7);
CreatePipe(xt7, xt6);
CreatePipe(xt6, P6);
@@ -339,73 +351,107 @@ namespace PamhagenSysCtrl.Helpers {
var v49 = CreateValve(49, t4X, tanksY + 220);
CreatePipe(xv47, v49);
var press1 = new Press("Presse 1", pr1X, prY, 200000, () => App.Plant?.FillLevelPress1 ?? FillState.Unknown);
var press2 = new Press("Presse 2", pr2X, prY, 150000, () => App.Plant?.FillLevelPress2 ?? FillState.Unknown);
var v50 = CreateValve(50, pr1X - 45, prY - 100);
var v51 = CreateValve(51, pr1X - 15, prY - 100);
var v52 = CreateValve(52, pr1X + 15, prY - 100);
var v53 = CreateValve(53, pr1X + 45, prY - 100);
var v54 = CreateValve(54, pr2X - 45, prY - 100);
var v55 = CreateValve(55, pr2X - 15, prY - 100);
var v56 = CreateValve(56, pr2X + 15, prY - 100);
var v57 = CreateValve(57, pr2X + 45, prY - 100);
var xp1 = new PipeJoin(pr1X - 15, 610);
var xp2 = new PipeJoin(pr1X + 15, 640);
var xp3 = new PipeJoin(pr2X - 45, 550);
var xp4 = new PipeJoin(pr2X + 45, 580);
var prX = (pr1X + pr2X) / 2;
var pr1cX = prX - 15;
var pr2cX = prX + 15;
Press1 = new Press("Presse 1", pr1X, prY, 20000, () => FillState.Unknown);
Press2 = new Press("Presse 2", pr2X, prY, 15000, () => FillState.Unknown);
var v50 = CreateValve(50, pr2cX + 30, 580);
var v51 = CreateValve(51, pr1cX - 30, 580);
var v52 = CreateValve(52, pr1cX - 30, 610);
var v53 = CreateValve(53, pr2cX + 30, 610);
var v54 = CreateValve(54, pr2cX + 30, 550);
var v55 = CreateValve(55, pr1cX - 30, 550);
var v56 = CreateValve(56, pr1cX - 30, 640);
var v57 = CreateValve(57, pr2cX + 30, 640);
var xp1 = new PipeJoin(pr1cX - 70, 580);
var xp2 = new PipeJoin(pr1cX - 70, 610);
var xp3 = new PipeJoin(pr2cX + 70, 550);
var xp4 = new PipeJoin(pr2cX + 70, 640);
var xp5 = new PipeJoin(pr1cX, 580);
var xp6 = new PipeJoin(pr1cX, 610);
var xp7 = new PipeJoin(pr2cX, 550);
var xp8 = new PipeJoin(pr2cX, 640);
CreatePipe(v6, xp1, true);
CreatePipe(v8, xp2, true);
CreatePipe(v49, xp3, true);
CreatePipe(P6, xp4, true);
CreatePipe(xp1, v51);
CreatePipe(xp1, v55);
CreatePipe(xp2, v52);
CreatePipe(xp2, v56);
CreatePipe(xp3, v50);
CreatePipe(xp3, v54);
CreatePipe(xp4, v53);
CreatePipe(xp4, v57);
CreatePipe(v6, xp1, true, label: "Ltg. 3", labelOffsetX: 30);
CreatePipe(v8, xp2, true, label: "Ltg. 4", labelOffsetX: 90);
CreatePipe(v49, xp3, true, label: "Ltg. 8");
CreatePipe(P6, xp4, true, label: "Ltg. 9");
CreatePipe(xp1, v51, true);
CreatePipe(xp1, v55, true);
CreatePipe(xp2, v52, true);
CreatePipe(xp2, v56, true);
CreatePipe(xp3, v50, true);
CreatePipe(xp3, v54, true);
CreatePipe(xp4, v53, true);
CreatePipe(xp4, v57, true);
CreateInlet(v50, press1);
CreateInlet(v51, press1);
CreateInlet(v52, press1);
CreateInlet(v53, press1);
CreateInlet(v54, press2);
CreateInlet(v55, press2);
CreateInlet(v56, press2);
CreateInlet(v57, press2);
CreatePipe(v50, xp5);
CreatePipe(v51, xp5);
CreatePipe(v52, xp6);
CreatePipe(v53, xp6);
CreatePipe(v54, xp7);
CreatePipe(v55, xp7);
CreatePipe(v56, xp8);
CreatePipe(v57, xp8);
CreatePipe(xp5, xp6);
CreatePipe(xp7, xp8);
var zv1 = new CenterValve("ZV1", pr1cX, prY - 40, () => App.Plant?.Press1HasClearance ?? false);
var zv2 = new CenterValve("ZV2", pr2cX, prY - 40, () => App.Plant?.Press2HasClearance ?? false);
CreatePipe(xp6, zv1, true);
CreatePipe(xp8, zv2, true);
CreateInlet(zv1, Press1, true);
CreateInlet(zv2, Press2, true);
}
protected static Valve CreateValve(int n, double x, double y) {
return new(n, x, y, () => App.Plant?.IsValveOpen(n) ?? false, () => App.Plant?.WantValveOpen(n) ?? false);
protected Valve CreateValve(int n, double x, double y) {
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) {
return new($"Tank {n}", x, y, capacity, () => App.Plant?.TankFillLevels[n - 1] ?? FillState.Unknown);
}
public void TraverseSubgraph(INode start, HashSet<IEdge>? visited = null, Action<IEdge>? edgeAction = null, Action<Valve>? valveAction = null) {
visited ??= [];
private int PathCost(Path p) {
return
1000 * p.Hops.Count(h => h.Node is Valve) +
(p.Hops.Any(h => h.Node is Valve v && v.Nr == 15) && !p.Hops.Any(h => h.Node is Valve v && (v.Nr == 16 || v.Nr == 20)) ? 10 : 0) +
(p.Hops.Any(h => h.Node is Valve v && v.Nr == 11) && !p.Hops.Any(h => h.Node is Valve v && (v.Nr == 14 || v.Nr == 21)) ? 10 : 0) +
(p.Hops.Any(h => h.Node is Valve 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 Valve v || !v.LockedForPaths.Any(p => p.Start != points.First())), PathCost);
} else {
return GetPath(points, (n) => n is not ISink && (n is not Valve v || !v.IsLocked), PathCost);
}
}
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) {
var visited = _visited ?? [];
if (start is Valve v) {
valveAction?.Invoke(v);
return;
} else if (start is ISink || start is Pump) {
}
if (invalid(start)) {
return;
}
foreach (var edge in start.Inputs) {
if (visited.Contains(edge)) continue;
var o = edge.IsTwoWay && edge.Start == start ? edge.End : edge.Start;
edgeAction?.Invoke(edge);
edgeAction?.Invoke(start, reverse || _visited == null, edge);
visited.Add(edge);
TraverseSubgraph(o, visited, edgeAction, valveAction);
TraverseSubgraph(o, invalid, visited, edgeAction, valveAction, reverse || _visited == null);
}
foreach (var edge in start.Outputs) {
if (visited.Contains(edge)) continue;
var o = edge.IsTwoWay && edge.End == start ? edge.Start : edge.End;
edgeAction?.Invoke(edge);
edgeAction?.Invoke(start, reverse, edge);
visited.Add(edge);
TraverseSubgraph(o, visited, edgeAction, valveAction);
TraverseSubgraph(o, invalid, visited, edgeAction, valveAction, reverse);
}
}
@@ -436,8 +482,12 @@ namespace PamhagenSysCtrl.Helpers {
private void UpdateValvesAdjacientToPath(Path path, bool locked, bool? wantOpen = null) {
foreach (var (edge, node) in path.Hops) {
TraverseSubgraph(node, valveAction: (v) => {
v.IsLocked = locked;
TraverseSubgraph(node, (n) => n is Valve || n is ISink || n is Pump, valveAction: (v) => {
if (locked) {
v.LockedForPaths.Add(path);
} else {
v.LockedForPaths.Remove(path);
}
if (wantOpen.HasValue) {
if (wantOpen.Value) {
App.Plant?.OpenV(v.Nr);
@@ -449,18 +499,41 @@ namespace PamhagenSysCtrl.Helpers {
}
}
public void ColorPipesAdjacientToPath(Path path, Brush? color1, bool flowArrows, Brush? color2) {
public void ColorPipesAdjacientToPath(Path path, Brush? color1, MotorState flowArrows, Brush? color2) {
var color = color1;
var flowPathOffset = 0.0;
HashSet<IEdge> visited = [];
foreach (var (edge, node) in path.Hops) {
edge.Highlight = color1;
edge.Flow = flowArrows ? (edge.End == node, flowPathOffset) : null;
if (edge.Flow != null) flowPathOffset += edge.FlowLength;
if (flowArrows > MotorState.Halt) {
var dir = edge.End == node;
if (flowArrows == MotorState.Backward) dir = !dir;
edge.Flow = (dir, flowPathOffset);
flowPathOffset += edge.FlowLength;
}
visited.Add(edge);
if (node is Pump) color = color2;
TraverseSubgraph(node, visited, edgeAction: (e) => e.Highlight = color);
TraverseSubgraph(node, (n) => n is Valve || n is ISink || n is Pump, visited, edgeAction: (s, r, e) => e.Highlight = color);
}
}
public void ColorPipesByValves(INode start, Brush? color1, MotorState flowArrows, Brush? color2) {
var flowPathOffset = 0.0;
TraverseSubgraph(start, (n) => n is ISink || n is Outlet || !((n as Valve)?.IsOpen ?? (n as CenterValve)?.IsOpen ?? true), null,
edgeAction: (s, r, e) => {
if (((e.Start as Valve)?.IsOpen ?? true) && ((e.End as Valve)?.IsOpen ?? true)) {
e.Highlight = color1;
if (flowArrows == MotorState.Forward || flowArrows == MotorState.Backward) {
var dir = e.Start == s;
if (r) dir = !dir;
if (flowArrows == MotorState.Backward) dir = !dir;
e.Flow = (dir, flowPathOffset);
flowPathOffset += e.FlowLength;
}
} else {
e.Highlight = color2;
}
});
}
}
}
+105 -70
View File
@@ -9,11 +9,15 @@ namespace PamhagenSysCtrl.Helpers {
protected bool IsRunning = true;
protected Thread? BackgroundThread;
public bool ManualControlMode { get; set; }
public bool OverridePathClearances { get; set; }
public bool OverrideDryRunClearances { get; set; }
public event EventHandler<PlantEventArgs>? Update;
protected bool ActuatorsChanged = false;
protected PamhagenPlc.ActuatorStates Actuators;
protected PamhagenPlc.SensorStates Sensors = new(default, default, default, default, [
protected PamhagenPlc.SensorStates Sensors = new(default, default, default, default, default, default, default, [
FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown
], default);
@@ -26,12 +30,12 @@ namespace PamhagenSysCtrl.Helpers {
public bool AreValvesClosed(params int[] ns) => ns.All(IsValveClosed) && ns.All(WantValveClosed);
public int TroughSelector => Sensors.TroughSelector;
public bool IsMWSelected => Sensors.TroughSelector == 1;
public bool IsMW1Selected => Sensors.TroughSelector == 1;
public bool IsMW2Selected => Sensors.TroughSelector == 2;
public FillState FillLevelMW1 => Sensors.FillingLevels.MW1;
public FillState FillLevelMW2 => Sensors.FillingLevels.MW2;
public FillState FillLevelPress1 => Sensors.FillingLevels.Press1;
public FillState FillLevelPress2 => Sensors.FillingLevels.Press2;
public FillState FillLevelMW1 => Sensors.FillingLevelMW1;
public FillState FillLevelMW2 => Sensors.FillingLevelMW2;
public bool Press1HasClearance => Sensors.ClearancePress1;
public bool Press2HasClearance => Sensors.ClearancePress2;
public FillState FillLevelT1 => Sensors.FillingLevelTanks[0];
public FillState FillLevelT2 => Sensors.FillingLevelTanks[1];
public FillState FillLevelT3 => Sensors.FillingLevelTanks[2];
@@ -50,13 +54,14 @@ namespace PamhagenSysCtrl.Helpers {
public bool WantMW2Selected => !Actuators.GetV(58);
public bool IsTroughAugerActive => Actuators.TroughAuger;
public bool TroughAugerHasClearance => IsReblerActive && !(IsMW1Selected && FillLevelMW1 == FillState.Full) && !(IsMW2Selected && FillLevelMW2 == FillState.Full);
public bool IsReblerActive => Actuators.Rebler;
public bool IsStirrerMW1Active => Actuators.StirrerMW1;
public bool IsStirrerMW2Active => Actuators.StirrerMW2;
public bool IsBeltActive => Actuators.Belt;
public bool IsAuger1Active => Actuators.Auger1;
public bool IsAuger2Active => Actuators.Auger2;
public bool IsWasteDisposalActive => IsBeltActive || IsAuger1Active || IsAuger2Active;
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;
@@ -78,14 +83,32 @@ namespace PamhagenSysCtrl.Helpers {
protected static void OnUpdate(object? sender, PlantEventArgs evt) {
if (sender is not PamhagenPlant p) return;
if (p.IsPresseQuerSchneckeActive && !p.IsBeltActive) p.StartBelt();
if (!p.ManualControlMode) {
if (p.IsPresseQuerSchneckeActive && !p.IsAuger2Active) p.StartAuger2();
if (p.IsReblerActive && !p.IsAuger1Active) p.StartAuger1();
if (p.IsAuger1Active && !p.IsBeltActive) p.StartBelt();
if (p.IsBeltActive && !p.IsAuger2Active) p.StartAuger2();
if (p.IsTroughAugerActive && (!p.IsReblerActive || (p.IsMWSelected && p.FillLevelMW1 == FillState.Full) || (p.IsMW2Selected && p.FillLevelMW2 == FillState.Full)))
if (p.IsAuger1Active && !p.IsAuger2Active) p.StartAuger2();
if (p.IsAuger2Active && !p.IsAuger3Active) p.StartAuger3();
if (p.IsTroughAugerActive && !p.TroughAugerHasClearance)
p.StopTroughAuger();
}
if (p.IsMP1Active && !p.MP1HasClearance) p.StopMP1();
if (p.IsMP2Active && !p.MP2HasClearance) p.StopMP2();
if (!p.ManualControlMode) {
if (p.MP1 == MotorState.Forward && p.FillLevelMW1 >= FillState.Half && !p.IsStirrerMW1Active) {
p.StartStirrerMW1();
} else if (p.IsStirrerMW1Active && (!p.IsMP1Active || p.FillLevelMW1 < FillState.Half)) {
p.StopStirrerMW1();
}
if (p.MP2 == MotorState.Forward && p.FillLevelMW2 >= FillState.Half && !p.IsStirrerMW2Active) {
p.StartStirrerMW2();
} else if (p.IsStirrerMW2Active && (!p.IsMP2Active || p.FillLevelMW2 < FillState.Half)) {
p.StopStirrerMW2();
}
}
if (p.IsP12Active && !p.P12HasClearance) p.StopP12();
if (p.IsP3Active && !p.P3HasClearance) p.StopP3();
if (p.IsP4Active && !p.P4HasClearance) p.StopP4();
@@ -210,9 +233,10 @@ namespace PamhagenSysCtrl.Helpers {
(a && ((HasWTClearanceAA && HasT9ClearanceA) || (HasWTClearanceAB && HasT9ClearanceB))) ||
(b && ((HasWTClearanceBA && HasT9ClearanceA) || (HasWTClearanceBB && HasT9ClearanceB)));
public bool MP1HasClearance => FillLevelMW1 != FillState.Empty && (
(MP1HasClearancePress1 && FillLevelPress1 != FillState.Full) ||
(MP1HasClearancePress2 && FillLevelPress2 != FillState.Full) ||
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) ||
@@ -224,11 +248,11 @@ namespace PamhagenSysCtrl.Helpers {
(HasClearanceTank9(MP1HasClearanceTanksA, MP1HasClearanceTanksB) && FillLevelT9 != FillState.Full) ||
false);
protected bool MP1HasClearancePress1 =>
(AreValvesOpen(1, 6, 51) && AreValvesClosed(3, 4, 5, 55)) ||
(AreValvesOpen(4, 8, 52) && AreValvesClosed(1, 2, 7, 56));
(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)) ||
(AreValvesOpen(4, 8, 56) && AreValvesClosed(1, 2, 7, 52));
(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);
@@ -237,9 +261,10 @@ namespace PamhagenSysCtrl.Helpers {
set => Actuators.MP1 = (value != MotorState.Halt && value != MotorState.Forward && value != MotorState.Backward) ? throw new ArgumentException("Invalid state for MP1") : value;
}
public bool MP2HasClearance => FillLevelMW2 != FillState.Empty && (
(MP2HasClearancePress1 && FillLevelPress1 != FillState.Full) ||
(MP2HasClearancePress2 && FillLevelPress2 != FillState.Full) ||
public bool MP2HasClearance => (OverrideDryRunClearances || FillLevelMW2 != FillState.Empty) && (
OverridePathClearances ||
(MP2HasClearancePress1 && Press1HasClearance) ||
(MP2HasClearancePress2 && Press2HasClearance) ||
(HasClearanceTank1(MP2HasClearanceTanksA, MP2HasClearanceTanksB) && FillLevelT1 != FillState.Full) ||
(HasClearanceTank2(MP2HasClearanceTanksA, MP2HasClearanceTanksB) && FillLevelT2 != FillState.Full) ||
(HasClearanceTank3(MP2HasClearanceTanksA, MP2HasClearanceTanksB) && FillLevelT3 != FillState.Full) ||
@@ -251,11 +276,11 @@ namespace PamhagenSysCtrl.Helpers {
(HasClearanceTank9(MP2HasClearanceTanksA, MP2HasClearanceTanksB) && FillLevelT9 != FillState.Full) ||
false);
protected bool MP2HasClearancePress1 =>
(AreValvesOpen(2, 8, 52) && AreValvesClosed(3, 4, 7, 56)) ||
(AreValvesOpen(3, 6, 51) && AreValvesClosed(1, 2, 5, 55));
(AreValvesOpen(2, 8, 52) && AreValvesClosed(3, 4, 7, 50, 51, 53, 56)) ||
(AreValvesOpen(3, 6, 51) && AreValvesClosed(1, 2, 5, 50, 52, 53, 55));
protected bool MP2HasClearancePress2 =>
(AreValvesOpen(2, 8, 56) && AreValvesClosed(3, 4, 7, 52)) ||
(AreValvesOpen(3, 6, 55) && AreValvesClosed(2, 1, 5, 51));
(AreValvesOpen(2, 8, 56) && AreValvesClosed(3, 4, 7, 52, 54, 55, 57)) ||
(AreValvesOpen(3, 6, 55) && AreValvesClosed(2, 1, 5, 51, 54, 56, 57));
protected bool MP2HasClearanceTanksA => AreValvesOpen(3, 5) && AreValvesClosed(1, 2, 6);
protected bool MP2HasClearanceTanksB => AreValvesOpen(2, 7, 9) && AreValvesClosed(3, 4, 8, 10);
@@ -264,9 +289,10 @@ namespace PamhagenSysCtrl.Helpers {
set => Actuators.MP2 = (value != MotorState.Halt && value != MotorState.Forward && value != MotorState.Backward) ? throw new ArgumentException("Invalid state for MP2") : value;
}
public bool P12HasClearance => (FillLevelT1 != FillState.Empty || FillLevelT2 != FillState.Empty) && (
(P12HasClearancePress1 && FillLevelPress1 != FillState.Full) ||
(P12HasClearancePress2 && FillLevelPress2 != FillState.Full) ||
public bool P12HasClearance => (OverrideDryRunClearances || FillLevelT1 != FillState.Empty || FillLevelT2 != FillState.Empty) && (
OverridePathClearances ||
(P12HasClearancePress1 && Press1HasClearance) ||
(P12HasClearancePress2 && Press2HasClearance) ||
(HasClearanceTank1(false, P12HasClearanceTanksB) && FillLevelT1 != FillState.Full) ||
(HasClearanceTank2(false, P12HasClearanceTanksB) && FillLevelT2 != FillState.Full) ||
(HasClearanceTank3(false, P12HasClearanceTanksB) && FillLevelT3 != FillState.Full) ||
@@ -277,8 +303,8 @@ namespace PamhagenSysCtrl.Helpers {
(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);
protected bool P12HasClearancePress2 => AreValvesOpen(44, 46, 49, 54) && AreValvesClosed(10, 45, 47, 48, 50);
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 {
@@ -286,9 +312,10 @@ namespace PamhagenSysCtrl.Helpers {
set => Actuators.P12 = (value != MotorState.Halt && value != MotorState.Forward) ? throw new ArgumentException("Invalid state for P12") : value;
}
public bool P3HasClearance => FillLevelT3 != FillState.Empty && (
(P3HasClearancePress1 && FillLevelPress1 != FillState.Full) ||
(P3HasClearancePress2 && FillLevelPress2 != FillState.Full) ||
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) ||
@@ -299,8 +326,8 @@ namespace PamhagenSysCtrl.Helpers {
(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, 54);
protected bool P3HasClearancePress2 => AreValvesOpen(45, 46, 49, 54) && AreValvesClosed(10, 44, 47, 48, 50);
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 {
@@ -308,9 +335,10 @@ namespace PamhagenSysCtrl.Helpers {
set => Actuators.P3 = (value != MotorState.Halt && value != MotorState.Forward) ? throw new ArgumentException("Invalid state for P3") : value;
}
public bool P4HasClearance => FillLevelT4 != FillState.Empty && (
(P4HasClearancePress1 && FillLevelPress1 != FillState.Full) ||
(P4HasClearancePress2 && FillLevelPress2 != FillState.Full) ||
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) ||
@@ -321,8 +349,8 @@ namespace PamhagenSysCtrl.Helpers {
(HasClearanceTank8(false, P4HasClearanceTanksB) && FillLevelT8 != FillState.Full) ||
(HasClearanceTank9(false, P4HasClearanceTanksB) && FillLevelT9 != FillState.Full) ||
false);
protected bool P4HasClearancePress1 => AreValvesOpen(47, 49, 50) && AreValvesClosed(46, 48, 54);
protected bool P4HasClearancePress2 => AreValvesOpen(47, 49, 54) && AreValvesClosed(46, 48, 50);
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 {
@@ -330,9 +358,10 @@ namespace PamhagenSysCtrl.Helpers {
set => Actuators.P4 = (value != MotorState.Halt && value != MotorState.Forward) ? throw new ArgumentException("Invalid state for P4") : value;
}
public bool P5HasClearance => FillLevelT5 != FillState.Empty && (
(P5HasClearancePress1 && FillLevelPress1 != FillState.Full) ||
(P5HasClearancePress2 && FillLevelPress2 != FillState.Full) ||
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) ||
@@ -343,8 +372,8 @@ namespace PamhagenSysCtrl.Helpers {
(HasClearanceTank8(false, P5HasClearanceTanksB) && FillLevelT8 != FillState.Full) ||
(HasClearanceTank9(false, P5HasClearanceTanksB) && FillLevelT9 != FillState.Full) ||
false);
protected bool P5HasClearancePress1 => AreValvesOpen(48, 49, 50) && AreValvesClosed(46, 47, 54);
protected bool P5HasClearancePress2 => AreValvesOpen(48, 49, 54) && AreValvesClosed(46, 47, 50);
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 {
@@ -352,9 +381,9 @@ namespace PamhagenSysCtrl.Helpers {
set => Actuators.P5 = (value != MotorState.Halt && value != MotorState.Forward) ? throw new ArgumentException("Invalid state for P5") : value;
}
public bool P6HasClearance =>
(IsValveOpen(53) && IsValveClosed(57) && FillLevelPress1 != FillState.Full) ||
(IsValveOpen(57) && IsValveClosed(53) && FillLevelPress2 != FillState.Full);
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;
@@ -373,18 +402,16 @@ namespace PamhagenSysCtrl.Helpers {
public void SelectMW1() {
Actuators.SetV(58, true);
//if (IsReblerActive) Actuators.StirrerMW1 = true;
ActuatorsChanged = true;
}
public void SelectMW2() {
Actuators.SetV(58, false);
//if (IsReblerActive) Actuators.StirrerMW2 = true;
ActuatorsChanged = true;
}
public void StartTroughAuger() {
if (!IsReblerActive || (IsMWSelected && FillLevelMW1 == FillState.Full) || (IsMW2Selected && FillLevelMW2 == FillState.Full))
if (!ManualControlMode && !TroughAugerHasClearance)
throw new NoClearanceException("Schnecke Auffangwanne hat keine Freigabe");
Actuators.TroughAuger = true;
ActuatorsChanged = true;
@@ -397,12 +424,12 @@ namespace PamhagenSysCtrl.Helpers {
public void StartRebler() {
Actuators.Rebler = true;
StartAuger1();
if (!ManualControlMode) StartAuger1();
ActuatorsChanged = true;
}
public void StopRebler() {
StopTroughAuger();
if (!ManualControlMode) StopTroughAuger();
Actuators.Rebler = false;
ActuatorsChanged = true;
}
@@ -427,42 +454,42 @@ namespace PamhagenSysCtrl.Helpers {
ActuatorsChanged = true;
}
public void StartBelt() {
Actuators.Belt = true;
StartAuger2();
ActuatorsChanged = true;
}
public void StopBelt() {
if (IsPresseQuerSchneckeActive) throw new NoClearanceException("Presse-Querschnecke aktiv");
if (IsAuger1Active) throw new NoClearanceException("Förderschnecke 1 aktiv");
Actuators.Belt = false;
ActuatorsChanged = true;
}
public void StartAuger1() {
Actuators.Auger1 = true;
StartBelt();
if (!ManualControlMode) StartAuger2();
ActuatorsChanged = true;
}
public void StopAuger1() {
if (IsReblerActive) throw new NoClearanceException("Rebler aktiv");
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 (IsBeltActive) throw new NoClearanceException("Förderband aktiv");
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;
@@ -475,35 +502,43 @@ namespace PamhagenSysCtrl.Helpers {
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;
}
+35 -35
View File
@@ -5,7 +5,10 @@ namespace PamhagenSysCtrl.Helpers {
public record struct SensorStates(
long PressureSensors,
(FillState MW1, FillState MW2, FillState Press1, FillState Press2) FillingLevels,
FillState FillingLevelMW1,
FillState FillingLevelMW2,
bool ClearancePress1,
bool ClearancePress2,
int TroughSelector,
double GradationOe,
FillState[] FillingLevelTanks,
@@ -17,7 +20,7 @@ namespace PamhagenSysCtrl.Helpers {
public record struct ActuatorStates(
long Valves,
MotorState MP1, MotorState MP2, MotorState P12, MotorState P3, MotorState P4, MotorState P5, MotorState P6,
bool TroughAuger, bool Rebler, bool StirrerMW1, bool StirrerMW2, bool Belt, bool Auger1, bool Auger2, bool GradationPump
bool TroughAuger, bool Rebler, bool StirrerMW1, bool StirrerMW2, bool Auger2, bool Auger3, bool Auger1, bool GradationPump
) {
public readonly bool GetV(int n) => (Valves & (1L << (n - 1))) != 0;
public void SetV(int n, bool val) {
@@ -40,31 +43,28 @@ namespace PamhagenSysCtrl.Helpers {
GC.SuppressFinalize(this);
}
protected static FillState Int3ToFillState(int n) {
return n switch {
7 => FillState.Full,
6 => FillState.Half,
4 => FillState.Filled,
0 => FillState.Empty,
_ => FillState.Invalid,
};
protected static FillState Int3ToFillState(int n, bool bigEndian = true) {
return
(bigEndian && (n & 0x4) != 0) || (!bigEndian && (n & 0x1) != 0) ? FillState.Full :
(bigEndian && (n & 0x2) != 0) || (!bigEndian && (n & 0x2) != 0) ? FillState.Half :
(bigEndian && (n & 0x1) != 0) || (!bigEndian && (n & 0x4) != 0) ? FillState.Filled :
n == 0 ? FillState.Empty :
FillState.Invalid;
}
protected static FillState Int2ToFillState(int n) {
return n switch {
2 => FillState.Full,
0 => FillState.Filled,
1 => FillState.Empty,
_ => FillState.Invalid,
};
protected static FillState Int2ToFillState(int n, bool bigEndian = true) {
return
(bigEndian && (n & 0x2) != 0) || (!bigEndian && (n & 0x1) != 0) ? FillState.Full :
(bigEndian && (n & 0x1) != 0) || (!bigEndian && (n & 0x2) != 0) ? FillState.Filled :
n == 0 ? FillState.Empty :
FillState.Invalid;
}
protected static FillState Int1ToFillState(int n) {
return n switch {
1 => FillState.Full,
0 => FillState.Unknown,
_ => FillState.Invalid,
};
return
n == 1 ? FillState.Full :
n == 0 ? FillState.Unknown :
FillState.Invalid;
}
protected static MotorState Int2ToPumpState(int n, bool inverse) {
@@ -107,10 +107,10 @@ namespace PamhagenSysCtrl.Helpers {
long pressure = (long)r1 | ((long)r2 << 16) | ((long)r3 << 32) | ((long)r4 << 48);
pressure = (pressure & 0x01FFFFFFFFFFFFFF) | ((pressure & 0x0600000000000000) << 1);
var mw1 = Int3ToFillState((f1 ) & 0x7); // 111 011 001 000
var mw2 = Int3ToFillState((f1 >> 3) & 0x7); // 111 011 001 000
var p1 = Int1ToFillState((f1 >> 6) & 0x1); // 1 0
var p2 = Int1ToFillState((f1 >> 7) & 0x1); // 1 0
var mw1 = Int3ToFillState((f1 ) & 0x7, false); // 111 011 001 000
var mw2 = Int3ToFillState((f1 >> 3) & 0x7, false); // 111 011 001 000
var p1 = ((f1 >> 6) & 0x1) == 0; // 1 0
var p2 = ((f1 >> 7) & 0x1) == 0; // 1 0
var wp = ((f1 >> 8) & 0x3); // 10 01
// Masseligerät : 4mA => 50 Oe
@@ -126,18 +126,18 @@ namespace PamhagenSysCtrl.Helpers {
// -> factor: 0,03125 + (offset) 25 Oe
double oe = v * 0.03125 + 25;
var t1 = Int2ToFillState((f2 ) & 0x3); // 10 01 00
var t2 = Int2ToFillState((f2 >> 2) & 0x3); // 10 01 00
var t3 = Int2ToFillState((f2 >> 4) & 0x3); // 10 01 00
var t4 = Int2ToFillState((f2 >> 6) & 0x3); // 10 01 00
var t5 = Int2ToFillState((f2 >> 8) & 0x3); // 10 01 00
var t1 = Int1ToFillState(((f2 ) & 0x3) >> 1); // 1X 0X
var t2 = Int1ToFillState(((f2 >> 2) & 0x3) >> 1); // 1X 0X
var t3 = Int1ToFillState(((f2 >> 4) & 0x3) >> 1); // 1X 0X
var t4 = Int1ToFillState(((f2 >> 6) & 0x3) >> 1); // 1X 0X
var t5 = Int1ToFillState(((f2 >> 8) & 0x3) >> 1); // 1X 0X
var t6 = Int1ToFillState((f2 >> 10) & 0x1); // 1 0
var t7 = Int1ToFillState((f2 >> 11) & 0x1); // 1 0
var t8 = Int1ToFillState((f2 >> 12) & 0x1); // 1 0
var t9 = Int1ToFillState((f2 >> 13) & 0x1); // 1 0
var q1 = Int1ToPumpState((f2 >> 15) & 0x1); // 1 0
return new SensorStates(pressure, (mw1, mw2, p1, p2), wp == 3 ? -1 : wp == 1 ? 2 : wp == 2 ? 1 : 0, oe, [t1, t2, t3, t4, t5, t6, t7, t8, t9], q1);
return new SensorStates(pressure, mw1, mw2, p1, p2, wp == 3 ? -1 : wp == 1 ? 2 : wp == 2 ? 1 : 0, oe, [t1, t2, t3, t4, t5, t6, t7, t8, t9], q1);
}
protected async Task<(ushort RW000, ushort RW001, ushort RW002, ushort RW003, ushort RW004, ushort RW005, ushort RW006)> ReadOutputRegisters() {
@@ -212,9 +212,9 @@ namespace PamhagenSysCtrl.Helpers {
(state.GetV(58) ? 0x04 : 0x00) |
(state.StirrerMW1 ? 0x08 : 0x00) |
(state.StirrerMW2 ? 0x10 : 0x00) |
(state.Belt ? 0x20 : 0x00) |
(state.Auger1 ? 0x40 : 0x00) |
(state.Auger2 ? 0x80 : 0x00);
(state.Auger2 ? 0x20 : 0x00) |
(state.Auger3 ? 0x40 : 0x00) |
(state.Auger1 ? 0x80 : 0x00);
int measuring =
(state.GetV(59) ? 0x01 : 0x00) |
@@ -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;
}
}
}
+28 -22
View File
@@ -12,10 +12,10 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
}
public Pipe CreatePipe(INode start, INode end, bool verticalFirst = false) {
public Pipe CreatePipe(INode start, INode end, bool verticalFirst = false, string? label = null, double? labelOffsetX = null) {
Nodes.Add(start);
Nodes.Add(end);
var p = new Pipe(start, end, verticalFirst);
var p = new Pipe(start, end, label, verticalFirst, default, labelOffsetX);
start.Outputs.Add(p);
end.Inputs.Add(p);
Edges.Add(p);
@@ -25,7 +25,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public Pipe CreateTwoWayPipe(INode n1, INode n2, bool verticalFirst = false) {
Nodes.Add(n1);
Nodes.Add(n2);
var p = new Pipe(n1, n2, verticalFirst, twoWay: true);
var p = new Pipe(n1, n2, default, verticalFirst, twoWay: true);
n1.Inputs.Add(p);
n1.Outputs.Add(p);
n2.Inputs.Add(p);
@@ -34,10 +34,10 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
return p;
}
public Inlet CreateInlet(INode start, ISink end) {
public Inlet CreateInlet(INode start, ISink end, bool sideways = false) {
Nodes.Add(start);
Nodes.Add(end);
var p = new Inlet(start, end, start.CenterX > end.CenterX);
var p = new Inlet(start, end, sideways);
start.Outputs.Add(p);
end.Inputs.Add(p);
Edges.Add(p);
@@ -54,47 +54,53 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
return p;
}
public Path? GetPath(IEnumerable<INode> points, Func<INode, bool>? valid = null) {
public Path? GetPath(IEnumerable<INode> points, Func<INode, bool>? valid = null, Func<Path, int>? cost = null) {
if (points.Count() < 2) return null;
var start = points.First();
var path = new Path(start, []);
List<Path> paths = [new(start, [])];
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;
} else if (GetPath(start, end, valid, visited: [.. path.Hops.Select(h => h.Node)]) is not Path p) {
var newPaths = new List<Path>();
foreach (var path in paths) {
foreach (var subPath in GetPaths(start, end, visited: [.. path.Hops.Select(h => h.Node)], valid)) {
newPaths.Add(new(path.Start, [.. path.Hops, .. subPath.Hops]));
}
}
if (newPaths.Count == 0)
return null;
} else {
path.Hops = [.. path.Hops, .. p.Hops];
paths = newPaths;
start = end;
}
if (paths.Count == 0) {
return null;
} else {
cost ??= p => 0;
return paths.OrderBy(cost).First();
}
return path;
}
public Path? GetPath(INode start, INode end, Func<INode, bool>? valid = null, HashSet<INode>? visited = null) {
public IEnumerable<Path> GetPaths(INode start, INode end, HashSet<INode>? visited = null, Func<INode, bool>? valid = null) {
if (!Nodes.Contains(start) || !Nodes.Contains(end)) throw new ArgumentException("Start/end node not contained in graph");
valid ??= a => true;
visited = [.. visited ?? [], start];
List<(IEdge, INode)> hops = [];
Path? path = null;
foreach (var o in start.Outputs) {
var e = o.IsTwoWay && o.End == start ? o.Start : o.End;
if (e == end) {
return new(start, [(o, e)]);
yield return new(start, [(o, e)]);
continue;
} else if (!valid(e) || visited.Contains(e)) {
continue;
}
var p = GetPath(e, end, valid, visited);
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;
foreach (var p in GetPaths(e, end, visited, valid)) {
yield return new(start, [(o, e), .. p.Hops]);
}
}
}
return path != null ? new(start, path.Value.Hops) : null;
}
public void Draw(Canvas canvas) {
canvas.Children.Add(new Rectangle() {
@@ -41,6 +41,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
RenderTransform = new RotateTransform(270),
SnapsToDevicePixels = true,
};
_rect.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_rect.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2);
@@ -6,6 +6,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public INode Start { get; }
public INode End { get; }
public string? Label { get; }
public bool IsTwoWay { get; }
public bool Orientation { get; }
public Brush? Highlight { get; set; }
+4 -3
View File
@@ -8,6 +8,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public INode Start { get; set; }
public INode End => Sink;
public ISink Sink { get; set; }
public string? Label => null;
public bool IsTwoWay => false;
public bool Orientation { get; set; }
public Brush? Highlight { get; set; }
@@ -30,9 +31,9 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
var x1 = Start.CenterX;
var y1 = Start.CenterY;
var x2 = Start.CenterX;
var y2 = Sink.TopY - 10;
var x3 = Start.CenterX + (Orientation ? -20 : 20);
var y3 = Sink.TopY + 20;
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,
@@ -10,6 +10,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public INode Start => Source;
public INode End { get; set; }
public ISource Source { get; set; }
public string? Label => null;
public bool IsTwoWay => false;
public bool Orientation { get; set; }
public Brush? Highlight { get; set; }
+1 -1
View File
@@ -3,7 +3,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
INode Start,
IEnumerable<(IEdge Edge, INode Node)> Hops)
{
public readonly double Bends => Hops.Sum(h => (h.Node is not Valve && h.Node is not PipeJoin ? 1 : 0) + (h.Edge.Start.CenterX != h.Edge.End.CenterX && h.Edge.Start.CenterY != h.Edge.End.CenterY ? 1 : 0));
public readonly int Bends => Hops.Sum(h => (h.Node is not Valve && h.Node is not PipeJoin ? 1 : 0) + (h.Edge.Start.CenterX != h.Edge.End.CenterX && h.Edge.Start.CenterY != h.Edge.End.CenterY ? 1 : 0));
public readonly double Length => Hops.Sum(h => Math.Abs(h.Edge.Start.CenterX - h.Edge.End.CenterX) + Math.Abs(h.Edge.Start.CenterY - h.Edge.End.CenterY));
}
}
+21 -1
View File
@@ -1,3 +1,4 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
@@ -7,6 +8,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public INode Start { get; set; }
public INode End { get; set; }
public string? Label { get; set; }
public bool IsTwoWay { get; set; }
public bool Orientation { get; set; }
public Brush? Highlight { get; set; }
@@ -15,11 +17,15 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
private Polyline? _outer;
private Polyline? _inner;
private TextBlock? _text;
private FlowArrows? _flowArrows;
private double? _textOffsetX;
public Pipe(INode start, INode end, bool orientation = false, bool twoWay = false) {
public Pipe(INode start, INode end, string? label = null, bool orientation = false, bool twoWay = false, double? textOffsetX = null) {
Start = start;
End = end;
Label = label;
_textOffsetX = textOffsetX;
Orientation = orientation;
IsTwoWay = twoWay;
}
@@ -49,6 +55,20 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
};
canvas.Children.Add(_outer);
canvas.Children.Add(_inner);
if (Label != null) {
_text = new() {
Text = Label,
FontSize = 12,
Width = 50,
TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Bottom,
HorizontalAlignment = HorizontalAlignment.Center,
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);
}
_flowArrows = FlowArrows.Create(canvas, _inner);
}
+14 -4
View File
@@ -16,11 +16,12 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public ISet<IEdge> Outputs { get; init; }
public Brush? Highlight { get; set; }
public bool IsActive => CallbackActive();
public bool IsActive => State == MotorState.Forward || State == MotorState.Backward;
public MotorState State => CallbackState();
public bool HasClearance => CallbackClearance();
public bool IsSelected => CallbackSelected(this);
protected Func<bool> CallbackActive;
protected Func<MotorState> CallbackState;
protected Func<bool> CallbackClearance;
protected Func<Pump, bool> CallbackSelected;
@@ -28,11 +29,11 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
private Shape? _triangle;
private TextBlock? _text;
public Pump(string label, double x, double y, Func<bool> cbActive, Func<bool> cbClearance, Func<Pump, bool> cbSelected) {
public Pump(string label, double x, double y, Func<MotorState> cbState, Func<bool> cbClearance, Func<Pump, bool> cbSelected) {
CenterX = x;
CenterY = y;
Label = label;
CallbackActive = cbActive;
CallbackState = cbState;
CallbackClearance = cbClearance;
CallbackSelected = cbSelected;
Inputs = new HashSet<IEdge>();
@@ -75,6 +76,14 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
}
public void Update(bool isHovering, bool isPresed) {
if (App.Plant?.ManualControlMode ?? false) {
_circle?.Fill = Highlight ?? (
IsActive && isHovering ? PamhagenBrushes.Red :
IsActive ? PamhagenBrushes.Green :
HasClearance && isHovering ? PamhagenBrushes.Green :
HasClearance ? PamhagenBrushes.Yellow :
PamhagenBrushes.Red);
} else {
_circle?.Fill = Highlight ?? (
IsActive && isHovering ? PamhagenBrushes.Red :
IsActive ? PamhagenBrushes.Green :
@@ -84,4 +93,5 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
isHovering ? Brushes.LightGray : Brushes.WhiteSmoke);
}
}
}
}
+19 -5
View File
@@ -14,19 +14,21 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public string Label => $"V{Nr}";
public Brush? Highlight { get; set; }
public bool IsLocked { get; set; }
public readonly ISet<Path> LockedForPaths = new HashSet<Path>();
public bool IsLocked => LockedForPaths.Any();
public bool IsOpen => CallbackIsOpen();
public bool WantOpen => CallbackWantOpen();
public bool WantOpen => CallbackWantOpen(this);
public ISet<IEdge> Inputs { get; init; }
public ISet<IEdge> Outputs { get; init; }
protected Func<bool> CallbackIsOpen;
protected Func<bool> CallbackWantOpen;
protected Func<Valve, bool> CallbackWantOpen;
private Shape? _circle;
private TextBlock? _text;
public Valve(int nr, double x, double y, Func<bool> cbOpen, Func<bool> cbWant) {
public Valve(int nr, double x, double y, Func<bool> cbOpen, Func<Valve, bool> cbWant) {
CenterX = x;
CenterY = y;
Nr = nr;
@@ -53,8 +55,9 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
Height = DIAMETER,
TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
SnapsToDevicePixels = true,
};
_text.SetValue(Canvas.LeftProperty, CenterX - DIAMETER / 2);
_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);
@@ -65,6 +68,16 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
}
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 :
@@ -73,4 +86,5 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray);
}
}
}
}
+77
View File
@@ -0,0 +1,77 @@
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Text.Json.Nodes;
namespace PamhagenSysCtrl.Helpers {
public static class UpdateService {
public static async Task<(string Version, string Url, long Size)?> GetLatestInstallerUrl(string feedUrl) {
try {
using var client = new HttpClient() {
Timeout = TimeSpan.FromSeconds(5),
};
client.DefaultRequestHeaders.UserAgent.Clear();
client.DefaultRequestHeaders.UserAgent.ParseAdd($"PamhagenSysCtrl/{App.Version} ({Environment.MachineName}, {Environment.OSVersion})");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new("application/json"));
using var res = await client.GetAsync(feedUrl);
if (!res.IsSuccessStatusCode)
return null;
var json = JsonNode.Parse(await res.Content.ReadAsStringAsync());
var latest = json!["data"]!.AsArray()[^1]!;
return ((string)latest["version"]!, (string)latest["url"]!, (long)latest["size"]!);
} catch {
return null;
}
}
public static async Task DownloadInstaller(string url, string filename, IProgress<double>? progress = null, CancellationToken cancellationToken = default) {
try {
using var client = new HttpClient() {
Timeout = TimeSpan.FromSeconds(5),
};
client.DefaultRequestHeaders.UserAgent.Clear();
client.DefaultRequestHeaders.UserAgent.ParseAdd($"PamhagenSysCtrl/{App.Version} ({Environment.MachineName}, {Environment.OSVersion})");
client.DefaultRequestHeaders.Accept.Clear();
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();
var contentLength = response.Content.Headers.ContentLength;
await using (var destination = new FileStream(filename, FileMode.Create)) {
await using var source = await response.Content.ReadAsStreamAsync(cancellationToken);
var buffer = new byte[81920];
long downloaded = 0;
int read;
while ((read = await source.ReadAsync(buffer, cancellationToken)) != 0) {
await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken);
downloaded += read;
if (contentLength.HasValue) {
progress?.Report((double)downloaded / contentLength.Value);
}
}
}
progress?.Report(1);
} catch {
File.Delete(filename);
throw;
}
}
public static void StartInstaller(string fileName) {
var startInfo = new ProcessStartInfo {
FileName = "msiexec.exe",
UseShellExecute = true,
Verb = "runas",
};
startInfo.ArgumentList.Add("/i");
startInfo.ArgumentList.Add(Path.GetFullPath(fileName));
if (Process.Start(startInfo) == null) {
throw new InvalidOperationException("Der Installer konnte nicht gestartet werden.");
}
}
}
}
+3 -1
View File
@@ -3,8 +3,10 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<Version>0.0.1</Version>
<Version>0.0.3</Version>
<Product>Anlagensteuerung Pamhagen</Product>
<AssemblyTitle>Anlagensteuerung Pamhagen</AssemblyTitle>
<AssemblyName>PamhagenSysCtrl</AssemblyName>
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
+28
View File
@@ -0,0 +1,28 @@
<Window x:Class="PamhagenSysCtrl.Windows.AboutWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Über - Anlagensteuerung Pamhagen"
Height="340" Width="560" ResizeMode="NoResize"
WindowStartupLocation="CenterOwner">
<Grid>
<TextBlock Margin="20,10" FontSize="12">
<Bold>Produkt:</Bold> Anlagensteuerung Pamhagen<LineBreak/>
<Bold>Beschreibung:</Bold> Schnittstelle zu einer SPS und grafische Benutzeroberfläche für die<LineBreak/>
<Bold Foreground="Transparent">Beschreibung:</Bold> Steuerung der Anlage<LineBreak/>
<Bold>Typ:</Bold> HMI- und SPS-Steuerungssoftware<LineBreak/>
<Bold>Version:</Bold> <Run x:Name="VersionText">0.0.0</Run><LineBreak/>
<Bold>Lizenz:</Bold> <Hyperlink NavigateUri="https://www.gnu.org/licenses/gpl-3.0.html" RequestNavigate="Hyperlink_RequestNavigate">GNU General Public License 3.0 (GPLv3)</Hyperlink><LineBreak/>
<Bold>Entwickler:</Bold> Lorenz Stechauner, Thomas Hilscher<LineBreak/>
<Bold>Kontakt:</Bold> <Hyperlink NavigateUri="mailto:lorenz.stechauner@necronda.net" RequestNavigate="Hyperlink_RequestNavigate">lorenz.stechauner@necronda.net</Hyperlink>, <Hyperlink NavigateUri="mailto:thomas.hilscher@gmail.com" RequestNavigate="Hyperlink_RequestNavigate">thomas.hilscher@gmail.com</Hyperlink><LineBreak/>
<Bold>Quellcode:</Bold> <Hyperlink NavigateUri="C:\Program Files\Anlagensteuerung Pamhagen\src" RequestNavigate="Hyperlink_RequestNavigate_Explorer">C:\Program Files\Anlagensteuerung Pamhagen\src</Hyperlink>,<LineBreak/>
<Bold Foreground="Transparent">Quellcode:</Bold> <Hyperlink NavigateUri="https://git.necronda.net/winzer/pamhagen-sysctrl" RequestNavigate="Hyperlink_RequestNavigate">https://git.necronda.net/winzer/pamhagen-sysctrl</Hyperlink><LineBreak/>
<Bold>Entwicklungszeitraum:</Bold> 2026<LineBreak/>
<LineBreak/>
<Bold>Verwendete Technologien:</Bold><LineBreak/>
Programmiersprache: C#<LineBreak/>
Framework: Windows Presentation Framework (WPF)<LineBreak/>
SPS-Schnittstelle: Serielle Verbindung<LineBreak/>
Paketierung: <Hyperlink NavigateUri="https://www.firegiant.com/wixtoolset/" RequestNavigate="Hyperlink_RequestNavigate">WiX Toolset</Hyperlink>
</TextBlock>
</Grid>
</Window>
@@ -0,0 +1,25 @@
using System.Diagnostics;
using System.Windows;
using System.Windows.Navigation;
namespace PamhagenSysCtrl.Windows {
public partial class AboutWindow : Window {
public AboutWindow() {
InitializeComponent();
VersionText.Text = App.Version.ToString();
}
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs evt) {
try {
Process.Start(new ProcessStartInfo(evt.Uri.AbsoluteUri) { UseShellExecute = true });
} catch { }
}
private void Hyperlink_RequestNavigate_Explorer(object sender, RequestNavigateEventArgs evt) {
try {
Process.Start("explorer.exe", evt.Uri.AbsoluteUri);
} catch { }
}
}
}
@@ -5,7 +5,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PamhagenSysCtrl"
mc:Ignorable="d"
Title="Manuelle Steuerung - Anlagensteuerung Pamhagen" Height="550" Width="860">
Closed="OnClosed"
Title="Handbetrieb - Anlagensteuerung Pamhagen" Height="550" Width="860">
<Grid>
<Grid x:Name="ValvePanel" Margin="5,5,5,5">
</Grid>
@@ -39,14 +40,14 @@
FontSize="14" BorderThickness="2" FontWeight="Bold"
Margin="135,10,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Click="Auger1Button_Click"/>
<Button x:Name="BeltButton" Content="Förderband" Padding="2" Height="30" Width="150"
FontSize="14" BorderThickness="2" FontWeight="Bold"
Margin="135,45,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Click="BeltButton_Click"/>
<Button x:Name="Auger2Button" Content="Förderschnecke 2" Padding="2" Height="30" Width="150"
FontSize="14" BorderThickness="2" FontWeight="Bold"
Margin="135,80,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Margin="135,45,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Click="Auger2Button_Click"/>
<Button x:Name="Auger3Button" Content="Förderschnecke 3" Padding="2" Height="30" Width="150"
FontSize="14" BorderThickness="2" FontWeight="Bold"
Margin="135,80,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Click="Auger3Button_Click"/>
<Button x:Name="P6Button" Content="Pumpe 6" Padding="2" Height="30" Width="150"
FontSize="14" BorderThickness="2" FontWeight="Bold"
@@ -1,4 +1,5 @@
using PamhagenSysCtrl.Helpers;
using PamhagenSysCtrl.Windows;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
@@ -47,6 +48,14 @@ namespace PamhagenSysCtrl {
App.Plant?.Update += OnUpdate;
}
private void OnClosed(object sender, EventArgs evt) {
foreach (var win in Application.Current.Windows) {
if (win is PlantSchemeWindow w) {
w.Menu_Settings_ManualControl.IsChecked = false;
}
}
}
public void OnUpdate(object? sender, PlantEventArgs evt) {
if (sender is not PamhagenPlant plant) return;
@@ -76,10 +85,6 @@ namespace PamhagenSysCtrl {
StirrerMW2Button.Foreground = plant.IsStirrerMW2Active ? Brushes.DarkGreen : Brushes.DarkRed;
StirrerMW2Button.Background = plant.IsStirrerMW2Active ? Brushes.MintCream : Brushes.MistyRose;
BeltButton.BorderBrush = plant.IsBeltActive ? Brushes.DarkGreen : Brushes.DarkRed;
BeltButton.Foreground = plant.IsBeltActive ? Brushes.DarkGreen : Brushes.DarkRed;
BeltButton.Background = plant.IsBeltActive ? Brushes.MintCream : Brushes.MistyRose;
Auger1Button.BorderBrush = plant.IsAuger1Active ? Brushes.DarkGreen : Brushes.DarkRed;
Auger1Button.Foreground = plant.IsAuger1Active ? Brushes.DarkGreen : Brushes.DarkRed;
Auger1Button.Background = plant.IsAuger1Active ? Brushes.MintCream : Brushes.MistyRose;
@@ -88,6 +93,10 @@ namespace PamhagenSysCtrl {
Auger2Button.Foreground = plant.IsAuger2Active ? Brushes.DarkGreen : Brushes.DarkRed;
Auger2Button.Background = plant.IsAuger2Active ? Brushes.MintCream : Brushes.MistyRose;
Auger3Button.BorderBrush = plant.IsAuger3Active ? Brushes.DarkGreen : Brushes.DarkRed;
Auger3Button.Foreground = plant.IsAuger3Active ? Brushes.DarkGreen : Brushes.DarkRed;
Auger3Button.Background = plant.IsAuger3Active ? Brushes.MintCream : Brushes.MistyRose;
MP1BackwardButton.BorderBrush = plant.MP1 == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray;
MP1BackwardButton.Foreground = plant.MP1 == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray;
MP1BackwardButton.Background = plant.MP1 == MotorState.Backward ? Brushes.MintCream : Brushes.WhiteSmoke;
@@ -132,10 +141,10 @@ namespace PamhagenSysCtrl {
Gradation °Oe: {plant.GradationOe:N2}
Presse Querschnecke: {plant.PresseQuerSchnecke}
Füllstände:
Maischewanne 1: {plant.FillLevelMW1} (ausgewählt: {plant.WantMW1Selected}, Status: {plant.IsMWSelected})
Maischewanne 1: {plant.FillLevelMW1} (ausgewählt: {plant.WantMW1Selected}, Status: {plant.IsMW1Selected})
Maischewanne 2: {plant.FillLevelMW2} (ausgewählt: {plant.WantMW2Selected}, Status: {plant.IsMW2Selected})
Presse 1: {plant.FillLevelPress1}
Presse 2: {plant.FillLevelPress2}
Presse 1: {plant.Press1HasClearance}
Presse 2: {plant.Press2HasClearance}
Tank 1: {plant.FillLevelT1}
Tank 2: {plant.FillLevelT2}
Tank 3: {plant.FillLevelT3}
@@ -200,19 +209,6 @@ namespace PamhagenSysCtrl {
}
}
private void BeltButton_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return;
try {
if (App.Plant.IsBeltActive) {
App.Plant.StopBelt();
} else {
App.Plant.StartBelt();
}
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void Auger1Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return;
try {
@@ -239,6 +235,19 @@ namespace PamhagenSysCtrl {
}
}
private void Auger3Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return;
try {
if (App.Plant.IsAuger3Active) {
App.Plant.StopAuger3();
} else {
App.Plant.StartAuger3();
}
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void MP1BackwardButton_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return;
try {
+180 -3
View File
@@ -4,6 +4,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PamhagenSysCtrl.Windows"
Closed="OnClosed"
Title="Anlagensteuerung Pamhagen" Height="950" Width="1600" MinWidth="1500" MinHeight="950">
<Grid>
<Grid.RowDefinitions>
@@ -14,11 +15,23 @@
<Menu Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="LightGray" Background="White">
<MenuItem Header="Einstellungen">
<MenuItem x:Name="Menu_Settings_ManualControl" Header="Manuelle Steuerung" Click="Menu_Settings_ManualControl_Click">
<MenuItem x:Name="Menu_Settings_ManualControl" Header="Handbetrieb" IsCheckable="True"
Checked="Menu_Settings_ManualControl_Changed"
Unchecked="Menu_Settings_ManualControl_Changed">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xF271;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_Settings_OverridePathClearances" Header="Pfad-Freigaben überbrücken" IsCheckable="True"
Checked="Menu_Settings_OverridePathClearances_Changed"
Unchecked="Menu_Settings_OverridePathClearances_Changed">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE785;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_Settings_OverrideDryRunClearances" Header="Trockenlauf-Freigaben überbrücken" IsCheckable="True"
Checked="Menu_Settings_OverrideDryRunClearances_Changed"
Unchecked="Menu_Settings_OverrideDryRunClearances_Changed"/>
<Separator/>
<MenuItem x:Name="Menu_Settings_Config" Header="Konfigurationsdatei öffnen..." Click="Menu_Settings_Config_Click">
<MenuItem.Icon>
@@ -32,11 +45,16 @@
</MenuItem>
</MenuItem>
<MenuItem Header="Hilfe">
<MenuItem Header="Über">
<MenuItem Header="Über" Click="Menu_Help_About_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE946;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_Help_CheckForUpdates" Header="Nach Updates suchen..." Click="Menu_Help_CheckForUpdates_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE895;"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
@@ -44,11 +62,170 @@
<Canvas x:Name="SchemeCanvas" Width="1550" Height="900" VerticalAlignment="Center" HorizontalAlignment="Center" SnapsToDevicePixels="True"
MouseMove="SchemeCanvas_MouseMove" MouseLeftButtonDown="SchemeCanvas_MouseLeftButtonDown" MouseLeftButtonUp="SchemeCanvas_MouseLeftButtonUp" Grid.ColumnSpan="2" Margin="25,0,0,0">
</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>
<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>
<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_Start" Grid.Column="0" 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 FontSize="18">Schnecke Start</Run></TextBlock>
</Button>
<Button x:Name="FastSelectButton_EntryTrough_Stop" Grid.Column="2" 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 FontSize="18">Schnecke Stop</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_Stop" Grid.Column="0" Grid.Row="4"
FontSize="16" FontWeight="Bold"
Click="FastSelectButton_MP2_Stop_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2">
<TextBlock TextAlignment="Center"><Run FontSize="24">MP2</Run><LineBreak/>Stop</TextBlock>
</Button>
<Button x:Name="FastSelectButton_MP2_Forward" Grid.Column="0" Grid.Row="5"
FontSize="16" FontWeight="Bold"
Click="FastSelectButton_MP2_Forward_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2">
<TextBlock TextAlignment="Center"><Run FontSize="24">MP2</Run><LineBreak/><Run x:Name="MP2_Target">Vor</Run></TextBlock>
</Button>
<Button x:Name="FastSelectButton_MP1_Stop" Grid.Column="3" Grid.Row="4"
FontSize="16" FontWeight="Bold"
Click="FastSelectButton_MP1_Stop_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2">
<TextBlock TextAlignment="Center"><Run FontSize="24">MP1</Run><LineBreak/>Stop</TextBlock>
</Button>
<Button x:Name="FastSelectButton_MP1_Forward" Grid.Column="3" Grid.Row="5"
FontSize="16" FontWeight="Bold"
Click="FastSelectButton_MP1_Forward_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2">
<TextBlock TextAlignment="Center"><Run FontSize="24">MP1</Run><LineBreak/><Run x:Name="MP1_Target">Vor</Run></TextBlock>
</Button>
</Grid>
</Border>
</Grid>
<StatusBar Grid.Row="2" BorderThickness="0,1,0,0" BorderBrush="Gray">
</StatusBar>
</Grid>
</Window>
+292 -35
View File
@@ -2,7 +2,9 @@ using PamhagenSysCtrl.Helpers;
using PamhagenSysCtrl.Helpers.Pipeline;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace PamhagenSysCtrl.Windows {
public partial class PlantSchemeWindow : Window {
@@ -14,16 +16,89 @@ namespace PamhagenSysCtrl.Windows {
private ISet<INode>? _lastSelected;
private ISet<INode>? _hover;
public PlantSchemeWindow() {
InitializeComponent();
App.Plant?.Update += OnUpdate;
Graph = new();
Graph.Draw(SchemeCanvas);
public Path? HoveringPath {
get {
foreach (var b in FastSelectPaths.Where(b => b.Key.IsMouseOver)) {
if (Graph.SelectedPaths.Any(p => p.Start == b.Value.Source && p.Hops.Last().Node == b.Value.Sink))
continue;
return Graph.GetFreePath([b.Value.Source, b.Value.Sink], true);
}
private void Menu_Settings_ManualControl_Click(object sender, RoutedEventArgs evt) {
var w = new ManualControlWindow();
w.Show();
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() {
InitializeComponent();
Menu_Help_CheckForUpdates.IsEnabled = App.Config.UpdateUrl != null;
Graph = new();
Graph.Draw(SchemeCanvas);
FastSelectPaths = new Dictionary<Button, (ISource, Pump, ISink)> {
[FastSelectButton_MW1_Press1] = (Graph.MW1, Graph.MP1, Graph.Press1),
[FastSelectButton_MW2_Press1] = (Graph.MW2, Graph.MP2, Graph.Press1),
[FastSelectButton_MW1_Press2] = (Graph.MW1, Graph.MP1, Graph.Press2),
[FastSelectButton_MW2_Press2] = (Graph.MW2, Graph.MP2, Graph.Press2),
[FastSelectButton_MW1_Tank1] = (Graph.MW1, Graph.MP1, Graph.Tank1),
[FastSelectButton_MW2_Tank1] = (Graph.MW2, Graph.MP2, Graph.Tank1),
[FastSelectButton_MW1_Tank2] = (Graph.MW1, Graph.MP1, Graph.Tank2),
[FastSelectButton_MW2_Tank2] = (Graph.MW2, Graph.MP2, Graph.Tank2),
[FastSelectButton_MW1_Tank3] = (Graph.MW1, Graph.MP1, Graph.Tank3),
[FastSelectButton_MW2_Tank3] = (Graph.MW2, Graph.MP2, Graph.Tank3),
[FastSelectButton_MW1_Tank4] = (Graph.MW1, Graph.MP1, Graph.Tank4),
[FastSelectButton_MW2_Tank4] = (Graph.MW2, Graph.MP2, Graph.Tank4),
[FastSelectButton_MW1_Tank5] = (Graph.MW1, Graph.MP1, Graph.Tank5),
[FastSelectButton_MW2_Tank5] = (Graph.MW2, Graph.MP2, Graph.Tank5),
};
App.Plant?.Update += OnUpdate;
}
private void OnClosed(object sender, EventArgs evt) {
Application.Current.Shutdown();
}
private void Menu_Settings_ManualControl_Changed(object sender, RoutedEventArgs evt) {
App.Plant?.ManualControlMode = Menu_Settings_ManualControl.IsChecked;
if (Menu_Settings_ManualControl.IsChecked) {
foreach (var win in Application.Current.Windows) {
if (win is ManualControlWindow w) {
w.Focus();
return;
}
}
var ctrl = new ManualControlWindow();
ctrl.Show();
} else {
foreach (var win in Application.Current.Windows) {
if (win is ManualControlWindow w) {
w.Close();
}
}
Menu_Settings_OverridePathClearances.IsChecked = false;
Menu_Settings_OverrideDryRunClearances.IsChecked = false;
}
OnUpdate(null, null);
}
private void Menu_Settings_OverridePathClearances_Changed(object sender, RoutedEventArgs evt) {
App.Plant?.OverridePathClearances = Menu_Settings_OverridePathClearances.IsChecked;
OnUpdate(null, null);
}
private void Menu_Settings_OverrideDryRunClearances_Changed(object sender, RoutedEventArgs evt) {
App.Plant?.OverrideDryRunClearances = Menu_Settings_OverrideDryRunClearances.IsChecked;
OnUpdate(null, null);
}
private void Menu_Settings_Directory_Click(object sender, RoutedEventArgs evt) {
@@ -43,13 +118,109 @@ namespace PamhagenSysCtrl.Windows {
} catch { }
}
private async void Menu_Help_CheckForUpdates_Click(object sender, RoutedEventArgs evt) {
await App.CheckForUpdates(true);
}
private void Menu_Help_About_Click(object sender, RoutedEventArgs evt) {
var window = new AboutWindow {
Owner = this,
};
window.Show();
}
private void FastSelectButton_EntryTroughStart_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.StartTroughAuger();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_EntryTroughStop_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.StopTroughAuger();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_MW1_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.SelectMW1();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_MW2_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.SelectMW2();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_MP1_Forward_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.StartMP1Forward();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_MP1_Stop_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.StopMP1();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_MP2_Forward_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.StartMP2Forward();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_MP2_Stop_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.StopMP2();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_Enter(object sender, MouseEventArgs evt) {
UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed);
}
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;
if (Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == val.Source, null) is Path p1) {
Graph.RemovePath(p1);
if (p1.Hops.Last().Node == val.Sink)
return;
}
if (Graph.GetFreePath([val.Source, val.Sink]) is Path p2) {
Graph.AddPath(p2);
_lastSource = null;
}
}
private void SchemeCanvas_MouseMove(object sender, MouseEventArgs evt) {
var p = evt.GetPosition(SchemeCanvas);
UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed);
}
private void SchemeCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs evt) {
var p = evt.GetPosition(SchemeCanvas);
_lastSelected = UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed);
}
@@ -61,19 +232,27 @@ namespace PamhagenSysCtrl.Windows {
_lastSource = null;
}
foreach (var c in clicked) {
if (App.Plant?.ManualControlMode ?? false) {
if (c is Valve v) {
if (v.WantOpen) {
App.Plant.CloseV(v.Nr);
} else {
App.Plant.OpenV(v.Nr);
}
}
} else {
if (_lastSource == null && Graph.SelectedPaths.Any(p => p.Start == c)) {
var path = Graph.SelectedPaths.First(p => p.Start == c);
if (!path.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false)) {
Graph.RemovePath(path);
}
} else if (_lastSource != null && c is ISink sink) {
var res = Graph.GetPath([_lastSource, .._path, sink], (n) => n is not ISink && (n is not Valve v || !v.IsLocked));
if (res is Path path) {
if (Graph.GetFreePath([_lastSource, .. _path, sink]) is Path path) {
Graph.AddPath(path);
_lastSource = null;
}
} else if (_lastSource != null) {
var path = Graph.GetPath([_lastSource, .. _path, c], (n) => n is not ISink && (n is not Valve v || !v.IsLocked));
var path = Graph.GetFreePath([_lastSource, .. _path, c]);
if (path != null) {
_path.Add(c);
}
@@ -83,6 +262,7 @@ namespace PamhagenSysCtrl.Windows {
} else {
_lastSource = null;
}
}
try {
if (c is EntryTrough trough) {
if (trough.IsActive) {
@@ -109,18 +289,18 @@ namespace PamhagenSysCtrl.Windows {
} else {
App.Plant?.StartAuger1();
}
} else if (auger == Graph.Auger2) {
} else if (auger == Graph.Auger3) {
if (auger.IsActive) {
App.Plant?.StopAuger2();
App.Plant?.StopAuger3();
} else {
App.Plant?.StartAuger2();
App.Plant?.StartAuger3();
}
}
} else if (c is ConveyorBelt belt) {
if (belt.IsActive) {
App.Plant?.StopBelt();
App.Plant?.StopAuger2();
} else {
App.Plant?.StartBelt();
App.Plant?.StartAuger2();
}
} else if (c is Pump pump) {
if (pump == Graph.MP1) {
@@ -174,6 +354,30 @@ namespace PamhagenSysCtrl.Windows {
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.Start == e.Source && p.Hops.Last().Node == e.Sink) ? (hoverPathDeactivate?.Start == e.Source && hoverPathDeactivate?.Hops.Last().Node == e.Sink) ? 2 : 1 : 0,
e.Pump.IsActive || Graph.GetFreePath([e.Source, e.Sink], overrideStart: true) == null);
}
private ISet<INode> UpdateScheme(Point pos, bool down) {
foreach (var e in Graph.Edges) {
e.Highlight = null;
@@ -184,30 +388,53 @@ namespace PamhagenSysCtrl.Windows {
p.Highlight = null;
}
}
foreach (var p in Graph.SelectedPaths) {
Graph.ColorPipesAdjacientToPath(p, PamhagenBrushes.Green, p.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false), PamhagenBrushes.DimOrange);
SetFastSelectButton(FastSelectButton_EntryTrough_Start, App.Plant?.IsTroughAugerActive ?? false ? 1 : 0, !(App.Plant?.TroughAugerHasClearance ?? false));
SetFastSelectButton(FastSelectButton_EntryTrough_Stop, App.Plant?.IsTroughAugerActive ?? false ? 0 : 2);
SetFastSelectButton(FastSelectButton_MW1, App.Plant?.IsMW1Selected ?? false ? 1 : 0);
SetFastSelectButton(FastSelectButton_MW2, App.Plant?.IsMW2Selected ?? false ? 1 : 0);
SetFastSelectButton(FastSelectButton_MP1_Forward, App.Plant?.MP1 == MotorState.Forward ? 1 : 0, !(App.Plant?.MP1HasClearance ?? false));
SetFastSelectButton(FastSelectButton_MP1_Stop, App.Plant?.MP1 == MotorState.Halt ? 2 : 0);
SetFastSelectButton(FastSelectButton_MP2_Forward, App.Plant?.MP2 == MotorState.Forward ? 1 : 0, !(App.Plant?.MP2HasClearance ?? false));
SetFastSelectButton(FastSelectButton_MP2_Stop, App.Plant?.MP2 == MotorState.Halt ? 2 : 0);
foreach (var b in FastSelectPaths) {
SetFastSelectButton(b.Key, 0, false);
}
_hover = Graph.GetHover(pos.X, pos.Y);
if (_lastSource != null && (_hover.Count > 0 || _path.Count > 0)) {
List<INode> points = [_lastSource, .. _path];
if (_hover.Count > 0 && !points.Contains(_hover.First())) {
points.Add(_hover.First());
var hoverButtons = FastSelectPaths.Where(b => b.Key.IsMouseOver).ToDictionary();
Path? hoverPathDeactivate = null;
if (App.Plant?.ManualControlMode ?? false) {
foreach (var src in Graph.Nodes.Where(n => n is Pump)) {
Graph.ColorPipesByValves(src, PamhagenBrushes.Green, (src as Pump)?.State ?? default, PamhagenBrushes.DimOrange);
}
if (points.Count >= 2) {
var res = Graph.GetPath(points, (n) => n is not ISink && (n is not Valve v || !v.IsLocked));
if (res is Path path) {
Graph.ColorPipesAdjacientToPath(path, PamhagenBrushes.Green, false, PamhagenBrushes.DimOrange);
MP1_Target.Text = "Vor";
MP2_Target.Text = "Vor";
MP1_Target.FontSize = 16;
MP2_Target.FontSize = 16;
} else {
foreach (var p in Graph.SelectedPaths) {
Graph.ColorPipesAdjacientToPath(p, PamhagenBrushes.Green, p.Hops.Select(h => (h.Node as Pump)?.State ?? default).FirstOrDefault(n => n != MotorState.Halt), 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 (_lastSource == null && _hover.Count > 0 && Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == _hover.First()) is Path path1) {
var pumpActive = path1.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)) {
foreach (var pump in path1.Hops.Select(h => h.Node as Pump).Where(p => p != null)) {
pump?.Highlight = PamhagenBrushes.Red;
}
} else {
Graph.ColorPipesAdjacientToPath(path, PamhagenBrushes.Red, false, null);
hoverPathDeactivate = path1;
Graph.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) {
var pumpActive = path2.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false);
if (pumpActive) {
foreach (var pump in path2.Hops.Select(h => h.Node as Pump).Where(p => p != null)) {
pump?.Highlight = PamhagenBrushes.Red;
}
} else {
hoverPathDeactivate = path2;
Graph.ColorPipesAdjacientToPath(path2, PamhagenBrushes.Red, default, null);
}
}
if (_lastSource != null) {
@@ -215,11 +442,41 @@ namespace PamhagenSysCtrl.Windows {
edge.Highlight = PamhagenBrushes.Green;
}
}
if (HoveringPath is Path hoveringPath) {
Graph.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 || h.Node is CenterValve ? 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 || h.Node is CenterValve ? 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.Keys) {
UpdateFastSelectPathButton(paths, hoverPathDeactivate, b);
}
Graph.Update(_hover, down);
return _hover;
}
public void OnUpdate(object? sender, PlantEventArgs evt) {
public void OnUpdate(object? sender, PlantEventArgs? evt) {
if (sender is not PamhagenPlant plant) return;
UpdateScheme(Mouse.GetPosition(SchemeCanvas), Mouse.LeftButton == MouseButtonState.Pressed);
}
+41 -1
View File
@@ -1,2 +1,42 @@
# Anlagensteuerung Pamhangen
Anlagensteuerung Pamhagen
=========================
Schnittstelle zur SPS und grafische Benutzeroberfläche.
Winzerkeller Seewinkel, Pamhagen.
About
=====
**Product:** Anlagensteuerung Pamhagen
**Description:** Interface to a LPC and graphical user interface for controlling the plant/system
**Type:** HMI and PLC Control Software
**Version:** 0.0.3 ([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.3 ([Ä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/)