Refactor PamhagenPlc
Test / Run tests (push) Successful in 36s

This commit is contained in:
2026-10-13 12:45:33 +02:00
parent 6f076919c7
commit 49bdd5d4c0
10 changed files with 754 additions and 727 deletions
@@ -0,0 +1,235 @@
using System.IO.Ports;
namespace PamhagenSysCtrl.Helpers.Plc {
public class LegacyPamhagenPlc : IDisposable {
public record struct SensorStates(
long PressureSensors,
FillState FillLevelMW1,
FillState FillLevelMW2,
bool ClearancePress1,
bool ClearancePress2,
int TroughSelector,
double GradationOe,
FillState[] FillLevelTanks,
MotorState PressCrossAuger
) {
public readonly bool GetDS(int n) => (PressureSensors & (1L << (n - 1))) != 0;
}
public record struct ActuatorStates(
long Valves,
MotorState MP1, MotorState MP2, MotorState P12, MotorState P3, MotorState P4, MotorState P5, MotorState P6,
bool TroughAuger, bool Rebler, bool StirrerMW1, bool StirrerMW2, bool Auger2, bool Auger3, bool Auger1, bool GradationPump
) {
public readonly bool GetV(int n) => (Valves & (1L << (n - 1))) != 0;
public void SetV(int n, bool val) {
if (val) {
Valves |= 1L << (n - 1);
} else {
Valves &= ~(1L << (n - 1));
}
}
}
protected readonly T1 Plc;
public string PortName => 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 void Dispose() {
Plc.Dispose();
GC.SuppressFinalize(this);
}
protected static FillState Int3ToFillState(int n, bool bigEndian = true) {
return
(bigEndian && (n & 0x4) != 0) || (!bigEndian && (n & 0x1) != 0) ? FillState.Full :
(bigEndian && (n & 0x2) != 0) || (!bigEndian && (n & 0x2) != 0) ? FillState.Half :
(bigEndian && (n & 0x1) != 0) || (!bigEndian && (n & 0x4) != 0) ? FillState.Filled :
n == 0 ? FillState.Empty :
FillState.Invalid;
}
protected static FillState Int2ToFillState(int n, bool bigEndian = true) {
return
(bigEndian && (n & 0x2) != 0) || (!bigEndian && (n & 0x1) != 0) ? FillState.Full :
(bigEndian && (n & 0x1) != 0) || (!bigEndian && (n & 0x2) != 0) ? FillState.Filled :
n == 0 ? FillState.Empty :
FillState.Invalid;
}
protected static FillState Int1ToFillState(int n) {
return
n == 1 ? FillState.Full :
n == 0 ? FillState.Unknown :
FillState.Invalid;
}
protected static MotorState Int2ToPumpState(int n, bool inverse) {
return n switch {
0 => MotorState.Halt,
1 => inverse ? MotorState.Backward : MotorState.Forward,
2 => inverse ? MotorState.Forward : MotorState.Backward,
_ => MotorState.Invalid,
};
}
protected static MotorState Int1ToPumpState(int n) {
return n switch {
1 => MotorState.Forward,
_ => MotorState.Halt,
};
}
public async Task EnsureRunning() {
var s = await Plc.StatusRead();
if (s.Mode == T1.Mode.RUN || s.Mode == T1.Mode.RUN_F)
return;
var error = await Plc.ErrorStatusRead();
throw new T1Exception($"Interner Fehler: {error} ({(int)error:0000})");
}
public async Task<(ushort RW010, ushort RW011, ushort RW012, ushort RW013, ushort RW014, ushort RW015, ushort RW016)> ReadInputRegisters() {
var d = await Plc.DataRead("RW010", 7);
// RW010, RW011, RW012, RW013 Drucksensoren
// RW014 Füllstände A1, A2, Presse1, Presse2, Wannenselekt
// RW015 Messwert von Marselli Gerät
// RW016 Füllstände der Tanks
return (d[0], d[1], d[2], d[3], d[4], d[5], d[6]);
}
public async Task<SensorStates> ReadInputs() {
await EnsureRunning();
var (r1, r2, r3, r4, f1, v, f2) = await ReadInputRegisters();
long pressure = (long)r1 | ((long)r2 << 16) | ((long)r3 << 32) | ((long)r4 << 48);
pressure = (pressure & 0x01FFFFFFFFFFFFFF) | ((pressure & 0x0600000000000000) << 1);
var mw1 = Int3ToFillState((f1 ) & 0x7, false); // 111 011 001 000
var mw2 = Int3ToFillState((f1 >> 3) & 0x7, false); // 111 011 001 000
var p1 = ((f1 >> 6) & 0x1) == 0; // 1 0
var p2 = ((f1 >> 7) & 0x1) == 0; // 1 0
var wp = ((f1 >> 8) & 0x3); // 10 01
// Masseligerät : 4mA => 50 Oe
// 20mA => 150 Oe
// ---> 0mA => 25 Oe
// Oe mA T1Wert
// 25 0 0
// 50 4 800
// 75 8 1600
// 100 12 2400
// 125 16 3200
// 150 20 4000
// -> factor: 0,03125 + (offset) 25 Oe
double oe = v * 0.03125 + 25;
var t1 = Int1ToFillState(((f2 ) & 0x3) >> 1); // 1X 0X
var t2 = Int1ToFillState(((f2 >> 2) & 0x3) >> 1); // 1X 0X
var t3 = Int1ToFillState(((f2 >> 4) & 0x3) >> 1); // 1X 0X
var t4 = Int1ToFillState(((f2 >> 6) & 0x3) >> 1); // 1X 0X
var t5 = Int1ToFillState(((f2 >> 8) & 0x3) >> 1); // 1X 0X
var t6 = Int1ToFillState((f2 >> 10) & 0x1); // 1 0
var t7 = Int1ToFillState((f2 >> 11) & 0x1); // 1 0
var t8 = Int1ToFillState((f2 >> 12) & 0x1); // 1 0
var t9 = Int1ToFillState((f2 >> 13) & 0x1); // 1 0
var q1 = Int1ToPumpState((f2 >> 15) & 0x1); // 1 0
return new SensorStates(pressure, mw1, mw2, p1, p2, wp == 3 ? -1 : wp == 1 ? 2 : wp == 2 ? 1 : 0, oe, [t1, t2, t3, t4, t5, t6, t7, t8, t9], q1);
}
protected async Task<(ushort RW000, ushort RW001, ushort RW002, ushort RW003, ushort RW004, ushort RW005, ushort RW006)> ReadOutputRegisters() {
var d = await Plc.DataRead("RW000", 7);
// RW000, RW001, RW002, RW003 Ventile
// RW004 Pumpensteuerung MP1, MP2, P1/2, P3, P4, P5, P6
// RW005 Steuerung Wannenschnecke, Rebler, Förderung, Rührer
// RW006 MessungsSteuerung
return (d[0], d[1], d[2], d[3], d[4], d[5], d[6]);
}
protected async Task WriteOutputRegisters(ushort rw000, ushort rw001, ushort rw002, ushort rw003, ushort rw004, ushort rw005, ushort rw006) {
// RW000, RW001, RW002, RW003 Ventile
// RW004 Pumpensteuerung MP1, MP2, P1/2, P3, P4, P5, P6
// RW005 Steuerung Wannenschnecke, Rebler, Förderung, Rührer
// RW006 MessungsSteuerung
await Plc.DataWrite("RW000", rw000, rw001, rw002, rw003, rw004, rw005, rw006);
}
public async Task<ActuatorStates> ReadOutputs() {
await EnsureRunning();
var (v1, v2, v3, v4, p1, s1, s2) = await ReadOutputRegisters();
long valves = (long)v1 | ((long)v2 << 16) | ((long)v3 << 32) | ((long)v4 << 48);
var mp1 = Int2ToPumpState((p1 ) & 0x3, true);
var mp2 = Int2ToPumpState((p1 >> 2) & 0x3, false);
var p12 = Int1ToPumpState((p1 >> 4) & 0x1);
var p3 = Int1ToPumpState((p1 >> 5) & 0x1);
var p4 = Int1ToPumpState((p1 >> 6) & 0x1);
var p5 = Int1ToPumpState((p1 >> 7) & 0x1);
var p6 = Int1ToPumpState((p1 >> 8) & 0x1);
var m1 = (s1 & 0x01) != 0;
var m2 = (s1 & 0x02) != 0;
var m3 = (s1 & 0x08) != 0;
var m4 = (s1 & 0x10) != 0;
var m5 = (s1 & 0x20) != 0;
var m6 = (s1 & 0x40) != 0;
var m7 = (s1 & 0x80) != 0;
var m8 = (s2 & 0x04) != 0;
var state = new ActuatorStates(valves, mp1, mp2, p12, p3, p4, p5, p6, m1, m2, m3, m4, m5, m6, m7, m8);
state.SetV(58, (s1 & 0x04) != 0);
state.SetV(59, (s2 & 0x01) != 0);
state.SetV(60, (s2 & 0x02) != 0);
return state;
}
public async Task WriteOutputs(ActuatorStates state) {
await EnsureRunning();
var v1 = (ushort)((state.Valves ) & 0xFFFF);
var v2 = (ushort)((state.Valves >> 16) & 0xFFFF);
var v3 = (ushort)((state.Valves >> 32) & 0xFFFF);
var v4 = (ushort)((state.Valves >> 48) & 0x01FF);
int pumps =
(state.MP1 == MotorState.Backward ? 0x001 : 0x000) |
(state.MP1 == MotorState.Forward ? 0x002 : 0x000) |
(state.MP2 == MotorState.Forward ? 0x004 : 0x000) |
(state.MP2 == MotorState.Backward ? 0x008 : 0x000) |
(state.P12 == MotorState.Forward ? 0x010 : 0x000) |
(state.P3 == MotorState.Forward ? 0x020 : 0x000) |
(state.P4 == MotorState.Forward ? 0x040 : 0x000) |
(state.P5 == MotorState.Forward ? 0x080 : 0x000) |
(state.P6 == MotorState.Forward ? 0x100 : 0x000);
int devices =
(state.TroughAuger ? 0x01 : 0x00) |
(state.Rebler ? 0x02 : 0x00) |
(state.GetV(58) ? 0x04 : 0x00) |
(state.StirrerMW1 ? 0x08 : 0x00) |
(state.StirrerMW2 ? 0x10 : 0x00) |
(state.Auger2 ? 0x20 : 0x00) |
(state.Auger3 ? 0x40 : 0x00) |
(state.Auger1 ? 0x80 : 0x00);
int measuring =
(state.GetV(59) ? 0x01 : 0x00) |
(state.GetV(60) ? 0x02 : 0x00) |
(state.GradationPump ? 0x04 : 0x00);
await WriteOutputRegisters(v1, v2, v3, v4, (ushort)pumps, (ushort)devices, (ushort)measuring);
}
public async Task TestConnection() {
await Plc.Test();
}
}
}
+267
View File
@@ -0,0 +1,267 @@
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;
}
}
}
@@ -0,0 +1,262 @@
namespace PamhagenSysCtrl.Helpers.Plc {
public readonly record struct PamhagenState {
private readonly LegacyPamhagenPlc.SensorStates Sensors;
private readonly Func<LegacyPamhagenPlc.ActuatorStates> ActuatorsCb;
private LegacyPamhagenPlc.ActuatorStates Actuators { get => ActuatorsCb == null ? default : ActuatorsCb(); }
public long Valves => Actuators.Valves;
public long PressureSensors => Sensors.PressureSensors;
public bool MW1_IsSelected => Sensors.TroughSelector == 1;
public bool MW2_IsSelected => Sensors.TroughSelector == 2;
public bool MW1_WantSelected => Actuators.GetV(58);
public bool MW2_WantSelected => !Actuators.GetV(58);
public FillState MW1_FillLevel => Sensors.FillLevelMW1;
public FillState MW2_FillLevel => Sensors.FillLevelMW2;
public FillState[] Tanks_FillLevel => Sensors.FillLevelTanks;
public FillState T1_FillLevel => Sensors.FillLevelTanks[0];
public FillState T2_FillLevel => Sensors.FillLevelTanks[1];
public FillState T3_FillLevel => Sensors.FillLevelTanks[2];
public FillState T4_FillLevel => Sensors.FillLevelTanks[3];
public FillState T5_FillLevel => Sensors.FillLevelTanks[4];
public FillState T6_FillLevel => Sensors.FillLevelTanks[5];
public FillState T7_FillLevel => Sensors.FillLevelTanks[6];
public FillState T8_FillLevel => Sensors.FillLevelTanks[7];
public FillState T9_FillLevel => Sensors.FillLevelTanks[8];
public MotorState PressCrossAuger_State => Sensors.PressCrossAuger;
public bool PressCrossAuger_IsActive => PressCrossAuger_State == MotorState.Forward;
public bool TroughAuger_IsActive => Actuators.TroughAuger;
public bool Rebler_IsActive => Actuators.Rebler;
public bool MW1_Stirrer_IsActive => Actuators.StirrerMW1;
public bool MW2_Stirrer_IsActive => Actuators.StirrerMW2;
public bool Auger1_IsActive => Actuators.Auger1;
public bool Auger2_IsActive => Actuators.Auger2;
public bool Auger3_IsActive => Actuators.Auger3;
public bool WasteDisposal_IsActive => Auger1_IsActive || Auger2_IsActive || Auger3_IsActive;
public MotorState MP1_State => Actuators.MP1;
public MotorState MP2_State => Actuators.MP2;
public MotorState P12_State => Actuators.P12;
public MotorState P3_State => Actuators.P3;
public MotorState P4_State => Actuators.P4;
public MotorState P5_State => Actuators.P5;
public MotorState P6_State => Actuators.P6;
public bool MP1_IsActive => Actuators.MP1 == MotorState.Forward || Actuators.MP1 == MotorState.Backward;
public bool MP2_IsActive => Actuators.MP2 == MotorState.Forward || Actuators.MP2 == MotorState.Backward;
public bool P12_IsActive => Actuators.P12 == MotorState.Forward;
public bool P3_IsActive => Actuators.P3 == MotorState.Forward;
public bool P4_IsActive => Actuators.P4 == MotorState.Forward;
public bool P5_IsActive => Actuators.P5 == MotorState.Forward;
public bool P6_IsActive => Actuators.P6 == MotorState.Forward;
public bool Press1_HasClearance => Sensors.ClearancePress1;
public bool Press2_HasClearance => Sensors.ClearancePress2;
public bool TroughAuger_HasClearance => Rebler_IsActive && !(MW1_IsSelected && MW1_FillLevel == FillState.Full) && !(MW2_IsSelected && MW2_FillLevel == FillState.Full);
public readonly bool MP1_HasPathClearance { get; }
public readonly bool MP2_HasPathClearance { get; }
public readonly bool P12_HasPathClearance { get; }
public readonly bool P3_HasPathClearance { get; }
public readonly bool P4_HasPathClearance { get; }
public readonly bool P5_HasPathClearance { get; }
public readonly bool P6_HasPathClearance { get; }
public readonly bool MP1_HasDryRunClearance { get; }
public readonly bool MP2_HasDryRunClearance { get; }
public readonly bool P12_HasDryRunClearance { get; }
public readonly bool P3_HasDryRunClearance { get; }
public readonly bool P4_HasDryRunClearance { get; }
public readonly bool P5_HasDryRunClearance { get; }
public readonly bool P6_HasDryRunClearance { get; }
public bool MP1_HasClearance => MP1_HasDryRunClearance && MP1_HasPathClearance;
public bool MP2_HasClearance => MP2_HasDryRunClearance && MP2_HasPathClearance;
public bool P12_HasClearance => P12_HasDryRunClearance && P12_HasPathClearance;
public bool P3_HasClearance => P3_HasDryRunClearance && P3_HasPathClearance;
public bool P4_HasClearance => P4_HasDryRunClearance && P4_HasPathClearance;
public bool P5_HasClearance => P4_HasDryRunClearance && P5_HasPathClearance;
public bool P6_HasClearance => P6_HasDryRunClearance && P6_HasPathClearance;
public PamhagenState(LegacyPamhagenPlc.SensorStates sensors, Func<LegacyPamhagenPlc.ActuatorStates> actuatorsCb, bool overridePathClearances = false, bool overrideDryRunClearances = false) {
Sensors = sensors;
ActuatorsCb = actuatorsCb;
MP1_HasPathClearance = overridePathClearances || HasPathClearance(this, Paths.MP1);
MP2_HasPathClearance = overridePathClearances || HasPathClearance(this, Paths.MP2);
P12_HasPathClearance = overridePathClearances || HasPathClearance(this, Paths.P12);
P3_HasPathClearance = overridePathClearances || HasPathClearance(this, Paths.P3);
P4_HasPathClearance = overridePathClearances || HasPathClearance(this, Paths.P4);
P5_HasPathClearance = overridePathClearances || HasPathClearance(this, Paths.P5);
P6_HasPathClearance = overridePathClearances || HasPathClearance(this, Paths.P6);
MP1_HasDryRunClearance = overrideDryRunClearances || MW1_FillLevel != FillState.Empty;
MP2_HasDryRunClearance = overrideDryRunClearances || MW2_FillLevel != FillState.Empty;
P12_HasDryRunClearance = overrideDryRunClearances || T1_FillLevel != FillState.Empty || T2_FillLevel != FillState.Empty;
P3_HasDryRunClearance = overrideDryRunClearances || T3_FillLevel != FillState.Empty;
P4_HasDryRunClearance = overrideDryRunClearances || T4_FillLevel != FillState.Empty;
P5_HasDryRunClearance = overrideDryRunClearances || T5_FillLevel != FillState.Empty;
P6_HasDryRunClearance = true;
}
public bool Valve_IsClosed(int n) => Sensors.GetDS(n);
public bool Valve_IsOpen(int n) => !Valve_IsClosed(n);
public bool Valve_WantOpen(int n) => Actuators.GetV(n);
public bool Valve_WantClosed(int n) => !Valve_WantOpen(n);
private record struct ValveStates(int[] Open, int[] Closed) {
public readonly ValveStateMask ToMask() => new(Open.Aggregate(0L, (m, n) => m | (1L << (n - 1))), Open.Concat(Closed).Aggregate(0L, (m, n) => m | (1L << (n - 1))));
public static ValveStates operator +(ValveStates a, ValveStates b) {
return new([.. a.Open, .. b.Open], [.. a.Closed, .. b.Closed]);
}
}
private record struct ValveStateMask(long States, long Mask) {
public static ValveStateMask[] From(IEnumerable<ValveStates> states) => [.. states.Select(s => s.ToMask())];
public static bool operator ==(long a, ValveStateMask b) => (a & b.Mask) == b.States;
public static bool operator >=(long a, ValveStateMask b) => (a & b.States) == b.States;
public static bool operator !=(long a, ValveStateMask b) => !(a == b);
public static bool operator <=(long a, ValveStateMask b) => !(a >= b);
}
private static bool HasSinglePathClearance(PamhagenState state, IEnumerable<ValveStateMask> masks) {
return masks.Any(m => state.Valves == m && ~state.PressureSensors == m);
}
private static bool HasAnyClearance(PamhagenState state, IEnumerable<ValveStateMask> masks) {
return masks.Any(m => state.Valves >= m && ~state.PressureSensors >= m);
}
private static bool HasPathClearance(PamhagenState state, (ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) masks) {
return
(HasSinglePathClearance(state, masks.Press1) && state.Press1_HasClearance) ||
(HasSinglePathClearance(state, masks.Press2) && state.Press2_HasClearance) ||
(HasSinglePathClearance(state, masks.Tanks[0]) && state.T1_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[1]) && state.T2_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[2]) && state.T3_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[3]) && state.T4_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[4]) && state.T5_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[5]) && state.T6_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[6]) && state.T7_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[7]) && state.T8_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[8]) && state.T9_FillLevel != FillState.Full) ||
false;
}
private static bool HasPathClearance(PamhagenState state, (ValveStateMask[] Press1, ValveStateMask[] Press2) masks) {
return
(HasSinglePathClearance(state, masks.Press1) && state.Press1_HasClearance) ||
(HasSinglePathClearance(state, masks.Press2) && state.Press2_HasClearance) ||
false;
}
private static readonly (
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) MP1,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) MP2,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) P12,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) P3,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) P4,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) P5,
(ValveStateMask[] Press1, ValveStateMask[] Press2) P6
) Paths;
static PamhagenState() {
ValveStates[] wt_aa = [
new([12], [15, 16, 20]), // bypass
new([15, 18, 16], [11, 12, 14, 17, 19, 20]), // WT1
new([15, 17, 20], [11, 12, 18, 19, 21]), // WT2
new([15, 18, 19, 20], [11, 12, 14, 16, 17, 21]), // WT1+WT2
];
ValveStates[] wt_ab = [
new([15, 18, 14], [11, 12, 13, 16, 17, 19, 21]), // WT1
new([15, 17, 21], [11, 12, 13, 18, 19, 20]), // WT2
new([15, 18, 19, 21], [11, 12, 13, 14, 16, 17, 20]), // WT1+WT2
];
ValveStates[] wt_ba = [
new([11, 18, 16], [15, 12, 13, 14, 17, 19, 20]), // WT1
new([11, 17, 20], [15, 12, 13, 18, 19, 21]), // WT2
new([11, 18, 19, 20], [15, 12, 13, 14, 16, 17, 21]), // WT1+WT2
];
ValveStates[] wt_bb = [
new([13], [11, 14, 21]), // bypass
new([11, 18, 14], [15, 13, 16, 17, 19, 21]), // WT1
new([11, 17, 21], [15, 13, 18, 19, 20]), // WT2
new([11, 18, 19, 21], [15, 13, 14, 16, 17, 20]), // WT1+WT2
];
ValveStates[][] tanks = [
[new([22], [24, 26, 28]), new([23], [25, 27, 29])],
[new([24], [22, 26, 28]), new([25], [23, 27, 29])],
[new([26], [22, 24, 28]), new([27], [23, 25, 29])],
[new([28, 30], [22, 24, 26, 32, 34]), new([29, 31], [23, 25, 27, 33, 35])],
[new([28, 32], [22, 24, 26, 30, 34]), new([29, 33], [23, 25, 27, 31, 35])],
[new([28, 34, 37], [22, 24, 26, 30, 32, 39, 41, 43]), new([29, 35, 36], [23, 25, 27, 31, 33, 38, 40, 42])],
[new([28, 34, 39], [22, 24, 26, 30, 32, 37, 41, 43]), new([29, 35, 38], [23, 25, 27, 31, 33, 36, 40, 42])],
[new([28, 34, 41], [22, 24, 26, 30, 32, 37, 39, 43]), new([29, 35, 40], [23, 25, 27, 31, 33, 36, 38, 42])],
[new([28, 34, 43], [22, 24, 26, 30, 32, 37, 39, 41]), new([29, 35, 42], [23, 25, 27, 31, 33, 36, 38, 40])],
];
ValveStates[] mp1_pr1 = [
new([1, 6, 51], [3, 4, 5, 50, 52, 53, 55]),
new([4, 8, 52], [1, 2, 7, 50, 51, 53, 56]),
];
ValveStates[] mp1_pr2 = [
new([1, 6, 55], [3, 4, 5, 51, 54, 56, 57]),
new([4, 8, 56], [1, 2, 7, 52, 54, 55, 57]),
];
ValveStates mp1_wt_a = new([1, 5], [3, 4, 6]);
ValveStates mp1_wt_b = new([4, 7, 9], [1, 2, 8, 10]);
ValveStates[] mp1_a = [.. wt_aa.Select(m => mp1_wt_a + m), .. wt_ba.Select(m => mp1_wt_b + m)];
ValveStates[] mp1_b = [.. wt_ab.Select(m => mp1_wt_a + m), .. wt_bb.Select(m => mp1_wt_b + m)];
ValveStates[][] mp1_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. mp1_a.Select(m => m + t[0]), .. mp1_b.Select(m => m + t[1])])];
ValveStates[] mp2_pr1 = [
new([2, 8, 52], [3, 4, 7, 50, 51, 53, 56]),
new([3, 6, 51], [1, 2, 5, 50, 52, 53, 55]),
];
ValveStates[] mp2_pr2 = [
new([2, 8, 56], [3, 4, 7, 52, 54, 55, 57]),
new([3, 6, 55], [2, 1, 5, 51, 54, 56, 57]),
];
ValveStates mp2_wt_a = new([3, 5], [1, 2, 6]);
ValveStates mp2_wt_b = new([2, 7, 9], [3, 4, 8, 10]);
ValveStates[] mp2_a = [.. wt_aa.Select(m => mp2_wt_a + m), .. wt_ba.Select(m => mp2_wt_b + m)];
ValveStates[] mp2_b = [.. wt_ab.Select(m => mp2_wt_a + m), .. wt_bb.Select(m => mp2_wt_b + m)];
ValveStates[][] mp2_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. mp2_a.Select(m => m + t[0]), .. mp2_b.Select(m => m + t[1])])];
ValveStates[] p12_pr1 = [new([44, 46, 49, 50], [10, 45, 47, 48, 54, 51, 52, 53])];
ValveStates[] p12_pr2 = [new([44, 46, 49, 54], [10, 45, 47, 48, 50, 55, 56, 57])];
ValveStates[] p12_b = [new([44, 10], [9, 45, 46])];
ValveStates[][] p12_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. p12_b.Select(m => m + t[1])])];
ValveStates[] p3_pr1 = [new([45, 46, 49, 50], [10, 44, 47, 48, 51, 52, 53, 54])];
ValveStates[] p3_pr2 = [new([45, 46, 49, 54], [10, 44, 47, 48, 50, 55, 56, 57])];
ValveStates[] p3_b = [new([45, 10], [9, 44, 46])];
ValveStates[][] p3_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. p3_b.Select(m => m + t[1])])];
ValveStates[] p4_pr1 = [new([47, 49, 50], [46, 48, 51, 52, 53, 54])];
ValveStates[] p4_pr2 = [new([47, 49, 54], [46, 48, 50, 55, 56, 57])];
ValveStates[] p4_b = [new([47, 46, 10], [9, 44, 45, 48, 49])];
ValveStates[][] p4_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. p4_b.Select(m => m + t[1])])];
ValveStates[] p5_pr1 = [new([48, 49, 50], [46, 47, 51, 52, 53, 54])];
ValveStates[] p5_pr2 = [new([48, 49, 54], [46, 47, 50, 55, 56, 57])];
ValveStates[] p5_b = [new([48, 46, 10], [9, 44, 45, 47, 49])];
ValveStates[][] p5_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. p5_b.Select(m => m + t[1])])];
ValveStates[] p6_pr1 = [new([53], [50, 51, 52, 57])];
ValveStates[] p6_pr2 = [new([57], [53, 54, 55, 56])];
Paths = (
(ValveStateMask.From(mp1_pr1), ValveStateMask.From(mp1_pr2), [.. mp1_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(mp2_pr1), ValveStateMask.From(mp2_pr2), [.. mp2_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p12_pr1), ValveStateMask.From(p12_pr2), [.. p12_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p3_pr1), ValveStateMask.From(p3_pr2), [.. p3_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p4_pr1), ValveStateMask.From(p4_pr2), [.. p4_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p5_pr1), ValveStateMask.From(p5_pr2), [.. p5_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p6_pr1), ValveStateMask.From(p6_pr2))
);
}
}
}
+137
View File
@@ -0,0 +1,137 @@
using System.IO;
using System.IO.Ports;
using System.Text;
namespace PamhagenSysCtrl.Helpers.Plc {
public class T1 : IDisposable {
public enum Mode { HALT = 1, RUN = 2, RUN_F = 3, HOLD = 4, ERROR = 6 }
public readonly record struct Status(Mode Mode, int Flags);
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,
DtrEnable = true,
RtsEnable = true,
Encoding = Encoding.ASCII,
};
Serial.Open();
Serial.DiscardInBuffer();
Serial.DiscardOutBuffer();
}
public void Dispose() {
Serial.Dispose();
GC.SuppressFinalize(this);
}
protected async Task SendCommand(string cmd, string? data = null, bool checksum = true) {
// max 255 bytes
var line = new StringBuilder("(A", 256);
line.AppendFormat("{0:00}{1}", 1, cmd);
if (data != null) {
if (data.Length > 244) throw new ArgumentException("T1 playload too long");
line.Append(data);
}
if (checksum) {
line.Append('&');
var chk = Enumerable.Range(0, line.Length).Aggregate(0, (c, i) => c + line[i]) & 0xFF;
line.AppendFormat("{0:X2}", chk);
}
line.Append(')');
Serial.WriteLine(line.ToString());
}
protected async Task<(string Command, string Data)> ReceiveResponse(string? expectedCmd = null) {
var line = Serial.ReadLine();
if (line.Length < 8 || !line.StartsWith("(A") || !line.EndsWith(')') || line[^4] != '&') {
throw new FormatException("Invalid response from PLC (T1)");
}
var chk1 = Convert.ToInt32(line[^3..^1], 16);
var chk2 = Enumerable.Range(0, line.Length - 3).Aggregate(0, (c, i) => c + line[i]) & 0xFF;
if (chk1 != chk2) {
throw new IOException($"Ungültige Prüfsumme von SPS (T1)");
}
var (cmd, data) = (line[4..6], line[6..^4]);
if (cmd == "ST" && data[3] == '6') {
// TODO check for other error bits?
await SendCommand("ER");
var (_, res) = await ReceiveResponse("ER");
int code = Convert.ToInt32(res);
var error = Enum.IsDefined(typeof(T1Error), code) ? (T1Error)code : T1Error.UNKNOWN_ERROR;
throw new T1Exception($"PLC Error ({data}): {error.GetName()}" + (error.GetDescription() != null ? $" ({error.GetDescription()})" : ""));
} else if (cmd == expectedCmd) {
return (cmd, data);
} else if (cmd == "CE") {
int code = Convert.ToInt32(data);
throw code switch {
1 => new T1Exception($"Computer Link Error ({data}): Command error (Received command is illegal)"),
2 => new T1Exception($"Computer Link Error ({data}): Format error (Received message format is invalid)"),
3 => new T1Exception($"Computer Link Error ({data}): Checksum error (Checksum mismatch is detected)"),
_ => new T1Exception($"Computer Link Error ({data}): Unknown error"),
};
} else if (cmd == "EE") {
int code = Convert.ToInt32(data);
var error = Enum.IsDefined(typeof(T1Error), code) ? (T1Error)code : T1Error.UNKNOWN_ERROR;
throw new T1Exception($"PLC Error ({data}): {error.GetName()}" + (error.GetDescription() != null ? $" ({error.GetDescription()})" : ""));
} else if (expectedCmd != null) {
throw new T1Exception($"Unexpected response from PLC (T1): {cmd} {data}");
}
return (cmd, data);
}
public async Task Test(string data = "_Ping_Pong_0123456789_abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_") {
await SendCommand("TS", data);
var (_, res) = await ReceiveResponse("TS");
if (data.Replace(" ", "") != res) throw new T1Exception("Wrong test response from PLC (T1)");
}
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;
}
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;
}
public Task<ushort[]> DataRead(string register, int length) {
return DataRead((register, length));
}
public async Task<ushort[]> DataRead(params (string Register, int Length)[] registers) {
await SendCommand("DR", string.Join(',', registers.Select(r => $"{r.Register},{r.Length}")));
var (_, res) = await ReceiveResponse("DR");
if (res.Length != registers.Sum(r => r.Length) * 4) throw new T1Exception("Invalid response length from PLC (T1)");
return [.. Enumerable.Range(0, res.Length / 4).Select(i => Convert.ToUInt16(res.Substring(i * 4, 4), 16))];
}
public Task<Status> DataWrite(string register, params ushort[] values) {
return DataWrite((register, values));
}
public async Task<Status> DataWrite(params (string Register, ushort[] Values)[] registers) {
await SendCommand("DW", string.Join(',', registers.Select(r => $"{r.Register},{r.Values.Length}," + string.Join(',', r.Values.Select(v => $"{v:X4}")))));
var (_, res) = await ReceiveResponse("ST");
return new Status((Mode)(res[3] - '0'), ((res[0] - '0') << 4) | (res[1] - '0'));
}
}
}
+144
View File
@@ -0,0 +1,144 @@
namespace PamhagenSysCtrl.Helpers.Plc {
public enum T1Error {
UNKNOWN_ERROR = -1,
NO_ERROR = 0,
SYSTEM_POWER_ON = 10,
SYSTEM_POWER_OFF = 11,
RAM_CHECK_ERROR = 20,
PROGRAM_BCC_ERROR = 21,
BATTERY_VOLTAGE_DROP = 22,
EEPROM_BCC_ERROR = 23,
EEPROM_WARNING = 26,
SYSTEM_RAM_CHECK_ERROR = 30,
SYSTEM_ROM_BCC_ERROR = 31,
PERIPHERAL_LSI_ERROR = 32,
CLOCK_CALENDAR_CHECK_ERROR = 33,
ILLEGAL_SYSTEM_INTERRUPT = 34,
WD_TIMER_ERROR = 35,
IO_BUS_ERROR = 40,
IO_MISMATCH = 41,
IO_NO_ANSWER = 42,
IO_PARITY_ERROR = 43,
ILLEGAL_IO_REGISTER = 46,
COMMUNICATION_BUSY = 51,
FORMAT_ERROR = 52,
SCAN_TIME_OVER = 64,
NO_END_IRET_ERROR = 80,
PAIR_INSTRUCTION_ERROR = 81,
OPERAND_ERROR = 82,
INVALID_PROGRAM = 83,
NO_SUBROUTINE_ENTRY = 86,
NO_RET_ERROR = 87,
SUBROUTINE_NESTING_ERROR = 88,
LOOP_NESTING_ERROR = 89,
INVALID_FUNCTION_INSTRUCTION = 98,
PASSWORD_PROTECT = 106,
ILLEGAL_INSTRUCTION = 110,
REGISTER_ADDRESS_ERROR = 111,
BOUNDARY_ERROR = 112,
MEMORY_FULL = 113,
MODE_MISMATCH = 114,
REGISTER_ADDRESS_SIZE_ERROR = 115,
DUPLICATE_ENTRY_NR = 121,
}
public static class T1ErrorExtension {
public static bool IsError(this T1Error error) {
return error switch {
T1Error.NO_ERROR => false,
T1Error.SYSTEM_POWER_ON => false,
T1Error.SYSTEM_POWER_OFF => false,
_ => true,
};
}
public static string GetName(this T1Error error) {
return error switch {
T1Error.UNKNOWN_ERROR => "Unknown error",
T1Error.NO_ERROR => "No error",
T1Error.SYSTEM_POWER_ON => "System power on",
T1Error.SYSTEM_POWER_OFF => "System power off",
T1Error.RAM_CHECK_ERROR => "RAM check error",
T1Error.PROGRAM_BCC_ERROR => "Program BCC error",
T1Error.BATTERY_VOLTAGE_DROP => "Battery voltage drop",
T1Error.EEPROM_BCC_ERROR => "EEPROM BCC error",
T1Error.EEPROM_WARNING => "EEPROM warning",
T1Error.SYSTEM_RAM_CHECK_ERROR => "System RAM check error",
T1Error.SYSTEM_ROM_BCC_ERROR => "System ROM BCC error",
T1Error.PERIPHERAL_LSI_ERROR => "Peripheral LSI error",
T1Error.CLOCK_CALENDAR_CHECK_ERROR => "Clock-calendar check error",
T1Error.ILLEGAL_SYSTEM_INTERRUPT => "Illegal system interrupt",
T1Error.WD_TIMER_ERROR => "WD timer error",
T1Error.IO_BUS_ERROR => "I/O bus error",
T1Error.IO_MISMATCH => "I/O mismatch",
T1Error.IO_NO_ANSWER => "I/O no answer",
T1Error.IO_PARITY_ERROR => "I/O parity error",
T1Error.ILLEGAL_IO_REGISTER => "Illegal I/O register",
T1Error.COMMUNICATION_BUSY => "Communication busy",
T1Error.FORMAT_ERROR => "Format error",
T1Error.SCAN_TIME_OVER => "Scan time over",
T1Error.NO_END_IRET_ERROR => "No END/IRET error",
T1Error.PAIR_INSTRUCTION_ERROR => "Pair instruction error",
T1Error.OPERAND_ERROR => "Operand error",
T1Error.INVALID_PROGRAM => "Invalid program",
T1Error.NO_SUBROUTINE_ENTRY => "No subroutine entry",
T1Error.NO_RET_ERROR => "No RET error",
T1Error.SUBROUTINE_NESTING_ERROR => "Subroutine nesting error",
T1Error.LOOP_NESTING_ERROR => "Loop nesting error",
T1Error.INVALID_FUNCTION_INSTRUCTION => "Invalid function instruction",
T1Error.PASSWORD_PROTECT => "Password protect",
T1Error.ILLEGAL_INSTRUCTION => "Illegal instruction",
T1Error.REGISTER_ADDRESS_ERROR => "Register address error",
T1Error.BOUNDARY_ERROR => "Boundary error",
T1Error.MEMORY_FULL => "Memory full",
T1Error.MODE_MISMATCH => "Mode mismatch",
T1Error.REGISTER_ADDRESS_SIZE_ERROR => "Register address/size error",
T1Error.DUPLICATE_ENTRY_NR => "Duplicate entry No.",
_ => error.ToString(),
};
}
public static string? GetDescription(this T1Error error) {
return error switch {
T1Error.NO_ERROR => "No error recorded",
T1Error.SYSTEM_POWER_ON => "Power on (no error)",
T1Error.SYSTEM_POWER_OFF => "Power off (no error)",
T1Error.RAM_CHECK_ERROR => "RAM read/write error has been detected",
T1Error.PROGRAM_BCC_ERROR => "Program BCC (memory check code) error has been detected",
T1Error.BATTERY_VOLTAGE_DROP => "Data invalidity of RAM (back-up area) has been detected",
T1Error.EEPROM_BCC_ERROR => "BCC error of built-in EEPROM has been detected",
T1Error.EEPROM_WARNING => "EEPROM write operation has exceeded 100,000 times",
T1Error.SYSTEM_RAM_CHECK_ERROR => "System RAM read/write error has been detected",
T1Error.SYSTEM_ROM_BCC_ERROR => "BCC error of system ROM has been detected",
T1Error.PERIPHERAL_LSI_ERROR => "CPU hardware error has been detected",
T1Error.CLOCK_CALENDAR_CHECK_ERROR => "Invalid clock-calendar data has been detected",
T1Error.ILLEGAL_SYSTEM_INTERRUPT => "Unregistered interrupt has occurred",
T1Error.WD_TIMER_ERROR => "Watchdog timer error has occurred",
T1Error.IO_BUS_ERROR => "I/O bus error has been detected",
T1Error.IO_MISMATCH => "Registered I/O allocation table and actual I/O configuration are not identical",
T1Error.IO_NO_ANSWER => "No response from I/O module has been received",
T1Error.IO_PARITY_ERROR => "I/O bus parity error has been detected",
T1Error.ILLEGAL_IO_REGISTER => "Excess I/O register allocation has been detected",
T1Error.COMMUNICATION_BUSY => "The T1/T1S is busy in processing for other peripheral communications",
T1Error.FORMAT_ERROR => "Received request is invalid (detected by the T1/T1S)",
T1Error.SCAN_TIME_OVER => "Scan time has exceeded 200 ms",
T1Error.NO_END_IRET_ERROR => "END or IRET instruction has not been programmed",
T1Error.PAIR_INSTRUCTION_ERROR => "Illegal combination of pair instructions has been programmed",
T1Error.OPERAND_ERROR => "Illegal operand has been detected",
T1Error.INVALID_PROGRAM => "Program abnormality has been detected",
T1Error.NO_SUBROUTINE_ENTRY => "Subroutine corresponding to CALL instruction has not been programmed",
T1Error.NO_RET_ERROR => "RET (subroutine return) instruction has not been in a subroutine",
T1Error.SUBROUTINE_NESTING_ERROR => "CALL instruction has been programmed in a subroutine (subroutine nesting)",
T1Error.LOOP_NESTING_ERROR => "Nesting of FOR-NEXT loop has been programmed",
T1Error.INVALID_FUNCTION_INSTRUCTION => "Function instruction which is not supported by T1/T1S has been programmed",
T1Error.PASSWORD_PROTECT => "Requested operation is protected by password",
T1Error.ILLEGAL_INSTRUCTION => "Illegal instruction has been detected",
T1Error.REGISTER_ADDRESS_ERROR => "Excess register address range has been programmed",
T1Error.BOUNDARY_ERROR => "Illegal register address is designated by index modification",
T1Error.MEMORY_FULL => "Program memory is insufficient for the requested command",
T1Error.MODE_MISMATCH => "Received command is invalid in the current T1/T1S operation mode",
T1Error.REGISTER_ADDRESS_SIZE_ERROR => "Specified register range exceeds the limit",
T1Error.DUPLICATE_ENTRY_NR => "Multiple subroutines which has same subroutine number have been programmed",
_ => null,
};
}
}
}
@@ -0,0 +1,4 @@
namespace PamhagenSysCtrl.Helpers.Plc {
public class T1Exception(string msg) : Exception(msg) {
}
}