Skip to main content

Write ladder logic in Python. Simulate it. Test it. Deploy it -- Currently targets AutomationDirect CLICK PLC and CircuitPython ProductivityOpen P1AM-200.

Project description

pyrung

Write ladder logic in Python. Simulate it. Test it. Deploy to Click PLCs or P1AM-200 hardware.

pyrung turns Python's with block into a ladder rung — condition on the rail, instructions in the body.

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

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

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

runner = PLCRunner(logic)
with runner.active():
    Button.value = True
    runner.step()
    assert Light.value is True

Why?

AutomationDirect Click PLCs have no built-in simulator. You write logic, download it to hardware, and hope. pyrung lets you test first — same tag names, deterministic scans, real assertions. When it works, encode it with pyrung_to_ladder() and paste via clicknick.

Or skip the Click editor entirely — generate a CircuitPython scan loop for a ProductivityOpen P1AM-200 and run your tested logic on open hardware.

Or run your logic as an emulated Click over Modbus to test send/receive, no hardware required. You can even spin up two pyrung programs and test them talking to each other.

Quick start

# Requires Python 3.11+
uv add pyrung

A motor with start/stop logic

from pyrung import Bool, Program, Rung, latch, reset

Start = Bool("Start")
Stop = Bool("Stop")
Running = Bool("Running")

with Program() as logic:
    with Rung(Start):
        latch(Running)
    with Rung(Stop):
        reset(Running)

Test it

from pyrung import PLCRunner

runner = PLCRunner(logic)
with runner.active():
    Start.value = True
    runner.step()
    assert Running.value is True

    # Release start — motor stays latched
    Start.value = False
    runner.step()
    assert Running.value is True

    Stop.value = True
    runner.step()
    assert Running.value is False

Map to Click hardware when you're ready

from pyrung.click import TagMap, x, y

mapping = TagMap({
    Start:   x[1],    # Physical input  → X001
    Stop:    x[2],    # Physical input  → X002
    Running: y[1],    # Physical output → Y001
})

mapping.validate(logic)                # Checks against Click constraints
mapping.to_nickname_file("motor.csv")  # For Click programming software

What's included

Core engine

Pure f(state) → new_state scan cycle with immutable snapshots. Coils, latches, timers, counters, branching, subroutines, structured tags, edge detection, and more. Built to match real Click behavior — no surprises when you move to hardware.

Click PLC dialect

Hardware address mapping, memory bank validation, Modbus instructions, and nickname file export. Run any program as an emulated Click over Modbus for integration testing.

CircuitPython dialect

Generate a self-contained CircuitPython scan loop from the same program you already tested. Targets the ProductivityOpen P1AM-200 with 35 supported I/O modules, SD-backed retentive storage, watchdog, Modbus TCP, and RUN/STOP control.

VS Code debugger

Step through scans rung by rung, set breakpoints, force tags, diff states, and time-travel through scan history.

Learn more

Core Concepts Scan cycle, SystemState, tags, blocks
Instruction Reference Full DSL reference
Tag Structures UDTs, named arrays, cloning, block config
Runner Guide Execution, time modes, history, fork
Testing Guide Unit testing with deterministic time
Forces & Debug Force values, breakpoints, time travel

Disclaimers

  • Simulation is best-effort. pyrung models Click PLC behavior as closely as practical, but it is not a certified simulator. You are responsible for verifying your program on real hardware before production use.
  • Modbus is unauthenticated. The emulated Click Modbus interface and CircuitPython Modbus TCP server listen on the network with no encryption or access control — standard for Modbus, but keep them off untrusted networks.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyrung-0.2.0.tar.gz (983.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyrung-0.2.0-py3-none-any.whl (327.1 kB view details)

Uploaded Python 3

File details

Details for the file pyrung-0.2.0.tar.gz.

File metadata

  • Download URL: pyrung-0.2.0.tar.gz
  • Upload date:
  • Size: 983.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pyrung-0.2.0.tar.gz
Algorithm Hash digest
SHA256 8192e8e24e964fb90f2cc8f4670bade067de92a51d1ea8f2ab81a4833d3db23b
MD5 a28589fdb8bcb0a616dd79dd6d28d895
BLAKE2b-256 e951132855d0438903c481ec457297545a383d332fe558f74e35584484b53211

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyrung-0.2.0.tar.gz:

Publisher: publish.yml on ssweber/pyrung

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pyrung-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: pyrung-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 327.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pyrung-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5b5ef29163178753a6a37ffc2afc2e450816cfb1a6c128ff5eb5dfe1ac2e062e
MD5 d44358493bb740f609e72cca0b64eab6
BLAKE2b-256 f80763a2336969fa0dc2f310adcce44ce23b8413ff8540d2989e36f547beeb4f

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyrung-0.2.0-py3-none-any.whl:

Publisher: publish.yml on ssweber/pyrung

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page