namespace PamhagenSysCtrl.Helpers.Plc { public readonly record struct PamhagenState { private readonly LegacyPamhagenPlc.SensorStates Sensors; private readonly Func 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 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 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 masks) { return masks.Any(m => state.Valves == m && ~state.PressureSensors == m); } private static bool HasAnyClearance(PamhagenState state, IEnumerable 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(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(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(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(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(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(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)) ); } } }