Add ReconnectThread to reconnect to PLC and Keyboard on the fly
Test / Run tests (push) Successful in 26s

This commit is contained in:
2026-10-13 17:26:35 +02:00
parent c3f4acaf07
commit 4b9bcc6ad3
7 changed files with 158 additions and 129 deletions
+129 -95
View File
@@ -32,40 +32,79 @@ namespace PamhagenSysCtrl.Helpers {
protected Xbe24Keyboard? Keyboard;
public PamhagenGraph Graph;
public bool IsPlcConnected => Plc != null;
public bool IsKeyboardConnected => Keyboard != null;
protected bool IsRunning = true;
protected Thread? BackgroundThread;
protected Thread? ReconnectThread;
protected Thread? PlcThread;
public event EventHandler<PlantEventArgs>? Update;
public string? PlcPortName => Plc?.PortName;
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; // FIXME
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 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);
}
public PamhagenPlant(string comPort) {
PlcComPort = comPort;
Graph = new();
try {
Keyboard = new();
Keyboard.KeyDown += Keyboard_KeyDown;
Keyboard.KeysChanged += Keyboard_KeysChanged;
Keyboard.SetFashFrequency(250);
Keyboard.SetBacklight(Color.FromRgb(4, 4, 4));
} catch { }
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) {
@@ -132,22 +171,17 @@ namespace PamhagenSysCtrl.Helpers {
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 async Task StartThread() {
if (Plc == null) throw new Exception();
await Plc.Initialize();
BackgroundThread = new Thread(new ParameterizedThreadStart(BackgroundLoop));
BackgroundThread.Start();
}
public void Dispose() {
IsRunning = false;
BackgroundThread?.Join();
ReconnectThread?.Join();
PlcThread?.Join();
Plc?.Dispose();
Keyboard?.Dispose();
GC.SuppressFinalize(this);
}
protected async void BackgroundLoop(object? parameters) {
protected async void PlcLoop(object? parameters) {
App.MainDispatcher?.Invoke(InitializePaths);
while (IsRunning && Plc != null) {
var start = DateTime.Now;
try {
@@ -160,22 +194,24 @@ namespace PamhagenSysCtrl.Helpers {
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;
IsRunning = false;
} else {
await Task.Delay(1000);
}
}
}
try {
if (Plc != null)
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);
} 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() {
@@ -219,69 +255,67 @@ namespace PamhagenSysCtrl.Helpers {
}
private void Keyboard_KeyDown(object? sender, int keyIndex) {
if (Plc == null) return;
var key = (Xbe24Key)keyIndex;
App.MainDispatcher?.BeginInvoke(() => {
if (Plc == null) return;
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();
}
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
} 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) {
@@ -34,12 +34,12 @@ namespace PamhagenSysCtrl.Helpers.Plc {
protected readonly T1 Plc;
public string PortName => Plc.PortName;
public string ComPort => Plc.PortName;
public T1.Status LastStatus => Plc.LastStatus;
public T1Error LastError => Plc.LastError;
public LegacyPamhagenPlc(string portName, Parity parity = Parity.Odd) {
Plc = new T1(portName, parity);
public LegacyPamhagenPlc(string comPort, Parity parity = Parity.Odd) {
Plc = new T1(comPort, parity);
}
public void Dispose() {
+6 -5
View File
@@ -13,7 +13,7 @@ namespace PamhagenSysCtrl.Helpers.Plc {
protected LegacyPamhagenPlc Plc;
public PamhagenState State { get; private set; }
public string? PortName => Plc?.PortName;
public string? ComPort => Plc?.ComPort;
public T1.Status? LastStatus => Plc?.LastStatus;
public T1Error? LastError => Plc?.LastError;
@@ -23,8 +23,8 @@ namespace PamhagenSysCtrl.Helpers.Plc {
FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown
], default);
public PamhagenPlc(string portName, Parity parity = Parity.Odd) {
Plc = new(portName, parity);
public PamhagenPlc(string comPort, Parity parity = Parity.Odd) {
Plc = new(comPort, parity);
}
public void Dispose() {
@@ -36,6 +36,8 @@ namespace PamhagenSysCtrl.Helpers.Plc {
await Plc.TestConnection();
Actuators = await Plc.ReadOutputs();
LastActuators = Actuators;
Sensors = await Plc.ReadInputs();
State = new(Sensors, () => Actuators, OverridePathClearances, OverrideDryRunClearances);
}
public async Task Shutdown() {
@@ -46,9 +48,8 @@ namespace PamhagenSysCtrl.Helpers.Plc {
UpdateInterlocks();
if (Actuators != LastActuators) {
// FIXME race condition!
await Plc.WriteOutputs(Actuators);
LastActuators = Actuators;
await Plc.WriteOutputs(LastActuators);
}
Sensors = await Plc.ReadInputs();
State = new(Sensors, () => Actuators, OverridePathClearances, OverrideDryRunClearances);
+1 -1
View File
@@ -15,7 +15,7 @@ namespace PamhagenSysCtrl.Helpers.Plc {
public FillState MW1_FillLevel => Sensors.FillLevelMW1;
public FillState MW2_FillLevel => Sensors.FillLevelMW2;
public FillState[] Tanks_FillLevel => Sensors.FillLevelTanks;
public FillState[] Tanks_FillLevel => Sensors.FillLevelTanks ?? [FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown];
public FillState T1_FillLevel => Sensors.FillLevelTanks[0];
public FillState T2_FillLevel => Sensors.FillLevelTanks[1];
public FillState T3_FillLevel => Sensors.FillLevelTanks[2];
+4
View File
@@ -39,9 +39,13 @@ namespace PamhagenSysCtrl.Helpers {
throw new IOException($"Unable to SetDataCallback for XBE keyboard: {res}");
if ((res = XbeDevice.SetErrorCallback(this)) != 0)
throw new IOException($"Unable to SetErrorCallback for XBE keyboard: {res}");
SetBacklight(Color.FromRgb(1, 1, 1));
}
public void Dispose() {
GC.SuppressFinalize(this);
SetBacklight(Color.FromRgb(1, 1, 1));
XbeDevice?.CloseInterface();
}