Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2b755db44 |
No files matched your search
@@ -36,11 +36,15 @@ namespace PamhagenSysCtrl {
|
||||
protected override async void OnStartup(StartupEventArgs evt) {
|
||||
Version = new Version(typeof(App).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Split('+')[0] ?? "0.0.0");
|
||||
|
||||
Plant = new(Config.PlcPort);
|
||||
try {
|
||||
Plant = new(Config.PlcPort);
|
||||
await Plant.StartThread();
|
||||
} catch (Exception exc) {
|
||||
// TODO
|
||||
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);
|
||||
|
||||
@@ -512,6 +512,7 @@ namespace PamhagenSysCtrl.Helpers {
|
||||
HashSet<IEdge> visited = [];
|
||||
foreach (var (edge, node) in path.Hops) {
|
||||
edge.Highlight = color1;
|
||||
(edge.Start as PipeJoin)?.Highlight = color1;
|
||||
if (flowArrows > MotorState.Halt) {
|
||||
var dir = edge.End == node;
|
||||
if (flowArrows == MotorState.Backward) dir = !dir;
|
||||
@@ -520,7 +521,10 @@ namespace PamhagenSysCtrl.Helpers {
|
||||
}
|
||||
visited.Add(edge);
|
||||
if (node is Pump) color = color2;
|
||||
TraverseSubgraph(node, (n) => n is Valve || n is ISink || n is Pump, visited, edgeAction: (s, r, e) => e.Highlight = color);
|
||||
TraverseSubgraph(node, (n) => n is Valve || n is ISink || n is Pump, visited, edgeAction: (s, r, e) => {
|
||||
(s as PipeJoin)?.Highlight = color;
|
||||
e.Highlight = color;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,6 +534,8 @@ namespace PamhagenSysCtrl.Helpers {
|
||||
edgeAction: (s, r, e) => {
|
||||
if (((e.Start as Valve)?.IsOpen ?? true) && ((e.End as Valve)?.IsOpen ?? true)) {
|
||||
e.Highlight = color1;
|
||||
(e.Start as PipeJoin)?.Highlight = color1;
|
||||
(e.End as PipeJoin)?.Highlight = color1;
|
||||
if (flowArrows == MotorState.Forward || flowArrows == MotorState.Backward) {
|
||||
var dir = e.Start == s;
|
||||
if (r) dir = !dir;
|
||||
|
||||
@@ -1,35 +1,10 @@
|
||||
using PamhagenSysCtrl.Helpers.Pipeline;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace PamhagenSysCtrl.Helpers {
|
||||
public class PamhagenPlant : IDisposable {
|
||||
|
||||
public enum Xbe24Key {
|
||||
START_REBLER = 6, STOP_REBLER = 7,
|
||||
START_TROUGH_AUGER = 12, STOP_TROUGH_AUGER = 13,
|
||||
SELECT_MW1 = 14, SELECT_MW2 = 8,
|
||||
STIRRER_MW1 = 15, STIRRER_MW2 = 9,
|
||||
START_MP1 = 16, STOP_MP1 = 17,
|
||||
START_MP2 = 10, STOP_MP2 = 11,
|
||||
FAST_SELECT_MW1_1 = 18,
|
||||
FAST_SELECT_MW1_2 = 19,
|
||||
FAST_SELECT_MW1_3 = 20,
|
||||
FAST_SELECT_MW1_4 = 21,
|
||||
FAST_SELECT_MW1_5 = 22,
|
||||
FAST_SELECT_MW1_6 = 23,
|
||||
FAST_SELECT_MW2_1 = 0,
|
||||
FAST_SELECT_MW2_2 = 1,
|
||||
FAST_SELECT_MW2_3 = 2,
|
||||
FAST_SELECT_MW2_4 = 3,
|
||||
FAST_SELECT_MW2_5 = 4,
|
||||
FAST_SELECT_MW2_6 = 5,
|
||||
}
|
||||
|
||||
protected PamhagenPlc? Plc;
|
||||
protected Xbe24Keyboard? Keyboard;
|
||||
public PamhagenGraph Graph;
|
||||
|
||||
protected bool IsRunning = true;
|
||||
protected Thread? BackgroundThread;
|
||||
@@ -46,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);
|
||||
@@ -103,25 +73,8 @@ namespace PamhagenSysCtrl.Helpers {
|
||||
public bool IsP6Active => Actuators.P6 == MotorState.Forward;
|
||||
|
||||
public PamhagenPlant(string portName) {
|
||||
try {
|
||||
Plc = new(portName);
|
||||
} catch (Exception exc) {
|
||||
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);
|
||||
}
|
||||
Graph = new();
|
||||
|
||||
try {
|
||||
Keyboard = new();
|
||||
Keyboard.KeyDown += Keyboard_KeyDown;
|
||||
Keyboard.KeysChanged += Keyboard_KeysChanged;
|
||||
Keyboard.SetFashFrequency(250);
|
||||
Keyboard.SetBacklight(Color.FromRgb(4, 4, 4));
|
||||
} catch { }
|
||||
|
||||
Plc = new(portName);
|
||||
Update += OnUpdate;
|
||||
Update += UpdateKeyboardBacklights;
|
||||
}
|
||||
|
||||
protected virtual void RaiseUpdateEvent(PlantEventArgs evt) {
|
||||
@@ -138,10 +91,6 @@ namespace PamhagenSysCtrl.Helpers {
|
||||
if (p.IsAuger2Active && !p.IsAuger3Active) p.StartAuger3();
|
||||
if (p.IsTroughAugerActive && !p.TroughAugerHasClearance)
|
||||
p.StopTroughAuger();
|
||||
if (!p.IsPresseQuerSchneckeActive && !p.IsAuger1Active) {
|
||||
p.StopAuger2();
|
||||
p.StopAuger3();
|
||||
}
|
||||
}
|
||||
|
||||
if (p.IsMP1Active && !p.MP1HasClearance) p.StopMP1();
|
||||
@@ -167,66 +116,6 @@ namespace PamhagenSysCtrl.Helpers {
|
||||
if (p.IsP6Active && !p.P6HasClearance) p.StopP6();
|
||||
}
|
||||
|
||||
protected void UpdateKeyboardBacklights(object? sender, PlantEventArgs evt) {
|
||||
if (sender is not PamhagenPlant plant || plant.Keyboard is not Xbe24Keyboard k) return;
|
||||
|
||||
k.SetBacklight((int)Xbe24Key.START_REBLER, plant.IsReblerActive ? Color.FromRgb(0, 255, 0) : plant.IsAuger1Active ? Color.FromRgb(255, 255, 0) : plant.IsAuger2Active || plant.IsAuger3Active ? Color.FromRgb(32, 32, 4) : Color.FromRgb(4, 32, 4));
|
||||
k.SetBacklight((int)Xbe24Key.STOP_REBLER, !plant.IsReblerActive ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4));
|
||||
|
||||
k.SetBacklight((int)Xbe24Key.START_TROUGH_AUGER, !plant.TroughAugerHasClearance ? k.IsKeyPressed((int)Xbe24Key.START_TROUGH_AUGER) ? Color.FromRgb(255, 32, 0) : Color.FromRgb(32, 4, 4) : plant.IsTroughAugerActive ? Color.FromRgb(0, 255, 0) : Color.FromRgb(4, 32, 4));
|
||||
k.SetBacklight((int)Xbe24Key.STOP_TROUGH_AUGER, !plant.IsTroughAugerActive ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4));
|
||||
|
||||
k.SetBacklight((int)Xbe24Key.SELECT_MW2, plant.WantMW2Selected || plant.IsMW2Selected ? Color.FromRgb(0, 0, 255) : Color.FromRgb(4, 4, 16), plant.WantMW2Selected && !plant.IsMW2Selected);
|
||||
k.SetBacklight((int)Xbe24Key.SELECT_MW1, plant.WantMW1Selected || plant.IsMW1Selected ? Color.FromRgb(0, 0, 255) : Color.FromRgb(4, 4, 16), plant.WantMW1Selected && !plant.IsMW1Selected);
|
||||
|
||||
var buttons = new Dictionary<Xbe24Key, (ISource Source, Pump Pump, ISink Sink)> {
|
||||
[Xbe24Key.FAST_SELECT_MW1_1] = (Graph.MW1, Graph.MP1, Graph.Press1),
|
||||
[Xbe24Key.FAST_SELECT_MW2_1] = (Graph.MW2, Graph.MP2, Graph.Press1),
|
||||
[Xbe24Key.FAST_SELECT_MW1_2] = (Graph.MW1, Graph.MP1, Graph.Press2),
|
||||
[Xbe24Key.FAST_SELECT_MW2_2] = (Graph.MW2, Graph.MP2, Graph.Press2),
|
||||
[Xbe24Key.FAST_SELECT_MW1_3] = (Graph.MW1, Graph.MP1, Graph.Tank1),
|
||||
[Xbe24Key.FAST_SELECT_MW2_3] = (Graph.MW2, Graph.MP2, Graph.Tank1),
|
||||
[Xbe24Key.FAST_SELECT_MW1_4] = (Graph.MW1, Graph.MP1, Graph.Tank2),
|
||||
[Xbe24Key.FAST_SELECT_MW2_4] = (Graph.MW2, Graph.MP2, Graph.Tank2),
|
||||
[Xbe24Key.FAST_SELECT_MW1_5] = (Graph.MW1, Graph.MP1, Graph.Tank3),
|
||||
[Xbe24Key.FAST_SELECT_MW2_5] = (Graph.MW2, Graph.MP2, Graph.Tank3),
|
||||
[Xbe24Key.FAST_SELECT_MW1_6] = (Graph.MW1, Graph.MP1, Graph.Tank4),
|
||||
[Xbe24Key.FAST_SELECT_MW2_6] = (Graph.MW2, Graph.MP2, Graph.Tank4),
|
||||
};
|
||||
|
||||
bool preparingMP1 = false, preparingMP2 = false;
|
||||
bool highlightOffMP1 = false, highlightOffMP2 = false;
|
||||
foreach (var (idx, d) in buttons) {
|
||||
var isRunning = d.Pump.IsActive;
|
||||
if (plant.Graph.SelectedPaths.Select(p => (Pipeline.Path?)p).FirstOrDefault(p => p?.Start == d.Source && p?.Hops.Last().Node == d.Sink) is Pipeline.Path path) {
|
||||
// TODO
|
||||
var isReady = path.Hops.All(h => h.Node is not ControlledValve v || v.IsOpen == v.WantOpen);
|
||||
if (!isReady && d.Pump == Graph.MP1) preparingMP1 = true;
|
||||
if (!isReady && d.Pump == Graph.MP2) preparingMP2 = true;
|
||||
var isDefault = !path.Hops.SkipLast(1).Any(h => h.Node is not Valve && h.Node is not PipeJoin && h.Node is not Pump);
|
||||
k.SetBacklight((int)idx, isRunning ? isDefault ? Color.FromRgb(0, 255, 255) : Color.FromRgb(255, 64, 255) : isDefault ? Color.FromRgb(0, 0, 255) : Color.FromRgb(128, 0, 128), !isReady);
|
||||
} else {
|
||||
var isFree = Graph.GetFreePath([d.Source, d.Sink], overrideStart: true) != null;
|
||||
if (isRunning && k.IsKeyPressed((int)idx) && d.Pump == Graph.MP1) highlightOffMP1 = true;
|
||||
if (isRunning && k.IsKeyPressed((int)idx) && d.Pump == Graph.MP2) highlightOffMP2 = true;
|
||||
k.SetBacklight((int)idx, isRunning ? k.IsKeyPressed((int)idx) ? Color.FromRgb(255, 16, 0) : Color.FromRgb(4, 4, 4) : isFree ? Color.FromRgb(8, 8, 32) : k.IsKeyPressed((int)idx) ? Color.FromRgb(255, 32, 0) : Color.FromRgb(4, 4, 4));
|
||||
}
|
||||
}
|
||||
|
||||
if (!plant.MP2HasPathClearance && plant.MP2HasDryRunClearance && preparingMP2) {
|
||||
k.SetBacklight((int)Xbe24Key.START_MP2, Color.FromRgb(4, 32, 4), true);
|
||||
} else {
|
||||
k.SetBacklight((int)Xbe24Key.START_MP2, !plant.MP2HasClearance ? k.IsKeyPressed((int)Xbe24Key.START_MP2) ? Color.FromRgb(255, 32, 0) : Color.FromRgb(32, 4, 4) : plant.IsMP2Active ? Color.FromRgb(0, 255, 0) : Color.FromRgb(4, 32, 4));
|
||||
}
|
||||
if (!plant.MP1HasPathClearance && plant.MP1HasDryRunClearance && preparingMP1) {
|
||||
k.SetBacklight((int)Xbe24Key.START_MP1, Color.FromRgb(4, 32, 4), true);
|
||||
} else {
|
||||
k.SetBacklight((int)Xbe24Key.START_MP1, !plant.MP1HasClearance ? k.IsKeyPressed((int)Xbe24Key.START_MP1) ? Color.FromRgb(255, 32, 0) : Color.FromRgb(32, 4, 4) : plant.IsMP1Active ? Color.FromRgb(0, 255, 0) : Color.FromRgb(4, 32, 4));
|
||||
}
|
||||
k.SetBacklight((int)Xbe24Key.STOP_MP2, highlightOffMP2 ? Color.FromRgb(255, 32, 0) : !plant.IsMP2Active ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4));
|
||||
k.SetBacklight((int)Xbe24Key.STOP_MP1, highlightOffMP1 ? Color.FromRgb(255, 32, 0) : !plant.IsMP1Active ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4));
|
||||
}
|
||||
|
||||
public async Task StartThread() {
|
||||
if (Plc == null) throw new Exception();
|
||||
await Plc.TestConnection();
|
||||
@@ -239,23 +128,19 @@ namespace PamhagenSysCtrl.Helpers {
|
||||
IsRunning = false;
|
||||
BackgroundThread?.Join();
|
||||
Plc?.Dispose();
|
||||
Keyboard?.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -278,115 +163,6 @@ namespace PamhagenSysCtrl.Helpers {
|
||||
}
|
||||
}
|
||||
|
||||
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<Pipeline.Path> ReconstructSelectedPaths() {
|
||||
if (App.Plant is not PamhagenPlant p) yield break;
|
||||
var sources = Graph.Nodes.Where(n => n is ISource).ToList();
|
||||
var sinks = Graph.Nodes.Where(n => n is ISink).ToList();
|
||||
foreach (var src in sources) {
|
||||
var paths = new List<Pipeline.Path>();
|
||||
foreach (var sink in sinks) {
|
||||
paths.AddRange(Graph.GetPaths(src, sink, null, (n) => n is not ISink && (n is not ControlledValve v || p.WantValveOpen(v.Nr))));
|
||||
}
|
||||
if (paths.Count == 1)
|
||||
yield return paths.First();
|
||||
}
|
||||
}
|
||||
|
||||
public void FastSelect(ISource source, ISink sink) {
|
||||
if (Graph.SelectedPaths.Select(p => (Pipeline.Path?)p).FirstOrDefault(p => p?.Start == source, null) is Pipeline.Path p1) {
|
||||
if (p1.Hops.Any(h => h.Node is Pump p && p.IsActive))
|
||||
throw new NoClearanceException("Pumpe aktiv");
|
||||
Graph.RemovePath(p1);
|
||||
if (p1.Hops.Last().Node == sink)
|
||||
return;
|
||||
}
|
||||
if (Graph.GetFreePath([source, sink]) is Pipeline.Path p2) {
|
||||
Graph.AddPath(p2);
|
||||
}
|
||||
}
|
||||
|
||||
private void Keyboard_KeyDown(object? sender, int keyIndex) {
|
||||
var key = (Xbe24Key)keyIndex;
|
||||
App.MainDispatcher?.BeginInvoke(() => {
|
||||
try {
|
||||
if (key == Xbe24Key.START_REBLER) {
|
||||
StartRebler();
|
||||
} else if (key == Xbe24Key.STOP_REBLER) {
|
||||
if (IsReblerActive) {
|
||||
StopRebler();
|
||||
} else {
|
||||
StopAuger1();
|
||||
if (!IsPresseQuerSchneckeActive) {
|
||||
StopAuger2();
|
||||
StopAuger3();
|
||||
}
|
||||
}
|
||||
} else if (key == Xbe24Key.START_TROUGH_AUGER) {
|
||||
StartTroughAuger();
|
||||
} else if (key == Xbe24Key.STOP_TROUGH_AUGER) {
|
||||
StopTroughAuger();
|
||||
} else if (key == Xbe24Key.SELECT_MW2) {
|
||||
SelectMW2();
|
||||
} else if (key == Xbe24Key.SELECT_MW1) {
|
||||
SelectMW1();
|
||||
} else if (key == Xbe24Key.START_MP2) {
|
||||
StartMP2Forward();
|
||||
} else if (key == Xbe24Key.STOP_MP2) {
|
||||
StopMP2();
|
||||
} else if (key == Xbe24Key.START_MP1) {
|
||||
StartMP1Forward();
|
||||
} else if (key == Xbe24Key.STOP_MP1) {
|
||||
StopMP1();
|
||||
} else if (key == Xbe24Key.FAST_SELECT_MW1_1) {
|
||||
FastSelect(Graph.MW1, Graph.Press1);
|
||||
} else if (key == Xbe24Key.FAST_SELECT_MW1_2) {
|
||||
FastSelect(Graph.MW1, Graph.Press2);
|
||||
} else if (key == Xbe24Key.FAST_SELECT_MW1_3) {
|
||||
FastSelect(Graph.MW1, Graph.Tank1);
|
||||
} else if (key == Xbe24Key.FAST_SELECT_MW1_4) {
|
||||
FastSelect(Graph.MW1, Graph.Tank2);
|
||||
} else if (key == Xbe24Key.FAST_SELECT_MW1_5) {
|
||||
FastSelect(Graph.MW1, Graph.Tank3);
|
||||
} else if (key == Xbe24Key.FAST_SELECT_MW1_6) {
|
||||
FastSelect(Graph.MW1, Graph.Tank4);
|
||||
} else if (key == Xbe24Key.FAST_SELECT_MW2_1) {
|
||||
FastSelect(Graph.MW2, Graph.Press1);
|
||||
} else if (key == Xbe24Key.FAST_SELECT_MW2_2) {
|
||||
FastSelect(Graph.MW2, Graph.Press2);
|
||||
} else if (key == Xbe24Key.FAST_SELECT_MW2_3) {
|
||||
FastSelect(Graph.MW2, Graph.Tank1);
|
||||
} else if (key == Xbe24Key.FAST_SELECT_MW2_4) {
|
||||
FastSelect(Graph.MW2, Graph.Tank2);
|
||||
} else if (key == Xbe24Key.FAST_SELECT_MW2_5) {
|
||||
FastSelect(Graph.MW2, Graph.Tank3);
|
||||
} else if (key == Xbe24Key.FAST_SELECT_MW2_6) {
|
||||
FastSelect(Graph.MW2, Graph.Tank4);
|
||||
}
|
||||
} catch (NoClearanceException) {
|
||||
// Keyboard?.SetBacklight(keyIndex, Color.FromRgb(255, 0, 0));
|
||||
} catch (Exception) {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void Keyboard_KeysChanged(object? sender, Xbe24Keyboard.KeyState state) {
|
||||
RaiseUpdateEvent(new());
|
||||
}
|
||||
|
||||
protected bool HasWTClearanceAA =>
|
||||
(AreValvesOpen(12) && AreValvesClosed(15, 16, 20)) || // bypass
|
||||
(AreValvesOpen(15, 18, 16) && AreValvesClosed(11, 12, 14, 17, 19, 20)) || // WT1
|
||||
@@ -457,9 +233,7 @@ namespace PamhagenSysCtrl.Helpers {
|
||||
(a && ((HasWTClearanceAA && HasT9ClearanceA) || (HasWTClearanceAB && HasT9ClearanceB))) ||
|
||||
(b && ((HasWTClearanceBA && HasT9ClearanceA) || (HasWTClearanceBB && HasT9ClearanceB)));
|
||||
|
||||
public bool MP1HasClearance => MP1HasDryRunClearance && MP1HasPathClearance;
|
||||
public bool MP1HasDryRunClearance => (OverrideDryRunClearances || FillLevelMW1 != FillState.Empty);
|
||||
public bool MP1HasPathClearance => (
|
||||
public bool MP1HasClearance => (OverrideDryRunClearances || FillLevelMW1 != FillState.Empty) && (
|
||||
OverridePathClearances ||
|
||||
(MP1HasClearancePress1 && Press1HasClearance) ||
|
||||
(MP1HasClearancePress2 && Press2HasClearance) ||
|
||||
@@ -487,9 +261,7 @@ namespace PamhagenSysCtrl.Helpers {
|
||||
set => Actuators.MP1 = (value != MotorState.Halt && value != MotorState.Forward && value != MotorState.Backward) ? throw new ArgumentException("Invalid state for MP1") : value;
|
||||
}
|
||||
|
||||
public bool MP2HasClearance => MP2HasDryRunClearance && MP2HasPathClearance;
|
||||
public bool MP2HasDryRunClearance => (OverrideDryRunClearances || FillLevelMW2 != FillState.Empty);
|
||||
public bool MP2HasPathClearance => (
|
||||
public bool MP2HasClearance => (OverrideDryRunClearances || FillLevelMW2 != FillState.Empty) && (
|
||||
OverridePathClearances ||
|
||||
(MP2HasClearancePress1 && Press1HasClearance) ||
|
||||
(MP2HasClearancePress2 && Press2HasClearance) ||
|
||||
|
||||
@@ -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,7 +1,6 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace PamhagenSysCtrl.Helpers.Pipeline {
|
||||
public class PipeJoin : INode {
|
||||
@@ -12,11 +11,12 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
|
||||
public double CenterX { get; set; }
|
||||
public double CenterY { get; set; }
|
||||
public string Label => "";
|
||||
public Brush? Highlight { get; set; }
|
||||
|
||||
public ISet<IEdge> Inputs { get; init; }
|
||||
public ISet<IEdge> Outputs { get; init; }
|
||||
|
||||
private Polygon? _rect;
|
||||
private System.Windows.Shapes.Path? _rect;
|
||||
|
||||
public PipeJoin(double x, double y) {
|
||||
CenterX = x;
|
||||
@@ -26,8 +26,8 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
|
||||
}
|
||||
|
||||
public void Draw(Canvas canvas) {
|
||||
_rect = new Polygon() {
|
||||
Fill = Brushes.Black,
|
||||
_rect = new System.Windows.Shapes.Path() {
|
||||
Fill = Brushes.DarkGray,
|
||||
Stroke = Brushes.Black,
|
||||
};
|
||||
canvas.Children.Add(_rect);
|
||||
@@ -43,43 +43,66 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
|
||||
var il = Inputs.Any(e => (e.Start.CenterX < CenterX && (e.Orientation || e.Start.CenterY == CenterY)) || (e.IsTwoWay && e.End.CenterX < CenterX && (e.Orientation || e.End.CenterY == CenterY)));
|
||||
var ol = Outputs.Any(e => (e.End.CenterX < CenterX && (!e.Orientation || e.End.CenterY == CenterY)) || (e.IsTwoWay && e.Start.CenterX < CenterX && (!e.Orientation || e.Start.CenterY == CenterY)));
|
||||
|
||||
var points = new List<Point> {
|
||||
new(0, 0)
|
||||
};
|
||||
if (ot && it) {
|
||||
points.AddRange([new(SIZE / 2 - POINT, 0), new(SIZE / 2, -POINT / 2), new(SIZE / 2, POINT / 2), new(SIZE / 2 + POINT, 0)]);
|
||||
} else if (ot) {
|
||||
points.AddRange([new(SIZE / 2 - POINT, 0), new(SIZE / 2, -POINT), new(SIZE / 2 + POINT, 0)]);
|
||||
} else if (it) {
|
||||
points.AddRange([new(SIZE / 2 - POINT, 0), new(SIZE / 2, POINT), new(SIZE / 2 + POINT, 0)]);
|
||||
}
|
||||
points.Add(new(SIZE, 0));
|
||||
if (or && ir) {
|
||||
points.AddRange([new(SIZE, SIZE / 2 - POINT), new(SIZE + POINT / 2, SIZE / 2), new(SIZE - POINT / 2, SIZE / 2), new(SIZE, SIZE / 2 + POINT)]);
|
||||
} else if (or) {
|
||||
points.AddRange([new(SIZE, SIZE / 2 - POINT), new(SIZE + POINT, SIZE / 2), new(SIZE, SIZE / 2 + POINT)]);
|
||||
} else if (ir) {
|
||||
points.AddRange([new(SIZE, SIZE / 2 - POINT), new(SIZE - POINT, SIZE / 2), new(SIZE, SIZE / 2 + POINT)]);
|
||||
}
|
||||
points.Add(new(SIZE, SIZE));
|
||||
if (ob && ib) {
|
||||
points.AddRange([new(SIZE / 2 + POINT, SIZE), new(SIZE / 2, SIZE + POINT / 2), new(SIZE / 2, SIZE - POINT / 2), new(SIZE / 2 - POINT, SIZE)]);
|
||||
} else if (ob) {
|
||||
points.AddRange([new(SIZE / 2 + POINT, SIZE), new(SIZE / 2, SIZE + POINT), new(SIZE / 2 - POINT, SIZE)]);
|
||||
} else if (ib) {
|
||||
points.AddRange([new(SIZE / 2 + POINT, SIZE), new(SIZE / 2, SIZE - POINT), new(SIZE / 2 - POINT, SIZE)]);
|
||||
}
|
||||
points.Add(new(0, SIZE));
|
||||
if (ol && il) {
|
||||
points.AddRange([new(0, SIZE / 2 + POINT), new(-POINT / 2, SIZE / 2), new(POINT / 2, SIZE / 2), new(0, SIZE / 2 - POINT)]);
|
||||
} else if (ol) {
|
||||
points.AddRange([new(0, SIZE / 2 + POINT), new(-POINT, SIZE / 2), new(0, SIZE / 2 - POINT)]);
|
||||
} else if (il) {
|
||||
points.AddRange([new(0, SIZE / 2 + POINT), new(POINT, SIZE / 2), new(0, SIZE / 2 - POINT)]);
|
||||
var size = new Size(SIZE / 2 * scale, SIZE / 2 * scale);
|
||||
var geo = new StreamGeometry();
|
||||
using (var ctx = geo.Open()) {
|
||||
var points = new List<Point> {
|
||||
new(0, 0)
|
||||
};
|
||||
|
||||
ctx.BeginFigure(new((SIZE / 2 - POINT) * scale, 0), true, false);
|
||||
if (ot && it) {
|
||||
points.AddRange([new(SIZE / 2, -POINT / 2), new(SIZE / 2, POINT / 2), new(SIZE / 2 + POINT, 0)]);
|
||||
} else if (ot) {
|
||||
ctx.PolyLineTo([new(SIZE / 2 * scale, -POINT * scale), new((SIZE / 2 + POINT) * scale, 0)], true, false);
|
||||
} else if (it) {
|
||||
ctx.PolyLineTo([new(SIZE / 2 * scale, POINT * scale), new((SIZE / 2 + POINT) * scale, 0)], true, false);
|
||||
} else {
|
||||
ctx.ArcTo(new((SIZE / 2 + POINT) * scale, 0), size, 0, false, SweepDirection.Clockwise, true, false);
|
||||
}
|
||||
|
||||
ctx.ArcTo(new(SIZE * scale, (SIZE / 2 - POINT) * scale), size, 0, false, SweepDirection.Clockwise, true, false);
|
||||
points.Add(new(SIZE, 0));
|
||||
if (or && ir) {
|
||||
points.AddRange([new(SIZE, SIZE / 2 - POINT), new(SIZE + POINT / 2, SIZE / 2), new(SIZE - POINT / 2, SIZE / 2), new(SIZE, SIZE / 2 + POINT)]);
|
||||
} else if (or) {
|
||||
ctx.PolyLineTo([new((SIZE + POINT) * scale, SIZE / 2 * scale), new(SIZE * scale, (SIZE / 2 + POINT) * scale)], true, false);
|
||||
} else if (ir) {
|
||||
ctx.PolyLineTo([new((SIZE - POINT) * scale, SIZE / 2 * scale), new(SIZE * scale, (SIZE / 2 + POINT) * scale)], true, false);
|
||||
} else {
|
||||
ctx.ArcTo(new(SIZE * scale, (SIZE / 2 + POINT) * scale), size, 0, false, SweepDirection.Clockwise, true, false);
|
||||
}
|
||||
|
||||
ctx.ArcTo(new((SIZE / 2 + POINT) * scale, SIZE * scale), size, 0, false, SweepDirection.Clockwise, true, false);
|
||||
points.Add(new(SIZE, SIZE));
|
||||
if (ob && ib) {
|
||||
points.AddRange([new(SIZE / 2 + POINT, SIZE), new(SIZE / 2, SIZE + POINT / 2), new(SIZE / 2, SIZE - POINT / 2), new(SIZE / 2 - POINT, SIZE)]);
|
||||
} else if (ob) {
|
||||
ctx.PolyLineTo([new(SIZE / 2 * scale, (SIZE + POINT) * scale), new((SIZE / 2 - POINT) * scale, SIZE * scale)], true, false);
|
||||
} else if (ib) {
|
||||
ctx.PolyLineTo([new(SIZE / 2 * scale, (SIZE - POINT) * scale), new((SIZE / 2 - POINT) * scale, SIZE * scale)], true, false);
|
||||
} else {
|
||||
ctx.ArcTo(new((SIZE / 2 - POINT) * scale, SIZE * scale), size, 0, false, SweepDirection.Clockwise, true, false);
|
||||
}
|
||||
|
||||
ctx.ArcTo(new(0, (SIZE / 2 + POINT) * scale), size, 0, false, SweepDirection.Clockwise, true, false);
|
||||
points.Add(new(0, SIZE));
|
||||
if (ol && il) {
|
||||
points.AddRange([new(0, SIZE / 2 + POINT), new(-POINT / 2, SIZE / 2), new(POINT / 2, SIZE / 2), new(0, SIZE / 2 - POINT)]);
|
||||
} else if (ol) {
|
||||
ctx.PolyLineTo([new(-POINT * scale, SIZE / 2 * scale), new(0, (SIZE / 2 - POINT) * scale)], true, false);
|
||||
} else if (il) {
|
||||
ctx.PolyLineTo([new(POINT * scale, SIZE / 2 * scale), new(0, (SIZE / 2 - POINT) * scale)], true, false);
|
||||
} else {
|
||||
ctx.ArcTo(new(0, (SIZE / 2 - POINT) * scale), size, 0, false, SweepDirection.Clockwise, true, false);
|
||||
}
|
||||
|
||||
ctx.ArcTo(new((SIZE / 2 - POINT) * scale, 0), size, 0, false, SweepDirection.Clockwise, true, false);
|
||||
}
|
||||
|
||||
_rect?.Points = [.. points.Select(p => new Point(p.X * scale, p.Y * scale))];
|
||||
_rect?.StrokeThickness = 2;
|
||||
_rect?.Data = geo;
|
||||
// [.. points.Select(p => new Point(p.X * scale, p.Y * scale))];
|
||||
_rect?.StrokeThickness = Graph.ToBorder(scale);
|
||||
_rect?.SetValue(Canvas.LeftProperty, (CenterX - SIZE / 2) * scale);
|
||||
_rect?.SetValue(Canvas.TopProperty, (CenterY - SIZE / 2) * scale);
|
||||
}
|
||||
@@ -89,7 +112,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
|
||||
}
|
||||
|
||||
public void Update(bool isHovering, bool isPressed) {
|
||||
_rect?.Stroke = isHovering ? Brushes.Black : Brushes.Black;
|
||||
_rect?.Fill = Highlight ?? Brushes.DarkGray;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
using PIEHidNetCore;
|
||||
using System.IO;
|
||||
using System.Windows.Media;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 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}");
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
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;
|
||||
// TODO
|
||||
}
|
||||
|
||||
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.
@@ -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>
|
||||
@@ -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>
|
||||
@@ -11,7 +11,7 @@ namespace PamhagenSysCtrl.Windows {
|
||||
|
||||
private const double _minMove = 50;
|
||||
|
||||
private static PamhagenGraph Graph => App.Plant!.Graph;
|
||||
private readonly PamhagenGraph Graph;
|
||||
|
||||
private Point? _lastMousePos;
|
||||
private bool _mouseDragging;
|
||||
@@ -50,25 +50,26 @@ namespace PamhagenSysCtrl.Windows {
|
||||
public PlantSchemeWindow() {
|
||||
InitializeComponent();
|
||||
Menu_Help_CheckForUpdates.IsEnabled = App.Config.UpdateUrl != null;
|
||||
Graph = new();
|
||||
Graph.Draw(SchemeCanvas, RoundedScale);
|
||||
FastSelectPaths = new Dictionary<Button, (ISource, Pump, ISink)> {
|
||||
[FastSelectButton_MW1_Press1] = (Graph.MW1, Graph.MP1, Graph.Press1),
|
||||
[FastSelectButton_MW2_Press1] = (Graph.MW2, Graph.MP2, Graph.Press1),
|
||||
[FastSelectButton_MW1_Press2] = (Graph.MW1, Graph.MP1, Graph.Press2),
|
||||
[FastSelectButton_MW2_Press2] = (Graph.MW2, Graph.MP2, Graph.Press2),
|
||||
[FastSelectButton_MW1_Tank1] = (Graph.MW1, Graph.MP1, Graph.Tank1),
|
||||
[FastSelectButton_MW2_Tank1] = (Graph.MW2, Graph.MP2, Graph.Tank1),
|
||||
[FastSelectButton_MW1_Tank2] = (Graph.MW1, Graph.MP1, Graph.Tank2),
|
||||
[FastSelectButton_MW2_Tank2] = (Graph.MW2, Graph.MP2, Graph.Tank2),
|
||||
[FastSelectButton_MW1_Tank3] = (Graph.MW1, Graph.MP1, Graph.Tank3),
|
||||
[FastSelectButton_MW2_Tank3] = (Graph.MW2, Graph.MP2, Graph.Tank3),
|
||||
[FastSelectButton_MW1_Tank4] = (Graph.MW1, Graph.MP1, Graph.Tank4),
|
||||
[FastSelectButton_MW2_Tank4] = (Graph.MW2, Graph.MP2, Graph.Tank4),
|
||||
[FastSelectButton_MW1_Tank5] = (Graph.MW1, Graph.MP1, Graph.Tank5),
|
||||
[FastSelectButton_MW2_Tank5] = (Graph.MW2, Graph.MP2, Graph.Tank5),
|
||||
[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;
|
||||
App.Plant?.InitializePaths();
|
||||
InitializePaths();
|
||||
}
|
||||
|
||||
private void OnClosed(object sender, EventArgs evt) {
|
||||
@@ -95,7 +96,7 @@ namespace PamhagenSysCtrl.Windows {
|
||||
Menu_Settings_OverridePathClearances.IsChecked = false;
|
||||
Menu_Settings_OverrideDryRunClearances.IsChecked = false;
|
||||
|
||||
App.Plant?.InitializePaths();
|
||||
InitializePaths();
|
||||
}
|
||||
OnUpdate(null, null);
|
||||
}
|
||||
@@ -210,12 +211,19 @@ 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;
|
||||
App.Plant?.FastSelect(val.Source, val.Sink);
|
||||
OnUpdate(null, null);
|
||||
_lastSource = 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;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs evt) {
|
||||
@@ -494,6 +502,8 @@ namespace PamhagenSysCtrl.Windows {
|
||||
foreach (var n in Graph.Nodes) {
|
||||
if (n is Pump p) {
|
||||
p.Highlight = null;
|
||||
} else if (n is PipeJoin j) {
|
||||
j.Highlight = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,6 +515,9 @@ namespace PamhagenSysCtrl.Windows {
|
||||
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();
|
||||
@@ -573,8 +586,8 @@ namespace PamhagenSysCtrl.Windows {
|
||||
|
||||
var paths = Graph.SelectedPaths;
|
||||
if (HoveringPath is Path hp) paths = paths.Append(hp);
|
||||
foreach (var b in FastSelectPaths) {
|
||||
UpdateFastSelectPathButton(paths, hoverPathDeactivate, b.Key);
|
||||
foreach (var b in FastSelectPaths.Keys) {
|
||||
UpdateFastSelectPathButton(paths, hoverPathDeactivate, b);
|
||||
}
|
||||
|
||||
Graph.Update(_hover, down);
|
||||
@@ -582,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
Binary file not shown.
-216
@@ -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 |
Reference in new issue
Block a user