355 lines
19 KiB
C#
355 lines
19 KiB
C#
using PamhagenSysCtrl.Helpers.Pipeline;
|
|
using PamhagenSysCtrl.Helpers.Plc;
|
|
using System.IO;
|
|
using System.Windows;
|
|
|
|
namespace PamhagenSysCtrl.Helpers {
|
|
public class PamhagenPlant : IDisposable {
|
|
|
|
public enum ShortcutKey {
|
|
START_REBLER, STOP_REBLER, TOGGLE_REBLER, INDICATE_REBLER,
|
|
START_WASTE_DISPOSAL, STOP_WASTE_DISPOSAL, TOGGLE_WASTE_DISPOSAL, INDICATE_WASTE_DISPOSAL,
|
|
START_AUGER1, STOP_AUGER1, TOGGLE_AUGER1, INDICATE_AUGER1,
|
|
START_AUGER2, STOP_AUGER2, TOGGLE_AUGER2, INDICATE_AUGER2,
|
|
START_AUGER3, STOP_AUGER3, TOGGLE_AUGER3, INDICATE_AUGER3,
|
|
INDICATE_PRESS_CROSS_AUGER,
|
|
START_TROUGH_AUGER, STOP_TROUGH_AUGER, TOGGLE_TROUGH_AUGER, INDICATE_TROUGH_AUGER,
|
|
SELECT_MW1, INDICATE_MW1_SELECTED,
|
|
SELECT_MW2, INDICATE_MW2_SELECTED,
|
|
INDICATE_MW1, INDICATE_MW1_EMPTY, INDICATE_MW1_FILLED, INDICATE_MW1_HALF, INDICATE_MW1_FULL,
|
|
INDICATE_MW2, INDICATE_MW2_EMPTY, INDICATE_MW2_FILLED, INDICATE_MW2_HALF, INDICATE_MW2_FULL,
|
|
START_STIRRER_MW1, STOP_STIRRER_MW1, TOGGLE_STIRRER_MW1, INDICATE_STIRRER_MW1,
|
|
START_STIRRER_MW2, STOP_STIRRER_MW2, TOGGLE_STIRRER_MW2, INDICATE_STIRRER_MW2,
|
|
START_MP1, STOP_MP1, TOGGLE_MP1, INDICATE_MP1,
|
|
START_MP2, STOP_MP2, TOGGLE_MP2, INDICATE_MP2,
|
|
ENABLE_P12, DISABLE_P12, TOGGLE_P12, INDICATE_P12,
|
|
ENABLE_P3, DISABLE_P3, TOGGLE_P3, INDICATE_P3,
|
|
ENABLE_P4, DISABLE_P4, TOGGLE_P4, INDICATE_P4,
|
|
ENABLE_P5, DISABLE_P5, TOGGLE_P5, INDICATE_P5,
|
|
ENABLE_P6, DISABLE_P6, TOGGLE_P6, INDICATE_P6,
|
|
INDICATE_PRESS1_CLEARANCE,
|
|
INDICATE_PRESS2_CLEARANCE,
|
|
PATH_MW1_PRESS1, PATH_MW1_LTG3_PRESS1, PATH_MW1_LTG4_PRESS1,
|
|
PATH_MW1_PRESS2, PATH_MW1_LTG3_PRESS2, PATH_MW1_LTG4_PRESS2,
|
|
PATH_MW1_T1, PATH_MW1_LTG1_T1, PATH_MW1_LTG2_T1,
|
|
PATH_MW1_T2, PATH_MW1_LTG1_T2, PATH_MW1_LTG2_T2,
|
|
PATH_MW1_T3, PATH_MW1_LTG1_T3, PATH_MW1_LTG2_T3,
|
|
PATH_MW1_T4, PATH_MW1_LTG1_T4, PATH_MW1_LTG2_T4,
|
|
PATH_MW1_T5, PATH_MW1_LTG1_T5, PATH_MW1_LTG2_T5,
|
|
PATH_MW1_T6, PATH_MW1_LTG1_T6, PATH_MW1_LTG2_T6,
|
|
PATH_MW1_T7, PATH_MW1_LTG1_T7, PATH_MW1_LTG2_T7,
|
|
PATH_MW1_T8, PATH_MW1_LTG1_T8, PATH_MW1_LTG2_T8,
|
|
PATH_MW1_T9, PATH_MW1_LTG1_T9, PATH_MW1_LTG2_T9,
|
|
PATH_MW2_PRESS1, PATH_MW2_LTG3_PRESS1, PATH_MW2_LTG4_PRESS1,
|
|
PATH_MW2_PRESS2, PATH_MW2_LTG3_PRESS2, PATH_MW2_LTG4_PRESS2,
|
|
PATH_MW2_T1, PATH_MW2_LTG1_T1, PATH_MW2_LTG2_T1,
|
|
PATH_MW2_T2, PATH_MW2_LTG1_T2, PATH_MW2_LTG2_T2,
|
|
PATH_MW2_T3, PATH_MW2_LTG1_T3, PATH_MW2_LTG2_T3,
|
|
PATH_MW2_T4, PATH_MW2_LTG1_T4, PATH_MW2_LTG2_T4,
|
|
PATH_MW2_T5, PATH_MW2_LTG1_T5, PATH_MW2_LTG2_T5,
|
|
PATH_MW2_T6, PATH_MW2_LTG1_T6, PATH_MW2_LTG2_T6,
|
|
PATH_MW2_T7, PATH_MW2_LTG1_T7, PATH_MW2_LTG2_T7,
|
|
PATH_MW2_T8, PATH_MW2_LTG1_T8, PATH_MW2_LTG2_T8,
|
|
PATH_MW2_T9, PATH_MW2_LTG1_T9, PATH_MW2_LTG2_T9,
|
|
PATH_T12_PRESS1, PATH_T12_PRESS2, PATH_T12_T1, PATH_T12_T2, PATH_T12_T3, PATH_T12_T4, PATH_T12_T5, PATH_T12_T6, PATH_T12_T7, PATH_T12_T8, PATH_T12_T9,
|
|
PATH_T3_PRESS1, PATH_T3_PRESS2, PATH_T3_T1, PATH_T3_T2, PATH_T3_T3, PATH_T3_T4, PATH_T3_T5, PATH_T3_T6, PATH_T3_T7, PATH_T3_T8, PATH_T3_T9,
|
|
PATH_T4_PRESS1, PATH_T4_PRESS2, PATH_T4_T1, PATH_T4_T2, PATH_T4_T3, PATH_T4_T4, PATH_T4_T5, PATH_T4_T6, PATH_T4_T7, PATH_T4_T8, PATH_T4_T9,
|
|
PATH_T5_PRESS1, PATH_T5_PRESS2, PATH_T5_T1, PATH_T5_T2, PATH_T5_T3, PATH_T5_T4, PATH_T5_T5, PATH_T5_T6, PATH_T5_T7, PATH_T5_T8, PATH_T5_T9,
|
|
PATH_P6_PRESS1, PATH_P6_PRESS2,
|
|
}
|
|
|
|
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.Error += (s, e) => {
|
|
try {
|
|
Keyboard.Dispose();
|
|
} catch { }
|
|
Keyboard = null;
|
|
};
|
|
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;
|
|
|
|
// TODO update all keys
|
|
|
|
k.SetBacklight(ShortcutKey.START_REBLER, State.Rebler_IsActive ? Xbe24Keyboard.Green : Xbe24Keyboard.LightGreen);
|
|
k.SetBacklight(ShortcutKey.STOP_REBLER, !State.Rebler_IsActive ? Xbe24Keyboard.Red : Xbe24Keyboard.LightRed);
|
|
|
|
k.SetBacklight(ShortcutKey.START_TROUGH_AUGER, !State.TroughAuger_HasClearance ? k.IsKeyPressed(ShortcutKey.START_TROUGH_AUGER) ? Xbe24Keyboard.Orange : Xbe24Keyboard.LightRed : State.TroughAuger_IsActive ? Xbe24Keyboard.Green : Xbe24Keyboard.LightGreen);
|
|
k.SetBacklight(ShortcutKey.STOP_TROUGH_AUGER, !State.TroughAuger_IsActive ? Xbe24Keyboard.Red : Xbe24Keyboard.LightRed);
|
|
|
|
k.SetBacklight(ShortcutKey.SELECT_MW2, State.MW2_WantSelected || State.MW2_IsSelected ? Xbe24Keyboard.Blue : Xbe24Keyboard.LightBlue, State.MW2_WantSelected && !State.MW2_IsSelected);
|
|
k.SetBacklight(ShortcutKey.SELECT_MW1, State.MW1_WantSelected || State.MW1_IsSelected ? Xbe24Keyboard.Blue : Xbe24Keyboard.LightBlue, State.MW1_WantSelected && !State.MW1_IsSelected);
|
|
|
|
Dictionary<Pump, bool> pumpHighlightOff = [];
|
|
foreach (var (key, d) in Graph.PathShortcuts) {
|
|
if (App.Config.MapKey(key) is not int idx)
|
|
continue;
|
|
var isActive = d.Pump.IsActive;
|
|
var paths = plant.Graph.SelectedPaths.Select(p => (Pipeline.Path?)p)
|
|
.Where(p => p?.Matches(d.Source, d.Intermediate, d.Sink) ?? false);
|
|
if (paths.FirstOrDefault() is Pipeline.Path path) {
|
|
var isReady = path.IsReady;
|
|
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(idx, isActive ? isDefault ? Xbe24Keyboard.Cyan : Xbe24Keyboard.Purple : isDefault ? Xbe24Keyboard.Blue : Xbe24Keyboard.DarkPurple, !isReady);
|
|
} else {
|
|
var isFree = Graph.GetFreePath([d.Source, d.Sink], overrideStart: true) != null;
|
|
if (isActive && k.IsKeyPressed(idx)) pumpHighlightOff[d.Pump] = true;
|
|
k.SetBacklight(idx, isActive ? k.IsKeyPressed(idx) ? Xbe24Keyboard.Orange : Xbe24Keyboard.Neutral : isFree ? Xbe24Keyboard.LightBlue : k.IsKeyPressed(idx) ? Xbe24Keyboard.Orange : Xbe24Keyboard.Neutral);
|
|
}
|
|
}
|
|
|
|
if (!plant.State.MP2_HasPathClearance && plant.State.MP2_HasDryRunClearance && plant.State.MP2_WantsPathClearance) {
|
|
k.SetBacklight(ShortcutKey.START_MP2, plant.State.MP2_WantActive ? Xbe24Keyboard.Green : Xbe24Keyboard.LightGreen, true);
|
|
} else {
|
|
k.SetBacklight(ShortcutKey.START_MP2, !State.MP2_HasClearance ? k.IsKeyPressed(ShortcutKey.START_MP2) ? Xbe24Keyboard.Orange : Xbe24Keyboard.LightRed : State.MP2_IsActive ? Xbe24Keyboard.Green : Xbe24Keyboard.LightGreen);
|
|
}
|
|
if (!plant.State.MP1_HasPathClearance && plant.State.MP1_HasDryRunClearance && plant.State.MP1_WantsPathClearance) {
|
|
k.SetBacklight(ShortcutKey.START_MP1, plant.State.MP1_WantActive ? Xbe24Keyboard.Green : Xbe24Keyboard.LightGreen, true);
|
|
} else {
|
|
k.SetBacklight(ShortcutKey.START_MP1, !State.MP1_HasClearance ? k.IsKeyPressed(ShortcutKey.START_MP1) ? Xbe24Keyboard.Orange : Xbe24Keyboard.LightRed : State.MP1_IsActive ? Xbe24Keyboard.Green : Xbe24Keyboard.LightGreen);
|
|
}
|
|
k.SetBacklight(ShortcutKey.STOP_MP2, pumpHighlightOff.GetValueOrDefault(Graph.MP2) ? Xbe24Keyboard.Orange : !State.MP2_IsActive ? Xbe24Keyboard.Red : Xbe24Keyboard.LightRed);
|
|
k.SetBacklight(ShortcutKey.STOP_MP1, pumpHighlightOff.GetValueOrDefault(Graph.MP1) ? Xbe24Keyboard.Orange : !State.MP1_IsActive ? Xbe24Keyboard.Red : Xbe24Keyboard.LightRed);
|
|
}
|
|
|
|
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.InnerValves.Any(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, INode? intermediate, ISink sink) {
|
|
if (Graph.SelectedPaths.Select(p => (Pipeline.Path?)p).FirstOrDefault(p => p?.Start == source, null) is Pipeline.Path p1) {
|
|
if (p1.HasActivePump)
|
|
throw new NoClearanceException("Pumpe aktiv");
|
|
Graph.RemovePath(p1);
|
|
if (p1.End == sink && (intermediate == null || p1.Contains(intermediate)))
|
|
return;
|
|
}
|
|
if (Graph.GetFreePath(intermediate == null ? [source, sink] : [source, intermediate, sink]) is Pipeline.Path p2) {
|
|
Graph.AddPath(p2);
|
|
}
|
|
}
|
|
|
|
private void Keyboard_KeyDown(object? sender, int keyIndex) {
|
|
if (Plc == null || App.Config.MapKey(keyIndex) is not ShortcutKey key) return;
|
|
try {
|
|
if (key == ShortcutKey.START_REBLER) {
|
|
Plc.StartRebler();
|
|
} else if (key == ShortcutKey.STOP_REBLER) {
|
|
Plc.StopRebler();
|
|
} else if (key == ShortcutKey.START_TROUGH_AUGER) {
|
|
Plc.StartTroughAuger();
|
|
} else if (key == ShortcutKey.STOP_TROUGH_AUGER) {
|
|
Plc.StopTroughAuger();
|
|
} else if (key == ShortcutKey.SELECT_MW2) {
|
|
Plc.SelectMW2();
|
|
} else if (key == ShortcutKey.SELECT_MW1) {
|
|
Plc.SelectMW1();
|
|
} else if (key == ShortcutKey.START_MP2) {
|
|
Plc.StartMP2Forward();
|
|
} else if (key == ShortcutKey.STOP_MP2) {
|
|
Plc.StopMP2();
|
|
} else if (key == ShortcutKey.START_MP1) {
|
|
Plc.StartMP1Forward();
|
|
} else if (key == ShortcutKey.STOP_MP1) {
|
|
Plc.StopMP1();
|
|
} else if (Graph.PathShortcuts.TryGetValue(key, out var path)) {
|
|
FastSelect(path.Source, path.Intermediate, path.Sink);
|
|
} else {
|
|
return;
|
|
}
|
|
RaiseUpdateEvent(new());
|
|
} catch (NoClearanceException) {
|
|
// Keyboard?.SetBacklight(keyIndex, Color.FromRgb(255, 0, 0));
|
|
} catch (Exception) {
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
private void Keyboard_KeysChanged(object? sender, Xbe24Keyboard.KeyState state) {
|
|
UpdateKeyboardBacklights(this, 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();
|
|
}
|
|
}
|