3 Commits
Author SHA1 Message Date
lorenz.stechauner e2b755db44 [WIP] Pipe joins
Test / Run tests (push) Successful in 13s
2026-09-12 20:16:07 +02:00
lorenz.stechauner 3d24c65758 Pipeline: Change Valve symbol and add abstract Valve
Test / Run tests (push) Successful in 15s
2026-09-12 19:54:49 +02:00
lorenz.stechauner f4ef8d2f16 Pipeline/Pump: Change triangle to ISO symbol 2026-09-12 19:50:51 +02:00
13 changed files with 279 additions and 472 deletions

No files matched your search

+72 -72
View File
@@ -94,10 +94,10 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(MP1, x1);
CreatePipe(MP2, x2);
var v1 = CreateValve(1, mw1X, mpY + 16);
var v2 = CreateValve(2, mw2X, mpY + 16);
var v3 = CreateValve(3, mw2X + 6, mpY + 16);
var v4 = CreateValve(4, mw1X - 6, mpY + 16);
var v1 = CreateValve(1, mw1X, mpY + 16, 2);
var v2 = CreateValve(2, mw2X, mpY + 16, 0);
var v3 = CreateValve(3, mw2X + 6, mpY + 16, 2);
var v4 = CreateValve(4, mw1X - 6, mpY + 16, 0);
CreatePipe(x1, v1);
CreatePipe(x1, v4);
@@ -116,11 +116,11 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(v4, x5, true);
CreatePipe(x5, x6, true);
var v5 = CreateValve(5, x1X + 6, ltg1Y);
var v6 = CreateValve(6, x1X + 6, ltg2Y + 8);
var v7 = CreateValve(7, x1X + 6, ltg2Y);
var v8 = CreateValve(8, x1X - 6, ltg2Y + 8);
var v9 = CreateValve(9, ltg2X, ltg5Y + 8);
var v5 = CreateValve(5, x1X + 6, ltg1Y, 1);
var v6 = CreateValve(6, x1X + 6, ltg2Y + 8, 3);
var v7 = CreateValve(7, x1X + 6, ltg2Y, 3);
var v8 = CreateValve(8, x1X - 6, ltg2Y + 8, 0);
var v9 = CreateValve(9, ltg2X, ltg5Y + 8, 2);
CreatePipe(x4, v5);
CreatePipe(x4, v6, true);
@@ -136,13 +136,13 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(v9, x7);
CreatePipe(x7, xv11);
var v10 = CreateValve(10, ltg2X + 8, ltg5Y);
var v11 = CreateValve(11, ltg1X - 6, wt1Y + 14);
var v12 = CreateValve(12, ltg1X, wt1Y);
var v13 = CreateValve(13, ltg2X, wt1Y);
var v14 = CreateValve(14, ltg1X - 6, wt1Y - 8);
var v15 = CreateValve(15, ltg1X - 6, wt1Y + 8);
var v16 = CreateValve(16, ltg1X - 6, wt1Y - 14);
var v10 = CreateValve(10, ltg2X + 8, ltg5Y, 3);
var v11 = CreateValve(11, ltg1X - 6, wt1Y + 14, 3);
var v12 = CreateValve(12, ltg1X, wt1Y, 0);
var v13 = CreateValve(13, ltg2X, wt1Y, 2);
var v14 = CreateValve(14, ltg1X - 6, wt1Y - 8, 3);
var v15 = CreateValve(15, ltg1X - 6, wt1Y + 8, 1);
var v16 = CreateValve(16, ltg1X - 6, wt1Y - 14, 1);
CreatePipe(v10, x7);
CreatePipe(xv11, v11);
CreatePipe(xv15, v15);
@@ -156,8 +156,8 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(v16, xv16);
CreatePipe(v14, xv14);
var v20 = CreateValve(20, ltg1X - 6, wt2Y - 14);
var v21 = CreateValve(21, ltg1X - 6, wt2Y - 8);
var v20 = CreateValve(20, ltg1X - 6, wt2Y - 14, 1);
var v21 = CreateValve(21, ltg1X - 6, wt2Y - 8, 3);
var xv20 = new PipeJoin(ltg1X, wt2Y - 14);
var xv21 = new PipeJoin(ltg2X, wt2Y - 8);
CreatePipe(xv16, xv20);
@@ -167,9 +167,9 @@ namespace PamhagenSysCtrl.Helpers {
var wt1 = new HeatExchanger("WT Most", wtX, wt1Y);
var wt2 = new HeatExchanger("WT Warmwasser", wtX, wt2Y);
var v17 = CreateValve(17, wtX + 14, wt1Y);
var v18 = CreateValve(18, wtX + 8, wt1Y + 8);
var v19 = CreateValve(19, wtX + 8, (wt1Y + wt2Y) / 2);
var v17 = CreateValve(17, wtX + 14, wt1Y, 2);
var v18 = CreateValve(18, wtX + 8, wt1Y + 8, 3);
var v19 = CreateValve(19, wtX + 8, (wt1Y + wt2Y) / 2, 0);
var xwt1 = new PipeJoin(wtX + 24, wt1Y + 8);
var xwt2 = new PipeJoin(wtX + 14, wt1Y + 8);
var xwt3 = new PipeJoin(wtX + 8, wt1Y - 8);
@@ -196,8 +196,8 @@ namespace PamhagenSysCtrl.Helpers {
var xv22 = new PipeJoin(t1X - 3, ltg7Y);
var xv23 = new PipeJoin(t1X + 3, ltg6Y);
var v22 = CreateValve(22, t1X - 3, tValveY);
var v23 = CreateValve(23, t1X + 3, tValveY);
var v22 = CreateValve(22, t1X - 3, tValveY, 0);
var v23 = CreateValve(23, t1X + 3, tValveY, 2);
Tank1 = CreateTank(1, t1X, tanksY, 35000);
CreatePipe(xv20, xv22, true, label: "Ltg. 7", labelOffsetX: 10);
CreatePipe(xv21, xv23, true, label: "Ltg. 6", labelOffsetX: 4);
@@ -208,8 +208,8 @@ namespace PamhagenSysCtrl.Helpers {
var xv24 = new PipeJoin(t2X - 3, ltg7Y);
var xv25 = new PipeJoin(t2X + 3, ltg6Y);
var v24 = CreateValve(24, t2X - 3, tValveY);
var v25 = CreateValve(25, t2X + 3, tValveY);
var v24 = CreateValve(24, t2X - 3, tValveY, 0);
var v25 = CreateValve(25, t2X + 3, tValveY, 2);
Tank2 = CreateTank(2, t2X, tanksY, 35000);
CreatePipe(xv22, xv24);
CreatePipe(xv23, xv25);
@@ -220,8 +220,8 @@ namespace PamhagenSysCtrl.Helpers {
var xv26 = new PipeJoin(t3X - 3, ltg7Y);
var xv27 = new PipeJoin(t3X + 3, ltg6Y);
var v26 = CreateValve(26, t3X - 3, tValveY);
var v27 = CreateValve(27, t3X + 3, tValveY);
var v26 = CreateValve(26, t3X - 3, tValveY, 0);
var v27 = CreateValve(27, t3X + 3, tValveY, 2);
Tank3 = CreateTank(3, t3X, tanksY, 35000);
CreatePipe(xv24, xv26);
CreatePipe(xv25, xv27);
@@ -230,15 +230,15 @@ namespace PamhagenSysCtrl.Helpers {
CreateInlet(v26, Tank3);
CreateInlet(v27, Tank3);
var v28 = CreateValve(28, (t3X + t4X) / 2, ltg7Y);
var v29 = CreateValve(29, (t3X + t4X) / 2, ltg6Y);
var v28 = CreateValve(28, (t3X + t4X) / 2, ltg7Y, 1);
var v29 = CreateValve(29, (t3X + t4X) / 2, ltg6Y, 3);
CreatePipe(xv26, v28);
CreatePipe(xv27, v29);
var xv30 = new PipeJoin(t4X - 3, ltg7Y);
var xv31 = new PipeJoin(t4X + 3, ltg6Y);
var v30 = CreateValve(30, t4X - 3, tValveY);
var v31 = CreateValve(31, t4X + 3, tValveY);
var v30 = CreateValve(30, t4X - 3, tValveY, 0);
var v31 = CreateValve(31, t4X + 3, tValveY, 2);
Tank4 = CreateTank(4, t4X, tanksY, 35000);
CreatePipe(v28, xv30);
CreatePipe(v29, xv31);
@@ -249,8 +249,8 @@ namespace PamhagenSysCtrl.Helpers {
var xv32 = new PipeJoin(t5X - 3, ltg7Y);
var xv33 = new PipeJoin(t5X + 3, ltg6Y);
var v32 = CreateValve(32, t5X - 3, tValveY);
var v33 = CreateValve(33, t5X + 3, tValveY);
var v32 = CreateValve(32, t5X - 3, tValveY, 0);
var v33 = CreateValve(33, t5X + 3, tValveY, 2);
Tank5 = CreateTank(5, t5X, tanksY, 35000);
CreatePipe(xv30, xv32, label: "Ltg. 7", labelOffsetX: 6);
CreatePipe(xv31, xv33, label: "Ltg. 6", labelOffsetX: 0);
@@ -259,15 +259,15 @@ namespace PamhagenSysCtrl.Helpers {
CreateInlet(v32, Tank5);
CreateInlet(v33, Tank5);
var v34 = CreateValve(34, (t5X + t6X) / 2, ltg7Y);
var v35 = CreateValve(35, (t5X + t6X) / 2, ltg6Y);
var v34 = CreateValve(34, (t5X + t6X) / 2, ltg7Y, 1);
var v35 = CreateValve(35, (t5X + t6X) / 2, ltg6Y, 3);
CreatePipe(xv32, v34);
CreatePipe(xv33, v35);
var xv36 = new PipeJoin(t6X + 3, ltg6Y);
var xv37 = new PipeJoin(t6X - 3, ltg7Y);
var v36 = CreateValve(36, t6X + 3, tValveY);
var v37 = CreateValve(37, t6X - 3, tValveY);
var v36 = CreateValve(36, t6X + 3, tValveY, 2);
var v37 = CreateValve(37, t6X - 3, tValveY, 0);
Tank6 = CreateTank(6, t6X, tanksY, 35000);
CreatePipe(v34, xv37);
CreatePipe(v35, xv36);
@@ -278,8 +278,8 @@ namespace PamhagenSysCtrl.Helpers {
var xv38 = new PipeJoin(t7X + 3, ltg6Y);
var xv39 = new PipeJoin(t7X - 3, ltg7Y);
var v38 = CreateValve(38, t7X + 3, tValveY);
var v39 = CreateValve(39, t7X - 3, tValveY);
var v38 = CreateValve(38, t7X + 3, tValveY, 2);
var v39 = CreateValve(39, t7X - 3, tValveY, 0);
Tank7 = CreateTank(7, t7X, tanksY, 35000);
CreatePipe(xv37, xv39);
CreatePipe(xv36, xv38);
@@ -290,8 +290,8 @@ namespace PamhagenSysCtrl.Helpers {
var xv40 = new PipeJoin(t8X + 3, ltg6Y);
var xv41 = new PipeJoin(t8X - 3, ltg7Y);
var v40 = CreateValve(40, t8X + 3, tValveY);
var v41 = CreateValve(41, t8X - 3, tValveY);
var v40 = CreateValve(40, t8X + 3, tValveY, 2);
var v41 = CreateValve(41, t8X - 3, tValveY, 0);
Tank8 = CreateTank(8, t8X, tanksY, 35000);
CreatePipe(xv38, xv40, label: "Ltg. 6", labelOffsetX: 0);
CreatePipe(xv39, xv41, label: "Ltg. 7", labelOffsetX: 6);
@@ -300,8 +300,8 @@ namespace PamhagenSysCtrl.Helpers {
CreateInlet(v40, Tank8);
CreateInlet(v41, Tank8);
var v42 = CreateValve(42, t9X + 3, tValveY);
var v43 = CreateValve(43, t9X - 3, tValveY);
var v42 = CreateValve(42, t9X + 3, tValveY, 2);
var v43 = CreateValve(43, t9X - 3, tValveY, 0);
Tank9 = CreateTank(9, t9X, tanksY, 35000);
CreatePipe(xv40, v42);
CreatePipe(xv41, v43);
@@ -321,11 +321,11 @@ namespace PamhagenSysCtrl.Helpers {
CreateOutlet(Tank4, P4);
CreateOutlet(Tank5, P5);
var v44 = CreateValve(44, (t1X + t2X) / 2, tanksY + 30);
var v45 = CreateValve(45, t3X, tanksY + 30);
var v46 = CreateValve(46, (t3X + t4X) / 2, ltg5Y);
var v47 = CreateValve(47, t4X, tanksY + 30);
var v48 = CreateValve(48, t5X, tanksY + 30);
var v44 = CreateValve(44, (t1X + t2X) / 2, tanksY + 30, 2);
var v45 = CreateValve(45, t3X, tanksY + 30, 2);
var v46 = CreateValve(46, (t3X + t4X) / 2, ltg5Y, 3);
var v47 = CreateValve(47, t4X, tanksY + 30, 2);
var v48 = CreateValve(48, t5X, tanksY + 30, 2);
var xv44 = new PipeJoin((t1X + t2X) / 2, tanksY + 36);
var xv45 = new PipeJoin(t3X, tanksY + 36);
var xv47 = new PipeJoin(t4X, tanksY + 36);
@@ -355,7 +355,7 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(xt7, xt6);
CreatePipe(xt6, P6);
var v49 = CreateValve(49, t4X, tanksY + 44);
var v49 = CreateValve(49, t4X, tanksY + 44, 2);
CreatePipe(xv47, v49);
var prX = (pr1X + pr2X) / 2;
@@ -363,14 +363,14 @@ namespace PamhagenSysCtrl.Helpers {
var pr2cX = prX + 3;
Press1 = new Press("Presse 1", pr1X, prY, 20000, () => FillState.Unknown);
Press2 = new Press("Presse 2", pr2X, prY, 15000, () => FillState.Unknown);
var v50 = CreateValve(50, pr2cX + 6, ltg3Y);
var v51 = CreateValve(51, pr1cX - 6, ltg3Y);
var v52 = CreateValve(52, pr1cX - 6, ltg4Y);
var v53 = CreateValve(53, pr2cX + 6, ltg4Y);
var v54 = CreateValve(54, pr2cX + 6, ltg8Y);
var v55 = CreateValve(55, pr1cX - 6, ltg8Y);
var v56 = CreateValve(56, pr1cX - 6, ltg9Y);
var v57 = CreateValve(57, pr2cX + 6, ltg9Y);
var v50 = CreateValve(50, pr2cX + 6, ltg3Y, 1);
var v51 = CreateValve(51, pr1cX - 6, ltg3Y, 1);
var v52 = CreateValve(52, pr1cX - 6, ltg4Y, 3);
var v53 = CreateValve(53, pr2cX + 6, ltg4Y, 3);
var v54 = CreateValve(54, pr2cX + 6, ltg8Y, 1);
var v55 = CreateValve(55, pr1cX - 6, ltg8Y, 1);
var v56 = CreateValve(56, pr1cX - 6, ltg9Y, 3);
var v57 = CreateValve(57, pr2cX + 6, ltg9Y, 3);
var xp1 = new PipeJoin(pr1cX - 14, ltg3Y);
var xp2 = new PipeJoin(pr1cX - 14, ltg4Y);
var xp3 = new PipeJoin(pr2cX + 14, ltg8Y);
@@ -404,16 +404,16 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(xp5, xp6);
CreatePipe(xp7, xp8);
var zv1 = new CenterValve("ZV1", pr1cX, prY - 8, () => App.Plant?.Press1HasClearance ?? false);
var zv2 = new CenterValve("ZV2", pr2cX, prY - 8, () => App.Plant?.Press2HasClearance ?? 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?.Press2HasClearance ?? false);
CreatePipe(xp6, zv1, true);
CreatePipe(xp8, zv2, true);
CreateInlet(zv1, Press1, true);
CreateInlet(zv2, Press2, true);
}
protected Valve CreateValve(int n, double x, double y) {
return new(n, x, y, () => App.Plant?.IsValveOpen(n) ?? false, (v) => App.Plant?.ManualControlMode ?? false ? App.Plant.WantValveOpen(n) : SelectedPaths.Any(p => p.Hops.Any(h => h.Node == v)));
protected ControlledValve CreateValve(int n, double x, double y, int o) {
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) {
@@ -423,23 +423,23 @@ namespace PamhagenSysCtrl.Helpers {
private int PathCost(Path p) {
return
1000 * p.Hops.Count(h => h.Node is Valve) +
(p.Hops.Any(h => h.Node is Valve v && v.Nr == 15) && !p.Hops.Any(h => h.Node is Valve v && (v.Nr == 16 || v.Nr == 20)) ? 10 : 0) +
(p.Hops.Any(h => h.Node is Valve v && v.Nr == 11) && !p.Hops.Any(h => h.Node is Valve v && (v.Nr == 14 || v.Nr == 21)) ? 10 : 0) +
(p.Hops.Any(h => h.Node is Valve v && v.Nr == 6) ? 10 : 0) +
(p.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.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.Hops.Any(h => h.Node is ControlledValve v && v.Nr == 6) ? 10 : 0) +
p.Bends;
}
public Path? GetFreePath(IEnumerable<INode> points, bool overrideStart = false) {
if (overrideStart) {
return GetPath(points, (n) => n is not ISink && (n is not Valve v || !v.LockedForPaths.Any(p => p.Start != points.First())), PathCost);
return GetPath(points, (n) => n is not ISink && (n is not ControlledValve v || !v.LockedForPaths.Any(p => p.Start != points.First())), PathCost);
} else {
return GetPath(points, (n) => n is not ISink && (n is not Valve v || !v.IsLocked), PathCost);
return GetPath(points, (n) => n is not ISink && (n is not ControlledValve v || !v.IsLocked), PathCost);
}
}
public void TraverseSubgraph(INode start, Func<INode, bool> invalid, HashSet<IEdge>? _visited = null, Action<INode, bool, IEdge>? edgeAction = null, Action<Valve>? valveAction = null, bool reverse = false) {
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) {
var visited = _visited ?? [];
if (start is Valve v) {
if (start is ControlledValve v) {
valveAction?.Invoke(v);
}
if (invalid(start)) {
@@ -467,7 +467,7 @@ namespace PamhagenSysCtrl.Helpers {
UpdateValvesAdjacientToPath(path, true, false);
if (App.Plant is PamhagenPlant plant) {
foreach (var (_, node) in path.Hops) {
if (node is Valve v) {
if (node is ControlledValve v) {
plant.OpenV(v.Nr);
}
}
@@ -480,7 +480,7 @@ namespace PamhagenSysCtrl.Helpers {
foreach (var p in _selectedPaths) UpdateValvesAdjacientToPath(p, true);
if (App.Plant is PamhagenPlant plant) {
foreach (var (_, node) in path.Hops) {
if (node is Valve v) {
if (node is ControlledValve v) {
plant.CloseV(v.Nr);
}
}
@@ -489,7 +489,7 @@ namespace PamhagenSysCtrl.Helpers {
private void UpdateValvesAdjacientToPath(Path path, bool locked, bool? wantOpen = null) {
foreach (var (edge, node) in path.Hops) {
TraverseSubgraph(node, (n) => n is Valve || n is ISink || n is Pump, valveAction: (v) => {
TraverseSubgraph(node, (n) => n is ControlledValve || n is ISink || n is Pump, valveAction: (v) => {
if (locked) {
v.LockedForPaths.Add(path);
} else {
@@ -530,7 +530,7 @@ namespace PamhagenSysCtrl.Helpers {
public void ColorPipesByValves(INode start, Brush? color1, MotorState flowArrows, Brush? color2) {
var flowPathOffset = 0.0;
TraverseSubgraph(start, (n) => n is ISink || n is Outlet || !((n as Valve)?.IsOpen ?? (n as CenterValve)?.IsOpen ?? true), null,
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)) {
e.Highlight = color1;
-8
View File
@@ -21,11 +21,6 @@ namespace PamhagenSysCtrl.Helpers {
FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown
], default);
public string? PlcPortName => Plc?.PortName;
public T1.Status? LastPlcStatus => Plc?.LastPlcStatus;
public T1Error? LastPlcError => Plc?.LastPlcError;
public long? LastRttNs { get; private set; }
public bool IsValveOpen(int n) => !IsValveClosed(n);
public bool IsValveClosed(int n) => Sensors.GetDS(n);
public bool WantValveClosed(int n) => !WantValveOpen(n);
@@ -138,17 +133,14 @@ namespace PamhagenSysCtrl.Helpers {
protected async void BackgroundLoop(object? parameters) {
while (IsRunning && Plc != null) {
var start = DateTime.Now;
try {
if (ActuatorsChanged) {
await Plc.WriteOutputs(Actuators);
ActuatorsChanged = false;
}
Sensors = await Plc.ReadInputs();
LastRttNs = (long)(DateTime.Now - start).TotalNanoseconds;
RaiseUpdateEvent(new());
} catch (Exception exc) {
LastRttNs = null;
var str = "Bei der SPS ist ein Fehler aufgetreten:\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "SPS-Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
-4
View File
@@ -34,10 +34,6 @@ namespace PamhagenSysCtrl.Helpers {
protected readonly T1 Plc;
public string PortName => Plc.PortName;
public T1.Status LastPlcStatus => Plc.LastStatus;
public T1Error LastPlcError => Plc.LastError;
public PamhagenPlc(string portName, Parity parity = Parity.Odd) {
Plc = new T1(portName, parity);
}
@@ -1,71 +0,0 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline {
public class CenterValve : INode {
public const double DIAMETER = 4.8;
public double CenterX { get; set; }
public double CenterY { get; set; }
public string Label { get; set; }
public Brush? Highlight { get; set; }
public bool IsOpen => CallbackIsOpen();
public ISet<IEdge> Inputs { get; init; }
public ISet<IEdge> Outputs { get; init; }
protected Func<bool> CallbackIsOpen;
private Shape? _circle;
private TextBlock? _text;
public CenterValve(string label, double x, double y, Func<bool> cbOpen) {
CenterX = x;
CenterY = y;
Label = label;
CallbackIsOpen = cbOpen;
Inputs = new HashSet<IEdge>();
Outputs = new HashSet<IEdge>();
}
public void Draw(Canvas canvas) {
_circle = new Ellipse() {
Fill = Brushes.DarkGray,
Stroke = Brushes.Black,
};
_text = new() {
Text = Label,
TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
SnapsToDevicePixels = true,
};
canvas.Children.Add(_circle);
canvas.Children.Add(_text);
}
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
Graph.SetCenter(_circle, CenterX, CenterY, DIAMETER, DIAMETER, scale);
_circle?.StrokeThickness = border;
_text?.Width = DIAMETER * scale;
_text?.Height = DIAMETER * scale;
_text?.FontSize = 2 * scale;
_text?.SetValue(Canvas.LeftProperty, (CenterX - DIAMETER / 2 + 0.1) * scale);
_text?.SetValue(Canvas.TopProperty, (CenterY - DIAMETER / 2 + 1.1) * scale);
}
public bool IsInside(double x, double y) {
return Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= DIAMETER / 2;
}
public void Update(bool isHovering, bool isPressed) {
_circle?.Fill =
!IsOpen ? PamhagenBrushes.Red :
IsOpen ? PamhagenBrushes.Green :
Highlight ?? Brushes.DarkGray;
}
}
}
@@ -0,0 +1,44 @@
using System.Windows;
using System.Windows.Media;
namespace PamhagenSysCtrl.Helpers.Pipeline {
public class ControlledValve : Valve {
public int Nr { get; set; }
public override string Label => $"V{Nr}";
public readonly ISet<Path> LockedForPaths = new HashSet<Path>();
public bool IsLocked => LockedForPaths.Any();
public bool WantOpen => CallbackWantOpen(this);
protected Func<Valve, bool> CallbackWantOpen;
public ControlledValve(int nr, double x, double y, int orientation, Func<bool> cbOpen, Func<Valve, bool> cbWant) :
base(x, y, orientation, cbOpen) {
Nr = nr;
CallbackWantOpen = cbWant;
}
public override void Update(bool isHovering, bool isPressed) {
if (App.Plant?.ManualControlMode ?? false) {
_text?.Visibility = Visibility.Visible;
_triangles?.Fill =
!WantOpen && !IsOpen && !isHovering ? PamhagenBrushes.Red :
!WantOpen && !IsOpen ? PamhagenBrushes.Green :
!WantOpen && IsOpen ? PamhagenBrushes.DimRed :
WantOpen && !IsOpen ? PamhagenBrushes.DimGreen :
WantOpen && IsOpen && !isHovering ? PamhagenBrushes.Green :
WantOpen && IsOpen ? PamhagenBrushes.Red :
Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray);
} else {
_text?.Visibility = isHovering ? Visibility.Visible : Visibility.Hidden;
_triangles?.Fill =
IsLocked && !WantOpen && !IsOpen ? PamhagenBrushes.DimOrange :
IsLocked && !WantOpen && IsOpen ? PamhagenBrushes.Red :
IsLocked && WantOpen && !IsOpen ? PamhagenBrushes.Yellow :
IsLocked && WantOpen && IsOpen ? PamhagenBrushes.Green :
Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray);
}
}
}
}
@@ -0,0 +1,22 @@
using System.Windows;
using System.Windows.Media;
namespace PamhagenSysCtrl.Helpers.Pipeline {
public class ExternalValve : Valve {
public override string Label { get; }
public ExternalValve(string label, double x, double y, int orientation, Func<bool> cbOpen) :
base(x, y, orientation, cbOpen) {
Label = label;
}
public override void Update(bool isHovering, bool isPressed) {
_text?.Visibility = isHovering || (App.Plant?.ManualControlMode ?? false) ? Visibility.Visible : Visibility.Hidden;
_triangles?.Fill =
!IsOpen ? PamhagenBrushes.Red :
IsOpen ? PamhagenBrushes.Green :
Highlight ?? Brushes.DarkGray;
}
}
}
+8 -6
View File
@@ -26,7 +26,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
protected Func<Pump, bool> CallbackSelected;
private Ellipse? _circle;
private Polygon? _triangle;
private Polyline? _triangle;
private TextBlock? _text;
public Pump(string label, double x, double y, Func<MotorState> cbState, Func<bool> cbClearance, Func<Pump, bool> cbSelected) {
@@ -45,9 +45,11 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
Fill = Brushes.WhiteSmoke,
Stroke = Brushes.Black,
};
_triangle = new Polygon() {
Fill = Brushes.Transparent,
_triangle = new Polyline() {
Stroke = Brushes.Black,
StrokeStartLineCap = PenLineCap.Square,
StrokeLineJoin = PenLineJoin.Round,
StrokeEndLineCap = PenLineCap.Square,
};
_text = new() {
Text = Label,
@@ -63,12 +65,12 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
var border = Graph.ToBorder(scale);
_circle?.StrokeThickness = border;
Graph.SetCenter(_circle, CenterX, CenterY, DIAMETER, DIAMETER, scale);
var dx = Math.Cos(Math.PI / 6) * (DIAMETER / 2);
var dy = Math.Sin(Math.PI / 6) * (DIAMETER / 2);
var dx = Math.Cos(Math.PI / 9.5) * (DIAMETER / 2);
var dy = Math.Sin(Math.PI / 9.5) * (DIAMETER / 2);
_triangle?.Points = [
Graph.ToPx(CenterX - dx, CenterY - dy, scale, dxpx: border, dypx: border / 2, round: false),
Graph.ToPx(CenterX + dx, CenterY - dy, scale, dxpx: -border, dypx: border / 2, round: false),
Graph.ToPx(CenterX, CenterY + DIAMETER / 2, scale, dypx: -border, round: false),
Graph.ToPx(CenterX + dx, CenterY - dy, scale, dxpx: -border, dypx: border / 2, round: false),
];
_triangle?.StrokeThickness = border;
_text?.FontSize = 14;
+88 -42
View File
@@ -4,88 +4,134 @@ using System.Windows.Media;
using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Valve : INode {
public abstract class Valve : INode {
public const double DIAMETER = 4.8;
public const double LENGTH = 4.8;
public const double WIDTH = 2.6;
public double CenterX { get; set; }
public double CenterY { get; set; }
public int Nr { get; set; }
public string Label => $"V{Nr}";
public abstract string Label { get; }
public Brush? Highlight { get; set; }
public readonly ISet<Path> LockedForPaths = new HashSet<Path>();
public int Orientation { get; set; }
public bool IsLocked => LockedForPaths.Any();
public bool IsOpen => CallbackIsOpen();
public bool WantOpen => CallbackWantOpen(this);
public ISet<IEdge> Inputs { get; init; }
public ISet<IEdge> Outputs { get; init; }
protected Func<bool> CallbackIsOpen;
protected Func<Valve, bool> CallbackWantOpen;
private Shape? _circle;
private TextBlock? _text;
protected Rectangle? _background;
protected Polygon? _triangles;
protected Polyline? _topLine;
protected Polyline? _bottomLine;
protected TextBlock? _text;
public Valve(int nr, double x, double y, Func<bool> cbOpen, Func<Valve, bool> cbWant) {
public Valve(double x, double y, int orientation, Func<bool> cbOpen) {
CenterX = x;
CenterY = y;
Nr = nr;
CallbackIsOpen = cbOpen;
CallbackWantOpen = cbWant;
Inputs = new HashSet<IEdge>();
Outputs = new HashSet<IEdge>();
Orientation = orientation;
}
public void Draw(Canvas canvas) {
_circle = new Ellipse() {
_background = new Rectangle() {
Fill = Brushes.White,
};
_triangles = new Polygon() {
Fill = Brushes.DarkGray,
Stroke = Brushes.Black,
};
_topLine = new Polyline() {
Stroke = Brushes.Black,
};
_bottomLine = new Polyline() {
Stroke = Brushes.Black,
};
_text = new() {
Text = Label,
TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
SnapsToDevicePixels = true,
};
canvas.Children.Add(_circle);
canvas.Children.Add(_background);
canvas.Children.Add(_triangles);
canvas.Children.Add(_topLine);
canvas.Children.Add(_bottomLine);
canvas.Children.Add(_text);
}
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
Graph.SetCenter(_circle, CenterX, CenterY, DIAMETER, DIAMETER, scale);
_circle?.StrokeThickness = border;
_text?.Width = DIAMETER * scale;
_text?.Height = DIAMETER * scale;
_text?.FontSize = 2 * scale;
_text?.SetValue(Canvas.LeftProperty, (CenterX - DIAMETER / 2 + 0.1) * scale);
_text?.SetValue(Canvas.TopProperty, (CenterY - DIAMETER / 2 + 1.1) * scale);
if (Orientation == 0 || Orientation == 2) {
Graph.SetCenter(_background, CenterX, CenterY, LENGTH / 2, LENGTH, scale);
_triangles?.Points = [
Graph.ToPx(CenterX - WIDTH / 2, CenterY - LENGTH / 2, scale),
Graph.ToPx(CenterX + WIDTH / 2, CenterY + LENGTH / 2, scale),
Graph.ToPx(CenterX - WIDTH / 2, CenterY + LENGTH / 2, scale),
Graph.ToPx(CenterX + WIDTH / 2, CenterY - LENGTH / 2, scale),
];
_topLine?.Points = [
Graph.ToPx(CenterX - WIDTH / 2, CenterY - LENGTH / 4, scale),
Graph.ToPx(CenterX - WIDTH / 4, CenterY, scale),
Graph.ToPx(CenterX - WIDTH / 2, CenterY + LENGTH / 4, scale),
];
_bottomLine?.Points = [
Graph.ToPx(CenterX + WIDTH / 2, CenterY - LENGTH / 4, scale),
Graph.ToPx(CenterX + WIDTH / 4, CenterY, scale),
Graph.ToPx(CenterX + WIDTH / 2, CenterY + LENGTH / 4, scale),
];
} else {
Graph.SetCenter(_background, CenterX, CenterY, LENGTH, LENGTH / 2, scale);
_triangles?.Points = [
Graph.ToPx(CenterX - LENGTH / 2, CenterY - WIDTH / 2, scale),
Graph.ToPx(CenterX + LENGTH / 2, CenterY + WIDTH / 2, scale),
Graph.ToPx(CenterX + LENGTH / 2, CenterY - WIDTH / 2, scale),
Graph.ToPx(CenterX - LENGTH / 2, CenterY + WIDTH / 2, scale),
];
_topLine?.Points = [
Graph.ToPx(CenterX - LENGTH / 4, CenterY - WIDTH / 2, scale),
Graph.ToPx(CenterX, CenterY - WIDTH / 4, scale),
Graph.ToPx(CenterX + LENGTH / 4, CenterY - WIDTH / 2, scale),
];
_bottomLine?.Points = [
Graph.ToPx(CenterX - LENGTH / 4, CenterY + WIDTH / 2, scale),
Graph.ToPx(CenterX, CenterY + WIDTH / 4, scale),
Graph.ToPx(CenterX + LENGTH / 4, CenterY + WIDTH / 2, scale),
];
}
_triangles?.StrokeThickness = border;
_topLine?.StrokeThickness = border;
_bottomLine?.StrokeThickness = border;
_text?.Width = 40;
_text?.Height = 16;
_text?.FontSize = 12;
_text?.TextAlignment = Orientation == 0 ? TextAlignment.Right : Orientation == 2 ? TextAlignment.Left : TextAlignment.Center;
if (Orientation == 0) {
_text?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 1.5) * scale - 40);
_text?.SetValue(Canvas.TopProperty, CenterY * scale - 8);
} else if (Orientation == 2) {
_text?.SetValue(Canvas.LeftProperty, (CenterX + WIDTH / 1.5) * scale);
_text?.SetValue(Canvas.TopProperty, CenterY * scale - 8 );
} else if (Orientation == 1) {
_text?.SetValue(Canvas.LeftProperty, CenterX * scale - 20);
_text?.SetValue(Canvas.TopProperty, (CenterY - WIDTH / 2) * scale - 16);
} else {
_text?.SetValue(Canvas.LeftProperty, CenterX * scale - 20);
_text?.SetValue(Canvas.TopProperty, (CenterY + WIDTH / 2) * scale);
}
}
public bool IsInside(double x, double y) {
return Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= DIAMETER / 2;
}
public void Update(bool isHovering, bool isPressed) {
if (App.Plant?.ManualControlMode ?? false) {
_circle?.Fill =
!WantOpen && !IsOpen && !isHovering ? PamhagenBrushes.Red :
!WantOpen && !IsOpen ? PamhagenBrushes.Green :
!WantOpen && IsOpen ? PamhagenBrushes.DimRed :
WantOpen && !IsOpen ? PamhagenBrushes.DimGreen :
WantOpen && IsOpen && !isHovering ? PamhagenBrushes.Green :
WantOpen && IsOpen ? PamhagenBrushes.Red :
Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray);
if (Orientation == 0 || Orientation == 2) {
return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= LENGTH / 2;
} else {
_circle?.Fill =
IsLocked && !WantOpen && !IsOpen ? PamhagenBrushes.DimOrange :
IsLocked && !WantOpen && IsOpen ? PamhagenBrushes.Red :
IsLocked && WantOpen && !IsOpen ? PamhagenBrushes.Yellow :
IsLocked && WantOpen && IsOpen ? PamhagenBrushes.Green :
Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray);
return Math.Abs(x - CenterX) <= LENGTH / 2 && Math.Abs(y - CenterY) <= WIDTH / 2;
}
}
public abstract void Update(bool isHovering, bool isPressed);
}
}
+4 -10
View File
@@ -10,16 +10,12 @@ namespace PamhagenSysCtrl.Helpers {
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) {
Serial = new SerialPort(portName, 9600, parity, 8, StopBits.One) {
Handshake = Handshake.None,
NewLine = "\r",
ReadTimeout = 500,
WriteTimeout = 500,
ReadTimeout = 1000,
WriteTimeout = 1000,
DtrEnable = true,
RtsEnable = true,
Encoding = Encoding.ASCII,
@@ -101,16 +97,14 @@ namespace PamhagenSysCtrl.Helpers {
public async Task<Status> StatusRead() {
await SendCommand("ST", checksum: false);
var (_, res) = await ReceiveResponse("ST");
LastStatus = new Status((Mode)(res[3] - '0'), ((res[0] - '0') << 4) | (res[1] - '0'));
return LastStatus;
return new Status((Mode)(res[3] - '0'), ((res[0] - '0') << 4) | (res[1] - '0'));
}
public async Task<T1Error> ErrorStatusRead() {
await SendCommand("ER", checksum: false);
var (_, res) = await ReceiveResponse("ER");
var code = Convert.ToInt32(res, 10);
LastError = Enum.IsDefined(typeof(T1Error), code) ? (T1Error)code : T1Error.UNKNOWN_ERROR;
return LastError;
return Enum.IsDefined(typeof(T1Error), code) ? (T1Error)code : T1Error.UNKNOWN_ERROR;
}
public Task<ushort[]> DataRead(string register, int length) {
Binary file not shown.
-3
View File
@@ -18,9 +18,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.11" />
<PackageReference Include="System.IO.Ports" Version="10.0.11" />
<Reference Include="PIEHidNetCore">
<HintPath>PIEHidNetCore.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
+1 -38
View File
@@ -227,44 +227,7 @@
</Grid>
<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>
</Grid>
</Window>
+40 -218
View File
@@ -1,6 +1,5 @@
using PamhagenSysCtrl.Helpers;
using PamhagenSysCtrl.Helpers.Pipeline;
using PIEHidNetCore;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
@@ -8,15 +7,10 @@ using System.Windows.Input;
using System.Windows.Media;
namespace PamhagenSysCtrl.Windows {
public partial class PlantSchemeWindow : Window, PIEDataHandler, PIEErrorHandler {
public partial class PlantSchemeWindow : Window {
private const double _minMove = 50;
private PIEDevice[]? devices;
private PIEDevice? xbe24;
private byte[]? lastData;
private Color[] lastColor = new Color[24];
private readonly PamhagenGraph Graph;
private Point? _lastMousePos;
@@ -51,198 +45,37 @@ namespace PamhagenSysCtrl.Windows {
}
}
protected Dictionary<Button, (int KeyIdx, ISource Source, Pump Pump, ISink Sink)> FastSelectPaths;
protected Dictionary<Button, (ISource Source, Pump Pump, ISink Sink)> FastSelectPaths;
public PlantSchemeWindow() {
InitializeComponent();
Menu_Help_CheckForUpdates.IsEnabled = App.Config.UpdateUrl != null;
Graph = new();
Graph.Draw(SchemeCanvas, RoundedScale);
FastSelectPaths = new Dictionary<Button, (int, ISource, Pump, ISink)> {
[FastSelectButton_MW1_Press1] = (12, Graph.MW1, Graph.MP1, Graph.Press1),
[FastSelectButton_MW2_Press1] = (18, Graph.MW2, Graph.MP2, Graph.Press1),
[FastSelectButton_MW1_Press2] = (13, Graph.MW1, Graph.MP1, Graph.Press2),
[FastSelectButton_MW2_Press2] = (19, Graph.MW2, Graph.MP2, Graph.Press2),
[FastSelectButton_MW1_Tank1] = (14, Graph.MW1, Graph.MP1, Graph.Tank1),
[FastSelectButton_MW2_Tank1] = (20, Graph.MW2, Graph.MP2, Graph.Tank1),
[FastSelectButton_MW1_Tank2] = (15, Graph.MW1, Graph.MP1, Graph.Tank2),
[FastSelectButton_MW2_Tank2] = (21, Graph.MW2, Graph.MP2, Graph.Tank2),
[FastSelectButton_MW1_Tank3] = (16, Graph.MW1, Graph.MP1, Graph.Tank3),
[FastSelectButton_MW2_Tank3] = (22, Graph.MW2, Graph.MP2, Graph.Tank3),
[FastSelectButton_MW1_Tank4] = (17, Graph.MW1, Graph.MP1, Graph.Tank4),
[FastSelectButton_MW2_Tank4] = (23, Graph.MW2, Graph.MP2, Graph.Tank4),
[FastSelectButton_MW1_Tank5] = (-1, Graph.MW1, Graph.MP1, Graph.Tank5),
[FastSelectButton_MW2_Tank5] = (-1, Graph.MW2, Graph.MP2, Graph.Tank5),
FastSelectPaths = new Dictionary<Button, (ISource, Pump, ISink)> {
[FastSelectButton_MW1_Press1] = (Graph.MW1, Graph.MP1, Graph.Press1),
[FastSelectButton_MW2_Press1] = (Graph.MW2, Graph.MP2, Graph.Press1),
[FastSelectButton_MW1_Press2] = (Graph.MW1, Graph.MP1, Graph.Press2),
[FastSelectButton_MW2_Press2] = (Graph.MW2, Graph.MP2, Graph.Press2),
[FastSelectButton_MW1_Tank1] = (Graph.MW1, Graph.MP1, Graph.Tank1),
[FastSelectButton_MW2_Tank1] = (Graph.MW2, Graph.MP2, Graph.Tank1),
[FastSelectButton_MW1_Tank2] = (Graph.MW1, Graph.MP1, Graph.Tank2),
[FastSelectButton_MW2_Tank2] = (Graph.MW2, Graph.MP2, Graph.Tank2),
[FastSelectButton_MW1_Tank3] = (Graph.MW1, Graph.MP1, Graph.Tank3),
[FastSelectButton_MW2_Tank3] = (Graph.MW2, Graph.MP2, Graph.Tank3),
[FastSelectButton_MW1_Tank4] = (Graph.MW1, Graph.MP1, Graph.Tank4),
[FastSelectButton_MW2_Tank4] = (Graph.MW2, Graph.MP2, Graph.Tank4),
[FastSelectButton_MW1_Tank5] = (Graph.MW1, Graph.MP1, Graph.Tank5),
[FastSelectButton_MW2_Tank5] = (Graph.MW2, Graph.MP2, Graph.Tank5),
};
App.Plant?.Update += OnUpdate;
InitializePaths();
devices = PIEDevice.EnumeratePIE();
if (devices.Length > 0) {
foreach (var device in devices) {
// XBE-24 uses the Consumer HID usage page.
if (device.HidUsagePage == 0x0C &&
device.WriteLength > 1) {
xbe24 = device;
break;
}
}
if (xbe24 == null) {
return;
}
// Open the HID interface.
xbe24.SetupInterface();
// Receive input reports.
xbe24.SetDataCallback(this);
// Receive device errors, e.g. unplugging.
xbe24.SetErrorCallback(this);
lastData = new byte[xbe24.ReadLength];
MessageBox.Show($"Connected: {xbe24.ProductString}");
}
}
private void OnClosed(object sender, EventArgs evt) {
xbe24?.CloseInterface();
xbe24 = null;
Application.Current.Shutdown();
}
public void HandlePIEHidData(byte[] data, PIEDevice sourceDevice, int error) {
if (xbe24 == null || sourceDevice != xbe24)
return;
Dispatcher.Invoke(() => {
ProcessXbe24Data(data);
});
}
public void HandlePIEHidError(PIEDevice sourceDevice, int error) {
if (xbe24 == null || sourceDevice != xbe24)
return;
Dispatcher.Invoke(() => {
MessageBox.Show($"XBE-24: {error}", "XBE-24", MessageBoxButton.OK, MessageBoxImage.Error);
});
}
private void ProcessXbe24Data(byte[] data) {
if (xbe24 == null || lastData == null)
return;
// XBE-24 button data starts at byte 3.
//
// There are 4 button bytes:
//
// byte 3 -> buttons 0-5
// byte 4 -> buttons 6-11
// byte 5 -> buttons 12-17
// byte 6 -> buttons 18-23
for (int column = 0; column < 4; column++) {
for (int row = 0; row < 6; row++) {
int button = column * 6 + row;
byte mask = (byte)(1 << row);
bool nowPressed =
(data[3 + column] & mask) != 0;
bool previouslyPressed =
(lastData[3 + column] & mask) != 0;
// Button was just pressed.
if (nowPressed && !previouslyPressed) {
OnButtonPressed(button);
}
// Button was just released.
if (!nowPressed && previouslyPressed) {
OnButtonReleased(button);
}
}
}
Array.Copy(data, lastData, xbe24.ReadLength);
}
private void OnButtonPressed(int button) {
// Your application logic goes here.
// 0 6 12 18
// 1 7 13 19
// 2 8 14 20
// 3 9 15 21
// 4 10 16 22
// 5 11 17 23
switch (button) {
case 2: FastSelectButton_EntryTroughStop_Click(null, null); break;
case 8: FastSelectButton_EntryTroughStart_Click(null, null); break;
case 3: FastSelectButton_MW2_Click(null, null); break;
case 9: FastSelectButton_MW1_Click(null, null); break;
case 4:
if (FastSelectButton_MP2_Forward.IsEnabled) {
FastSelectButton_MP2_Forward_Click(null, null);
} else {
SetXbeLight(4, Color.FromRgb(255, 0, 0));
}
break;
case 5: if (FastSelectButton_MP2_Stop.IsEnabled) FastSelectButton_MP2_Stop_Click(null, null); break;
case 10:
if (FastSelectButton_MP1_Forward.IsEnabled) {
FastSelectButton_MP1_Forward_Click(null, null);
} else {
SetXbeLight(10, Color.FromRgb(255, 0, 0));
}
break;
case 11: if (FastSelectButton_MP1_Stop.IsEnabled) FastSelectButton_MP1_Stop_Click(null, null); break;
case 12: FastSelectButton_Click(FastSelectButton_MW1_Press1, null); break;
case 13: FastSelectButton_Click(FastSelectButton_MW1_Press2, null); break;
case 14: FastSelectButton_Click(FastSelectButton_MW1_Tank1, null); break;
case 15: FastSelectButton_Click(FastSelectButton_MW1_Tank2, null); break;
case 16: FastSelectButton_Click(FastSelectButton_MW1_Tank3, null); break;
case 17: FastSelectButton_Click(FastSelectButton_MW1_Tank4, null); break;
case 18: FastSelectButton_Click(FastSelectButton_MW2_Press1, null); break;
case 19: FastSelectButton_Click(FastSelectButton_MW2_Press2, null); break;
case 20: FastSelectButton_Click(FastSelectButton_MW2_Tank1, null); break;
case 21: FastSelectButton_Click(FastSelectButton_MW2_Tank2, null); break;
case 22: FastSelectButton_Click(FastSelectButton_MW2_Tank3, null); break;
case 23: FastSelectButton_Click(FastSelectButton_MW2_Tank4, null); break;
}
}
private void OnButtonReleased(int button) {
// Your application logic goes here.
}
private void SetXbeLight(byte button, Color col, bool flashing = false) {
if (xbe24 == null || lastColor[button] == col)
return;
byte[] data = new byte[xbe24.WriteLength];
for (byte i = 0; i < 2; i++) {
data[0] = 0;
data[1] = 0xA5; // Set RGB LED
data[2] = button;
data[3] = i; // upper + lower LED
data[4] = col.R; // Red
data[5] = col.G; // Green
data[6] = col.B; // Blue
data[7] = flashing ? (byte)1 : (byte)0;
int result;
do {
result = xbe24.WriteData(data);
} while (result == 404);
}
lastColor[button] = col;
}
private void Menu_Settings_ManualControl_Changed(object sender, RoutedEventArgs evt) {
App.Plant?.ManualControlMode = Menu_Settings_ManualControl.IsChecked;
if (Menu_Settings_ManualControl.IsChecked) {
@@ -378,7 +211,7 @@ namespace PamhagenSysCtrl.Windows {
UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed);
}
private void FastSelectButton_Click(object sender, RoutedEventArgs? evt) {
private void FastSelectButton_Click(object sender, RoutedEventArgs evt) {
if (sender is not Button b || !FastSelectPaths.TryGetValue(b, out var val))
return;
@@ -516,7 +349,7 @@ namespace PamhagenSysCtrl.Windows {
}
foreach (var c in clicked) {
if (App.Plant?.ManualControlMode ?? false) {
if (c is Valve v) {
if (c is ControlledValve v) {
if (v.WantOpen) {
App.Plant.CloseV(v.Nr);
} else {
@@ -637,32 +470,28 @@ namespace PamhagenSysCtrl.Windows {
UpdateScheme(pos, evt.LeftButton == MouseButtonState.Pressed);
}
private void SetFastSelectButton(Button b, byte? keyIdx, int m, bool disabled = false) {
private static void SetFastSelectButton(Button b, int m, bool disabled = false) {
b.IsEnabled = !disabled;
if (m == 1) {
b.Background = Brushes.MintCream;
b.BorderBrush = Brushes.DarkGreen;
b.Foreground = Brushes.DarkGreen;
if (keyIdx is byte k) SetXbeLight(k, Color.FromRgb(0, 255, 0));
} else if (m == 2) {
b.Background = Brushes.MistyRose;
b.BorderBrush = Brushes.DarkRed;
b.Foreground = Brushes.DarkRed;
if (keyIdx is byte k) SetXbeLight(k, Color.FromRgb(255, 0, 0));
} else {
b.Background = Brushes.LightGray;
b.BorderBrush = Brushes.DimGray;
b.Foreground = Brushes.Black;
if (keyIdx is byte k) SetXbeLight(k, disabled ? Color.FromRgb(8, 8, 8) : Color.FromRgb(255, 64, 64));
}
}
private void UpdateFastSelectPathButton(IEnumerable<Path> paths, Path? hoverPathDeactivate, Button b, byte? keyIdx) {
private void UpdateFastSelectPathButton(IEnumerable<Path> paths, Path? hoverPathDeactivate, Button b) {
var e = FastSelectPaths[b];
SetFastSelectButton(b, keyIdx,
SetFastSelectButton(b,
paths.Any(p => p.Start == e.Source && p.Hops.Last().Node == e.Sink) ? (hoverPathDeactivate?.Start == e.Source && hoverPathDeactivate?.Hops.Last().Node == e.Sink) ? 2 : 1 : 0,
e.Pump.IsActive || Graph.GetFreePath([e.Source, e.Sink], overrideStart: true) == null);
}
private ISet<INode> UpdateScheme(Point pos, bool down) {
@@ -678,16 +507,17 @@ namespace PamhagenSysCtrl.Windows {
}
}
SetFastSelectButton(FastSelectButton_EntryTrough_Start, 8, App.Plant?.IsTroughAugerActive ?? false ? 1 : 0, !(App.Plant?.TroughAugerHasClearance ?? false));
SetFastSelectButton(FastSelectButton_EntryTrough_Stop, 2, App.Plant?.IsTroughAugerActive ?? false ? 0 : 2);
SetFastSelectButton(FastSelectButton_MW1, 9, App.Plant?.IsMW1Selected ?? false ? 1 : 0);
SetFastSelectButton(FastSelectButton_MW2, 3, App.Plant?.IsMW2Selected ?? false ? 1 : 0);
SetFastSelectButton(FastSelectButton_MP1_Forward, 10, App.Plant?.MP1 == MotorState.Forward ? 1 : 0, !(App.Plant?.MP1HasClearance ?? false));
SetFastSelectButton(FastSelectButton_MP1_Stop, 11, App.Plant?.MP1 == MotorState.Halt ? 2 : 0);
SetFastSelectButton(FastSelectButton_MP2_Forward, 4, App.Plant?.MP2 == MotorState.Forward ? 1 : 0, !(App.Plant?.MP2HasClearance ?? false));
SetFastSelectButton(FastSelectButton_MP2_Stop, 5, App.Plant?.MP2 == MotorState.Halt ? 2 : 0);
SetFastSelectButton(FastSelectButton_EntryTrough_Start, App.Plant?.IsTroughAugerActive ?? false ? 1 : 0, !(App.Plant?.TroughAugerHasClearance ?? false));
SetFastSelectButton(FastSelectButton_EntryTrough_Stop, App.Plant?.IsTroughAugerActive ?? false ? 0 : 2);
SetFastSelectButton(FastSelectButton_MW1, App.Plant?.IsMW1Selected ?? false ? 1 : 0);
SetFastSelectButton(FastSelectButton_MW2, App.Plant?.IsMW2Selected ?? false ? 1 : 0);
SetFastSelectButton(FastSelectButton_MP1_Forward, App.Plant?.MP1 == MotorState.Forward ? 1 : 0, !(App.Plant?.MP1HasClearance ?? false));
SetFastSelectButton(FastSelectButton_MP1_Stop, App.Plant?.MP1 == MotorState.Halt ? 2 : 0);
SetFastSelectButton(FastSelectButton_MP2_Forward, App.Plant?.MP2 == MotorState.Forward ? 1 : 0, !(App.Plant?.MP2HasClearance ?? false));
SetFastSelectButton(FastSelectButton_MP2_Stop, App.Plant?.MP2 == MotorState.Halt ? 2 : 0);
foreach (var b in FastSelectPaths) {
SetFastSelectButton(b.Key, 0, false);
}
_hover = Graph.GetHover(pos.X, pos.Y, RoundedScale);
var hoverButtons = FastSelectPaths.Where(b => b.Key.IsMouseOver).ToDictionary();
@@ -736,14 +566,14 @@ namespace PamhagenSysCtrl.Windows {
if (Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == Graph.MW1) is Path pmw1) {
MP1_Target.Text = "\u2b9e " + string.Join(" \u2b9e ", pmw1.Hops
.Select(h => h.Node is Valve || h.Node is Pump || h.Node is CenterValve ? null : h.Node.Label)
.Select(h => h.Node is Valve || h.Node is Pump ? null : h.Node.Label)
.Where(l => !string.IsNullOrWhiteSpace(l)));
} else {
MP1_Target.Text = "Vor";
}
if (Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == Graph.MW2) is Path pmw2) {
MP2_Target.Text = "\u2b9e " + string.Join(" \u2b9e ", pmw2.Hops
.Select(h => h.Node is Valve || h.Node is Pump || h.Node is CenterValve ? null : h.Node.Label)
.Select(h => h.Node is Valve || h.Node is Pump ? null : h.Node.Label)
.Where(l => !string.IsNullOrWhiteSpace(l)));
} else {
MP2_Target.Text = "Vor";
@@ -754,13 +584,10 @@ namespace PamhagenSysCtrl.Windows {
MP2_Target.FontSize = len2 > 24 ? 8 : len2 > 20 ? 10 : len2 > 16 ? 12 : len2 > 12 ? 14 : 16;
}
foreach (var b in FastSelectPaths) {
//SetFastSelectButton(b.Key, b.Value.KeyIdx == -1 ? null : (byte)b.Value.KeyIdx, 0, false);
}
var paths = Graph.SelectedPaths;
if (HoveringPath is Path hp) paths = paths.Append(hp);
foreach (var b in FastSelectPaths) {
UpdateFastSelectPathButton(paths, hoverPathDeactivate, b.Key, b.Value.KeyIdx == -1 ? null : (byte)b.Value.KeyIdx);
foreach (var b in FastSelectPaths.Keys) {
UpdateFastSelectPathButton(paths, hoverPathDeactivate, b);
}
Graph.Update(_hover, down);
@@ -768,12 +595,7 @@ namespace PamhagenSysCtrl.Windows {
}
public void OnUpdate(object? sender, PlantEventArgs? evt) {
if (App.Plant is PamhagenPlant p) {
Status_Connection.Text = $"Ja ({p.PlcPortName}" + (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.IsValveClosed) ? "Ein" : Enumerable.Range(1, 57).Any(p.WantValveClosed) ? "Aus" : "?";
Status_ValvesOpen.Text = Enumerable.Range(1, 57).Count(p.IsValveOpen) + " / " + Enumerable.Range(1, 57).Count(p.WantValveOpen) + " / 57";
}
if (sender is not PamhagenPlant plant) return;
UpdateScheme(Mouse.GetPosition(SchemeCanvas), Mouse.LeftButton == MouseButtonState.Pressed);
}
@@ -785,7 +607,7 @@ namespace PamhagenSysCtrl.Windows {
App.Plant?.CloseV(i);
}
foreach (var p in add) {
if (p.Hops.Any(h => h.Node is Valve v && v.IsLocked)) continue;
if (p.Hops.Any(h => h.Node is ControlledValve v && v.IsLocked)) continue;
Graph.AddPath(p);
}
}
@@ -797,7 +619,7 @@ namespace PamhagenSysCtrl.Windows {
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 Valve v || p.WantValveOpen(v.Nr))));
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();