Documentation
The full docs ship with the public release.
This page covers the basics.
Quickstart, concepts, and a worked example. Reference docs are being written alongside the parsers; design partners get them ahead of GA.
For technical questions in the meantime, send a note to hello@koyl.ai.
Step 1
Install.
The framework, IDE, and vendor support ship to design partners ahead of public release.
# coming with public release · today via design partner program
pip install plx Step 2
Write your first FB.
@fb turns a
class into a function block. Descriptors declare its interface;
logic() is
the body the compiler reads.
from plx.framework import fb, input_var, output_var
@fb
class StartStopLatch:
start: bool = input_var()
stop: bool = input_var()
running: bool = output_var()
def logic(self):
if self.stop:
self.running = False
elif self.start:
self.running = True Step 3
Emit native vendor files.
One Python source. Three vendor outputs. The same FB lands as an AOI in ControlLogix, an FB in TwinCAT 3, and an FB in TIA Portal.
from plx.framework import project
from plx.ab import target as ab
from plx.beckhoff import target as beckhoff
from plx.siemens import target as siemens
from controllers import StartStopLatch
proj = project(
name="line_a",
pous=[StartStopLatch],
)
# emit native vendor files
proj.compile().emit(ab, out="exports/line_a.L5X")
proj.compile().emit(beckhoff, out="exports/line_a.TcPOU")
proj.compile().emit(siemens, out="exports/line_a.SimaticML") Step 4
Test it before it touches hardware.
The simulator walks the IR scan by scan. Drive inputs, advance cycles, assert on outputs. pytest discovers the tests automatically.
from plx.simulate import scan
from controllers import StartStopLatch
fb = StartStopLatch()
fb.start = True
scan(fb, cycles=1)
assert fb.running is True
fb.start = False
fb.stop = True
scan(fb, cycles=1)
assert fb.running is False Concepts
Four layers, four jobs.
Framework
What you write.
Decorators turn ordinary Python classes into POUs (program organization units). @fb, @program, @function, @method. Descriptors (input_var, output_var, static_var, inout_var, temp_var) declare interfaces. Type constructors (BOOL, INT, REAL, ARRAY, STRING) build typed references.
src/plx/framework/
Universal IR
What the compiler produces.
A vendor-agnostic Pydantic model. Every IF, FOR, FB invocation, tag reference compiles to a typed IR node. The simulator walks it. AI agents read and write it. Static analyzers query it.
src/plx/model/
Vendor IRs
Where round-trip happens.
Per-vendor Pydantic models that mirror native vendor schemas (L5X, SimaticML, TcPOU). Lossless parsing into and emission from the vendor IR. The Universal IR is a view onto the vendor IR, not a re-encoding.
src/plx/{ab,siemens,beckhoff}/
Simulator
Deterministic scan-cycle execution.
A tree-walking interpreter that executes the IR cycle by cycle. Same inputs produce same outputs. pytest, coverage, and CI work because the source is Python.
src/plx/simulate/
A deeper architectural walkthrough lives at how it works. The capability matrix per vendor lives at multi-vendor round-trip.
Reference (in progress)
What the framework gives you today.
Decorators
- @fb
- @program
- @function
- @method
- @struct
- @enumeration
- @global_vars
Descriptors
- input_var()
- output_var()
- static_var()
- inout_var()
- temp_var()
- global_var()
Types
- BOOL · INT · DINT · REAL · LREAL
- STRING · WSTRING · CHAR · WCHAR
- TIME · DATE · LTIME · LDATE
- ARRAY(T, n) · POINTER_TO(T)
- REFERENCE_TO(T)
Hero examples on the homepage are generated by the live compiler. CI fails if they drift from the framework's actual surface.
Get the rest of the docs
Reference, guides, and worked examples ship to design partners ahead of GA.
Apply for the design partner program for hands-on access to the full toolchain (chat agent, simulator, vendor parsers) and a direct line to the engineers building it.