using System.IO.Ports; namespace PamhagenSysCtrl.Helpers.Plc { public class PamhagenPlc : IDisposable { private bool _manualControlMode = false; private bool _overridePathClearnaces = false; private bool _overrideDryRunClearances = false; public bool ManualControlMode { get => _manualControlMode; set { _manualControlMode = value; UpdateInterlocks(); } } public bool OverridePathClearances { get => _overridePathClearnaces; set { _overridePathClearnaces = value; UpdateInterlocks(); } } public bool OverrideDryRunClearances { get => _overrideDryRunClearances; set { _overrideDryRunClearances = value; UpdateInterlocks(); } } protected LegacyPamhagenPlc Plc; public PamhagenState State { get; private set; } public string? PortName => Plc?.PortName; public T1.Status? LastStatus => Plc?.LastStatus; public T1Error? LastError => Plc?.LastError; protected LegacyPamhagenPlc.ActuatorStates Actuators; protected LegacyPamhagenPlc.ActuatorStates LastActuators; protected LegacyPamhagenPlc.SensorStates Sensors = new(default, default, default, default, default, default, default, [ 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 void Dispose() { GC.SuppressFinalize(this); Plc.Dispose(); } public async Task Initialize() { await Plc.TestConnection(); Actuators = await Plc.ReadOutputs(); LastActuators = Actuators; } public async Task Shutdown() { await Plc.WriteOutputs(new() { Valves = Actuators.Valves }); } public async Task Update() { UpdateInterlocks(); if (Actuators != LastActuators) { // FIXME race condition! await Plc.WriteOutputs(Actuators); LastActuators = Actuators; } Sensors = await Plc.ReadInputs(); State = new(Sensors, () => Actuators, OverridePathClearances, OverrideDryRunClearances); UpdateInterlocks(); } protected void UpdateInterlocks() { if (!ManualControlMode) { if (State.PressCrossAuger_IsActive && !State.Auger2_IsActive) StartAuger2(); if (State.Rebler_IsActive && !State.Auger1_IsActive) StartAuger1(); if (State.Auger1_IsActive && !State.Auger2_IsActive) StartAuger2(); if (State.Auger2_IsActive && !State.Auger3_IsActive) StartAuger3(); if (State.TroughAuger_IsActive && !State.TroughAuger_HasClearance) StopTroughAuger(); if (!State.PressCrossAuger_IsActive && !State.Auger1_IsActive) { StopAuger2(); StopAuger3(); } } if (State.MP1_IsActive && !State.MP1_HasClearance) StopMP1(); if (State.MP2_IsActive && !State.MP2_HasClearance) StopMP2(); if (!ManualControlMode) { if (State.MP1_State == MotorState.Forward && State.MW1_FillLevel >= FillState.Half && !State.MW1_Stirrer_IsActive) { StartStirrerMW1(); } else if (State.MW2_Stirrer_IsActive && (!State.MP1_IsActive || State.MW1_FillLevel < FillState.Half)) { StopStirrerMW1(); } if (State.MP2_State == MotorState.Forward && State.MW2_FillLevel >= FillState.Half && !State.MW2_Stirrer_IsActive) { StartStirrerMW2(); } else if (State.MW2_Stirrer_IsActive && (!State.MP2_IsActive || State.MW2_FillLevel < FillState.Half)) { StopStirrerMW2(); } } if (State.P12_IsActive && !State.P12_HasClearance) DisableP12(); if (State.P3_IsActive && !State.P3_HasClearance) DisableP3(); if (State.P4_IsActive && !State.P4_HasClearance) DisableP4(); if (State.P5_IsActive && !State.P5_HasClearance) DisableP5(); if (State.P6_IsActive && !State.P6_HasClearance) DisableP6(); } public void OpenV(int n) => SetV(n, true); public void CloseV(int n) => SetV(n, false); protected void SetV(int n, bool val) { if (n < 1 || n > 57) throw new ArgumentException("Invalid value for n: 1 <= #V <= 57"); Actuators.SetV(n, val); } public void SelectMW1() { Actuators.SetV(58, true); } public void SelectMW2() { Actuators.SetV(58, false); } public void StartTroughAuger() { if (!ManualControlMode && !State.TroughAuger_HasClearance) throw new NoClearanceException("Schnecke Auffangwanne hat keine Freigabe"); Actuators.TroughAuger = true; } public void StopTroughAuger() { Actuators.TroughAuger = false; } public void StartRebler() { Actuators.Rebler = true; if (!ManualControlMode) StartAuger1(); } public void StopRebler() { if (!ManualControlMode) StopTroughAuger(); Actuators.Rebler = false; } public void StartStirrerMW1() { Actuators.StirrerMW1 = true; } public void StopStirrerMW1() { Actuators.StirrerMW1 = false; } public void StartStirrerMW2() { Actuators.StirrerMW2 = true; } public void StopStirrerMW2() { Actuators.StirrerMW2 = false; } public void StartAuger1() { Actuators.Auger1 = true; if (!ManualControlMode) StartAuger2(); } public void StopAuger1() { if (!ManualControlMode && State.Rebler_IsActive) throw new NoClearanceException("Rebler aktiv"); Actuators.Auger1 = false; } public void StartAuger2() { Actuators.Auger2 = true; if (!ManualControlMode) StartAuger3(); } public void StopAuger2() { if (!ManualControlMode && State.PressCrossAuger_IsActive) throw new NoClearanceException("Presse-Querschnecke aktiv"); if (!ManualControlMode && State.Auger1_IsActive) throw new NoClearanceException("Förderschnecke 1 aktiv"); Actuators.Auger2 = false; } public void StartAuger3() { Actuators.Auger3 = true; } public void StopAuger3() { if (!ManualControlMode && State.Auger2_IsActive) throw new NoClearanceException("Förderschnecke 2 aktiv"); Actuators.Auger3 = false; } public void StartGradationPump() { Actuators.GradationPump = true; } public void StopGradationPump() { Actuators.GradationPump = false; } public void StartMP1Forward() { if (!State.MP1_HasClearance) throw new NoClearanceException("MP1 hat keine Freigabe"); if (Actuators.MP1 == MotorState.Backward) throw new NoClearanceException("MP1 läuft bereits (zurück)"); Actuators.MP1 = MotorState.Forward; if (State.MW1_FillLevel >= FillState.Half) StartStirrerMW1(); } public void StartMP1Backward() { if (!State.MP1_HasClearance) throw new NoClearanceException("MP1 hat keine Freigabe"); if (Actuators.MP1 == MotorState.Forward) throw new NoClearanceException("MP1 läuft bereits (vor)"); Actuators.MP1 = MotorState.Backward; } public void StopMP1() { Actuators.MP1 = MotorState.Halt; if (!ManualControlMode) StopStirrerMW1(); } public void StartMP2Forward() { if (!State.MP2_HasClearance) throw new NoClearanceException("MP2 hat keine Freigabe"); if (Actuators.MP2 == MotorState.Backward) throw new NoClearanceException("MP2 läuft bereits (zurück)"); Actuators.MP2 = MotorState.Forward; if (State.MW2_FillLevel >= FillState.Half) StartStirrerMW2(); } public void StartMP2Backward() { if (!State.MP2_HasClearance) throw new NoClearanceException("MP2 hat keine Freigabe"); if (Actuators.MP2 == MotorState.Forward) throw new NoClearanceException("MP2 läuft bereits (vor)"); Actuators.MP2 = MotorState.Backward; } public void StopMP2() { Actuators.MP2 = MotorState.Halt; if (!ManualControlMode) StopStirrerMW1(); } public void EnableP12() { if (!State.P12_HasClearance) throw new NoClearanceException("P1/2 hat keine Freigabe"); Actuators.P12 = MotorState.Forward; } public void DisableP12() { Actuators.P12 = MotorState.Halt; } public void EnableP3() { if (!State.P3_HasClearance) throw new NoClearanceException("P3 hat keine Freigabe"); Actuators.P3 = MotorState.Forward; } public void DisableP3() { Actuators.P3 = MotorState.Halt; } public void EnableP4() { if (!State.P4_HasClearance) throw new NoClearanceException("P4 hat keine Freigabe"); Actuators.P4 = MotorState.Forward; } public void DisableP4() { Actuators.P4 = MotorState.Halt; } public void EnableP5() { if (!State.P5_HasClearance) throw new NoClearanceException("P5 hat keine Freigabe"); Actuators.P5 = MotorState.Forward; } public void DisableP5() { Actuators.P5 = MotorState.Halt; } public void EnableP6() { if (!State.P6_HasClearance) throw new NoClearanceException("P6 hat keine Freigabe"); Actuators.P6 = MotorState.Forward; } public void DisableP6() { Actuators.P6 = MotorState.Halt; } } }