Skip to content

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:

report = mapping.validate(logic, mode="warn")
print(report.summary())

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:

clicknick-rung program load ./output

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