The PLC program from the old repo's 04-plc/, flattened into one folder and
checked against the running system.
Verified during the move:
- build.py regenerates register-map.csv byte-identically (69 points)
- polled the live PLC: the SIMULATION build is what is deployed and
running, %MW21=2 wet weather, values moving, run hours accumulating
- addresses, %MW HR1024 segmentation and %QW17/%QW7 signedness all
match the map
Corrections against the old repo:
- 10_globals.st header cited WRPS-CTL-002 (the FDS); it means CTL-003
- build.py wrote the map to its parent directory; now beside itself
- deploy/README.md was a single-file folder; now DEPLOY.md
- dropped the empty editor-devices/remote/
- README no longer claims the simulation build is uncompiled - it is
the one running
Two open items are now stated plainly rather than buried:
- none of the 20 acceptance tests in CTL-003 have ever been run
- the OpenPLC Editor lived only on the retired dev-ubuntu host, so
there is currently NO route to deploy a new program (DEPLOY.md 0)
Documents the setpoint distinction: IO_MUX seeds %MW defaults once at
first scan, operators retune them live, and that tuning exists only in
the container volume - a restart reverts it.
595 lines
20 KiB
Smalltalk
595 lines
20 KiB
Smalltalk
(* =====================================================================
|
|
30_prog_control.st - PROGRAM CONTROL, section 4.5
|
|
|
|
Contains no located variable reference of any kind. Every input
|
|
arrives through the process image globals written by IO_MUX, and
|
|
every output leaves the same way. This is what makes the pass 2
|
|
simulation a mux change rather than a control change.
|
|
|
|
Execution order below follows section 4.5 step for step.
|
|
===================================================================== *)
|
|
|
|
PROGRAM CONTROL
|
|
VAR_EXTERNAL
|
|
(* process image in *)
|
|
g_LevelRaw_mm : INT;
|
|
g_Level_mm : INT;
|
|
g_Level_m : REAL;
|
|
g_Inflow_Lps : REAL;
|
|
g_Disch_Lps : REAL;
|
|
g_PumpP_kPa : ARRAY[1..3] OF REAL;
|
|
g_Vib_mms : ARRAY[1..3] OF REAL;
|
|
g_LSHH : BOOL;
|
|
g_LSLL_Wet : BOOL;
|
|
g_SpillDetected : BOOL;
|
|
g_ThermalOK : ARRAY[1..3] OF BOOL;
|
|
g_SealLeak : ARRAY[1..3] OF BOOL;
|
|
g_MainsOK : BOOL;
|
|
|
|
(* commands and setpoints in, unvalidated *)
|
|
g_cmd_Mode : INT;
|
|
g_cmd_Word : INT;
|
|
g_cmd_Param : INT;
|
|
g_sp_Level : INT;
|
|
g_sp_StartDuty : INT;
|
|
g_sp_StartP2 : INT;
|
|
g_sp_StartP3 : INT;
|
|
g_sp_StopAll : INT;
|
|
g_sp_HighAlarm : INT;
|
|
g_sp_MinSpeed : INT;
|
|
g_sp_ServiceHrs : INT;
|
|
|
|
(* published out *)
|
|
g_o_RunCmd : ARRAY[1..3] OF BOOL;
|
|
g_o_Running : ARRAY[1..3] OF BOOL;
|
|
g_o_Available : ARRAY[1..3] OF BOOL;
|
|
g_o_Tripped : ARRAY[1..3] OF BOOL;
|
|
g_o_InAuto : BOOL;
|
|
g_o_HighLevel : BOOL;
|
|
g_o_SpillActive : BOOL;
|
|
g_o_Level_mm : INT;
|
|
g_o_Inflow_x10 : INT;
|
|
g_o_Disch_x10 : INT;
|
|
g_o_PumpsRun : INT;
|
|
g_o_Speed_x10 : INT;
|
|
g_o_TimeToSpill : INT;
|
|
g_o_TimeToLSHH : INT;
|
|
g_o_NetAccum : INT;
|
|
g_o_RunHours : ARRAY[1..3] OF INT;
|
|
g_o_VolToSpill : INT;
|
|
g_o_StationState: INT;
|
|
g_o_PumpState : ARRAY[1..3] OF INT;
|
|
g_o_DutyPump : INT;
|
|
g_o_AlarmWord : INT;
|
|
g_o_CmdAck : INT;
|
|
END_VAR
|
|
|
|
VAR CONSTANT
|
|
SPILL_MM : INT := 6000; (* spill weir, mm *)
|
|
LEVEL_MAX_MM : INT := 7000; (* LIT-101 range top *)
|
|
HARD_MIN_HZ : REAL := 38.0;
|
|
HARD_MAX_HZ : REAL := 50.0;
|
|
END_VAR
|
|
|
|
VAR
|
|
(* --- function block instances. Three explicit pump instances
|
|
rather than an ARRAY OF FB_PUMP: arrays of function blocks
|
|
are the kind of construct section 9 warns about. --------- *)
|
|
Pump1 : FB_PUMP;
|
|
Pump2 : FB_PUMP;
|
|
Pump3 : FB_PUMP;
|
|
Duty : FB_DUTY_SELECT;
|
|
LvlCtl : FB_LEVEL_CTRL;
|
|
Head : FB_HEADROOM;
|
|
|
|
(* --- validated setpoints, seeded with the section 2.3 defaults
|
|
and held at the last good value on a bad write ---------- *)
|
|
v_Mode : INT := 1;
|
|
v_SpLevel : INT := 4200;
|
|
v_StartDuty : INT := 4000;
|
|
v_StartP2 : INT := 4500;
|
|
v_StartP3 : INT := 5000;
|
|
v_StopAll : INT := 1000;
|
|
v_HighAlarm : INT := 5200;
|
|
v_MinSpeed : INT := 380;
|
|
v_ServiceHrs : INT := 4000;
|
|
SpRejected : BOOL; (* latched, bit 15, cleared by cmd 6 *)
|
|
SpOK : BOOL;
|
|
|
|
(* --- command handshake ------------------------------------------ *)
|
|
CmdBusy : BOOL;
|
|
ResetTrip : ARRAY[1..3] OF BOOL;
|
|
ResetHours : ARRAY[1..3] OF BOOL;
|
|
Lockout : ARRAY[1..3] OF BOOL;
|
|
AckAlarms : BOOL;
|
|
p : INT;
|
|
|
|
(* --- staging ---------------------------------------------------- *)
|
|
PumpsRequired : INT; (* held across scans - the hysteresis *)
|
|
PumpsAllowed : INT; (* after start stagger *)
|
|
StaggerTmr : TON;
|
|
StaggerArm : BOOL;
|
|
|
|
(* --- interlocks -------------------------------------------------- *)
|
|
DryRun : BOOL;
|
|
DryLockout : BOOL;
|
|
LevelRangeFault : BOOL;
|
|
LevelFrozen : BOOL;
|
|
LevelFault : BOOL;
|
|
LevelRef : INT;
|
|
LevelMoved : BOOL;
|
|
FrozenTmr : TON;
|
|
AnyRunning : BOOL;
|
|
|
|
(* --- duty selector interface ------------------------------------ *)
|
|
Avail : ARRAY[1..3] OF BOOL;
|
|
Hours : ARRAY[1..3] OF REAL;
|
|
SvcDue : ARRAY[1..3] OF BOOL;
|
|
RunNow : ARRAY[1..3] OF BOOL;
|
|
Req : ARRAY[1..3] OF BOOL;
|
|
|
|
(* --- misc -------------------------------------------------------- *)
|
|
Speed : REAL;
|
|
MinSpeedHz : REAL;
|
|
SpLevel_m : REAL;
|
|
HighLevel : BOOL;
|
|
PumpsRun : INT;
|
|
Alarm : DINT;
|
|
i : INT;
|
|
r : REAL;
|
|
Primed : BOOL := FALSE;
|
|
END_VAR
|
|
|
|
(* =====================================================================
|
|
Step 1 - read and clamp setpoints, section 2.3
|
|
|
|
Every setpoint is validated as a set, not individually: the start
|
|
levels only make sense in order. A rejected write holds the last
|
|
good value and raises bit 15 rather than acting on it.
|
|
===================================================================== *)
|
|
|
|
(* Mode *)
|
|
IF (g_cmd_Mode = 1) OR (g_cmd_Mode = 2) THEN
|
|
v_Mode := g_cmd_Mode;
|
|
ELSE
|
|
SpRejected := TRUE;
|
|
END_IF;
|
|
|
|
(* Level setpoints. A start level at or above the spill weir must
|
|
never be accepted, section 2.3. *)
|
|
SpOK := TRUE;
|
|
IF (g_sp_StopAll < 0) OR (g_sp_StopAll >= g_sp_StartDuty) THEN
|
|
SpOK := FALSE;
|
|
END_IF;
|
|
IF (g_sp_StartDuty >= g_sp_StartP2) OR (g_sp_StartDuty >= SPILL_MM) THEN
|
|
SpOK := FALSE;
|
|
END_IF;
|
|
IF (g_sp_StartP2 >= g_sp_StartP3) OR (g_sp_StartP2 >= SPILL_MM) THEN
|
|
SpOK := FALSE;
|
|
END_IF;
|
|
IF (g_sp_StartP3 >= SPILL_MM) THEN
|
|
SpOK := FALSE;
|
|
END_IF;
|
|
IF (g_sp_Level <= g_sp_StopAll) OR (g_sp_Level >= SPILL_MM) THEN
|
|
SpOK := FALSE;
|
|
END_IF;
|
|
IF (g_sp_HighAlarm <= 0) OR (g_sp_HighAlarm > SPILL_MM) THEN
|
|
SpOK := FALSE;
|
|
END_IF;
|
|
|
|
IF SpOK THEN
|
|
v_SpLevel := g_sp_Level;
|
|
v_StartDuty := g_sp_StartDuty;
|
|
v_StartP2 := g_sp_StartP2;
|
|
v_StartP3 := g_sp_StartP3;
|
|
v_StopAll := g_sp_StopAll;
|
|
v_HighAlarm := g_sp_HighAlarm;
|
|
ELSE
|
|
SpRejected := TRUE;
|
|
END_IF;
|
|
|
|
(* Minimum drive speed, Hz x 10, bounded by the hard physical limits *)
|
|
IF (g_sp_MinSpeed >= 380) AND (g_sp_MinSpeed <= 500) THEN
|
|
v_MinSpeed := g_sp_MinSpeed;
|
|
ELSE
|
|
SpRejected := TRUE;
|
|
END_IF;
|
|
|
|
(* Service interval *)
|
|
IF g_sp_ServiceHrs > 0 THEN
|
|
v_ServiceHrs := g_sp_ServiceHrs;
|
|
ELSE
|
|
SpRejected := TRUE;
|
|
END_IF;
|
|
|
|
MinSpeedHz := INT_TO_REAL(v_MinSpeed) / 10.0;
|
|
IF MinSpeedHz < HARD_MIN_HZ THEN
|
|
MinSpeedHz := HARD_MIN_HZ;
|
|
END_IF;
|
|
SpLevel_m := INT_TO_REAL(v_SpLevel) / 1000.0;
|
|
|
|
|
|
(* =====================================================================
|
|
Step 2 - command word and acknowledge, section 3.3
|
|
|
|
Executes on the rising edge of a non-zero %MW1, echoes the value to
|
|
%QW20, then takes no further action until %MW1 returns to 0.
|
|
===================================================================== *)
|
|
|
|
(* one-shot pulses, consumed by the FB_PUMP calls later this scan *)
|
|
FOR i := 1 TO 3 DO
|
|
ResetTrip[i] := FALSE;
|
|
ResetHours[i] := FALSE;
|
|
END_FOR;
|
|
AckAlarms := FALSE;
|
|
|
|
IF (g_cmd_Word <> 0) AND NOT CmdBusy THEN
|
|
CmdBusy := TRUE;
|
|
p := g_cmd_Param;
|
|
|
|
CASE g_cmd_Word OF
|
|
1: (* reset all trips *)
|
|
FOR i := 1 TO 3 DO
|
|
ResetTrip[i] := TRUE;
|
|
END_FOR;
|
|
(* the dry run lockout is manual-reset and only clears once
|
|
the level has actually recovered, section 5 *)
|
|
IF g_Level_mm > v_StopAll THEN
|
|
DryLockout := FALSE;
|
|
END_IF;
|
|
|
|
2: (* reset trip on pump in %MW2 *)
|
|
IF (p >= 1) AND (p <= 3) THEN
|
|
ResetTrip[p] := TRUE;
|
|
END_IF;
|
|
|
|
3: (* lock out pump in %MW2 *)
|
|
IF (p >= 1) AND (p <= 3) THEN
|
|
Lockout[p] := TRUE;
|
|
END_IF;
|
|
|
|
4: (* release lockout on pump in %MW2 *)
|
|
IF (p >= 1) AND (p <= 3) THEN
|
|
Lockout[p] := FALSE;
|
|
END_IF;
|
|
|
|
5: (* reset run hours on pump in %MW2 - service done *)
|
|
IF (p >= 1) AND (p <= 3) THEN
|
|
ResetHours[p] := TRUE;
|
|
END_IF;
|
|
|
|
6: (* acknowledge alarms *)
|
|
AckAlarms := TRUE;
|
|
SpRejected := FALSE;
|
|
END_CASE;
|
|
|
|
g_o_CmdAck := g_cmd_Word;
|
|
|
|
ELSIF g_cmd_Word = 0 THEN
|
|
CmdBusy := FALSE;
|
|
g_o_CmdAck := 0;
|
|
END_IF;
|
|
|
|
|
|
(* =====================================================================
|
|
Level signal integrity, section 5
|
|
|
|
A frozen transmitter reading a plausible value is the failure that
|
|
actually causes spills, and a range check alone cannot see it.
|
|
===================================================================== *)
|
|
|
|
LevelRangeFault := (g_LevelRaw_mm < 0) OR (g_LevelRaw_mm > LEVEL_MAX_MM);
|
|
|
|
IF NOT Primed THEN
|
|
LevelRef := g_LevelRaw_mm;
|
|
Primed := TRUE;
|
|
END_IF;
|
|
|
|
IF ABS(g_LevelRaw_mm - LevelRef) > 1 THEN
|
|
LevelRef := g_LevelRaw_mm;
|
|
LevelMoved := TRUE;
|
|
ELSE
|
|
LevelMoved := FALSE;
|
|
END_IF;
|
|
|
|
AnyRunning := Pump1.Running OR Pump2.Running OR Pump3.Running;
|
|
FrozenTmr(IN := AnyRunning AND NOT LevelMoved, PT := T#10m);
|
|
LevelFrozen := FrozenTmr.Q;
|
|
|
|
LevelFault := LevelRangeFault OR LevelFrozen;
|
|
|
|
|
|
(* =====================================================================
|
|
Step 3 - determine PumpsRequired from level
|
|
|
|
The band between StopAll and StartDuty holds the previous value.
|
|
That hysteresis is the whole point; it is never recomputed from
|
|
scratch.
|
|
===================================================================== *)
|
|
|
|
IF NOT LevelFault THEN
|
|
IF g_Level_mm >= v_StartP3 THEN
|
|
PumpsRequired := 3;
|
|
ELSIF g_Level_mm >= v_StartP2 THEN
|
|
PumpsRequired := 2;
|
|
ELSIF g_Level_mm >= v_StartDuty THEN
|
|
PumpsRequired := 1;
|
|
ELSIF g_Level_mm <= v_StopAll THEN
|
|
PumpsRequired := 0;
|
|
END_IF;
|
|
(* otherwise: hold *)
|
|
ELSE
|
|
(* Fall back to discrete level control, section 5. LSHH and LSLL
|
|
are independent instruments and remain trustworthy. *)
|
|
IF g_LSHH THEN
|
|
PumpsRequired := 3;
|
|
END_IF;
|
|
(* otherwise: hold, and let the LSLL override below stop the
|
|
station if the well is actually dry *)
|
|
END_IF;
|
|
|
|
|
|
(* =====================================================================
|
|
Step 4 - LSHH override. Start all available, bypass min-off.
|
|
===================================================================== *)
|
|
IF g_LSHH THEN
|
|
PumpsRequired := 3;
|
|
END_IF;
|
|
|
|
|
|
(* =====================================================================
|
|
Step 5 - LSLL override. Fail-safe: the instrument reads TRUE when
|
|
wet, so a broken wire reads dry and stops the station.
|
|
===================================================================== *)
|
|
DryRun := NOT g_LSLL_Wet;
|
|
IF DryRun THEN
|
|
PumpsRequired := 0;
|
|
DryLockout := TRUE; (* latched, manual reset via command 1 *)
|
|
END_IF;
|
|
IF DryLockout THEN
|
|
PumpsRequired := 0;
|
|
END_IF;
|
|
|
|
|
|
(* =====================================================================
|
|
Step 6 - station mode off
|
|
===================================================================== *)
|
|
IF v_Mode = 2 THEN
|
|
PumpsRequired := 0;
|
|
END_IF;
|
|
|
|
|
|
(* =====================================================================
|
|
Step 11 (applied here, before selection) - stagger starts
|
|
|
|
Held second and third starts by 30 s each, to limit inrush and the
|
|
hydraulic transient. Applied before FB_DUTY_SELECT because it
|
|
limits how many units may start, which is an input to selection,
|
|
not a correction applied afterwards. Stops are never staggered.
|
|
|
|
LSHH bypasses the stagger as well as the min-off timers, so that
|
|
the emergency response is immediate.
|
|
===================================================================== *)
|
|
IF g_LSHH THEN
|
|
PumpsAllowed := PumpsRequired;
|
|
StaggerArm := FALSE;
|
|
ELSE
|
|
StaggerTmr(IN := StaggerArm, PT := T#30s);
|
|
IF PumpsAllowed < PumpsRequired THEN
|
|
IF PumpsAllowed = 0 THEN
|
|
PumpsAllowed := 1; (* first unit starts at once *)
|
|
StaggerArm := FALSE;
|
|
ELSIF StaggerTmr.Q THEN
|
|
PumpsAllowed := PumpsAllowed + 1;
|
|
StaggerArm := FALSE;
|
|
ELSE
|
|
StaggerArm := TRUE;
|
|
END_IF;
|
|
ELSE
|
|
IF PumpsAllowed > PumpsRequired THEN
|
|
PumpsAllowed := PumpsRequired;
|
|
END_IF;
|
|
StaggerArm := FALSE;
|
|
END_IF;
|
|
END_IF;
|
|
|
|
|
|
(* =====================================================================
|
|
Step 7 - duty selection
|
|
===================================================================== *)
|
|
Avail[1] := Pump1.Available; Avail[2] := Pump2.Available; Avail[3] := Pump3.Available;
|
|
Hours[1] := Pump1.RunHours; Hours[2] := Pump2.RunHours; Hours[3] := Pump3.RunHours;
|
|
SvcDue[1] := Pump1.ServiceDue; SvcDue[2] := Pump2.ServiceDue; SvcDue[3] := Pump3.ServiceDue;
|
|
RunNow[1] := Pump1.Running; RunNow[2] := Pump2.Running; RunNow[3] := Pump3.Running;
|
|
|
|
Duty(Available := Avail,
|
|
RunHours := Hours,
|
|
ServiceDue := SvcDue,
|
|
RunningNow := RunNow,
|
|
PumpsRequired := PumpsAllowed);
|
|
|
|
Req[1] := Duty.RunRequest[1];
|
|
Req[2] := Duty.RunRequest[2];
|
|
Req[3] := Duty.RunRequest[3];
|
|
|
|
|
|
(* =====================================================================
|
|
Step 8 - level control. On LSHH force 50.0 Hz.
|
|
===================================================================== *)
|
|
LvlCtl(Level := g_Level_m,
|
|
Setpoint := SpLevel_m,
|
|
Enable := (PumpsAllowed > 0),
|
|
MinSpeed := MinSpeedHz,
|
|
MaxSpeed := HARD_MAX_HZ);
|
|
|
|
Speed := LvlCtl.Speed;
|
|
IF g_LSHH THEN
|
|
Speed := HARD_MAX_HZ;
|
|
END_IF;
|
|
|
|
|
|
(* =====================================================================
|
|
Step 9 - the pumps
|
|
===================================================================== *)
|
|
Pump1(RunRequest := Req[1],
|
|
SpeedRef := Speed,
|
|
ThermalOK := g_ThermalOK[1],
|
|
SealLeak := g_SealLeak[1],
|
|
Vibration := g_Vib_mms[1],
|
|
DischPressure := g_PumpP_kPa[1],
|
|
ResetTrip := ResetTrip[1],
|
|
Lockout := Lockout[1],
|
|
MinOffBypass := g_LSHH,
|
|
ServiceInterval := INT_TO_REAL(v_ServiceHrs),
|
|
ResetHours := ResetHours[1]);
|
|
|
|
Pump2(RunRequest := Req[2],
|
|
SpeedRef := Speed,
|
|
ThermalOK := g_ThermalOK[2],
|
|
SealLeak := g_SealLeak[2],
|
|
Vibration := g_Vib_mms[2],
|
|
DischPressure := g_PumpP_kPa[2],
|
|
ResetTrip := ResetTrip[2],
|
|
Lockout := Lockout[2],
|
|
MinOffBypass := g_LSHH,
|
|
ServiceInterval := INT_TO_REAL(v_ServiceHrs),
|
|
ResetHours := ResetHours[2]);
|
|
|
|
Pump3(RunRequest := Req[3],
|
|
SpeedRef := Speed,
|
|
ThermalOK := g_ThermalOK[3],
|
|
SealLeak := g_SealLeak[3],
|
|
Vibration := g_Vib_mms[3],
|
|
DischPressure := g_PumpP_kPa[3],
|
|
ResetTrip := ResetTrip[3],
|
|
Lockout := Lockout[3],
|
|
MinOffBypass := g_LSHH,
|
|
ServiceInterval := INT_TO_REAL(v_ServiceHrs),
|
|
ResetHours := ResetHours[3]);
|
|
|
|
|
|
(* =====================================================================
|
|
Step 10 - headroom
|
|
===================================================================== *)
|
|
Head(Level := g_Level_m,
|
|
Inflow := g_Inflow_Lps,
|
|
TotalDischarge := g_Disch_Lps);
|
|
|
|
|
|
(* =====================================================================
|
|
Step 12 - publish
|
|
===================================================================== *)
|
|
|
|
PumpsRun := 0;
|
|
IF Pump1.Running THEN PumpsRun := PumpsRun + 1; END_IF;
|
|
IF Pump2.Running THEN PumpsRun := PumpsRun + 1; END_IF;
|
|
IF Pump3.Running THEN PumpsRun := PumpsRun + 1; END_IF;
|
|
|
|
HighLevel := g_Level_mm >= v_HighAlarm;
|
|
|
|
g_o_RunCmd[1] := Pump1.RunCmd;
|
|
g_o_RunCmd[2] := Pump2.RunCmd;
|
|
g_o_RunCmd[3] := Pump3.RunCmd;
|
|
g_o_Running[1] := Pump1.Running;
|
|
g_o_Running[2] := Pump2.Running;
|
|
g_o_Running[3] := Pump3.Running;
|
|
g_o_Available[1] := Pump1.Available;
|
|
g_o_Available[2] := Pump2.Available;
|
|
g_o_Available[3] := Pump3.Available;
|
|
g_o_Tripped[1] := Pump1.Tripped;
|
|
g_o_Tripped[2] := Pump2.Tripped;
|
|
g_o_Tripped[3] := Pump3.Tripped;
|
|
|
|
g_o_PumpState[1] := Pump1.State;
|
|
g_o_PumpState[2] := Pump2.State;
|
|
g_o_PumpState[3] := Pump3.State;
|
|
|
|
g_o_InAuto := (v_Mode = 1);
|
|
g_o_HighLevel := HighLevel;
|
|
g_o_SpillActive := g_SpillDetected;
|
|
|
|
g_o_Level_mm := g_Level_mm;
|
|
g_o_PumpsRun := PumpsRun;
|
|
g_o_DutyPump := Duty.DutyPump;
|
|
|
|
(* scaled analogues, clamped into 16-bit signed range *)
|
|
r := g_Inflow_Lps * 10.0;
|
|
IF r > 32767.0 THEN r := 32767.0; ELSIF r < -32768.0 THEN r := -32768.0; END_IF;
|
|
g_o_Inflow_x10 := REAL_TO_INT(r);
|
|
|
|
r := g_Disch_Lps * 10.0;
|
|
IF r > 32767.0 THEN r := 32767.0; ELSIF r < -32768.0 THEN r := -32768.0; END_IF;
|
|
g_o_Disch_x10 := REAL_TO_INT(r);
|
|
|
|
r := Speed * 10.0;
|
|
IF r > 32767.0 THEN r := 32767.0; ELSIF r < 0.0 THEN r := 0.0; END_IF;
|
|
g_o_Speed_x10 := REAL_TO_INT(r);
|
|
|
|
r := Head.NetInflow * 10.0;
|
|
IF r > 32767.0 THEN r := 32767.0; ELSIF r < -32768.0 THEN r := -32768.0; END_IF;
|
|
g_o_NetAccum := REAL_TO_INT(r);
|
|
|
|
r := Head.VolToSpill;
|
|
IF r > 32767.0 THEN r := 32767.0; ELSIF r < 0.0 THEN r := 0.0; END_IF;
|
|
g_o_VolToSpill := REAL_TO_INT(r);
|
|
|
|
g_o_TimeToSpill := Head.TimeToSpill;
|
|
g_o_TimeToLSHH := Head.TimeToLSHH;
|
|
|
|
r := Pump1.RunHours;
|
|
IF r > 32767.0 THEN r := 32767.0; END_IF;
|
|
g_o_RunHours[1] := REAL_TO_INT(r);
|
|
r := Pump2.RunHours;
|
|
IF r > 32767.0 THEN r := 32767.0; END_IF;
|
|
g_o_RunHours[2] := REAL_TO_INT(r);
|
|
r := Pump3.RunHours;
|
|
IF r > 32767.0 THEN r := 32767.0; END_IF;
|
|
g_o_RunHours[3] := REAL_TO_INT(r);
|
|
|
|
(* --- station state, section 3.1 ---------------------------------- *)
|
|
IF v_Mode = 2 THEN
|
|
g_o_StationState := 0; (* Off *)
|
|
ELSIF g_LSHH THEN
|
|
g_o_StationState := 4; (* Emergency *)
|
|
ELSIF DryLockout THEN
|
|
g_o_StationState := 5; (* Dry run lockout *)
|
|
ELSIF LevelFault THEN
|
|
g_o_StationState := 6; (* Fault *)
|
|
ELSIF HighLevel THEN
|
|
g_o_StationState := 3; (* High level *)
|
|
ELSIF PumpsRun > 0 THEN
|
|
g_o_StationState := 2; (* Pumping *)
|
|
ELSE
|
|
g_o_StationState := 1; (* Idle *)
|
|
END_IF;
|
|
|
|
(* --- alarm bitmask, section 6.
|
|
|
|
Accumulated in a DINT because bit 15 does not fit a signed INT.
|
|
Values at or above 32768 are folded into the negative half of the
|
|
16-bit word; CI Server must read %QW17 as UNSIGNED. ------------- *)
|
|
Alarm := 0;
|
|
IF HighLevel THEN Alarm := Alarm + 1; END_IF; (* bit0 *)
|
|
IF g_LSHH THEN Alarm := Alarm + 2; END_IF; (* bit1 *)
|
|
IF DryRun OR DryLockout THEN Alarm := Alarm + 4; END_IF; (* bit2 *)
|
|
IF g_SpillDetected THEN Alarm := Alarm + 8; END_IF; (* bit3 *)
|
|
IF Pump1.Tripped THEN Alarm := Alarm + 16; END_IF; (* bit4 *)
|
|
IF Pump2.Tripped THEN Alarm := Alarm + 32; END_IF; (* bit5 *)
|
|
IF Pump3.Tripped THEN Alarm := Alarm + 64; END_IF; (* bit6 *)
|
|
IF Pump1.SealAlarm THEN Alarm := Alarm + 128; END_IF; (* bit7 *)
|
|
IF Pump2.SealAlarm THEN Alarm := Alarm + 256; END_IF; (* bit8 *)
|
|
IF Pump3.SealAlarm THEN Alarm := Alarm + 512; END_IF; (* bit9 *)
|
|
IF Pump1.VibAlarm THEN Alarm := Alarm + 1024; END_IF; (* bit10 *)
|
|
IF Pump2.VibAlarm THEN Alarm := Alarm + 2048; END_IF; (* bit11 *)
|
|
IF Pump3.VibAlarm THEN Alarm := Alarm + 4096; END_IF; (* bit12 *)
|
|
IF LevelFault THEN Alarm := Alarm + 8192; END_IF; (* bit13 *)
|
|
IF NOT g_MainsOK THEN Alarm := Alarm + 16384; END_IF; (* bit14 *)
|
|
IF SpRejected THEN Alarm := Alarm + 32768; END_IF; (* bit15 *)
|
|
|
|
IF Alarm >= 32768 THEN
|
|
g_o_AlarmWord := DINT_TO_INT(Alarm - 65536);
|
|
ELSE
|
|
g_o_AlarmWord := DINT_TO_INT(Alarm);
|
|
END_IF;
|
|
|
|
END_PROGRAM
|