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 it.
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
Status: Core engine, Click PLC dialect, CircuitPython dialect, and VS Code debugger are implemented and tested (~26k lines, 1,600+ tests). Not yet on PyPI. API may still change.
Documentation: https://ssweber.github.io/pyrung/ LLM docs index: https://ssweber.github.io/pyrung/llms.txt LLM full context: https://ssweber.github.io/pyrung/llms-full.txt
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, transpose it to Click.
Or don't transpose at all. Run your program as a soft PLC to test Modbus send/receive — it runs behind a Click-compatible Modbus interface, no hardware required. You can even spin up two pyrung programs and test them talking to each other. Or generate a CircuitPython scan loop for a ProductivityOpen P1AM-200 and run it on actual I/O.
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
tags = TagMap()
tags.map(Start, "X001") # Physical input
tags.map(Stop, "X002") # Physical input
tags.map(Running, "Y001") # Physical output
tags.validate(logic) # Checks against Click constraints
tags.export_nicknames("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 a soft PLC behind a Click-compatible Modbus interface for integration testing. Docs →
CircuitPython dialect — Generates a self-contained scan loop for P1AM-200 hardware from any pyrung program. Docs →
VS Code debugger — Step through scans, set breakpoints on rungs, force tags, diff states, and time-travel through scan history. Docs →
Learn more
| Core Concepts | Scan cycle, SystemState, tags, blocks |
| Ladder Logic Guide | 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 |
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyrung-0.1.0.tar.gz.
File metadata
- Download URL: pyrung-0.1.0.tar.gz
- Upload date:
- Size: 410.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d06eb385aef51c5ed27c723088ec4178ec316ed722e59df69dec4903053f2899
|
|
| MD5 |
c0766d681ad5ec67545e727f1773e5c0
|
|
| BLAKE2b-256 |
35ae182b3a2851ac4d2311e745134d2f3918cfdd5c48c89edae7d5dc0c705e4c
|
Provenance
The following attestation bundles were made for pyrung-0.1.0.tar.gz:
Publisher:
publish.yml on ssweber/pyrung
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyrung-0.1.0.tar.gz -
Subject digest:
d06eb385aef51c5ed27c723088ec4178ec316ed722e59df69dec4903053f2899 - Sigstore transparency entry: 1003993989
- Sigstore integration time:
-
Permalink:
ssweber/pyrung@933305c59c952ddc6c581c3b660d733cd162a1bb -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ssweber
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@933305c59c952ddc6c581c3b660d733cd162a1bb -
Trigger Event:
release
-
Statement type:
File details
Details for the file pyrung-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pyrung-0.1.0-py3-none-any.whl
- Upload date:
- Size: 213.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
af8f7598e5d2a79b2bc04b93616bc420c2822e55f3f4575d115b94006650bdf8
|
|
| MD5 |
c0b1d63cdd58a4bd6410ebb393818099
|
|
| BLAKE2b-256 |
3fb10c5b43800017d4236120a8b3bcf39fca573295155e9bd3db299513b30e46
|
Provenance
The following attestation bundles were made for pyrung-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on ssweber/pyrung
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyrung-0.1.0-py3-none-any.whl -
Subject digest:
af8f7598e5d2a79b2bc04b93616bc420c2822e55f3f4575d115b94006650bdf8 - Sigstore transparency entry: 1003993992
- Sigstore integration time:
-
Permalink:
ssweber/pyrung@933305c59c952ddc6c581c3b660d733cd162a1bb -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ssweber
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@933305c59c952ddc6c581c3b660d733cd162a1bb -
Trigger Event:
release
-
Statement type: