Your First Click PLC Program
Write a motor start/stop circuit, test it, map it to Click hardware addresses, export ladder CSV, and load it into Click via ClickNick.
The program
A sealed motor circuit: press Start to latch the motor on, press Stop to reset it off. A speed input copies through only while the motor runs.
from pyrung import Bool, Real, PLC, Program, Rung, copy, latch, reset, rise
from pyrung.click import x, y, ds, df, TagMap
# Semantic tags — no hardware addresses yet
StartButton = Bool("StartButton")
StopButton = Bool("StopButton")
MotorRunning = Bool("MotorRunning")
Speed = Real("Speed")
DisplaySpeed = Real("DisplaySpeed")
with Program() as logic:
with Rung(rise(StartButton)):
latch(MotorRunning)
with Rung(rise(StopButton)):
reset(MotorRunning)
with Rung(MotorRunning):
copy(Speed, DisplaySpeed)
rise() triggers on the rising edge — one scan pulse when the button transitions from off to on. Without it, holding Start would re-latch every scan (harmless here, but wrong for counting or toggling).
Test it
def test_motor_start_stop():
with PLC(logic, dt=0.1) as plc:
# Start the motor
StartButton.value = True
plc.step()
assert MotorRunning.value is True
# Release button — motor stays latched
StartButton.value = False
plc.run(cycles=5)
assert MotorRunning.value is True
# Stop the motor
StopButton.value = True
plc.step()
assert MotorRunning.value is False
# Speed only copies while running
StopButton.value = False
StartButton.value = True
Speed.value = 75.0
plc.step()
assert DisplaySpeed.value == 75.0
StartButton.value = False
StopButton.value = True
plc.step()
Speed.value = 99.0
plc.step()
assert DisplaySpeed.value == 75.0 # Didn't update — motor is off
Same logic, deterministic timing, real assertions. Run with pytest.
Map to Click hardware
Once the logic is correct, map semantic tags to Click memory addresses:
mapping = TagMap({
StartButton: x[1], # X001 — discrete input
StopButton: x[2], # X002
MotorRunning: y[1], # Y001 — discrete output
Speed: df[1], # DF1 — float register (analog input)
DisplaySpeed: df[11], # DF11
})
Validate that the program fits Click's constraints:
The validator checks type compatibility, instruction support, and addressing limits. Fix any findings, then tighten to mode="strict" when the program is clean.
Export ladder CSV
from pyrung.click import pyrung_to_ladder
bundle = pyrung_to_ladder(logic, mapping)
bundle.write("./output") # writes main.csv + subroutines/*.csv
This produces deterministic Click ladder CSV files ready for import.
Load into Click via ClickNick
ClickNick loads the exported CSV into Click Programming Software via the clipboard. In the GUI, use Ladder → Open in Guided Paste... and point it at the output folder. Or from the command line:
Either way, ClickNick walks you through pasting each rung and subroutine into Click. The addresses, nicknames, and logic are all wired up.
Next steps
- Click PLC Dialect — pre-built blocks, TagMap details, validation findings, nickname CSV I/O
- Click Python Codegen — round-trip: import Click ladder CSV back into pyrung
- Testing Guide — forces, time travel, forking, pytest patterns
- ClickNick — the companion tool for Click clipboard I/O and nickname management