Skip to content

Quickstart

Connect to a simulated PLC, read and write values, then generate CLICK project files — no hardware required.

Install

# Requires Python 3.11+
uv add pyclickplc

Connect and read/write

Start a simulated PLC with ClickServer, then read and write registers with ClickClient:

import asyncio
from pyclickplc import ClickClient, ClickServer, MemoryDataProvider

async def main():
    provider = MemoryDataProvider()
    provider.bulk_set({"DS1": 100, "DS2": 200, "C1": True})

    async with ClickServer(provider, host="localhost", port=5020):
        async with ClickClient("localhost", 5020) as plc:
            # Read a single register
            ds1 = await plc.ds[1]
            print(f"DS1 = {ds1}")  # 100

            # Write a register
            await plc.ds.write(1, 999)
            print(f"DS1 = {await plc.ds[1]}")  # 999

            # Read a range
            values = await plc.ds.read(1, 2)
            print(values)  # {"DS1": 999, "DS2": 200}

            # Read/write by address string
            await plc.addr.write("C1", False)
            c1 = await plc.addr.read("C1")
            print(f"C1 = {c1}")  # False

asyncio.run(main())

MemoryDataProvider holds PLC values in memory. ClickServer exposes them over Modbus TCP. ClickClient talks Modbus TCP and returns native Python types — bool for coils, int for data registers, float for DF, str for TXT.

Traffic light simulator

A more realistic example: a state machine that cycles a traffic light through red, green, and yellow.

import asyncio
from pyclickplc import ClickClient, ClickServer, MemoryDataProvider

STATES = {
    "red":    {"C1": True,  "C2": False, "C3": False, "TXT1": "RED"},
    "green":  {"C1": False, "C2": False, "C3": True,  "TXT1": "GREEN"},
    "yellow": {"C1": False, "C2": True,  "C3": False, "TXT1": "YELLOW"},
}
CYCLE = ["red", "green", "yellow"]
DURATIONS = {"red": 3, "green": 3, "yellow": 1}

async def run_traffic_light(provider):
    """Cycle through states, updating the provider directly."""
    idx = 0
    while True:
        state = CYCLE[idx]
        provider.bulk_set(STATES[state])
        print(f"Light: {state}")
        await asyncio.sleep(DURATIONS[state])
        idx = (idx + 1) % len(CYCLE)

async def main():
    provider = MemoryDataProvider()
    provider.bulk_set(STATES["red"])

    # Run the state machine in the background
    asyncio.create_task(run_traffic_light(provider))

    async with ClickServer(provider, host="localhost", port=5020):
        async with ClickClient("localhost", 5020) as plc:
            for _ in range(5):
                await asyncio.sleep(2)
                txt = await plc.addr.read("TXT1")
                c1 = await plc.addr.read("C1")
                c2 = await plc.addr.read("C2")
                c3 = await plc.addr.read("C3")
                print(f"  Client sees: {txt}  (R={c1} Y={c2} G={c3})")

asyncio.run(main())

The server side updates MemoryDataProvider directly. The client reads over Modbus TCP and gets the current state — same as it would from a real PLC.

Export to ClickNick

Generate nickname CSV and DataView CDV files that you can load in ClickNick or CLICK programming software:

from pyclickplc import (
    make_address_record,
    make_dataview_record,
    write_cdv,
    write_csv,
)

# Nickname CSV — maps addresses to human-readable names
nicknames = [
    make_address_record("C1", nickname="RedLight"),
    make_address_record("C2", nickname="YellowLight"),
    make_address_record("C3", nickname="GreenLight"),
    make_address_record("TXT1", nickname="TrafficState"),
]
write_csv("traffic_light_nicknames.csv", nicknames)

# DataView CDV — defines a monitoring view
write_cdv("traffic_light_dataview.cdv", [
    make_dataview_record(r.display_address) for r in nicknames
])

The CSV file uses the same format as CLICK programming software's nickname export. The CDV file is a UTF-16 LE CSV that CLICK's DataView feature reads directly.

Next steps

  • Client guide — bank accessors, string addresses, and tag-based access
  • Types & values — what Python types each bank returns
  • Addressing — normalization, sparse X/Y ranges, XD/YD display indexing
  • Examples — full runnable scripts