Skip to content

Latest commit

 

History

History
202 lines (153 loc) · 5.95 KB

File metadata and controls

202 lines (153 loc) · 5.95 KB

Getting Started with plc-toolkit

This guide walks you through writing, compiling, and validating a PLC program using plc-toolkit. No GUI, no vendor IDE — just your terminal.

Prerequisites

  • Nix (for the dev environment)
  • Or: Rust toolchain + LLVM 21 installed manually

1. Set up the environment

git clone https://github.com/Adjoint-uk/plc-toolkit.git
cd plc-toolkit
nix develop

You should see:

PLC Toolkit dev shell (Rust + LLVM 21)
  cargo build       — build all crates
  cargo test        — run tests
  cargo run -- compile examples/blink.st

Build the toolkit:

cargo build

2. Write your first ST program

Create a file called my_program.st:

PROGRAM TrafficLight
VAR
    state : INT := 0;        (* 0=red, 1=green, 2=yellow *)
    timer_count : INT := 0;
    red_duration : INT := 30;
    green_duration : INT := 25;
    yellow_duration : INT := 5;
    red_light : BOOL := TRUE;
    green_light : BOOL := FALSE;
    yellow_light : BOOL := FALSE;
END_VAR
    timer_count := timer_count + 1;

    CASE state OF
        0: (* Red *)
            red_light := TRUE;
            green_light := FALSE;
            yellow_light := FALSE;
            IF timer_count >= red_duration THEN
                state := 1;
                timer_count := 0;
            END_IF;

        1: (* Green *)
            red_light := FALSE;
            green_light := TRUE;
            yellow_light := FALSE;
            IF timer_count >= green_duration THEN
                state := 2;
                timer_count := 0;
            END_IF;

        2: (* Yellow *)
            red_light := FALSE;
            green_light := FALSE;
            yellow_light := TRUE;
            IF timer_count >= yellow_duration THEN
                state := 0;
                timer_count := 0;
            END_IF;
    END_CASE;
END_PROGRAM

This is a simple traffic light controller — a state machine that cycles through red, green, and yellow phases. It's the kind of program that runs on real PLCs controlling real intersections.

3. Validate it

cargo run -- validate my_program.st

If the code is correct you'll see:

my_program.st: ok

4. Compile it

cargo run -- compile my_program.st

This outputs JSON with:

  • success: whether compilation succeeded
  • diagnostics: any errors or warnings, each with a fix suggestion
  • ir: the generated LLVM IR (the compiled output)

To save the LLVM IR to a file:

cargo run -- compile my_program.st -o traffic_light.ll

5. Understand the output

The LLVM IR is a low-level representation of your program. It defines:

  • A struct for your program's variables (%TrafficLight with fields for state, timer_count, lights, etc.)
  • A function that implements the program logic
  • A constructor that sets initial values

From here, LLVM can compile this to native machine code for any target: x86 servers, ARM embedded boards, RISC-V microcontrollers, or even WASM for running in a browser.

6. See what happens with errors

Try compiling something broken:

echo 'PROGRAM Bad
VAR
    x : NONEXISTENT_TYPE;
END_VAR
    x := 42;
END_PROGRAM' > bad.st

cargo run -- compile bad.st

You'll get structured JSON errors:

{
  "success": false,
  "diagnostics": [
    {
      "severity": "error",
      "message": "Unknown type: NONEXISTENT_TYPE",
      "suggestion": "Check spelling of the type name. Standard types: BOOL, INT, DINT, REAL, LREAL, STRING, TIME"
    }
  ]
}

Every error includes a suggestion — designed so both humans and LLMs can fix the problem without guessing.

7. Run it

This is the best part. Instead of just compiling, you can execute your program in a simulated scan loop and watch the variables change:

cargo run -- run examples/traffic_light.st -n 65 --interval 0

Output:

running TrafficLight — 65 cycles, 0ms interval
(Ctrl+C to stop)

cycle |  state | timer_count | red_duration | green_duration | yellow_duration | red_light | green_light | yellow_light
------+--------+-------------+--------------+----------------+-----------------+-----------+-------------+-------------
    0 |      0 |           1 |           30 |             25 |               5 |         1 |           0 |            0
   ...
   29 |      1 |           0 |           30 |             25 |               5 |         1 |           0 |            0
   30 |      1 |           1 |           30 |             25 |               5 |         0 |           1 |            0
   ...
   54 |      2 |           0 |           30 |             25 |               5 |         0 |           1 |            0
   55 |      2 |           1 |           30 |             25 |               5 |         0 |           0 |            1
   ...
   59 |      0 |           0 |           30 |             25 |               5 |         0 |           0 |            1
   60 |      0 |           1 |           30 |             25 |               5 |         1 |           0 |            0

You can see the state machine cycling: red (30 cycles) → green (25) → yellow (5) → red again. The lights columns show which output is active.

Under the hood, plc run compiles your ST to native machine code and executes it directly. No interpreter, no VM — this is the same code that would run on a real PLC.

With --interval 100 (the default), it pauses 100ms between cycles, simulating a real PLC scan time.

8. Try the examples

cargo run -- run examples/counter.st -n 15     # watch a counter increment
cargo run -- compile examples/counter.st        # see the JSON output
cargo run -- validate examples/motor_control.st # industrial motor control

What's next?

  • MCP server (coming soon) — expose compile/validate as tools for Claude, GPT, or any LLM agent
  • LSP server (Phase 2) — autocomplete and diagnostics in VS Code
  • Multi-vendor import (Phase 3) — read Rockwell L5X and Siemens SimaticML projects

See the roadmap for the full plan.