Skip to content

pyrung

Ladder logic in Python that reads like ladder, scans like a PLC, and deploys to real hardware.

from pyrung import Bool, PLC, Program, Rung, out

Button = Bool("Button")
Light  = Bool("Light")

with Program() as logic:
    with Rung(Button):
        out(Light)

with PLC(logic) as plc:
    Button.value = True
    plc.step()
    assert Light.value is True

What it does

Ladder logic has always been a domain language for industrial control. pyrung asks a simple question: what if that language lived in Python?

For controls engineers: Write and simulate Click PLC logic without hardware or proprietary software. Use plain tag names from day one. Add hardware addresses when you're ready. A validator checks your program against Click constraints and tells you exactly what to fix.

For developers: VS Code becomes your PLC programming environment — step through scans, set breakpoints on rungs, watch tags update inline, and force overrides from the debug console.

For makers and P1AM-200 users: The same program can generate a deployable CircuitPython scan loop with built-in ladder instructions, Modbus TCP, and SD card persistence — no plumbing required.

How it works

Every scan is a snapshot. Logic is a pure function — the same inputs always produce the same outputs, nothing is mutated in place. Every step produces a new immutable state, so history is always there when you want it.

You drive execution. The engine never runs on its own. Call step(), run(), or run_until() from tests, a GUI, or a debugger. Pause anywhere, inject inputs, inspect any historical state.

Time is a variable. dt=0.010 advances the clock by a fixed amount each scan, making timers and counters perfectly deterministic in tests. Rewind and replay whenever you need to.

Write first, validate later. Start with semantic tag names and plain Python. Map to hardware addresses when you're ready, then run the validator. It tells you what Click can and can't do — before you find out at the PLC.

Ask why, not just what. plc.cause(Running) traces backward through scan history to explain exactly why a tag changed — proximate triggers vs. enabling conditions. plc.effect(StartBtn) traces forward to show what it caused. Projected mode answers "what would it take to clear this fault?" without running a single scan.

Prove it before you ship it. prove() exhaustively checks a property over all reachable states — no test cases to write, no scenarios to miss. Automated fault coverage proves every device coupling has an alarm path. Lock files catch behavioral regressions in PRs.