TiaLink
把 TIA Portal 接到你的智能体:导入、编译、读回核对、PLCSIM 仿真。本地运行,工程数据不出厂。
打开 TiaLink把三电机延时启停里的三段重复定时逻辑重构成一个参数化 FB,用同一张场景表在虚拟 PLC 上做回归,逐点对比前后行为。
学完你能:能安全地重构一段现有程序:先存行为基线,再改结构,最后用同一张场景表证明行为没变;并知道编译 0/0 为什么证明不了这件事。
前五课一路把「敢改」拆开了:读懂(L1)→ 知道波及谁(L2)→ 写块到编译 0/0(L3)→ 让程序真跑起来(L4)。这一课补上最后一块:改结构,但不改行为。
本课对象是示例库的三电机延时启停:按一下按钮,20 秒窗口打开,三台电机分别延时 5/10/15 秒启动,窗口一过全停。原程序在同一个网络里把 TON + 线圈 写了三遍,只有 PT 和输出不同——典型的复制粘贴。我们把它收成一个参数化 FB,实例化三次,点表、符号、地址一个都不动。
本课流程已在 TiaLink 1.1.0.0 + TIA Portal V19 + S7-PLCSIM Advanced 6.0 上实测跑通,而且真的翻车了一次:我顺手把 IEC_Timer_0_DB 的调用从 TP 写成 TON,编译照样 0 错误 0 警告,但按按钮后三个电机一个都不启动。改回 TP 后,同一张场景表逐点回归,前后完全一致。
口径:重构的前提是行为基线已经存在。没有基线就动手,等于拿现场当测试环境;安全逻辑、联锁、急停相关块不做「顺手重构」。
Enable/Pt,输出 Q),用实例 DB 三次调用;TP 这类系统定时器必须写成实例 DB 的方法;TP 与 TON 一字之差,ET 会骗人;DB.Ton.ET)读不到就换证据;--no-sim)并连接沙盒工程;仿真前确认 http://localhost:8080/api/v1/sim/status 里 available=true;http://localhost:8080/mcp;真实使用只有一次输入:说清「先跑基线、再重构成一个 FB、用同一张表回归」。建设备、导入、跑场景、写 SCL、替换、再跑一遍,都是 agent 做的。
在博途那台机器上启动 TiaLink、打开沙盒工程。页首状态条会自动检测;然后在客户端里加一个 Streamable HTTP MCP 服务:
先在本页顶部点「设置」填一次地址(存在本机,示例库页面也用同一个地址),下面的 URL 会跟着更新,点「复制」直接贴进客户端配置:
{
"mcpServers": {
"tialink": {
"type": "http",
"url": "http://localhost:8080/mcp",
"headers": { "Authorization": "Bearer <你的 TiaLink API Token>" }
}
}
}看到什么:客户端列出 tialink_ 开头的工具,含 tialink_import(SimaticML / SCL)、tialink_compile、tialink_delete、tialink_sim_*。
把下面这段发给 agent(练习对象换成你的工程):
用虚拟 PLC 做一次「重构 + 行为回归」,不要保存工程。
- 新建一台 S7-1500(CPU 1516-3 PN/DP),插一块 DI、一块 DQ;导入示例库的「三电机延时启停」,编译到 0 错误 0 警告;
- 先跑基线:注册仿真实例、上电、下载、RUN,然后按这张场景表记录实测值(只记实测,不要抄期望):
- 按钮 1 秒脉冲后 +3 s / +6 s / +11 s / +16 s / +22 s,分别记录
Tag_1、Motor 1..3;- 再重构:把
Main里三段重复的TON + 线圈收成一个参数化 FB(Enable、Pt→Q),用三个实例 DB 调用;点表、符号、地址保持不变;编译到 0/0;- 再跑同一张表,逐格对比基线;
- 交付:重构前后对照(结构变化 / 行为契约)+ 回归表 + 一句结论。如果哪一格对不上,先定位原因,不要改场景表迁就结果。
发完这一条,你等着看结果就行。下面是它实际会做的事(本次实测过程)。
先跑基线(改之前必须有一次)。一次按钮后的实测:
| 时刻 | Tag_1 | Motor 1 | Motor 2 | Motor 3 | 证据 |
|---|---|---|---|---|---|
| S0 初始 | 0 | 0 | 0 | 0 | — |
| +3 s | 1 | 0 | 0 | 0 | 20 s 窗口在跑 |
| +6 s | 1 | 1 | 0 | 0 | IEC_Timer_0_DB_4.ET = 5000 ms(钳在 PT) |
| +11 s | 1 | 1 | 1 | 0 | — |
| +16 s | 1 | 1 | 1 | 1 | IEC_Timer_0_DB.ET = 19990 ms |
| +22 s | 0 | 0 | 0 | 0 | 窗口结束,全部复位 |
行为契约(重构不能碰的东西):① 20 秒窗口由 TP 决定,按一下开一次;② 三个 TON 都挂在 Tag_1 上,5/10/15 秒错峰;③ 窗口一关全部复位;④ 点表符号与地址不变(HMI/图纸都按符号引用)。
删掉旧 Main(tialink_delete 先 dryRun 再 confirm),再用一份 SCL 外部源生成 FB + 3 个实例 DB + 新 Main。先按最直接的写法生成第一版:
1FUNCTION_BLOCK "DelayedMotor"2{ S7_Optimized_Access := 'TRUE' }3VERSION : 0.14VAR_INPUT5 Enable : Bool;6 Pt : Time;7END_VAR8VAR_OUTPUT9 Q : Bool;10END_VAR11VAR12 Ton : TON_TIME;13END_VAR14BEGIN15 #Ton(IN := #Enable, PT := #Pt);16 #Q := #Ton.Q;17END_FUNCTION_BLOCK1ORGANIZATION_BLOCK "Main"2{ S7_Optimized_Access := 'TRUE' }3VERSION : 0.14BEGIN5 "IEC_Timer_0_DB".TON(IN := "PB Switch", PT := T#20S);6 "Tag_1" := "IEC_Timer_0_DB".Q;78 "DelayedMotor_DB_1"(Enable := "Tag_1", Pt := T#5S);9 "Motor 1" := "DelayedMotor_DB_1".Q;1011 "DelayedMotor_DB_2"(Enable := "Tag_1", Pt := T#10S);12 "Motor 2" := "DelayedMotor_DB_2".Q;1314 "DelayedMotor_DB_3"(Enable := "Tag_1", Pt := T#15S);15 "Motor 3" := "DelayedMotor_DB_3".Q;16END_ORGANIZATION_BLOCK看到什么:tialink_import(format=scl, generate=true)一次生成 DelayedMotor(FB1)、DelayedMotor_DB_1..3(DB)、Main(OB1);编译 0 错误 0 警告。
第一版里,IEC_Timer_0_DB 的调用写成了 TON(原程序是 TP)。编译 0/0,但同一张场景表实测全灭:
| 时刻 | Tag_1 | M1 | M2 | M3 | 对比基线 |
|---|---|---|---|---|---|
| S0 | 0 | 0 | 0 | 0 | ✓ |
| +3 s | 0 | 0 | 0 | 0 | ✗ 基线是 1 / 0 / 0 / 0 |
| +6 s | 0 | 0 | 0 | 0 | ✗ 基线是 1 / 1 / 0 / 0 |
| +11 s | 0 | 0 | 0 | 0 | ✗ 基线是 1 / 1 / 1 / 0 |
| +16 s | 0 | 0 | 0 | 0 | ✗ 基线是 1 / 1 / 1 / 1 |
| +22 s | 0 | 0 | 0 | 0 | ✓ |
按住按钮时 IEC_Timer_0_DB.IN = true、ET 会往上走(实测 2118 ms),但 Q 始终是 false——因为 TON 要求 IN 连续保持 20 秒才置位;而按钮只是一个 1 秒脉冲。原程序的 TP 是「按一下,窗口固定 20 秒」,所以 Tag_1 立刻置位并保持。编译只能证明语法与一致性,证明不了行为没变。
修正只改一行(其余 FB、实例 DB、三个调用都不动):
- "IEC_Timer_0_DB".TON(IN := "PB Switch", PT := T#20S); // 第一版:错
+ "IEC_Timer_0_DB".TP(IN := "PB Switch", PT := T#20S); // 修正:沿用原程序的 TP
重新编译 0/0,换一个新实例名(避免 ResetModule),再跑同一张表:
| 时刻 | Tag_1 | M1 | M2 | M3 | 对比基线 |
|---|---|---|---|---|---|
| S0 | 0 | 0 | 0 | 0 | ✓ |
| +3 s | 1 | 0 | 0 | 0 | ✓ |
| +6 s | 1 | 1 | 0 | 0 | ✓ |
| +11 s | 1 | 1 | 1 | 0 | ✓ |
| +16 s | 1 | 1 | 1 | 1 | ✓(IEC_Timer_0_DB.ET = 16798 ms) |
| +22 s | 0 | 0 | 0 | 0 | ✓ |
结构变了(3 段重复 → 1 个 FB + 3 实例),行为契约没变。这才是「重构」的完成定义。
TP → TON);DB.Ton.ET)不用来下结论;TP 写成 TON(或反过来):编译 0/0,行为完全不同;定时器类型是行为契约的一部分。DATA_BLOCK "X" "FB" { S7_Optimized_Access := ... } 会报 语法错误:设定值“{”无效;写成 DATA_BLOCK "X" "FB" 后直接 BEGIN。"IEC_Timer_0_DB".TP(...);"IEC_Timer_0_DB"(...) 和裸 TON(...) 都编译失败(Invalid function name / 形参无效)。"DelayedMotor_DB_1".Ton.ET 返回 DoesNotExist(标签表里能看到,按名读失败);用 Q 和输出位做判据。ResetModule was unhandled;换新实例名重新注册(同 L4)。把 TIA Portal 接到你的智能体:导入、编译、读回核对、PLCSIM 仿真。本地运行,工程数据不出厂。
打开 TiaLink