1 Commits
Author SHA1 Message Date
lorenz.stechauner c12ec57950 Pipeline: Change Valve symbol and add abstract Valve
Test / Run tests (push) Failing after 31s
2026-09-12 19:53:07 +02:00
31 changed files with 811 additions and 2381 deletions

No files matched your search

-32
View File
@@ -1,39 +1,7 @@
[plc] [plc]
port = COM1 port = COM1
; sensor delay in seconds
sensor_delay_open = 0
sensor_delay_close = 2
sensor_delay_mw = 10
; from which filling level on does the stirrer start (or stop when below) automatically when pumping
auto_stirrer = Half
[update] [update]
url = https://elwig.at/files/pamhagen-sysctrl/latest url = https://elwig.at/files/pamhagen-sysctrl/latest
auto = true auto = true
[xbe24]
a1 = PATH_MW2_PRESS1
a2 = PATH_MW2_PRESS2
a3 = PATH_MW2_T1
a4 = PATH_MW2_T2
a5 = PATH_MW2_T3
a6 = PATH_MW2_T4
b1 = START_REBLER
b2 = STOP_REBLER
b3 = SELECT_MW2
b4 = TOGGLE_STIRRER_MW2
b5 = START_MP2
b6 = STOP_MP2
c1 = START_TROUGH_AUGER
c2 = STOP_TROUGH_AUGER
c3 = SELECT_MW1
c4 = TOGGLE_STIRRER_MW1
c5 = START_MP1
c6 = STOP_MP1
d1 = PATH_MW1_PRESS1
d2 = PATH_MW1_PRESS2
d3 = PATH_MW1_T1
d4 = PATH_MW1_T2
d5 = PATH_MW1_T3
d6 = PATH_MW1_T4
+10 -3
View File
@@ -34,12 +34,19 @@ namespace PamhagenSysCtrl {
} }
protected override async void OnStartup(StartupEventArgs evt) { protected override async void OnStartup(StartupEventArgs evt) {
if (Config == null)
return;
Version = new Version(typeof(App).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Split('+')[0] ?? "0.0.0"); Version = new Version(typeof(App).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Split('+')[0] ?? "0.0.0");
try {
Plant = new(Config.PlcPort); Plant = new(Config.PlcPort);
await Plant.StartThread();
} catch (Exception exc) {
Plant?.Dispose();
Plant = null;
var str = $"Beim Aufbauen einer Verbindung zur SPS ist ein Fehler aufgetreten:\n\n{exc.GetType().Name}: {exc.Message}";
if (exc.InnerException != null) str += $"\n\n{exc.InnerException.GetType().Name}: {exc.InnerException.Message}";
MessageBox.Show(str, "SPS-Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
base.OnStartup(evt); base.OnStartup(evt);
if (Config.UpdateUrl != null && Config.UpdateAuto) { if (Config.UpdateUrl != null && Config.UpdateAuto) {
-27
View File
@@ -9,18 +9,11 @@ namespace PamhagenSysCtrl.Helpers {
private readonly string FileName; private readonly string FileName;
public string PlcPort = "COM1"; public string PlcPort = "COM1";
public int PlcSensorDelayOpen = 0;
public int PlcSensorDelayClose = 0;
public int PlcSensorDelayMW = 0;
public FillState PlcAutoStirrer = FillState.Half;
public string? UpdateUrl; public string? UpdateUrl;
public bool UpdateAuto; public bool UpdateAuto;
public PamhagenPlant.ShortcutKey?[] Xbe24KeyMap;
public Config(string filename) { public Config(string filename) {
FileName = filename; FileName = filename;
Xbe24KeyMap = new PamhagenPlant.ShortcutKey?[24];
Read(); Read();
} }
@@ -28,32 +21,12 @@ namespace PamhagenSysCtrl.Helpers {
try { try {
var config = new ConfigurationBuilder().AddIniFile(FileName).Build(); var config = new ConfigurationBuilder().AddIniFile(FileName).Build();
PlcPort = config["plc:port"] ?? "COM1"; PlcPort = config["plc:port"] ?? "COM1";
PlcSensorDelayOpen = int.Parse(config["plc:sensor_delay_open"] ?? "0");
PlcSensorDelayClose = int.Parse(config["plc:sensor_delay_close"] ?? "0");
PlcSensorDelayMW = int.Parse(config["plc:sensor_delay_mw"] ?? "0");
PlcAutoStirrer = Enum.Parse<FillState>(config["plc:auto_stirrer"] ?? "Half");
UpdateUrl = config["update:url"]; UpdateUrl = config["update:url"];
UpdateAuto = TrueValues.Contains(config["update:auto"]?.ToLower()); UpdateAuto = TrueValues.Contains(config["update:auto"]?.ToLower());
var xbe24 = config.GetSection("xbe24");
for (char c = 'a'; c <= 'd'; c++) {
for (int r = 1; r <= 6; r++) {
int n = (c - 'a') * 6 + r - 1;
var s = xbe24[$"{c}{r}"]?.ToUpper();
Xbe24KeyMap[n] = s == null ? null : Enum.Parse<PamhagenPlant.ShortcutKey>(s);
}
}
} catch (Exception exc) { } catch (Exception exc) {
MessageBox.Show($"Die Konfigurationsdatei konnte nicht gelesen werden:\n\n{exc.Message}", "Konfigurationsdatei lesen", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"Die Konfigurationsdatei konnte nicht gelesen werden:\n\n{exc.Message}", "Konfigurationsdatei lesen", MessageBoxButton.OK, MessageBoxImage.Error);
Application.Current.Shutdown(); Application.Current.Shutdown();
} }
} }
public PamhagenPlant.ShortcutKey? MapKey(int n) {
return n < 0 || Xbe24KeyMap.Length < n ? null : Xbe24KeyMap[n];
}
public int? MapKey(PamhagenPlant.ShortcutKey key) {
return Xbe24KeyMap.IndexOf(key) is int n ? n == -1 ? null : n : null;
}
} }
} }
-19
View File
@@ -1,19 +0,0 @@
using System.Drawing;
namespace PamhagenSysCtrl.Helpers {
public static class Extensions {
public static void SetBacklight(this Xbe24Keyboard k, PamhagenPlant.ShortcutKey key, (Color Color, bool Flashing) state) {
k.SetBacklight(key, state.Color, state.Flashing);
}
public static void SetBacklight(this Xbe24Keyboard k, PamhagenPlant.ShortcutKey key, Color color, bool flashing = false) {
if (App.Config.MapKey(key) is int n)
k.SetBacklight(n, color, flashing);
}
public static bool IsKeyPressed(this Xbe24Keyboard k, PamhagenPlant.ShortcutKey key) {
return App.Config.MapKey(key) is int n && k.IsKeyPressed(n);
}
}
}
+1 -1
View File
@@ -1,3 +1,3 @@
namespace PamhagenSysCtrl.Helpers { namespace PamhagenSysCtrl.Helpers {
public enum FillState { Unknown = 0, Empty = 1, Filled = 2, Quarter = 3, Half = 4, ThreeQuarter = 5, AlmostFull = 6, Full = 7, Invalid = -1 } public enum FillState { Empty = 0, Filled = 1, Half = 2, Full = 3, Invalid = -1, Unknown = -2 }
} }
@@ -16,10 +16,7 @@ namespace PamhagenSysCtrl.Helpers {
return state switch { return state switch {
FillState.Empty => GetEmpty(background), FillState.Empty => GetEmpty(background),
FillState.Filled => GetFilled(background), FillState.Filled => GetFilled(background),
FillState.Quarter => GetQuarter(background),
FillState.Half => GetHalf(background), FillState.Half => GetHalf(background),
FillState.ThreeQuarter => GetThreeQuarter(background),
FillState.AlmostFull => GetAlmostFull(background),
FillState.Full => GetFull(background), FillState.Full => GetFull(background),
_ => GetEmpty(background), _ => GetEmpty(background),
}; };
@@ -37,22 +34,10 @@ namespace PamhagenSysCtrl.Helpers {
return new LinearGradientBrush(background, Green.Color, new(0.5, 0.875 - TRANSITION), new(0.5, 0.875 + TRANSITION)); return new LinearGradientBrush(background, Green.Color, new(0.5, 0.875 - TRANSITION), new(0.5, 0.875 + TRANSITION));
} }
public static Brush GetQuarter(Color background) {
return new LinearGradientBrush(background, Green.Color, new(0.5, 0.75 - TRANSITION), new(0.5, 0.75 + TRANSITION));
}
public static Brush GetHalf(Color background) { public static Brush GetHalf(Color background) {
return new LinearGradientBrush(background, Green.Color, new(0.5, 0.5 - TRANSITION), new(0.5, 0.5 + TRANSITION)); return new LinearGradientBrush(background, Green.Color, new(0.5, 0.5 - TRANSITION), new(0.5, 0.5 + TRANSITION));
} }
public static Brush GetThreeQuarter(Color background) {
return new LinearGradientBrush(background, Green.Color, new(0.5, 0.25 - TRANSITION), new(0.5, 0.25 + TRANSITION));
}
public static Brush GetAlmostFull(Color background) {
return new LinearGradientBrush(background, Green.Color, new(0.5, 0.125 - TRANSITION), new(0.5, 0.125 + TRANSITION));
}
public static Brush GetFull(Color background) { public static Brush GetFull(Color background) {
return new LinearGradientBrush(background, Red.Color, new(0.5, 0.0625 - TRANSITION), new(0.5, 0.0625 + TRANSITION)); return new LinearGradientBrush(background, Red.Color, new(0.5, 0.0625 - TRANSITION), new(0.5, 0.0625 + TRANSITION));
} }
+88 -135
View File
@@ -29,12 +29,6 @@ namespace PamhagenSysCtrl.Helpers {
public readonly Tank Tank7; public readonly Tank Tank7;
public readonly Tank Tank8; public readonly Tank Tank8;
public readonly Tank Tank9; public readonly Tank Tank9;
public readonly Valve V5;
public readonly Valve V6;
public readonly Valve V7;
public readonly Valve V8;
public readonly Dictionary<PamhagenPlant.ShortcutKey, (ISource Source, Pump Pump, INode? Intermediate, ISink Sink)> PathShortcuts;
public PamhagenGraph() { public PamhagenGraph() {
double mw2X = -118; double mw2X = -118;
@@ -72,26 +66,26 @@ namespace PamhagenSysCtrl.Helpers {
var pr1X = -15; var pr1X = -15;
var pr2X = 23; var pr2X = 23;
var trough = new EntryTrough("Auffangwanne", mw1X + 14, rebY - 20, () => App.Plant?.State.TroughAuger_IsActive ?? false); var trough = new EntryTrough("Auffangwanne", mw1X + 14, rebY - 20, () => App.Plant?.IsTroughAugerActive ?? false);
Nodes.Add(trough); Nodes.Add(trough);
Auger1 = new EncasedAuger("", rebX - 19, rebY, () => App.Plant?.State.Auger1_IsActive ?? false); Auger1 = new EncasedAuger("", rebX - 19, rebY, () => App.Plant?.IsAuger1Active ?? false);
var auger0 = new EncasedAuger("Pressen-\nQuerschnecke", rebX - 39, rebY, () => App.Plant?.State.PressCrossAuger_IsActive ?? false, 180, clickable: false); var auger0 = new EncasedAuger("Pressen-\nQuerschnecke", rebX - 39, rebY, () => App.Plant?.IsPresseQuerSchneckeActive ?? false, 180, clickable: false);
Auger3 = new EncasedAuger("", rebX - 23, rebY - 24, () => App.Plant?.State.Auger3_IsActive ?? false, 135); Auger3 = new EncasedAuger("", rebX - 23, rebY - 24, () => App.Plant?.IsAuger3Active ?? false, 135);
var belt = new ConveyorBelt("", rebX - 29, rebY - 8, () => App.Plant?.State.Auger2_IsActive ?? false); var belt = new ConveyorBelt("", rebX - 29, rebY - 8, () => App.Plant?.IsAuger2Active ?? false);
Nodes.Add(belt); Nodes.Add(belt);
Nodes.Add(Auger1); Nodes.Add(Auger1);
Nodes.Add(auger0); Nodes.Add(auger0);
Nodes.Add(Auger3); Nodes.Add(Auger3);
var rebler = new Rebler("Rebler", rebX, rebY, () => App.Plant?.State.Rebler_IsActive ?? false); var rebler = new Rebler("Rebler", rebX, rebY, () => App.Plant?.IsReblerActive ?? false);
Nodes.Add(rebler); Nodes.Add(rebler);
var switcher = new Switcher("", rebX, rebY + 12, () => App.Plant?.State.MW1_IsSelected == true ? 1 : App.Plant?.State.MW2_IsSelected == true ? 2 : 0, () => App.Plant?.State.MW1_WantSelected == true ? 1 : App.Plant?.State.MW2_WantSelected == true ? 2 : 0); var switcher = new Switcher("", rebX, rebY + 12, () => App.Plant?.TroughSelector ?? 0, () => App.Plant == null ? 0 : App.Plant.WantMW1Selected ? 1 : 2);
Nodes.Add(switcher); Nodes.Add(switcher);
MW1 = new Trough("MW1", mw1X, mpY - 14, () => App.Plant?.State.MW1_FillLevel ?? FillState.Unknown); MW1 = new Trough("MW1", mw1X, mpY - 14, () => App.Plant?.FillLevelMW1 ?? FillState.Unknown);
MW2 = new Trough("MW2", mw2X, mpY - 14, () => App.Plant?.State.MW2_FillLevel ?? FillState.Unknown); MW2 = new Trough("MW2", mw2X, mpY - 14, () => App.Plant?.FillLevelMW2 ?? FillState.Unknown);
MP1 = new Pump("MP1", mw1X, mpY, () => App.Plant?.State.MP1_State ?? default, () => App.Plant?.State.MP1_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x))); MP1 = new Pump("MP1", mw1X, mpY, () => App.Plant?.MP1 ?? default, () => App.Plant?.MP1HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
MP2 = new Pump("MP2", mw2X, mpY, () => App.Plant?.State.MP2_State ?? default, () => App.Plant?.State.MP2_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x))); MP2 = new Pump("MP2", mw2X, mpY, () => App.Plant?.MP2 ?? default, () => App.Plant?.MP2HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
var x1 = new PipeJoin(mw1X, mpY + 8); var x1 = new PipeJoin(mw1X, mpY + 8);
var x2 = new PipeJoin(mw2X, mpY + 8); var x2 = new PipeJoin(mw2X, mpY + 8);
@@ -122,23 +116,23 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(v4, x5, true); CreatePipe(v4, x5, true);
CreatePipe(x5, x6, true); CreatePipe(x5, x6, true);
V5 = CreateValve(5, x1X + 6, ltg1Y, 1); var v5 = CreateValve(5, x1X + 6, ltg1Y, 1);
V6 = CreateValve(6, x1X + 6, ltg2Y + 8, 3); var v6 = CreateValve(6, x1X + 6, ltg2Y + 8, 3);
V7 = CreateValve(7, x1X + 6, ltg2Y, 3); var v7 = CreateValve(7, x1X + 6, ltg2Y, 3);
V8 = CreateValve(8, x1X - 6, ltg2Y + 8, 0); var v8 = CreateValve(8, x1X - 6, ltg2Y + 8, 0);
var v9 = CreateValve(9, ltg2X, ltg5Y + 8, 2); var v9 = CreateValve(9, ltg2X, ltg5Y + 8, 2);
CreatePipe(x4, V5); CreatePipe(x4, v5);
CreatePipe(x4, V6, true); CreatePipe(x4, v6, true);
CreatePipe(x6, V7); CreatePipe(x6, v7);
CreatePipe(x6, V8); CreatePipe(x6, v8);
CreatePipe(V7, v9, label: "Ltg. 2", labelOffsetX: 6); CreatePipe(v7, v9, label: "Ltg. 2", labelOffsetX: 6);
var xv15 = new PipeJoin(ltg1X, wt1Y + 8); var xv15 = new PipeJoin(ltg1X, wt1Y + 8);
var xv11 = new PipeJoin(ltg2X, wt1Y + 14); var xv11 = new PipeJoin(ltg2X, wt1Y + 14);
var x7 = new PipeJoin(ltg2X, ltg5Y); var x7 = new PipeJoin(ltg2X, ltg5Y);
CreatePipe(V5, xv15, label: "Ltg. 1", labelOffsetX: 6); CreatePipe(v5, xv15, label: "Ltg. 1", labelOffsetX: 6);
CreatePipe(v9, x7); CreatePipe(v9, x7);
CreatePipe(x7, xv11); CreatePipe(x7, xv11);
@@ -315,14 +309,14 @@ namespace PamhagenSysCtrl.Helpers {
CreateInlet(v43, Tank9); CreateInlet(v43, Tank9);
var xp12 = new PipeJoin((t1X + t2X) / 2, tanksY + 16); var xp12 = new PipeJoin((t1X + t2X) / 2, tanksY + 16);
P12 = new Pump("P1/2", (t1X + t2X) / 2, tanksY + 22, () => App.Plant?.State.P12_State ?? default, () => App.Plant?.State.P12_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x))); P12 = new Pump("P1/2", (t1X + t2X) / 2, tanksY + 22, () => App.Plant?.P12 ?? default, () => App.Plant?.P12HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
CreateOutlet(Tank1, xp12); CreateOutlet(Tank1, xp12);
CreateOutlet(Tank2, xp12); CreateOutlet(Tank2, xp12);
CreatePipe(xp12, P12); CreatePipe(xp12, P12);
P3 = new Pump("P3", t3X, tanksY + 22, () => App.Plant?.State.P3_State ?? default, () => App.Plant?.State.P3_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x))); P3 = new Pump("P3", t3X, tanksY + 22, () => App.Plant?.P3 ?? default, () => App.Plant?.P3HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
P4 = new Pump("P4", t4X, tanksY + 22, () => App.Plant?.State.P4_State ?? default, () => App.Plant?.State.P4_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x))); P4 = new Pump("P4", t4X, tanksY + 22, () => App.Plant?.P4 ?? default, () => App.Plant?.P4HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
P5 = new Pump("P5", t5X, tanksY + 22, () => App.Plant?.State.P5_State ?? default, () => App.Plant?.State.P5_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x))); P5 = new Pump("P5", t5X, tanksY + 22, () => App.Plant?.P5 ?? default, () => App.Plant?.P5HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
CreateOutlet(Tank3, P3); CreateOutlet(Tank3, P3);
CreateOutlet(Tank4, P4); CreateOutlet(Tank4, P4);
CreateOutlet(Tank5, P5); CreateOutlet(Tank5, P5);
@@ -352,7 +346,7 @@ namespace PamhagenSysCtrl.Helpers {
var xt6 = new PipeJoin(t6X, tanksY + 22); var xt6 = new PipeJoin(t6X, tanksY + 22);
var xt7 = new PipeJoin(t7X, tanksY + 22); var xt7 = new PipeJoin(t7X, tanksY + 22);
var xt8 = new PipeJoin(t8X, tanksY + 22); var xt8 = new PipeJoin(t8X, tanksY + 22);
P6 = new Pump("P6", t6X, tanksY + 30, () => App.Plant?.State.P6_State ?? default, () => App.Plant?.State.P6_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x))); P6 = new Pump("P6", t6X, tanksY + 30, () => App.Plant?.P6 ?? default, () => App.Plant?.P6HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x)));
CreateOutlet(Tank6, xt6); CreateOutlet(Tank6, xt6);
CreateOutlet(Tank7, xt7); CreateOutlet(Tank7, xt7);
CreateOutlet(Tank8, xt8); CreateOutlet(Tank8, xt8);
@@ -367,8 +361,8 @@ namespace PamhagenSysCtrl.Helpers {
var prX = (pr1X + pr2X) / 2; var prX = (pr1X + pr2X) / 2;
var pr1cX = prX - 3; var pr1cX = prX - 3;
var pr2cX = prX + 3; var pr2cX = prX + 3;
Press1 = new Press("Presse 1", pr1X, prY, 20000, () => FillState.Unknown, () => App.Plant?.State.Press1_HasClearance ?? false); Press1 = new Press("Presse 1", pr1X, prY, 20000, () => FillState.Unknown);
Press2 = new Press("Presse 2", pr2X, prY, 15000, () => FillState.Unknown, () => App.Plant?.State.Press2_HasClearance ?? false); Press2 = new Press("Presse 2", pr2X, prY, 15000, () => FillState.Unknown);
var v50 = CreateValve(50, pr2cX + 6, ltg3Y, 1); var v50 = CreateValve(50, pr2cX + 6, ltg3Y, 1);
var v51 = CreateValve(51, pr1cX - 6, ltg3Y, 1); var v51 = CreateValve(51, pr1cX - 6, ltg3Y, 1);
var v52 = CreateValve(52, pr1cX - 6, ltg4Y, 3); var v52 = CreateValve(52, pr1cX - 6, ltg4Y, 3);
@@ -386,8 +380,8 @@ namespace PamhagenSysCtrl.Helpers {
var xp7 = new PipeJoin(pr2cX, ltg8Y); var xp7 = new PipeJoin(pr2cX, ltg8Y);
var xp8 = new PipeJoin(pr2cX, ltg9Y); var xp8 = new PipeJoin(pr2cX, ltg9Y);
CreatePipe(V6, xp1, true, label: "Ltg. 3", labelOffsetX: 6); CreatePipe(v6, xp1, true, label: "Ltg. 3", labelOffsetX: 6);
CreatePipe(V8, xp2, true, label: "Ltg. 4", labelOffsetX: 18); CreatePipe(v8, xp2, true, label: "Ltg. 4", labelOffsetX: 18);
CreatePipe(v49, xp3, true, label: "Ltg. 8"); CreatePipe(v49, xp3, true, label: "Ltg. 8");
CreatePipe(P6, xp4, true, label: "Ltg. 9"); CreatePipe(P6, xp4, true, label: "Ltg. 9");
CreatePipe(xp1, v51, true); CreatePipe(xp1, v51, true);
@@ -410,97 +404,28 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(xp5, xp6); CreatePipe(xp5, xp6);
CreatePipe(xp7, xp8); CreatePipe(xp7, xp8);
var zv1 = new ExternalValve("ZV1", pr1cX, prY - 8, 0, () => App.Plant?.State.Press1_HasClearance ?? false); var zv1 = new ExternalValve("ZV1", pr1cX, prY - 8, 0, () => App.Plant?.Press1HasClearance ?? false);
var zv2 = new ExternalValve("ZV2", pr2cX, prY - 8, 2, () => App.Plant?.State.Press2_HasClearance ?? false); var zv2 = new ExternalValve("ZV2", pr2cX, prY - 8, 2, () => App.Plant?.Press2HasClearance ?? false);
CreatePipe(xp6, zv1, true); CreatePipe(xp6, zv1, true);
CreatePipe(xp8, zv2, true); CreatePipe(xp8, zv2, true);
CreateInlet(zv1, Press1, true); CreateInlet(zv1, Press1, true);
CreateInlet(zv2, Press2, true); CreateInlet(zv2, Press2, true);
PathShortcuts = [];
foreach (var key in Enum.GetValues<PamhagenPlant.ShortcutKey>()) {
var n = Enum.GetName(key);
if (n?.StartsWith("PATH_") != true) continue;
ISource src;
Pump p;
INode? inter = null;
ISink sink;
if (n.StartsWith("PATH_MW1_")) {
src = MW1;
p = MP1;
} else if (n.StartsWith("PATH_MW2_")) {
src = MW2;
p = MP2;
} else if (n.StartsWith("PATH_T12_")) {
src = Tank1;
p = P12;
} else if (n.StartsWith("PATH_T3_")) {
src = Tank3;
p = P3;
} else if (n.StartsWith("PATH_T4_")) {
src = Tank4;
p = P4;
} else if (n.StartsWith("PATH_T5_")) {
src = Tank5;
p = P5;
} else if (n.StartsWith("PATH_P6_")) {
src = Tank6;
p = P6;
} else {
continue;
}
if (n.Contains("_LTG1_")) {
inter = V5;
} else if (n.Contains("_LTG2_")) {
inter = V7;
} else if (n.Contains("_LTG3_")) {
inter = V6;
} else if (n.Contains("_LTG4_")) {
inter = V8;
}
if (n.EndsWith("_PRESS1")) {
sink = Press1;
} else if (n.EndsWith("_PRESS2")) {
sink = Press2;
} else if (n.EndsWith("_T1")) {
sink = Tank1;
} else if (n.EndsWith("_T2")) {
sink = Tank2;
} else if (n.EndsWith("_T3")) {
sink = Tank3;
} else if (n.EndsWith("_T4")) {
sink = Tank4;
} else if (n.EndsWith("_T5")) {
sink = Tank5;
} else if (n.EndsWith("_T6")) {
sink = Tank6;
} else if (n.EndsWith("_T7")) {
sink = Tank7;
} else if (n.EndsWith("_T8")) {
sink = Tank8;
} else if (n.EndsWith("_T9")) {
sink = Tank9;
} else {
continue;
}
PathShortcuts[key] = (src, p, inter, sink);
}
} }
protected ControlledValve CreateValve(int n, double x, double y, int o) { protected ControlledValve CreateValve(int n, double x, double y, int o) {
return new(n, x, y, o, () => App.Plant?.State.Valve_OpenState(n), (v) => App.Plant?.ManualControlMode ?? false ? App.Plant.State.Valve_WantOpen(n) : SelectedPaths.Any(p => p.Contains(v))); return new(n, x, y, o, () => App.Plant?.IsValveOpen(n) ?? false, (v) => App.Plant?.ManualControlMode ?? false ? App.Plant.WantValveOpen(n) : SelectedPaths.Any(p => p.Hops.Any(h => h.Node == v)));
} }
protected static Tank CreateTank(int n, double x, double y, int capacity) { protected static Tank CreateTank(int n, double x, double y, int capacity) {
return new($"Tank {n}", x, y, capacity, () => App.Plant?.State.Tanks_FillLevel[n - 1] ?? FillState.Unknown, () => true); return new($"Tank {n}", x, y, capacity, () => App.Plant?.TankFillLevels[n - 1] ?? FillState.Unknown);
} }
private int PathCost(Path p) { private int PathCost(Path p) {
return return
1000 * p.Valves.Count() + 1000 * p.Hops.Count(h => h.Node is Valve) +
(p.InnerValves.Any(v => v.Nr == 15) && !p.InnerValves.Any(v => v.Nr == 16 || v.Nr == 20) ? 10 : 0) + (p.Hops.Any(h => h.Node is ControlledValve v && v.Nr == 15) && !p.Hops.Any(h => h.Node is ControlledValve v && (v.Nr == 16 || v.Nr == 20)) ? 10 : 0) +
(p.InnerValves.Any(v => v.Nr == 11) && !p.InnerValves.Any(v => v.Nr == 14 || v.Nr == 21) ? 10 : 0) + (p.Hops.Any(h => h.Node is ControlledValve v && v.Nr == 11) && !p.Hops.Any(h => h.Node is ControlledValve v && (v.Nr == 14 || v.Nr == 21)) ? 10 : 0) +
(p.InnerValves.Any(v => v.Nr == 6) ? 10 : 0) + (p.Hops.Any(h => h.Node is ControlledValve v && v.Nr == 6) ? 10 : 0) +
p.Bends; p.Bends;
} }
@@ -512,47 +437,76 @@ namespace PamhagenSysCtrl.Helpers {
} }
} }
public static IEnumerable<(INode Start, IEdge Edge, bool EdgeReverse, INode End)> TraverseSubgraph(INode start, Func<INode, bool> isBorder, HashSet<IEdge>? _visited = null, bool reverse = false) { public void TraverseSubgraph(INode start, Func<INode, bool> invalid, HashSet<IEdge>? _visited = null, Action<INode, bool, IEdge>? edgeAction = null, Action<ControlledValve>? valveAction = null, bool reverse = false) {
if (isBorder(start))
yield break;
var visited = _visited ?? []; var visited = _visited ?? [];
if (start is ControlledValve v) {
valveAction?.Invoke(v);
}
if (invalid(start)) {
return;
}
foreach (var edge in start.Inputs) { foreach (var edge in start.Inputs) {
if (visited.Contains(edge)) continue; if (visited.Contains(edge)) continue;
var o = edge.IsTwoWay && edge.Start == start ? edge.End : edge.Start; var o = edge.IsTwoWay && edge.Start == start ? edge.End : edge.Start;
yield return (start, edge, reverse || _visited == null, o); edgeAction?.Invoke(start, reverse || _visited == null, edge);
visited.Add(edge); visited.Add(edge);
foreach (var e in TraverseSubgraph(o, isBorder, visited, reverse || _visited == null)) TraverseSubgraph(o, invalid, visited, edgeAction, valveAction, reverse || _visited == null);
yield return e;
} }
foreach (var edge in start.Outputs) { foreach (var edge in start.Outputs) {
if (visited.Contains(edge)) continue; if (visited.Contains(edge)) continue;
var o = edge.IsTwoWay && edge.End == start ? edge.Start : edge.End; var o = edge.IsTwoWay && edge.End == start ? edge.Start : edge.End;
yield return (start, edge, reverse, o); edgeAction?.Invoke(start, reverse, edge);
visited.Add(edge); visited.Add(edge);
foreach (var e in TraverseSubgraph(o, isBorder, visited, reverse)) TraverseSubgraph(o, invalid, visited, edgeAction, valveAction, reverse);
yield return e;
} }
} }
public void AddPath(Path path) { public void AddPath(Path path) {
_selectedPaths.Add(path); _selectedPaths.Add(path);
path.LockValves(); UpdateValvesAdjacientToPath(path, true, false);
if (App.Plant is PamhagenPlant plant) { if (App.Plant is PamhagenPlant plant) {
foreach (var v in path.InnerValves) plant.OpenV(v.Nr); foreach (var (_, node) in path.Hops) {
foreach (var v in path.BorderValves) plant.CloseV(v.Nr); if (node is ControlledValve v) {
plant.OpenV(v.Nr);
}
}
} }
} }
public void RemovePath(Path path) { public void RemovePath(Path path) {
_selectedPaths.Remove(path); _selectedPaths.Remove(path);
path.UnlockValves(); UpdateValvesAdjacientToPath(path, false);
foreach (var p in _selectedPaths) UpdateValvesAdjacientToPath(p, true);
if (App.Plant is PamhagenPlant plant) { if (App.Plant is PamhagenPlant plant) {
foreach (var v in path.InnerValves) plant.CloseV(v.Nr); foreach (var (_, node) in path.Hops) {
if (node is ControlledValve v) {
plant.CloseV(v.Nr);
}
}
} }
} }
public static void ColorPipesAdjacientToPath(Path path, Brush? color1, MotorState flowArrows, Brush? color2) { private void UpdateValvesAdjacientToPath(Path path, bool locked, bool? wantOpen = null) {
foreach (var (edge, node) in path.Hops) {
TraverseSubgraph(node, (n) => n is ControlledValve || 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);
} else {
App.Plant?.CloseV(v.Nr);
}
}
});
}
}
public void ColorPipesAdjacientToPath(Path path, Brush? color1, MotorState flowArrows, Brush? color2) {
var color = color1; var color = color1;
var flowPathOffset = 0.0; var flowPathOffset = 0.0;
HashSet<IEdge> visited = []; HashSet<IEdge> visited = [];
@@ -566,15 +520,14 @@ namespace PamhagenSysCtrl.Helpers {
} }
visited.Add(edge); visited.Add(edge);
if (node is Pump) color = color2; if (node is Pump) color = color2;
foreach (var (_, e, _, _) in TraverseSubgraph(node, (n) => n is Valve || n is ISink || n is Pump, visited)) { TraverseSubgraph(node, (n) => n is Valve || n is ISink || n is Pump, visited, edgeAction: (s, r, e) => e.Highlight = color);
e.Highlight = color;
}
} }
} }
public static void ColorPipesByValves(INode start, Brush? color1, MotorState flowArrows, Brush? color2) { public void ColorPipesByValves(INode start, Brush? color1, MotorState flowArrows, Brush? color2) {
var flowPathOffset = 0.0; var flowPathOffset = 0.0;
foreach (var (s, e, r, _) in TraverseSubgraph(start, (n) => n is ISink || n is Outlet || !((n as Valve)?.IsOpen ?? (n as ExternalValve)?.IsOpen ?? true))) { TraverseSubgraph(start, (n) => n is ISink || n is Outlet || !((n as Valve)?.IsOpen ?? (n as ExternalValve)?.IsOpen ?? true), null,
edgeAction: (s, r, e) => {
if (((e.Start as Valve)?.IsOpen ?? true) && ((e.End as Valve)?.IsOpen ?? true)) { if (((e.Start as Valve)?.IsOpen ?? true) && ((e.End as Valve)?.IsOpen ?? true)) {
e.Highlight = color1; e.Highlight = color1;
if (flowArrows == MotorState.Forward || flowArrows == MotorState.Backward) { if (flowArrows == MotorState.Forward || flowArrows == MotorState.Backward) {
@@ -587,7 +540,7 @@ namespace PamhagenSysCtrl.Helpers {
} else { } else {
e.Highlight = color2; e.Highlight = color2;
} }
} });
} }
} }
} }
File diff suppressed because it is too large. Load diff
@@ -1,18 +1,18 @@
using System.IO.Ports; using System.IO.Ports;
namespace PamhagenSysCtrl.Helpers.Plc { namespace PamhagenSysCtrl.Helpers {
public class LegacyPamhagenPlc : IDisposable { public class PamhagenPlc : IDisposable {
public record struct SensorStates( public record struct SensorStates(
long PressureSensors, long PressureSensors,
FillState FillLevelMW1, FillState FillingLevelMW1,
FillState FillLevelMW2, FillState FillingLevelMW2,
bool ClearancePress1, bool ClearancePress1,
bool ClearancePress2, bool ClearancePress2,
int TroughSelector, int TroughSelector,
double GradationOe, double GradationOe,
FillState[] FillLevelTanks, FillState[] FillingLevelTanks,
MotorState PressCrossAuger MotorState PresseQuerSchnecke
) { ) {
public readonly bool GetDS(int n) => (PressureSensors & (1L << (n - 1))) != 0; public readonly bool GetDS(int n) => (PressureSensors & (1L << (n - 1))) != 0;
} }
@@ -34,12 +34,8 @@ namespace PamhagenSysCtrl.Helpers.Plc {
protected readonly T1 Plc; protected readonly T1 Plc;
public string ComPort => Plc.PortName; public PamhagenPlc(string portName, Parity parity = Parity.Odd) {
public T1.Status LastStatus => Plc.LastStatus; Plc = new T1(portName, parity);
public T1Error LastError => Plc.LastError;
public LegacyPamhagenPlc(string comPort, Parity parity = Parity.Odd) {
Plc = new T1(comPort, parity);
} }
public void Dispose() { public void Dispose() {
@@ -49,7 +45,7 @@ namespace PamhagenSysCtrl.Helpers.Plc {
protected static FillState Int3ToFillState(int n, bool bigEndian = true) { protected static FillState Int3ToFillState(int n, bool bigEndian = true) {
return return
(bigEndian && (n & 0x4) != 0) || (!bigEndian && (n & 0x1) != 0) ? FillState.AlmostFull : (bigEndian && (n & 0x4) != 0) || (!bigEndian && (n & 0x1) != 0) ? FillState.Full :
(bigEndian && (n & 0x2) != 0) || (!bigEndian && (n & 0x2) != 0) ? FillState.Half : (bigEndian && (n & 0x2) != 0) || (!bigEndian && (n & 0x2) != 0) ? FillState.Half :
(bigEndian && (n & 0x1) != 0) || (!bigEndian && (n & 0x4) != 0) ? FillState.Filled : (bigEndian && (n & 0x1) != 0) || (!bigEndian && (n & 0x4) != 0) ? FillState.Filled :
n == 0 ? FillState.Empty : n == 0 ? FillState.Empty :
@@ -95,7 +91,7 @@ namespace PamhagenSysCtrl.Helpers.Plc {
throw new T1Exception($"Interner Fehler: {error} ({(int)error:0000})"); throw new T1Exception($"Interner Fehler: {error} ({(int)error:0000})");
} }
public async Task<(ushort RW010, ushort RW011, ushort RW012, ushort RW013, ushort RW014, ushort RW015, ushort RW016)> ReadInputRegisters() { protected async Task<(ushort RW010, ushort RW011, ushort RW012, ushort RW013, ushort RW014, ushort RW015, ushort RW016)> ReadInputRegisters() {
var d = await Plc.DataRead("RW010", 7); var d = await Plc.DataRead("RW010", 7);
// RW010, RW011, RW012, RW013 Drucksensoren // RW010, RW011, RW012, RW013 Drucksensoren
// RW014 Füllstände A1, A2, Presse1, Presse2, Wannenselekt // RW014 Füllstände A1, A2, Presse1, Presse2, Wannenselekt
@@ -1,4 +1,4 @@
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
@@ -13,7 +13,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
protected Func<Valve, bool> CallbackWantOpen; protected Func<Valve, bool> CallbackWantOpen;
public ControlledValve(int nr, double x, double y, int orientation, Func<bool?> cbOpen, Func<Valve, bool> cbWant) : public ControlledValve(int nr, double x, double y, int orientation, Func<bool> cbOpen, Func<Valve, bool> cbWant) :
base(x, y, orientation, cbOpen) { base(x, y, orientation, cbOpen) {
Nr = nr; Nr = nr;
CallbackWantOpen = cbWant; CallbackWantOpen = cbWant;
@@ -23,9 +23,9 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
if (App.Plant?.ManualControlMode ?? false) { if (App.Plant?.ManualControlMode ?? false) {
_text?.Visibility = Visibility.Visible; _text?.Visibility = Visibility.Visible;
_triangles?.Fill = _triangles?.Fill =
!WantOpen && IsClosed && !isHovering ? PamhagenBrushes.Red : !WantOpen && !IsOpen && !isHovering ? PamhagenBrushes.Red :
!WantOpen && IsClosed ? PamhagenBrushes.Green : !WantOpen && !IsOpen ? PamhagenBrushes.Green :
!WantOpen && !IsClosed ? PamhagenBrushes.DimRed : !WantOpen && IsOpen ? PamhagenBrushes.DimRed :
WantOpen && !IsOpen ? PamhagenBrushes.DimGreen : WantOpen && !IsOpen ? PamhagenBrushes.DimGreen :
WantOpen && IsOpen && !isHovering ? PamhagenBrushes.Green : WantOpen && IsOpen && !isHovering ? PamhagenBrushes.Green :
WantOpen && IsOpen ? PamhagenBrushes.Red : WantOpen && IsOpen ? PamhagenBrushes.Red :
@@ -33,19 +33,12 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} else { } else {
_text?.Visibility = isHovering ? Visibility.Visible : Visibility.Hidden; _text?.Visibility = isHovering ? Visibility.Visible : Visibility.Hidden;
_triangles?.Fill = _triangles?.Fill =
IsLocked && !WantOpen && IsClosed ? PamhagenBrushes.DimOrange : IsLocked && !WantOpen && !IsOpen ? PamhagenBrushes.DimOrange :
IsLocked && !WantOpen && !IsClosed ? PamhagenBrushes.Red : IsLocked && !WantOpen && IsOpen ? PamhagenBrushes.Red :
IsLocked && WantOpen && !IsOpen ? PamhagenBrushes.Yellow : IsLocked && WantOpen && !IsOpen ? PamhagenBrushes.Yellow :
IsLocked && WantOpen && IsOpen ? PamhagenBrushes.Green : IsLocked && WantOpen && IsOpen ? PamhagenBrushes.Green :
Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray); Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray);
} }
if (!IsClosed) {
_topLine?.Points[1] = _middlePoints.Open.Top;
_bottomLine?.Points[1] = _middlePoints.Open.Bottom;
} else {
_topLine?.Points[1] = _middlePoints.Closed.Top;
_bottomLine?.Points[1] = _middlePoints.Closed.Bottom;
}
} }
} }
} }
@@ -6,7 +6,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public override string Label { get; } public override string Label { get; }
public ExternalValve(string label, double x, double y, int orientation, Func<bool?> cbOpen) : public ExternalValve(string label, double x, double y, int orientation, Func<bool> cbOpen) :
base(x, y, orientation, cbOpen) { base(x, y, orientation, cbOpen) {
Label = label; Label = label;
} }
@@ -17,13 +17,6 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
!IsOpen ? PamhagenBrushes.Red : !IsOpen ? PamhagenBrushes.Red :
IsOpen ? PamhagenBrushes.Green : IsOpen ? PamhagenBrushes.Green :
Highlight ?? Brushes.DarkGray; Highlight ?? Brushes.DarkGray;
if (!IsClosed) {
_topLine?.Points[1] = _middlePoints.Open.Top;
_bottomLine?.Points[1] = _middlePoints.Open.Bottom;
} else {
_topLine?.Points[1] = _middlePoints.Closed.Top;
_bottomLine?.Points[1] = _middlePoints.Closed.Bottom;
}
} }
} }
} }
@@ -2,7 +2,6 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public interface ISink : INode { public interface ISink : INode {
public double TopY { get; } public double TopY { get; }
public bool IsFull { get; } public bool IsFull { get; }
public bool HasFillingClearance { get; }
public int? CapacityLiters { get; } public int? CapacityLiters { get; }
} }
} }
+2 -36
View File
@@ -1,43 +1,9 @@
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public record struct Path( public record struct Path(
INode Start, INode Start,
(IEdge Edge, INode Node)[] Hops IEnumerable<(IEdge Edge, INode Node)> Hops)
) { {
public readonly int Bends => Hops.Sum(h => (h.Node is not Valve && h.Node is not PipeJoin ? 1 : 0) + (h.Edge.Start.CenterX != h.Edge.End.CenterX && h.Edge.Start.CenterY != h.Edge.End.CenterY ? 1 : 0)); public readonly int Bends => Hops.Sum(h => (h.Node is not Valve && h.Node is not PipeJoin ? 1 : 0) + (h.Edge.Start.CenterX != h.Edge.End.CenterX && h.Edge.Start.CenterY != h.Edge.End.CenterY ? 1 : 0));
public readonly double Length => Hops.Sum(h => Math.Abs(h.Edge.Start.CenterX - h.Edge.End.CenterX) + Math.Abs(h.Edge.Start.CenterY - h.Edge.End.CenterY)); public readonly double Length => Hops.Sum(h => Math.Abs(h.Edge.Start.CenterX - h.Edge.End.CenterX) + Math.Abs(h.Edge.Start.CenterY - h.Edge.End.CenterY));
public readonly INode End => Hops.Last().Node;
public readonly IEnumerable<INode> Nodes => [Start, .. Hops.Select(h => h.Node)];
public readonly bool Contains(INode node) => Nodes.Any(h => h == node);
public readonly bool Matches(INode start, INode end) => Matches(start, null, end);
public readonly bool Matches(INode start, INode? intermediate, INode end) => Start == start && End == end && (intermediate == null || Nodes.Any(n => n == intermediate));
public readonly IEnumerable<Pump> Pumps => Nodes.Select(n => n as Pump).Where(p => p != null).Cast<Pump>();
public readonly IEnumerable<Valve> Valves => Nodes.Select(n => n as Valve).Where(v => v != null).Cast<Valve>();
private readonly IEnumerable<ControlledValve> InnerAndBorderValves => Nodes
.SelectMany(node => PamhagenGraph.TraverseSubgraph(node, (n) => n is ControlledValve || n is ISink || n is Pump))
.Select(e => e.End as ControlledValve)
.Where(v => v != null)
.Cast<ControlledValve>()
.Distinct();
public readonly IEnumerable<ControlledValve> InnerValves => Nodes.Select(n => n as ControlledValve).Where(v => v != null).Cast<ControlledValve>();
public readonly IEnumerable<ControlledValve> BorderValves => InnerAndBorderValves.Except(InnerValves);
public readonly bool HasActivePump => Nodes.Any(n => n is Pump p && p.IsActive);
public readonly bool IsReady => InnerValves.All(v => v.IsOpen && v.WantOpen && v.IsLocked) && BorderValves.All(v => v.IsClosed && !v.WantOpen && v.IsLocked);
public readonly void LockValves() {
foreach (var v in InnerAndBorderValves) {
v.LockedForPaths.Add(this);
}
}
public readonly void UnlockValves() {
foreach (var v in InnerAndBorderValves) {
v.LockedForPaths.Remove(this);
}
}
} }
} }
+1 -4
View File
@@ -18,24 +18,21 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public double TopY => CenterY - DIAMETER / 2; public double TopY => CenterY - DIAMETER / 2;
public FillState FillState => CallbackFillState(); public FillState FillState => CallbackFillState();
public bool IsFull => FillState == FillState.Full; public bool IsFull => FillState == FillState.Full;
public bool HasFillingClearance => CallbackFillingClearance();
public int? CapacityLiters { get; set; } public int? CapacityLiters { get; set; }
protected Func<FillState> CallbackFillState; protected Func<FillState> CallbackFillState;
protected Func<bool> CallbackFillingClearance;
private Polygon? _frame; private Polygon? _frame;
private Ellipse? _circle; private Ellipse? _circle;
private TextBlock? _text; private TextBlock? _text;
public Press(string label, double x, double y, int capacityLiters, Func<FillState> cbFillState, Func<bool> cbFillingClearance) { public Press(string label, double x, double y, int capacityLiters, Func<FillState> cbFillState) {
CenterX = x; CenterX = x;
CenterY = y; CenterY = y;
Label = label; Label = label;
CapacityLiters = capacityLiters; CapacityLiters = capacityLiters;
CallbackFillState = cbFillState; CallbackFillState = cbFillState;
CallbackFillingClearance = cbFillingClearance;
Inputs = new HashSet<IEdge>(); Inputs = new HashSet<IEdge>();
Outputs = new HashSet<IEdge>(); Outputs = new HashSet<IEdge>();
} }
+1 -4
View File
@@ -17,7 +17,6 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public FillState FillState => CallbackFillState(); public FillState FillState => CallbackFillState();
public bool IsFull => FillState == FillState.Full; public bool IsFull => FillState == FillState.Full;
public bool IsFilled => FillState != FillState.Empty; public bool IsFilled => FillState != FillState.Empty;
public bool HasFillingClearance => CallbackFillingClearance();
public ISet<IEdge> Inputs { get; init; } public ISet<IEdge> Inputs { get; init; }
public ISet<IEdge> Outputs { get; init; } public ISet<IEdge> Outputs { get; init; }
@@ -25,18 +24,16 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public int? CapacityLiters { get; set; } public int? CapacityLiters { get; set; }
protected Func<FillState> CallbackFillState; protected Func<FillState> CallbackFillState;
protected Func<bool> CallbackFillingClearance;
private Shape? _rect; private Shape? _rect;
private TextBlock? _text; private TextBlock? _text;
public Tank(string label, double x, double y, int capacityLiters, Func<FillState> cbFillState, Func<bool> cbFillingClearance) { public Tank(string label, double x, double y, int capacityLiters, Func<FillState> cbFillState) {
CenterX = x; CenterX = x;
CenterY = y; CenterY = y;
Label = label; Label = label;
CapacityLiters = capacityLiters; CapacityLiters = capacityLiters;
CallbackFillState = cbFillState; CallbackFillState = cbFillState;
CallbackFillingClearance = cbFillingClearance;
Inputs = new HashSet<IEdge>(); Inputs = new HashSet<IEdge>();
Outputs = new HashSet<IEdge>(); Outputs = new HashSet<IEdge>();
} }
+3 -12
View File
@@ -16,22 +16,19 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public int Orientation { get; set; } public int Orientation { get; set; }
public bool IsOpen => CallbackIsOpen() == true; public bool IsOpen => CallbackIsOpen();
public bool IsClosed => CallbackIsOpen() == false;
public bool IsTransitioning => CallbackIsOpen() == null;
public ISet<IEdge> Inputs { get; init; } public ISet<IEdge> Inputs { get; init; }
public ISet<IEdge> Outputs { get; init; } public ISet<IEdge> Outputs { get; init; }
protected Func<bool?> CallbackIsOpen; protected Func<bool> CallbackIsOpen;
protected Rectangle? _background; protected Rectangle? _background;
protected Polygon? _triangles; protected Polygon? _triangles;
protected Polyline? _topLine; protected Polyline? _topLine;
protected Polyline? _bottomLine; protected Polyline? _bottomLine;
protected ((Point Top, Point Bottom) Closed, (Point Top, Point Bottom) Open) _middlePoints;
protected TextBlock? _text; protected TextBlock? _text;
public Valve(double x, double y, int orientation, Func<bool?> cbOpen) { public Valve(double x, double y, int orientation, Func<bool> cbOpen) {
CenterX = x; CenterX = x;
CenterY = y; CenterY = y;
CallbackIsOpen = cbOpen; CallbackIsOpen = cbOpen;
@@ -86,9 +83,6 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
Graph.ToPx(CenterX + WIDTH / 4, CenterY, scale), Graph.ToPx(CenterX + WIDTH / 4, CenterY, scale),
Graph.ToPx(CenterX + WIDTH / 2, CenterY + LENGTH / 4, scale), Graph.ToPx(CenterX + WIDTH / 2, CenterY + LENGTH / 4, scale),
]; ];
if (_topLine != null && _bottomLine != null)
_middlePoints = ((_topLine.Points[1], _bottomLine.Points[1]),
(Graph.ToPx(CenterX - WIDTH / 3, CenterY, scale), Graph.ToPx(CenterX + WIDTH / 3, CenterY, scale)));
} else { } else {
Graph.SetCenter(_background, CenterX, CenterY, LENGTH, LENGTH / 2, scale); Graph.SetCenter(_background, CenterX, CenterY, LENGTH, LENGTH / 2, scale);
_triangles?.Points = [ _triangles?.Points = [
@@ -107,9 +101,6 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
Graph.ToPx(CenterX, CenterY + WIDTH / 4, scale), Graph.ToPx(CenterX, CenterY + WIDTH / 4, scale),
Graph.ToPx(CenterX + LENGTH / 4, CenterY + WIDTH / 2, scale), Graph.ToPx(CenterX + LENGTH / 4, CenterY + WIDTH / 2, scale),
]; ];
if (_topLine != null && _bottomLine != null)
_middlePoints = ((_topLine.Points[1], _bottomLine.Points[1]),
(Graph.ToPx(CenterX, CenterY - WIDTH / 3, scale), Graph.ToPx(CenterX, CenterY + WIDTH / 3, scale)));
} }
_triangles?.StrokeThickness = border; _triangles?.StrokeThickness = border;
_topLine?.StrokeThickness = border; _topLine?.StrokeThickness = border;
-365
View File
@@ -1,365 +0,0 @@
using System.IO.Ports;
namespace PamhagenSysCtrl.Helpers.Plc {
public class PamhagenPlc : IDisposable {
private bool _manualControlMode = false;
private bool _overridePathClearnaces = false;
private bool _overrideDryRunClearances = false;
public bool ManualControlMode { get => _manualControlMode; set { _manualControlMode = value; UpdateInterlocks(); } }
public bool OverridePathClearances { get => _overridePathClearnaces; set { _overridePathClearnaces = value; UpdateInterlocks(); } }
public bool OverrideDryRunClearances { get => _overrideDryRunClearances; set { _overrideDryRunClearances = value; UpdateInterlocks(); } }
internal bool MP1_WantActive { get; private set; }
internal bool MP2_WantActive { get; private set; }
internal bool P12_WantActive { get; private set; }
internal bool P3_WantActive { get; private set; }
internal bool P4_WantActive { get; private set; }
internal bool P5_WantActive { get; private set; }
internal bool P6_WantActive { get; private set; }
protected LegacyPamhagenPlc Plc;
public PamhagenState State { get; private set; }
public PamhagenState LastState { get; private set; }
public string? ComPort => Plc?.ComPort;
public T1.Status? LastStatus => Plc?.LastStatus;
public T1Error? LastError => Plc?.LastError;
internal LegacyPamhagenPlc.ActuatorStates Actuators;
internal LegacyPamhagenPlc.ActuatorStates LastActuators;
internal LegacyPamhagenPlc.SensorStates[] LastSensors = [];
internal readonly int ConsiderLastSensorsOpen;
internal readonly int ConsiderLastSensorsClose;
internal readonly int ConsiderLastSensorsMW;
internal readonly FillState AutoStirrer;
public PamhagenPlc(string comPort, int sensorDelayOpenMs, int sensorDelayCloseMs, int sensorDelayMwMs, FillState autoStirrer = FillState.Half, Parity parity = Parity.Odd) {
Plc = new(comPort, parity);
ConsiderLastSensorsOpen = sensorDelayOpenMs / 125 + 1;
ConsiderLastSensorsClose = sensorDelayCloseMs / 125 + 1;
ConsiderLastSensorsMW = sensorDelayMwMs / 125 + 1;
AutoStirrer = autoStirrer;
LastSensors = new LegacyPamhagenPlc.SensorStates[Math.Max(Math.Max(ConsiderLastSensorsOpen, ConsiderLastSensorsClose), ConsiderLastSensorsMW)];
}
public void Dispose() {
GC.SuppressFinalize(this);
Plc.Dispose();
}
public async Task Initialize() {
await Plc.TestConnection();
Actuators = await Plc.ReadOutputs();
LastActuators = Actuators;
var sensors = await Plc.ReadInputs();
LastSensors[0] = sensors;
State = new(LastSensors, this);
LastState = State;
}
public async Task Shutdown() {
await Plc.WriteOutputs(new() { Valves = Actuators.Valves });
}
public async Task Update() {
UpdateInterlocks();
if (Actuators != LastActuators) {
LastActuators = Actuators;
await Plc.WriteOutputs(LastActuators);
}
var sensors = await Plc.ReadInputs();
Array.Copy(LastSensors, 0, LastSensors, 1, LastSensors.Length - 1);
LastSensors[0] = sensors;
var current = new PamhagenState(LastSensors, this);
LastState = State;
State = current;
UpdateInterlocks();
}
protected void UpdateInterlocks() {
if (!ManualControlMode) {
if (State.PressCrossAuger_IsActive && !State.Auger2_IsActive) StartAuger2();
if (State.Rebler_IsActive && !State.Auger1_IsActive) StartAuger1();
if (State.Auger1_IsActive && !State.Auger2_IsActive) StartAuger2();
if (State.Auger2_IsActive && !State.Auger3_IsActive) StartAuger3();
if (State.TroughAuger_IsActive && !State.TroughAuger_HasClearance)
StopTroughAuger();
if (!State.PressCrossAuger_IsActive && !State.Auger1_IsActive) {
StopAuger2();
StopAuger3();
}
}
if (State.MP1_IsActive && !State.MP1_HasClearance) StopMP1();
if (State.MP2_IsActive && !State.MP2_HasClearance) StopMP2();
if (State.P12_IsActive && !State.P12_HasClearance) DisableP12();
if (State.P3_IsActive && !State.P3_HasClearance) DisableP3();
if (State.P4_IsActive && !State.P4_HasClearance) DisableP4();
if (State.P5_IsActive && !State.P5_HasClearance) DisableP5();
if (State.P6_IsActive && !State.P6_HasClearance) DisableP6();
if (MP1_WantActive && State.MP1_HasClearance) StartMP1Forward();
if (MP2_WantActive && State.MP2_HasClearance) StartMP2Forward();
if (P12_WantActive && State.P12_HasClearance) EnableP12();
if (P3_WantActive && State.P3_HasClearance) EnableP3();
if (P4_WantActive && State.P4_HasClearance) EnableP4();
if (P5_WantActive && State.P5_HasClearance) EnableP5();
if (P6_WantActive && State.P6_HasClearance) EnableP6();
if (!ManualControlMode) {
if (State.MW1_Stirrer_IsActive && (State.MW1_FillLevel == FillState.Empty || (LastState.MW1_FillLevel >= AutoStirrer && State.MW1_FillLevel < AutoStirrer))) {
StopStirrerMW1();
}
if (State.MW2_Stirrer_IsActive && (State.MW2_FillLevel == FillState.Empty || (LastState.MW2_FillLevel >= AutoStirrer && State.MW2_FillLevel < AutoStirrer))) {
StopStirrerMW2();
}
}
}
public void OpenV(int n) => SetV(n, true);
public void CloseV(int n) => SetV(n, false);
protected void SetV(int n, bool val) {
if (n < 1 || n > 57) throw new ArgumentException("Invalid value for n: 1 <= #V <= 57");
Actuators.SetV(n, val);
}
public void SelectMW1() {
Actuators.SetV(58, true);
}
public void SelectMW2() {
Actuators.SetV(58, false);
}
public void StartTroughAuger() {
if (!ManualControlMode && !State.TroughAuger_HasClearance)
throw new NoClearanceException("Schnecke Auffangwanne hat keine Freigabe");
Actuators.TroughAuger = true;
}
public void StopTroughAuger() {
Actuators.TroughAuger = false;
}
public void StartRebler() {
Actuators.Rebler = true;
if (!ManualControlMode) StartAuger1();
}
public void StopRebler() {
if (!ManualControlMode) StopTroughAuger();
Actuators.Rebler = false;
if (!ManualControlMode) StopAuger1();
}
public void StartStirrerMW1() {
if (!ManualControlMode && State.MW1_FillLevel == FillState.Empty) throw new NoClearanceException("MW1 ist leer");
Actuators.StirrerMW1 = true;
}
public void StopStirrerMW1() {
Actuators.StirrerMW1 = false;
}
public void StartStirrerMW2() {
if (!ManualControlMode && State.MW2_FillLevel == FillState.Empty) throw new NoClearanceException("MW2 ist leer");
Actuators.StirrerMW2 = true;
}
public void StopStirrerMW2() {
Actuators.StirrerMW2 = false;
}
public void StartAuger1() {
Actuators.Auger1 = true;
if (!ManualControlMode) StartAuger2();
}
public void StopAuger1() {
if (!ManualControlMode && State.Rebler_IsActive) throw new NoClearanceException("Rebler aktiv");
Actuators.Auger1 = false;
if (!ManualControlMode && !State.PressCrossAuger_IsActive)
StopAuger2();
}
public void StartAuger2() {
Actuators.Auger2 = true;
if (!ManualControlMode) StartAuger3();
}
public void StopAuger2() {
if (!ManualControlMode && State.PressCrossAuger_IsActive) throw new NoClearanceException("Presse-Querschnecke aktiv");
if (!ManualControlMode && State.Auger1_IsActive) throw new NoClearanceException("Förderschnecke 1 aktiv");
Actuators.Auger2 = false;
if (!ManualControlMode)
StopAuger3();
}
public void StartAuger3() {
Actuators.Auger3 = true;
}
public void StopAuger3() {
if (!ManualControlMode && State.Auger2_IsActive) throw new NoClearanceException("Förderschnecke 2 aktiv");
Actuators.Auger3 = false;
}
public void StartWasteDisposal() {
StartAuger1();
StartAuger2();
StartAuger3();
}
public void StopWasteDisposal() {
StopAuger1();
StopAuger2();
StopAuger3();
}
public void StartGradationPump() {
Actuators.GradationPump = true;
}
public void StopGradationPump() {
Actuators.GradationPump = false;
}
public void StartMP1Forward() {
if (!State.MP1_HasClearance) {
if (!State.MP1_HasDryRunClearance || !State.MP1_WantsPathClearance)
throw new NoClearanceException("MP1 hat keine Freigabe");
MP1_WantActive = true;
return;
}
MP1_WantActive = false;
if (Actuators.MP1 == MotorState.Backward) throw new NoClearanceException("MP1 läuft bereits (zurück)");
Actuators.MP1 = MotorState.Forward;
if (!ManualControlMode && State.MW1_FillLevel >= AutoStirrer) StartStirrerMW1();
}
public void StartMP1Backward() {
MP1_WantActive = false;
if (!State.MP1_HasClearance) throw new NoClearanceException("MP1 hat keine Freigabe");
if (Actuators.MP1 == MotorState.Forward) throw new NoClearanceException("MP1 läuft bereits (vor)");
Actuators.MP1 = MotorState.Backward;
}
public void StopMP1() {
MP1_WantActive = false;
Actuators.MP1 = MotorState.Halt;
if (!ManualControlMode) StopStirrerMW1();
}
public void StartMP2Forward() {
if (!State.MP2_HasClearance) {
if (!State.MP2_HasDryRunClearance || !State.MP2_WantsPathClearance)
throw new NoClearanceException("MP2 hat keine Freigabe");
MP2_WantActive = true;
return;
}
MP2_WantActive = false;
if (Actuators.MP2 == MotorState.Backward) throw new NoClearanceException("MP2 läuft bereits (zurück)");
Actuators.MP2 = MotorState.Forward;
if (!ManualControlMode && State.MW2_FillLevel >= AutoStirrer) StartStirrerMW2();
}
public void StartMP2Backward() {
MP2_WantActive = false;
if (!State.MP2_HasClearance) throw new NoClearanceException("MP2 hat keine Freigabe");
if (Actuators.MP2 == MotorState.Forward) throw new NoClearanceException("MP2 läuft bereits (vor)");
Actuators.MP2 = MotorState.Backward;
}
public void StopMP2() {
MP2_WantActive = false;
Actuators.MP2 = MotorState.Halt;
if (!ManualControlMode) StopStirrerMW2();
}
public void EnableP12() {
if (!State.P12_HasClearance) {
if (!State.P12_HasDryRunClearance || !State.P12_WantsPathClearance)
throw new NoClearanceException("P1/2 hat keine Freigabe");
P12_WantActive = true;
return;
}
P12_WantActive = false;
if (!State.P12_HasClearance) throw new NoClearanceException("P1/2 hat keine Freigabe");
Actuators.P12 = MotorState.Forward;
}
public void DisableP12() {
P12_WantActive = false;
Actuators.P12 = MotorState.Halt;
}
public void EnableP3() {
if (!State.P3_HasClearance) {
if (!State.P3_HasDryRunClearance || !State.P3_WantsPathClearance)
throw new NoClearanceException("P3 hat keine Freigabe");
P3_WantActive = true;
return;
}
P3_WantActive = false;
Actuators.P3 = MotorState.Forward;
}
public void DisableP3() {
P3_WantActive = false;
Actuators.P3 = MotorState.Halt;
}
public void EnableP4() {
if (!State.P4_HasClearance) {
if (!State.P4_HasDryRunClearance || !State.P4_WantsPathClearance)
throw new NoClearanceException("P4 hat keine Freigabe");
P4_WantActive = true;
return;
}
P4_WantActive = false;
Actuators.P4 = MotorState.Forward;
}
public void DisableP4() {
P4_WantActive = false;
Actuators.P4 = MotorState.Halt;
}
public void EnableP5() {
if (!State.P5_HasClearance) {
if (!State.P5_HasDryRunClearance || !State.P5_WantsPathClearance)
throw new NoClearanceException("P5 hat keine Freigabe");
P5_WantActive = true;
return;
}
P5_WantActive = false;
Actuators.P5 = MotorState.Forward;
}
public void DisableP5() {
P5_WantActive = false;
Actuators.P5 = MotorState.Halt;
}
public void EnableP6() {
if (!State.P6_HasClearance) {
if (!State.P6_HasDryRunClearance || !State.P6_WantsPathClearance)
throw new NoClearanceException("P6 hat keine Freigabe");
P6_WantActive = true;
return;
}
P6_WantActive = false;
Actuators.P6 = MotorState.Forward;
}
public void DisableP6() {
P6_WantActive = false;
Actuators.P6 = MotorState.Halt;
}
}
}
@@ -1,372 +0,0 @@
namespace PamhagenSysCtrl.Helpers.Plc {
public readonly record struct PamhagenState {
public readonly record struct PumpState(
MotorState State, bool IsActive,
bool HasClearance, bool HasPathClearance, bool HasDryRunClearance,
bool WantsPathClearance, bool WantActive);
private readonly LegacyPamhagenPlc.SensorStates Sensors;
private readonly PamhagenPlc Plc;
private LegacyPamhagenPlc.ActuatorStates Actuators => Plc == null ? default : Plc.Actuators;
public bool ManualControlMode => Plc != null && Plc.ManualControlMode;
public bool OverridePathClearances => Plc != null && Plc.OverridePathClearances;
public bool OverrideDryRunClearances => Plc != null && Plc.OverrideDryRunClearances;
public long Valves => Actuators.Valves;
public readonly long OpenValves { get; }
public readonly long ClosedValves { get; }
public bool MW1_IsSelected => Sensors.TroughSelector == 1;
public bool MW2_IsSelected => Sensors.TroughSelector == 2;
public bool MW1_WantSelected => Actuators.GetV(58);
public bool MW2_WantSelected => !Actuators.GetV(58);
public readonly FillState MW1_FillLevel { get; }
public readonly FillState MW2_FillLevel { get; }
public FillState[] Tanks_FillLevel => Sensors.FillLevelTanks ?? [FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown];
public FillState T1_FillLevel => Sensors.FillLevelTanks[0];
public FillState T2_FillLevel => Sensors.FillLevelTanks[1];
public FillState T3_FillLevel => Sensors.FillLevelTanks[2];
public FillState T4_FillLevel => Sensors.FillLevelTanks[3];
public FillState T5_FillLevel => Sensors.FillLevelTanks[4];
public FillState T6_FillLevel => Sensors.FillLevelTanks[5];
public FillState T7_FillLevel => Sensors.FillLevelTanks[6];
public FillState T8_FillLevel => Sensors.FillLevelTanks[7];
public FillState T9_FillLevel => Sensors.FillLevelTanks[8];
public MotorState PressCrossAuger_State => Sensors.PressCrossAuger;
public bool PressCrossAuger_IsActive => PressCrossAuger_State == MotorState.Forward;
public bool TroughAuger_IsActive => Actuators.TroughAuger;
public bool Rebler_IsActive => Actuators.Rebler;
public bool MW1_Stirrer_IsActive => Actuators.StirrerMW1;
public bool MW2_Stirrer_IsActive => Actuators.StirrerMW2;
public bool Auger1_IsActive => Actuators.Auger1;
public bool Auger2_IsActive => Actuators.Auger2;
public bool Auger3_IsActive => Actuators.Auger3;
public bool WasteDisposal_IsActive => Auger1_IsActive || Auger2_IsActive || Auger3_IsActive;
public bool WasteDisposal_IsConsistent => (Auger1_IsActive && Auger2_IsActive && Auger3_IsActive) || (!Auger1_IsActive && !Auger2_IsActive && !Auger3_IsActive);
public MotorState MP1_State => Actuators.MP1;
public MotorState MP2_State => Actuators.MP2;
public MotorState P12_State => Actuators.P12;
public MotorState P3_State => Actuators.P3;
public MotorState P4_State => Actuators.P4;
public MotorState P5_State => Actuators.P5;
public MotorState P6_State => Actuators.P6;
public bool MP1_IsActive => Actuators.MP1 == MotorState.Forward || Actuators.MP1 == MotorState.Backward;
public bool MP2_IsActive => Actuators.MP2 == MotorState.Forward || Actuators.MP2 == MotorState.Backward;
public bool P12_IsActive => Actuators.P12 == MotorState.Forward;
public bool P3_IsActive => Actuators.P3 == MotorState.Forward;
public bool P4_IsActive => Actuators.P4 == MotorState.Forward;
public bool P5_IsActive => Actuators.P5 == MotorState.Forward;
public bool P6_IsActive => Actuators.P6 == MotorState.Forward;
public bool Press1_HasClearance => Sensors.ClearancePress1;
public bool Press2_HasClearance => Sensors.ClearancePress2;
public bool TroughAuger_HasClearance => Rebler_IsActive && !(MW1_IsSelected && MW1_FillLevel == FillState.Full) && !(MW2_IsSelected && MW2_FillLevel == FillState.Full);
private readonly bool MP1_PathClearance;
private readonly bool MP2_PathClearance;
private readonly bool P12_PathClearance;
private readonly bool P3_PathClearance;
private readonly bool P4_PathClearance;
private readonly bool P5_PathClearance;
private readonly bool P6_PathClearance;
private readonly bool MP1_DryRunClearance;
private readonly bool MP2_DryRunClearance;
private readonly bool P12_DryRunClearance;
private readonly bool P3_DryRunClearance;
private readonly bool P4_DryRunClearance;
private readonly bool P5_DryRunClearance;
private readonly bool P6_DryRunClearance;
public readonly bool MP1_WantsPathClearance { get; }
public readonly bool MP2_WantsPathClearance { get; }
public readonly bool P12_WantsPathClearance { get; }
public readonly bool P3_WantsPathClearance { get; }
public readonly bool P4_WantsPathClearance { get; }
public readonly bool P5_WantsPathClearance { get; }
public readonly bool P6_WantsPathClearance { get; }
public bool MP1_WantActive => Plc != null && Plc.MP1_WantActive;
public bool MP2_WantActive => Plc != null && Plc.MP2_WantActive;
public bool P12_WantActive => Plc != null && Plc.P12_WantActive;
public bool P3_WantActive => Plc != null && Plc.P3_WantActive;
public bool P4_WantActive => Plc != null && Plc.P4_WantActive;
public bool P5_WantActive => Plc != null && Plc.P5_WantActive;
public bool P6_WantActive => Plc != null && Plc.P6_WantActive;
public bool MP1_HasPathClearance => OverridePathClearances || MP1_PathClearance;
public bool MP2_HasPathClearance => OverridePathClearances || MP2_PathClearance;
public bool P12_HasPathClearance => OverridePathClearances || P12_PathClearance;
public bool P3_HasPathClearance => OverridePathClearances || P3_PathClearance;
public bool P4_HasPathClearance => OverridePathClearances || P4_PathClearance;
public bool P5_HasPathClearance => OverridePathClearances || P5_PathClearance;
public bool P6_HasPathClearance => OverridePathClearances || P6_PathClearance;
public bool MP1_HasDryRunClearance => OverrideDryRunClearances || MP1_DryRunClearance;
public bool MP2_HasDryRunClearance => OverrideDryRunClearances || MP2_DryRunClearance;
public bool P12_HasDryRunClearance => OverrideDryRunClearances || P12_DryRunClearance;
public bool P3_HasDryRunClearance => OverrideDryRunClearances || P3_DryRunClearance;
public bool P4_HasDryRunClearance => OverrideDryRunClearances || P4_DryRunClearance;
public bool P5_HasDryRunClearance => OverrideDryRunClearances || P5_DryRunClearance;
public bool P6_HasDryRunClearance => OverrideDryRunClearances || P6_DryRunClearance;
public bool MP1_HasClearance => MP1_HasDryRunClearance && MP1_HasPathClearance;
public bool MP2_HasClearance => MP2_HasDryRunClearance && MP2_HasPathClearance;
public bool P12_HasClearance => P12_HasDryRunClearance && P12_HasPathClearance;
public bool P3_HasClearance => P3_HasDryRunClearance && P3_HasPathClearance;
public bool P4_HasClearance => P4_HasDryRunClearance && P4_HasPathClearance;
public bool P5_HasClearance => P4_HasDryRunClearance && P5_HasPathClearance;
public bool P6_HasClearance => P6_HasDryRunClearance && P6_HasPathClearance;
public PumpState MP1 => new(MP1_State, MP1_IsActive, MP1_HasClearance, MP1_HasPathClearance, MP1_HasDryRunClearance, MP1_WantsPathClearance, MP1_WantActive);
public PumpState MP2 => new(MP2_State, MP2_IsActive, MP2_HasClearance, MP2_HasPathClearance, MP2_HasDryRunClearance, MP2_WantsPathClearance, MP2_WantActive);
public PumpState P12 => new(P12_State, P12_IsActive, P12_HasClearance, P12_HasPathClearance, P12_HasDryRunClearance, P12_WantsPathClearance, P12_WantActive);
public PumpState P3 => new(P3_State, P3_IsActive, P3_HasClearance, P3_HasPathClearance, P3_HasDryRunClearance, P3_WantsPathClearance, P3_WantActive);
public PumpState P4 => new(P4_State, P4_IsActive, P4_HasClearance, P4_HasPathClearance, P4_HasDryRunClearance, P4_WantsPathClearance, P4_WantActive);
public PumpState P5 => new(P5_State, P5_IsActive, P5_HasClearance, P5_HasPathClearance, P5_HasDryRunClearance, P5_WantsPathClearance, P5_WantActive);
public PumpState P6 => new(P6_State, P6_IsActive, P6_HasClearance, P6_HasPathClearance, P6_HasDryRunClearance, P6_WantsPathClearance, P6_WantActive);
public PamhagenState(IEnumerable<LegacyPamhagenPlc.SensorStates> sensors, PamhagenPlc plc) {
Sensors = sensors.First();
Plc = plc;
OpenValves = sensors.Take(Plc.ConsiderLastSensorsOpen).Aggregate(-1L, (m, s) => m & ~s.PressureSensors);
ClosedValves = sensors.Take(Plc.ConsiderLastSensorsClose).Aggregate(-1L, (m, s) => m & s.PressureSensors);
var mw1 = sensors.Take(Plc.ConsiderLastSensorsMW).Select(s => s.FillLevelMW1).Where(s => s != FillState.Unknown).Distinct().ToArray();
var mw2 = sensors.Take(Plc.ConsiderLastSensorsMW).Select(s => s.FillLevelMW2).Where(s => s != FillState.Unknown).Distinct().ToArray();
MW1_FillLevel =
mw1.Contains(FillState.Invalid) ? FillState.Invalid :
mw1.Length == 1 && mw1[0] == FillState.Empty ? mw1[0] :
mw1.Length == 1 ? mw1[0] + 1 :
mw1.Length >= 2 ? mw1[0] > mw1[1] ? mw1[0] : mw1[1] :
FillState.Unknown;
MW2_FillLevel =
mw2.Contains(FillState.Invalid) ? FillState.Invalid :
mw2.Length == 1 && mw2[0] == FillState.Empty ? mw2[0] :
mw2.Length == 1 ? mw2[0] + 1 :
mw2.Length >= 2 ? mw2[0] > mw2[1] ? mw2[0] : mw2[1] :
FillState.Unknown;
MP1_WantsPathClearance = WantsPathClearnace(this, Paths.MP1);
MP2_WantsPathClearance = WantsPathClearnace(this, Paths.MP2);
P12_WantsPathClearance = WantsPathClearnace(this, Paths.P12);
P3_WantsPathClearance = WantsPathClearnace(this, Paths.P3);
P4_WantsPathClearance = WantsPathClearnace(this, Paths.P4);
P5_WantsPathClearance = WantsPathClearnace(this, Paths.P5);
P6_WantsPathClearance = WantsPathClearnace(this, Paths.P6);
MP1_PathClearance = HasPathClearance(this, Paths.MP1);
MP2_PathClearance = HasPathClearance(this, Paths.MP2);
P12_PathClearance = HasPathClearance(this, Paths.P12);
P3_PathClearance = HasPathClearance(this, Paths.P3);
P4_PathClearance = HasPathClearance(this, Paths.P4);
P5_PathClearance = HasPathClearance(this, Paths.P5);
P6_PathClearance = HasPathClearance(this, Paths.P6);
MP1_DryRunClearance = MW1_FillLevel != FillState.Empty;
MP2_DryRunClearance = MW2_FillLevel != FillState.Empty;
P12_DryRunClearance = T1_FillLevel != FillState.Empty || T2_FillLevel != FillState.Empty;
P3_DryRunClearance = T3_FillLevel != FillState.Empty;
P4_DryRunClearance = T4_FillLevel != FillState.Empty;
P5_DryRunClearance = T5_FillLevel != FillState.Empty;
P6_DryRunClearance = true;
}
public bool Valve_IsClosed(int n) => (ClosedValves & (1L << (n - 1))) != 0;
public bool Valve_IsTransitioning(int n) => !Valve_IsClosed(n) && !Valve_IsOpen(n);
public bool Valve_IsOpen(int n) => (OpenValves & (1L << (n - 1))) != 0;
public bool? Valve_OpenState(int n) => Valve_IsOpen(n) ? true : Valve_IsClosed(n) ? false : null;
public bool Valve_WantOpen(int n) => Actuators.GetV(n);
public bool Valve_WantClosed(int n) => !Valve_WantOpen(n);
private record struct ValveStates(int[] Open, int[] Closed) {
public readonly ValveStateMask ToMask() => new(Open.Aggregate(0L, (m, n) => m | (1L << (n - 1))), Open.Concat(Closed).Aggregate(0L, (m, n) => m | (1L << (n - 1))));
public static ValveStates operator +(ValveStates a, ValveStates b) {
return new([.. a.Open, .. b.Open], [.. a.Closed, .. b.Closed]);
}
}
private record struct ValveStateMask(long States, long Mask) {
public static ValveStateMask[] From(IEnumerable<ValveStates> states) => [.. states.Select(s => s.ToMask())];
public static bool operator ==(long a, ValveStateMask b) => (a & b.Mask) == b.States;
public static bool operator >=(long a, ValveStateMask b) => (a & b.States) == b.States;
public static bool operator !=(long a, ValveStateMask b) => !(a == b);
public static bool operator <=(long a, ValveStateMask b) => !(a >= b);
}
private static bool HasSinglePathClearance(PamhagenState state, IEnumerable<ValveStateMask> masks) {
return masks.Any(m => state.Valves == m && state.OpenValves == m && ~state.ClosedValves == m);
}
private static bool HasAnyPathClearance(PamhagenState state, IEnumerable<ValveStateMask> masks) {
return masks.Any(m => state.Valves >= m && state.OpenValves >= m && ~state.ClosedValves >= m);
}
private static bool WantsSinglePathClearance(PamhagenState state, IEnumerable<ValveStateMask> masks) {
return masks.Any(m => state.Valves == m);
}
private static bool WantsAnyPathClearance(PamhagenState state, IEnumerable<ValveStateMask> masks) {
return masks.Any(m => state.Valves >= m);
}
private static bool HasPathClearance(PamhagenState state, (ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) masks) {
return
(HasSinglePathClearance(state, masks.Press1) && state.Press1_HasClearance) ||
(HasSinglePathClearance(state, masks.Press2) && state.Press2_HasClearance) ||
(HasSinglePathClearance(state, masks.Tanks[0]) && state.T1_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[1]) && state.T2_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[2]) && state.T3_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[3]) && state.T4_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[4]) && state.T5_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[5]) && state.T6_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[6]) && state.T7_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[7]) && state.T8_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[8]) && state.T9_FillLevel != FillState.Full) ||
false;
}
private static bool WantsPathClearnace(PamhagenState state, (ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) masks) {
return
(WantsSinglePathClearance(state, masks.Press1) && state.Press1_HasClearance) ||
(WantsSinglePathClearance(state, masks.Press2) && state.Press2_HasClearance) ||
(WantsSinglePathClearance(state, masks.Tanks[0]) && state.T1_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[1]) && state.T2_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[2]) && state.T3_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[3]) && state.T4_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[4]) && state.T5_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[5]) && state.T6_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[6]) && state.T7_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[7]) && state.T8_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[8]) && state.T9_FillLevel != FillState.Full) ||
false;
}
private static bool HasPathClearance(PamhagenState state, (ValveStateMask[] Press1, ValveStateMask[] Press2) masks) {
return
(HasSinglePathClearance(state, masks.Press1) && state.Press1_HasClearance) ||
(HasSinglePathClearance(state, masks.Press2) && state.Press2_HasClearance) ||
false;
}
private static bool WantsPathClearnace(PamhagenState state, (ValveStateMask[] Press1, ValveStateMask[] Press2) masks) {
return
(WantsSinglePathClearance(state, masks.Press1) && state.Press1_HasClearance) ||
(WantsSinglePathClearance(state, masks.Press2) && state.Press2_HasClearance) ||
false;
}
private static readonly (
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) MP1,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) MP2,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) P12,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) P3,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) P4,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) P5,
(ValveStateMask[] Press1, ValveStateMask[] Press2) P6
) Paths;
static PamhagenState() {
ValveStates[] wt_aa = [
new([12], [15, 16, 20]), // bypass
new([15, 18, 16], [11, 12, 14, 17, 19, 20]), // WT1
new([15, 17, 20], [11, 12, 18, 19, 21]), // WT2
new([15, 18, 19, 20], [11, 12, 14, 16, 17, 21]), // WT1+WT2
];
ValveStates[] wt_ab = [
new([15, 18, 14], [11, 12, 13, 16, 17, 19, 21]), // WT1
new([15, 17, 21], [11, 12, 13, 18, 19, 20]), // WT2
new([15, 18, 19, 21], [11, 12, 13, 14, 16, 17, 20]), // WT1+WT2
];
ValveStates[] wt_ba = [
new([11, 18, 16], [15, 12, 13, 14, 17, 19, 20]), // WT1
new([11, 17, 20], [15, 12, 13, 18, 19, 21]), // WT2
new([11, 18, 19, 20], [15, 12, 13, 14, 16, 17, 21]), // WT1+WT2
];
ValveStates[] wt_bb = [
new([13], [11, 14, 21]), // bypass
new([11, 18, 14], [15, 13, 16, 17, 19, 21]), // WT1
new([11, 17, 21], [15, 13, 18, 19, 20]), // WT2
new([11, 18, 19, 21], [15, 13, 14, 16, 17, 20]), // WT1+WT2
];
ValveStates[][] tanks = [
[new([22], [24, 26, 28]), new([23], [25, 27, 29])],
[new([24], [22, 26, 28]), new([25], [23, 27, 29])],
[new([26], [22, 24, 28]), new([27], [23, 25, 29])],
[new([28, 30], [22, 24, 26, 32, 34]), new([29, 31], [23, 25, 27, 33, 35])],
[new([28, 32], [22, 24, 26, 30, 34]), new([29, 33], [23, 25, 27, 31, 35])],
[new([28, 34, 37], [22, 24, 26, 30, 32, 39, 41, 43]), new([29, 35, 36], [23, 25, 27, 31, 33, 38, 40, 42])],
[new([28, 34, 39], [22, 24, 26, 30, 32, 37, 41, 43]), new([29, 35, 38], [23, 25, 27, 31, 33, 36, 40, 42])],
[new([28, 34, 41], [22, 24, 26, 30, 32, 37, 39, 43]), new([29, 35, 40], [23, 25, 27, 31, 33, 36, 38, 42])],
[new([28, 34, 43], [22, 24, 26, 30, 32, 37, 39, 41]), new([29, 35, 42], [23, 25, 27, 31, 33, 36, 38, 40])],
];
ValveStates[] mp1_pr1 = [
new([1, 6, 51], [3, 4, 5, 50, 52, 53, 55]),
new([4, 8, 52], [1, 2, 7, 50, 51, 53, 56]),
];
ValveStates[] mp1_pr2 = [
new([1, 6, 55], [3, 4, 5, 51, 54, 56, 57]),
new([4, 8, 56], [1, 2, 7, 52, 54, 55, 57]),
];
ValveStates mp1_wt_a = new([1, 5], [3, 4, 6]);
ValveStates mp1_wt_b = new([4, 7, 9], [1, 2, 8, 10]);
ValveStates[] mp1_a = [.. wt_aa.Select(m => mp1_wt_a + m), .. wt_ba.Select(m => mp1_wt_b + m)];
ValveStates[] mp1_b = [.. wt_ab.Select(m => mp1_wt_a + m), .. wt_bb.Select(m => mp1_wt_b + m)];
ValveStates[][] mp1_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. mp1_a.Select(m => m + t[0]), .. mp1_b.Select(m => m + t[1])])];
ValveStates[] mp2_pr1 = [
new([2, 8, 52], [3, 4, 7, 50, 51, 53, 56]),
new([3, 6, 51], [1, 2, 5, 50, 52, 53, 55]),
];
ValveStates[] mp2_pr2 = [
new([2, 8, 56], [3, 4, 7, 52, 54, 55, 57]),
new([3, 6, 55], [2, 1, 5, 51, 54, 56, 57]),
];
ValveStates mp2_wt_a = new([3, 5], [1, 2, 6]);
ValveStates mp2_wt_b = new([2, 7, 9], [3, 4, 8, 10]);
ValveStates[] mp2_a = [.. wt_aa.Select(m => mp2_wt_a + m), .. wt_ba.Select(m => mp2_wt_b + m)];
ValveStates[] mp2_b = [.. wt_ab.Select(m => mp2_wt_a + m), .. wt_bb.Select(m => mp2_wt_b + m)];
ValveStates[][] mp2_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. mp2_a.Select(m => m + t[0]), .. mp2_b.Select(m => m + t[1])])];
ValveStates[] p12_pr1 = [new([44, 46, 49, 50], [10, 45, 47, 48, 54, 51, 52, 53])];
ValveStates[] p12_pr2 = [new([44, 46, 49, 54], [10, 45, 47, 48, 50, 55, 56, 57])];
ValveStates[] p12_b = [new([44, 10], [9, 45, 46])];
ValveStates[][] p12_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. p12_b.Select(m => m + t[1])])];
ValveStates[] p3_pr1 = [new([45, 46, 49, 50], [10, 44, 47, 48, 51, 52, 53, 54])];
ValveStates[] p3_pr2 = [new([45, 46, 49, 54], [10, 44, 47, 48, 50, 55, 56, 57])];
ValveStates[] p3_b = [new([45, 10], [9, 44, 46])];
ValveStates[][] p3_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. p3_b.Select(m => m + t[1])])];
ValveStates[] p4_pr1 = [new([47, 49, 50], [46, 48, 51, 52, 53, 54])];
ValveStates[] p4_pr2 = [new([47, 49, 54], [46, 48, 50, 55, 56, 57])];
ValveStates[] p4_b = [new([47, 46, 10], [9, 44, 45, 48, 49])];
ValveStates[][] p4_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. p4_b.Select(m => m + t[1])])];
ValveStates[] p5_pr1 = [new([48, 49, 50], [46, 47, 51, 52, 53, 54])];
ValveStates[] p5_pr2 = [new([48, 49, 54], [46, 47, 50, 55, 56, 57])];
ValveStates[] p5_b = [new([48, 46, 10], [9, 44, 45, 47, 49])];
ValveStates[][] p5_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. p5_b.Select(m => m + t[1])])];
ValveStates[] p6_pr1 = [new([53], [50, 51, 52, 57])];
ValveStates[] p6_pr2 = [new([57], [53, 54, 55, 56])];
Paths = (
(ValveStateMask.From(mp1_pr1), ValveStateMask.From(mp1_pr2), [.. mp1_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(mp2_pr1), ValveStateMask.From(mp2_pr2), [.. mp2_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p12_pr1), ValveStateMask.From(p12_pr2), [.. p12_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p3_pr1), ValveStateMask.From(p3_pr2), [.. p3_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p4_pr1), ValveStateMask.From(p4_pr2), [.. p4_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p5_pr1), ValveStateMask.From(p5_pr2), [.. p5_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p6_pr1), ValveStateMask.From(p6_pr2))
);
}
}
}
@@ -2,7 +2,7 @@ using System.IO;
using System.IO.Ports; using System.IO.Ports;
using System.Text; using System.Text;
namespace PamhagenSysCtrl.Helpers.Plc { namespace PamhagenSysCtrl.Helpers {
public class T1 : IDisposable { public class T1 : IDisposable {
public enum Mode { HALT = 1, RUN = 2, RUN_F = 3, HOLD = 4, ERROR = 6 } public enum Mode { HALT = 1, RUN = 2, RUN_F = 3, HOLD = 4, ERROR = 6 }
@@ -10,16 +10,12 @@ namespace PamhagenSysCtrl.Helpers.Plc {
protected SerialPort Serial; protected SerialPort Serial;
public string PortName => Serial.PortName;
public Status LastStatus { get; private set; }
public T1Error LastError { get; private set; }
public T1(string portName, Parity parity = Parity.Odd) { public T1(string portName, Parity parity = Parity.Odd) {
Serial = new SerialPort(portName, 9600, parity, 8, StopBits.One) { Serial = new SerialPort(portName, 9600, parity, 8, StopBits.One) {
Handshake = Handshake.None, Handshake = Handshake.None,
NewLine = "\r", NewLine = "\r",
ReadTimeout = 500, ReadTimeout = 1000,
WriteTimeout = 500, WriteTimeout = 1000,
DtrEnable = true, DtrEnable = true,
RtsEnable = true, RtsEnable = true,
Encoding = Encoding.ASCII, Encoding = Encoding.ASCII,
@@ -51,7 +47,7 @@ namespace PamhagenSysCtrl.Helpers.Plc {
Serial.WriteLine(line.ToString()); Serial.WriteLine(line.ToString());
} }
protected async Task<(string Command, string Data)> ReceiveResponse(string? expectedCmd = null) { protected async Task<(string, string)> ReceiveResponse(string? expectedCmd = null) {
var line = Serial.ReadLine(); var line = Serial.ReadLine();
if (line.Length < 8 || !line.StartsWith("(A") || !line.EndsWith(')') || line[^4] != '&') { if (line.Length < 8 || !line.StartsWith("(A") || !line.EndsWith(')') || line[^4] != '&') {
throw new FormatException("Invalid response from PLC (T1)"); throw new FormatException("Invalid response from PLC (T1)");
@@ -101,16 +97,14 @@ namespace PamhagenSysCtrl.Helpers.Plc {
public async Task<Status> StatusRead() { public async Task<Status> StatusRead() {
await SendCommand("ST", checksum: false); await SendCommand("ST", checksum: false);
var (_, res) = await ReceiveResponse("ST"); var (_, res) = await ReceiveResponse("ST");
LastStatus = new Status((Mode)(res[3] - '0'), ((res[0] - '0') << 4) | (res[1] - '0')); return new Status((Mode)(res[3] - '0'), ((res[0] - '0') << 4) | (res[1] - '0'));
return LastStatus;
} }
public async Task<T1Error> ErrorStatusRead() { public async Task<T1Error> ErrorStatusRead() {
await SendCommand("ER", checksum: false); await SendCommand("ER", checksum: false);
var (_, res) = await ReceiveResponse("ER"); var (_, res) = await ReceiveResponse("ER");
var code = Convert.ToInt32(res, 10); var code = Convert.ToInt32(res, 10);
LastError = Enum.IsDefined(typeof(T1Error), code) ? (T1Error)code : T1Error.UNKNOWN_ERROR; return Enum.IsDefined(typeof(T1Error), code) ? (T1Error)code : T1Error.UNKNOWN_ERROR;
return LastError;
} }
public Task<ushort[]> DataRead(string register, int length) { public Task<ushort[]> DataRead(string register, int length) {
@@ -1,4 +1,4 @@
namespace PamhagenSysCtrl.Helpers.Plc { namespace PamhagenSysCtrl.Helpers {
public enum T1Error { public enum T1Error {
UNKNOWN_ERROR = -1, UNKNOWN_ERROR = -1,
NO_ERROR = 0, NO_ERROR = 0,
@@ -1,4 +1,4 @@
namespace PamhagenSysCtrl.Helpers.Plc { namespace PamhagenSysCtrl.Helpers {
public class T1Exception(string msg) : Exception(msg) { public class T1Exception(string msg) : Exception(msg) {
} }
} }
-136
View File
@@ -1,136 +0,0 @@
using PIEHidNetCore;
using System.Drawing;
using System.IO;
namespace PamhagenSysCtrl.Helpers {
public class Xbe24Keyboard : IDisposable, PIEDataHandler, PIEErrorHandler {
public enum LedBank { UPPER = 0, LOWER = 1 }
public record struct KeyState(byte[] Rows) {
public readonly bool GetKey(int n) => (Rows[n / 6] & (1 << (n % 6))) != 0;
}
public static readonly Color Off = Color.FromArgb(0, 0, 0);
public static readonly Color DefaltBacklight = Color.FromArgb(3, 3, 3);
public static readonly Color Neutral = Color.FromArgb(4, 4, 4);
public static readonly Color Red = Color.FromArgb(255, 0, 0);
public static readonly Color Green = Color.FromArgb(0, 255, 0);
public static readonly Color Blue = Color.FromArgb(0, 0, 255);
public static readonly Color Yellow = Color.FromArgb(255, 255, 0);
public static readonly Color Magenta = Color.FromArgb(255, 0, 255);
public static readonly Color Cyan = Color.FromArgb(0, 255, 255);
public static readonly Color Orange = Color.FromArgb(255, 32, 0);
public static readonly Color DarkPurple = Color.FromArgb(128, 0, 128);
public static readonly Color Purple = Color.FromArgb(255, 64, 255);
public static readonly Color Lime = Color.FromArgb(64, 255, 4);
public static readonly Color LightRed = Color.FromArgb(32, 4, 4);
public static readonly Color LightGreen = Color.FromArgb(4, 32, 4);
public static readonly Color LightBlue = Color.FromArgb(4, 4, 32);
protected readonly PIEDevice XbeDevice;
private KeyState LastKeyState = new(new byte[4]);
private readonly (Color Color, bool Flashing)[] LastStates = new (Color, bool)[48];
public bool IsKeyPressed(int keyIndex) => LastKeyState.GetKey(keyIndex);
public event EventHandler<int>? KeyDown;
public event EventHandler<int>? KeyUp;
public event EventHandler<KeyState>? KeysChanged;
public event EventHandler<int>? Error;
public Xbe24Keyboard(int pid = 0x0555) {
foreach (var device in PIEDevice.EnumeratePIE()) {
if (device.Pid == pid && device.HidUsagePage == 0x0C && device.HidUsage == 1 && device.WriteLength > 1) {
XbeDevice = device;
break;
}
}
if (XbeDevice == null)
throw new ArgumentException($"No XBE keyboard with id 05f3:{pid:x4}");
int res;
if ((res = XbeDevice.SetupInterface()) != 0)
throw new IOException($"Unable to SetupInterface for XBE keyboard: {res}");
if ((res = XbeDevice.SetDataCallback(this)) != 0)
throw new IOException($"Unable to SetDataCallback for XBE keyboard: {res}");
if ((res = XbeDevice.SetErrorCallback(this)) != 0)
throw new IOException($"Unable to SetErrorCallback for XBE keyboard: {res}");
SetBacklight(DefaltBacklight);
}
public void Dispose() {
GC.SuppressFinalize(this);
SetBacklight(DefaltBacklight);
XbeDevice?.CloseInterface();
}
public void HandlePIEHidData(byte[] data, PIEDevice sourceDevice, int error) {
if (sourceDevice != XbeDevice)
return;
var current = new KeyState(data[3..8]);
var last = LastKeyState;
LastKeyState = current;
HandleKeyStateChange(current, last);
}
public void HandlePIEHidError(PIEDevice sourceDevice, int error) {
if (sourceDevice != XbeDevice)
return;
Error?.Invoke(this, error);
}
protected void HandleKeyStateChange(KeyState current, KeyState last) {
for (int i = 0; i < 24; i++) {
if (!last.GetKey(i) && current.GetKey(i)) {
KeyDown?.Invoke(this, i);
} else if (last.GetKey(i) && !current.GetKey(i)) {
KeyUp?.Invoke(this, i);
}
}
KeysChanged?.Invoke(this, current);
}
protected void SendCommand(byte command, byte[] payload) {
byte[] data = new byte[XbeDevice.WriteLength];
data[1] = command;
Array.Copy(payload, 0, data, 2, Math.Min(payload.Length, data.Length - 2));
int res;
do {
res = XbeDevice.WriteData(data);
} while (res == 404);
if (res != 0)
throw new IOException($"Unable to WriteData for XBE keyboard: {res}");
}
public void SetBacklight(int keyIndex, Color color, bool flashing = false) {
SetBacklight(keyIndex, LedBank.UPPER, color, flashing);
SetBacklight(keyIndex, LedBank.LOWER, color, flashing);
}
public void SetBacklight(int keyIndex, LedBank bank, Color color, bool flashing = false) {
if (LastStates[keyIndex * 2 + (int)bank] == (color, flashing))
return;
SendCommand(165, [(byte)keyIndex, (byte)bank, color.R, color.G, color.B, (byte)(flashing ? 1 : 0)]);
LastStates[keyIndex * 2 + (int)bank] = (color, flashing);
}
public void SetBacklight(Color color) {
SetBacklight(LedBank.UPPER, color);
SetBacklight(LedBank.LOWER, color);
}
public void SetBacklight(LedBank bank, Color color) {
SendCommand(166, [(byte)bank, color.R, color.G, color.B]);
for (int i = 0; i < LastStates.Length; i++)
LastStates[i] = (color, false);
}
public void SetFashFrequency(int ms) {
SendCommand(180, [(byte)(ms / 16)]);
}
}
}
Binary file not shown.
-3
View File
@@ -18,9 +18,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.11" /> <PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.11" />
<PackageReference Include="System.IO.Ports" Version="10.0.11" /> <PackageReference Include="System.IO.Ports" Version="10.0.11" />
<Reference Include="PIEHidNetCore">
<HintPath>PIEHidNetCore.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -12,7 +12,7 @@
</Grid> </Grid>
<TextBlock x:Name="Status" FontSize="14" <TextBlock x:Name="Status" FontSize="14"
Margin="350,10,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"/> Margin="350,50,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<Grid x:Name="ActuatorPanel" Margin="350,350,5,5"> <Grid x:Name="ActuatorPanel" Margin="350,350,5,5">
@@ -29,14 +29,14 @@ namespace PamhagenSysCtrl {
if (App.Plant == null || sender is not Button b) return; if (App.Plant == null || sender is not Button b) return;
var i = int.Parse($"{b.Content}"[1..]); var i = int.Parse($"{b.Content}"[1..]);
if (i == 58) { if (i == 58) {
if (App.Plant.State.MW1_WantSelected) { if (App.Plant.WantMW1Selected) {
App.Plant.SelectMW2(); App.Plant.SelectMW2();
} else { } else {
App.Plant.SelectMW1(); App.Plant.SelectMW1();
} }
} else if (i > 57) { } else if (i > 57) {
return; return;
} else if (App.Plant.State.Valve_WantOpen(i)) { } else if (App.Plant.WantValveOpen(i)) {
App.Plant.CloseV(i); App.Plant.CloseV(i);
} else { } else {
App.Plant.OpenV(i); App.Plant.OpenV(i);
@@ -60,8 +60,8 @@ namespace PamhagenSysCtrl {
if (sender is not PamhagenPlant plant) return; if (sender is not PamhagenPlant plant) return;
for (int i = 1; i <= 58; i++) { for (int i = 1; i <= 58; i++) {
var target = plant.State.Valve_WantClosed(i); var target = plant.WantValveClosed(i);
var actual = plant.State.Valve_IsClosed(i); var actual = plant.IsValveClosed(i);
if (ValvePanel.Children[i - 1] is not Button b) if (ValvePanel.Children[i - 1] is not Button b)
continue; continue;
b.BorderBrush = target ? Brushes.DarkRed : Brushes.DarkGreen; b.BorderBrush = target ? Brushes.DarkRed : Brushes.DarkGreen;
@@ -69,98 +69,98 @@ namespace PamhagenSysCtrl {
b.Background = actual ? Brushes.MistyRose : Brushes.MintCream; b.Background = actual ? Brushes.MistyRose : Brushes.MintCream;
} }
ReblerButton.BorderBrush = plant.State.Rebler_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; ReblerButton.BorderBrush = plant.IsReblerActive ? Brushes.DarkGreen : Brushes.DarkRed;
ReblerButton.Foreground = plant.State.Rebler_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; ReblerButton.Foreground = plant.IsReblerActive ? Brushes.DarkGreen : Brushes.DarkRed;
ReblerButton.Background = plant.State.Rebler_IsActive ? Brushes.MintCream: Brushes.MistyRose; ReblerButton.Background = plant.IsReblerActive ? Brushes.MintCream: Brushes.MistyRose;
TroughAugerButton.BorderBrush = plant.State.TroughAuger_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; TroughAugerButton.BorderBrush = plant.IsTroughAugerActive ? Brushes.DarkGreen : Brushes.DarkRed;
TroughAugerButton.Foreground = plant.State.TroughAuger_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; TroughAugerButton.Foreground = plant.IsTroughAugerActive ? Brushes.DarkGreen : Brushes.DarkRed;
TroughAugerButton.Background = plant.State.TroughAuger_IsActive ? Brushes.MintCream : Brushes.MistyRose; TroughAugerButton.Background = plant.IsTroughAugerActive ? Brushes.MintCream : Brushes.MistyRose;
StirrerMW1Button.BorderBrush = plant.State.MW1_Stirrer_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; StirrerMW1Button.BorderBrush = plant.IsStirrerMW1Active ? Brushes.DarkGreen : Brushes.DarkRed;
StirrerMW1Button.Foreground = plant.State.MW1_Stirrer_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; StirrerMW1Button.Foreground = plant.IsStirrerMW1Active ? Brushes.DarkGreen : Brushes.DarkRed;
StirrerMW1Button.Background = plant.State.MW1_Stirrer_IsActive ? Brushes.MintCream : Brushes.MistyRose; StirrerMW1Button.Background = plant.IsStirrerMW1Active ? Brushes.MintCream : Brushes.MistyRose;
StirrerMW2Button.BorderBrush = plant.State.MW2_Stirrer_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; StirrerMW2Button.BorderBrush = plant.IsStirrerMW2Active ? Brushes.DarkGreen : Brushes.DarkRed;
StirrerMW2Button.Foreground = plant.State.MW2_Stirrer_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; StirrerMW2Button.Foreground = plant.IsStirrerMW2Active ? Brushes.DarkGreen : Brushes.DarkRed;
StirrerMW2Button.Background = plant.State.MW2_Stirrer_IsActive ? Brushes.MintCream : Brushes.MistyRose; StirrerMW2Button.Background = plant.IsStirrerMW2Active ? Brushes.MintCream : Brushes.MistyRose;
Auger1Button.BorderBrush = plant.State.Auger1_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; Auger1Button.BorderBrush = plant.IsAuger1Active ? Brushes.DarkGreen : Brushes.DarkRed;
Auger1Button.Foreground = plant.State.Auger1_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; Auger1Button.Foreground = plant.IsAuger1Active ? Brushes.DarkGreen : Brushes.DarkRed;
Auger1Button.Background = plant.State.Auger1_IsActive ? Brushes.MintCream : Brushes.MistyRose; Auger1Button.Background = plant.IsAuger1Active ? Brushes.MintCream : Brushes.MistyRose;
Auger2Button.BorderBrush = plant.State.Auger2_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; Auger2Button.BorderBrush = plant.IsAuger2Active ? Brushes.DarkGreen : Brushes.DarkRed;
Auger2Button.Foreground = plant.State.Auger2_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; Auger2Button.Foreground = plant.IsAuger2Active ? Brushes.DarkGreen : Brushes.DarkRed;
Auger2Button.Background = plant.State.Auger2_IsActive ? Brushes.MintCream : Brushes.MistyRose; Auger2Button.Background = plant.IsAuger2Active ? Brushes.MintCream : Brushes.MistyRose;
Auger3Button.BorderBrush = plant.State.Auger3_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; Auger3Button.BorderBrush = plant.IsAuger3Active ? Brushes.DarkGreen : Brushes.DarkRed;
Auger3Button.Foreground = plant.State.Auger3_IsActive ? Brushes.DarkGreen : Brushes.DarkRed; Auger3Button.Foreground = plant.IsAuger3Active ? Brushes.DarkGreen : Brushes.DarkRed;
Auger3Button.Background = plant.State.Auger3_IsActive ? Brushes.MintCream : Brushes.MistyRose; Auger3Button.Background = plant.IsAuger3Active ? Brushes.MintCream : Brushes.MistyRose;
MP1BackwardButton.BorderBrush = plant.State.MP1_State == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray; MP1BackwardButton.BorderBrush = plant.MP1 == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray;
MP1BackwardButton.Foreground = plant.State.MP1_State == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray; MP1BackwardButton.Foreground = plant.MP1 == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray;
MP1BackwardButton.Background = plant.State.MP1_State == MotorState.Backward ? Brushes.MintCream : Brushes.WhiteSmoke; MP1BackwardButton.Background = plant.MP1 == MotorState.Backward ? Brushes.MintCream : Brushes.WhiteSmoke;
MP1HaltButton.BorderBrush = plant.State.MP1_State == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray; MP1HaltButton.BorderBrush = plant.MP1 == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray;
MP1HaltButton.Foreground = plant.State.MP1_State == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray; MP1HaltButton.Foreground = plant.MP1 == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray;
MP1HaltButton.Background = plant.State.MP1_State == MotorState.Halt ? Brushes.MistyRose : Brushes.WhiteSmoke; MP1HaltButton.Background = plant.MP1 == MotorState.Halt ? Brushes.MistyRose : Brushes.WhiteSmoke;
MP1ForwardButton.BorderBrush = plant.State.MP1_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray; MP1ForwardButton.BorderBrush = plant.MP1 == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray;
MP1ForwardButton.Foreground = plant.State.MP1_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray; MP1ForwardButton.Foreground = plant.MP1 == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray;
MP1ForwardButton.Background = plant.State.MP1_State == MotorState.Forward ? Brushes.MintCream: Brushes.WhiteSmoke; MP1ForwardButton.Background = plant.MP1 == MotorState.Forward ? Brushes.MintCream: Brushes.WhiteSmoke;
MP2BackwardButton.BorderBrush = plant.State.MP2_State == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray; MP2BackwardButton.BorderBrush = plant.MP2 == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray;
MP2BackwardButton.Foreground = plant.State.MP2_State == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray; MP2BackwardButton.Foreground = plant.MP2 == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray;
MP2BackwardButton.Background = plant.State.MP2_State == MotorState.Backward ? Brushes.MintCream : Brushes.WhiteSmoke; MP2BackwardButton.Background = plant.MP2 == MotorState.Backward ? Brushes.MintCream : Brushes.WhiteSmoke;
MP2HaltButton.BorderBrush = plant.State.MP2_State == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray; MP2HaltButton.BorderBrush = plant.MP2 == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray;
MP2HaltButton.Foreground = plant.State.MP2_State == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray; MP2HaltButton.Foreground = plant.MP2 == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray;
MP2HaltButton.Background = plant.State.MP2_State == MotorState.Halt ? Brushes.MistyRose : Brushes.WhiteSmoke; MP2HaltButton.Background = plant.MP2 == MotorState.Halt ? Brushes.MistyRose : Brushes.WhiteSmoke;
MP2ForwardButton.BorderBrush = plant.State.MP2_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray; MP2ForwardButton.BorderBrush = plant.MP2 == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray;
MP2ForwardButton.Foreground = plant.State.MP2_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray; MP2ForwardButton.Foreground = plant.MP2 == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray;
MP2ForwardButton.Background = plant.State.MP2_State == MotorState.Forward ? Brushes.MintCream : Brushes.WhiteSmoke; MP2ForwardButton.Background = plant.MP2 == MotorState.Forward ? Brushes.MintCream : Brushes.WhiteSmoke;
P12Button.BorderBrush = plant.State.P12_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P12Button.BorderBrush = plant.P12 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P12Button.Foreground = plant.State.P12_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P12Button.Foreground = plant.P12 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P12Button.Background = plant.State.P12_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P12Button.Background = plant.P12 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
P3Button.BorderBrush = plant.State.P3_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P3Button.BorderBrush = plant.P3 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P3Button.Foreground = plant.State.P3_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P3Button.Foreground = plant.P3 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P3Button.Background = plant.State.P3_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P3Button.Background = plant.P3 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
P4Button.BorderBrush = plant.State.P4_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P4Button.BorderBrush = plant.P4 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P4Button.Foreground = plant.State.P4_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P4Button.Foreground = plant.P4 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P4Button.Background = plant.State.P4_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P4Button.Background = plant.P4 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
P5Button.BorderBrush = plant.State.P5_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P5Button.BorderBrush = plant.P5 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P5Button.Foreground = plant.State.P5_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P5Button.Foreground = plant.P5 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P5Button.Background = plant.State.P5_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P5Button.Background = plant.P5 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
P6Button.BorderBrush = plant.State.P6_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P6Button.BorderBrush = plant.P6 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P6Button.Foreground = plant.State.P6_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P6Button.Foreground = plant.P6 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P6Button.Background = plant.State.P6_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P6Button.Background = plant.P6 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
Status.Text = $""" Status.Text = $"""
Presse Querschnecke: {plant.State.PressCrossAuger_State} Gradation °Oe: {plant.GradationOe:N2}
Freigaben: Presse Querschnecke: {plant.PresseQuerSchnecke}
Presse 1: {plant.State.Press1_HasClearance}
Presse 2: {plant.State.Press2_HasClearance}
Füllstände: Füllstände:
Maischewanne 1: {plant.State.MW1_FillLevel} (ausgewählt-soll: {plant.State.MW1_WantSelected}, ausgewählt-ist: {plant.State.MW1_IsSelected}) Maischewanne 1: {plant.FillLevelMW1} (ausgewählt: {plant.WantMW1Selected}, Status: {plant.IsMW1Selected})
Maischewanne 2: {plant.State.MW2_FillLevel} (ausgewählt-soll: {plant.State.MW2_WantSelected}, ausgewählt-ist: {plant.State.MW2_IsSelected}) Maischewanne 2: {plant.FillLevelMW2} (ausgewählt: {plant.WantMW2Selected}, Status: {plant.IsMW2Selected})
Tank 1: {plant.State.T1_FillLevel} Presse 1: {plant.Press1HasClearance}
Tank 2: {plant.State.T2_FillLevel} Presse 2: {plant.Press2HasClearance}
Tank 3: {plant.State.T3_FillLevel} Tank 1: {plant.FillLevelT1}
Tank 4: {plant.State.T4_FillLevel} Tank 2: {plant.FillLevelT2}
Tank 5: {plant.State.T5_FillLevel} Tank 3: {plant.FillLevelT3}
Tank 6: {plant.State.T6_FillLevel} Tank 4: {plant.FillLevelT4}
Tank 7: {plant.State.T7_FillLevel} Tank 5: {plant.FillLevelT5}
Tank 8: {plant.State.T8_FillLevel} Tank 6: {plant.FillLevelT6}
Tank 9: {plant.State.T9_FillLevel} Tank 7: {plant.FillLevelT7}
Tank 8: {plant.FillLevelT8}
Tank 9: {plant.FillLevelT9}
"""; """;
} }
private void TroughAugerButton_Click(object? sender, RoutedEventArgs? evt) { private void TroughAugerButton_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.State.TroughAuger_IsActive) { if (App.Plant.IsTroughAugerActive) {
App.Plant.StopTroughAuger(); App.Plant.StopTroughAuger();
} else { } else {
App.Plant.StartTroughAuger(); App.Plant.StartTroughAuger();
@@ -173,7 +173,7 @@ namespace PamhagenSysCtrl {
private void ReblerButton_Click(object? sender, RoutedEventArgs? evt) { private void ReblerButton_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.State.Rebler_IsActive) { if (App.Plant.IsReblerActive) {
App.Plant.StopRebler(); App.Plant.StopRebler();
} else { } else {
App.Plant.StartRebler(); App.Plant.StartRebler();
@@ -186,7 +186,7 @@ namespace PamhagenSysCtrl {
private void StirrerMW1Button_Click(object? sender, RoutedEventArgs? evt) { private void StirrerMW1Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.State.MW1_Stirrer_IsActive) { if (App.Plant.IsStirrerMW1Active) {
App.Plant.StopStirrerMW1(); App.Plant.StopStirrerMW1();
} else { } else {
App.Plant.StartStirrerMW1(); App.Plant.StartStirrerMW1();
@@ -199,7 +199,7 @@ namespace PamhagenSysCtrl {
private void StirrerMW2Button_Click(object? sender, RoutedEventArgs? evt) { private void StirrerMW2Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.State.MW2_Stirrer_IsActive) { if (App.Plant.IsStirrerMW2Active) {
App.Plant.StopStirrerMW2(); App.Plant.StopStirrerMW2();
} else { } else {
App.Plant.StartStirrerMW2(); App.Plant.StartStirrerMW2();
@@ -212,7 +212,7 @@ namespace PamhagenSysCtrl {
private void Auger1Button_Click(object? sender, RoutedEventArgs? evt) { private void Auger1Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.State.Auger1_IsActive) { if (App.Plant.IsAuger1Active) {
App.Plant.StopAuger1(); App.Plant.StopAuger1();
} else { } else {
App.Plant.StartAuger1(); App.Plant.StartAuger1();
@@ -225,7 +225,7 @@ namespace PamhagenSysCtrl {
private void Auger2Button_Click(object? sender, RoutedEventArgs? evt) { private void Auger2Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.State.Auger2_IsActive) { if (App.Plant.IsAuger2Active) {
App.Plant.StopAuger2(); App.Plant.StopAuger2();
} else { } else {
App.Plant.StartAuger2(); App.Plant.StartAuger2();
@@ -238,7 +238,7 @@ namespace PamhagenSysCtrl {
private void Auger3Button_Click(object? sender, RoutedEventArgs? evt) { private void Auger3Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.State.Auger3_IsActive) { if (App.Plant.IsAuger3Active) {
App.Plant.StopAuger3(); App.Plant.StopAuger3();
} else { } else {
App.Plant.StartAuger3(); App.Plant.StartAuger3();
@@ -306,10 +306,10 @@ namespace PamhagenSysCtrl {
private void P12Button_Click(object? sender, RoutedEventArgs? evt) { private void P12Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.State.P12_IsActive) { if (App.Plant.P12 == MotorState.Forward) {
App.Plant.DisableP12(); App.Plant.StopP12();
} else { } else {
App.Plant.EnableP12(); App.Plant.StartP12();
} }
} catch (NoClearanceException exc) { } catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
@@ -319,10 +319,10 @@ namespace PamhagenSysCtrl {
private void P3Button_Click(object? sender, RoutedEventArgs? evt) { private void P3Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.State.P3_IsActive) { if (App.Plant.P3 == MotorState.Forward) {
App.Plant.DisableP3(); App.Plant.StopP3();
} else { } else {
App.Plant.EnableP3(); App.Plant.StartP3();
} }
} catch (NoClearanceException exc) { } catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
@@ -332,10 +332,10 @@ namespace PamhagenSysCtrl {
private void P4Button_Click(object? sender, RoutedEventArgs? evt) { private void P4Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.State.P4_IsActive) { if (App.Plant.P4 == MotorState.Forward) {
App.Plant.DisableP4(); App.Plant.StopP4();
} else { } else {
App.Plant.EnableP4(); App.Plant.StartP4();
} }
} catch (NoClearanceException exc) { } catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
@@ -345,10 +345,10 @@ namespace PamhagenSysCtrl {
private void P5Button_Click(object? sender, RoutedEventArgs? evt) { private void P5Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.State.P5_IsActive) { if (App.Plant.P5 == MotorState.Forward) {
App.Plant.DisableP5(); App.Plant.StopP5();
} else { } else {
App.Plant.EnableP5(); App.Plant.StartP5();
} }
} catch (NoClearanceException exc) { } catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
@@ -358,10 +358,10 @@ namespace PamhagenSysCtrl {
private void P6Button_Click(object? sender, RoutedEventArgs? evt) { private void P6Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.State.P6_IsActive) { if (App.Plant.P6 == MotorState.Forward) {
App.Plant.DisableP6(); App.Plant.StopP6();
} else { } else {
App.Plant.EnableP6(); App.Plant.StartP6();
} }
} catch (NoClearanceException exc) { } catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
+1 -38
View File
@@ -227,44 +227,7 @@
</Grid> </Grid>
<StatusBar Grid.Row="2" BorderThickness="0,1,0,0" BorderBrush="Gray"> <StatusBar Grid.Row="2" BorderThickness="0,1,0,0" BorderBrush="Gray">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem>
<TextBlock>
SPS-Verbindung: <Run x:Name="Status_Connection" Text="-"/>
</TextBlock>
</StatusBarItem>
<Separator Grid.Column="1"/>
<StatusBarItem Grid.Column="2">
<TextBlock>
SPS-Status: <Run x:Name="Status_Plc" Text="-"/>
</TextBlock>
</StatusBarItem>
<Separator Grid.Column="3"/>
<StatusBarItem Grid.Column="4">
<TextBlock>
Druckluft: <Run x:Name="Status_CompressedAir" Text="-"/>
</TextBlock>
</StatusBarItem>
<Separator Grid.Column="5"/>
<StatusBarItem Grid.Column="6">
<TextBlock>
Ventile offen: <Run x:Name="Status_ValvesOpen" Text="-"/>
</TextBlock>
</StatusBarItem>
</StatusBar> </StatusBar>
</Grid> </Grid>
</Window> </Window>
@@ -1,6 +1,5 @@
using PamhagenSysCtrl.Helpers; using PamhagenSysCtrl.Helpers;
using PamhagenSysCtrl.Helpers.Pipeline; using PamhagenSysCtrl.Helpers.Pipeline;
using PamhagenSysCtrl.Helpers.Plc;
using System.Diagnostics; using System.Diagnostics;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
@@ -12,7 +11,7 @@ namespace PamhagenSysCtrl.Windows {
private const double _minMove = 50; private const double _minMove = 50;
private static PamhagenGraph Graph => App.Plant!.Graph; private readonly PamhagenGraph Graph;
private Point? _lastMousePos; private Point? _lastMousePos;
private bool _mouseDragging; private bool _mouseDragging;
@@ -27,7 +26,7 @@ namespace PamhagenSysCtrl.Windows {
public Path? HoveringPath { public Path? HoveringPath {
get { get {
foreach (var b in FastSelectPaths.Where(b => b.Key.IsMouseOver)) { foreach (var b in FastSelectPaths.Where(b => b.Key.IsMouseOver)) {
if (Graph.SelectedPaths.Any(p => p.Matches(b.Value.Source, b.Value.Sink))) if (Graph.SelectedPaths.Any(p => p.Start == b.Value.Source && p.Hops.Last().Node == b.Value.Sink))
continue; continue;
return Graph.GetFreePath([b.Value.Source, b.Value.Sink], true); return Graph.GetFreePath([b.Value.Source, b.Value.Sink], true);
} }
@@ -51,6 +50,7 @@ namespace PamhagenSysCtrl.Windows {
public PlantSchemeWindow() { public PlantSchemeWindow() {
InitializeComponent(); InitializeComponent();
Menu_Help_CheckForUpdates.IsEnabled = App.Config.UpdateUrl != null; Menu_Help_CheckForUpdates.IsEnabled = App.Config.UpdateUrl != null;
Graph = new();
Graph.Draw(SchemeCanvas, RoundedScale); Graph.Draw(SchemeCanvas, RoundedScale);
FastSelectPaths = new Dictionary<Button, (ISource, Pump, ISink)> { FastSelectPaths = new Dictionary<Button, (ISource, Pump, ISink)> {
[FastSelectButton_MW1_Press1] = (Graph.MW1, Graph.MP1, Graph.Press1), [FastSelectButton_MW1_Press1] = (Graph.MW1, Graph.MP1, Graph.Press1),
@@ -68,10 +68,8 @@ namespace PamhagenSysCtrl.Windows {
[FastSelectButton_MW1_Tank5] = (Graph.MW1, Graph.MP1, Graph.Tank5), [FastSelectButton_MW1_Tank5] = (Graph.MW1, Graph.MP1, Graph.Tank5),
[FastSelectButton_MW2_Tank5] = (Graph.MW2, Graph.MP2, Graph.Tank5), [FastSelectButton_MW2_Tank5] = (Graph.MW2, Graph.MP2, Graph.Tank5),
}; };
if (App.Plant != null) { App.Plant?.Update += OnUpdate;
Status_Connection.Text = $"Nein ({App.Plant.PlcComPort})"; InitializePaths();
App.Plant.Update += OnUpdate;
}
} }
private void OnClosed(object sender, EventArgs evt) { private void OnClosed(object sender, EventArgs evt) {
@@ -98,7 +96,7 @@ namespace PamhagenSysCtrl.Windows {
Menu_Settings_OverridePathClearances.IsChecked = false; Menu_Settings_OverridePathClearances.IsChecked = false;
Menu_Settings_OverrideDryRunClearances.IsChecked = false; Menu_Settings_OverrideDryRunClearances.IsChecked = false;
App.Plant?.InitializePaths(); InitializePaths();
} }
OnUpdate(null, null); OnUpdate(null, null);
} }
@@ -213,13 +211,20 @@ namespace PamhagenSysCtrl.Windows {
UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed); UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed);
} }
private void FastSelectButton_Click(object sender, RoutedEventArgs? evt) { private void FastSelectButton_Click(object sender, RoutedEventArgs evt) {
if (sender is not Button b || !FastSelectPaths.TryGetValue(b, out var val)) if (sender is not Button b || !FastSelectPaths.TryGetValue(b, out var val))
return; return;
App.Plant?.FastSelect(val.Source, null, val.Sink);
OnUpdate(null, null); 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; _lastSource = null;
} }
}
private void OnSizeChanged(object sender, SizeChangedEventArgs evt) { private void OnSizeChanged(object sender, SizeChangedEventArgs evt) {
var rx = evt.NewSize.Width / 1550.0; var rx = evt.NewSize.Width / 1550.0;
@@ -354,7 +359,7 @@ namespace PamhagenSysCtrl.Windows {
} else { } else {
if (_lastSource == null && Graph.SelectedPaths.Any(p => p.Start == c)) { if (_lastSource == null && Graph.SelectedPaths.Any(p => p.Start == c)) {
var path = Graph.SelectedPaths.First(p => p.Start == c); var path = Graph.SelectedPaths.First(p => p.Start == c);
if (!path.HasActivePump) { if (!path.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false)) {
Graph.RemovePath(path); Graph.RemovePath(path);
} }
} else if (_lastSource != null && c is ISink sink) { } else if (_lastSource != null && c is ISink sink) {
@@ -388,7 +393,7 @@ namespace PamhagenSysCtrl.Windows {
App.Plant?.StartRebler(); App.Plant?.StartRebler();
} }
} else if (c is Switcher switcher) { } else if (c is Switcher switcher) {
if (App.Plant?.State.MW1_WantSelected ?? false) { if (App.Plant?.WantMW1Selected ?? false) {
App.Plant?.SelectMW2(); App.Plant?.SelectMW2();
} else { } else {
App.Plant?.SelectMW1(); App.Plant?.SelectMW1();
@@ -428,33 +433,33 @@ namespace PamhagenSysCtrl.Windows {
} }
} else if (pump == Graph.P12) { } else if (pump == Graph.P12) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.DisableP12(); App.Plant?.StopP12();
} else { } else {
App.Plant?.EnableP12(); App.Plant?.StartP12();
} }
} else if (pump == Graph.P3) { } else if (pump == Graph.P3) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.DisableP3(); App.Plant?.StopP3();
} else { } else {
App.Plant?.EnableP3(); App.Plant?.StartP3();
} }
} else if (pump == Graph.P4) { } else if (pump == Graph.P4) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.DisableP4(); App.Plant?.StopP4();
} else { } else {
App.Plant?.EnableP4(); App.Plant?.StartP4();
} }
} else if (pump == Graph.P5) { } else if (pump == Graph.P5) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.DisableP5(); App.Plant?.StopP5();
} else { } else {
App.Plant?.EnableP5(); App.Plant?.StartP5();
} }
} else if (pump == Graph.P6) { } else if (pump == Graph.P6) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.DisableP6(); App.Plant?.StopP6();
} else { } else {
App.Plant?.EnableP6(); App.Plant?.StartP6();
} }
} }
} }
@@ -485,7 +490,7 @@ namespace PamhagenSysCtrl.Windows {
private void UpdateFastSelectPathButton(IEnumerable<Path> paths, Path? hoverPathDeactivate, Button b) { private void UpdateFastSelectPathButton(IEnumerable<Path> paths, Path? hoverPathDeactivate, Button b) {
var e = FastSelectPaths[b]; var e = FastSelectPaths[b];
SetFastSelectButton(b, SetFastSelectButton(b,
paths.Any(p => p.Matches(e.Source, e.Sink)) ? (hoverPathDeactivate?.Matches(e.Source, e.Sink) ?? false) ? 2 : 1 : 0, 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); e.Pump.IsActive || Graph.GetFreePath([e.Source, e.Sink], overrideStart: true) == null);
} }
@@ -500,21 +505,24 @@ namespace PamhagenSysCtrl.Windows {
} }
} }
SetFastSelectButton(FastSelectButton_EntryTrough_Start, App.Plant?.State.TroughAuger_IsActive ?? false ? 1 : 0, !(App.Plant?.State.TroughAuger_HasClearance ?? false)); SetFastSelectButton(FastSelectButton_EntryTrough_Start, App.Plant?.IsTroughAugerActive ?? false ? 1 : 0, !(App.Plant?.TroughAugerHasClearance ?? false));
SetFastSelectButton(FastSelectButton_EntryTrough_Stop, App.Plant?.State.TroughAuger_IsActive ?? false ? 0 : 2); SetFastSelectButton(FastSelectButton_EntryTrough_Stop, App.Plant?.IsTroughAugerActive ?? false ? 0 : 2);
SetFastSelectButton(FastSelectButton_MW1, App.Plant?.State.MW1_IsSelected?? false ? 1 : 0); SetFastSelectButton(FastSelectButton_MW1, App.Plant?.IsMW1Selected ?? false ? 1 : 0);
SetFastSelectButton(FastSelectButton_MW2, App.Plant?.State.MW2_IsSelected ?? false ? 1 : 0); SetFastSelectButton(FastSelectButton_MW2, App.Plant?.IsMW2Selected ?? false ? 1 : 0);
SetFastSelectButton(FastSelectButton_MP1_Forward, App.Plant?.State.MP1_State == MotorState.Forward ? 1 : 0, !(App.Plant?.State.MP1_HasClearance ?? false)); SetFastSelectButton(FastSelectButton_MP1_Forward, App.Plant?.MP1 == MotorState.Forward ? 1 : 0, !(App.Plant?.MP1HasClearance ?? false));
SetFastSelectButton(FastSelectButton_MP1_Stop, App.Plant?.State.MP1_State == MotorState.Halt ? 2 : 0); SetFastSelectButton(FastSelectButton_MP1_Stop, App.Plant?.MP1 == MotorState.Halt ? 2 : 0);
SetFastSelectButton(FastSelectButton_MP2_Forward, App.Plant?.State.MP2_State == MotorState.Forward ? 1 : 0, !(App.Plant?.State.MP2_HasClearance ?? false)); SetFastSelectButton(FastSelectButton_MP2_Forward, App.Plant?.MP2 == MotorState.Forward ? 1 : 0, !(App.Plant?.MP2HasClearance ?? false));
SetFastSelectButton(FastSelectButton_MP2_Stop, App.Plant?.State.MP2_State == MotorState.Halt ? 2 : 0); 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, RoundedScale); _hover = Graph.GetHover(pos.X, pos.Y, RoundedScale);
var hoverButtons = FastSelectPaths.Where(b => b.Key.IsMouseOver).ToDictionary(); var hoverButtons = FastSelectPaths.Where(b => b.Key.IsMouseOver).ToDictionary();
Path? hoverPathDeactivate = null; Path? hoverPathDeactivate = null;
if (App.Plant?.ManualControlMode ?? false) { if (App.Plant?.ManualControlMode ?? false) {
foreach (var src in Graph.Nodes.Where(n => n is Pump)) { foreach (var src in Graph.Nodes.Where(n => n is Pump)) {
PamhagenGraph.ColorPipesByValves(src, PamhagenBrushes.Green, (src as Pump)?.State ?? default, PamhagenBrushes.DimOrange); Graph.ColorPipesByValves(src, PamhagenBrushes.Green, (src as Pump)?.State ?? default, PamhagenBrushes.DimOrange);
} }
MP1_Target.Text = "Vor"; MP1_Target.Text = "Vor";
MP2_Target.Text = "Vor"; MP2_Target.Text = "Vor";
@@ -522,21 +530,27 @@ namespace PamhagenSysCtrl.Windows {
MP2_Target.FontSize = 16; MP2_Target.FontSize = 16;
} else { } else {
foreach (var p in Graph.SelectedPaths) { foreach (var p in Graph.SelectedPaths) {
PamhagenGraph.ColorPipesAdjacientToPath(p, PamhagenBrushes.Green, p.Pumps.Select(p => p.State).FirstOrDefault(n => n != MotorState.Halt), PamhagenBrushes.DimOrange); Graph.ColorPipesAdjacientToPath(p, PamhagenBrushes.Green, p.Hops.Select(h => (h.Node as Pump)?.State ?? default).FirstOrDefault(n => n != MotorState.Halt), PamhagenBrushes.DimOrange);
} }
if (_lastSource == null && _hover.Count > 0 && Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == _hover.First()) is Path path1) { if (_lastSource == null && _hover.Count > 0 && Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == _hover.First()) is Path path1) {
if (path1.HasActivePump) { var pumpActive = path1.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false);
foreach (var p in path1.Pumps) p.Highlight = PamhagenBrushes.Red; if (pumpActive) {
foreach (var pump in path1.Hops.Select(h => h.Node as Pump).Where(p => p != null)) {
pump?.Highlight = PamhagenBrushes.Red;
}
} else { } else {
hoverPathDeactivate = path1; hoverPathDeactivate = path1;
PamhagenGraph.ColorPipesAdjacientToPath(path1, PamhagenBrushes.Red, default, null); 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) { } else if (hoverButtons.Count != 0 && Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == hoverButtons.First().Value.Source) is Path path2) {
if (path2.HasActivePump) { var pumpActive = path2.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false);
foreach (var p in path2.Pumps) p.Highlight = PamhagenBrushes.Red; if (pumpActive) {
foreach (var pump in path2.Hops.Select(h => h.Node as Pump).Where(p => p != null)) {
pump?.Highlight = PamhagenBrushes.Red;
}
} else { } else {
hoverPathDeactivate = path2; hoverPathDeactivate = path2;
PamhagenGraph.ColorPipesAdjacientToPath(path2, PamhagenBrushes.Red, default, null); Graph.ColorPipesAdjacientToPath(path2, PamhagenBrushes.Red, default, null);
} }
} }
if (_lastSource != null) { if (_lastSource != null) {
@@ -545,7 +559,7 @@ namespace PamhagenSysCtrl.Windows {
} }
} }
if (HoveringPath is Path hoveringPath) { if (HoveringPath is Path hoveringPath) {
PamhagenGraph.ColorPipesAdjacientToPath(hoveringPath, PamhagenBrushes.Green, default, PamhagenBrushes.DimOrange); Graph.ColorPipesAdjacientToPath(hoveringPath, PamhagenBrushes.Green, default, PamhagenBrushes.DimOrange);
} }
if (Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == Graph.MW1) is Path pmw1) { if (Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == Graph.MW1) is Path pmw1) {
@@ -570,8 +584,8 @@ namespace PamhagenSysCtrl.Windows {
var paths = Graph.SelectedPaths; var paths = Graph.SelectedPaths;
if (HoveringPath is Path hp) paths = paths.Append(hp); if (HoveringPath is Path hp) paths = paths.Append(hp);
foreach (var b in FastSelectPaths) { foreach (var b in FastSelectPaths.Keys) {
UpdateFastSelectPathButton(paths, hoverPathDeactivate, b.Key); UpdateFastSelectPathButton(paths, hoverPathDeactivate, b);
} }
Graph.Update(_hover, down); Graph.Update(_hover, down);
@@ -579,20 +593,35 @@ namespace PamhagenSysCtrl.Windows {
} }
public void OnUpdate(object? sender, PlantEventArgs? evt) { public void OnUpdate(object? sender, PlantEventArgs? evt) {
if (App.Plant is PamhagenPlant p) { if (sender is not PamhagenPlant plant) return;
if (p.IsPlcConnected) {
Status_Connection.Text = $"Ja ({p.PlcComPort}" + (p.LastRttNs != null ? $", {p.LastRttNs / 1000000.0:N0} ms" : "") + ")";
Status_Plc.Text = $"{p.LastPlcStatus?.Mode} ({p.LastPlcStatus?.Flags:X3}) / {p.LastPlcError?.GetName()} ({(int?)p.LastPlcError:0000})";
Status_CompressedAir.Text = Enumerable.Range(1, 57).Any(p.State.Valve_IsClosed) ? "Ein" : Enumerable.Range(1, 57).Any(p.State.Valve_WantClosed) ? "Aus" : "?";
Status_ValvesOpen.Text = Enumerable.Range(1, 57).Count(p.State.Valve_IsOpen) + " / " + Enumerable.Range(1, 57).Count(p.State.Valve_WantOpen) + " / 57";
} else {
Status_Connection.Text = p.PlcConnectionError is string err ? $"Nein ({p.PlcComPort}, {err})" : $"Nein ({p.PlcComPort})";
Status_Plc.Text = "-";
Status_CompressedAir.Text = "-";
Status_ValvesOpen.Text = "-";
}
}
UpdateScheme(Mouse.GetPosition(SchemeCanvas), Mouse.LeftButton == MouseButtonState.Pressed); UpdateScheme(Mouse.GetPosition(SchemeCanvas), Mouse.LeftButton == MouseButtonState.Pressed);
} }
public void InitializePaths() {
var add = ReconstructSelectedPaths().ToList();
var remove = Graph.SelectedPaths.ToList();
foreach (var p in remove) Graph.RemovePath(p);
for (int i = 1; i <= 57; i++) {
App.Plant?.CloseV(i);
}
foreach (var p in add) {
if (p.Hops.Any(h => h.Node is ControlledValve v && v.IsLocked)) continue;
Graph.AddPath(p);
}
}
public IEnumerable<Path> ReconstructSelectedPaths() {
if (App.Plant is not PamhagenPlant p) yield break;
var sources = Graph.Nodes.Where(n => n is ISource).ToList();
var sinks = Graph.Nodes.Where(n => n is ISink).ToList();
foreach (var src in sources) {
var paths = new List<Path>();
foreach (var sink in sinks) {
paths.AddRange(Graph.GetPaths(src, sink, null, (n) => n is not ISink && (n is not ControlledValve v || p.WantValveOpen(v.Nr))));
}
if (paths.Count == 1)
yield return paths.First();
}
}
} }
} }
-233
View File
@@ -1,233 +0,0 @@
#!/bin/env python3
import socket
import time
import random
import argparse
mw1 = 0
mw2 = 0
t1 = 0
t2 = 0
t3 = 0
t4 = 0
t5 = 0
t6 = 0
t7 = 0
t8 = 0
t9 = 0
pr1 = 0
pr2 = 0
mw1_max = 5000
mw2_max = 5000
t_max = 35000
pr1_max = 20000
pr2_max = 15000
ENTRY_RATE = 20 / 20
PUMP_RATE = 5 / 20
rw = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
def are_valves_open(valves, *ns):
mask = 0
for n in ns:
mask |= 1 << (n - 1)
return (~valves & mask) == mask
def send_cmd(cmd: str, data: str) -> None:
msg = f'(A01{cmd}{data}&'
chk = sum(c for c in msg.encode('ascii')) & 0xFF
msg += f'{chk:02X})\r'
time.sleep(len(msg) * 8 / 9600 * 2)
print(f'-> {msg}')
client.send(msg.encode('ascii'))
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('socket')
args = parser.parse_args()
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client.connect(args.socket)
while True:
line = b''
while b'\r' not in line:
line += client.recv(256)
cmds = line.split(b'\r')
line = cmds[-1]
cmds = [c.decode('ascii') for c in cmds[:-1]]
for cmd in cmds:
print(f'<- {cmd}')
time.sleep(len(cmd) * 8 / 9600 * 2)
if not cmd.startswith('(A01') or not cmd.endswith(')') or len(cmd) < 7:
send_cmd('CE', '02')
continue
c, d = cmd[4:6], cmd[6:-1]
if cmd[-4] == '&':
chk1 = int(cmd[-3:-1], 16)
chk2 = sum(c for c in cmd[:-3].encode('ascii')) & 0xFF
if chk1 != chk2:
send_cmd('CE', '03')
continue
d = d[:-3]
if c == 'TS':
send_cmd('TS', d)
elif c == 'ST':
send_cmd('ST', '0002')
elif c == 'DR':
# FIXME
d = d.split(',')
s = int(d[0][2:])
l = int(d[1])
send_cmd('DR', ''.join(f'{r:04X}' for r in rw[s:s+l]))
elif c == 'DW':
# FIXME
d = d.split(',')
s = int(d[0][2:])
l = int(d[1])
for i in range(l):
rw[s + i] = int(d[2 + i], 16)
send_cmd('ST', '0002')
else:
send_cmd('CE', '01')
# tick - valves
for i in range(0, 60):
want_open = rw[i // 16] & (1 << (i % 16)) == 0
is_open = rw[10 + i // 16] & (1 << (i % 16)) != 0
if want_open and not is_open:
if random.random() > 0.875:
rw[10 + i // 16] |= 1 << (i % 16)
elif not want_open and is_open:
if random.random() > 0.875:
rw[10 + i // 16] &= ~(1 << (i % 16))
# tick - wannen
want_a1_selected = (rw[5] & 0x0004) != 0
want_a2_selected = not want_a1_selected
is_a1_selected = (rw[14] & 0x0200) != 0
is_a2_selected = (rw[14] & 0x0100) != 0
if is_a1_selected and not want_a1_selected:
rw[14] &= 0xFDFF
elif not is_a1_selected and want_a1_selected:
if random.random() > 0.875:
rw[14] |= 0x0200
if is_a2_selected and not want_a2_selected:
rw[14] &= 0xFEFF
elif not is_a2_selected and want_a2_selected:
if random.random() > 0.875:
rw[14] |= 0x0100
# tick - pipeline
valves = (rw[13] << 48) | (rw[12] << 32) | (rw[11] << 16) | rw[10]
wannen_schnecke = (rw[5] & 0x1) != 0
mp1 = (rw[4] & 0x002) != 0
mp2 = (rw[4] & 0x004) != 0
p12 = (rw[4] & 0x010) != 0
p3 = (rw[4] & 0x020) != 0
p4 = (rw[4] & 0x040) != 0
p5 = (rw[4] & 0x080) != 0
p6 = (rw[4] & 0x100) != 0
if wannen_schnecke and is_a1_selected:
mw1 += ENTRY_RATE
elif wannen_schnecke and is_a2_selected:
mw2 += ENTRY_RATE
if mp1 and mw1 > 0:
mw1 -= PUMP_RATE
if are_valves_open(valves, 1, 6, 51) or are_valves_open(valves, 4, 8, 52):
pr1 += PUMP_RATE
elif are_valves_open(valves, 1, 6, 55) or are_valves_open(valves, 4, 8, 56):
pr2 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 22) or are_valves_open(valves, 4, 7, 9, 13, 23):
t1 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 24) or are_valves_open(valves, 4, 7, 9, 13, 25):
t2 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 26) or are_valves_open(valves, 4, 7, 9, 13, 27):
t3 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 28, 30) or are_valves_open(valves, 4, 7, 9, 13, 29, 31):
t4 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 28, 32) or are_valves_open(valves, 4, 7, 9, 13, 29, 33):
t5 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 28, 34, 37) or are_valves_open(valves, 4, 7, 9, 13, 29, 35, 36):
t6 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 28, 34, 39) or are_valves_open(valves, 4, 7, 9, 13, 29, 35, 38):
t7 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 28, 34, 41) or are_valves_open(valves, 4, 7, 9, 13, 29, 35, 40):
t8 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 28, 34, 43) or are_valves_open(valves, 4, 7, 9, 13, 29, 35, 42):
t9 += PUMP_RATE
if mp2 and mw2 > 0:
mw2 -= PUMP_RATE
if are_valves_open(valves, 2, 8, 52) or are_valves_open(valves, 3, 6, 51):
pr1 += PUMP_RATE
elif are_valves_open(valves, 2, 8, 56) or are_valves_open(valves, 3, 6, 55):
pr2 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 22) or are_valves_open(valves, 2, 7, 9, 13, 23):
t1 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 24) or are_valves_open(valves, 2, 7, 9, 13, 25):
t2 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 26) or are_valves_open(valves, 2, 7, 9, 13, 27):
t3 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 28, 30) or are_valves_open(valves, 2, 7, 9, 13, 29, 31):
t4 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 28, 32) or are_valves_open(valves, 2, 7, 9, 13, 29, 33):
t5 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 28, 34, 37) or are_valves_open(valves, 2, 7, 9, 13, 29, 35, 36):
t6 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 28, 34, 39) or are_valves_open(valves, 2, 7, 9, 13, 29, 35, 38):
t7 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 28, 34, 41) or are_valves_open(valves, 2, 7, 9, 13, 29, 35, 40):
t8 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 28, 34, 43) or are_valves_open(valves, 2, 7, 9, 13, 29, 35, 42):
t9 += PUMP_RATE
if p6 and (t6 > 0 or t7 > 0 or t8 > 0 or t9 > 0):
if t6 > 0:
t6 -= PUMP_RATE
if t7 > 0:
t7 -= PUMP_RATE
if t8 > 0:
t8 -= PUMP_RATE
if t9 > 0:
t9 -= PUMP_RATE
if are_valves_open(valves, 53):
pr1 += PUMP_RATE
elif are_valves_open(valves, 57):
pr2 += PUMP_RATE
# tick - fill levels
rw[14] &= 0xFF00
rw[14] |= 0x0004 if mw1 > 50 else 0
rw[14] |= 0x0002 if mw1 > mw1_max / 2 else 0
rw[14] |= 0x0001 if mw1 > mw1_max * 0.875 else 0
rw[14] |= 0x0020 if mw2 > 50 else 0
rw[14] |= 0x0010 if mw2 > mw2_max / 2 else 0
rw[14] |= 0x0008 if mw2 > mw2_max * 0.875 else 0
rw[14] |= 0x0040 if pr1 > pr1_max * 0.875 else 0
rw[14] |= 0x0080 if pr2 > pr2_max * 0.875 else 0
rw[16] &= 0xC000
rw[16] |= 0x0001 if t1 > 50 else 0
rw[16] |= 0x0002 if t1 > t_max * 0.875 else 0
rw[16] |= 0x0004 if t2 > 50 else 0
rw[16] |= 0x0008 if t2 > t_max * 0.875 else 0
rw[16] |= 0x0010 if t3 > 50 else 0
rw[16] |= 0x0020 if t3 > t_max * 0.875 else 0
rw[16] |= 0x0040 if t4 > 50 else 0
rw[16] |= 0x0080 if t4 > t_max * 0.875 else 0
rw[16] |= 0x0100 if t5 > 50 else 0
rw[16] |= 0x0200 if t5 > t_max * 0.875 else 0
rw[16] |= 0x0400 if t6 > t_max * 0.875 else 0
rw[16] |= 0x0800 if t7 > t_max * 0.875 else 0
rw[16] |= 0x1000 if t8 > t_max * 0.875 else 0
rw[16] |= 0x2000 if t9 > t_max * 0.875 else 0
print(mw1, mw2, t1, t2, t3, t4, t5, t6, t7, t8, t9, pr1, pr2)
BIN
View File
Binary file not shown.
-216
View File
@@ -1,216 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="78mm" height="117mm" viewBox="0 0 780 1170">
<title>XBE-24 Key Legend Pamhagen</title>
<defs>
<path id="key" d="M 5,5 l 150,0 l 0,130 q -75,35 -150,0 Z"
stroke-width="10" stroke="#808080" paint-order="stroke"/>
<path id="start" d="M 0,-40 l 40,40 l -40,40 l -40,-40 Z M 0,-20 l 0,40 Z"
stroke-width="7" stroke="#004000" fill-opacity="0"
stroke-linejoin="round"/>
<path id="stop" d="M 0,40 a 40,40 180 0,0 0,-80 a 40,40 180 1,0 0,80 m 0,-5 l -30.310889,-52.5 l 60.621778,0 Z"
stroke-width="7" stroke="#400000" fill-opacity="0"/>
<path id="on-off" d="M 0,40 a 40,40 180 0,0 0,-80 a 40,40 180 1,0 0,80 Z M 0,-20 l 0,40 Z"
stroke-width="7" stroke="#000000" fill-opacity="0"
stroke-linejoin="round"/>
<path id="arrow-down" d="M 0,-35 l 15,0 l 0,50 l 10,0 l -25,25 l -25,-25 l 10,0 l 0,-50 Z"
fill="#000040"/>
<use id="arrow-left-down" href="#arrow-down" transform="rotate(30)"/>
<path id="arrow-from-mw1" d="M -70,-20 l 80,0 l 0,30 l 10,0 l -20,20 l -20,-20 l 10,0 l 0,-10 l -60,0 Z"
fill="#000040"/>
<use id="arrow-from-mw2" href="#arrow-from-mw1" transform="scale(-1,1)"/>
<path id="press" d="M 35,0 l 10,0 l 0,55 l -20,0 l 0,-10 l -50,0 l 0,10 l -20,0 l 0,-55 l 10,0 a 35,35 180 0,1 70,0 a 35,35 180 0,1 -70,0"
stroke-width="5" stroke="#000000" fill-opacity="0"/>
<!--path id="tank" d="M 35,-32 l 0,90 l -10,0 l 0,-23 l 10,-9 l -30,25 l -10,0 l -30,-25 l 10,9 l 0,23 l -10,0 l 0,-90 Z"
stroke-width="5" stroke="#000000" fill-opacity="0"/-->
<path id="tank" d="M 35,-32 l 0,90 l -10,0 l 0,-23 l -20,16 l -10,0 l -20,-16 l 0,23 l -10,0 l 0,-90 Z"
stroke-width="5" stroke="#000000" fill-opacity="0"/>
</defs>
<rect x="0" y="0" width="780" height="1170" stroke="#000000" fill="#FFFFFF" stroke-width="5"/>
<g id="k0">
<use href="#key" x="25" y="30" fill="#E8E8FF"/>
<use href="#press" x="105" y="110"/>
<text x="105" y="110" font-size="35" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">P1</text>
<use href="#arrow-from-mw2" x="105" y="60"/>
<text x="148" y="50" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k1">
<use href="#key" x="25" y="220" fill="#E8E8FF"/>
<use href="#press" x="105" y="300"/>
<text x="105" y="300" font-size="35" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">P2</text>
<use href="#arrow-from-mw2" x="105" y="250"/>
<text x="148" y="240" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k2">
<use href="#key" x="25" y="410" fill="#E8E8FF"/>
<use href="#tank" x="105" y="490"/>
<text x="105" y="490" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT1</text>
<use href="#arrow-from-mw2" x="105" y="440"/>
<text x="148" y="430" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k3">
<use href="#key" x="25" y="600" fill="#E8E8FF"/>
<use href="#tank" x="105" y="680"/>
<text x="105" y="680" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT2</text>
<use href="#arrow-from-mw2" x="105" y="630"/>
<text x="148" y="620" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k4">
<use href="#key" x="25" y="790" fill="#E8E8FF"/>
<use href="#tank" x="105" y="870"/>
<text x="105" y="870" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT3</text>
<use href="#arrow-from-mw2" x="105" y="820"/>
<text x="148" y="810" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k5">
<use href="#key" x="25" y="980" fill="#E8E8FF"/>
<use href="#tank" x="105" y="1060"/>
<text x="105" y="1060" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT4</text>
<use href="#arrow-from-mw2" x="105" y="1010"/>
<text x="148" y="1000" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k6">
<use href="#key" x="215" y="30" fill="#E8FFE8"/>
<use href="#start" x="295" y="85"/>
<text x="295" y="150" font-size="40" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">Rebler</text>
</g>
<g id="k7">
<use href="#key" x="215" y="220" fill="#FFE8E8"/>
<use href="#stop" x="295" y="275"/>
<text x="295" y="340" font-size="40" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">Rebler</text>
</g>
<g id="k8">
<use href="#key" x="215" y="410" fill="#E8E8FF"/>
<use href="#arrow-left-down" x="300" y="465"/>
<text x="295" y="530" font-size="45" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k9">
<use href="#key" x="215" y="600" fill="#FFFFFF"/>
<use href="#on-off" x="295" y="655"/>
<text x="295" y="710" font-size="25" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;"><tspan>Rührer</tspan><tspan font-size="30" dx="-2.5em" dy="0.875em">MW2</tspan></text>
</g>
<g id="k10">
<use href="#key" x="215" y="790" fill="#E8FFE8"/>
<use href="#start" x="295" y="845"/>
<text x="295" y="913" font-size="45" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">MP2</text>
</g>
<g id="k11">
<use href="#key" x="215" y="980" fill="#FFE8E8"/>
<use href="#stop" x="295" y="1035"/>
<text x="295" y="1103" font-size="45" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">MP2</text>
</g>
<g id="k12">
<use href="#key" x="405" y="30" fill="#E8FFE8"/>
<use href="#start" x="485" y="85"/>
<text x="485" y="140" font-size="25" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;"><tspan>Trog-</tspan><tspan dx="-3.5em" dy="0.875em">schnecke</tspan></text>
</g>
<g id="k13">
<use href="#key" x="405" y="220" fill="#FFE8E8"/>
<use href="#stop" x="485" y="275"/>
<text x="485" y="330" font-size="25" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;"><tspan>Trog-</tspan><tspan dx="-3.5em" dy="0.875em">schnecke</tspan></text>
</g>
<g id="k14">
<use href="#key" x="405" y="410" fill="#E8E8FF"/>
<use href="#arrow-down" x="485" y="465"/>
<text x="485" y="530" font-size="45" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
<g id="k15">
<use href="#key" x="405" y="600" fill="#FFFFFF"/>
<use href="#on-off" x="485" y="655"/>
<text x="485" y="710" font-size="25" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;"><tspan>Rührer</tspan><tspan font-size="30" dx="-2.5em" dy="0.875em">MW1</tspan></text>
</g>
<g id="k16">
<use href="#key" x="405" y="790" fill="#E8FFE8"/>
<use href="#start" x="485" y="845"/>
<text x="485" y="913" font-size="45" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">MP1</text>
</g>
<g id="k17">
<use href="#key" x="405" y="980" fill="#FFE8E8"/>
<use href="#stop" x="485" y="1035"/>
<text x="485" y="1103" font-size="45" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">MP1</text>
</g>
<g id="k18">
<use href="#key" x="595" y="30" fill="#E8E8FF"/>
<use href="#press" x="675" y="110"/>
<text x="675" y="110" font-size="35" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">P1</text>
<use href="#arrow-from-mw1" x="675" y="60"/>
<text x="632" y="50" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
<g id="k19">
<use href="#key" x="595" y="220" fill="#E8E8FF"/>
<use href="#press" x="675" y="300"/>
<text x="675" y="300" font-size="35" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">P2</text>
<use href="#arrow-from-mw1" x="675" y="250"/>
<text x="632" y="240" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
<g id="k20">
<use href="#key" x="595" y="410" fill="#E8E8FF"/>
<use href="#tank" x="675" y="490"/>
<text x="675" y="490" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT1</text>
<use href="#arrow-from-mw1" x="675" y="440"/>
<text x="632" y="430" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
<g id="k21">
<use href="#key" x="595" y="600" fill="#E8E8FF"/>
<use href="#tank" x="675" y="680"/>
<text x="675" y="680" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT2</text>
<use href="#arrow-from-mw1" x="675" y="630"/>
<text x="632" y="620" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
<g id="k22">
<use href="#key" x="595" y="790" fill="#E8E8FF"/>
<use href="#tank" x="675" y="870"/>
<text x="675" y="870" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT3</text>
<use href="#arrow-from-mw1" x="675" y="820"/>
<text x="632" y="810" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
<g id="k23">
<use href="#key" x="595" y="980" fill="#E8E8FF"/>
<use href="#tank" x="675" y="1060"/>
<text x="675" y="1060" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT4</text>
<use href="#arrow-from-mw1" x="675" y="1010"/>
<text x="632" y="1000" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB