DEV Community

Cover image for Claude Code Meets TIA Portal: One Prompt, a Compiled and Simulated PLC Program
tia-forge
tia-forge

Posted on Originally published at hubwiz.com

Claude Code Meets TIA Portal: One Prompt, a Compiled and Simulated PLC Program

Most "AI writes PLC code" posts stop at the SCL listing. The listing is the easy part. What matters is whether the thing lands in your project, compiles, downloads, and behaves — and whether you can tell when it doesn't.

We ran that whole path on a real TIA Portal V19 machine: from an empty device, one requirement, one prompt, to a program that compiled with zero errors, downloaded to S7-PLCSIM Advanced, and passed three behavioural scenarios. Everything below is measured, including the parts that failed.

How this was measured. 2026-10-27, Windows, TIA Portal V19 (Openness 19.0), S7-PLCSIM Advanced 6.0, gateway 1.3.2.0 driving the open project. The agent talked to TIA Portal over MCP, the same way any MCP client would.

What the gateway gives an agent

Five groups of capability, all against the project that is currently open in TIA Portal:

Capability What it does
Read project tree, block contents, tag tables, UDTs, cross-references, HMI screens, hardware config
Write generate or rewrite SCL, create blocks and tables, write back, compile one block or the whole PLC
Configure add a CPU, plug modules, create subnets, wire IO systems, set PROFINET device names
Verify download to S7-PLCSIM Advanced, read and write variables, run scenarios
Archive export SimaticML for offline diffing and version control

This build exposes 34 tools across those five groups, plus one more for LAD work. Install it in whichever client you use — the tool surface is identical.

Hooking it up

With Claude Code, it is two commands:

claude mcp add --transport http tialink http://192.168.1.110:8080/mcp
claude mcp list          # confirm it connected, and how many tools it sees
Enter fullscreen mode Exit fullscreen mode

Other clients differ only in where the config lives: Cursor (.cursor/mcp.json), Claude Desktop (claude_desktop_config.json), or a UI connector in WorkBuddy/CodeBuddy (remember to click Enable — an added-but-disabled connector shows no tools and looks broken).

One practical note: raise the client timeout. Adding a CPU took 34 s on an older device and 2.2 s on a current one; a download takes ~10 s. A 60 s default will fire on work that is merely slow, and you will misread it as a failure.

One requirement, one prompt

The requirement, as an engineer would write it:

  • One start button starts motor 1; motor 1 running for 3 s starts motor 2 automatically.
  • The stop button (normally closed) or a fault on either motor stops both immediately.
  • Each motor has a run output; timing uses IEC timers.
  • The logic lives in a reusable FB; OB1 calls it.
  • I/O on M area: M0.0 start, M0.1 stop (normally closed), M0.2 fault, M0.3 motor 1, M0.4 motor 2.

The prompt we actually sent (verbatim):

【需求】两台电机的顺序启动控制
- 一台启动按钮启动 1 号电机;1 号电机运行 3 秒后自动启动 2 号电机
- 停止按钮(常闭)或任一电机故障时,1 号、2 号电机立即全部停止
- 两台电机各有运行输出,定时用 IEC 定时器
- 逻辑写成一个可复用的 FB,OB1 里调用它
- 输入输出用 M 区:M0.0 = 启动按钮,M0.1 = 停止(常闭,1 表示允许),M0.2 = 故障,
  M0.3 = 1 号电机,M0.4 = 2 号电机

【要求】
1. 自己决定步骤和工具,把它们下发到 PLC_3 并编译通过(0 错误)
2. 编译通过后下载到 S7-PLCSIM Advanced,创建全新的实例名,运行后用场景验证
3. 不要保存工程
4. 最后贴出完整的 SCL 源码和实测数值
Enter fullscreen mode Exit fullscreen mode

No SCL template, no step list — the sequence was the agent's choice.

What it produced

FUNCTION_BLOCK "FB_MotorSeq"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
   VAR_INPUT
      Start : Bool;             // start button (M0.0)
      StopPermit : Bool;        // stop, normally closed: 1 = allowed (M0.1)
      Fault : Bool;             // fault (M0.2)
      RunDelay : Time := T#3S;  // motor 2 delay
   END_VAR
   VAR_OUTPUT
      Motor1 : Bool;            // motor 1 running (M0.3)
      Motor2 : Bool;            // motor 2 running (M0.4)
   END_VAR
   VAR
      statRun : Bool;           // run latch
      statPermit : Bool;        // run permitted
      rtStart : R_TRIG;         // rising edge of the start button
      tonRun2 : TON_TIME;       // IEC on-delay timer
   END_VAR
BEGIN
    // Run is permitted when the normally-closed stop is closed and there is no fault
    #statPermit := #StopPermit AND NOT #Fault;

    // Latch on the start edge; stop or fault clears it immediately
    #rtStart(CLK := #Start);
    #statRun := (#rtStart.Q OR #statRun) AND #statPermit;

    // Motor 1 runs at once; motor 2 starts RunDelay after motor 1
    #Motor1 := #statRun;
    #tonRun2(IN := #Motor1,
             PT := #RunDelay);
    #Motor2 := #statRun AND #tonRun2.Q;
END_FUNCTION_BLOCK
Enter fullscreen mode Exit fullscreen mode

And OB1, wiring it to the M area:

ORGANIZATION_BLOCK "Main"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
BEGIN
    "DB_MotorSeq"(Start := %M0.0,
                  StopPermit := %M0.1,
                  Fault := %M0.2,
                  RunDelay := T#3s,
                  Motor1 => %M0.3,
                  Motor2 => %M0.4);
END_ORGANIZATION_BLOCK
Enter fullscreen mode Exit fullscreen mode

Compile: state=Success, errorCount=0, warningCount=0, both Main (OB1) and FB_MotorSeq (FB1) cleaned.

Two details worth reading in that code: the start is edge-triggered (R_TRIG) — otherwise holding the button keeps it latched — and stop/fault runs through #statPermit, a path that never touches the timer. That second one is exactly what the third scenario below checks.

How long each step took

Step Time
health probe (attached, project, device count, prerequisites) < 1 s
look up the CPU model in the hardware catalogue ~1 s
create the CPU (CPU 1516-3 PN/DP, FW V2.9) 2.2 s
import the FB from SCL source 11.8 s
import the instance DB and OB1 0.6 s each
compile 1.4 s
create a virtual PLC and power it on ~6 s
download to S7-PLCSIM Advanced 9.6 s
run, write variables, read variables < 100 ms per call

One ordering constraint you cannot skip: import does not compile. Right after import the block is not marked consistent, and even exporting it is refused (Block is inconsistent (not compiled)). Import → compile → read back, in that order.

Three scenarios

Timing: start pulse, motor 1 runs immediately, motor 2 starts 3.12 s later, then stop or fault drops both within the same scan

A green compile only says the code is acceptable to the PLC. These three runs say what it does.

Scenario Action Observed Read as
Start M0.1=1, pulse M0.0 motor 1 goes to 1 at once; motor 2 at +3.12 s; both hold after the button is released delay is fed by "motor 1 running", not by the button
Stop both running, M0.1 → 0 both drop within one sample; restoring M0.1=1 does not restart them the stop path is not gated by the timer
Fault both running, M0.2 → 1 both drop within 13 ms (one scan); clearing the fault does not restart fault is an independent cut-out, and it does not self-heal

The 3.12 s figure is 120 ms off the 3 s setting; that is one polling quantum (150 ms) plus scan jitter, not a logic error. The stop and fault cases are the ones worth insisting on: they are invisible at compile time, hard to reproduce on a machine, and they are what decides whether an operator has to press reset afterwards.

Beyond "Success"

A download that returns success still deserves a look at what actually landed. We exported the three blocks as SimaticML and checked three things: OB1 really calls the instance DB with those M addresses, the FB uses an IEC TON and no stray instance, and the block metadata says consistent. Sizes for the record: Main 7,766 B, FB_MotorCtrl 15,978 B (the earlier single-motor variant), DB_MotorCtrl 6,903 B.

The gateway's own notes make one behaviour explicit: nothing is persisted until you save. Imports, hardware writes, deletes and compiles all live in memory. That is what makes "try it, look at the result, throw it away" safe — and it is also the trap: if you never save and assume you are done, closing TIA Portal loses the lot. Our habit: verify first, save once, at the end.

A checklist before you let it touch a real project

  1. Read-only first. Have it read the tree, blocks and cross-references; confirm it understood your project before it writes anything.
  2. Preview writes for a week. Compare its previewed diff against what a person would change.
  3. Read back after every write. Export or compare hashes; if it does not match, treat it as not written.
  4. One project, one session, one writer at a time. The Openness side is serial; parallel agents produce half-finished work.
  5. Compile as a gate. Passing does not mean correct, but failing must block the commit. This is the only gate you can fully automate.
  6. Archive before changing. VCI or Git, exported as diffable text.
  7. Keep an audit trail. Who, when, in which mode, which object.
  8. Do not hand download or go-online to an unattended agent. Man in the loop, one step at a time, with a rollback plan.

And the rule that outranks all of them: AI-generated SCL gets a human review, especially safety logic (emergency stop, interlocks, F-programs). The tool guarantees it was written, not that it is right.

Versions and boundaries

Item What we ran
TIA Portal V19 / V20 supported; this run was V19 with Openness 19.0
Simulator S7-PLCSIM Advanced 6.0, Softbus
CPU firmware matters more than expected — a V1.1 device was refused by the simulator, V2.9 worked
Deployment the gateway runs on the same Windows machine as TIA Portal; the agent can be anywhere that can reach its HTTP port
Tool count 34

The deployment line surprises people: the gateway has to be on the TIA machine, the agent does not. Every call in this article was made from a different computer over HTTP.

Run it yourself

You need two things: a Windows machine with TIA Portal V19 or V20, and the gateway installed on that same machine. It publishes its endpoint on http://localhost:8080/, and the client side is the one line of config from earlier.

Setup and version support: tia.hubwiz.com/tia-link. If you want the agent to understand the project before it starts writing, TIA-Context feeds it blocks, tag tables and call relationships.

Once the loop works, the mistake to avoid is asking for too much at once. One block, one change with a test case in front of it, beats "refactor this project" every time.


Further reading (in Chinese): AI changes a TIA Portal program: write guards and boundaries · OPC UA MCP hands-on · AI × industrial / PLC guide

Timings and error strings come from the 2026-10-27 run. AI-generated PLC code needs human review before it goes anywhere near production.

Top comments (0)