Files
pamhagen-sysctrl/PamhagenSysCtrl/Helpers/PamhagenPlant.cs
T
2026-10-13 17:26:35 +02:00

361 lines
19 KiB
C#

using PamhagenSysCtrl.Helpers.Pipeline;
using PamhagenSysCtrl.Helpers.Plc;
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;
public bool IsPlcConnected => Plc != null;
public bool IsKeyboardConnected => Keyboard != null;
protected bool IsRunning = true;
protected Thread? ReconnectThread;
protected Thread? PlcThread;
public event EventHandler<PlantEventArgs>? Update;
public readonly string PlcComPort;
public string? PlcConnectionError { get; private set; }
public T1.Status? LastPlcStatus => Plc?.LastStatus;
public T1Error? LastPlcError => Plc?.LastError;
public long? LastRttNs { get; private set; }
public PamhagenState State => Plc?.State ?? default;
public bool ManualControlMode { get => Plc?.ManualControlMode ?? false; set => Plc?.ManualControlMode = value; }
public bool OverridePathClearances { get => Plc?.OverridePathClearances ?? false; set => Plc?.OverridePathClearances = value; }
public bool OverrideDryRunClearances { get => Plc?.OverrideDryRunClearances ?? false; set => Plc?.OverrideDryRunClearances = value; }
public PamhagenPlant(string comPort) {
PlcComPort = comPort;
Graph = new();
Update += UpdateKeyboardBacklights;
ReconnectThread = new Thread(new ParameterizedThreadStart(ReconnectLoop));
ReconnectThread.Start();
}
protected async void ReconnectLoop(object? parameters) {
while (IsRunning) {
if (Plc == null) {
PlcThread?.Join();
PlcThread = null;
PamhagenPlc? tmp = null;
try {
tmp = new PamhagenPlc(PlcComPort);
await tmp.Initialize();
Plc = tmp;
PlcConnectionError = null;
PlcThread = new Thread(new ParameterizedThreadStart(PlcLoop));
PlcThread.Start();
} catch (Exception exc) {
tmp?.Dispose();
PlcConnectionError = $"{exc.GetType().Name}: {exc.Message}";
if (exc is FileNotFoundException) {
// COM port does not exist - ignore
PlcConnectionError = "Port existiert nicht";
} else if (exc is UnauthorizedAccessException) {
// COM port exists, but is used by other process - ignore
PlcConnectionError = "Port wird bereits verwendet";
} else if (exc is TimeoutException) {
// COM port exists, but PLC not responding - ignore
PlcConnectionError = "Keine Rückmeldung von SPS";
} else {
var str = $"Beim Aufbauen einer Verbindung zur SPS ist ein Fehler aufgetreten:\n\n{exc.GetType().Name}: {exc.Message}";
if (exc.InnerException != null) str += $"\n\n{exc.InnerException.GetType().Name}: {exc.InnerException.Message}";
MessageBox.Show(str, "SPS-Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
RaiseUpdateEvent(new());
}
if (Keyboard == null) {
try {
Keyboard = new();
Keyboard.KeyDown += Keyboard_KeyDown;
Keyboard.KeysChanged += Keyboard_KeysChanged;
Keyboard.SetFashFrequency(250);
} catch { }
}
await Task.Delay(100);
}
}
protected virtual void RaiseUpdateEvent(PlantEventArgs evt) {
App.MainDispatcher?.BeginInvoke(() => Update?.Invoke(this, evt));
}
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, State.Rebler_IsActive ? Color.FromRgb(0, 255, 0) : State.Auger1_IsActive ? Color.FromRgb(255, 255, 0) : State.WasteDisposal_IsActive ? Color.FromRgb(32, 32, 4) : Color.FromRgb(4, 32, 4));
k.SetBacklight((int)Xbe24Key.STOP_REBLER, !State.Rebler_IsActive ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4));
k.SetBacklight((int)Xbe24Key.START_TROUGH_AUGER, !State.TroughAuger_HasClearance ? k.IsKeyPressed((int)Xbe24Key.START_TROUGH_AUGER) ? Color.FromRgb(255, 32, 0) : Color.FromRgb(32, 4, 4) : State.TroughAuger_IsActive ? Color.FromRgb(0, 255, 0) : Color.FromRgb(4, 32, 4));
k.SetBacklight((int)Xbe24Key.STOP_TROUGH_AUGER, !State.TroughAuger_IsActive ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4));
k.SetBacklight((int)Xbe24Key.SELECT_MW2, State.MW2_WantSelected || State.MW2_IsSelected ? Color.FromRgb(0, 0, 255) : Color.FromRgb(4, 4, 16), State.MW2_WantSelected && !State.MW2_IsSelected);
k.SetBacklight((int)Xbe24Key.SELECT_MW1, State.MW1_WantSelected || State.MW1_IsSelected ? Color.FromRgb(0, 0, 255) : Color.FromRgb(4, 4, 16), State.MW1_WantSelected && !State.MW1_IsSelected);
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.State.MP2_HasPathClearance && plant.State.MP2_HasDryRunClearance && preparingMP2) {
k.SetBacklight((int)Xbe24Key.START_MP2, Color.FromRgb(4, 32, 4), true);
} else {
k.SetBacklight((int)Xbe24Key.START_MP2, !State.MP2_HasClearance ? k.IsKeyPressed((int)Xbe24Key.START_MP2) ? Color.FromRgb(255, 32, 0) : Color.FromRgb(32, 4, 4) : State.MP2_IsActive ? Color.FromRgb(0, 255, 0) : Color.FromRgb(4, 32, 4));
}
if (!plant.State.MP1_HasPathClearance && plant.State.MP1_HasDryRunClearance && preparingMP1) {
k.SetBacklight((int)Xbe24Key.START_MP1, Color.FromRgb(4, 32, 4), true);
} else {
k.SetBacklight((int)Xbe24Key.START_MP1, !State.MP1_HasClearance ? k.IsKeyPressed((int)Xbe24Key.START_MP1) ? Color.FromRgb(255, 32, 0) : Color.FromRgb(32, 4, 4) : State.MP1_IsActive ? Color.FromRgb(0, 255, 0) : Color.FromRgb(4, 32, 4));
}
k.SetBacklight((int)Xbe24Key.STOP_MP2, highlightOffMP2 ? Color.FromRgb(255, 32, 0) : !State.MP2_IsActive ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4));
k.SetBacklight((int)Xbe24Key.STOP_MP1, highlightOffMP1 ? Color.FromRgb(255, 32, 0) : !State.MP1_IsActive ? Color.FromRgb(255, 0, 0) : Color.FromRgb(32, 4, 4));
}
public void Dispose() {
IsRunning = false;
ReconnectThread?.Join();
PlcThread?.Join();
Plc?.Dispose();
Keyboard?.Dispose();
GC.SuppressFinalize(this);
}
protected async void PlcLoop(object? parameters) {
App.MainDispatcher?.Invoke(InitializePaths);
while (IsRunning && Plc != null) {
var start = DateTime.Now;
try {
await Plc.Update();
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);
if (exc is TimeoutException or IOException) {
PlcConnectionError = $"{exc.GetType().Name}: {exc.Message}";
Plc?.Dispose();
Plc = null;
} else {
await Task.Delay(1000);
}
}
}
if (Plc != null) {
try {
await Plc.Shutdown();
} catch (Exception exc) {
var str = "Beim Schließen der Verbindung zur SPS ist ein Fehler aufgetreten:\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "SPS-Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
RaiseUpdateEvent(new());
}
public void InitializePaths() {
var add = ReconstructSelectedPaths().ToList();
var remove = Graph.SelectedPaths.ToList();
foreach (var p in remove) Graph.RemovePath(p);
for (int i = 1; i <= 57; i++) {
App.Plant?.Plc?.CloseV(i);
}
foreach (var p in add) {
if (p.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 || State.Valve_WantOpen(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) {
if (Plc == null) return;
var key = (Xbe24Key)keyIndex;
try {
if (key == Xbe24Key.START_REBLER) {
Plc.StartRebler();
} else if (key == Xbe24Key.STOP_REBLER) {
if (State.Rebler_IsActive) {
Plc.StopRebler();
} else {
Plc.StopAuger1();
if (!State.PressCrossAuger_IsActive) {
Plc.StopAuger2();
Plc.StopAuger3();
}
}
} else if (key == Xbe24Key.START_TROUGH_AUGER) {
Plc.StartTroughAuger();
} else if (key == Xbe24Key.STOP_TROUGH_AUGER) {
Plc.StopTroughAuger();
} else if (key == Xbe24Key.SELECT_MW2) {
Plc.SelectMW2();
} else if (key == Xbe24Key.SELECT_MW1) {
Plc.SelectMW1();
} else if (key == Xbe24Key.START_MP2) {
Plc.StartMP2Forward();
} else if (key == Xbe24Key.STOP_MP2) {
Plc.StopMP2();
} else if (key == Xbe24Key.START_MP1) {
Plc.StartMP1Forward();
} else if (key == Xbe24Key.STOP_MP1) {
Plc.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());
}
public void OpenV(int n) => Plc?.OpenV(n);
public void CloseV(int n) => Plc?.CloseV(n);
public void SelectMW1() => Plc?.SelectMW1();
public void SelectMW2() => Plc?.SelectMW2();
public void StartTroughAuger() => Plc?.StartTroughAuger();
public void StopTroughAuger() => Plc?.StopTroughAuger();
public void StartRebler() => Plc?.StartRebler();
public void StopRebler() => Plc?.StopRebler();
public void StartStirrerMW1() => Plc?.StartStirrerMW1();
public void StopStirrerMW1() => Plc?.StopStirrerMW1();
public void StartStirrerMW2() => Plc?.StartStirrerMW2();
public void StopStirrerMW2() => Plc?.StopStirrerMW2();
public void StartAuger1() => Plc?.StartAuger1();
public void StopAuger1() => Plc?.StopAuger1();
public void StartAuger2() => Plc?.StartAuger2();
public void StopAuger2() => Plc?.StopAuger2();
public void StartAuger3() => Plc?.StartAuger3();
public void StopAuger3() => Plc?.StopAuger3();
public void StartMP1Forward() => Plc?.StartMP1Forward();
public void StartMP1Backward() => Plc?.StartMP1Backward();
public void StopMP1() => Plc?.StopMP1();
public void StartMP2Forward() => Plc?.StartMP2Forward();
public void StartMP2Backward() => Plc?.StartMP2Backward();
public void StopMP2() => Plc?.StopMP2();
public void EnableP12() => Plc?.EnableP12();
public void DisableP12() => Plc?.DisableP12();
public void EnableP3() => Plc?.EnableP3();
public void DisableP3() => Plc?.DisableP3();
public void EnableP4() => Plc?.EnableP4();
public void DisableP4() => Plc?.DisableP4();
public void EnableP5() => Plc?.EnableP5();
public void DisableP5() => Plc?.DisableP5();
public void EnableP6() => Plc?.EnableP6();
public void DisableP6() => Plc?.DisableP6();
}
}