The existing Controller.hpp and Controller.cpp already separate control decisions from physical movement. FactoryFlow.st translates that same controller into IEC 61131-3 Structured Text (ST). It adds no simulation or control features. Read the ST file from top to bottom: data types, controller, cyclic caller.
| C++ concept | PLC equivalent | Meaning in FactoryFlow |
|---|---|---|
Inputs |
A structure of BOOL values representing the input process image |
A snapshot of sensor and fault signals used for this scan. |
Outputs |
A structure of BOOL commands representing the output process image |
Motor and diverter commands sent to output channels after execution. |
Controller object |
An FB_Controller function block instance |
Owns sequence state across calls. |
scan(inputs) |
One execution of the function block body | Checks safety, updates state, then calculates outputs. |
State and switch |
An enumerated type and CASE |
Selects one sequence branch per call. |
Sensor calculations in main.cpp |
Physical sensors connected to digital input channels | The plant supplies feedback; the controller does not calculate box position. |
conveyorMotor |
A digital run command to a drive or motor starter | Requests motion; it is not motor power or a speed setpoint. |
diverterA/B/C |
Digital commands to diverter actuators, such as valve solenoids | Only B is selected in this milestone. Commands are not arrival feedback. |
| The approximately 10 ms C++ loop | A cyclic PLC task with a 10 ms interval | Calls the same controller instance once each cycle. |
A function block preserves instance state between executions, which is why it
matches the C++ object. Here state is exposed as an output for observation,
like Controller::state(); callers should not write it. This scan-to-scan memory
does not imply power-loss retention. See the vendor documentation for
function blocks.
| Input | Current simulator | Physical interpretation |
|---|---|---|
entrySensor |
Box is within 0–0.10 m and has not entered Lane B | Box detected at conveyor entry. |
sortingSensor |
Box is within 1.95–2.05 m and has not entered Lane B | Box detected near the sorting station. |
laneBSensor |
Box has entered Lane B at 2.5 m | Arrival confirmation at Lane B. |
emergencyStop |
Boolean input, normally false | Logical stop-active status: TRUE requests FAULT. |
motorFault |
Boolean input, normally false | Logical drive/starter fault status: TRUE requests FAULT. |
These are logical signal meanings, not electrical wiring polarities. Hardware I/O mapping must provide those meanings to the controller. The C++ code treats safety signals as scan-level inputs; this ST translation has the same scope and is not a safety-rated emergency-stop implementation.
The ST controller does not know the box ID, conveyor speed, meters, or travel time. Those belong to the existing C++ plant or to the physical machine. Lane B selection is fixed; no route lookup is needed. All signals are level-sensitive: there are no edge detectors, timers, or debounce logic.
The C++ loop initializes sensors from the starting position, then repeats:
- Pass the current
Inputssnapshot tocontroller.scan(inputs). - Check the two safety inputs and update the sequence state.
- Calculate
Outputsfrom the resulting state. - Apply the outputs to the simulated plant and advance position by 10 ms.
- Recalculate sensors from position for the next scan.
On a PLC, the physical machine moves independently. The cyclic program reads
mapped inputs, calls controller(inputs := inputs), and copies
controller.outputs to mapped outputs. A common runtime model samples inputs at
task start and transfers outputs to the I/O driver at task end; physical bus timing
depends on the target configuration. See
I/O task timing and
cyclic task configuration.
There is no sleep or infinite loop inside the ST program: the task schedules it.
Before CASE, either safety input forces FAULT, including when the previous state
was COMPLETE. Otherwise the following sequence applies:
| State before sequence | Condition | State after sequence |
|---|---|---|
| IDLE | entrySensor |
TRANSPORTING |
| TRANSPORTING | sortingSensor |
SORTING |
| SORTING | laneBSensor |
COMPLETE |
| Any state | No applicable condition | Unchanged |
There is no fall-through or repeated state evaluation. For example, if all three
sensors are already TRUE at startup, three calls are required to reach COMPLETE.
ST uses a CASE statement
with the same branch ordering as the C++ switch.
Outputs are calculated after that transition, within the same invocation:
| Resulting state | Motor | Diverter A | Diverter B | Diverter C |
|---|---|---|---|---|
| IDLE | OFF | OFF | OFF | OFF |
| TRANSPORTING | ON | OFF | OFF | OFF |
| SORTING | ON | OFF | ON | OFF |
| COMPLETE | OFF | OFF | OFF | OFF |
| FAULT | OFF | OFF | OFF | OFF |
The motor remains on during sorting. Arrival feedback, not diverter activation, causes completion. COMPLETE has no normal exit; a safety fault can still move it to FAULT. FAULT stays latched when inputs clear. Neither version has a reset input. A newly initialized controller begins at IDLE; actual PLC restart/initialization behavior depends on the runtime and its retention settings.
plc/FactoryFlow.st is readable source, not a vendor-specific project or a
configured hardware deployment. In a PLC editor:
- Create the three data types, the
FB_Controllerfunction block, and thePLC_PRGprogram from the corresponding sections. Some editors require separate objects and separate declaration/body panes rather than a file import. - Assign
PLC_PRGto one cyclic task with a 10 ms interval. - Bind
PLC_PRG.inputsmembers to logical input channels andPLC_PRG.outputsmembers to output channels using the target's I/O mapping. No device addresses are prescribed here. Without mapping or manually supplied inputs, the default inputs remain FALSE and the controller stays IDLE. - Keep the same
controllerinstance between scans and call it once per cycle.
The source uses ordinary structs, an enum, IF, and CASE; it needs no libraries.
The task and I/O setup belong to the chosen PLC tool, so they are described here
rather than adding a vendor project to this milestone.
Both implementations use the same initialized state, safety priority, three conditional transitions, and four output assignments. To compare them in a PLC watch window, initialize a fresh instance for each scenario:
| Scenario | Inputs supplied on successive calls | Expected result |
|---|---|---|
| Waiting | All FALSE | IDLE; all outputs OFF. |
| Normal flow | Entry TRUE; sorting TRUE; Lane B TRUE | TRANSPORTING, SORTING, COMPLETE; outputs as above. |
| No arrival yet | Reach SORTING, then all sensors FALSE | Remain SORTING with motor and B ON. |
| Simultaneous sensors | All three sensors TRUE for three calls | TRANSPORTING, then SORTING, then COMPLETE. |
| Emergency stop | Set emergencyStop TRUE in any state |
FAULT; all outputs OFF in that call. |
| Motor fault | Set motorFault TRUE in any state |
FAULT; all outputs OFF in that call. |
| Fault clears | Enter FAULT, then clear both fault inputs | Remain FAULT; all outputs OFF. |
This translation has been reviewed against the C++ source. No ST compiler or PLC runtime is configured in this repository, so CMake/CTest validate only the C++ implementation and simulator, not ST compilation or execution.