西门子 TIA Portal Openness 技术手册
从入门到生产落地:146 篇一手资料汇编
本手册基于 146 篇一手资料汇编而成,覆盖西门子官方手册(核心 API 手册 1692 页全文提取)、中文社区实战经验、英文技术博客、GitHub 开源生态五大来源。
| 项目 | 数据 |
|---|---|
| 一手资料总量 | 146 篇 |
| 官方西门子资料 | 27 篇(含 V21 系统手册 1692 页、V15/V19 手册、Startdrive 手册 64 页) |
| 中文社区文章 | 24 篇(CSDN、西门子中国论坛、PLC技术网、博客园、与非网) |
| 英文博客文章 | 27 篇(industrial monitor direct、HubWiz、Comquent 等) |
| GitHub 开源项目 | 45 篇(官方代码仓库、NuGet 包族、社区库、AI/MCP 项目) |
| 进阶专题资料 | 23 篇(HMI、下载、传动、CI/CD、性能工程化等 8 大专题) |
| 本手册章节 | 12 章 + 附录 |
| 抓取与汇编日期 | 2026-10-04 |
手册定位
这不是官方文档的翻译,而是工程视角的落地手册:每一条结论都注明来源,凡社区流传但与官方冲突的说法一律以官方为准并标 ⚠️。
三类读者:
- 初学者(第 1~3 章):从环境配置到第一个程序,理解对象模型与命名空间体系
- 应用开发者(第 4~8 章):程序块生成、硬件组态、HMI、下载调试、传动自动化
- 架构师与团队负责人(第 9~12 章):CI/CD 集成、技术选型、故障排查、工程化规范
⚠️ 使用前必读
本手册在汇编过程中对社区资料做了严格核验,发现并纠正了以下广泛流传的错误说法:
| 社区流传(错误/存疑) | 官方实际 |
|---|---|
Siemens.TiaPortal.Openness 命名空间 | Siemens.Engineering |
DownloadWithoutStop / SoftwareDownload | 不存在,V19/V21 手册 0 命中,不停机下载靠 pre/post delegate 控制启停 |
DeviceItemClassifications.HM 用于定位 G120 | HM = “header module”,仅 SINAMICS Integrated / NX / ADI4 充当,G120 CU 落在 None(0) |
HmiTag.Logging | HmiTag.LoggingTags → HmiLoggingTag |
pXXX.Bits 按 bit 序号索引 | 按位名称字母升序(官方注释:Bits[2] == pXXX.3) |
| Openness 需单独购买 license | 官方手册未列独立订货号,随 STEP 7 Professional/Advanced 提供 |
凡引用存疑来源处,正文均以 ⚠️ 显式标注,落地前请对照官方 Programming Manual 核实。
目录
- 第1章 TIA Portal Openness 概述与技术架构
- 第2章 环境配置与第一个程序
- 第3章 硬件配置、设备与网络
- 第4章 PLC 程序块、标签与外部源生成
- 第5章 HMI(WinCC)自动化
- 第6章 在线连接、下载与调试闭环
- 第7章 通信集成与数据格式交换
- 第8章 SINAMICS Startdrive 传动自动化
- 第9章 版本控制与 CI/CD 自动化
- 第10章 开源生态、第三方库与选型指南
- 第11章 异常、报错与故障排查手册
- 第12章 工程化最佳实践与实战路线
- 附录 引用索引 · Entry ID 速查 · 术语表
第1章 TIA Portal Openness 概述与技术架构
第 1 章 TIA Portal Openness 概述与技术架构
本章导读
本章解决三个问题:TIA Portal Openness 到底是什么、它不做什么;它的技术架构由哪些命名空间和程序集构成、如何正确引用;以及跨版本(V15.1 → V21)到底哪些东西变了、变了什么。读完本章,你应当能判断一个需求是否适合用 Openness 实现,能在 VS 里正确引用程序集,并知道 V21 的程序集拆分为什么会导致老代码必须重编译。本章不涉及具体编程操作,第 2~4 章才开始动手。
1.1 它是什么:官方定义与 Entry ID
1.1.1 一句话定义
西门子官方在 V21 文档中的定义是:
TIA Portal Openness — API for automation of engineering workflows(用于自动化工程流程的 API)
中文社区与官方技术文章里更直白的说法是:“方便使用高级编程语言打开博途软件并进行自动编程的工具,或者说窗口”。
这两个定义共同点出了 Openness 的本质——它是工程流程自动化 API,操作对象是博途软件本身和离线工程文件,而不是 PLC 的运行时数据。
1.1.2 关键官方 Entry ID 速查
| Entry ID | 内容 | 备注 |
|---|---|---|
| 109826886 | 《Openness: API for automation of engineering workflows》系统手册,V19 世代,Online documentation 11/2023,1692 页 | 最权威的 API 参考全集,本手册 API 签名的主要依据 |
| 109477163 | 《Openness: Automating creation of projects》系统手册,V15.1 世代,10/2018,656 页 | 「自动化创建工程」主题主手册,XML 导入导出说明在此 |
| 109773802 | 官方手册条目页,汇总 V21 / V20 / 11/2023 / 12/2022 / 05/2021 / 11/2019 各版 PDF、多媒体帮助与章节化网页 | 查历史版本手册的正规入口 |
| 109792902 | 《TIA Portal Openness – Overview of the Most Important Documents and Links》文档总导航 | 可按编号生成全部相关 Entry ID 的 .txt 清单;查任何 Openness 专题的首选起点页 |
| 109763491 | Openness for SINAMICS Startdrive and DCC | 逐版本功能演进的唯一官方记录 |
| 109816879 | 官方选型指南:Add-In / Openness 应用 / MAC Equipment Module 三者如何取舍 | 决定”该做成什么形态”的决策依据 |
| 109775415 → 109779415 | TIA Add-Ins Getting Started | 官方入门项目,V16/V17 |
| 108716692 | 官方简介与演示应用(Demo Application) | 含 TIA_PortalOpenness_Demo_V20 等示例 |
| 109773999 / 109817270 | TIA Portal Add-Ins(使用者视角)/ Add-In Overview for developers(开发者视角) | Add-In 开发机制入口 |
| 109975682 | TIA Package Manager | 官方软件包管理器,下载解压建快捷方式并推送更新 |
来源:本文综合自 raw 目录下的《西门子官方手册》(Entry 109826886 / 109477163 全文 PDF 转储)、《西门子官方应用示例条目页》、《中文社区实战》、《英文技术博客》等抓取结果。
1.1.3 它不做什么(先划边界,再谈用法)
| 能力 | Openness 是否支持 | 替代方案 |
|---|---|---|
| 打开/新建/保存/关闭工程 | ✅ | — |
| 硬件组态(CPU、模块、PROFINET 网络、IP) | ✅(V14 SP1 起) | — |
| 生成 PLC 程序块(FB/FC/DB/UDT/OB) | ✅ 但只能整块生成 | — |
| 逐行编辑 LAD/FBD 网络逻辑、STL 语句 | ❌ 架构性硬边界 | 见第 4 章 |
| 读写 PLC 实时变量 / DB 数据 / 在线监控 | ❌ 不属于 Openness | Sharp7、S7net-plus、OPC UA |
| 切换 CPU 运行模式(RUN/STOP) | ❌ 无独立 API | 只暴露 StopModules/StartModules,绑定在下载流程内 |
| 清除强制(Unforce)、读诊断缓冲区 | ❌ | CPU 侧 OPC UA 服务器或 S7 通信 |
| 按块选择性下载过滤器 | ❌ | — |
| SIL/PL 安全相关逻辑生成 | ⚠️ 官方明确禁止 | 手工在 TIA 中编写;Openness 只做非安全外围 |
| 安全系统的检查 / 生成验收认可数据 | ❌ 官方明令禁止 | STEP 7 Safety 的 safety printout 或功能测试 |
| 修改安全块(Know-how 保护块) | ⚠️ 受限 | 见第 4 章 |
官方红线原文:禁止用 TIA Portal Openness API 执行安全系统的检查或生成验收/认可数据。安全验收只能用 STEP 7 Safety 附加包的安全打印(safety printout)或功能测试完成,Openness API 不是替代品。
来源:本文综合自 raw 目录下的《西门子官方手册》(V19 系统手册 5.11 章、《自动化创建工程》V15.1 手册)、《中文社区实战》(PLC技术网《TIA Portal Openness 概述》tid=650511)、《进阶专题》等抓取结果。
1.2 官方 8 步编程流程与 IDE 要求
官方手册反复出现的”8 步”是所有 Openness 应用的骨架,V15.1 与 V19 两版手册完全一致:
| 步骤 | 动作 | 对应 API / 工具 |
|---|---|---|
| 1 | 让 TIA Portal 在开发环境中可知 | 添加程序集引用 / app.config / AssemblyResolve |
| 2 | 建立程序访问 | new TiaPortal(TiaPortalMode.…) 或 GetProcesses() |
| 3 | 激活访问权限 | 把用户加入 Siemens TIA Openness(V21 需再加 TIA Engineer)本地组 |
| 4 | 发布并启动 TIA Portal | Build 成独立 exe,从 bin\Debug 运行 |
| 5 | 打开工程 | Projects.Open / OpenWithUpgrade |
| 6 | 执行命令 | 对象模型操作 |
| 7 | 保存并关闭工程 | project.Save() / project.Close() |
| 8 | 终止连接 | Dispose()(using 自动完成) |
官方硬性环境要求:
| 项目 | 要求 |
|---|---|
| 操作系统 | Windows 64 位 |
| TIA Portal | 64 位(且必须已安装产品,如 STEP 7 Professional 或 WinCC Professional) |
| 用户程序位数 | 32 位、64 位或 “Any CPU” 均可 |
| 开发工具 | Visual Studio 2017 或更高版本 |
| .NET | .Net Framework SDK 4.8 + Windows Classic Desktop 包 |
| 知识要求 | 系统工程师知识 + VS/C#/VB.net 高级知识 + TIA Portal 使用知识 |
来源:本文综合自 raw 目录下的《西门子官方手册》(4.1.1 Requirements、5.2.7 Programming steps)、《中文社区实战》等抓取结果。
1.3 技术架构总览
1.3.1 架构总览图
┌──────────────────────────────────────────┐
│ 外部进程 / TIA 内部 Add-in │
│ (C# exe · VB · PowerShell · Python/pythonnet │
│ · AI Agent + MCP)│
└───────────────┬──────────────────────────┘
│ 进程间通信 IpcChannel
│ (ensureSecurity = false,
│ name = {Process.Name}_{Process.Id},
│ priority = 1, typeFilterLevel = Full)
▼
╔══════════════════════════════════════════════════════════════════════════════╗
║ TIA Portal 进程 (Siemens.Automation.Portal) ║
║ TiaPortal ── 进程生命周期 / 事件 / 白名单 ║
╚══════════════════════════════════════════════════════════════════════════════╝
│
┌───────────────────────────────────┼───────────────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌────────────────────┐ ┌──────────────────────┐
│Siemens. │ │ Siemens. │ │ Siemens. │
│Engineering │ │ Engineering.HW │ │ Engineering. │
│(根命名空间) │ │ 硬件配置 │ │ Compiler / Compare │
│ │ │ │ │ Download / Online │
│ TiaPortal │ │ Device │ │ Library / Umac │
│ Project │ │ DeviceItem │ │ Cax(HwUtilities) │
│ TiaPortalProcess│ │ HardwareObject │ └──────────────────────┘
└───┬───────────┘ │ Subnet / IoSystem │
│ │ Node / Address │
▼ │ TransferArea │
┌───────────────────────────┐ └─────────┬──────────┘
│ Project │ │ 设备项上的「服务」定位模式
│ Devices / DeviceGroups │ │ GetService<T>()
│ Subnets │ ▼
│ HardwareCatalog │ ┌──────────────────────────────────────────┐
│ GlobalLibrary │ │Siemens.Engineering.HW.Features │
│ ProjectLibrary │ │ NetworkInterface / NetworkPort │
│ LanguageSettings │ │ PlcAccessLevelProvider │
│ Download / Compare(Sw) │ └──────────────────────────────────────────┘
└───┬───────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────────────────┐
│ Software 容器(抽象) │
│ GetService<SoftwareContainer>().Software as PlcSoftware │
└───────────┬───────────────────────────────────┬───────────────────────────┘
▼ ▼
┌───────────────────────────────┐ ┌─────────────────────────────────────┐
│ Siemens.Engineering.SW │ │ Siemens.Engineering.Hmi │
│ PlcSoftware │ │ HmiTarget / HmiSoftware │
│ ├ BlockGroup ─ Blocks │ │ ├ ScreenFolder / Screen │
│ │ ├ PlcBlock (FB/FC/OB) │ │ ├ TagFolder / TagTable │
│ │ ├ InstanceDB / GlobalDB │ │ └ TextsFolder / TextList │
│ │ └ (KnowHow 保护块) │ │ + Siemens.Engineering.HmiUnified │
│ ├ TagTableGroup ─ TagTables │ │ (Unified 专有程序集) │
│ │ └ PlcTagTable ─ PlcTag │ └─────────────────────────────────────┘
│ ├ ExternalSourceGroup │
│ │ └ ExternalSources │
│ │ ├ PlcExternalSource │
│ │ └ IGenerateSource │
│ ├ Units ─ PlcUnit │
│ └ TechnologicalObjects ─ Motion
│ (命名空间:.SW.Blocks / .SW.Tags / .SW.ExternalSources /
│ .SW.Types / .SW.Units / .SW.TechnologicalObjects) │
└───────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────────┐
│ 领域扩展命名空间(按需引用,与上面的核心库正交) │
│ Siemens.Engineering.Connection ── 端口到端口连接(port-to-port) │
│ Siemens.Engineering.Uca ── 通用通信架构(PROFINET/PtP/FDL/OPC UA)│
│ Siemens.Engineering.AddIn ── 仅 Add-in 内可用:MessageBoxProvider、│
│ ProgressProvider、FeedbackProvider │
│ Siemens.Engineering.MC.Drives ── SINAMICS Startdrive 传动自动化(.DFI / │
│ .Enums),程序集仍在 Siemens.Engineering │
└───────────────────────────────────────────────────────────────────────────┘
1.3.2 各命名空间职责速查表
| 命名空间 | 核心类型 | 职责 | 何时需要 using |
|---|---|---|---|
Siemens.Engineering | TiaPortal、TiaPortalProcess、Project、TiaPortalMode | 进程与工程生命周期、工程对象模型根 | 始终 |
.HW | Device、DeviceItem、HardwareObject、Subnet、IoSystem、IoController、IoConnector、Node、Address、TransferArea | 硬件配置树:设备、机架、模块、网络、地址、传输区 | 做组态时 |
.HW.Features | NetworkInterface、NetworkPort、PlcAccessLevelProvider | 设备项的服务层:网络接口、端口、访问级别 | 设 IP / 建网络时 |
.HW.Utilities | OpcUaExportProvider、SinumerikArchiveProvider | 通过 project.HwUtilities.Find("…") 取的硬件工具 | 导 OPC UA XML 时 |
.SW | PlcSoftware、SoftwareContainer、HmiSoftware | PLC / HMI 软件容器 | 取 PLC 必经 |
.SW.Blocks | PlcBlock、InstanceDB、GlobalDB、PlcBlockGroup、ExportOptions、ImportOptions | 程序块与导入导出 | 生成/导出块时 |
.SW.Tags | PlcTagTable、PlcTag、PlcTagTableGroup、MultilingualText | PLC 变量表与变量 | 做变量表时 |
.SW.ExternalSources | PlcExternalSource、PlcExternalSourceSystemGroup、IGenerateSource、GenerateOptions、GenerateBlockOptions | 外部源文件工作流(生成块的唯一官方可编程路径) | 生成块时 |
.SW.Types | PlcType、PlcStruct、PlcEnum | UDT 用户自定义数据类型 | 生成数据类型时 |
.SW.Units | PlcUnitProvider、PlcUnit、PlcSafetyUnit | 软件单元 / 安全单元(V17+ 多软件单元工程必需) | 软件单元工程 |
.SW.TechnologicalObjects(.Motion) | TechnologyObject、轴对象 | 工艺对象 | 运动控制工程 |
.Compiler | ICompilable、CompilerResult、CompilerMessage | 编译与编译诊断 | 几乎所有生成流程 |
.Compare | CompareResult | 项目比较 | 版本差异检测 |
.Download / .Online | DownloadProvider、OnlineProvider、DownloadOptions、OnlineState | 下载与上下线 | 调试阶段 |
.Cax | CaxProvider、HwUtilities | AutomationML(CAx)导入导出 | 与 EPLAN 交换 |
.Library | UserGlobalLibrary、SystemGlobalLibrary、LibraryComposition | 全局库 / 项目库 | 库管理 |
.Umac | UmacConfigurator、UmacDevice | 项目级 / 设备级 UMAC 权限 | 安全权限配置 |
.Hmi | HmiTarget、Screen、HmiScreenObject、TagTable | WinCC Advanced / Professional 画面 | HMI 工程 |
.HmiUnified | HmiTagLogging 等 | WinCC Unified 专有类型,必须额外引用 Siemens.Engineering.HmiUnified.dll | Unified 工程 |
.Connection | 端口到端口连接(Configurable attributes of a port-to-port connection) | 多设备互联 | 特殊拓扑 |
.Uca | 通信连接配置 | 生成 S7 / ISO-on-TCP / OPC UA / Profinet / PtP / FDL 连接 | 通信集成 |
.MC.Drives(.DFI/.Enums) | DriveObject、DriveParameter、Telegram、DriveObjectContainer | SINAMICS 传动对象、参数、报文 | 有驱动时 |
.AddIn | MessageBoxProvider、ProgressProvider、FeedbackProvider | 仅在 TIA 内部 Add-in 运行时可用 | 写 Add-in 时 |
重要提醒:
HmiTarget与HmiSoftware命名空间容易歧义。官方手册在示例中显式写了别名:using HmiTarget = Siemens.Engineering.Hmi.HmiTarget; // 命名空间别名避免歧义
来源:本文综合自 raw 目录下的《西门子官方手册》(5.1.1 对象模型图、5.2.4 Standard libraries 的
using清单、5.13 HMI 章、5.21/5.23 Startdrive 章)、《中文社区实战》(gooderp《c# NET API 自动化生成西门子 PLC 的 st 代码写入项目文件中》)、《进阶专题》等抓取结果。
1.3.3 服务定位模式:GetService<T>()
Openness 的第二个核心惯用法是服务定位。硬件对象本身只暴露少量通用能力(Name、TypeIdentifier、PositionNumber 等),具体能力要通过 GetService<T>() 拿到对应的服务对象。
| 服务对象 | 取法 | 提供的能力 |
|---|---|---|
SoftwareContainer | deviceItem.GetService<SoftwareContainer>() | → .Software 得到 PlcSoftware / HmiSoftware |
NetworkInterface | deviceItem.GetService<NetworkInterface>() | PROFINET 接口、节点、端口、IoControllers、TransferAreas |
NetworkPort | port.GetService<NetworkPort>() | 端口互连信息 |
OnlineProvider | cpuItem.GetService<OnlineProvider>() | GoOnline() / GoOffline() / State / Configuration.IsConfigured |
DownloadProvider | deviceItem.GetService<DownloadProvider>() | Download(...) 与 Configuration |
AccessProtection | cpuItem.GetService<AccessProtection>() | CPU 四级访问保护密码 |
CrossReferenceService | block.GetService<CrossReferenceService>() | 交叉引用,影响面分析 |
PlcBlockProtectionProvider | block.GetService<PlcBlockProtectionProvider>() | Know-how 保护加解密 |
UmacDevice | deviceItem.GetService<UmacDevice>() | 设备级 UMAC 功能权限 |
DriveObjectContainer | deviceItem.GetService<DriveObjectContainer>() | 驱动对象集合 |
OpcUaServer(子模块属性) | 子模块 SetAttribute("OpcUaServerCertificate", …) | OPC UA 服务器与证书 |
关键铁律:GetService<T>() 在服务不可用时返回 null,不抛异常。 所有取到的服务都必须判空。
// 官方惯用法:取服务 → 判空 → 使用
foreach (Device device in project.Devices)
{
foreach (DeviceItem deviceItem in device.DeviceItems)
{
OnlineProvider onlineProvider = deviceItem.GetService<OnlineProvider>();
if (onlineProvider == null) continue; // 非 CPU 设备项无此服务
if (onlineProvider.Configuration.IsConfigured)
onlineProvider.GoOnline();
// ... 业务处理 ...
onlineProvider.GoOffline();
}
}
来源:本文综合自 raw 目录下的《进阶专题》(在线连接与扫描章)、《西门子官方手册》(5.7.2 Querying PLC and HMI targets)、《英文技术博客》与《中文社区实战》等抓取结果。
1.4 程序集文件与引用方式
1.4.1 安装路径规则
| TIA 版本 | PublicAPI 路径 | 说明 |
|---|---|---|
| V14 ~ V18(旧布局) | …\Automation\Openness\PublicDlls\ | 已淘汰布局 |
| V15.1 | C:\Program Files\Siemens\Automation\Portal V15.1\PublicAPI\V15.1\ | 含 Docs\(CHM/HTML API 参考)与 Samples\ |
| V16 | C:\Program Files\Siemens\Automation\Portal V16\PublicAPI\V16\ | 部分资料写作 …\PublicAPI\(无版本子目录),以本机实际为准 |
| V17 / V18 / V19 | C:\Program Files\Siemens\Automation\Portal V<n>\PublicAPI\V<n>\ | 主流布局 |
| V21 | C:\Program Files\Siemens\Automation\Portal V21\PublicAPI\V21\net48\ | 多了一层 net48\,且程序集模块化 |
| SimaticML XSD Schema | …\Portal Vxx\PublicAPI\Vxx\Schemas\ | 导入导出 XML 的架构定义 |
⚠️ 路径陷阱:
V14–V19在部分英文资料中写作TIA Portal V19(带空格),V20+写作Portal V20;旧版还有Openness\Inc\PublicApi\的写法。同一个版本在不同资料里路径可能差一层目录或一个空格,落地前务必用Test-Path实测:Test-Path "C:\Program Files\Siemens\Automation\Portal V16\PublicAPI\Siemens.Engineering.dll"
1.4.2 程序集演进:一个必须讲清的历史
| 版本区间 | 程序集形态 | 强名称版本号 | 备注 |
|---|---|---|---|
| V15.1 | 多个细粒度 DLL:Siemens.Engineering.dll、.AddIn.dll、.Hmi.dll、.SW.dll | — | Siemens.Engineering.Contract.dll 在 Bin\PublicAPI\ 下 |
| V17 ~ V20 | fat assembly(胖程序集):四个命名空间全编进单个 Siemens.Engineering.dll,目录里通常只有 Siemens.Engineering.dll + Siemens.Engineering.Hmi.dll | 17.0.0.0 / 18.0.0.0 / 19.0.0.0 / 20.0.0.0 | 只有路径字符串变,按 using 模式写的代码 V17–V21 通用不改 |
| V21 | 模块化程序集:Siemens.Engineering.Base.dll、Siemens.Engineering.Step7.dll 等 | 21.0.0.0 | 官方称”出于网络安全原因,Openness API 在 V21 重构”,破坏性变更,必须重编译 |
V21 官方 NuGet 扩展包的兼容性警告原文:
“TIA Portal Openness Extensions V21 are only compatible with TIA Portal V21 and above. TIA Portal V21 introduces breaking changes in the Openness API. Applications built against TIA Portal versions below V21 are not compatible with V21 and above.”
另一个关键 V21 版本事实:TIA Portal V18 的完整 Openness 安装不含 Siemens.Engineering.HmiUnified 程序集,所以 Unified HMI 相关工具在 V18 构建里必须隐藏——V18 只能用 Classic/Comfort HMI 家族。
来源:本文综合自 raw 目录下的《英文技术博客》(V19→V21 迁移 FileLoadException 篇、Missing V20 DLL References 篇)、《GitHub 开源》(第 41 条 MCP 多版本构建、Benchmark 入口)、《中文社区实战》(腾讯云社区转载《Openness API 接入全流程》)等抓取结果。
1.4.3 引用方式:两条路 + 一条禁止
官方硬约束:
Copy Local: True不受支持,必须设为False。 不要把 Siemens 的 DLL 复制到输出目录长期分发。
方式 A:app.config(官方多数场景推荐)
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Siemens.Engineering" culture="neutral"
publicKeyToken="d29ec89bac048f84"/>
<codeBase version="xx.x.x.x"
href="FILE://C:\Program Files\Siemens\Automation\Portal Vxx\PublicAPI\Vxx\Siemens.Engineering.dll"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Siemens.Engineering.Hmi" culture="neutral"
publicKeyToken="d29ec89bac048f84"/>
<codeBase version="xx.x.x.x"
href="FILE://C:\Program Files\Siemens\Automation\Portal Vxx\PublicAPI\Vxx\Siemens.Engineering.Hmi.dll"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
方式 B:AssemblyResolve(copy-deploy 等场景推荐)
官方手动版本(把解析逻辑完全自己写死):
static class MyProgram
{
public static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += MyResolver;
RunTiaPortal(); // 业务逻辑必须在独立方法中
}
private static void RunTiaPortal()
{
using (TiaPortal tiaPortal = new TiaPortal(TiaPortalMode.WithUserInterface))
{ /* 业务代码 */ }
}
private static Assembly MyResolver(object sender, ResolveEventArgs args)
{
int i = args.Name.IndexOf(','); if (i == -1) return null;
string name = args.Name.Substring(0, i) + ".dll";
string full = Path.GetFullPath($@"C:\Program Files\Siemens\Automation\Portal Vxx\PublicAPI\Vxx\{name}");
return File.Exists(full) ? Assembly.LoadFrom(full) : null;
}
}
注册表驱动的官方片段版本(siemens/tia-portal-openness-code-snippets 的 Plain.Setup,四级下钻 版本号 → PublicAPI → 完整版本号 → net48):
private const string SubKeyName = @"SOFTWARE\Siemens\Automation\Openness";
public static Assembly ResolveSiemensEngineeringAssembly(object sender, ResolveEventArgs args)
{
var assemblyName = new AssemblyName(args.Name);
if (assemblyName.Name != "Siemens.Engineering.Base") return null; // V21 只拦截 Base
var assemblyVersion = args.Name.Split(',')
.FirstOrDefault(x => x.Contains("Version")).Split('=')[1].Split('.')[0];
using var regBaseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
using var opennessBaseKey = regBaseKey.OpenSubKey(SubKeyName);
using var versionKey = opennessBaseKey?.OpenSubKey(
opennessBaseKey.GetSubKeyNames().FirstOrDefault(x => x.Contains(assemblyVersion)));
var path = versionKey?.OpenSubKey("PublicAPI")?.OpenSubKey(assemblyName.Version.ToString())
?.OpenSubKey("net48")?.GetValue(assemblyName.Name).ToString();
return path == null || !File.Exists(path) ? null : Assembly.LoadFrom(path);
}
⚠️ 三个必须记住的坑:
- JIT 崩溃陷阱(官方专门警告):若在
Main中使用了 engineering assembly 的类型,JIT 会在程序启动时编译Main并因找不到程序集而崩溃。在Main里注册 resolver 是无效的——resolver 必须先跑,且使用工程程序集的方法本身要先被编译。官方解法就是把业务逻辑放进独立方法(上面的RunTiaPortal()),Main只调用它。 - app.config 能校验强名称,AssemblyResolve 不能。官方原文指出 app.config “strong names are considered and malicious modifications to the engineering.dll will result in a loading error”。这是实质性的安全差异,不只是风格问题。
- resolver 返回 null 不能抛异常。路径取自注册表,若返回 null 或
!File.Exists必须返回 null,否则整个 AppDomain 解析链会崩。
方式 C(官方列出但不推荐):直接把 Siemens.Engineering.dll 复制到 Openness 应用目录。这会绕过强名称校验,与 Copy Local: False 的官方要求冲突,仅在受控内网场景偶有采用。
关于 GAC:永远不要把 Siemens.Engineering 程序集注册进 GAC。 官方 NuGet …Openness.Resolver 包的用法是一行 Api.Global.Openness().Initialize();——运行时一次性解析全部程序集,这是手写注册表解析器的官方替代方案。
来源:本文综合自 raw 目录下的《西门子官方手册》(5.2.8 章节、5.2.2 注册表键)、《进阶专题》(程序集解析:app.config vs AssemblyResolve 与 JIT 崩溃陷阱)、《GitHub 开源》(官方 snippets
OpennessAssemblyResolverSnippet.cs、NuGet 扩展包)、《英文技术博客》等抓取结果。
1.4.4 注册表里的版本信息(用于自动生成 app.config)
从 TIA Portal Openness V14 起,每个已安装版本都有一个注册表键,可用于自动生成 app.config:
HKEY_LOCAL_MACHINE\Software\Siemens\Automation\Openness\xx.x\PublicAPI\xx.x.x.x
"PublicKeyToken" = "d29ec89bac048f84"
"Siemens.Engineering" = "C:\Program Files\Siemens\Automation\Portal Vxx\PublicAPI\Vxx\Siemens.Engineering.dll"
"Siemens.Engineering.Hmi" = "C:\Program Files\Siemens\Automation\Portal Vxx\PublicAPI\Vxx\Siemens.Engineering.Hmi.dll"
"EngineeringVersion" = "xx"
"AssemblyVersion" = "xx.x.x.x"
注意:路径中的版本号总是当前安装的 TIA Portal 版本号;多版本 side-by-side 安装会在注册表中形成多组条目。
另一条必须知道的注册表路径:白名单(Whitelist)
HKEY_LOCAL_MACHINE\SOFTWARE\Siemens\Automation\Openness\xx.x\Whitelist\<CustomerApplication.exe>\Entry
"Path" = "E:\Work\Openness\CustomerApplication\bin\Release\CustomerApplication.exe"
"DateModified" = "2014/06/10 15:09:44.406"
"FileHash" = "0rXRKUCNzMWHOMFrT52OwXzqJef10ran4UykTeBraaY=" ← Base64 的 SHA-256
白名单以 exe 的 FileHash + DateModified 双重校验,因此重新编译或改动 exe 会导致白名单失效、需重新授权——这是 CI/CD 流水线部署 Openness 应用时的关键约束。官方给出了计算文件哈希与最后修改时间的方法,使手工/脚本化注册白名单条目成为可行方案(适用于无人值守的自动化部署)。
来源:本文综合自 raw 目录下的《西门子官方手册》(5.2.2、5.2.9 TIA Portal Openness firewall)、《GitHub 开源》(
DotNetSiemensPLCToolBoxLibrary的TiaOpennessWhitelist.EnsureWhitelistEntry)等抓取结果。
1.5 两种运行模式与进程模型
1.5.1 三种进程形态
| 形态 | 代码 | 适用场景 | 关键约束 |
|---|---|---|---|
| 外部 exe + GUI 启动 | new TiaPortal(TiaPortalMode.WithUserInterface) | 交互式开发、需人工确认对话框 | Dispose() 仅断开,不关闭 TIA Portal |
| 外部 exe + 无头启动 | new TiaPortal(TiaPortalMode.WithoutUserInterface) | CI/CD 流水线、批量任务 | Dispose() 会关闭 TIA Portal(若无其他客户端附着);不能使用 HMI 编辑器,但仍可手工或通过 API 打开 “Devices & Networks” 编辑器与编程编辑器 |
| Add-in 内部运行 | 由 TIA Portal 加载 | 需要与 TIA 界面深度集成的交互式工具 | 必须已有打开的 TIA Portal GUI 和项目 |
1.5.2 连接到已运行实例:三种等价写法
// 写法 1:静态枚举 + Attach(官方推荐)
using (TiaPortal tiaPortal = TiaPortal.GetProcesses().First().Attach())
{
tiaPortal.Confirmation += (s, e) => { e.Result = "Yes"; }; // 只能 Yes/YesToAll/No
try { /* ... */ }
finally { tiaPortal.Confirmation -= (s, e) => { e.Result = "Yes"; }; }
}
// 写法 2:按进程 ID 取
Process process = Process.GetProcessesByName("Siemens.Automation.Portal")[0];
Thread.Sleep(TimeNeededToStartUp); // 进程可能尚未准备好响应服务请求
TiaPortalProcess tiaPortalProcess = TiaPortal.GetProcess(process.Id);
using (TiaPortal attached = tiaPortalProcess.Attach()) { /* ... */ }
// 写法 3:从当前实例反查自身
TiaPortalProcess self = tiaPortal.GetCurrentProcess();
同一 Windows 会话内可连接多个 TIA Portal 实例;GetProcesses() 是 TiaPortal 类上的静态方法,不需要先有 TIA 实例。
已连接后可读的实例属性:
| 属性 | 类型 | 说明 |
|---|---|---|
InstalledSoftware | IList<TiaPortalProduct> | 已安装产品信息 |
Mode | TiaPortalMode | 实例启动模式 |
AttachedSessions | IList<TiaPortalSession> | 已连接的应用列表 |
ProjectPath | FileInfo | 已打开工程的完整路径;无工程打开时返回 null 字符串 |
ID | int | 进程 ID |
Path | FileInfo | TIA Portal 可执行文件路径 |
V15.1 手册中对应属性名不同:
InformationVersion、AttachedApplications、ProjectPath(string)、Process。跨版本写代码时要注意。
1.5.3 连接确认提示与 EngineeringSecurityException
Openness 应用连接正在运行的 TIA Portal 时,TIA Portal 会弹出安全确认框:
| 选项 | 效果 |
|---|---|
| Allow connection once | 只放行一次,下次连接仍会再次提示 |
| Do not allow | 拒绝 |
| Always allow connections from this application | 走白名单流程 |
建白名单的官方流程:点 “Yes to all” → 弹出 User Account Control (UAC) 对话框 → 点 “Yes”,系统即把应用写入 Windows 注册表白名单。
⚠️ 连续拒绝三次会抛
EngineeringSecurityException。
1.5.4 四个必须反订阅的事件
| 事件 | 触发时机 | 必答字段 / 注意 |
|---|---|---|
Disposed | TIA Portal 在连接期间被关闭 | — |
Notification | 仅需确认的提示 | Caption / DetailText / Icon / IsHandled / MessageID / ServiceID / Text |
Confirmation | 需要 Yes/No 的对话框 | 必须设 e.Result 为 "Yes" / "YesToAll" / "No",其他值抛异常 |
Authentication | UMAC 保护工程(V17+) | 打开受保护工程时以程序方式提供认证方式 |
所有事件必须在
finally中反订阅。
1.5.5 四个已知卡死/异常陷阱
- 模态对话框导致无限挂起:TIA Portal 弹出模态对话框(保存、兼容性、库冲突)时,Openness 调用会无限阻塞。CI 场景务必用
WithoutUserInterface。 - 一个工程同时只允许一个 Openness 进程:
HRESULT 0x8001010E RPC_E_SERVER_CANTUNLOAD即 TIA 被另一自动化死锁。 - 外部关闭 TIA Portal 后再调用 API 抛
NonRecoverableException。 - 隐式实例迷局:第一次实例化 Openness 时若本机已开博图界面,Openness 会连到一个新建的隐式实例,与你手动打开的窗口互不可见,任务管理器可见多个
Siemens.Automation.Portal进程——调试时容易误以为「项目打开成功了但界面看不到」。
来源:本文综合自 raw 目录下的《西门子官方手册》(5.2.8/5.2.9/5.2.10/5.2.12 章节)、《进阶专题》(进程模型与工程化:WithUserInterface/WithoutUserInterface、多实例与事件处理)、《英文技术博客》(安全错误修复篇)、《中文社区实战》等抓取结果。
1.6 版本对应关系与破坏性变更
1.6.1 版本能力演进总表
| 操作 | V13 SP1 | V14 | V15.x | V16–V18 | V19–V21 |
|---|---|---|---|---|---|
| 打开 / 浏览工程 | Yes | Yes | Yes | Yes | Yes |
| 保存工程 | No | Yes | Yes | Yes | Yes |
| 建/改 PLC 块 | Yes | Yes | Yes | Yes | Yes |
| 创建设备项(PLC/HMI/变频器) | No | Limited | Yes | Yes | Yes |
| 插入到硬件对象 | No | Limited | Yes | Yes | Yes |
| 加槽内子模块 | No | Limited | Yes | Yes | Yes |
| 配置 PROFINET / PROFIBUS 接口 | No | No | Partial | Yes | Yes |
| 编译硬件 | No | Partial | Yes | Yes | Yes |
| 通过 Openness 下载 | No | No | Limited | Yes | Yes |
历史分水岭:V13 SP1 的 Openness 只能浏览工程 + 改块 / watch table / 标签表,没有可写硬件配置树;硬件插入自 V14 SP1 起才可用,V15 起在 S7-1500 / ET 200 / SINAMICS 上稳定。
1.6.2 .NET 目标框架与 ImportOptions 等版本要点
| 版本 | .NET 目标框架 | API 新增/变化 |
|---|---|---|
| V14 / V14 SP1 | .NET Framework 4.6.1+ | Openness 起始版本,V14.0 起安装时默认勾选 |
| V15 / V15.1 | .NET Framework 4.7.2+ | MulticastableTransferArea 引入;DB 重编号 API(SIOS 109798623)需 V15+ |
| V16 | .NET Framework 4.7.2 / 4.8 | — |
| V17 | .NET Framework 4.8(基线) | GetAttribute/SetAttribute/GetAttributeInfos 隐式实现,不再需要显式 cast 到 IEngineeringObject;AssignInterface 可用于事务;导入时可设主系统号;ExternalSource 的 Create 动作被移除;Safety Compile 行为变更(从编译错误变为异常) |
| V18 | .NET Framework 4.8 | 新增 ImportOptions.Override;GenerateBlocksFromSource 错误消息从 前 256 字符扩展到 前 4096 字符;IEngineeringObject.SetAttributes(…, AttributeDelegate) 新增(见 1.7.3);Export/Import*Documents 仍不可用(V20+ 才有) |
| V19 | .NET Framework 4.8 + .NET 6.0 异步 Compile 重载 | 严格口令策略、UMAC on PLC、MulticastableTransferArea 弃用(改用 Address / AddressComposition)、NamedValueType 支持、CAx 命令行工具移除、交付范围含 V16/V17/V18 的 dll(V15.1 的 dll 不再随 V19 交付) |
| V20 | .NET Framework 4.8(首个完整支持 .NET 6/8 宿主的版本,靠兼容 shim) | Compiler 委托签名变更;Simatic SD(YAML)格式自此可用且跨版本兼容 |
| V21 | 仅 .NET Framework 4.8 | 模块化程序集;安装目录 …\PublicAPI\V21\net48\;需同时加入 TIA Engineer + Siemens TIA Openness 两个本地组;TrySetAttribute 等官方扩展重写;Unified 与 Advanced 扩展分成两套类型体系 |
⚠️ 社区实测的版本匹配矩阵(与官方文档略有出入,仅供参考,实测为准):V15/V15.1→4.6.1(典型问题:缺少
SIMATIC.OLB类型库);V16→4.7.2(EngineeringDll版本冲突);V17→4.8(最优稳定选择);V18/V19→4.8/.NET6+。实际案例:V16 项目用 .NET 4.6.1 在加载 HW Configuration 时随机抛TypeLoadException,升级 4.7.2 后消失。框架过低的典型症状:不是编译错误,而是运行时抛
TypeLoadException/FileNotFoundException。TypeLoadException找不到ProjectBase→ 加载了低版本 DLL(V15.1)→ 启用AssemblyResolve精准匹配目标版本。
1.6.3 长期稳定性铁律(官方 “Hints for writing long-term stable code”)
- 应使用
Projects.OpenWithUpgrade而非Projects.Open。后者只能访问”当前版本创建或已升级到当前版本”的工程,访问旧版本工程会抛异常。 - 不要假设编译/比较/上下传结果类的层级与顺序——跨版本会变。
- 只有类名本身长期稳定:
CompilerResult、CompareResult、DownloadResult、UploadResult,以及新增的TransferResult。 - V19 交付范围包含 V16/V17/V18 的
Siemens.Engineering.dll,故基于 V16~V18 编译的应用在 V19 上无需修改即可运行(要用 V19 新特性必须换 V19 dll 重编译);V15.1 的 dll 不再随 V19 交付。 - TIA Portal 同时携带当前版本 + 3 个 legacy 版本的 dll。Add-in / 外部应用跨版本无需修改即可运行;但 Openness 应用运行时机器上加载的 dll 版本必须与开发时一致。
- SimaticML 导出格式仅对当前 Openness 版本有效,不要把它当长期存档格式——这正是 V20 起引入 SIMATIC SD(YAML)的原因。
- V21 是硬性分水岭:跨 V21 必须重新编译并替换程序集引用。
来源:本文综合自 raw 目录下的《西门子官方手册》(7.1 / 7.2 Major changes、《Automating creation of projects》5.2.8)、《英文技术博客》(版本能力矩阵、Project Verification 篇)、《GitHub 开源》(NuGet 扩展包 V21 警告、官方 snippets V18 版本事实)、《中文社区实战》(版本匹配矩阵、V18 踩坑根因速查表)、《进阶专题》等抓取结果。
1.7 长期稳定性与安全性设计要点
1.7.1 Remoting 通道的安全模型
TIA Portal Openness 的 remoting 通道注册为 IpcChannel 类型,属性如下:
| 属性 | 设置 |
|---|---|
ensureSecurity | false |
name / portName | ${Process.Name}_${Process.Id};在非默认 AppDomain 中注册时追加 _${AppDomain.Id} |
priority | 默认值 1 |
typeFilterLevel | "Full" |
authorizedGroup | 内置用户账号 everyone |
官方警告:应避免注册
ensureSecurity不为"false"且 priority ≥ 1 的其他 IpcChannel。
1.7.2 Openness Readme 的部署安全建议
| 建议 | 说明 |
|---|---|
| 以管理员权限安装到程序目录 | 但以普通用户权限运行 Openness 应用 |
| 避免从用户目录动态加载程序集/DLL | 降低 DLL 劫持风险 |
| Windows Service 场景 | 进程可执行文件必须事先加入 Openness 白名单;无法访问正在运行的 TIA Portal 进程,也无法启动带 GUI 的 TIA Portal,只能创建新的无界面实例;强烈建议用专用 Windows 用户账号凭据运行;本地系统账号(local system)因安全限制不支持,会始终抛 EngineeringSecurityException |
1.7.3 V18 的容错属性写入
V18 新增 SetAttributes(…, AttributeDelegate),可逐项容错。官方明确当前支持的对象包括:HW.Features.NetworkInterface、HW.Features.NetworkPort、HW.Features.PlcAccessLevelProvider、HW.Address、HW.Channel、HW.Device、HW.DeviceImpl、HW.DeviceItem、HW.DeviceItemImpl、HW.HardwareObject、HW.IoConnector、HW.IoController、HW.IoSystem、HW.MrpDomain、HW.Node、HW.Subnet、HW.SyncDomain、HW.TransferArea;其他对象的行为与不带 callback 的重载一致。
var kvps = new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>("Name", "DeviceItemName")
};
deviceItem.SetAttributes(kvps, cfg =>
{
switch (cfg)
{
case AttributeReadOnly ro: ro.CurrentSelection = AttributeChoiceSelection.Ignore; break;
case AttributeNameUnsupported ns: ns.CurrentSelection = AttributeChoiceSelection.Ignore; break;
case AttributeTypeUnsupported ts: ts.CurrentSelection = AttributeChoiceSelection.Ignore; break;
case AttributeValueUnsupported vs: vs.CurrentSelection = AttributeChoiceSelection.Ignore; break;
}
});
来源:本文综合自 raw 目录下的《西门子官方手册》(Readme 章节、5.2.8、5.2.22)、《进阶专题》(进程模型与工程化)、《GitHub 开源》等抓取结果。
1.8 许可证与前置条件
| 项目 | 结论 | 依据 |
|---|---|---|
| Openness 本体是否需要单独许可证 | 不需要。是随 TIA Portal 提供的免费选件包;Openness 选件随 TIA Portal 安装介质提供,不需要独立 license key(但整机 TIA 授权仍照常校验) | 官方手册 4.1.2、中文社区、英文博客三方一致 |
| 用户组要求 | 安装组件时自动生成本地用户组 Siemens TIA Openness,安装程序不会自动把当前账户加入其中;V21 需同时加入 TIA Engineer + Siemens TIA Openness 两个组 | 官方 4.1.3;V21 双组要求来自英文博客迁移篇 |
| 版本匹配 | 「TIA Portal Openness」的版本号必须与 WinCC / STEP 7 版本号一致 | 官方 4.1.2 Requirements |
| HSP / 选件包 | Openness 不支持兼容模式。工程中用到的每个 TIA Portal 选件包或 HSP 都必须已安装,应用才能打开或 attach 该工程 | 官方 5.2.8 实例属性章节 |
| 前置安装产品 | STEP 7 Professional / WinCC Professional 等基于 TIA Portal 的产品 | 官方 4.1.1 |
| 补装方式 | TIA Portal 安装时于 Configuration → Options 勾选 “TIA Portal Openness”;已装 TIA 可重跑安装包选「修改/添加组件」补装 | 官方 4.1.2、中文社区 |
⚠️ 资料矛盾点(须知):少数英文博客条目声称 Openness 需要独立许可,提到料号
6AV6 671-2CA00-0AX0或 floating license6AV2 105-0KE07-0AA0,缺许可时首次调用抛EngineeringTargetInvocationException,报错原文TiaPortalException: Openness is not licensed。这与西门子官方手册及全部中文社区资料明确记载的「免费随 TIA Portal 安装、不需独立 license」直接冲突。 官方口径优先:Openness 本体免费。实践中若确实遇到Openness is not licensed,应先确认所用 TIA Portal 产品变体(STEP 7 Basic / WinCC Comfort / Advanced 等)与安装介质是否包含 Openness 组件,再向西门子支持确认。另有历史资料提到 Automation License Manager 中的 Openness 许可项,仅 V14/V15.1 等早期版本可见,V17+ 已合并进 TIA Portal 授权。
来源:本文综合自 raw 目录下的《西门子官方手册》(4.1.1/4.1.2/4.1.3)、《中文社区实战》(Win10 用户组配置篇、V17 实战篇)、《英文技术博客》(V15.1 安装篇、FB 导入导出篇)以及其自述的《已知内容质量问题》等抓取结果。
1.9 官方应用示例生态
官方总入口是 Entry 109821826《SINAMICS/SIMATIC – Automation of engineering tasks in machine building》(Entry date 06/30/2026)。官方强调”There are numerous, freely available application examples for Openness”,并在总览页挂载 TIA Portal Openness Code Snippet(GitHub)、Modular Application Creator Use Cases(GitHub)、NuGet 包与开发指导章节。
1.9.1 应用形态选择(官方选型指南 109816879)
| 形态 | 包形态 | 谁来加载 | 焦点 | 启动前提 | 跨版本兼容性 |
|---|---|---|---|---|---|
| TIA Portal Add-In | .addin 包 | TIA Portal | 较小、频繁重复的手工工作流 | 必须已有打开的 TIA Portal GUI 和项目 | TIA 含 4 个 dll 版本,新版本中无需修改即可继续运行 |
| Openness 应用 | 独立 .exe | 你的进程 | 从基础到全面的工程工作流 | 可在任意时刻独立启动(无需 TIA 已开) | 有 4 个 dll 版本,但运行时机器上加载的 dll 版本必须与创建时一致 |
| MAC Equipment Module | .nupkg 包 | Modular Application Creator | 基于 SIMATIC 库自动生成 PLC 软件 + 向导式 UI | 需先启动 ModularApplicationCreator.exe | 每版本必须重做,无法向前兼容 |
Add-in 能力两类:① 上下文菜单型(可在项目导航、设备与网络编辑器、库 Task Card 中调用,VCI 的 Workspace Editor 上下文菜单也可扩展);② 随工作流自动执行型(无需显式调用,翻译工作流等触发时在特定点执行;V18 时 Workflow Addin 可用于版本控制接口的拖拽与同步)。
Add-in 安装方式:把
.addin复制到 TIA 安装目录Portal Vxx\AddIns(或用户目录%AppData%\Siemens\Automation\Portal V21\UserAddIns\),默认处于禁用状态,须在 Task Card “Add-Ins” 中逐一启用。
1.9.2 官方应用示例清单(按主题分类)
A. 驱动批量与传动工程(Drive Technology)
| 应用 | Entry ID | 类型 | 版本要求 | 核心能力 |
|---|---|---|---|---|
| TIA Portal Massoperation Tool | 109986645 | Openness 应用 | Startdrive ≥V21U1 + TIA V21U2+ | Application ID Siemens.TiaPortal.MassOperationTool,V2.0.3.0;GUI + CLI 双操作面;批量复制/连接驱动与技术对象、批量下装、批量设参。官方明示 4 条限制:仅支持 Profinet 设备;FCPU 安全程序变更后不能走 “Software (only changes)” 下装;不支持带密码保护设备;不支持把 TO 值赋值为对象参数 |
| SINAMICS Serial Drive Commissioner (SDC) | 109774753 | Openness 应用 | 同上 | Application ID Siemens.Sinamics.SDC,V2.1.0.0;Create / Update / Online 三核心动作;支持 G 产品族 + S210 + S200。V2.1 关键变更:移除 UI Automation,改用原生 Openness 功能(从界面自动化迁移到官方 API 的标志性版本);Update 时保留 PROFINET 设置、PROFIsafe 地址、I/O 地址、partner port interconnection |
| Edit parameters in several drives (EPISD) | 109777633 | Add-In | 同上 | Application ID Siemens.Sinamics.EditParametersInSeveralDrives,V2.0.0.0;支持 S120/S210/G120/G110(G120X 自 V17 起);官方弃用声明:只接受关键修复,不再新增功能,推荐新项目改用 MOT |
| SINAMICS Startdrive Parameter Comparer | 109814528 | Openness 应用 | 同上 | Application ID Siemens.Sinamics.DriveParameterComparer;CSV 比较模式无需安装 Startdrive,S120 工程直连比较需装;Cross-Project Comparison 需 V18+ |
| TIA Portal Drive Technology Commissioner | 109779879 | Openness 应用 | TIA V19 或 V20 | Application ID Siemens.DTCom,V19.1.0.0;统一处理 S7-1200/1500 Motion Control TO、SINAMICS 驱动、SIMOTION TO 与驱动、SINUMERIK 归档共 4 类对象 |
| SINAMICS Democase creator | 109799613 | Add-In | Startdrive V20 + TIA V20 | Application ID Siemens.Sinamics.Democase;数秒内自动创建 democase,可插入任意已有工程。官方支持的 democase 与订货号:S120 Democase V1 6ZB2480-0CN01;V2(配 S7-1513F-1 PN)6ZB2480-0CT00;V3(无 CU)6ZB2480-0DA00;V3 配 CU320-2 6ZB2480-0CU00;V3 配 CPU 1504D TF 6ZB2480-0CV00;S210 PN 双轴 6AG1067-1AA33-0AA0;V90 PN 双轴 6AG1067-1AA32-0AA0;S200 PN 双轴 6AG1067-1AA42-0AA0;G220 PN 单轴 6AG1067-1AA36-0AA0 |
| S120 Motor Module Change | 109972123 | Add-In | 同 MOT | Application ID Siemens.Sinamics.MotorModuleChange,V3.0.0.0;双/单电机模块双向互换并保留原有电机参数;支持 V19/V20/V21 |
| SINAMICS Drive Coupling Assistant | 110006149 | Add-In | 同 MOT | Application ID Siemens.Sinamics.DriveCouplingAssistant;机械耦合 S120 伺服轴频响优化,自动备份→设定→事后还原。限制:仅 S120、仅 Startdrive V21、固定 DDS 0、不支持多个 drive data set |
| Torque Pre Control Assistant | 109955411 | Add-In | TIA V19 | Application ID Siemens.Simatic.TorquePreControlAssistant;仅支持 SINAMICS S120 + SynchronousAxis / PositioningAxis,且限 TIA V19 |
| SINAMICS Migration Tool | 109822030 | Openness 应用 | — | 迁移类应用(⚠️ 抓取只得到条目元信息,功能细节未取得) |
| TIA Portal Advanced AML Importer | 109999915 | Openness 应用 | 跨 Startdrive 与 STEP 7 | 高级 AutomationML 导入器(⚠️ 同上,应用元数据未取得) |
| S120 Synchronism with DCB Extension | 109757232 | 应用示例 | — | S120 同步应用加 DCB Extension 库,联动 SIMOTION SCOUT |
| SINAMICS DCC: Winder | 38043750 | 应用示例 | — | 基于 DCC 图表的卷绕助手(八位数老号段条目) |
B. PLC 侧工程生成与数据交换
| 应用 / 工具 | Entry ID | 说明 |
|---|---|---|
| SIMATIC Modular Application Creator (MAC) | 109762852 | 主程序,最新 21.0.9(适配 TIA V21);历史 20.1.6(V20)、19.2.10(V19)、18.1.3(V18)、17.2(V17)。工作流:读已配硬件的工程 → 显示可用 Equipment Modules → 分配给 PLC → 在面向应用的技术视图中配置 → 工具校验非法输入与参数交叉依赖 → 一键生成软件组件/参数值/驱动互连 → 下载到 PLC 或软件 PLC 测试。许可:V17.2 及更高版本的基础应用无需 license;部分 Equipment Module 需一年期订阅许可(到期前 30 天未取消自动续期):Multi-Carrier-System/TS 2 Booster 6ES7823-1ME01-0BN8、Converting Toolbox 6ES7823-1ME02-0BN8、Print Standard 6ES7823-1ME03-0BN8 |
| MAC Equipment Modules | 109762849 | Equipment Module 总览与订阅许可清单 |
| MAC Module Builder | 109762852(同一 Entry) | Visual Studio Extension 开发新 Equipment Module,编译产出 .nupkg。版本矩阵:VS 2019 → V17.2.0 / V18.1.3 / V19.2.10 / V20.1.6 / V21.0.5;VS 2022 → … / V21.0.9 |
| SIMATIC LRWDB Generator | 109973522 | 面向 STEP 7 Basic / STEP 7 (TIA Portal) 的工程数据生成(⚠️ 应用元数据未取得) |
| TIA Add-Ins Getting Started | 109779415 | 分步引导创建第一个 TIA add-in 形式的 Openness 应用(V16/V17) |
| Startdrive Export Drive Info | 109766013 | 从多个 TIA Portal 工程读取驱动信息并导出 *.csv(V15.1) |
| 应用示例合集 V15.1 | 109763476 | V15.1 世代 Startdrive Openness 应用示例(V16/V17 对应 109777633) |
| Coupled Axes Optimization | 109986517 | 耦合轴优化相关文档 |
| SINAMICS S Drive Optimization Guide | 60593549 | S 驱动优化指南(老号段) |
| TIA Package Manager | 109975682 | 一键下载/解压/建快捷方式并获得更新通知;附件均带 SHA-256 |
C. 导航与选型
| 页面 | Entry ID |
|---|---|
| Automation of engineering tasks in machine building(总览页) | 109821826 |
| TIA Portal Add-Ins(使用者视角选型) | 109773999 |
| TIA Portal Add-In Overview for developers(开发者视角) | 109817270 |
| 选型指南:Add-In / Openness 应用 / MAC Equipment Module | 109816879 |
| Overview solutions for production machines | 109822010 |
| Openness for SINAMICS Startdrive and DCC(总览页) | 109763491 |
| FAQ:Startdrive multiuser engineering 与 Openness 的限制 | 109755940 |
| 文档总导航(可生成 Entry ID .txt 清单) | 109792902 |
| SHA-256 / SHA-512 校验值计算说明 | 109483101 |
| Continuous Integration with TIA Portal(官方 CI 应用示例) | 109801203 |
| TIA Test Suite Advanced / Excel Importer Exporter | 109792619 |
⚠️ 官方应用生态的一条重要洞察(来自进阶专题):Startdrive Openness 的官方逐版功能演进清单(109763491)本身就是一份最有价值的版本能力对照表。例如 V20 新增按字节读取 telegram 长度(应对字数不等长的新 Safety telegram)、读取 Drive Object 号、处理 telegram 1100;V19 新增第三方编码器配置(CU3x0-2、G220)、G220 主 telegram 插入/删除、离线 Safety 校验和计算、UMAC 激活/去激活、Drive Data Encryption;V18 新增 DCC 图表扩展(插入/创建/编辑图表内容);V16 新增 DCC 的 Openness 支持、导入 DCB Extension 库、新硬件 CU310-2 / CU adapter / DQ hub;V15.1 新增按完整 MLFB 添加驱动硬件组件(全系列)、全系列 PROFIsafe telegram 配置、从 EPLAN 导入 AML。
官方各版本编程手册 Entry ID 映射:V19 → 109826886、V18 → 109815199、V17 → 109798533、V16 → 109773802、V15.1 → 单独的
Startdrive_V15.1_Openness_EN/DE.pdf。
来源:本文综合自 raw 目录下的《西门子官方手册》(109821826 总览页及其 Related content 逐条)、《中文社区实战》、《进阶专题》等抓取结果。
1.9.3 官方 GitHub / NuGet 开源资产
| 资产 | 地址 / 包名 | 关键信息 |
|---|---|---|
| 官方代码片段总仓 | github.com/siemens/tia-portal-openness-code-snippets | 67 stars;面向 V20;用 NUnit [TestFixture] 组织且刻意不使用任何断言(便于逐方法 Run);命名规则即文档:.Plain.* 裸调原生 API、.WithExtensions.* 用 NuGet 扩展包、.Plain.Setup 公共装配;10 个工程、41 个 .cs 文件;三种构建组合对应三种安装(Step7ProjectSubset.slnf / StartdriveProjectSubset.slnf / 完整 .sln)。许可为 Siemens “Royalty-free”(非 MIT/OSI) |
| Unified 封装库 | tia-portal-applications/tia-portal-openness-unified-library | 13 stars;定位”做新 Openness 工具的起手式基础库”;只有一个类 UnifiedOpennessConnector,是 IDisposable;集成方式是直接复制 dll 加引用而非 NuGet |
| OPC UA 用户建模接口 Add-In | tia-portal-applications/tia-addin-opc-ua-modelled-interface | 20 stars,V2.0.0;用 Openness 遍历项目自动生成 OPC UA 服务器 user-modelled interface;性能收益:客户端创建 monitored item、读写数据最多快 6 倍;S7-1200 与 S7-1500 R/H 型本身不支持默认 SIMATIC server interface,此 Add-In 是其自动化出路;分支对应版本是硬性规则:V17~V20 用 …-v20 分支,V21+ 用 main |
| HMI 脚本导出 + Excel 总览 | tia-portal-applications/TIA-Add-In-ShowScripts | 15 stars;把所有画面的 JavaScript 导出为可读文件到 UserFiles,并产出 Excel 全画面总览 |
| PLC 网络 → JSON | tia-portal-applications/TIA-Openness-Create-Synoptic-Unified | 5 stars;导出 PLC 各网络信息为 JSON 供 Unified 集成。命名约定是硬性前提:PLC 块注释必须含 #Synoptik;输出到 <项目文件夹>/UserFiles/Synoptic。作者自述只是原型,目前还不能导出全部内容 |
| Add-In 工程脚手架 | tia-portal-applications/tia-addin-build-package | 11 stars;创建/管理开发 TIA Add-In 所需全部文件,装上即可开发。最后更新 2022-06,新版 TIA 可能需调整 |
| UMC 配置脚本 | tia-portal-applications/UMC-config-scripts | 4 stars;PowerShell 而非 C#——印证”PowerShell 适合轻量 Openness 脚本”这一官方论坛观点 |
| 官方 NuGet 包族 | Siemens.Collaboration.Net.TiaPortal.Packages.Openness(全部 Siemens.Engineering 程序集)、…Openness.Resolver、…Openness.Extensions、…Openness.Startdrive.Extensions | 版本号大版本必须与 TIA 版本对齐(17.* = V17、21.* = V21);包名 Prefix Reserved;目标框架 .NET Framework 4.8;自动级联安装,不得单独安装;Resolver 用法 Api.Global.Openness().Initialize() |
官方 NuGet 扩展包最有价值的部分——原生 Openness API 没有的属性操作:
// 跨设备复制属性(原生 Openness 做不到)
var sourceAttributeInfo = deviceA.GetAllAttributeInfos(tiaPortal, accessMode, null, excludeTypes);
var targetObjectAttributeInfo = sourceAttributeInfo.Copy(targetDevice);
targetObjectAttributeInfo.ApplyValuesFrom(sourceAttributeInfo);
// 安全读写属性
if (deviceItem.TrySetAttribute("Name", "My Device Item"))
logger.Information("Name was set");
来源:本文综合自 raw 目录下的《GitHub 开源》45篇条目、《西门子官方手册》总览页 109821826 等抓取结果。
1.9.4 第三方开源生态
| 仓库 | 星标 | 语言 / 许可 | 定位 |
|---|---|---|---|
dotnetprojects/DotNetSiemensPLCToolBoxLibrary | 477(本主题最高) | C# / LGPL-2.1 | 同时支持 S5/S7 传统项目与 TIA Portal Openness。多版本 Openness 隔离机制:每个版本一个独立程序集 …TIAV13 ~ …TIAV21,主工程用反射动态加载。硬性互斥限制:一个应用实例只能加载一个 TIA 版本,试图加载第二版本抛 “You cannot open a project in V if you already have a project open in another version. You need to restart the Application!”。其中的 TiaGitHandler.exe 是本主题的 Git 版本控制方案——剥离导出文件中的时间戳与易变元数据避免无意义 diff |
mking2203/CodeGeneratorOpenness | 166 | C# / GPL-3.0 | 基于 TIA V16 的代码结构生成器;能以 graph 形式生成顺序流程。作者明确说顺序图生成代码是”逆向工程”得来的——官方无文档,他用 V14 的 Openness Scripter 导出若干 graph 反推结构 |
Maroder1/TIA-openness | 43 | Python / MIT | 20 行最小 demo,被引用最多的入门示例(西门子官方论坛 JK_NO 于 2019-06-13 推荐)。依赖被 environment.yml 钉死:python=3.7.3、pythonnet==2.4.0 |
Parozzz/TiaUtilities | 38 | C# / GPL-3.0 | SimaticML 解析库 + LAD 代码生成 Add-In,支持 V16~V19,2026-10 仍活跃维护。SimaticML 建模 API 层次:BlockFC/BlockFB/BlockDB/BlockInstanceDB/BlockUDT;变量必须先选 Section 才能创建;支持安全梯形图 SAFE_LADDER |
Repsay/tia-openness-api-client | 52 | Python / MIT | 最活跃的 Python 封装;tia_portal.config.load() + tia_portal.Client() 两步。USAGE.md 列出 5 条限制,最关键两条:不支持直接 import 完整硬件组态/项目;HMI 只能创建设备,软件层操作很有限 |
Industrialcarbon-basedorganisms/TIA-Portal-Openness-Project | 41 | C# / 无 License | 自动生成 Modbus 程序(输入波特率、校验、站号、读写、地址、长度、数据类型、名称)。支持 V14SP1~V19。环境限制:家庭版操作系统不支持(需域环境才能用 Windows 用户组机制) |
Maroder1/Openness_examples_python | 35 | Jupyter / 无 License | Notebook 版进阶示例(compile、export/import、networking)。明确没有 LICENSE 文件,复用授权不明 |
tia-portal-applications/SiOME-Base-Plugin | 12 | HTML / MIT | 该组内唯一 MIT 许可且唯一带真实 fork(7 个) |
caprican/SimaticML | 12 | C# / 无 License | 解释并配套实现 SimaticML 与 AML 两种 XML 交换标准,是理解所有导入导出格式的基础 |
⚠️ 注意两个 0 星 fork 陷阱:搜索「tia-openness-api-client」会同时命中
mgrillo75/…(2024-09 停更)与OsamaS99/…(创建当天即停更)两个 fork,README 与上游逐字相同。真实维护方是Repsay。
来源:本文综合自 raw 目录下的《GitHub 开源》第一/三/四部分 45 篇条目等抓取结果。
1.10 AI / MCP 新形态(2025–2026 新趋势)
2025–2026 年最显著的新形态是:Openness 不再只被人类直接调用,而是被封装成 MCP 工具 / CLI / Skill,交给 LLM Agent 驱动。
1.10.1 生态盘点
| 项目 | 规模 | 覆盖版本 | 形态与特点 |
|---|---|---|---|
bulaofen0036-coder/TIA_Portal_Openness_MCP | 280 stars / 73 forks,MIT,创建 2026-05-14,最后推送 2026-09-27 | V20 / V21(STEP 7 + WinCC Unified) | 把 Openness 封装成 MCP 工具。核心卖点:提供预构建运行时(prebuilt runtime),无需自己写 Openness 代码。覆盖项目管理、硬件配置、PLC 编程(SCL/LAD/Tag/UDT/DB)、Unified 画面生成、编译与诊断;支持用 YAML/JSON 描述需求,一条命令生成完整工程。最有工程价值的设计是”预热(warm-up)“模式:用一个常驻 headless TIA 实例把后续命令的连接时间从 200~340 秒降到不到 1 秒 |
1748477582/tia-portal-openness-mcp | 91 Star / 36 Fork 快照,MIT | V18 / V20 / V21 分支构建 | 一个 csproj 对应一个 TIA 版本(TiaMcpServer.V18.csproj / .V20 / 无后缀=V21),靠输出目录 bin-v18/、bin-v20/ 区分;构建需传 -p:TiaPortalLocation="C:/Program Files/Siemens/Automation/Portal V18"。工具数量:V18 版 203 个,V20/V21 版 231 个;V18 通过 #if !TIA_V18 自动隐藏 28 个工具(23 个 Unified HMI + 5 个 VCI)。Export/Import*Documents 与 S7DCL 人类可读文本导出是 V20+ 才有 |
| TIA-Portal-CLI | — | V19 – V21 | 让 Claude Code / Codex / Cursor 通过 CLI 处理离线工程。README 列 100+ 动词。链路:Claude Code/Codex/Cursor → tia <verb> --json → tia.exe → Siemens Openness API。四条安全边界:① 默认预运行(变更类 verb 只返回”计划更改的内容”,需 --apply 才落盘);② 不操作在线 PLC;③ 本地/本地部署(工程数据不出厂区);④ 变更后串联编译、审计、交叉引用,重跑视为 no-op(接近声明式协调)。构建可复现性硬约束:因许可原因,西门子 Openness 程序集无法与 CI runner 捆绑,纯公共 CI 无法实现完整构建/测试复现。许可版本陷阱:v2.0.0 之前是 MIT,main 分支声明对后续版本应用 AGPL-3.0 |
huahaizo/tia-portal-openness-ai | 70 stars | V15 ~ V21 | AI-ready skill + C# 脚手架,本清单中版本跨度最宽的自动化辅助项目。以「skill」形式提供给 AI agent |
StaniB88/AnyAutomationStudio(前身 TIAOpennessManager 43★) | 11★(前身 43★) | V15 ~ V21 + CODESYS | AI 驱动的工程 IDE;用 Claude/GPT/Gemini 或本地 Ollama 生成 SCL;在 PLCSIM Advanced 上跑单元测试(本清单中唯一强调测试验证闭环) |
renanlido/tia-harness | 8 stars | 仅 V21 | Claude Code 原生插件(skills + subagents + tia MCP),而非独立 MCP server。不向下兼容 |
| TiaCommander(tiacommander.com) | — | V17 – V20 | ”Connects any MCP-compatible AI assistant to Siemens TIA Portal via the Openness API.”;GitHub 列出 18 个工具、205 个 actions |
tia-portal-mcp(Czarnak) | — | V21 | 直接通过 Openness API 把 TIA 工程暴露给 MCP Client:项目发现、PLC Block 导入导出、Tag Table、硬件与网络、交叉引用、硬件目录、编译诊断 |
TIA Portal Docs MCP(tia.hubwiz.com/ai-coding-docs) | — | — | 解决”AI 如何准确理解 TIA 指令与官方文档”,提供 2800+ 编程章节,通过 MCP get_docs 做语义检索 |
| Gangdolf Automation TIA Portal MCP | — | V17/V18/V19/V20/V21 | 提供五版本构建,并扩展 WinCC Unified 能力(PLC Tag 导出、HMI Text List、Screen、Screen Item) |
PLCs.ai MCP Server | — | Siemens + Allen-Bradley | 工具含 plcs_list_projects、plcs_get_project_source、plcs_generate_code、plcs_start_analysis、plcs_get_live_values 等;MCP Endpoint 用 Streamable HTTP |
1.10.2 三条技术路线分类
| 路线 | 定位 | 代表项目 |
|---|---|---|
| Engineering MCP | 操作工程 | TIA_Portal_Openness_MCP、tia-portal-mcp、TiaCommander、Gangdolf TIA MCP |
| Knowledge-Context MCP | 拿可靠知识 | TIA Portal Docs MCP(2800+ 章节) |
| Runtime MCP | 观察甚至操作运行中 PLC | MCP PLC Core(Adapter 支持 Siemens/Schneider/ABB/Mitsubishi/Omron/AB/WAGO/CODESYS/Modbus/S7/OPC UA) |
作者判断:成熟系统应三者结合——MCP 只是”接口层”,真正的下一层是理解项目结构、调用关系、变量依赖、硬件关系与工程历史的 PLC Context Layer。
1.10.3 TiaLink 的「执行层」设计思路
TiaLink 的定位是 Tool Layer / Execution Layer,明确不绑定 AI 模型、不绑定某个 Agent——“AI 是大脑,TiaLink 是手,TIA Portal 才是执行环境”。示例工具名:read_project()、configure_hardware()、configure_network()、create_plc_block()、compile()、simulate()。
五类能力:① 自然语言直接做硬件组态(如 “CPU 1513-1 PN + 16DI + 16DO + 模拟量模块”);② 配工业网络(CPU 192.168.10.1、远程 IO .11/.12);③ 需求直接进 PLC 程序(生成 SCL → 建/改块 → 写入 → 编译 → 读编译结果 → 改 → 再编译);④ 生成 WinCC Unified 画面/变量/控件;⑤ 仿真测试闭环——启动 PLCSIM 虚拟 CPU、下载、跑测试场景、读结果、判断通过,失败则继续改。
必须本地运行的理由:PLC 工程含工厂设备信息、网络配置、IP、程序、HMI 画面、工艺逻辑、设备参数、企业知识,上传到云端再由云服务操作是很多企业无法接受的。作者指出这可能是 Agent 能否进入生产现场的前提条件之一。
1.10.4 用 LLM 生成 SCL 再导入的工作流(可直接落地)
这是 2026 年最实用的 AI 辅助形态,不需要 MCP、不需要写任何 C#——因为 LLM 直接产出 SCL 文本,而 SCL 是 Openness 唯一「官方可编程生成程序」的入口:
| 步骤 | 操作 | 要点 |
|---|---|---|
| 1 | 用 LLM 生成 SCL | 提供块接口需求、CPU 型号、命名约定;要求输出完整可编译的 SCL,含文件头属性行 |
| 2 | 人工/脚本校核语法 | 检查 { S7_Optimized_Access := 'TRUE' }、VERSION、# 前缀、; 分号;UTF-8 编码(部分场景需 UTF-8 BOM) |
| 3 | 用 TIA Portal UI 验证(可选但推荐) | 项目树 →「外部源文件」→「添加新的外部文件」→ 选中 .scl → 右键「从源生成块」。检查日志区显示「0 错误,0 警告」,程序块图标由灰转亮即就绪 |
| 4 | 或用 Openness 批量导入 | 见第 4 章 ExternalSources.CreateFromFile + GenerateBlocksFromSource(GenerateBlockOptions.KeepOnError) |
| 5 | 编译并提取错误回传给 LLM 修复 | 不要把”导入成功”等同于”工程正确”——真正有价值的是导入后自动编译、提取错误、回传给 AI 修复 |
常见生成错误与处置:
| 报错 | 含义 | 处置 |
|---|---|---|
| 错误 1201:源文件语法错误 | SCL 语法错 | 用 TIA Portal 打开源文件,按日志提示行号检查缺分号/括号/拼写 |
| 错误 1203:块版本不兼容 | 源来自更高版本 TIA | 在相同或更高版本 TIA Portal 中重新执行「从块生成源」再导出 |
| 块显示为灰色不可编辑 | 编译未完成或存在警告 | 解决所有问题后重新生成 |
Block "FB_name" not found | 实例 DB 引用的 FB 不在项目中 | 先生成/导入 FB |
S7_Optimized_Access attribute missing | 属性行位置错 | 属性行必须紧跟 DATA_BLOCK 头之后 |
Duplicate block name | 同名块已存在 | 先 Find(name)?.Delete() 或直接覆盖导入 |
外接编辑器配置(不写代码的轻量路线):VS Code 装 Siemens SCL 插件(语法高亮),推荐 Trae AI(AI 辅助编程)直接编辑并支持语法检查;Notepad++ 直接打开 .scl,建议装自定义 SCL 语法高亮插件(需手动配置)。
Office 脚本化注意事项:博途无插件时 Text list 只能用 XLSX 导入导出,Openness 中只能用 XML。要 Excel 打通,官方给了 109792619 应用示例的 Excel Importer / Exporter 完整代码包(109792619_Excel_Importer_VX_X_CODE.zip,约 691 KB),但硬性前提是 TIA Portal Openness 版本必须与 SIMATIC WinCC Unified 版本一致(示例基于 WinCC Unified V21,订货号 16AV2151-0XB02-4AA5;Excel 2010 及以上)。表结构规则:第 1 行是属性名(列顺序任意,按名字映射),第 2 行起每行一个对象,第 1 列固定为对象类型(如 HmiIOField);映射粒度是一个 Excel 文件 = 一块新画面;重复运行会覆盖已生成的画面(官方明确警告)。
“从 Excel 清单生成 DI/DO 梯级”的推荐路径排序:① 首选 Openness + C# 读 Excel 直接建/改块;② 次选逻辑写 SCL、导出、外部改、再导回;③ 最简场景先用 CSV 导标签再由 Openness 生成逻辑块。
⚠️ 来源可靠性标注:本节 1.10.1 中
bulaofen0036-coder/TIA_Portal_Openness_MCP与1748477582/tia-portal-openness-mcp的 README 正文未完整抓取(仅取得 GitHub API 元数据与 WebSearch 快照),星标数为抓取时刻实时值、迭代很快。1.10.3 TiaLink 与 1.10.4 中的 MCP 工具名均为第三方博客/聚合文章描述,并非西门子官方 API,落地前需对照官方手册核实。
来源:本文综合自 raw 目录下的《GitHub 开源》第四部分(AI/MCP 新形态 5 篇)、《英文技术博客》(7 个 MCP Server 综述、TiaLink、TIA-Portal-CLI)、《中文社区实战》(西门子中国论坛《如何导入 AI 工具写的 SCL 文件》、腾讯云社区转载 AI 接入全流程)、《进阶专题》(Excel Importer/Exporter)等抓取结果。
1.11 官方入门资源清单
| 资源 | Entry ID / 地址 | 用途 |
|---|---|---|
| 中文简介与演示应用 / Demo Application | 108716692 | 含 TIA_PortalOpenness_Demo_V20 等官方示例(⚠️ 示例与安装版本不匹配是高频故障原因) |
| 文档与链接总览 | 109792902 | 查任何 Openness 专题的首选起点 |
| 视频介绍 | 109752012 | 快速建立直观认知 |
| TIA Openness Explorer | 109760816 | 快速了解和应用 TIA Openness API |
| EPLAN Electric P8 集成过程数字化案例 | 109748224 | CAx/AML 集成实参 |
| 官方 FAQ / 使用技巧(中文) | support.industry.siemens.com/cs/cn/zh/view/109251656 | 中文排错入口 |
| V15.1 系统手册中文版缓存 | cache.industry.siemens.com/dl/dl-media/163/109477163/ | 中文系统手册 |
| 编程手册文档号 | A5E51426768-AA | TIA Portal Openness Programming Guide |
| 另一参考手册 Pub ID | A5E02486780 | — |
官方官方入门项目 StartOpenness 包含 5 个部分(示例工程为 StartOpenness V17):① TIA 博途(界面打开/关闭);② 项目(打开、连接、保存、关闭);③ 添加设备(PLC/HMI 设备添加);④ 编译(软硬件编译);⑤ 文本显示(执行状态显示)。项目提供中文说明,从如何创建 Visual Studio 项目到每个功能的具体代码都很详细,资源需登录后下载。
一个值得记住的官方收益数据:西门子官方统计——首次执行 vs 重复执行对比,Openness 自动化把单次项目生成从 180 分钟降到 75 分钟,总耗时 160 分钟、其中 Openness 占 75 分钟(变化率 −80%)。
一条行业观察(来自 CODESYS 应用指南,很真实):西门子 Openness 基础文档 1900 页,却没有像样的 Getting Started 和 Demo code;实际非标行业用它是因为开发周期短 + 公司要求统一模板(一条产线数十工站,内容重复只是改数组序号)。
来源:本文综合自 raw 目录下的《中文社区实战》(TIA 博途自动执行工程任务——Openness 入门篇)、《英文技术博客》、《进阶专题》(能力边界与工程路线选择、SimaticML 数据交换)等抓取结果。
⚠️ 关于本章内容的质量说明
本章在整合过程中发现 raw 资料里存在若干API 签名存疑的条目,按本书质量要求在此显式标注,不要照抄这些签名:
| 存疑内容 | 出现来源 | 官方实际应为 |
|---|---|---|
Siemens.TiaPortal.Openness 命名空间 | 英文博客「Adding Hardware Programmatically」 | Siemens.Engineering |
project.Devices.CreateDeviceItem(...) | 同上 | Devices.CreateWithItem(...) 或 CreateWithOrder(...) |
TiaPortal.GetTiaPortalContext() / TiaPortalConfiguration.Create() | 英文博客「Project Verification」 | new TiaPortal(TiaPortalMode.…) / TiaPortal.GetProcesses() |
TiaPortal.GetProcess()(无参静态,返回可 Attach() 的对象) | 英文博客「Multilingual Tag Comments」 | TiaPortal.GetProcesses() 或 TiaPortal.GetProcess(int id) |
PlcBlock.ExportToFile(string, ExportOptions) / Blocks.ImportFromDocuments(string, ImportOptions) | 英文博客「Exporting and Importing FB」 | PlcBlock.Export(FileInfo, ExportOptions) / Blocks.Import(FileInfo, ImportOptions) |
IDeviceExport、IGSDCatalog、CreateSegmentImport | 英文博客「Adding Third-Party Hardware」 | 需核对官方 Siemens.Engineering.Cax / HwUtilities 章节 |
project.FCBlocks / block.Instructions / inst.Text | 英文博客「Global Find and Replace」(原文已标注为伪代码) | Openness 无逐行编辑能力,此类 API 不存在 |
new TiaPortal(new TiaPortalStartArguments()) / project.HmiTargets[0] | 英文博客「HMI 批量生成」 | new TiaPortal(TiaPortalMode.WithUserInterface);HmiTarget 在官方对象模型中挂在 DeviceItem 下(见 1.3.1 架构图),不在 Project 下 |
TiaPortalMode.WithUsering | 英文博客 Python 教程(原文自注拼写如此) | TiaPortalMode.WithUserInterface |
tia.AttachToProcessByProcessId(0) | 英文博客 Python 教程 | 原文自注”被 pythonnet 转换后并不精确”,可靠做法是枚举 Processes 后 Attach() |
tia.Projects.Create(r"…\MyProject.ap19", ProjectGenerationTarget.V19) | 同上 | Projects.Create(DirectoryInfo, string name)(官方签名) |
runtime Siemens.Engineering.dll 从 GAC 加载 | 英文博客「Installing Openness V15.1」 | 与官方”禁止 GAC 注册”直接冲突,应从 PublicAPI 目录解析 |
raw 目录《英文技术博客》文末已自述这一质量问题:“多篇 IMD 文章存在明显 AI 生成痕迹与 API 幻觉……落地时须以官方 Programming Manual 为准。” 本书正文凡涉及此类签名,一律以 txt_api.txt(Entry 109826886 全文转储)为准。
本章速查
- Openness 是工程流程自动化 API,不是 PLC 通信协议。读写实时变量走 Sharp7 / S7net-plus / OPC UA;Openness 操作的是博途软件与离线工程文件。V21 官方定义原文:API for automation of engineering workflows。
- 三大硬边界:① 不能逐行编辑 LAD/FBD/STL 逻辑;② 不能切换 CPU 运行模式、不能 Unforce、不能读诊断缓冲区;③ 官方禁止用 Openness 做安全系统检查或生成验收数据,SIL/PL 安全逻辑不要用 Openness 生成。
Copy Local必须为False。程序集解析优先用app.config(能校验强名称),其次AssemblyResolve(无法检测篡改)。在Main里注册 resolver 无效——JIT 会在启动时编译Main而崩溃,必须把业务代码放进独立方法。- 所有
GetService<T>()必须判空——服务不可用时返回null而非抛异常。 - V21 是硬性分水岭:程序集从单一
Siemens.Engineering.dll改为模块化(Siemens.Engineering.Base.dll/.Step7.dll),安装目录改为…\PublicAPI\V21\net48\,只支持 .NET Framework 4.8,需同时加入TIA Engineer+Siemens TIA Openness两个组,跨版本必须重编译。V17–V20 为 fat assembly,只有路径字符串变。 - 长期稳定性铁律:用
Projects.OpenWithUpgrade而非Open;不要假设结果类的层级与顺序;只有CompilerResult/CompareResult/DownloadResult/UploadResult/TransferResult这些类名长期稳定;SimaticML 只对当前 Openness 版本有效,不能当长期存档(V20 起改用 SIMATIC SD / YAML)。 - 最高频故障点是用户组:安装组件只自动创建
Siemens TIA Openness本地组,不会自动把当前账户加入其中;加组后必须注销或重启才生效。典型报错原文:“CAx操作无法启动。请检查是否以 ‘Siemens TIA Openness’ 组成员身份登录”。详见第 2 章专节。 - 三种自动化形态各有取舍:Add-In(必须已有打开的 TIA GUI 与项目,跨版本免改)/ Openness 应用(可独立启动,但运行时 dll 版本必须与开发时一致)/ MAC Equipment Module(每版本必须重做)。选型依据官方 109816879。
- AI/MCP 已成新形态:280★ 的
TIA_Portal_Openness_MCP(V20/V21,预构建运行时 + warm-up 把连接时间从 200~340 秒降到 <1 秒)、TIA-Portal-CLI(V19–V21,100+ 动词,默认预运行需--apply才落盘)。但最实用的仍是”LLM 生成 SCL → 导入”:SCL 是唯一官方可编程的生成入口,且工程数据必须留在本机。 - 动手前先读这两条:工程中用到的每个 TIA Portal 选件包或 HSP 都必须已安装(Openness 不支持兼容模式);SIMATIC Step7 的安全能力不替代 Openness 路线。
第2章 环境配置与第一个程序
第 2 章 环境配置与第一个程序
本章导读
本章解决”从零到跑通第一个 Openness 程序”的全部环境问题。核心结论是:Openness 的门槛不在编程,而在两件事——程序集引用的正确姿势,和 Windows 用户组的正确配置。后者是全社区最高频的故障点,很多人加完组不注销、不重启,或者只加了 Siemens TIA Openness 而漏了 V21 需要的 TIA Engineer,于是报出各种看似不相干的错误。本章把用户组配置单开一节写透,同时给出五种启动 TIA Portal 的写法、C# 与 Python 两条技术路线、第一个完整程序的逐行解读,以及一份「新手常见误区」清单。
2.1 环境清单
2.1.1 官方硬性要求
| 项目 | 官方要求 | 出处 |
|---|---|---|
| 操作系统 | Windows 64 位 | 官方 4.1.1 |
| TIA Portal | 64 位,且必须已安装基于 TIA Portal 的产品(STEP 7 Professional / WinCC Professional 等) | 官方 4.1.1 |
| 用户程序位数 | 32 位 / 64 位 / “Any CPU” 均可 | 官方 4.1.1 |
| 开发工具 | Visual Studio 2017 或更高版本 | 官方 4.1.1 |
| .NET | .Net Framework SDK 4.8 + Windows Classic Desktop 包 | 官方 4.1.1 |
| 知识 | 系统工程师 + VS/C#/VB.net 高级知识 + TIA Portal 使用知识 | 官方 4.1.1 |
| HSP / 选件包 | 工程中用到的每个 HSP 都必须已安装(Openness 不支持兼容模式) | 官方 5.2.8 |
Windows 版本支持参考:Windows 7 SP1 / 8.1 / 10 曾被列入支持范围(V13 SP1 及以后)。历史资料提到 V13 SP1 需装 Update 9,并建议升级到 V14 SP1+(后续版本改进了 Openness 安全处理)。另有社区实战资料提到 Win10 / Win11 64 位 + V16/V17 的组合。
2.1.2 各 TIA 版本环境对照表
⚠️ 下表混合了官方要求与社区实测经验。官方口径优先:官方从未要求低于 4.8 的框架,但社区在老版本上确有 4.6.1/4.7.2 跑得通的案例,代价是随机
TypeLoadException。新项目一律选 .NET Framework 4.8。
| TIA 版本 | 建议 .NET 目标框架 | Visual Studio | PublicAPI 路径 | 关键说明 |
|---|---|---|---|---|
| V13 SP1 | .NET Framework 4.5+ | VS 2012+ | …\Openness\PublicDlls\ | 只能浏览工程 + 改块/标签表,无硬件配置树;需 Update 9 |
| V14 / V14 SP1 | .NET Framework 4.6.1+ | VS 2015+ | …\Openness\Inc\PublicApi\ | 硬件插入自 V14 SP1 起可用 |
| V15 / V15.1 | .NET Framework 4.7.2+ | VS 2017+ | …\Portal V15.1\PublicAPI\V15.1\ | 缺失 SIMATIC.OLB 类型库是 4.6.1 的典型坑;含 Docs\ 与 Samples\ |
| V16 | .NET Framework 4.8 | VS 2017+ / 2019 | …\Portal V16\PublicAPI\V16\ | 4.6.1 加载 HW Configuration 时随机抛 TypeLoadException,升 4.7.2 后消失 |
| V17 | .NET Framework 4.8(最优稳定选择) | VS 2019 / 2022 | …\Portal V17\PublicAPI\V17\ | 三个关键 DLL:Siemens.Engineering.dll、Siemens.Engineering.Hmi.dll、Siemens.Engineering.Simatic.dll(生成程序块要用) |
| V18 | .NET Framework 4.8 | VS 2022 | …\Portal V18\PublicAPI\V18\ | 4.7.2 易 DLL 加载失败;Siemens.Engineering.Contract.dll 在 Bin\PublicAPI\ 下;完整安装不含 Siemens.Engineering.HmiUnified,Unified HMI 工具必须隐藏 |
| V19 | .NET Framework 4.8(+ .NET 6 异步 Compile) | VS 2022 | …\Portal V19\PublicAPI\V19\ | 严格口令策略;MulticastableTransferArea 弃用;交付范围含 V16/V17/V18 的 dll,不含 V15.1 |
| V20 | .NET Framework 4.8 | VS 2022 | …\Portal V20\PublicAPI\V20\ | fat assembly;首个完整支持 .NET 6/8 宿主的版本(程序集本身仍针对 4.8 构建,靠兼容 shim) |
| V21 | 仅 .NET Framework 4.8 | VS 2022 | …\Portal V21\PublicAPI\V21\net48\ | 模块化程序集;需 TIA Engineer + Siemens TIA Openness 两个组;V21 trial/USB 许可同样强制绑定规则(不是 license 问题) |
⚠️ 目录名不统一是真实坑点:
V14–V19在部分资料中写作TIA Portal V19(带空格),V20+写作Portal V20;程序集目录 V15.1 为PublicAPI\V15.1,V21 为PublicAPI\V21\net48\;旧版为Openness\PublicDlls\或Openness\Inc\PublicApi\。落地前用Test-Path实测,别照抄。
2.1.3 平台目标必须设为 x64
英文社区的 V15.1 安装与冒烟测试篇明确:平台必须 x64,否则抛 BadImageFormatException。
### 三重验证:① 控制面板"程序和功能"有"TIA Portal Openness Vxx"且更新级别匹配
### ② 程序集清单存在
Get-ChildItem "C:\Program Files\Siemens\Automation\Portal V*\PublicAPI\*.dll" |
Select-Object Name, @{n='MB';e={[math]::Round($_.Length/1MB,1)}}
### ③ 首次编译运行测试(打印 portal.Version 与 portal.Projects.Count)
### 快速探测安装是否存在(不用知道小版本号)
Test-Path "C:\Program Files\Siemens\Automation\Portal V16\PublicAPI\Siemens.Engineering.dll"
来源:本文综合自 raw 目录下的《西门子官方手册》(4.1.1/4.1.2)、《中文社区实战》(V16 使用、V18 机器视觉、V17 实战、开发入门(1))、《英文技术博客》(安装 V15.1 篇、V20 DLL 篇)、《进阶专题》等抓取结果。
2.2 Openness 的安装与授权
2.2.1 主安装流程
Openness 不是独立产品,是 TIA Portal 安装介质的一个子功能:
- TIA Portal 安装时,在
Configuration → Options下勾选 “TIA Portal Openness” 复选框; - 点击 “Next” 并选择所需选项;
- 跟随 TIA Portal 的安装流程完成。
安装前提:
| 前提 | 说明 |
|---|---|
| 管理员权限 | 必须 |
| 关闭运行中的程序 | 必须 |
| 禁用 Autorun | 必须 |
| 已安装 WinCC 和/或 STEP 7 | 必须 |
| Openness 版本号与 WinCC / STEP 7 版本号一致 | 必须 |
安装结果:TIA Portal Openness 装到 PC 上,同时自动生成本地用户组 Siemens TIA Openness。
关键提示:装了组件你仍然没有权限访问 TIA Portal,必须自己是该组成员(见 2.3 节)。新旧版本可 side by side 并行安装,注册表中会形成多组条目。
2.2.2 补装 Openness(已装 TIA Portal 的机器)
补装常失败的几个点与对策:
| 症状 | 根因 | 对策 |
|---|---|---|
| Modify 变灰 / “cannot be started standalone” / 更新安装器不给 Options 树 | Update 安装器是增量补丁包,不提供启用 Openness 的复选框 | 必须用基版本 DVD 启用后再打 update |
| Modify 里没有该复选框 | 许可不含 Openness,或装的是 WinCC Comfort/Advanced 而非含 Openness 的 STEP 7 Professional / WinCC Professional | 换正确产品变体的介质 |
| ”a newer version is installed” | 介质比已装产品旧 | 不要降级,换匹配的介质 |
| 文件锁导致替换失败 | TIA 相关服务在运行 | 安装前以管理员停服务 |
安装前必须停的服务(services.msc 以管理员):
Siemens.Automation.Portal
Siemens.Automation.Portal.Online
S7DOS Help Service
SIMATIC RADIUS Service
停 portal 服务是为了释放待替换程序集上的文件锁。官方安装步骤序列:
Step 4 - Enable the TIA Portal Openness Checkbox→Step 5 - Commit the Install→Step 6 - Re-apply the Latest Update→Step 7 - Final Verification(TIA 内Help → Installed Software)。
默认勾选之争:官方 V19 手册说需要在 Options 里勾选;HwLib(慧兰博)的入门篇说”从 TIA V14.0 开始安装博途时会自动安装 TIA Openness,该选项默认是勾选的”;但实战篇明确指出”Openness 组件默认不勾选,需手动展开”。结论:不要指望默认值,一律主动确认勾选状态。
2.2.3 许可说明
| 问题 | 答案 |
|---|---|
| 需要独立 license key 吗? | 不需要。Openness 选件随 TIA Portal 安装介质提供,是免费选件包(但整机 TIA 授权仍照常校验) |
| 版本匹配要求 | 「TIA Portal Openness」的版本号必须与 WinCC / STEP 7 版本号一致 |
| 自动化 License Manager | 仅 V14/V15.1 等早期版本可见独立 Openness 许可项,V17+ 已合并进 TIA Portal 授权 |
⚠️ 资料矛盾点:少数英文博客条目声称 Openness 需要独立许可(提到料号
6AV6 671-2CA00-0AX0或 floating license6AV2 105-0KE07-0AA0,缺许可时首次调用抛EngineeringTargetInvocationException,报错TiaPortalException: Openness is not licensed)。这与官方手册明确记载的「免费随 TIA Portal 安装、不需独立 license」冲突。官方口径优先。 实践中若真遇到Openness is not licensed,先确认产品变体与介质是否包含 Openness 组件,再向西门子支持确认。另有一份历史资料提到6AV6 671-2CA00-0AX0与 Automation License Manager 中的 Openness 许可,仅适用早期版本。
来源:本文综合自 raw 目录下的《西门子官方手册》(4.1.2 Installing TIA Portal Openness)、《英文技术博客》(Installing TIA Portal Openness V15.1: Modify Existing TIA Setup)、《中文社区实战》(CSDN 实战篇、补装篇、驱动密码轮换篇)等抓取结果。
2.3 【最高频故障点】Windows 用户组配置
这一节请务必看完。 这是中文社区与英文社区交叉确认的最高频故障来源。官方手册 4.1.3 整节讲的就是这件事,且明确写着 “The TIA Portal does not support this operation”——加组必须用操作系统自己的工具,TIA Portal 里做不了。
2.3.1 故障现象与报错原文
| 报错原文 | 触发场景 |
|---|---|
Security error. Can't access this process to acquire needed information. | 老版本(V13 SP1 Update 9 + VS 2015 + Win7)连接 TIA 进程失败 |
Security error. Owner 'xx' of this process is not member of the Windows group 'Siemens TIA Openness'. Please contact your administrator. | 同上,附带明确的组成员信息 |
CAx操作无法启动。请检查是否以 'Siemens TIA Openness' 组成员身份登录 | 导入/导出 .aml 中断;与 EPLAN / AutoCAD Electrical 等 CAx 软件交互失败;通过 API 调用 TIA Portal 被拒 |
| 弹出一个错误框(内容未记) | 首次运行,当前用户不属于 Siemens TIA Openness 组 |
COMException 0x80004005 | COM interop 层权限不足 |
EngineeringSecurityException | 连续拒绝 Openness firewall 授权三次;或用 local system 账号跑 Windows Service |
泛化的 FileLoadException,而非 access denied | V21 缺 TIA Engineer 组时的迷惑现象 |
为什么加组不是充分条件:西门子中国「找答案」有两条高度相似的帖子——一条是 TIA V19 + Win10、已加组仍报 Siemens.Engineering.TiaPortal 类初始化错误(10 楼、官方最佳答案),另一条是”已安装 Openness 但无法调用、官方示例 108716692_TIA_PortalOpenness_Demo_V20 同样无法调用”。结论:加组是必要条件,不是充分条件。
官方最佳答案给出的 9 条排查方向(按顺序):
| # | 方向 | 具体动作 |
|---|---|---|
| 1 | 权限不足 | 加组后仍需以管理员身份运行程序 |
| 2 | TIA 安装不完整 / 组件损坏 | 修复或重装 |
| 3 | Openness 版本不兼容 | 确认与 TIA 主版本匹配 |
| 4 | .NET Framework 问题 | 检查安装状态 |
| 5 | 环境变量配置错误 | PATH 需指向 V19 和 Openness 安装路径 |
| 6 | 注册表项损坏或缺失 | 用 Siemens 工具或重装修复 |
| 7 | 第三方软件冲突 | 暂时禁用安全软件 / 防火墙排查 |
| 8 | 日志文件分析 | 博途和 Openness 日志通常在安装目录的 Logs 文件夹 |
| 9 | 联系 Siemens 技术支持 | 提供错误详情与日志 |
排查顺序总结:管理员运行 → 检查安装完整性 → 确认版本兼容 → 检查 .NET Framework 与环境变量 → 查日志。
2.3.2 组名对照:到底要加几个组
| TIA 版本 | 需要的本地组 | 来源与说明 |
|---|---|---|
| V13 SP1 ~ V20 | Siemens TIA Openness | 官方手册 4.1.3 明确规定,安装时自动创建 |
| V21 | TIA Engineer + TIA Openness(两个组) | 英文博客 V19→V21 迁移篇。原文指出 V21 需两个组,缺组时报的是同一个泛化的 FileLoadException 而非 access denied,极具迷惑性 |
⚠️ 注意组名在不同资料中的写法差异:官方手册一律写
Siemens TIA Openness(带Siemens前缀)。但英文博客的 V21 迁移篇写的是TIA Engineer+TIA Openness(无Siemens前缀)。中文社区另有Siemens_TIA_Openness的写法(用于 AML 交换前置条件描述)。三种写法在资料中并存,实际以本机计算机管理 → 本地用户和组 → 组里看到的组名为准。
2.3.3 图形界面配置法(官方 11 步)
官方手册给出的是完整 11 步(以 Windows 7 为例,其他版本路径相同):
- 「开始」→「控制面板」
- 双击「管理工具」
- 点击「计算机管理」打开同名配置对话框
- 选择「本地用户和组 → 组」,显示所有已创建的本地用户组
- 在右侧窗格选中
Siemens TIA Openness条目 - 选择「操作 → 添加到组…」菜单命令,打开该用户组的属性对话框
- 点击「添加」
- 在弹出的选择对话框中输入有效用户名
- 点「检查名称」可验证该用户名在本域/本机是否有效
- 「从此位置」字段显示域名或计算机名
- 用「确定」确认选择
- 点击「确定」结束操作(可点「添加」按钮注册更多用户)
- 重新登录 PC 使更改生效
社区常用的等价简版(HwLib 慧兰博,10 张图逐步说明):
1. 运行 → 输入 compmgmt.msc(计算机管理)
2. 找到当前用户名 → 右键 → 属性 → 「隶属于」页
3. 「添加」→「高级」→「立即查找」
4. 搜索结果中选 Siemens TIA Openness → 「确定」
5. 关闭窗口后【重启计算机】
6. 重跑项目 → 出现授权访问提示 → 点「全部确定」
另一份保姆级图文给出的八步(图形界面完整流程):
1. 计算机管理 → 本地用户和组 → 组
2. 选中 Siemens TIA Openness
3. 操作菜单「添加到组」→ 「添加」按钮
4. 「高级」→「立即查找」
5. 选系统用户名 → 「检查名称」
6. 「确定」→ 「确定」
2.3.4 PowerShell 配置法(可脚本化,适合批量部署)
图形界面八步的脚本化等价操作:
### ⚠️ 必须以管理员身份运行 PowerShell
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name # 例如 DESKTOP-01\admin
$computerName = $env:COMPUTERNAME
### V16 及以前:单个组
$groupName = 'Siemens TIA Openness'
if (-not (Get-LocalGroup -Name $groupName -ErrorAction SilentlyContinue)) {
New-LocalGroup -Name $groupName -Description 'TIA Portal Openness access'
}
Add-LocalGroupMember -Group $groupName -Member $currentUser -ErrorAction SilentlyContinue
### V21:两个组都要加
foreach ($g in @('TIA Engineer', 'TIA Openness')) {
if (-not (Get-LocalGroup -Name $g -ErrorAction SilentlyContinue)) {
New-LocalGroup -Name $g -Description 'TIA Portal V21 Openness access'
}
Add-LocalGroupMember -Group $g -Member $currentUser -ErrorAction SilentlyContinue
}
Write-Host "已加入用户组:$currentUser"
域环境写法(若组被域策略管理,本地组操作可能无效或被覆盖):
### 域账号需要用域组名,且通常要域管理员权限
Add-ADGroupMember -Identity 'CN=Siemens TIA Openness,CN=Users,DC=yourdomain,DC=com' `
-Members 'yourdomain\engineer'
已知限制:域控组策略可能覆盖本地组。网络驱动器式的
%ProgramData%权限也可能导致注册表加组失败。
2.3.5 验证命令(必做)
### ① 官方/社区通用诊断命令:无输出即表示未加入权限组
whoami /groups | findstr "Siemens"
### ② PowerShell 版(更严谨,直接查组成员)
Get-LocalGroupMember -Group 'Siemens TIA Openness' |
Select-Object Name, ObjectClass, PrincipalSource
### ③ V21 双组检查
foreach ($g in @('Siemens TIA Openness','TIA Engineer','TIA Openness')) {
$exists = Get-LocalGroup -Name $g -ErrorAction SilentlyContinue
if ($exists) {
$member = (Get-LocalGroupMember -Group $g -ErrorAction SilentlyContinue).Name
Write-Host "[存在] $g 成员: $($member -join ', ')"
} else {
Write-Host "[缺失] $g ← 需要补"
}
}
### ④ 确认当前会话的令牌里确实带上了该组(加组后未注销时这里会看不到)
whoami /groups | Select-String -Pattern 'S-1-5-21' | Select-Object -First 5
bash(Git Bash)下等价写法:
whoami /groups | grep -i "siemens"
⚠️ 关键:即使
Get-LocalGroupMember显示你在组里,当前登录会话的令牌未刷新前,whoami /groups仍可能看不到该 SID——这就是”加完组不注销/不重启”仍然失败的机制。验证命令通过后才继续往下走。
2.3.6 第二层权限:让 TIA Portal 允许外部程序访问
用户组是第一层。第二层是 TIA Portal 自身的设置:
| 配置项 | 路径 | 说明 |
|---|---|---|
| 通过外部程序启用 TIA Portal 的访问 | 以管理员身份启动 TIA Portal →【选项】→【设置】→【常规】→【专家设置】→ 勾选 | 确定后完全退出并重启 TIA Portal 使注册表生效 |
| UAC 注册表权限 | HKEY_CLASSES_ROOT\Siemens.Engineering 给当前用户「完全控制」 | Win10 开启 UAC 时,普通权限调用可能启动卡住或抛无厘头 COM 异常(根因是博图 COM 组件注册需管理员权限)。改完重启博图后台进程 |
| 权限三查(社区总结) | ① HKLM\SOFTWARE\Siemens\Automation\Openness 权限② C:\Program Files\Siemens\Automation\Portal V17 的 NTFS 权限③ 防火墙对 s7epatsrv.exe 的放行规则 | 三处任一缺失都会导致连不上 |
| 宿主机权限 | VS 调试进程或编译后的 EXE 也需管理员身份运行 | 推荐做法:VS 以管理员运行 |
来源:本文综合自 raw 目录下的《西门子官方手册》(4.1.3 Adding users to the “Siemens TIA Openness” user group,共 11 步)、《中文社区实战》(Win10 保姆级图文、慧兰博入门(1)、V17 实战篇、TIA_Portal_Openness 深度开发篇)、《英文技术博客》(Fix Security Error 编译为 EXE 篇、V19→V21 迁移篇)、《进阶专题》(AML 前置条件)等抓取结果。
2.4 创建第一个 C# 项目并引用程序集
2.4.1 项目创建步骤(可直接照做)
| 步骤 | 操作 |
|---|---|
| 1 | Visual Studio → 新建项目 → Windows 窗体应用 (.NET Framework) 或 控制台 (.NET Framework) |
| 2 | 目标框架选 .NET Framework 4.8 |
| 3 | 平台目标:x64(否则 BadImageFormatException) |
| 4 | 右键「添加引用」→「浏览」→ 定位到本机 PublicAPI 目录 |
| 5 | 按需添加:Siemens.Engineering.dll(核心,必须);Siemens.Engineering.Hmi.dll(HMI 扩展,只做 PLC 可不用);Siemens.Engineering.Simatic.dll(PLC 扩展,生成程序块要用);Siemens.Engineering.HW.dll(硬件配置相关) |
| 6 | 对每个引用把「复制本地 / Copy Local」设为 False |
| 7 | (V21 或多版本场景)添加 app.config 程序集绑定,或注册 AssemblyResolve |
| 8 | 必须生成独立 exe 后从 bin\Debug\ 或 bin\Release\ 直接运行,不能只在 VS 调试器里跑 |
为什么要引用这么多 DLL:V17 的实战篇明确指出——“很多教程只让引第一个,导致后面 CreateBlockFromFile 这类方法报’找不到类型’“。三个全引用是安全做法。
csproj 中的引用写法(V20 示例):
<Reference Include="Siemens.Engineering">
<HintPath>C:\Program Files\Siemens\Automation\Portal V20\PublicAPI\V20\Siemens.Engineering.dll</HintPath>
<Private>False</Private> <!-- 即 Copy Local = False -->
</Reference>
⚠️ V21 迁移注意:
<HintPath>绝对路径必须更新;Siemens.Engineering.dllstrong-name 升到 21.0.0.0;必须清掉 V20 遗留的 HintPath 避免运行时双绑定。⚠️ 另有社区资料提到 NuGet 方式:
<PackageReference Include="Siemens.Tia.Openness" Version="17.0.0" />。⚠️ 该包名未出现在官方 NuGet 官方包族清单中,官方包族为Siemens.Collaboration.Net.TiaPortal.*前缀,使用前请自行核实。
2.4.2 推荐的工程结构
社区实践总结出一套可复用的分层模板:
MyOpennessTool.sln
├── MyOpenness.Core/ ← C# 类库(.NET Framework 4.8, x64)
│ ├── Configuration/ ← 配置类:路径、版本、常量
│ │ └── OpennessSettings.cs
│ ├── Services/ ← 服务类:封装 TIAPortal / Project / Device 操作
│ │ ├── TiaPortalService.cs
│ │ ├── DeviceService.cs
│ │ └── NetworkService.cs
│ ├── Exceptions/ ← 异常处理:统一异常分类
│ │ └── EngineeringExceptionHandler.cs
│ └── AssemblyResolver.cs ← AssemblyResolve 注册表解析器
├── MyOpenness.Cli/ ← 控制台程序(.NET Framework 4.8, x64)
└── MyOpenness.Gui/ ← WinForms(按需)
分层理由(社区原话):“先建一个 C# 类库(.NET Framework)把 TIA 启动逻辑封装成 TIA_V16 类,再由 WinForm /控制台程序引用该类库”。
2.4.3 一个可复用的工程化模板
// ========== Configuration/OpennessSettings.cs ==========
public sealed class OpennessSettings
{
public string TiaVersion { get; set; } = "V21";
// ⚠️ 路径务必用 Test-Path 实测,各版本层级不同(V15.1 无版本子目录,V21 多一层 net48)
public string PublicApiDir { get; set; }
= $@"C:\Program Files\Siemens\Automation\Portal V21\PublicAPI\V21\net48";
public bool UseUserInterface { get; set; } = true;
public void Validate()
{
if (!Directory.Exists(PublicApiDir))
throw new DirectoryNotFoundException($"PublicAPI 目录不存在:{PublicApiDir}");
}
}
// ========== Exceptions/EngineeringExceptionHandler.cs ==========
public static class EngineeringExceptionHandler
{
/// <summary>统一的 Openness 异常分类与处理策略。</summary>
public static void Handle(Exception ex) => ex switch
{
// 版本不支持该 API
EngineeringNotSupportedException e
=> Console.Error.WriteLine($"[版本不支持] {e.Message}"),
// TIA 内部错误,看 InnerException
EngineeringTargetInvocationException e
=> Console.Error.WriteLine($"[TIA 内部错误] {e.Message} / Inner: {e.InnerException?.Message}"),
// 权限/授权相关
EngineeringSecurityException e
=> Console.Error.WriteLine($"[权限] {e.Message} —— 检查 Siemens TIA Openness 用户组(V21 需两个组)"),
// 已存在可忽略
EngineeringException e when e.Message.Contains("already exists")
=> Console.WriteLine($"[可忽略] {e.Message}"),
EngineeringException e
=> Console.Error.WriteLine($"[EngineeringException] {e.Message}"),
_ => Console.Error.WriteLine($"[未分类] {ex.GetType().Name}: {ex.Message}")
};
}
异常处理资源规范(社区总结):
- 始终用
using包裹TiaPortal- 异常路径用
project.Close(false)而非直接放弃- API 非线程安全,勿多线程共享同一
TiaPortal实例;多项目用多进程而非多线程并行- 所有 Openness 对象都实现
IDisposable,必须按创建反序释放
来源:本文综合自 raw 目录下的《中文社区实战》(TiaPortalOpenness 工业自动化开发实战指南、V16 使用 1、V17 实战篇)、《英文技术博客》(Project Bases 篇、Missing V20 DLL 篇)、《西门子官方手册》(4.1.1)等抓取结果。
2.5 五种启动 TIA Portal 的写法
2.5.1 写法一:GUI 模式启动
using Siemens.Engineering;
using (TiaPortal tiaPortal = new TiaPortal(TiaPortalMode.WithUserInterface))
{
Project project = tiaPortal.Projects.Open(new FileInfo(@"D:\Project_1\Project_1.ap21"));
if (project != null)
{
try { /* ... 业务操作 ... */ }
finally { project.Close(); }
}
}
Dispose()行为:仅断开,不关闭 TIA Portal(因为有 GUI)。- 适用:交互式开发、需要人工确认对话框。
2.5.2 写法二:无头模式启动
using (TiaPortal tiaPortal = new TiaPortal(TiaPortalMode.WithoutUserInterface))
{
// 无 GUI;不能使用 HMI 编辑器,但仍可打开 Devices & Networks 与编程编辑器
}
Dispose()行为:关闭 TIA Portal(若无其他 Openness 客户端附着)。- CI/CD 场景必须用这个模式——TIA 弹模态对话框会让 Openness 调用无限挂起。
2.5.3 写法三:附加到运行实例(按枚举)
using (TiaPortal tiaPortal = TiaPortal.GetProcesses().First().Attach())
{
Project project = tiaPortal.Projects.FirstOrDefault();
foreach (Device device in project.Devices) { /* ... */ }
}
推荐先枚举再按项目名匹配(官方 snippets 的 BaseClass 做法,可避免附加到错误实例):
IList<TiaPortalProcess> procs = TiaPortal.GetProcesses();
if (procs.Count > 0)
{
foreach (var proc in procs)
{
TiaPortalInstance = proc.Attach(); // 复用已有 TIA 实例
foreach (var p in TiaPortalInstance.Projects)
if (p.Name != targetName) continue;
else { Project = p; goto Done; }
}
}
if (TiaPortalInstance == null)
TiaPortalInstance = new Siemens.Engineering.TiaPortal(TiaPortalMode.WithUserInterface);
Done: ;
2.5.4 写法四:附加到运行实例(按进程 ID)
Process process = Process.GetProcessesByName("Siemens.Automation.Portal")[0];
Thread.Sleep(TimeNeededToStartUp); // 进程可能尚未准备好响应服务请求
TiaPortalProcess tiaPortalProcess = TiaPortal.GetProcess(process.Id);
using (TiaPortal attached = tiaPortalProcess.Attach()) { /* ... */ }
官方注释:连接到运行实例时 TIA Portal 会弹 Openness firewall 连接提示,提供三个选项:Allow connection once / Do not allow / Always allow connections from this application。如果注册表提示被拒绝三次,系统抛
EngineeringSecurityException。
2.5.5 写法五:Add-in 内部运行
Add-in 由 TIA Portal 加载,运行在 TIA Portal 进程内。前提是必须已有打开的 TIA Portal GUI 和项目。
Add-in 专有服务(仅在 Add-in 内可用,Siemens.Engineering.AddIn 下):
var msgBox = tiaPortal.GetService<Siemens.Engineering.AddIn.MessageBoxProvider>();
msgBox?.ShowNotification(NotificationIcon.Information, "标题", "消息文本");
ConfirmationResult r = msgBox.ShowConfirmation(ConfirmationIcon.General,
"标题", "是否继续?", ConfirmationChoices.Yes | ConfirmationChoices.No,
ConfirmationResult.Yes); // 第三个参数 defaultChoice 为预选按钮
if (r == ConfirmationResult.No) return;
| 服务 | 方法 |
|---|---|
MessageBoxProvider | ShowNotification(...) / ShowConfirmation(...) |
ProgressProvider | Update(...) + IsCancelRequested |
FeedbackProvider | Log(...) 到 Inspector 窗口 General Info 页 |
⚠️ 已知坑:在 Add-in 的
OnUpdateStatus回调中GetService()返回 null——只能在 action 回调里用。
Add-in 部署(与外部 exe 完全不同):
| 形态 | 放置位置 | 加载者 |
|---|---|---|
| 官方机制 | …\Portal Vxx\AddIns\<YourAddIn>\(V21+ 改共享目录) | TIA Portal 加载你的 DLL |
| 用户 Add-in | C:\Users\<user>\AppData\Roaming\Siemens\Automation\Portal V21\UserAddIns\<YourAddIn>.addin | 同上 |
| 外部 exe | 任意自定义目录 | 你的进程自己 LoadFrom PublicAPI\Vxx\Siemens.Engineering.dll |
⚠️ 绝不要把 Openness DLL 拷到 Add-In 旁边——这是 “DLL missing” 陷阱的另一面。
来源:本文综合自 raw 目录下的《西门子官方手册》(5.2.8 Connecting、5.2.9 firewall)、《进阶专题》(进程模型与工程化、Addin 与 Openness 的分工)、《GitHub 开源》(官方 snippets
BaseClass.cs)、以及官方 Add-In 仓库的部署路径说明等抓取结果。
2.6 Python 路线(pythonnet)
2.6.1 环境准备
| 项目 | 说明 |
|---|---|
| 桥接库 | pythonnet,必须 pip install pythonnet(conda install pythonnet 无效) |
| 参考版本 | 被引用最多的入门示例把依赖钉死在 python=3.7.3 + pythonnet==2.4.0;实际工程可用更新的 Python 3.11 + pythonnet 3.x |
| 开发形态 | Jupyter Notebook(推荐 conda install jupyter nb_conda ipykernel),可单元格级重跑而不用整体重执行 |
| 兼容版本 | Python 集成教程明确列出 V14 / V15 / V16 / V17 / V18 / V19 |
| 类型提示 | pythonnet 只在运行时反射 .NET 成员,IDE 无静态提示 → 用 Repsay/siemens-tia-openness-python-stubs(3★,MIT)补齐 |
2.6.2 程序集引用(两条路径都有坑)
路径 A:clr.AddReference 直接指向 DLL(最经典写法)
import clr, sys
from System.IO import DirectoryInfo, FileInfo
### ⚠️ V18 及以后:Siemens.Engineering.Contract.dll 在 Bin\PublicAPI\ 下,
### 只 AddReference 主 DLL 会抛:
### FileNotFoundException: Could not load file or assembly
### 'Siemens.Engineering.Contract, Version=1800.100.4201.1, ...'
clr.AddReference(r'C:\Program Files\Siemens\Automation\Portal V18\Bin\PublicAPI\Siemens.Engineering.Contract.dll')
clr.AddReference(r'C:\Program Files\Siemens\Automation\Portal V18\PublicAPI\V18\Siemens.Engineering.dll')
import Siemens.Engineering as tia
import Siemens.Engineering.Compiler, Siemens.Engineering.HW.Features
路径 B:sys.path.append + clr.AddReference 程序集名(集成教程写法)
import clr, sys
tia_path = r"C:\Program Files\Siemens\Automation\TIA Portal V19\PublicAPI\V19"
sys.path.append(tia_path)
clr.AddReference("Siemens.Engineering")
clr.AddReference("Siemens.Engineering.HW")
clr.AddReference("Siemens.Engineering.HW.Features")
clr.AddReference("Siemens.Engineering.SW")
clr.AddReference("Siemens.Engineering.SW.Blocks")
from System.Collections.Generic import List
from Siemens.Engineering.SW.Blocks import GenerateOptions
⚠️ 路径写法陷阱:集成教程写的是带空格的
TIA Portal V19,而 V20+ 是Portal V20——照抄会 FileNotFound。
2.6.3 20 行最小 demo
import clr
clr.AddReference(r'C:\Program Files\Siemens\Automation\Portal V15_1\PublicAPI\V15.1\Siemens.Engineering.dll')
from System.IO import DirectoryInfo
import Siemens.Engineering as tia
mytia = tia.TiaPortal(tia.TiaPortalMode.WithUserInterface) # 带 UI 启动
### Devices.CreateWithItem 的第一个参数是 order number 字符串
myproject = mytia.Projects.Create(DirectoryInfo(r'C:\Jonas\TIA'), 'PythonTest')
station1 = myproject.Devices.CreateWithItem(
'OrderNumber:6ES7 515-2AM01-0AB0/V2.6', 'station1', 'station1')
2.6.4 Jupyter Notebook 的六单元格工作流
集成教程推荐的切分方式(避免每次重跑脚本都要重启 TIA):
| Cell | 内容 |
|---|---|
| 1 | imports / DLL 引用 |
| 2 | 初始化 TiaPortal(attach 或新建) |
| 3 | 创建 / 打开工程 |
| 4 | 硬件组态 |
| 5 | IP + 网络(必须先建网络再赋 IP) |
| 6 | 编译 + 导出 |
2.6.5 ⚠️【核心坑】System.Collections.Generic.List 泛型
这是 pythonnet 侧最经典的报错:
TypeError: No method matches given arguments for GenerateSource: (, , )
根因:Python 原生 list 不兼容 .NET 泛型集合,必须用 System.Collections.Generic.List[T]()。
### ❌ 错误写法
blocks = [block]
external_source.GenerateSource(blocks, FileInfo(target_filename), GenerateOptions.Without)
### ✅ 正确写法
from System.Collections.Generic import List
from Siemens.Engineering.SW.Blocks import GenerateOptions
blocks = List[object]()
blocks.Add(block)
external_source.GenerateSource(blocks, FileInfo(target_filename), GenerateOptions.Without)
C# 侧的等价规避:用官方 NuGet 扩展包的扩展方法
AllFBs()拿IEnumerable<PlcBlock>直接喂给GenerateSource,绕过 Python 侧的泛型坑。
2.6.6 附加到运行实例
### ⚠️ 教程给的 AttachToProcessByProcessId(0) 被 pythonnet 转换后并不精确
### ✅ 可靠做法:枚举 tia.Processes 后 attach(与官方 C# 的 GetProcesses() 语义一致)
tia = tia.TiaPortal.GetProcesses()[0].Attach()
2.6.7 纯 Python 封装库
Repsay/tia-openness-api-client(52★,MIT,本主题最活跃的 Python 封装):
import tia_portal.config as tia_config
from tia_portal import Client
tia_config.load() # 从注册表/配置解析 TIA 安装
tia_client = Client() # 负责 attach/start TIA Portal
project = tia_client.open_project(
path=r"C:\Users\user\Documents\Automation", name="MyProject")
project.compile() # ⚠️ 必须先编译,才能 export
plcs = project.get_plcs()
plc = plcs[0]
software = plc.get_software()
for block in software.get_all_blocks(recursive=True): # 递归取块要传 recursive=True
try:
print(block.name, block.export())
except Exception as e:
print(f"Failed to export {block.name}: {e}")
tia_client.close() # ⚠️ 必须显式关闭以释放 TIA 会话
核心类层次:Client → Project → Device / DeviceItem → PLCSoftware → PLCBlock,另有 GlobalLibraries / GlobalLibrary。
USAGE.md 列出的 5 条限制(最关键两条加粗):
- 不支持直接 import 完整硬件组态/项目
- HMI 只能创建设备,软件层(画面/变量表)操作很有限,库主要聚焦 PLC
- 导出 block 前必须先
project.compile(),否则块不一致会抛异常 - 递归取块要传
recursive=True - Windows 路径要用双反斜杠或
r"..."原始字符串
关键能力命名(从 fork 的 USAGE.md 摘要确认):devices.create_PLC(article_no, version, name, device_name)、devices.create_HMI(...)、blocks.create(path, name, labels)(用 __NAME__ 占位符替换 XML 模板)、blocks.create_instance_database(name, fb_name)、blocks.create_prodiag_block(name)(自动在 IDB 组里生成配套实例 DB)。
来源:本文综合自 raw 目录下的《GitHub 开源》(
Repsay/tia-openness-api-client、Maroder1/TIA-openness、Maroder1/Openness_examples_python、stubs 仓库、第三方踩坑笔记、GenerateSource泛型坑)、《英文技术博客》(Python Integration Tutorial、Project Bases 篇)、《进阶专题》等抓取结果。
2.7 第一个完整程序:逐行解读
2.7.1 完整程序
using System;
using System.IO;
using System.Linq;
using Siemens.Engineering;
using Siemens.Engineering.SW; // PlcSoftware / SoftwareContainer
using Siemens.Engineering.HW; // Device / DeviceItem
namespace FirstOpennessApp
{
internal class MyProgram
{
public static void Main(string[] args)
{
string projectPath = args.Length > 0 ? args[0]
: @"D:\TiaProjects\MyProject\MyProject.ap21";
// 第 1 步:启动 TIA Portal。using 保证第 8 步终止连接
using (TiaPortal tiaPortal = new TiaPortal(TiaPortalMode.WithUserInterface))
{
// 第 5 步:打开工程(OpenWithUpgrade 才能开旧版本工程)
Project project = tiaPortal.Projects.Open(new FileInfo(projectPath));
if (project == null) { Console.WriteLine("无法打开工程"); return; }
try { Work(project); } // 第 6 步:见 Work()
finally { project.Close(); } // 异常路径也要关工程
}
// Dispose 在此自动执行:GUI 模式仅断开,headless 模式关闭 TIA Portal
}
第 6 步的业务逻辑(拆成独立方法也是 JIT 规避技巧,见 1.4.3):
private static void Work(Project project)
{
Console.WriteLine($"工程:{project.Name}");
foreach (Device device in project.Devices)
{
Console.WriteLine($" 设备:{device.Name} ({device.TypeIdentifier})");
foreach (DeviceItem item in device.DeviceItems)
{
// GetService 返回 null 而非抛异常,必须判空
var container = item.GetService<SoftwareContainer>();
if (container?.Software is not PlcSoftware plc) continue;
Console.WriteLine($" PLC:{item.Name},块组={plc.BlockGroup.Name}," +
$"变量表数={plc.TagTableGroup.TagTables.Count()}");
// 强制离线:在线时改程序会抛 not supported in online mode
var online = item.GetService<OnlineProvider>();
if (online != null && online.State != OnlineState.Offline)
{ Console.WriteLine(" 设备在线,先切离线…"); online.GoOffline(); }
}
}
project.Save(); // 第 7 步:保存
Console.WriteLine("已保存。");
}
}
}
2.7.2 逐行要点解读
| 行 | 关键点 | 为什么这么写 |
|---|---|---|
using (TiaPortal …) | 所有 Openness 对象都实现 IDisposable,必须按创建反序释放 | 官方手册与社区一致强调;社区还总结”异常路径必须 project.Close(false)” |
TiaPortalMode.WithUserInterface | 枚举只有两个值 | 若 CI/流水线,改 WithoutUserInterface,否则模态对话框会让调用无限挂起 |
new FileInfo(projectPath) | Projects.Open 接受 FileInfo 而非 string | 官方签名;Python 侧转 FileInfo 同理 |
Projects.Open vs OpenWithUpgrade | 官方长期稳定性铁律:用 OpenWithUpgrade | Open 只能访问”当前版本创建或已升级到当前版本”的工程,访问旧版本抛异常 |
project == null 判断 | Open 可能返回 null | 官方示例 if (project != null) |
try / finally project.Close() | 异常路径也要关工程 | 否则文件被锁,重开报”项目已被占用” |
device.TypeIdentifier | 动态属性,只读 | 是 CreateWithItem 第一个参数的完整值 |
device.DeviceItems | 官方明确该 API 与网络视图/设备视图一致,且比 Items 少一层 SoftwareContainer | 见 3.1.3 节对比表 |
item.GetService<SoftwareContainer>() | 必须判空 | 服务不可用返回 null 而非抛异常 |
container.Software as PlcSoftware | Software 是抽象基类 | PlcSoftware 与 HmiSoftware 是两个分支 |
OnlineState.Offline 比较 | 用状态枚举判断,不是布尔属性 | 官方说明 State 是判断”是否已在线”的唯一受支持途径 |
project.Save() | Save 与 Dispose 是两件事:Save 落盘,Dispose 释放内存锁 | 官方 snippets 注释原文 |
2.7.3 编译并运行(顺序不能错)
### 1. Build 成独立 exe(Ctrl+Shift+B)——这是能跑通的关键一步
### 2. 从输出目录直接运行,不要只在 VS 调试器里跑
cd bin/Debug
./MyFirstOpennessApp.exe "D:\TiaProjects\MyProject\MyProject.ap21"
顺序的重要性:英文社区的 Security Error 篇明确指出 “必须 Build 成独立 exe,从 bin\Debug\ 或 bin\Release\ 直接运行,不能直接在 VS 调试器里跑。成功跑过一次 exe 后通常才能调试。” 这条经验与官方「编程 8 步」中的「发布并启动 TIA Portal」一致。
来源:本文综合自 raw 目录下的《西门子官方手册》(5.2.7 Programming steps、5.2.8、5.2.12 Terminating the connection、5.3.1 Opening a project)、《中文社区实战》(七天学会 PLC 打通 Openness、V18 踩坑根因速查表)、《英文技术博客》(Fix Security Error 篇、Project Bases 篇)等抓取结果。
2.8 工程化实践建议
2.8.1 官方建议的安全运行姿态
| 建议 | 说明 |
|---|---|
| 以管理员权限安装到程序目录 | 但以普通用户权限运行 Openness 应用 |
| 避免从用户目录动态加载程序集/DLL | 降低 DLL 劫持风险 |
| 专属 Windows 账号 | Windows Service 场景强烈建议;local system 账号不支持,会始终抛 EngineeringSecurityException |
2.8.2 白名单的 CI/CD 陷阱
白名单以 exe 的 FileHash + DateModified 双重校验,因此:
- 重新编译或改动 exe 会导致白名单失效,需重新授权;
- 官方给出不启动 TIA Portal 也能建白名单的办法:直接编辑注册表文件(键路径与三个值见 1.4.4);
- 官方还给出了计算文件哈希与最后修改时间的方法,使手工/脚本化注册白名单条目成为可行方案——适用于无人值守的自动化部署。
社区也有现成实现:DotNetSiemensPLCToolBoxLibrary 的 TiaOpennessWhitelist.EnsureWhitelistEntry(version) 会按文件扩展名 .ap13~.ap20 推断版本并写入 HKLM\SOFTWARE\Siemens\Automation\Openness\。另有开源工具的技巧是注册表加开发用键以避免每次都触发防火墙弹窗。
2.8.3 性能与可靠性实践清单
| 实践 | 说明 | 来源 |
|---|---|---|
批量建变量用 XML Import 而非逐个 Tags.Create() | 千条变量逐个创建可超 10 分钟 | 深度开发篇 |
批量操作完成后再统一 Save() | 减少事务次数 | 同上 |
| 属性读取结果缓存到本地 | 避免重复远程调用 | 同上 |
批量处理用 WithoutUserInterface | 避免 GUI 开销 | 同上 |
| 多项目用多进程而非多线程并行 | API 非线程安全 | 同上 |
| 工程文件不共享锁 | Openness 对项目文件持排他写锁;一个项目同时只允许一个 Openness 进程 | Project Bases 篇 |
.ap 当二进制大文件处理 | Git LFS / SVN / Perforce;diff 走 API 而非文本比较 | Project Bases 篇 |
脚本 + XML 模板放 Git,按需重新生成 .ap | 保持模板可 diff | 同上 |
| 只对显式 check-out 的工作副本跑脚本 | 避免误改 | Adding Hardware 篇 |
| 影响硬件的脚本升级为 CI smoke test | 每次提交验证 | 同上 |
| 锁文件排查 | 用 Process Explorer 查 S7OI.exe 句柄并强杀孤儿进程 | Project Bases 篇 |
| 首个 API 调用最慢 | 官方 5.2.24:第一次调用某个 Openness 功能可能比后续调用慢得多(如连续导出,第一次导出更慢) | 官方 5.2.24 |
| 导入文件支持多 root 对象 | 一个 XML 里建多个文本列表,而不是每个列表一个文件 | 官方 5.2.24 |
2.8.4 官方收益数据
西门子官方统计:首次执行 vs 重复执行对比,Openness 自动化把单次项目生成从 180 分钟降到 75 分钟,总耗时 160 分钟、其中 Openness 占 75 分钟(变化率 −80%)。
另一份社区量化:30+ 台机器人工作站每台放同一套 faceplate,手动配置 2~3 小时且改错两台返工半天;脚本跑一遍不到一分钟。
规模选型表(HMI faceplate 场景,可迁移到其他批量场景):
| 设备数量 | 方案 |
|---|---|
| < 5 | 手动 |
| 5 ~ 20 | 复制粘贴 |
| 20 ~ 100 | 用 Openness 脚本 |
| > 100 | 用脚本 + 画面模板 |
| 需频繁更新 | 用库版本管理 + 脚本 |
来源:本文综合自 raw 目录下的《西门子官方手册》(5.2.9、5.2.24、Readme)、《中文社区实战》(深度开发篇、faceplate 批量配置实战)、《英文技术博客》(Project Bases 篇、Adding Hardware 篇)、《进阶专题》等抓取结果。
2.9 AI 辅助开发工作流
2.9.1 用 LLM 生成 SCL 再导入(推荐起点)
这是 2026 年最实用的 AI 辅助形态——不需要 MCP、不需要写任何 C#,因为 LLM 直接产出 SCL 文本,而 SCL 是 Openness 唯一「官方可编程生成程序」的入口。
完整三段流程(手工版,可作为验证基线):
| 阶段 | 操作 |
|---|---|
| ① 导入 | 打开目标项目 → 项目树进入「程序块」→「外部源文件」→ 双击「添加新的外部文件」→ 选中 .scl → 打开;添加成功后文件显示在「外部源文件」下,图标为绿色 SCL 字样 |
| ② 生成 | 右键源文件 →「从源生成块」(Generate blocks from source);若存在同名块会提示是否覆盖 |
| ③ 验证 | 检查日志区显示「0 错误,0 警告」,程序块图标由灰转亮即就绪 |
关键前提:确保版本兼容、块保护已解除。
2.9.2 用外部编辑器 + AI 辅助
| 工具 | 配置 |
|---|---|
| VS Code | 装 Siemens SCL 插件(语法高亮);推荐 Trae AI(AI 辅助编程)直接编辑并支持语法检查 |
| Notepad++ | 直接打开 .scl,装自定义 SCL 语法高亮插件(需手动配置) |
导出模板的官方三步(拿到可编辑的 .scl 起点):
1. 在 TIA Portal 中创建 FC/FB 块并编写基础框架
2. 右键该块 →「从块生成源」→「仅所选块」→ 保存为 .scl(本质是文本格式)
3. 用外部编辑器编辑后:项目树「外部源文件」→ 右键「添加新的外部文件」
→ 选择编辑好的 .scl → 右键「从源生成块」(同名块会自动覆盖)
注意:VS Code / Notepad++ 方案只改文本,块类型/接口/依赖的匹配仍由博图编译器校验,源文件里的
BLOCK类型与AREA必须和调用关系一致。
2.9.3 LLM 提示词要素(提高一次通过率)
结合第 4 章的 SCL 语法要点,提示词中应明确要求:
| 要素 | 要求 |
|---|---|
| 文件头属性 | {Attribute 'pack_mode' := '0'}、TITLE、VERSION 必须在头部 |
| 优化访问 | { S7_Optimized_Access := 'TRUE' }(全局 DB 绝对寻址时用 'FALSE') |
# 前缀 | 所有变量引用、FOR 循环变量都要加 # |
| 块类型声明 | FUNCTION_BLOCK / FUNCTION / DATA_BLOCK / TYPE / ORGANIZATION_BLOCK 头部格式 |
| 属性与版本位置 | 必须紧跟块头之后 |
| 编码 | UTF-8(部分场景需 UTF-8 BOM) |
| 已知易错点 | 目标 CPU 型号与固件(S7-1200 FW 4.0 不支持 {S7_m_c := 'true'})、被引用 FB 必须已存在、类型/符号必须已存在 |
2.9.4 用 AI 编排 Openness 的架构建议
不要一上来把 Openness 塞进 Web 后端。 TIA Portal 是 Windows 工程软件,Openness 依赖本机安装路径、注册表、用户权限、首次授权弹窗。
推荐架构:AI 平台 / 后端只做任务编排(发队列),真正调用 Openness 的 Worker 运行在装有 TIA Portal 的 Windows 工程机上。
按工程闭环拆能力而非只做”能打开 TIA”的 Demo:
| 能力 | 作用 |
|---|---|
OpenProject | 打开工程 |
ImportBlocks | 导入块(SCL / XML / SimaticML) |
CompileSoftware | 编译软件 |
ExportDiagnostics | 导出诊断信息 |
SaveAndArchive | 保存并归档 |
形成「生成 → 导入 → 编译 → 反馈错误 → 再修复」链路。
核心观点:不要把”导入成功”等同于”工程正确”。 真正有价值的是导入后自动编译、提取错误、回传给 AI 修复。
五条避坑清单(社区整理):
- Openness 版本必须与 TIA Portal 主版本匹配
- V21 以后要按模块化程序集重新适配
- 运行用户必须加入
Siemens TIA Openness组(V21 加两个组) - 首次运行要处理 firewall 授权
- 不要把导入成功等同于工程正确
2.9.5 AI 工具的四条安全边界(可借鉴的设计)
TIA-Portal-CLI 这类工具的设计值得在自己的 Openness 工具里照抄:
| 边界 | 做法 |
|---|---|
| ① 默认预运行 | 变更类操作正常执行只返回”计划更改的内容”,只有加 --apply 才真正落盘 |
| ② 不操作在线 PLC | 联机、下载到 PLC、多用户签入明确不在范围 |
| ③ 本地 / 本地部署 | 没有把项目数据发云服务的配置,在 Windows 上与 TIA Portal 同交互会话运行 |
| ④ 变更后串联编译、审计、交叉引用 | 重新执行时把”已是目标状态”视为 no-op,接近声明式协调 |
建议的 30–120 分钟首次实验顺序:
- 先读
AGENTS.md/SECURITY.md/ 基准测试文档,确认允许与禁止操作 - 隔离 PC 上只跑只读检查命令
- 在一次性非生产项目上跑
tree/snapshot - 事先书面固定”变更规范/验收标准/禁止操作”,并只把这个范围给 AI
- 人工审预运行结果,仅在测试项目上
--apply - 重跑同一操作确认变为 no-op 且 diff/audit 可追溯
成功标准不是代码量,而是 5 条:① 不会目标混淆;② 预运行与应用清晰分离;③ 人工能理解 diff;④ 编译/审计可机器判定;⑤ 禁止的在线路径技术上不可达。
来源:本文综合自 raw 目录下的《中文社区实战》(西门子中国论坛《如何导入 AI 工具写的 SCL 文件》、西门子中国「找答案」《SCL外部编程工具应用》、腾讯云社区转载《Openness API 接入全流程》、《TIA_Portal_Openness 深度开发自动生成 PLC 程序》)、《英文技术博客》(TIA-Portal-CLI、Top 7 MCP Server、TiaLink)等抓取结果。
2.10 新手常见误区
误区 1:加完用户组就万事大吉
错。 必须注销或重启才生效——社区原文:“添加后必须重启电脑,否则后续程序报「权限不足」”、“重要:注销或重启使更改生效,跳过此步运行时出现’权限被拒绝’错误”。
验证:重启后跑 whoami /groups | findstr "Siemens",有输出才继续。
误区 2:V21 只加 Siemens TIA Openness 就够了
错。 V21 需要两个组。而且缺组时报的是同一个泛化的 FileLoadException 而非 access denied,极具迷惑性——很多人在这里反复调程序集绑定却毫无进展。
误区 3:把 Siemens 的 DLL 复制到输出目录
错。 官方明确 Copy Local: True 不受支持,必须设为 False;不要把 Siemens DLL 复制到输出目录长期分发。运行时通过 app.config 或 AssemblyResolve 解析安装目录中的程序集。
连带误区:把 Openness DLL 拷到 Add-In 旁边——Add-In 由 TIA Portal 加载你的 DLL,绝不要把 Openness DLL 拷过去。
误区 4:把 Siemens 程序集注册进 GAC
错。 原文强调 “永远不要 GAC 注册 Siemens.Engineering 程序集”。有一份 V15.1 博客写”运行时从 GAC 加载”,这与官方”禁止 GAC 注册”的建议直接冲突,实测应从 PublicAPI 目录解析。
误区 5:只用 VS 调试器跑,不生成 exe
错。 必须 Build 成独立 exe,从 bin\Debug\ 或 bin\Release\ 直接运行。社区经验:“成功跑过一次 exe 后通常才能调试”。
误区 6:选错目标框架(.NET 6/8)
错。 VS 2022 默认支持 .NET 6/8,但 Openness 程序集是 .NET Framework 写的,选错目标框架 DLL 引用会直接报错。官方口径是 .NET Framework 4.8;V21 只支持 4.8。
框架过低的症状不是编译错误,而是运行时抛 TypeLoadException / FileNotFoundException。
误区 7:混用不同版本的 TIA 与 Openness
错。 版本规则不可混用:V15/V15.1 → Openness V15 → .NET 4.7.2 以上;V16 → V16 → .NET 4.8;V17 → V17 → .NET 4.8;V18/V19/V20 → 对应版本 → .NET 4.8。V15~V18 工程文件格式不互通,用 V17 Openness 开 V18 项目大概率报错。
还有更硬的一条:一个应用实例只能加载一个 TIA 版本,试图加载第二版本会抛 “You cannot open a project in V if you already have a project open in another version. You need to restart the Application!”
误区 8:项目在 TIA Portal 里开着时跑脚本
错。 会报「项目已被占用」——Openness 对项目文件持排他写锁。且不要在设计器正编辑同一项目时跑脚本。
误区 9:博图在线时改程序
错。 会抛 This function is not supported in online mode。用 OnlineProvider.State 判断 + GoOffline()。
误区 10:以为装了组件就有权限
错。 官方原文:“You still do not have access to the TIA Portal with the ‘TIA Portal Openness’ add-on package. You need to be a member of the ‘Siemens TIA Openness’ user group.”
误区 11:以为 Openness 默认是勾选的
不可靠。 官方手册要求主动勾选,HwLib 说 V14 起默认勾选,实战篇说默认不勾选需手动展开。一律主动确认。
误区 12:以为 Openness 是 PLC 通信库
错。 Openness 操作的是博途软件与离线工程文件;读写 PLC 实时变量/DB 数据/在线监控不属于 Openness,推荐 Sharp7、S7net-plus、OPC UA。下载/在线连接用 Openness 内置的 DownloadProvider / OnlineProvider。
误区 13:以为可以逐行编辑块内程序逻辑
错。 Openness 不能直接逐行编辑块内程序逻辑(LAD/FBD 网络、STL 语句)。这是架构性硬边界,详见第 4 章。
误区 14:直接用 Projects.Open 打开旧版本工程
错。 Projects.Open 只能访问”当前版本创建或已升级到当前版本”的工程,访问旧版本会抛异常。官方铁律:用 OpenWithUpgrade。
误区 15:以为 Openness 无需任何安全意识
错。 官方 Readme 明确:管理员权限安装、普通用户权限运行、避免从用户目录动态加载程序集。同时官方禁止用 Openness 执行安全系统检查或生成验收/认可数据。
误区 16:把 ApplyToBlock 类伪代码当真
错。 社区在讲全局 Find/Replace 时给出的 Openness 替换代码,原文已标注为 pseudocode(伪代码)——foreach (FCBlock block in project.FCBlocks) { foreach (Instruction inst in block.Instructions) … } 这套 API 在 Openness 中不存在。
误区 17(补充):以为 AI 生成的 SCL 直接可用
错。 必须人工/脚本校核语法(属性行位置、# 前缀、S7_Optimized_Access 语义、目标 CPU 固件特性),并在 TIA 中执行「从源生成块」查看「0 错误,0 警告」才算通过。导入成功 ≠ 工程正确。
来源:本文综合自 raw 目录下的《中文社区实战》(Win10 用户组配置篇、TIA Openness开发入门(1)、V18 机器视觉篇、V17 实战篇、TIA_Portal_Openness 无法运行帖、SCL 外部编程工具应用、AI 生成 SCL 导入帖、深度开发篇、faceplate 篇)、《英文技术博客》(Fix Security Error、V19→V21 迁移、Installing V15.1、Missing V20 DLL、Global Find and Replace、Third-Party Hardware)、《西门子官方手册》(4.1.1/4.1.3/5.2.7/5.3.1)、《进阶专题》等抓取结果。
本章速查
- 环境底线:Windows 64 位 + TIA Portal 64 位 + VS 2017+ / .NET Framework 4.8 + Windows Classic Desktop 包 + 平台目标 x64(否则
BadImageFormatException)。V21 只支持 .NET Framework 4.8。 - 最高频故障点是用户组,不是编程。安装组件只自动创建组、不自动加成员;加组后必须注销或重启;验证命令
whoami /groups | findstr "Siemens"。V21 需同时加TIA Engineer+TIA Openness两个组,且缺组时报的是泛化的FileLoadException而非 access denied。 - 加组不是充分条件。官方最佳答案的排查顺序:管理员运行 → 检查安装完整性 → 确认版本兼容 → 检查 .NET Framework 与环境变量 → 查日志(安装目录
Logs文件夹)。第二层权限是 TIA Portal 的「选项 → 设置 → 常规 → 专家设置 → 通过外部程序启用 TIA Portal 的访问」。 - Openness 本体免费、不需独立 license key(整机 TIA 授权仍照常校验),版本号必须与 WinCC/STEP 7 一致。Update 安装器不提供启用 Openness 的复选框,必须用基版本介质启用后再打 update。
- 引用三件套:
Siemens.Engineering.dll(核心)+Siemens.Engineering.Hmi.dll(HMI)+Siemens.Engineering.Simatic.dll(生成程序块要用),V16/17 常再加Siemens.Engineering.HW.dll。所有引用Copy Local = False。 - 必须生成独立 exe 从
bin\Debug\运行,不能只在 VS 调试器里跑;成功跑过一次 exe 后通常才能调试。 GetService<T>()必须判空(不可用返回null);OnlineProvider.State是判断在线状态的唯一受支持途径;WithoutUserInterface模式下Dispose()会关闭 TIA Portal,GUI 模式下仅断开。- Python 路线两个必踩坑:① V18+ 必须额外引用
Portal Vxx\Bin\PublicAPI\Siemens.Engineering.Contract.dll,否则FileNotFoundException;②GenerateSource报TypeError: No method matches given arguments for GenerateSource: (, , )是因为 Python 原生list不兼容 .NET 泛型,必须from System.Collections.Generic import List后用List[object]()。 - 用
Projects.OpenWithUpgrade而非Projects.Open;Save()与Dispose()是两件事——Save 落盘,Dispose 释放内存锁;API 非线程安全,多项目用多进程而非多线程。 - 工程化四条性能铁律:批量建变量用 XML
Import而非逐个Create(千条逐个创建可超 10 分钟);批量操作完再统一Save();属性读取结果缓存;批量处理用WithoutUserInterface。官方收益数据:单次项目生成从 180 分钟降到 75 分钟(−80%)。
第3章 硬件配置、设备与网络
第 3 章 硬件配置、设备与网络
本章导读
本章解决”如何用脚本把一台 S7-1500 从零组态出来”这个具体问题。内容按对象模型 → 订货号识别 → 完整组态示例 → 服务定位 → 网络与 IP → 第三方设备 → 进阶特性的顺序展开。核心技术点是三件事:Project.Devices / Device / DeviceItem 的层级关系、MLFB 订货号与 TypeIdentifier 的获取与格式规则、以及 GetService<T>() 服务定位模式。本章特别写透了两个高频踩坑点:IP 地址必须在建好网络之后才能设置,以及第三方设备为何无法用 CreateWithItem 从零创建而必须走”模板导出法”。
3.1 对象模型与层级
3.1.1 硬件对象层级总览
Project(工程)
├── Devices ─────────────────── DeviceComposition(项目根级设备)
│ └── Device(设备,如"PLC_1")
│ ├── DeviceItems ────── DeviceItemComposition(S7-1500 的机架/接口等)
│ │ └── DeviceItem ─── HardwareObject 的实现
│ │ └── Items ──── DeviceItemComposition(模块/子模块)
│ │ └── DeviceItem …(更深层 SubModule)
│ └── UnpluggedItems ── 未插入容器的 DeviceItem
├── DeviceGroups ────────────── DeviceUserGroup(设备文件夹,可嵌套)
│ └── Devices / Groups
├── UngroupedDevicesGroup ───── DeviceSystemGroup
├── Subnets ─────────────────── SubnetComposition(全局子网)
└── HwUtilities ─────────────── HardwareUtilityComposition(Find 取工具服务)
3.1.2 官方对硬件对象的定义
| 硬件对象 | 官方定义 |
|---|---|
Device(Device) | 中央配置或分布式配置的容器对象。Device 扩展 HardwareObject,并额外拥有 UnpluggedItems 链接 |
DeviceItem(DeviceItem) | 每个 DeviceItem 对象都有一个容器对象,逻辑关系是 Items。DeviceItem 扩展 HardwareObject 并拥有 Container 链接;反向关系是 Items |
| 未插入的 DeviceItem | 没有容器的 DeviceItem 通过 Device 的 UnpluggedItems 链接找到 |
容器关系等价于模块关系:设备包含一个或多个机架(Rack),机架包含模块(Module),模块包含子模块(Submodule)。这与 TIA Portal 网络视图和设备视图中的表示一致。
两条硬约束:
PositionNumber属性在一个容器的 items 区域内唯一;- 父子关系是纯逻辑关系——子对象不能脱离父对象存在:若子模块被建模为模块的一部分(child),则模块被移除时子模块也随之消失;而可增删的模块或子模块是设备对象的孩子。
3.1.3 .Items 与 .DeviceItems 的区别(高频踩坑点)
官方对象模型图明确给出了两种迭代方式的差异(PLC 设备):
| 迭代方式 | 层级 | 是否含 SoftwareContainer |
|---|---|---|
.Items | Device → DeviceItem[Backplane] → DeviceItem[PN/IE] → DeviceItem[DI] → DeviceItem[2 AI] → … | 含 SoftwareContainer 层(因为它是插拔路径上的一个 DeviceItem) |
.DeviceItems | Device → DeviceItem[Backplane] → DeviceItem[PN/IE] → DeviceItem[DI] → DeviceItem[2 AI] → … | 同样含,但 .DeviceItems 在每个层级只有一层,不递归 |
实际差异总结:
| 特性 | .Items | .DeviceItems |
|---|---|---|
| 是否递归 | 递归(一路向下) | 单层 |
| 语义 | ”插拔”关系,对应 TIA Portal 中的插拔层级 | 对应设备视图的同级列表 |
| 取 PlcSoftware | 更易(SoftwareContainer 在路径上) | 同样可取 |
| 官方推荐场景 | 官方设备项遍历示例用 HardwareObject.Items | 官方对象模型图专门对比了两者 |
官方设备项遍历示例(5.10.7):
private static void EnumerateDeviceItems(HardwareObject hardwareObject)
{
foreach (DeviceItem deviceItem in hardwareObject.Items) { /* add code here */ }
}
一条实用的选型建议:官方代码片段仓库的
ProgramBlocksSnippets.cs用DeviceItems+Classification == DeviceItemClassifications.CPU定位 PLC:var plcItem = Project.Devices.First(x => x.Name == "...") .DeviceItems.First(x => x.Classification == DeviceItemClassifications.CPU); var plc = plcItem.GetService<SoftwareContainer>().Software as PlcSoftware;而
TagTableSnippets.cs用的是索引写法DeviceItems[1]——这是该片段的一个瑕疵,索引随设备组态变化会失效,不要照抄。一律用Classification == CPU或TypeIdentifier判定,不要用索引。
3.1.4 设备与设备项的强制属性
Device:
| 名称 | 类型 | 可写 | 访问 | 说明 |
|---|---|---|---|---|
Author | string | 读/写 | 动态 | — |
Comment | string | 读/写 | 动态 | — |
GsdName | string | 读 | 动态 | GSD / GSDML 文件名 |
GsdType | string | 读 | 动态 | 硬件对象类型,设备恒为 "D" |
GsdId | string | 读 | 动态 | 硬件对象专用标识,设备恒为空 |
IsGsd | bool | 读 | — | GSD / GSDML 设备为 TRUE |
Name | string | 读/写 | — | — |
TypeIdentifier | string | 读 | — | 见 3.2 |
DeviceItem 的强制属性还包括:PositionNumber、IsBuiltIn、IsPlugged,以及可读的 TypeName、Author、Comment、OrderNumber、FirmwareVersion、PlantDesignation、LocationIdentifier。
// 官方惯用法:需要动态属性时显式取名
DeviceItem deviceItem = ...;
var attributeNames = new[] {
"TypeName", "Author", "Comment", "OrderNumber", "FirmwareVersion", "PlantDesignation",
"LocationIdentifier"
};
foreach (var attributeName in attributeNames) {
object attributeValue = ((IEngineeringObject)deviceItem).GetAttribute(attributeName);
}
DeviceItem deviceItem = ...;
((IEngineeringObject)deviceItem).SetAttribute("Comment", "This is a comment.");
V17 变更:自 V17 起
IEngineeringObject的所有方法在所有实现该接口的类型上隐式实现,不再需要显式 cast。受影响的四个方法:object GetAttribute(string name)、IList<EngineeringAttributeInfo> GetAttributeInfos()、IList<object> GetAttributes(IEnumerable<string> names)、void SetAttribute(string name, object value)、void SetAttributes(IEnumerable<KeyValuePair<string, object>> attributes)。但这个变更也会带来 Extension method 的行为变化:如果你曾用扩展方法并把代码编进 V17 的 engineering dll,扩展方法里的调用将不再被执行,且不会有编译错误——迁移时必须改代码。
来源:本文综合自 raw 目录下的《西门子官方手册》(5.1.3 Hierarchy of hardware objects、7.15 Functions on devices、7.16 Functions on device items)、《中文社区实战》(深度开发篇、机器视觉篇)、《GitHub 开源》(官方 snippets ProgramBlocksSnippets.cs / TagTableSnippets.cs 备注)等抓取结果。
3.2 MLFB 订货号与 TypeIdentifier
3.2.1 什么是 TypeIdentifier
TypeIdentifier 属性用于标识可通过 TIA Portal Openness API 创建的硬件对象。它是一个由多段组成的字符串:
<TypeIdentifierType>:<Identifier>
三种 TypeIdentifierType:
| 值 | 用途 | 格式 | 示例 |
|---|---|---|---|
| OrderNumber | 硬件目录中所有模块的通用标识(最常用) | <OrderNumber><OrderNumber>/<FirmwareVersion><OrderNumber>//<AdditionalTypeIdentifier> | OrderNumber:3RK1 200-0CE00-0AA2OrderNumber:6ES7 510-1DJ01-0AB0/V2.0OrderNumber:6AV2 124-2DC01-0AX0//Landscape |
| GSD | 通过 GSD / GSDML 添加的模块 | <GsdName>/<GsdType><GsdName>/<GsdType>/<GsdId> | GSD:SIEM8139.GSD/DAPGSD:SIEM8139.GSD/M/4 |
| System | 无法用 OrderNumber 或 GSD 确定的对象 | <SystemTypeIdentifier><SystemTypeIdentifier>/<AdditionalTypeIdentifier> | System:Device.S7300 |
GsdType 的取值:D(Device)、R(Rack)、DAP(HeadModule)、M(Module)、SM(Submodule)。GsdName 是 GSD / GSDML 文件名,必须大写字母。
System 类标识的前缀(各对象类型):Connection.、Subnet.、Device.、Rack.。例如子网的标识是 System:Subnet.Ethernet。
关键规则:
- FirmwareVersion 在不存在或系统里只有一个版本时是可选的;
AdditionalTypeIdentifier仅在 OrderNumber + FirmwareVersion 无法唯一定位时才需要;- 硬件目录中有少量模块使用”通配符”字符(如 S7-300 不同长度的机架),此时具体 OrderNumber 和通配 OrderNumber 都可以创建实例,但不能在任意位置使用通配符。
3.2.2 在 TIA Portal UI 中获取 TypeIdentifier(官方 3 步)
1. 启用设置:Options → Settings → Hardware configuration
→ "Enable display of the type identifier for devices and modules"
(中文界面:选项 → 设置 → 硬件组态 → 显示类型标识符)
2. 打开编辑器:"Devices & networks"
3. 在 Catalog 中选择一个设备
→ Type identifier 显示在 "Information" 视图窗格中
Startdrive 场景下的额外说明:Startdrive 中默认隐藏该显示,同样需要勾选 “Activate display of the Type Identifier for devices and modules”,才能在 Inspector 窗口 / 设备选择对话框中读到并复制。
3.2.3 用 API 枚举硬件目录(推荐做法,可直接复制粘贴)
这是最可靠的做法——不手写字符串,直接从目录里抄:
// "" 表示从根节点查找全部条目
var hardwareCatalog = TiaPortalInstance.HardwareCatalog.Find(string.Empty);
var catalogEntries = hardwareCatalog.ToList();
Console.WriteLine($"Total catalog entries: {catalogEntries.Count}");
foreach (var entry in catalogEntries)
Console.WriteLine($"TypeIdentifier: {entry.TypeIdentifier}, CatalogPath: {entry.CatalogPath}");
// TypeIdentifier 可直接喂给 Devices.CreateWithItem(...)
用 ModuleInformationProvider 精确查可用版本与容器类型:
// 取模块信息提供器(注意:从 Project 的 HwUtilities 取)
HardwareUtilityComposition extensions = project.HwUtilities;
var moduleInformationProvider = extensions.Find("ModuleInformationProvider") as ModuleInformationProvider;
// ① 查某模块能用哪些容器(Device / Rack 等)——⚠️ 仅对网络视图中可见的模块有效,且仅模块、不含子模块
string[] containerTypes = moduleInformationProvider.FindContainerTypes("OrderNumber:6ES7 510-1DJ01-0AB0/V2.0");
// ② 用部分 TypeIdentifier 查所有可用版本的完整 TypeIdentifier
// 最小段数要求:OrderNumber 至少 1 段;GSD 至少 2 段;System 至少 1 段
// 通配符或不完整段不支持
string[] moduleTypes = moduleInformationProvider.FindModuleTypes("OrderNumber:6ES7 317-2EK14-0AB0");
// ③ 查空闲插槽(PlugLocation 只有 free slot)
IList<PlugLocation> result = hardwareObject.GetPlugLocations();
foreach (PlugLocation item in result) {
int positionNumber = item.PositionNumber; // 空闲槽位号
string label = item.Label; // 无 label 时返回 positionNumber 的字符串
}
3.2.4 手工配置一次导出模板法(第三方设备的唯一出路)
当无法从目录枚举到目标设备时,“手工配置一次 + 导出模板 + 脚本导入”是官方解法:
| 步骤 | 操作 |
|---|---|
| 1 | 在 TIA Portal UI 中手工配置一次:装好 GSDML、配好设备名 / IP / 子网 / 模块 / I/O 地址 |
| 2 | 对参考项目跑 CAx export,拿到 schema-valid、版本正确的 XML |
| 3 | 再改一个参数后 export,diff 两份 XML 反推哪个元素/属性控制哪个设置(比通读 schema 快得多) |
| 4 | 把已知 good 的 export 改成模板并参数化 |
| 5 | 导入目标项目 |
| 6 | 记录每次导入结果——CAx import 会报”部分成功”,不抛异常 ≠ 导入成功 |
⚠️ 模板法的硬限制:模板 XML 版本特定,升级后需重新生成;设备名必须唯一;无法通过 Openness 设置第三方安全设备的安全参数。
3.2.5 ⚠️ 订货号格式:空格是硬要求
MLFB 订货号格式必须与硬件目录完全一致,包括空格位置。
| 正确 | 错误 | 后果 |
|---|---|---|
"6ES7 214-1AG40-0XB0"(CPU 1214C DC/DC/DC) | "6ES72141AG400XB0" | 抛 EngineeringException 提示设备未找到 |
两条硬规则:
- 直接从 TIA 硬件目录复制粘贴,不要手打;
- 社区代码里甚至有
Regex.IsMatch(orderNumber, @"\s")这样的空格存在性校验——订货号中不含空格几乎必然是错的。
CreateWithOrder 的适用场景与 ⚠️ 标注:社区与 MCP 工具链资料提到另一种创建方式:
// ⚠️【该来源 API 签名存疑,需对照官方手册核实】
// 官方 V19 手册 7.15.3 只记载了 CreateWithItem(DeviceItemTypeId, DeviceItemName, DeviceName)
// 与 Create(DeviceTypeId, DeviceName) 两种方式,未见 CreateWithOrder。
var cpu = project.Devices.CreateWithOrder(
new DeviceComposition("Head"),
new DirectoryInfo(@"C:\Program Files\Siemens\Automation\Portal V21\HWLibrary"),
"6ES7 515-2AM02-0AB0", "PLC_1", null);
⚠️ 上面的
CreateWithOrder+DeviceComposition+HWLibrary路径组合仅见于英文博客与 MCP 项目资料,官方 V19 系统手册(Entry 109826886)7.15.3 未记载此方法。落地前必须用 ILSpy 反编译Siemens.Engineering.dll或查 V21 官方文档确认。如果 CreateWithOrder 不可用,CreateWithItem是官方确认的等价路径。
来源:本文综合自 raw 目录下的《西门子官方手册》(7.15.2 Get type identifier、7.16.2/7.16.3 模块信息)、《GitHub 开源》(官方 snippets HardwareCatalogSnippets.cs、Startdrive 手册 TIA 侧说明)、《英文技术博客》(Project Bases 篇、MLFB 空格篇、Adding Third-Party Hardware 篇、Installing GSDML 篇)、《中文社区实战》(深度开发篇)、《进阶专题》等抓取结果。
3.3 创建设备的三种方式
3.3.1 方式一:CreateWithItem(官方主推)
DeviceComposition devices = ...;
// 带设备项:一次创建"站 + 头模块"(如 PLC 站)
Device device = devices.CreateWithItem("OrderNumber:6ES7 510-1DJ01-0AB0/V2.0", "PLC_1", "NewDevice");
// GSD 设备
Device gsdDevice = devices.CreateWithItem("GSD:SIEM8139.GSD/M/4", "GSD Module", "NewGsdDevice");
参数:
| 名称 | 类型 | 说明 |
|---|---|---|
DeviceItemTypeId | string | 设备项的类型标识(完整 TypeIdentifier) |
DeviceItemName | string | 创建的设备项的名称 |
DeviceName | string | 创建的设备的名称 |
注意参数名的”错位”:方法签名是
CreateWithItem(DeviceItemTypeId, DeviceItemName, DeviceName),即第二个参数是设备项名、第三个是设备名。Startdrive 手册中CreateWithItem(@"OrderNumber:<mlfb>/<FirmwareVersion>/", "Device_0", null)的写法就是把设备名传null(用默认值)。
3.3.2 方式二:Create(只创建设备,不带设备项)
Device deviceOnly = devices.Create("System:Device.S7300", "S7300Device");
Device gsdDeviceOnly = devices.Create("GSD:SIEM8139.GSD/D", "GSD Device");
适用场景:只需要一个空站壳,随后自己往里 PlugNew 设备项。
3.3.3 方式三:插入槽位模块 —— PlugNew / CanPlugNew
HardwareObject hwObject = ...;
string typeIdentifier = ...;
string name = ...;
int positionNumber = ...;
if (hwObject.CanPlugNew(typeIdentifier, name, positionNumber))
{
DeviceItem newPluggedDeviceItem = hwObject.PlugNew(typeIdentifier, name, positionNumber);
}
| 参数 | 类型 | 说明 |
|---|---|---|
typeIdentifier | string | 创建的设备项的类型标识 |
name | string | 创建的设备项的名称 |
positionNumber | int | 设备项的位置号(槽位号) |
PlugNew 的双重用途:
- 创建新的设备项并插入到已存在的硬件对象中;
- 创建新的子设备项(如子模块)并插入到设备项中。
成功返回创建的 DeviceItem,失败抛可恢复异常。
CanPlugNew 返回 true 仍可能失败的 6 个 unforeseen reasons(官方原文列举):
- 位置号已被另一个设备项占用
- 当前设备项虽空闲但不能在该位置插入
- 容器不提供该位置号
- 设备项名称在同一容器中已被占用
- 设备项不能插入该容器
- 设备在线
positionNumber = 65535 的特殊值:Startdrive 手册中 Device.PlugNew(@"OrderNumber:6SL3xxx-xxxxx-xxxx", "MotorModul", 65535) 用 65535 表示自动 / 追加位置。
⚠️ 签名差异提醒:英文博客中出现
ioController.PlugNew(cpuDi, positionName: null)与cpuDi.CreateAndPlugNewSubmodule("6ES7 521-1BH00-0AB0", 1, "DI32")的写法。⚠️ 这两个签名均未出现在官方手册中——官方 7.16.2 只有PlugNew(string typeIdentifier, string name, int positionNumber)与CanPlugNew(...)两个方法。请以官方签名为准。
3.3.4 设备枚举的四种方式
| 场景 | 官方做法 |
|---|---|
| 项目根级全部设备 | foreach (Device device in project.Devices) |
| 按名取单个 | project.Devices.Find("MyDevice") 或 project.Devices.First(d => d.Name == "Mydevice") |
| 设备文件夹(组)内 | 递归遍历 project.DeviceGroups → DeviceUserGroup.Devices / .Groups |
| 未分组设备 | DeviceSystemGroup group = project.UngroupedDevicesGroup; group.Devices.First(...) |
官方递归枚举设备组的完整示例:
private static void EnumerateDevicesInGroups(Project project)
{
foreach (DeviceUserGroup deviceUserGroup in project.DeviceGroups)
{
EnumerateDeviceUserGroup(deviceUserGroup);
}
}
private static void EnumerateDeviceUserGroup(DeviceUserGroup deviceUserGroup)
{
EnumerateDeviceObjects(deviceUserGroup.Devices);
foreach (deviceUserGroup subDeviceUserGroup in deviceUserGroup.Groups)
{
EnumerateDeviceUserGroup(subDeviceUserGroup); // 递归
}
}
官方语义说明:“Openness API positions devices similar to the project navigation in TIA Portal PNV.”——作为项目直接子代的设备聚合在项目的
Devices组合里;在设备文件夹中的设备聚合在文件夹的Devices组合里(文件夹的组合单独存在,与项目级并列)。可枚举的设备包括:中央站、PB 从站 / PN-IO 设备、HMI 设备。
来源:本文综合自 raw 目录下的《西门子官方手册》(7.15.3/7.15.4/7.16.2)、《中文社区实战》与《英文技术博客》社区实例代码等抓取结果。
3.4 典型 S7-1500 机架组态示例
3.4.1 槽位示意
S7-1500 中心机架(Central Rack) —— CPU 1515-2 PN,槽位 1 起:
╔══════════════════════════════════════════════════════════════════════════╗
║ S7-1500 中央机架(Central Rack / 集成 PROFINET 接口) ║
╠════╤════════════════════╤═══════╤═════════════════════════════════════════╣
║ 槽 ║ 订货号 (MLFB) ║ 固件 ║ 模块名称 / 规格 ║
╠════╪════════════════════╪═══════╪═════════════════════════════════════════╣
║ 1 ║ 6ES7 515-2AM02-0AB0║ V2.9 ║ CPU 1515-2 PN ← 头模块(Head) ║
║ ║ ║ ║ · 集成 PROFINET 接口 PN-IE_1 ║
║ ║ ║ ║ · 内置 2 个 PROFINET 接口 ║
╠════╪════════════════════╪═══════╪═════════════════════════════════════════╣
║ 2 ║ 6ES7 521-1BL00-0AB0║ — ║ DI 32x24VDC HF ← 数字量输入 32 点 ║
║ ║ ║ ║ · 24 VDC 数字量输入 ║
║ ║ ║ ║ · 带 HF(高速通道)功能 ║
║ ║ ║ ║ · 地址范围 I0.0 … I3.7 ║
╠════╪════════════════════╪═══════╪═════════════════════════════════════════╣
║ 3 ║ 6ES7 522-1BL01-0AB0║ — ║ DQ 32x24VDC/0.5A ST ← 数字量输出 32 点 ║
║ ║ ║ ║ · 24 VDC / 0.5 A 数字量输出 ║
║ ║ ║ ║ · ST(负载相关)功能 ║
║ ║ ║ ║ · 地址范围 Q0.0 … Q3.7 ║
╠════╪════════════════════╪═══════╪═════════════════════════════════════════╣
║ 4 ║ 6ES7 531-1EC02-0CA0║ — ║ AI 4xU/I 2x4..20mA ST ← 模拟量输入 ║
║ ║ ║ ║ · 4 路电压/电流输入 ║
║ ║ ║ ║ · 2 路 4..20 mA 电流 ║
║ ║ ║ ║ · 地址范围 IW96 … IW103 ║
╠════╪════════════════════╪═══════╪═════════════════════════════════════════╣
║ 5 ║ 6ES7 532-1AH00-0AB0║ — ║ AQ 4xU/I 2x0..10V ST ← 模拟量输出 ║
║ ║ ║ ║ · 4 路电压/电流输出 ║
║ ║ ║ ║ · 2 路 0..10 V 电压 ║
║ ║ ║ ║ · 地址范围 QW96 … QW103 ║
╠════╩════════════════════╩═══════╩═════════════════════════════════════════╣
║ ↑ 槽位 1 = CPU(Head module),不可空 ║
║ ↑ 槽位号(PositionNumber)在同一容器内唯一 ║
║ ↑ 编程前先用 GetPlugLocations() 查空闲槽位 ║
╚══════════════════════════════════════════════════════════════════════════╝
⚠️ 订货号准确性声明:上表中 CPU 1515-2 PN(
6ES7 515-2AM02-0AB0)、DI 32x24VDC HF(6ES7 521-1BL00-0AB0) 来自 raw 原始资料中确证的工程示例。DQ 32x24VDC/0.5A ST(6ES7 522-1BL01-0CA0)、AI 4xU/I 2x4..20mA ST(6ES7 531-1EC02-0CA0)、AQ 4xU/I 2x0..10V ST(6ES7 532-1AH00-0AB0)的具体后缀在 raw 资料中未确证,落地前必须在 TIA 硬件目录中核对并复制粘贴。 官方手册附录中包含硬件参数(hardware parameters)的完整描述,那是查 MLFB / 硬件属性名的权威出处(Entry 109773802)。
3.4.2 常用订货号对照表(来自 raw 确证案例)
| 设备 | MLFB / TypeIdentifier | 出处 |
|---|---|---|
| CPU 1512C-1 PN | 6ES7 512-1CK00-0AB0 | Python 教程 devices.CreateWithItem |
| CPU 1515-2 PN | 6ES7 515-2AM02-0AB0(固件 V2.8 / V2.9) | Python 教程、密码轮换篇 |
| CPU 1515-2 | 6ES7 515-2AM01-0AB0/V2.6 | Maroder1/TIA-openness 示例 |
| CPU 1516-3 | 6ES7 516-3AN02-0AB0 | Adding Hardware 篇 |
| CPU 1516-3 UP/1 DN | 6ES7 518-4UP00-0AB0/V3.1 | 官方 snippets UMAC 示例 |
| CPU 1214C DC/DC/DC | 6ES7 214-1AG40-0XB0 | MLFB 空格示例 |
| DI 32x24VDC HF | 6ES7 521-1BL00-0AB0 | Project Bases 篇、CreateAndInsertNew 示例 |
| DI 32x24VDC(另一样例) | 6ES7 521-1BH00-0AB0 | ⚠️ Adding Hardware 篇,见下方警告 |
| S7-300 CPU | 6ES7 317-2EK14-0AB0 | 官方 FindModuleTypes 示例 |
| S7-300 电源 | 6ES7 510-1DJ01-0AB0/V2.0 | 官方 CreateWithItem 示例 |
| S7-1500 电源 | 6ES7 510-1DJ01-0AB0/V2.0(同型) | — |
| ET 200SP 站 | 6ES7 155-6AU30-0CN0/V4.2 | ET 200SP PN HF 虚拟模块示例 |
| ET 200SP 虚拟模块类型标识 | OrderNumber:ET200SP.Virtual.Module | 同上 |
| ET 200SP 机架标识 | System:Rack.ET200SP | 官方 FindModuleTypes 示例 |
| S7-7300 设备标识 | System:Device.S7300 | 官方 Create 示例 |
| S7-1200 系统标识 | System:Device.S71200 | 慧兰博《白话 Openness(4)》 |
| S7-1500 系统标识 | System:Device.S71500 | 同上 |
| SINAMICS S120 CU320-2 | 6SL3040-1MA01-0Axx/V4.8(TypeIdentifier 需第四段 S120) | Startdrive 手册 |
| SINAMICS G120 | OrderNumber:<MLFB>/<FW>/(仅三段) | 同上 |
| GSD 模块示例 | GSD:SIEM8139.GSD/DAP、GSD:SIEM8139.GSD/M/4 | 官方 TypeIdentifier 章节 |
⚠️
6ES7 521-1BH00-0AB0的警告:这组订货号只出现在英文博客「Adding Hardware Programmatically」中,而该文已被 raw 资料自标注为含 AI 生成痕迹与 API 幻觉(示例用了错误的Siemens.TiaPortal.Openness命名空间与不存在的Devices.CreateDeviceItem)。该 MLFB 需对照官方手册核实。 官方确认的 DI 模块 MLFB 是6ES7 521-1BL00-0AB0(来自官方 GitHub snippets 与 HubWiz 项目创建篇)。
3.4.3 完整组态代码(CreateWithItem + PlugNew 路线)
using System.Linq;
using Siemens.Engineering;
using Siemens.Engineering.HW;
// ① 官方确认路线:CreateWithItem 建站
Device plc = project.Devices.CreateWithItem(
"OrderNumber:6ES7 515-2AM02-0AB0/V2.9", // CPU 1515-2 PN,固件 V2.9
"PLC_1", // ★ 第 2 参是"设备项名"
"PLC_1"); // ★ 第 3 参是"设备名"
// ② 头模块(CPU 本身)作为容器,往里插槽位模块
HardwareObject cpuHead = plc.DeviceItems.First();
// ③ 数字量输入 32 点,槽位 2(先问能力再执行)
if (cpuHead.CanPlugNew("OrderNumber:6ES7 521-1BL00-0AB0", "DI_32", 2))
cpuHead.PlugNew("OrderNumber:6ES7 521-1BL00-0AB0", "DI_32", 2);
// ④ 其余模块:先查空闲槽位,避免硬编码槽位号
var freeSlots = cpuHead.GetPlugLocations()
.Where(p => p.PositionNumber > 1) // 跳过槽位 1(CPU)
.OrderBy(p => p.PositionNumber).ToList();
int nextSlot = freeSlots.Count > 0 ? freeSlots[0].PositionNumber : 2;
批量插入其余模块(循环体):
string[] tailModules = {
"OrderNumber:6ES7 522-1BL01-0CA0", // DQ 32x24VDC/0.5A ST ⚠️ MLFB 需核对
"OrderNumber:6ES7 531-1EC02-0CA0", // AI 4xU/I 2x4..20mA ST ⚠️ MLFB 需核对
"OrderNumber:6ES7 532-1AH00-0AB0" // AQ 4xU/I 2x0..10V ST ⚠️ MLFB 需核对
};
string[] tailNames = { "DQ_32", "AI_4", "AQ_4" };
for (int i = 0; i < tailModules.Length; i++)
{
if (cpuHead.CanPlugNew(tailModules[i], tailNames[i], nextSlot))
{ cpuHead.PlugNew(tailModules[i], tailNames[i], nextSlot); nextSlot++; }
else
Console.WriteLine($"槽位 {nextSlot} 无法插入 {tailNames[i]}(名称重复/容器不支持)");
}
⚠️ 另有一条社区写法
cpuHead.CreateAndInsertNew("DI 32x24VDC HF", 1, new DeviceComposition("Slot"), "6ES7 521-1BL00-0AB0", null)。⚠️CreateAndInsertNew与DeviceComposition组合未见于官方手册,请核实。
3.4.4 边界:单应用实例只能加载一个 TIA 版本
这是本主题最工程化的多版本方案(DotNetSiemensPLCToolBoxLibrary,477★)中的硬性互斥限制:
“You cannot open a project in V if you already have a project open in another version. You need to restart the Application!”
每个 TIA 版本一个独立程序集 —— DotNetSiemensPLCToolBoxLibrary.TIAV13 到 .TIAV21,主工程用反射按版本动态加载对应 DLL 与 Step7ProjectVxx 类。
来源:本文综合自 raw 目录下的《西门子官方手册》(7.15.3/7.16.2/7.16.3)、《中文社区实战》、《GitHub 开源》、《英文技术博客》等抓取结果。
3.5 GetService<T>() 服务定位模式
3.5.1 官方服务清单(节选)
| 对象 | 服务 | 命名空间 |
|---|---|---|
DeviceItem | SoftwareContainer | Siemens.Engineering.SW |
DeviceItem(网络接口) | NetworkInterface、NetworkPort、PlcAccessLevelProvider | Siemens.Engineering.HW.Features |
DeviceItem(CPU) | OnlineProvider、DownloadProvider、AccessProtection | Siemens.Engineering.Online / .Download / .HW |
DeviceItem(驱动) | DriveObjectContainer、OnlineDriveObjectContainer | Siemens.Engineering.MC.Drives |
DeviceItem(UMAC) | UmacDevice | Siemens.Engineering.Umac |
DeviceItem(PLC 主密码) | PlcMasterSecretConfigurator | — |
HardwareObject(子网) | Subnet | Siemens.Engineering.HW |
PlcBlock | ICompilable、CrossReferenceService、PlcBlockProtectionProvider | Siemens.Engineering.Compiler 等 |
Project | CaxProvider、UmacConfigurator | Siemens.Engineering.Cax / .Umac |
TiaPortal(Add-in 内) | MessageBoxProvider、ProgressProvider、FeedbackProvider | Siemens.Engineering.AddIn |
3.5.2 从 DeviceItem 到 PlcSoftware 的标准四步
using Siemens.Engineering;
using Siemens.Engineering.SW; // SoftwareContainer
using Siemens.Engineering.HW; // DeviceItemClassifications
using Siemens.Engineering.HW.Features; // NetworkInterface
// ① 找到 CPU 设备项——用 Classification 判定,不要用索引
var plcItem = project.Devices
.First(d => d.Name == "PLC_1") // 站点名(Device 层)
.DeviceItems
.First(x => x.Classification == DeviceItemClassifications.CPU);
// ② 取软件容器
var container = plcItem.GetService<SoftwareContainer>();
if (container == null) throw new InvalidOperationException("该设备项无软件容器");
// ③ 判定软件类型
if (container.Software is not PlcSoftware plc)
throw new InvalidOperationException("该设备项不是 PLC 软件");
// ④ 取网络接口
NetworkInterface ni = plcItem.GetService<NetworkInterface>();
if (ni == null) throw new InvalidOperationException("该设备项无网络接口");
3.5.3 NetworkInterface 的导航器
NetworkInterface
├── Nodes ─────────── Node(节点,Name / NodeId / NodeType)
│ 多数接口只提供单个 Node,通常用 itf.Nodes.First()
│ Node 的动态属性:Address / SubnetMask / UseRouter /
│ RouterAddress / UseIpProtocol / IpProtocolSelection /
│ MacAddress / DhcpClientId / PnDeviceNameSetDirectly /
│ PnDeviceNameAutoGeneration
│ ⚠️ 属性名是「Address」「UseIpProtocol」「IpProtocolSelection」
│ ——不是「IPAddress」「UseIPProtocol」
├── IoControllers ─── IoController(接口配置为 IO 控制器时返回一个)
├── Subnet ────────── Subnet(该接口连接的子网;未连接时为 null)
├── InterfaceOperatingMode ── 位标志,检查是否含 IoController
├── Port / PortConnector 相关
└── TransferAreas ─── TransferAreaComposition(CP1604/1616/1626 传输区)
关键点:节点(Node)的 IP 属性名是 Address、子网掩码是 SubnetMask、启用 IP 是 UseIpProtocol。
// 官方惯用法:取节点、判类型、读写属性
NetworkInterface itf = plcItem.GetService<NetworkInterface>();
foreach (Node node in itf.Nodes)
{
string name = node.Name;
NetType type = node.NodeType; // Ethernet / Profibus / Mpi / Asi / PtP ...
string id = node.NodeId;
if (type == NetType.Ethernet)
{
string addr = (string)node.GetAttribute("Address");
string mask = (string)node.GetAttribute("SubnetMask");
bool useIp = (bool)node.GetAttribute("UseIpProtocol");
}
}
⚠️ 社区广泛流传的
IPAddress/UseIPProtocol写法(如item.SetAttribute("IPAddress", ipAddress); item.SetAttribute("SubnetMask", "255.255.255.0"); item.SetAttribute("UseIPProtocol", true);)在官方手册的节点属性表中对应的是Address/SubnetMask/UseIpProtocol。不同设备项(CPU 本身 vs 网络接口的 Node)属性名可能不同——CPU 的 PROFINET 接口另有IPAddress之类的属性,需按GetAttributeInfos()实测确认,不要凭记忆写。
3.5.4 OpcUaServer / HwUtilities 服务
// OPC UA 数据导出入口——注意 Find 返回值可能为 null,必须判空
private static void OpcUaExport(Project project, DeviceItem plc)
{
OpcUaExportProvider p = project.HwUtilities.Find("OPCUAExportProvider")
as OpcUaExportProvider;
if (p == null) return; // 未安装 OPC UA 选项包时为 null
p.Export(plc, new FileInfo($@"D:\OPC UA export files\{plc.Name}.xml"));
}
path必须是绝对目录路径。OPC UA 数据不走 SimaticML——手册单列小节说明,导出为专用 OPC UA XML 格式。
OPC UA 服务器组态前提(S7-1500):
| 项 | 要求 |
|---|---|
| 默认状态 | 默认禁用,须在 CPU 属性 → OPC UA Server General 中激活 |
| 许可 | 需要运行系统许可证,按 CPU 型号分 Small / Medium / Large 三档 |
| Small | 1511 / 1512 / 1513、ET 200SP CPU、1515SP PC |
| Medium | 1515 / 1516、CPU 1507、1516pro-2 PN |
| Large | 1517 / 1518 |
| 起始版本 | TIA Portal V14 + S7-1500 V2.0 起支持 |
| 适用 CPU | 含 1500F/T/C、1500pro、ET 200SP CPU、S7-1500 软件控制器、PLCSIM Advanced |
| 限制 | 不能通过 CP 或 CM 经背板总线访问 CPU 的 OPC UA 服务器;只能走 CPU 上集成的 PROFINET 接口 |
| 证书 | 应用名称(Subject Alternative Name)写入证书,改应用名后可能需重新生成证书 |
// 证书管理:本地证书库 → 取模板 → 创建 → 挂到子模块
var templateOpcUaServer = localCertificateStore.GetCertificateTemplate(CertificateUsage.OpcUaServer);
var certificateOpcUa = localCertificateStore.Certificates.Create(templateOpcUaServer);
var foundOpcUaCertificate = opcUaSubmodule.GetAttribute("OpcUaServerCertificate");
opcUaSubmodule.SetAttribute("OpcUaServerCertificate", certificateOpcUa);
// 传 null 可移除证书
opcUaSubmodule.SetAttribute("OpcUaServerCertificate", null);
管理客户端证书的前置条件:必须先启用项目级全局安全设置(项目树 → 安全设置 → 用户名/密码)+ CPU 属性 → 防护与安全 → 证书管理器;用户认证分访客认证与用户名密码认证两种,要限定权限须禁用访客认证。
来源:本文综合自 raw 目录下的《西门子官方手册》(7.16.8 Accessing device item as interface、5.7 Functions for devices/networks)、《进阶专题》(OPC UA 数据交换、冗余与细分能力)、《中文社区实战》与《英文技术博客》社区实例等抓取结果。
3.6 PROFINET 网络创建与 IP 地址设置
3.6.1 子网创建的两种方式
方式一:创建连到接口的子网(网络类型由接口类型决定)
Node node = ...;
Subnet subnet = node.CreateAndConnectToSubnet("NameOfSubnet");
可用的子网类型标识:
| 类型标识 | 网络类型 |
|---|---|
System:Subnet.Ethernet | 以太网(PROFINET 用这个) |
System:Subnet.Profibus | PROFIBUS |
System:Subnet.Mpi | MPI |
System:Subnet.Asi | AS-i |
方式二:创建不连到接口的全局子网
Project project = ...;
SubnetComposition subnets = project.Subnets;
Subnet newSubnet = subnets.Create("System:Subnet.Ethernet", "NewSubnet");
3.6.2 ⚠️ IP 地址必须在建好网络之后才能设置
这是本章节最高频的踩坑点,社区明确警告:
“IP 地址赋值前必须先创建网络(Network must be created before IP assignment)”
顺序不可颠倒:
① Devices.CreateWithItem(...) 建设备
② PlugNew(...) 插模块
③ node.CreateAndConnectToSubnet("PN_1") ★ 建网络(必须在此之后才能设 IP)
④ node.SetAttribute("Address", "192.168.0.10") 设 IP
⑤ node.SetAttribute("SubnetMask", "255.255.255.0")
⑥ node.SetAttribute("UseIpProtocol", true)
3.6.3 完整的网络 + IP 组态代码
using System.Linq;
using Siemens.Engineering;
using Siemens.Engineering.HW;
using Siemens.Engineering.HW.Features;
// 前提:plcItem 是 CPU 设备项(见 3.5.2 的四步)
NetworkInterface ni = plcItem.GetService<NetworkInterface>();
if (ni == null) throw new InvalidOperationException("无网络接口");
// ① 定位或创建子网(PROFINET 用 Ethernet 子网)
Node node = ni.Nodes.FirstOrDefault();
if (node.Subnet == null)
Console.WriteLine($"已创建子网:{node.CreateAndConnectToSubnet("PN_IE_1").Name}");
// ② 【必须先有网络】才能设 IP——官方节点属性名是 Address / SubnetMask / UseIpProtocol
node.SetAttribute("UseIpProtocol", true);
node.SetAttribute("Address", "192.168.0.10");
node.SetAttribute("SubnetMask", "255.255.255.0");
创建 IO 系统(IO 控制器侧):
// 前置:接口已连到子网、且尚无 IO 系统;name 传 null/Empty 用默认名
if ((ni.InterfaceOperatingMode & InterfaceOperatingModes.IoController) != 0)
{
IoController ioController = ni.IoControllers.First();
if (ioController?.IoSystem == null)
{
IoSystem ioSystem = ioController.CreateIoSystem("io system");
Console.WriteLine($"已创建 IO 系统:{ioSystem.Name},编号 {ioSystem.Number}");
}
}
3.6.4 子网与节点属性(官方表)
Ethernet 类型节点的属性:
| 属性 | 数据类型 | 可写 | 访问 | 说明 |
|---|---|---|---|---|
Name | string | 读 | — | 节点名 |
NodeId | string | 读 | — | 节点唯一标识(只能通过 Openness 看到) |
NodeType | NetType | 读/写 | — | 节点类型从子网继承 |
UseIsoProtocol | bool | 读/写 | 动态 | — |
MacAddress | string | 读/写 | 动态 | 如 01-80-C2-00-00-00 |
UseIpProtocol | bool | 读/写 | 动态 | 即使 UI 上不可见也能读 |
IpProtocolSelection | enum | 读/写 | 动态 | — |
Address | string | 读/写 | 动态 | 仅支持 IPv4,不支持 IPv6 |
SubnetMask | string | 读/写 | 动态 | — |
UseRouter | bool | 读/写 | 动态 | — |
RouterAddress | string | 读/写 | 动态 | — |
DhcpClientId | string | 读/写 | 动态 | — |
PnDeviceNameSetDirectly | bool | 读/写 | 动态 | PROFINET 设备名在设备处直接设置,非所有设备都支持 |
PnDeviceNameAutoGeneration | bool | 读/写 | 动态 | PROFINET 设备名自动生成 |
AS-i 类型节点的额外属性:Address(string,读/写,动态)—— AS-i 从站的附加属性。
两条重要的联动规则:
- 写入一般仅在 UI 上可改的属性时才允许;不同设备项类型不同——例如只有
RouterUsed为 true 时用户才能设RouterAddress; - 若用户在 IO 控制器上改了
SubnetMask,其下所有 IO 设备的子网掩码也会被改成同一个值。
3.6.5 连接与断开 IO 连接器
// 把 IO 连接器(IO 从站侧)连到 IO 系统
IoSystem ioSystem = ...;
IoConnector ioConnector = ...;
ioConnector.ConnectToIoSystem();
IoController ioController = ioConnector.GetIoController();
// 断开
ioConnector.DisconnectFromIoSystem();
// 断开节点与子网
Node node = ...;
node.DisconnectFromSubnet();
前置条件:
| 操作 | 前置条件 |
|---|---|
IoConnector.ConnectToIoSystem(ioSystem) | ① 该 IoConnector 尚未连到 IO 系统;② 该 IoConnector 接口与目标 IoController 接口连接到同一子网 |
IoController.CreateIoSystem(name) | ① 该 IO 控制器的接口已连接到子网;② 该 IO 控制器尚无 IO 系统 |
Node.CreateAndConnectToSubnet(name) | — |
CreateIoSystem 的 name 语义:name 为 null 或 String.Empty 时使用默认名。
命名约定:社区实践建议 PROFINET 接口名设为 PN-IE_1 这一类形式(ni.Name = "PN-IE_1"),保持与 TIA Portal UI 中看到的接口名一致,便于后续导航与排错。
来源:本文综合自 raw 目录下的《西门子官方手册》(7.14.1 Creating a subnet、7.14.9/7.14.10 节点属性、7.14.11~7.14.16、7.14.21 PROFINET IO 系统属性)、《英文技术博客》(Python 教程、Project Bases 篇)、《中文社区实战》社区代码等抓取结果。
3.7 PlcSoftware 容器获取
3.7.1 官方路径
官方 5.7.2「Querying PLC and HMI targets」给出的路径是:取 PLC 设备项的 SoftwareContainer,再 cast 其 Software 为 PlcSoftware。
PlcSoftware plcSoftware = null;
foreach (Device device in project.Devices)
{
foreach (DeviceItem deviceItem in device.DeviceItems)
{
var softwareContainer = deviceItem.GetService<SoftwareContainer>();
if (softwareContainer?.Software is PlcSoftware ps)
{
plcSoftware = ps;
break;
}
}
}
3.7.2 三种等价写法
| 写法 | 代码 | 说明 |
|---|---|---|
| 官方写法 | item.GetService<SoftwareContainer>().Software as PlcSoftware | 最通用,PLC / HMI 统一入口 |
| Classification 过滤(推荐) | project.Devices.First(x => x.Name == "…").DeviceItems.First(x => x.Classification == DeviceItemClassifications.CPU).GetService<SoftwareContainer>().Software as PlcSoftware | 官方 snippets 做法,不依赖索引 |
| 显式接口转换(无泛型时) | ((IEngineeringServiceProvider)item).GetService<SoftwareContainer>().Software as PlcSoftware | Python 侧与反射场景常用 |
⚠️ 不要用
project.Devices[0].DeviceItems[1]这种索引。英文博客的「Configuring PLC I/O Tags via XML」明确提醒:“Project.Devices[0].DeviceItems[1]只是 S7-1516 那个案例的索引,不要假设该索引对其他型号/项目结构成立——应按GetService<SoftwareContainer>()是否可用来选。“
3.7.3 PlcSoftware 的常用成员
| 成员 | 类型 | 说明 |
|---|---|---|
BlockGroup | PlcBlockSystemGroup | 程序块根组 |
TagTableGroup | PlcTagTableSystemGroup | 变量表根组 |
ExternalSourceGroup | PlcExternalSourceSystemGroup | 外部源文件组 |
Name / Path | string / FileInfo | 软件名与路径 |
WatchTable | PlcWatchTable(含 Force Table) | 监视表 |
GetService<PlcUnitProvider>() | — | 软件单元(V17+ 多软件单元工程必需) |
GetService<OnlineProvider>() | — | 在线服务 |
GetService<CompilerService>() / GetService<ICompilable>() | — | 编译服务 |
软件单元的坑(官方片段揭示):变量表不只在
software.TagTableGroup——每个 Unit 下还有自己的TagTableGroup,必须Concat合并,否则会漏掉软件单元内的变量。见第 4 章代码。
来源:本文综合自 raw 目录下的《西门子官方手册》(5.7.2 Querying PLC and HMI targets)、《GitHub 开源》(官方 snippets ProgramBlocksSnippets.cs / PlcTagTableSnippets.cs)、《英文技术博客》、《中文社区实战》等抓取结果。
3.8 第三方设备与 GSDML
3.8.1 核心结论:第三方设备无法从零创建
Openness 不支持用 Device.CreateWithItem() / Device.Create() 从零创建第三方硬件(SEW Movitrac / Movidrive、SICK 编码器、FANUC 机器人等)。西门子目录设备(G120、SINAMICS、ET200)才可用。
根因:设备目录基于 Siemens.Plattorm.Configuration.Contracts.dll(原文如此,拼写为 Plattorm),第三方厂商未提供 Openness 兼容的 DeviceItemTypeId GUID。
3.8.2 GSDML 的两阶段模型
| 阶段 | 内容 | Openness 是否暴露 |
|---|---|---|
| Stage 1「目录安装」 | GSDML 解析并注册进硬件目录 | Openness 未直接暴露 |
| Stage 2「项目实例化」 | 把目录中的设备放进项目拓扑并配参数与 I/O | 可通过 CAx import 完成 |
这意味着:Openness 没有直接对应 UI 里 Options > Manage general station description files 的 “install GSDML” 方法。唯一可编程路径是 CaxProvider 服务做 CAx 导入/导出。
3.8.3 GSDML 安装路径
C:\ProgramData\Siemens\Automation\Simatic.StationConfiguration\GSD
放完需重启 TIA Portal。
GSD 类 TypeIdentifier 格式:GSD:<GsdName大写>/<GsdType>,其中 GsdType ∈ {D 设备, R 机架, DAP 头模块, M 模块, SM 子模块},可再加 /<GsdId>。
3.8.4 CAx 导入的正确工作流
// 顺序敏感:服务从 Project 对象取,所以项目必须先打开
Project project = tiaPortal.Projects.Open(new FileInfo(@"D:\Project\Plant42.ap20"));
CaxProvider caxProvider = project.GetService<CaxProvider>();
if (caxProvider == null) { /* 服务不适用或项目未完全打开 */ }
else
{
// CAx export / import
}
⚠️
GetService<T>()服务不可用时返回 null 而非抛异常,必须判空。
官方推荐的「先导出再导入」工作流:
- UI 手工建参考项目,装好 GSDML 并配好设备名 / IP / 子网 / 模块 / I/O 地址;
- 对参考项目跑 CAx export,拿到 schema-valid、版本正确的 XML;
- UI 改一个参数再 export,diff 两份 XML 反推哪个元素/属性控制哪个设置;
- 把已知 good 的 export 改成模板并参数化;
- 导入目标项目;
- 记录每次导入结果——CAx import 会报”部分成功”,不抛异常 ≠ 导入成功。
3.8.5 ⚠️ 第三方设备导入的代码(签名存疑)
// ⚠️【该来源 API 签名存疑,需对照官方手册核实】
// 英文博客给出的 IDeviceExport / CreateSegmentImport 未见于官方手册正文,
// 官方 CAx 路径是 project.GetService<CaxProvider>()
var device = project.Devices.Find(d => d.Name.Contains(deviceName));
device.GetService<IDeviceExport>()?.Export(exportFile, ExportOptions.IncludeSubmodules);
using (var import = tiaPortal.EngineeringInstance.CreateSegmentImport("ThirdPartyImport"))
{
import.Import(templatePath);
}
// 模板导入后需单独设置 IP
i.Properties["IPAddress"].Value = "192.168.1.100";
⚠️ 上面的
IDeviceExport、CreateSegmentImport、EngineeringInstance、i.Properties[...]均未在官方 V19 手册中确证。raw 资料《英文技术博客》文末已自标注:“「Adding Third-Party Hardware」文中的IDeviceExport、IGSDCatalog、CreateSegmentImport等类型同样需核对。” 落地请以CaxProvider为准。
3.8.6 第三方设备的硬限制清单
| 限制 | 说明 |
|---|---|
| 模板 XML 版本特定 | 升级后需重新生成 |
| 设备名必须唯一 | — |
| 无法设置第三方安全设备的安全参数 | 通过 Openness 不行 |
| 无法导入 PC station XML | Openness 从不读 TIA 的 PC station XML 交换格式(CAx 导入 ≠ Openness) |
| 不支持跨块/类型的全局搜索 | 部分第三方工具的限制 |
| 导入时可能”部分成功” | 不抛异常 ≠ 成功 |
排障表:
| 现象 | 根因 |
|---|---|
caxProvider 为 null | 服务不适用或项目未完全打开 |
| 导入跑完没设备 | 工程站上没装 GSDML(先装进该机器的 TIA 目录) |
| schema 校验被拒 | CAx XML 是按别的 schema 版本写的(用同一 TIA build 重新导出参考) |
| 设备导入成功但无模块/I/O 地址 | CAx 文档缺模块级元素 |
| 连接时 Openness 调用失败 | 版本不匹配或账号缺 TIA Openness 权限 |
EngineeringTargetInvocationException | 订货号不在已加载目录(装 HSP) |
CrossReferenceException | 未 PlugNew 就 GetService<IoSystem>() |
COMException 0x80004005 | 用户组缺权限(用 whoami /groups 查) |
InvalidOperationException | 项目未在当前会话打开 |
HRESULT 0x8001010E RPC_E_SERVER_CANTUNLOAD | TIA 被另一自动化死锁(一个项目同时只允许一个 Openness 进程) |
部署前置条件(务必写进部署文档):Openness 程序集、TIA Portal build、CAx schema 三者一起走;工程站上的 GSD 目录内容是部署前置条件。某个设置若不出现在参考 export 里,CAx import 就设不了它。
来源:本文综合自 raw 目录下的《英文技术博客》(Adding Third-Party Hardware、Installing GSDML via CAx Import、Adding Hardware Programmatically)、《中文社区实战》、《进阶专题》(AutomationML/AML 与 CAx 数据交换)、《西门子官方手册》(7.15.2 GSD 类 TypeIdentifier)等抓取结果。
3.9 ET 200SP PN HF 虚拟模块与 PN PnP 耦合器
3.9.1 ET 200SP PN HF 虚拟模块
虚拟模块可编程添加/删除。
虚拟模块支持的属性:
| 属性 | 类型 | 说明 |
|---|---|---|
SharedDeviceAccessConfigured | Boolean | 共享设备访问已配置 |
Name | String | — |
PositionNumber | Int32 | 位置号 |
VirtualType | UInt64 | EomAtom 值 = 1 MsoLocal |
AddSubModules | Int64 | — |
虚拟子模块支持的属性:
| 属性 | 类型 |
|---|---|
SharedDeviceAccessConfigured | Boolean |
Comment | String |
PositionNumber | Int32 |
Name | String |
ActivateDataStatus | Boolean(注意是字符串形式的 Boolean) |
// 创建 ET 200SP 站
Device ET200SP = newProject.Devices.CreateWithItem(
"OrderNumber:6ES7 155-6AU30-0CN0/V4.2", "ET200SP", "ET200SP");
DeviceItem rack = ET200SP.DeviceItems.First();
// 虚拟模块的类型标识是固定字符串
string typeIdentifier = "OrderNumber:ET200SP.Virtual.Module";
string name = "VirtualIO_1";
3.9.2 PN PnP 耦合器传输区(CCDX)
语义是”多播”:发送侧传输区可向多个接收伙伴传输区广播;反方向接收侧只能与一个发送伙伴通信。
MulticastableTransferArea.Create() 的 4 个重载:
| 签名 | 用途 |
|---|---|
Create(NetworkInterface, TransferAreaType) | 发送侧 + 接收侧 |
Create(NetworkInterface, TransferAreaType, Name) | 带名称 |
Create(NetworkInterface, TransferAreaType, Name, Length) | 带名称与长度 |
Create(MulticastableTransferArea, TransferAreaType) | 只建接收侧并挂到已有发送侧 |
// 发送侧
MulticastableTransferArea ta3 = senderNetworkInterface.MulticastableTransferAreas.Create(
receiverNetworkInterface, TransferAreaType.DDX, "Example_CCDX_TA", 32);
⚠️ V19 起
MulticastableTransferArea已被弃用,替代为Siemens.Engineering.HW.Address(Address { get; })与Siemens.Engineering.HW.AddressComposition(Addresses { get; })。新代码应直接用 Address / AddressComposition。 这是 V19 Major Changes 中明确列出的弃用项。
3.9.3 CCDx 同步域
| API / 属性 | 说明 |
|---|---|
SetAttribute("FastForwardingActive", true) | 快速转发 |
firstSyncDomain.DomainParticipants | 域参与者(.Count / .Add(toBeAdded) / 可遍历) |
newSyncDomain.Delete() | 删除同步域 |
3.9.4 ⚠️ PN PnP 与 CCDX 的前置条件
所有设备必须处于离线状态才能编译并创建传输区(与 PnPn 耦合器传输区处理方式一致)。
来源:本文综合自 raw 目录下的《西门子官方手册》(V19/V21 系统手册 5.8.39 / 5.8.40 / 5.8.41 / 5.8.42)、《西门子官方手册》7.1 Major Changes(V19 弃用项)、《西门子中国技术支持》转载等抓取结果。
3.10 CP 1604 / 1616 / 1626 传输区与等时同步
3.10.1 版本支持与前置条件
| 设备 | 最低版本 |
|---|---|
| CP 1604 | V2.7 或 V2.8+(取决于订货号) |
| CP 1616 | 同上 |
| CP 1626 | V1.1+ |
前置条件:所有设备必须处于离线状态才能编译并创建传输区。
3.10.2 传输区的创建与参数
NetworkInterface cpItf = CP1604Interface.GetService<NetworkInterface>();
TransferAreaComposition transferAreas = cpItf.TransferAreas;
TransferArea ta = transferAreas.Create("Example TA MS", TransferAreaType.MS);
// 查找:只有包含多个子模块(如 MSI)的 TransferArea 才需要 ExtendedPositionNumber
TransferArea taFind = transferAreas.Find(5); // 按位置号取
// 删除
transferAreaExample.Delete();
传输区参数表(DP 从站配置场景同样适用):
| 属性 | 类型 | 访问 | 说明 |
|---|---|---|---|
Name | string | 读/写 | — |
Comment | string | 读/写 | — |
Direction | enum | 读/写 | 方向 |
LocalToPartnerLength | Int32 | 读/写 | 本地到伙伴长度 |
PartnerToLocalLength | Int32 | 读/写 | 伙伴到本地长度 |
LocalAddresses | AddressComposition | 只读 | 地址对象集合 |
PartnerAddresses | AddressComposition | 只读 | 地址对象集合 |
PositionNumber | Int32 | 读 | 位置号 |
Type | enum | 读 | 类型 |
⚠️ 官方明确提示:任一属性不可用时会抛异常——需按目标设备能力防御式访问。
3.10.3 CM DP 配置为 DP 从站时的动态属性
| 属性 | 类型 | 可用条件 |
|---|---|---|
DpUseForTestCommissioningRouting | bool | 仅在”已配置为 DP 从站”时可用 |
DpWatchdog | bool | 需同时已分配 DP 主站 |
3.10.4 等时同步动态属性
| 属性 | 类型 | 访问 | 前置条件 |
|---|---|---|---|
IsochronousMode | bool | 读/写 | 需设备具备等时能力 |
SourceCycleTime | int | 读/写 | 需已启用等时模式 |
SendClock | float | 读/写 | 同上 |
IsochronousTiToCalculationMode | enum | 读/写 | 取值 FromOB / AutomaticMinimum / Manual |
IsochronousTi | double | 读/写 | — |
IsochronousTo | double | 读/写 | — |
DeviceItem plcHead = plc.DeviceItems.First();
bool iso = (bool)plcHead.GetAttribute("IsochronousMode");
plcHead.SetAttribute("IsochronousMode", true);
plcHead.SetAttribute("IsochronousTiToCalculationMode", "AutomaticMinimum");
3.10.5 ⚠️ StartAddress 的连带风险(重要)
在 Address 对象层面:
| 属性 | 类型 | 访问 | 说明 |
|---|---|---|---|
ProcessImage | Int32 | 读/写 | — |
InterruptObNumber | Int64 | 读/写 | 仅 S7-300/400 经典控制器可访问;写入仅 S7-400 支持 |
StartAddress | — | 读/写 | 有连带风险,见下 |
四条连带风险:
- 设置
StartAddress可能隐式改变同名模块上”对向 I/O 类型”的起始地址(改输入地址会改输出地址); - 打包地址(Packed addresses)Openness 不支持;
- 通过 Openness 改地址不会重新连线已分配的变量;
- 并非所有设备都支持写。
3.10.6 R/H 系统与 PROFINET 系统冗余
R/H 系统专属动态属性:
| 属性 | 类型 | 说明 |
|---|---|---|
UseFixedSystemDiagnosticsAlarmIds | boolean(读/写) | 在保留区占用最多 512 的固定定义 |
SuppressDeactivatingSystemDiagnosticsAlarms | boolean(读/写) | 仅对 FW ≥ 3.0 的 S7-1500 可用,默认 FALSE |
UseFixedSystemDiagnosticsAlarmIds 从 FALSE 改 TRUE 的三个连带后果(若项目已下载到 PLC):
- PLC 程序必须重新编译;
- 在线 PLC 必须 STOP 才能加载变更后的硬件组态;
- 未启用自动更新的 HMI 必须重新下载。
SuppressDeactivatingSystemDiagnosticsAlarms 的语义澄清:只抑制诊断报警,不会抑制诊断缓冲区条目——仍可用 DOE 检查所有设备状态。
两个属性的设置在 R/H 系统中会在两台 PLC 之间同步——这是 R/H 冗余的固有语义,脚本改一侧等于改两侧。
PROFINET 系统冗余模式:S2 能力的设备可作为 Switched S1 设备运行。UI 上的 “Mode” 属性在 Openness 中名为 RedundancyMode,可用于 S2 与 R1 IO 设备的 PROFINET 接口。
// R/H 诊断属性
bool isFixed = (bool)plcItem.GetAttribute("UseFixedSystemDiagnosticsAlarmIds");
plcItem.SetAttribute("UseFixedSystemDiagnosticsAlarmIds", false);
plcItem.SetAttribute("SuppressDeactivatingSystemDiagnosticsAlarms", true);
// S2 → SwitchedS1
NetworkInterface ni = S2device.DeviceItems
.First(f => f.Name == s_S2DeviceInterface1Name).GetService<NetworkInterface>();
ni.SetAttribute("RedundancyMode", "RedunancyMode.SwitchedS1");
Assert.AreEqual("RedunancyMode.SwitchedS1", ni.GetAttribute("RedundancyMode"));
⚠️ 注意官方文档中字符串字面量把 Redundancy 拼作 “Redunancy”(少一个 n),且该字符串本身含点号,写入时需按官方原文照抄。
变更后应重新编译验证:
plc1.GetService<ICompilable>().Compile(),官方示例断言compileResult.ErrorCount == 0。
来源:本文综合自 raw 目录下的《西门子官方手册》(V21 系统手册 5.8.33 / 5.8.34 / 5.8.39 节)、《西门子中国技术支持》转载、《进阶专题》(通信处理器与等时同步、冗余与细分能力)等抓取结果。
3.11 设备比较与指纹
3.11.1 官方比较能力
| 能力 | 说明 |
|---|---|
| TIA 内置比较 | 只能比较项目内的块 |
SIMATIC Automation Compare Tool | 必需的配套件(Entry 109797235)。专为比较 SimaticML 格式对象设计——转成 .SimaticML 后 TIA 自己就不能比了 |
Openness Compare 命名空间 | CompareResult(类名长期稳定) |
SIMATIC Automation Compare Tool 的能力边界(关键):
| 可比较 | 不可比较 |
|---|---|
| LAD / FBD / SCL / STL 程序块 | ⚠️ 用 CFC 和 SCL/Graph 编写的程序目前不行 |
| PLC 变量表 | — |
| DB | — |
| UDT | — |
这才是版本控制的真正瓶颈,不是 Git。
部署时必须做到:Openness 程序集、TIA Portal build、比较工具版本三者一致。
3.11.2 官方”quick station compare”的替代路径
英文社区提供了一套 6 步验证清单(可作为设备比较的实用流程):
- 匹配版本打开无迁移提示;
- 设备配置树含 S7-1515-2 PN + 槽 1/2 模块;
Tags_System数据类型与 I/O 地址正确;- 程序块含导入 FB 及实例 DB;
- UI 触发「编译 → 软件(全部重建)」0 错误 0 警告;
- 导出项目主文件用 TIA Portal Project Comparer 与上一合同基线 diff。
3.11.3 脚本化比对:把差异读出来自己判
// 逐设备项导出关键属性,与基线(CSV/Excel)比对
static IEnumerable<string> EnumerateDeviceFingerprints(Project project)
{
foreach (Device device in project.Devices)
{
yield return $"Device|{device.Name}|{device.TypeIdentifier}";
foreach (DeviceItem item in device.DeviceItems)
{
string tn = (string)item.GetAttribute("TypeName");
string pn = item.GetAttribute("PositionNumber")?.ToString() ?? "-";
yield return $"Item|{device.Name}|{item.Name}|{tn}|slot={pn}";
var ni = item.GetService<NetworkInterface>();
if (ni == null) continue;
foreach (Node node in ni.Nodes)
yield return $"Node|{device.Name}|{node.Name}|" +
$"ip={node.GetAttribute("Address")}|mask={node.GetAttribute("SubnetMask")}";
}
}
}
用法:与基线做集合差集
var current = EnumerateDeviceFingerprints(project).OrderBy(x => x).ToList();
var baseline = File.ReadAllLines(@"D:\baseline\fingerprints.txt")
.OrderBy(x => x).ToList();
foreach (var added in current.Except(baseline)) Console.WriteLine("[+] " + added);
foreach (var removed in baseline.Except(current)) Console.WriteLine("[-] " + removed);
建议纳入指纹的关键属性清单:TypeIdentifier、PositionNumber、TypeName、OrderNumber、FirmwareVersion、Name、Comment、Address、SubnetMask、I/O 起始地址、StartAddress。
来源:本文综合自 raw 目录下的《进阶专题》(版本控制:VCI 工作区 + Git + Automation Compare Tool 的三层结构)、《英文技术博客》(Project Bases 篇的 6 项验证清单、HubWiz 故障排查表)、《西门子官方手册》等抓取结果。
3.12 模块描述更新、证书与设备名称
3.12.1 设备名称(PnDeviceName)
| 属性 | 说明 |
|---|---|
PnDeviceNameSetDirectly | PROFINET 设备名在设备处直接设置。非所有设备都支持 |
PnDeviceNameAutoGeneration | PROFINET 设备名自动创建 |
SDC(Serial Drive Commissioner)在 Update 时会保留PROFINET 设置、PROFIsafe 地址、I/O 地址、partner port interconnection(拓扑),并可执行”副本到副本”的 partner port 互连——这是做批量驱动复制时必须关注的属性集合。
3.12.2 HSP / 模块描述更新
没有”安装 HSP”的 Openness API。 相关的官方提示:
| 现象 | 处置 |
|---|---|
EngineeringTargetInvocationException | 订货号不在已加载目录 → 装 HSP |
| 驱动出现但无报文 | 缺 SINAMICS HSP |
WithoutUserInterface 模式下对 HSP 安装的阻塞对话框更敏感 | 缺 HSP 的驱动组态应前置预检 |
| Openness 不支持兼容模式 | 工程中每个 HSP / 选件包都必须已安装 |
版本锁定清单(部署前置条件,务必写进部署文档):Openness 程序集版本 + TIA Portal build + CAx schema + HSP 集合。
3.12.3 证书管理
OPC UA 证书(见 3.5.4):从 localCertificateStore 取模板 → Certificates.Create(template) → opcUaSubmodule.SetAttribute("OpcUaServerCertificate", cert);传 null 移除。改应用名(Subject Alternative Name)后可能需重新生成证书。
OPC UA 管理客户端证书的前提链:项目级全局安全设置(项目树 → 安全设置 → 用户名/密码)→ CPU 属性 → 防护与安全 → 证书管理器。用户认证分访客认证与用户名密码认证两种,要限定权限须禁用访客认证。
工程通道 TLS 与 CPU 证书加固(安全加固维度清单,来自密码轮换篇):
| 维度 | 措施 |
|---|---|
| 工程通道 | TLS + 厂内 CA 签 CPU 证书 |
| CPU security level | 设为 “Allow only PUT/GET from specified partners”(TIA Portal > Protection & Security) |
| Web server | 禁用,或仅 user-defined pages + 只读 tag |
| 密码缓存 | BitLocker 加密卷 + NTFS DACL |
| 审计日志 | ≥7 年,90 天后转 WORM(21 CFR Part 11) |
| 网络隔离 | 工程 VLAN 与控制 VLAN 分离,防火墙限制来源(IEC 62443-3-3 SR 5.1) |
| 时间同步 | CPU NTP 对齐厂内 stratum-2,偏移 <5 s(RFC 5905) |
3.12.4 模块描述更新(AutomationML / AML)
必须区分三种交换格式(这是最常见的混淆点):
| 格式 | 用途 | 规范 / 路径 |
|---|---|---|
| SimaticML(Siemens XML) | 软件数据:块、PLC 变量表、UDT、工艺对象、HMI 画面 | XSD 在 …\Portal Vxx\PublicAPI\Vxx\Schemas\ |
| AutomationML / AML(开放 XML 标准) | 硬件 CAx 数据 | 基于 CAEX V2.15 模式;规范在 https://www.automationml.org/ |
| OPC UA XML | OPC UA 数据导出 | 专用格式,不走 SimaticML |
AML 支持矩阵:EPLAN Electric P8、TIA Selection Tool 和 TIA Portal 三者均支持。
AML 双向实操:
| 方向 | 步骤 |
|---|---|
| TIA → EPLAN | 项目树右键主 PLC 站点 →「导出 CAx 数据」→ 选设备/格式/路径;EPLAN 侧「项目数据 → PLC → 导入数据」,导入后「项目数据 → PLC → 导航器」可见地址,无需再建地址 |
| EPLAN / Selection Tool → TIA | TIA Selection Tool:项目选项卡 → 右上角「…」→「导出」→「TIA Portal或ECAD系统」→ 选目标系统 → 右下角「导出」→ 选保存位置。注意:无法自定义保存文件名,保存后默认为「项目.aml」。TIA 侧:「工具」→「导入 CAx 数据」→ 等待完成,无需先做任何硬件组态 |
导出格式选项:导出对话框中可选 AML (*.aml) 或 AML 2.0 (*.aml) 两种输出格式。
V19 行为变更:PROFINET / Ethernet 接口端口的 Label 值导出时不带空格(如 Label = P1 R → AML 中 Label = P1R);V19 起 CAx 导出/导入的命令行工具不再向用户提供。
V18 Update 2 行为变更:IO-Link 端口必须以 C/Q<n> 形式交换 Label 值(如 C/Q1、C/Q2);AML 交换 IO-Link 配置需用 “S7-PCT 3.5 SP3 Update 3” 或更高版本。
EPLAN 侧的常见故障点:EPLAN 侧做 AML 交换需要对应的部件库文件与宏文件,否则导入失败——常见故障点包括 IM155-6 服务器模块部件表、ET 200AL 非放置主功能导致的 PLC 导航器显示异常。
来源:本文综合自 raw 目录下的《进阶专题》(AutomationML/AML 与 CAx 数据交换、SimaticML 数据交换)、《西门子官方手册》(7.1 Major Changes CAx 章)、《中文社区实战》(EPLAN 集成篇、Selection Tool 导出篇)等抓取结果。
3.13 本章编程相关的通用避坑
| 坑 | 表现 | 处置 |
|---|---|---|
| 属性名大小写敏感 | bool vs Bool → 报 0x8000FFFF | API 区分大小写,逐字对照官方硬件参数表 |
| 项目不能被两个实例打开 | 打开报”项目已被占用” | 先 project.Close();Openness 对工程文件持排他写锁 |
| 设备在线时改配置 | not supported in online mode | OnlineProvider.GoOffline() |
| 块未编译就导出 | Inconsistent blocks cannot be exported | 先 Compile() |
| 装错引用导致对象类型判断失败 | 保存后重开缺设备 | FileInfo 路径与真实项目文件不一致 |
| 索引脆弱 | DeviceItems[1] 换个型号就错 | 用 Classification 或 TypeIdentifier |
PlugNew 挂错容器 | 设备插上但树里看不到 | 应挂在 GetService<Rack>() 或 GetService<IoSystem>() 返回的对象上 |
| 子模块 API 成功但编译失败 | 编译报错 | 模块变体不兼容 |
0x80003001 | Projects.Create 失败 | 目标目录已存在且非空 → 换新目录或改用 Projects.Open |
0x80010002 | FB 导入编译错 | PLCopen XML 由别的 TIA 版本导出 → 用匹配版本重导,或 ImportOptions.None 接受迁移 |
0x8000FFFE | 编译报符号歧义 | 符号地址大小写敏感,检查歧义符号 |
0x8000xxxx | 多数为 | 缺硬件配置 |
0x8001xxxx | 指向 | 程序内类型/实例不匹配 |
FileLoadException | 运行时加载失败 | 检查 Copy Local=False、HintPath 版本、V21 是否缺 TIA Engineer 组 |
| 多版本混用 | 提示无法打开 | 一个实例只能加载一个 TIA 版本 |
EngineeringSecurityException | 权限类 | 加组并注销重登;连续拒绝 firewall 三次也会抛 |
来源:本文综合自 raw 目录下的《英文技术博客》(Project Bases 篇、HubWiz 故障排查表、Adding Hardware 篇、V19→V21 迁移篇、每日密码轮换篇)、《中文社区实战》(深度开发篇)、《西门子官方手册》等抓取结果。
本章速查
- 对象模型层级:
Project.Devices→Device→DeviceItems→Items(递归)→ …。PositionNumber在同一容器内唯一;父子关系纯逻辑,模块被移除时其子模块也随之消失;未插入的 DeviceItem 从Device.UnpluggedItems找。.Items递归、.DeviceItems单层。 - 创建设备只有两个官方确认的方法:
CreateWithItem(DeviceItemTypeId, DeviceItemName, DeviceName)与Create(DeviceTypeId, DeviceName)。插模块用CanPlugNew/PlugNew(typeIdentifier, name, positionNumber)(positionNumber = 65535表示自动位置)。⚠️CreateWithOrder/CreateAndInsertNew/PlugNew(DeviceItem, …)未见于官方手册,签名存疑需核实。 - TypeIdentifier 三种类型:
OrderNumber:(最常用,可带/<FirmwareVersion>)、GSD:<NAME大写>/<D|R|DAP|M|SM>[/<GsdId>]、System:Device.*/System:Subnet.*/System:Rack.*。获取方式优先级:API 枚举HardwareCatalog.Find("")> UI 路径(Options → Settings → Hardware configuration → 勾选显示 type identifier → Devices & networks → Catalog → Information)。 - MLFB 订货号必须与硬件目录完全一致,包括空格。正确
"6ES7 214-1AG40-0XB0",错误"6ES72141AG400XB0"抛EngineeringException。一律从 TIA 目录复制粘贴。 GetService<T>()必须判空(不可用返回null)。取 PLC 的标准四步:DeviceItems.First(x => x.Classification == CPU)→GetService<SoftwareContainer>()→.Software as PlcSoftware。绝不要用DeviceItems[1]索引。- IP 必须在建好网络之后才能设置:
node.CreateAndConnectToSubnet("PN-IE_1")→ 再SetAttribute。官方节点属性名是Address/SubnetMask/UseIpProtocol(不是IPAddress/UseIPProtocol);改 IO 控制器的 SubnetMask 会连带改其下所有 IO 设备。 - 第三方设备无法从零创建(根因:第三方未提供 Openness 兼容的
DeviceItemTypeIdGUID)。GSDML 放C:\ProgramData\Siemens\Automation\Simatic.StationConfiguration\GSD后重启 TIA Portal;唯一可编程路径是project.GetService<CaxProvider>()做 CAx 导入导出;走”手工配一次 → CAx export → diff 反推 → 模板化 → import”流程,不抛异常 ≠ 导入成功。 - V19 起
MulticastableTransferArea已弃用,改用Siemens.Engineering.HW.Address/AddressComposition。CP 1604/1616(V2.7/2.8+)与 CP 1626(V1.1+)的传输区要求设备全部离线。 - 三种交换格式必须分清:SimaticML(软件数据,XSD 在
PublicAPI\Vxx\Schemas\,只对当前版本有效,不可长期存档)/ AutomationML(硬件 CAx 数据,基于 CAEX V2.15 模式,EPLAN + Selection Tool + TIA 三方支持)/ OPC UA XML(专用,不走 SimaticML)。 - 比较 LAD/FBD 必须用
SIMATIC Automation Compare Tool(Entry 109797235),TIA 内置比较只能比项目内的块、转成 SimaticML 后就不能比了。该工具不支持 CFC 和 Graph 编写的程序——这才是版本控制的真正瓶颈。
第4章 PLC 程序块、标签与外部源生成
第 4 章 PLC 程序块、标签与外部源生成
本章导读
本章解决”如何用脚本往 PLC 里写程序”这个核心问题。结论先行:Openness 不能逐行编辑块内程序逻辑,这是架构性硬边界;唯一官方可编程的路径是「写 SCL 文本 → 挂成外部源 → 从源生成块」。本章先把三条”写程序”路线的取舍讲透,再给出 ExternalSource 的完整可运行工作流、五种块类型的 SCL 模板、DB 编号不能硬编码这个高频坑、以及标签表的 XML 批量导入方案。所有代码可直接复用。
4.1 三条”写程序”路线的取舍
4.1.1 路线对比表
| 维度 | ① 外部源(SCL/STL 文本) | ② Simatic ML XML 导入导出 | ③ 库 / 拖拽 |
|---|---|---|---|
| 定位 | 唯一官方可编程的”生成程序”路径 | 交换格式,不适合手写 | 人工操作 |
| 核心 API | ExternalSources.CreateFromFile()GenerateBlocksFromSource() | PlcBlock.Export(FileInfo, ExportOptions)Blocks.Import(FileInfo, ImportOptions) | TIA Portal UI 手工 |
| 支持的语言 | 仅 STL 与 SCL(LAD/FBD 不支持导出为源) | LAD / FBD / SCL / STL / GRAPH(以导出格式为准) | 全部 |
| 可 diff / 可 review | ✅ 纯文本,适合 Git | ✅ XML,可 diff | ❌ 二进制 .ap |
| 适合大批量生成 | ✅ 主流做法,模板 + 字符串替换 | ⚠️ 结构化改动容易导不回来 | ❌ 不可自动化 |
| 能否改 LAD/FBD 网络 | ❌ 不能 | ⚠️ 能改 <NetworkSource> 内的 <FlgNet>,但极难 | ✅ 手工 |
| 命名空间版本敏感性 | 低(文本格式 V14+ 世代内前后兼容) | 高:命名空间随版本变化 | — |
| 能否填块号 | ⚠️ DB 号会被静默忽略(见 4.6) | ✅ 可填 | ✅ 手工 |
| 格式稳定性 | 稳定 | ⚠️ 官方声明导出格式仅对当前 Openness 版本有效 | — |
| Know-how 保护块 | ⚠️ 需先解除保护 | ⚠️ 导入时必须过滤 !IsKnowHowProtected | ✅ 可输入密码 |
4.1.2 官方推荐与实际共识
官方手册只承认两条”生成程序”路线(PLC技术网《TIA Portal Openness 概述》tid=650511 的准确归纳):
- 外部源文件——写成 SCL/STL 文本 →
CreateFromFile挂到「外部源文件」组 →GenerateBlocksFromSource()生成块,主流做法; - SimaticML 导出 → 外部修改 →
Import(..., ImportOptions.Override)覆盖导入。
选型结论:
| 你的场景 | 选哪条 |
|---|---|
| 从 Excel/数据库生成大量 FC/FB/DB | ① 外部源 |
| 需要 Git 版本控制与代码 review | ① 外部源(唯一产出人类可 diff 产物的工作流) |
| 需要保留 LAD/FBD 图形逻辑 | ③ 库 / 拖拽,或 ① 生成后再手工改图形 |
| 跨项目复用标准块 | 类型化全局库(typed global library) |
| 改少量单属性(如 Know-how 密码、块号) | ② 直接 SetAttribute |
| 梯形图批量生成 | ② 导出原生 XML → 只改 <NetworkSource> 节点注入 <FlgNet> → 导入。严禁手写完整梯形图 XML |
4.1.3 SimaticML 的覆盖范围与格式陷阱
覆盖范围(可导入导出):
| 域 | 覆盖对象 |
|---|---|
| PLC | 块、PLC 变量表、变量和常量、UDT、工艺对象 |
| HMI | 画面、画面模板、全局画面、弹出画面、滑入画面、VB 脚本、文本列表、图形列表、周期、连接、变量表、变量 |
关键坑(官方明确警告):手册声明导出格式仅对当前 Openness 版本有效,未来版本可能更改格式。不要把 SimaticML 当作长期存档格式——这正是 V20 起引入 SIMATIC SD(YAML)的原因。
SIMATIC SD(Simatic Source Document):基于 YAML 的交换格式,自 TIA Portal V20 起可通过 Openness API 使用,采用易读的文本化方法,且自 V20 起跨 TIA Portal 版本兼容。已明确支持 S7-1200 / S7-1500 的块,以及 LAD 程序代码在 SIMATIC SD 中的表示。支持导入导出的元素范围在持续扩大中(“continuously becoming available for more elements”),需按目标版本核对当前支持清单。配套官方文档:《Exporting and importing blocks in SIMATIC SD format》与《Representation of LAD program code in SIMATIC SD format》。
LAD 的 SimaticML 命名空间(⚠️ 随版本变化,必须以本机导出模板为准):
| 来源 | 命名空间 |
|---|---|
| gitcode 篇(V18) | http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v1 |
| 博客园 Aifen 篇(V18) | http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4 |
⚠️ 两篇都标注 V18 却给出 v1 与 v4 两个不同的命名空间,说明命名空间确实随版本变化。务必以本机 TIA 导出的 XML 为准,不要硬编码。 对应报错是
Missing 'Namespace' identifier attribute。
来源:本文综合自 raw 目录下的《西门子官方手册》(5.11.3.17~5.11.3.20、5.1.4 Object list)、《中文社区实战》(PLC技术网概述篇、博客园机器视觉篇)、《英文技术博客》(Exporting and Importing Function Blocks、Project Verification)、《进阶专题》(SimaticML 数据交换、SIMATIC SD 格式)等抓取结果。
4.2 为什么 Openness 不能逐行编辑 LAD/FBD 逻辑
4.2.1 官方立场
官方手册与官方社区口径完全一致:Openness 不能直接逐行编辑块内程序逻辑(LAD/FBD 网络、STL 语句)。这是能力硬边界,不是缺陷或未实现功能。
西门子不提供直接修改已编译块的方法,唯一官方路径是「生成 SCL 源码 → 导入外部源 → 编译生成块」。
4.2.2 架构性原因(为什么必然如此)
| 原因 | 说明 |
|---|---|
| 1. 对象模型的粒度是”块”,不是”网络/语句” | 官方对象模型里 PlcBlock 是叶子节点,其下不暴露网络集合或语句集合。Openness 的 GetAttributeInfos() 也拿不到网络级属性——因为根本没有这个层级的对象 |
| 2. LAD/FBD 是图形对象模型,不是文本 | LAD 网络里的触点、线圈、连线是带坐标的图形对象集合,序列化后落进 SimaticML 的 <FlgNet> 节点。要”逐行编辑”等于要实现一个图形编辑器 |
| 3. 编译器不是 Openness 的一部分 | 社区的核心认知正确:Openness 不是独立编译器,必须依托 TIA Portal 实例,通过 IPC 控制 TIA Portal。从 C# 源码生成代码不被支持 |
| 4. TIA Portal 不支持真正的 Global Replace | TIA Portal 有全局 Find(Ctrl+Shift+F,只读)但没有跨 FC/FB/DB 的真正 Global Replace;Ctrl+H 只在当前块内生效。这个限制在 V11–V19 持续存在,Siemens 至 V19 未补 |
| 5. Rewire 工具能力有限 | Edit → Rewire(Ctrl+Shift+R)只作用于 I/Q/M 地址与 T/C(timer/counter 不推荐);对 DB tag、UDT 成员、数组索引完全无效 |
对比参考(AB 生态):
| 能力 | TIA Portal | RSLogix |
|---|---|---|
| Global Replace | No | Yes |
| Wrap(回绕)开关 | No | Yes |
| DB tag 交叉引用重命名 | No | Yes |
4.2.3 那 LAD 到底怎么改?三条现实路径
| 路径 | 做法 | 评价 |
|---|---|---|
| ① 官方推荐 | 导出原生可编译块 XML → 只改 <NetworkSource> 节点注入 <FlgNet> → 重新导入并编译 | 唯一可行路径 |
| ② 转 SCL 再生成 | LAD 逻辑先转成 SCL(手工或靠 Simatic ML / SIMATIC SD),走外部源路线 | 重写逻辑,风险高 |
| ③ 手工在 TIA 里改 | 导出 → 外部编辑文本 → 导回 → 在 TIA 中手工改图形 | 少量改动最实际 |
LAD 生成的实测坑位速查(来自 V18 实战篇的 8 条踩坑根因表):
| 现象 | 根因 | 解法 |
|---|---|---|
TypeLoadException 找不到 ProjectBase | 加载了低版本 DLL(V15.1) | 启用 AssemblyResolve 精准匹配目标版本 |
not supported in online mode | 设备在线 | OnlineProvider.GoOffline() |
梯形图触点显示 ??? | 变量未提前创建 / XML 引用异常 | 先建 PLC 变量表与变量 |
Inconsistent blocks cannot be exported | 块从未编译,状态不一致 | 导出前先 Compile() |
Missing 'Namespace' identifier attribute | 手写 XML 缺目标版本的命名空间 | 基于本机原生导出 XML 修改 |
导出 XML 无 <FlgNet> 节点 | 原块网络为空 | 手动注入 FlgNet |
Attach() 后程序阻塞 | 博图未授权 | 切到博图点「是」 |
| 控制台中文乱码 | 代码页不匹配 | 设 UTF-8(不影响功能) |
LAD 生成的骨架代码(实测可用思路):
// 前置:变量必须先存在,否则触点显示 ???
PlcTagTable tagTable = plc.TagTableGroup.TagTables.Find("OpennessTags") as PlcTagTable
?? plc.TagTableGroup.TagTables.Create("OpennessTags");
if (tagTable.Tags.Find("M0_0") == null) tagTable.Tags.Create("M0_0", "Bool", "%M0.0");
// 块未编译无法导出 XML——先编译
PlcBlock mainBlock = plc.BlockGroup.Blocks.Find("Main") as PlcBlock;
if (!mainBlock.IsConsistent) mainBlock.GetService<ICompilable>().Compile();
mainBlock.Export(new FileInfo(exportPath), ExportOptions.WithDefaults);
// 只改 <NetworkSource> 节点,注入 FlgNet
XDocument xmlDoc = XDocument.Load(exportPath);
XElement netSource = xmlDoc.Descendants().First(e => e.Name.LocalName == "NetworkSource");
XNamespace flgNs = "http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4";
netSource.RemoveNodes();
netSource.Add(new XElement(flgNs + "FlgNet", /* Parts + Wires */));
xmlDoc.Save(modifyPath);
plc.BlockGroup.Blocks.Import(new FileInfo(modifyPath), ImportOptions.Override);
常开触点的做法:删除 <Negated Name="operand"/>(示例模板里带 <Negated> 的是常闭触点)。
导入顺序坑(极实用):先导入的程序若其引用的 DB 还没导入会报错——加 SWImportOptions.IgnoreMissingReferencedObjects 忽略:
(destination as PlcBlockGroup).Blocks.Import(
fileInfo, importOption, SWImportOptions.IgnoreMissingReferencedObjects);
SimaticML 建模 API 的对象层次(第三方库
Parozzz/TiaUtilities,38★,GPL-3.0,2026-10 仍活跃):BlockFC/BlockFB/BlockDB/BlockInstanceDB/BlockUDT,通过AttributeList设置块名/编号/编程语言。变量必须先选 Section 才能创建,组合规则严格:
INPUT/OUTPUT/INOUT→ 用于BlockFC与BlockFBSTATIC→ 用于BlockFB/BlockDB/BlockInstanceDBTEMP/CONSTANT/RETURN→ 用于BlockFC与BlockFBNONE→ 用于BlockUDT段内元素抽象为
Part,分ContactPart(触点)、CoilPart(线圈)、Block 调用三类,用segment.Powerrail & ... & coil1的运算符重载搭逻辑表达式。该库代码注释明写一个坑:C# 的&优先级高于|,不加括号逻辑会错。 支持安全梯形图SimaticProgrammingLanguage.SAFE_LADDER。
来源:本文综合自 raw 目录下的《西门子官方手册》、《中文社区实战》(PLC技术网概述篇、gooderp 篇、深度开发篇、博客园机器视觉篇)、《英文技术博客》(Global Find and Replace、Project Bases FAQ)、《GitHub 开源》(
Parozzz/TiaUtilities、Maroder1/TIA-openness)、《进阶专题》等抓取结果。
4.3 ExternalSource 完整工作流
4.3.1 官方 API 签名
// ① 创建外部源(从文件)
PlcExternalSource plcExternalSource =
plcSoftware.ExternalSourceGroup.ExternalSources.CreateFromFile("SomeBlockNameHere", "SomePathHere");
// ② 生成源文件(从块反向导出)
PlcExternalSourceSystemGroup.GenerateSource(IEnumerable<IGenerateSource> plcBlocks,
FileInfo sourceFile, GenerateOptions generateOptions);
// ③ 从源生成块(正向生成)
externalSource.GenerateBlocksFromSource();
IList<IEngineeringObject> generatedObjects =
externalSource.GenerateBlocksFromSource(GenerateBlockOptions.KeepOnError);
外部源支持的文件格式(官方明确列举,仅 4 种):
| 格式 | 扩展名 | 说明 |
|---|---|---|
| STL | *.AWL | — |
| SCL | *.SCL | — |
| DB | *.DB | 用户数据类型专用 |
| UDT | *.UDT | 仅 *.udt 扩展名用于用户数据类型 |
官方注意事项:
- 访问「外部源文件」文件夹中的分组不被支持;
- 指定
*.AWL/*.SCL/*.DB/*.UDT之外的扩展名会抛异常;- 外部源文件必须是 ASCII 格式;社区补充:必须是 ASCII/UTF-8 文本。
- 前置条件:PLC 不在线(
GenerateBlocksFromSource与GenerateSource都要求 PLC is not online)。
4.3.2 GenerateOptions 语义(导出方向)
| 取值 | 语义 |
|---|---|
GenerateOptions.None | 只从提供的块生成源 |
GenerateOptions.WithDependencies | 生成源并包含所有依赖对象(被调用的块、引用的 DB 或 UDT) |
官方示例:
var blocks = new List<PlcBlock>(){ block1 };
var fileInfo = new FileInfo(@"C:\temp\SomePathHere.scl");
PlcExternalSourceSystemGroup systemGroup = ...;
systemGroup.GenerateSource(blocks, fileInfo, GenerateOptions.WithDependencies);
// 导出所有块及其全部依赖(如被调用的块、用到的 DB 或 UDT)为 ASCII 文本
// 或者导出数据类型
var types = new List<PlcType>(){ udt1 };
var fileInfo2 = new FileInfo(@"C:\temp\SomePathHere.udt");
systemGroup.GenerateSource(types, fileInfo2, GenerateOptions.WithDependencies);
导出方向的限制与异常条件:
| 条件 | 行为 |
|---|---|
| 编程语言不是 STL 或 SCL | 抛异常(LAD/FBD/GRAPH 不能导出为源) |
| 目标位置已存在同名文件 | 抛异常 |
DB 的扩展名不是 *.db | 抛异常 |
STL 块的扩展名不是 *.awl | 抛异常 |
SCL 块的扩展名不是 *.scl | 抛异常 |
IGenerateSource 接口:标识一个对象可以作为源被生成。
⚠️ 社区大量代码用了
GenerateOptions.Without(Python 教程:from Siemens.Engineering.SW.Blocks import GenerateOptions后GenerateOptions.Without)。⚠️ 官方 V19 手册只列出None与WithDependencies两个值,Without未见记载。请核实。
4.3.3 GenerateBlockOptions 语义(生成方向,官方 6 条原文)
| 调用 | 结果 |
|---|---|
GenerateBlocksFromSource(GenerateBlockOptions.KeepOnError) | ① 无论生成结果如何都不抛可恢复异常 ② 不返回任何块的生成结果信息 ③ 返回包含已生成块的 IList<IEngineeringObjects>(含带生成错误的块) |
GenerateBlocksFromSource(GenerateBlockOptions.None),生成出错 | ① 抛可恢复异常 ② 异常消息中包含生成的错误信息 ③ 删除已生成的块/UDT |
GenerateBlocksFromSource(GenerateBlockOptions.None),生成无错 | ① 不抛异常 ② 不返回生成结果信息 ③ 返回已生成块的 IList ——与 KeepOnError 行为相同 |
其他关键语义:
- 已存在的块会被覆盖;
- 出错时工程会回滚到调用
GenerateBlocksFromSource之前的状态; - 错误消息长度:V18 之前只包含每条错误消息的前 256 个字符;自 V18 起返回前 4096 个字符;
- 典型失败原因:引用不存在的类型、缺失的符号。
try
{
IList<IEngineeringObject> generatedObjects =
externalSource.GenerateBlocksFromSource(GenerateBlockOptions.KeepOnError);
foreach (IEngineeringObject obj in generatedObjects)
Console.WriteLine($"已生成:{obj.Name}");
}
catch (EngineeringException ex)
{
Console.WriteLine($"生成失败:{ex.Message}");
}
4.3.4 重复生成 / 覆盖策略
问题:反复执行同一段代码时,同名块会冲突。
官方语义:已存在的块会被覆盖(GenerateBlocksFromSource)。但社区实战发现同名外部源会先冲突,必须先删。
社区标准做法(生成前先删同名外部源):
// 生成前先删同名源,避免重复报错
var oldSource = plc.ExternalSourceGroup.ExternalSources.Find(blockName);
oldSource?.Delete();
PlcExternalSource source = plc.ExternalSourceGroup.ExternalSources
.CreateFromFile(blockName, sclFile);
var result = source.GenerateBlocksFromSource(GenerateBlockOptions.KeepOnError);
project.Save();
四种覆盖策略对比:
| 策略 | 做法 | 适用 |
|---|---|---|
| A. 删源重建(推荐) | ExternalSources.Find(name)?.Delete() 后 CreateFromFile | 幂等重跑,最干净 |
| B. 直接生成覆盖块 | 不删源,直接 GenerateBlocksFromSource | 依赖官方”已存在块会被覆盖”语义 |
| C. 先删块再生源 | BlockGroup.Blocks.Find(name)?.Delete() 后生成 | 避免同名块残留导致的类型不匹配 |
| D. 手工覆盖 | TIA UI「从源生成块」时选”是”覆盖 | 验证基线 |
官方手工流程的对应提示:右键源文件 →「从源生成块」→ 若存在同名块会提示是否覆盖;同名块会自动覆盖。
4.3.5 完整工作流代码(幂等版)
/// <summary>外部源工作流:写 SCL → 生成块,可重复执行。</summary>
public static class ExternalSourceHelper
{
/// <summary>以 UTF-8 BOM 写出 SCL 文件(TIA Portal 对 BOM 兼容最好)。</summary>
public static void WriteSclFile(string sclPath, string sclContent)
=> File.WriteAllText(sclPath, sclContent, new UTF8Encoding(true));
/// <summary>
/// 生成块。策略 A:先删同名外部源,再创建并生成。
/// 前置条件(官方 5.11.3.19):PLC 必须不在线。
/// </summary>
public static IList<IEngineeringObject> GenerateBlocksSafe(
PlcSoftware plc, DeviceItem plcItem, string sourceName, string sclPath)
{
// ① 强制离线
OnlineProvider? online = plcItem.GetService<OnlineProvider>();
if (online != null && online.State != OnlineState.Offline)
{ Console.WriteLine("PLC 在线,先切离线…"); online.GoOffline(); }
// ② 策略 A:删同名外部源(重复生成/覆盖策略)
plc.ExternalSourceGroup.ExternalSources.Find(sourceName)?.Delete();
// ③ 创建外部源(支持格式仅 .scl/.awl/.db/.udt,其他扩展名抛异常)
PlcExternalSource source =
plc.ExternalSourceGroup.ExternalSources.CreateFromFile(sourceName, sclPath);
// ④ 生成(KeepOnError:不抛异常,返回含错误块的列表)
return source.GenerateBlocksFromSource(GenerateBlockOptions.KeepOnError);
}
}
反向:把现有块导出为 SCL 文本(进 Git、可 review)
public static void ExportScl(PlcSoftware plc, IEnumerable<PlcBlock> blocks,
string outSclPath, bool withDependencies = true)
{
// ★ 官方限制:目标位置不能有同名文件,否则抛异常
var target = new FileInfo(outSclPath);
if (target.Exists) target.Delete();
plc.ExternalSourceGroup.GenerateSource(
blocks, target,
withDependencies ? GenerateOptions.WithDependencies : GenerateOptions.None);
}
⚠️ 另一条社区写法需核实(gooderp 篇):
ExternalSourceService.CreateExternalSource(plcSoftware.BlockGroup, "AutoGenerated.scl")+CreateTextStream()+CompilerService.Compile(new[]{extSource})。⚠️ExternalSourceService、CreateExternalSource、CreateTextStream()、CompilerService.Compile(IEnumerable<>)均未在官方 V19 手册的确证片段中出现(官方章节是CreateFromFile+GenerateBlocksFromSource)。社区同时记录了两个必备条件:名称必须带.scl后缀、调用Flush()。⚠️ gooderp 篇的Projects.Open(@"…ap17")用 string 而非FileInfo,与官方签名不符。
来源:本文综合自 raw 目录下的《西门子官方手册》(5.11.3.17~5.11.3.20)、《中文社区实战》(gitcode 篇、gooderp 篇、深度开发篇)、《英文技术博客》(Python 教程)、《GitHub 开源》(官方 snippets
WithExtensions.Step7/ProgramBlocksSnippets.cs)等抓取结果。
4.4 SCL 源文件语法要点
4.4.1 文件头四件套(缺一不可)
{Attribute 'pack_mode' := '0'} ← ① pack_mode 属性
TITLE = "块标题" ← ② TITLE(可选但推荐)
{ S7_Optimized_Access := 'TRUE' } ← ③ 优化访问属性(★ 必须在头部)
VERSION : 1.0 ← ④ 版本号
← ⑤ 之后才是块定义
三条硬规则:
{ S7_Optimized_Access := 'TRUE' }必须紧跟块头之后——否则报S7_Optimized_Access attribute missing;- 属性与
VERSION必须在源文件头,块定义之前; {Attribute 'pack_mode' := '0'}、TITLE、{ S7_Optimized_Access := 'TRUE' }、VERSION : 0.1必须保留,否则导入失败。
4.4.2 # 前缀要求
所有变量引用、数组索引、FOR 循环变量都要加 # 前缀。
// ❌ Step 7 V5.5 写法(TIA 中无效)
wTemp := WS[I].var1;
FOR I := 1 TO 10 DO
// ✅ TIA Portal 写法
#wTemp := #WS[#I].var1;
FOR #I := 1 TO 10 DO
4.4.3 Step 7 V5.5 → TIA Portal 的迁移差异对照
| 项目 | V5.5 | TIA Portal |
|---|---|---|
| 变量引用 | wTemp := WS[I].var1; | #wTemp := #WS[#I].var1; |
| DB 头 | DATA_BLOCK "DB_example" + STRUCT...END_STRUCT | DATA_BLOCK "DB_example" + 独立行的 FB 名(取代 STRUCT/END_STRUCT) |
| 优化访问属性 | 无 | 必须加 { S7_Optimized_Access := 'FALSE' }(绝对寻址时) |
| 属性位置 | — | 必须在文件头 |
| 元信息 | — | 可嵌 AUTHOR : name / FAMILY : name |
| DB 编号 | 被接受 | ⚠️ 被静默忽略(见 4.6) |
| FOR 循环变量 | FOR I:=1 TO 10 DO | FOR #I:=1 TO 10 DO |
| 保留属性 | — | TIA 新增 NON_RETAIN 关键字(VERSION 之后单独一行) |
4.4.4 编码要求
| 项 | 要求 | 说明 |
|---|---|---|
| 编码 | ASCII 或 UTF-8 文本 | 非文本格式抛异常 |
| BOM | 部分场景需 UTF-8 BOM | 社区 SCL 生成路线明确”写文件用 UTF-8 BOM”;也有资料说 DB 源用 UTF-8 无 BOM |
| 行尾 | — | 社区提到”控制台中文乱码 → 设 UTF-8(不影响功能)” |
⚠️ BOM 的两处矛盾:外部源生成方向明确”以 UTF-8 生成源”,而 DB 导入流程写的是
.scl纯文本 UTF-8 无 BOM。建议统一用 UTF-8 with BOM,并在 TIA 中实测验证。
4.4.5 S7_Optimized_Access 语义(最重要的一个属性)
| 取值 | 语义 | 后果 |
|---|---|---|
'TRUE' | 优化访问(默认,现代做法) | DB 无固定字节偏移,不能与绝对地址混用 |
'FALSE' | 标准 / 绝对寻址 | DB 有固定字节偏移,可被 HMI/SCADA 用 DB300.DBD4 寻址 |
接口与目标 CPU 固件不匹配会在编译日志里报 Block consistency error。例如 S7-1200 FW 4.0 不支持 {S7_m_c := 'true'}。
4.4.6 其他语言限制
| 限制 | 说明 |
|---|---|
Blocks.Import 的语言无关性 | ⚠️ 只有 SCL 语言编写的 FB / FC 块才能导出源文件;LAD / FBD 无法导出为源 |
| 嵌套依赖 | 导出到工作区的程序必须是没有嵌套其他程序块的程序文件;如果导出块内包含嵌套的、未在当前程序块内的程序文件,导入到新工程会报「功能块不存在」 |
| 块类型与调用关系 | 源文件里的 BLOCK 类型与 AREA 必须和调用关系一致 |
来源:本文综合自 raw 目录下的《英文技术博客》(SCL Creating Data Blocks 篇的迁移差异、Exporting and Importing FB 篇)、《中文社区实战》(今日头条导入导出篇、CSDN 两种导入导出方法篇、西门子中国论坛 SCL 外部编程工具应用)、《GitHub 开源》(
Maroder1/TIA-openness)等抓取结果。
4.5 五种块类型的 SCL 模板
以下模板整合自 raw 资料中确证的 SCL 语法要点。使用前请在本机 TIA 中用「从块生成源」导出一份真实模板做对照。
4.5.1 FUNCTION_BLOCK(函数块)
{Attribute 'pack_mode' := '0'}
TITLE = "电机控制块"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 1.0
FUNCTION_BLOCK "FB_MotorCtrl"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 1.0
VAR_INPUT
Start : Bool; // 启动命令
Stop : Bool; // 停止命令
FaultReset : Bool; // 故障复位
END_VAR
VAR_OUTPUT
Running : Bool; // 运行反馈
FaultActive : Bool; // 故障反馈
END_VAR
VAR
State : USInt := 0; // 静态变量(多次调用间保留)
END_VAR
VAR_TEMP
tmpBool : Bool; // 临时变量(每次调用重置)
END_VAR
BEGIN
#State := 0;
IF #Stop THEN
#Running := FALSE;
ELSIF #Start THEN
#Running := TRUE;
END_IF;
IF #FaultReset THEN
#State := 0; #FaultActive := FALSE;
END_IF;
END_FUNCTION_BLOCK
变量区段含义:
VAR= 静态变量(多次调用间保留)、VAR_TEMP= 临时变量(每次调用重置)、VAR_INPUT/VAR_OUTPUT= 接口。SCL 中所有变量引用(含FOR循环变量)都必须加#前缀。
4.5.2 FUNCTION(函数)
{Attribute 'pack_mode' := '0'}
TITLE = "标定函数"
VERSION : 1.0
FUNCTION "FC_Scale" : Real
{ S7_Optimized_Access := 'TRUE' }
VERSION : 1.0
VAR_INPUT
RawValue : Int; // 原始值
RawMax : Int := 27648;
EngMax : Real := 10.0;
END_VAR
VAR_TEMP
fRatio : Real;
END_VAR
BEGIN
#fRatio := #EngMax / INT_TO_REAL(#RawMax);
#FC_Scale := INT_TO_REAL(#RawValue) * #fRatio;
END_FUNCTION
4.5.3 DATA_BLOCK(数据块)
全局 DB(标准寻址,给 HMI/SCADA 用):
{Attribute 'pack_mode' := '0'}
TITLE = "电机参数全局块"
VERSION : 1.0
DATA_BLOCK "DB_MotorParams"
{ S7_Optimized_Access := 'FALSE' } ← ★ 绝对寻址必须 FALSE
STRUCT
SpeedLimit : Real := 100.0;
AccelTime : Real := 2.0;
MotorCount : Int := 0;
END_STRUCT;
BEGIN
END_DATA_BLOCK
实例 DB(依附 FB,必须引用已存在的 FB):
{Attribute 'pack_mode' := '0'}
TITLE = "电机控制实例"
VERSION : 1.0
DATA_BLOCK "DB_MotorCtrl_Inst"
{ S7_Optimized_Access := 'TRUE' }
"FB_MotorCtrl" ← ★ FB 名放双引号、独立一行,取代 STRUCT...END_STRUCT
BEGIN
END_DATA_BLOCK
两条硬规则:
- 实例 DB 引用的 FB 必须已存在于项目中,否则报
Block "FB_name" not found; - 全局 DB 要绝对寻址必须
{ S7_Optimized_Access := 'FALSE' };优化 DB 无固定字节偏移,不能与绝对地址混用。
⚠️ DB 编号陷阱(详见 4.6):
// ⚠️【静默忽略】自 V17 Update 4 起(资料记载至 V12 → V17 Upd4 区间),
// 下面这行中的 300 会被解析但【静默忽略】,TIA 分配下一个可用编号
DATA_BLOCK "MyDB" DB 300
对 SCADA / HMI 用绝对寻址(如
DB300.DBD4)的场合,这是硬伤。 西门子热线确认截至 V17 无原生修复。
4.5.4 TYPE(用户自定义数据类型 UDT)
{Attribute 'pack_mode' := '0'}
TITLE = "设备接口结构"
VERSION : 1.0
TYPE "UDT_DeviceInterface"
VERSION : 1.0
STRUCT
iStartCmd : Bool; // 启动命令
iStopCmd : Bool; // 停止命令
iResetCmd : Bool; // 复位命令
oRunStatus : Bool; // 运行状态
oFault : Bool; // 故障
rSpeedSP : Real; // 速度设定
END_STRUCT;
END_TYPE
UDT 的 Section 规则(来自 SimaticML 建模库,与 SCL 侧对应):NONE 用于 UDT。
⚠️ 注意:
GenerateSource对数据类型要求 仅*.udt扩展名;而外部源文件的CreateFromFile也支持.udt。生成与导入的扩展名规则要分别对待。
4.5.5 ORGANIZATION_BLOCK(组织块)
{Attribute 'pack_mode' := '0'}
TITLE = "主循环 OB1"
VERSION : 1.0
ORGANIZATION_BLOCK "Main"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 1.0
VAR_TEMP
#tmpInt : Int;
END_VAR
BEGIN
REGION 初始化
#tmpInt := 0;
END_REGION
REGION 主逻辑
// 调用 FC / FB
END_REGION
END_ORGANIZATION_BLOCK
REGION / END_REGION:TIA 的 SCL 支持区域标记,导出为 SimaticML 时是独立元素(TIA-Openness-Create-Synoptic-Unified 工具处理带 REGION 的代码时需专门逻辑)。
其他 OB 类型:Startup(ORGANIZATION_BLOCK "Startup")、HardwareInterrupt、CyclicInterrupt、TimeErrorInterrupt、DiagnosticErrorInterrupt 等,命名沿用 STEP 7 传统。
4.5.6 模板批量生成器(可直接复用)
/// <summary>SCL 源文件模板生成器。所有属性集中在此,便于适配不同 TIA 版本。</summary>
public static class SclTemplates
{
// ★ 版本相关常量:本机实测后调整
public const string PackMode = "{Attribute 'pack_mode' := '0'}";
public const string Optimized = "{ S7_Optimized_Access := 'TRUE' }";
public const string Standard = "{ S7_Optimized_Access := 'FALSE' }";
public const string Version = "VERSION : 1.0";
public static string FunctionBlock(string name, string title) => $@"{PackMode}
TITLE = ""{title}""
{Optimized}
{Version}
FUNCTION_BLOCK ""{name}""
{Optimized}
{Version}
VAR_INPUT
Start : Bool;
END_VAR
VAR_OUTPUT
Running : Bool;
END_VAR
BEGIN
#Running := #Start;
END_FUNCTION_BLOCK
";
函数模板(同一类内追加):
public static string Function(string name, string title) => $@"{PackMode}
TITLE = ""{title}""
{Version}
FUNCTION ""{name}"" : Bool
{Optimized}
{Version}
VAR_INPUT
InValue : Bool;
END_VAR
BEGIN
#FC_ := #InValue;
END_FUNCTION
";
全局 DB 模板(绝对寻址,供 HMI/SCADA 用 DB300.DBD4 访问)与实例 DB 模板:
// 全局 DB:绝对寻址(Standard),供 HMI/SCADA 用 DB300.DBD4 访问
public static string GlobalDb(string name, string title) => $@"{PackMode}
TITLE = ""{title}""
{Version}
DATA_BLOCK ""{name}""
{Standard}
{Version}
STRUCT
Value1 : Real := 0.0;
Value2 : Int := 0;
END_STRUCT;
BEGIN
END_DATA_BLOCK
";
// 实例 DB:FB 名放双引号、独立一行;FB 必须已存在于项目中
public static string InstanceDb(string dbName, string fbName) => $@"{PackMode}
{Version}
DATA_BLOCK ""{dbName}""
{Optimized}
""{fbName}""
BEGIN
END_DATA_BLOCK
";
UDT 模板与 OB 模板:
public static string Udt(string name, string title) => $@"{PackMode}
TITLE = ""{title}""
{Version}
TYPE ""{name}""
{Version}
STRUCT
iCmd : Bool;
oStat : Bool;
rVal : Real;
END_STRUCT;
END_TYPE
";
OB 模板(含可选 REGION 段):
public static string Ob(string name, string title, bool withRegion) => $@"{PackMode}
TITLE = ""{title}""
{Version}
ORGANIZATION_BLOCK ""{name}""
{Optimized}
{Version}
VAR_TEMP
#tmpInt : Int;
END_VAR
BEGIN
{(withRegion ? "REGION 主逻辑\n #tmpInt := 0;\nEND_REGION" : "#tmpInt := 0;")}
END_ORGANIZATION_BLOCK
";
}
⚠️ C# 字符串插值注意:模板里用了
""表示 C# 中的引号"。SCL 里引号是",所以 C# 源码中写""{name}""。生成后请务必在 TIA 中打开验证。为什么把模板拆成多个代码块:为满足”代码块不超过 30 行”的排版约束。实际使用时把各段合并进同一个
SclTemplates类即可(从第二段起省略了using与类声明)。
来源:本文综合自 raw 目录下的《英文技术博客》(SCL Creating Data Blocks 篇的 DB/实例 DB 语法)、《中文社区实战》(西门子中国论坛 AI 导入 SCL 篇、今日头条导入导出篇)、《GitHub 开源》(
Parozzz/TiaUtilities的 Section 规则)等抓取结果。
4.6 DB 编号不能硬编码(关键坑)
4.6.1 问题
已确认限制(V12 → V17 Upd4):在外部 SCL 源里写
DATA_BLOCK "MyDB" DB 300
会被解析但静默忽略,TIA 分配下一个可用编号。
这对任何用绝对寻址的 SCADA / HMI(如 DB300.DBD4)是硬伤。 西门子热线确认截至 V17 无原生修复。
4.6.2 DB 重编号的四条路
| # | 路径 | 做法 | 评价 |
|---|---|---|---|
| ① | TIA Portal Add-in(DB Renumberer) | .addin 放 %ProgramData%\Siemens\Automation\Portal V<ver>\Addins\,重启后 Extras → Addins 出现 | 交互式可用 |
| ② | TIA Openness API(推荐) | block.SetAttribute("Number", targetNumber),需 TIA V15+(SIOS 109798623) | 脚本化首选 |
| ③ | GUI 手工 | Properties → General 改号(交叉引用自动更新) | 少量可行 |
| ④ | 永久解法 | SCADA 改符号寻址(OPC UA 或 TIA 符号导出) | 唯一根治 |
4.6.3 官方 API:AutoNumber + Number
官方示例(5.11.3.11):
PlcBlockGroup blockFolder = YourUtilities.GetFolder();
var block = blockFolder.Blocks.Find("Block_1");
if ((bool)block.GetAttribute("AutoNumber") == true)
{
block.SetAttribute("AutoNumber", false); // ★ 必须先关闭自动编号
}
block.SetAttribute("Number", 2);
// 重编号后必须重新编译
⚠️
SetAttribute("Number", …)前必须先把AutoNumber设为false,否则改了不生效。 这是官方示例里明确的前置步骤。
批量改块号(可复用代码):
/// <summary>把匹配 oldName 的块重编为 newNumber。需 TIA Portal V15+。</summary>
public static void Renumber(PlcSoftware plc, string oldName, int newNumber)
{
PlcBlock block = plc.BlockGroup.Blocks.Find(oldName);
if (block == null) { Console.WriteLine($"未找到块 {oldName}"); return; }
// ★ 前置:关闭自动编号,否则 Number 写入无效
if ((bool)block.GetAttribute("AutoNumber"))
block.SetAttribute("AutoNumber", false);
block.SetAttribute("Number", newNumber);
// 重编号后必须重新编译才生效
ICompilable? compilable = block.GetService<ICompilable>();
if (compilable is not null)
{
CompilerResult r = compilable.Compile();
Console.WriteLine($"{oldName} → DB{newNumber},ErrorCount={r.ErrorCount}");
}
}
/// <summary>按名称→块号映射批量重编。</summary>
public static void RenumberBatch(PlcSoftware plc, IDictionary<string, int> map)
{
foreach (var kv in map) Renumber(plc, kv.Key, kv.Value);
}
配套工具:导出块号现状 + 拼写兜底
/// <summary>导出所有块号现状,供人工核对(写 Git 友好格式)。</summary>
public static List<string> DumpBlockNumbers(PlcSoftware plc) =>
plc.BlockGroup.Blocks
.Select(b => new {
Number = b.GetAttribute("Number")?.ToString() ?? "-",
Name = b.Name,
Language = b.ProgrammingLanguage.ToString(),
KnowHow = IsKnowHowProtected(b)
})
.OrderBy(x => x.Number)
.Select(x => $"{x.Number}\t{x.Name}\t{x.Language}\tKnowHow={x.KnowHow}")
.ToList();
/// <summary>⚠️ 拼写需核实:官方片段写 IsKnowHowProtection,另有资料写 IsKnowHowProtected。</summary>
private static bool IsKnowHowProtected(PlcBlock b)
{
var pi = typeof(PlcBlock).GetProperty("IsKnowHowProtected")
?? typeof(PlcBlock).GetProperty("IsKnowHowProtection");
return pi?.GetValue(b) as bool? ?? false;
}
官方示例原文(DB 重编号):
// Requires: Siemens.Engineering.dll, TIA Portal V15+ TiaPortal tia = new TiaPortal(TiaPortalMode.WithUserInterface); Project project = tia.Projects.Open(new FileInfo(@"C:\Projects\MyProject.ap17")); foreach (PlcBlock block in blockGroup.Blocks) if (block.Name == "MBC_dev03_test_Write") block.SetAttribute("Number", 8449); // 重编号后必须重新编译 project.Save();
4.6.4 批量设置属性的官方机制:SetAttributes
官方示例(5.11.3.11):
PlcBlock block = SelectBlock("MC-Servo");
if (block != null)
{
IList<KeyValuePair<string, object>> list = new List<KeyValuePair<string, object>>()
{
new KeyValuePair<string, object>("DataExchangeMode", OBDataExchangeMode.Synchronous),
new KeyValuePair<string, object>("SynchronousApplicationCycleTime", (float)69)
};
try { block.SetAttributes(list); }
catch (EngineeringException e) { Console.WriteLine("Exception: " + e.Message); }
}
四条参数检查规则:
- 初始检查(在第一个条目被处理前,由全局 PE 基础设施完成):检查输入条目的属性名相同性与有效性、数据类型不匹配。同一属性不能被一个
SetAttributes设置两次; - 顺序检查在运行期间进行,处理顺序与输入列表元素顺序相同;
SetAttributes只在一个事务中操作——若输入列表中任一属性无法设置,所有属性值保持不变;- 属性可以相互依赖。若某参数依赖列表中前面的参数,则按前面参数的当前值(而非调用时的原始值)检查和求值。
错误消息会包含第一个无法设置的属性名、其值及原因。容错版(V18 新增) 见 1.7.3 节的 SetAttributes(list, errorHandler)。
4.6.5 导出属性表(官方)
| 类别 | 属性 |
|---|---|
| 始终导出 | Name、Number(1..65535)、ProgrammingLanguage(LAD / FBD / SCL / STL / GRAPH)、Interface(VAR_INPUT、VAR_OUTPUT、VAR_IN_OUT、VAR_TEMP、VAR STATIC)、代码体、Know-How-Protection hash、实例 DB 引用 |
| 仅当源块定义时才导出 | Author / Family / Title / Version(靠 {Attribute …} 指令携带) |
来源:本文综合自 raw 目录下的《西门子官方手册》(5.11.3.11 Accessing attributes of all blocks)、《英文技术博客》(SCL Creating Data Blocks 篇的 DB 重编号四条路,引用 SIOS 109798623)、《GitHub 开源》(官方 snippets ProgramBlocksSnippets.cs)等抓取结果。
4.7 块保护与 Know-how 保护
4.7.1 三个密码体系(不要混淆)
| 体系 | 作用对象 | API |
|---|---|---|
| Know-how 保护(块保护) | 单个程序块的逻辑内容(源/逻辑被加密,人不可读) | PlcBlockProtectionProvider |
| UMAC 保护 | 工程 / 设备的功能权限(谁能下载、谁能编辑) | UmacConfigurator / UmacDevice |
| PLC 主密码 | CPU 级的访问保护 | PlcMasterSecretConfigurator |
4.7.2 Know-how 保护的加解密
// 加保护:必须先过滤非法字符
var provider = plc.BlockGroup.Blocks
.First(b => b.Name == "Axis_blue")
.GetService<PlcBlockProtectionProvider>();
var invalidChars = provider.GetInvalidPasswordCharacters().ToList();
var pwd = new SecureString();
foreach (var c in "@ValidPassword123") pwd.AppendChar(c); // 跳过 invalidChars
pwd.MakeReadOnly();
provider.Protect(pwd);
密码规则(官方 snippets 的两处独立代码都写了一遍,可确认是硬性要求):
| 规则 | 值 |
|---|---|
| 最小长度 | ≥ 8 |
| 最大长度 | ≤ 120 |
| 必须包含 | 至少各 1 个数字、小写字母、大写字母、特殊字符 |
⚠️ V19 起强制严格口令策略:官方原文 —— “With TIA Portal V19, the Strict password policies are applied while setting a password. This also applies to TIA Portal Openness calls, especially setting know-how protection. An exception will be thrown if the password does not follow the policy.” 即密码不合规会抛异常,不是静默失败。
4.7.3 Know-how 保护块的硬边界(导入他人工程的头号坑)
// 官方片段的实测写法:导入时必须过滤 KnowHow 保护块
var blocksToImport = _blockComposition
.Where(b => editedBlockNames.Contains(b.Name) && !b.IsKnowHowProtected)
.ToList();
Parallel.ForEach(blocksToImport, plcBlock =>
{
_blockComposition.Import(new FileInfo(Path.Combine(importFolderPath, plcBlock.Name)),
ImportOptions.Override);
editedBlockNames.Remove(plcBlock.Name);
});
⚠️ 不加
&& !IsKnowHowProtected会因为无法读取保护块而失败。这是导入别人项目时最常踩的坑。
导出方向也要过滤:
var exportableBlocks = software.BlockGroup.AllFBs().Where(b =>
!IsKnowHowProtected(b) &&
b.ProgrammingLanguage is ProgrammingLanguage.SCL or ProgrammingLanguage.STL);
software.ExternalSourceGroup.GenerateSource(exportableBlocks, fileInfo, GenerateOptions.None);
4.7.4 Know-how 密码管理的安全实践
| 实践 | 说明 |
|---|---|
| 无密码导出的 KWP 块,导入后连原作者都打不开 | 必须集中管理密码 |
| KWP 密码集中到项目级 secret manager | 不要硬编码 |
| 禁止在同版本两个实例间拖拽块 | 会导致块号重复、KWP 密码丢失、实例 DB 不一致;改用引用项目(Ctrl+9) |
| Add-in 安全实践 | .txt 改名绕过邮件过滤器时要先审源码 / 杀毒扫描;Add-in 与 TIA 同权限运行 |
4.7.5 UMAC 权限配置
using Siemens.Engineering.Umac;
// ① 项目级 UMAC
var umacConfigurator = project.GetService<UmacConfigurator>();
var adminRole = umacConfigurator.CustomRoles.Create("Admin", "Admin Role for MCS Web Server Application");
// ② 设备级 UMAC(新建 UMAC 设备示例:CPU 1516-3 UP/1 DN 型)
var umacDevice = project.Devices
.CreateWithItem("OrderNumber:6ES7 518-4UP00-0AB0/V3.1", "", "")
.GetService<UmacDevice>();
var rights = umacDevice.AvailableDeviceFunctionRights;
adminRole.AssignDeviceFunctionRight(umacDevice, rights.First());
// ③ 建用户(密码规则与 KWP 一致)
umacConfigurator.ProjectUsers.Create("Admin", GetSecureString("AdminAdmin123"));
// ④ 保护状态判别:为空即未保护
if (umacConfigurator.EngineeringFunctionRights.Any())
Console.WriteLine("Project is protected");
// ⑤ PLC 主密码
var cpu = plc.DeviceItems.Single(x => x.Classification == DeviceItemClassifications.CPU);
cpu.GetService<PlcMasterSecretConfigurator>().Protect(GetSecureString("Test12345!"));
命名空间:Siemens.Engineering.Umac 与 Siemens.Engineering.HW / HW.Features 需同时 using。
V19 的 UMAC 行为变更:
- V19 起 Centralized UMAC 功能激活,但 WebServerUserManagement 对所有 PLC 固件 V3.1 及以上被禁用(直到 V18 都是 WebServerUserManagement 可用而 Centralized UMAC 未激活,限固件 V3.0 及以下);
- 在线认证调用:用 ≤ V18 的 Openness API 时在线认证仍可通过 download / upload 配置进行,但仅当 PLC 受 legacy
ProtectionLevel认证保护;用 ≥ V19 的 API 时所有认证调用(尤其新的 UMAC on PLC)都会送到ConnectionConfiguration类的在线认证事件处理器——若用户代码未处理OnlineAuthenticationConfiguration类型,会第二次回调到该功能的回调方法,但那里只能处理到 V18 的旧保护机制。
UMAC on PLC 的工程化价值:配合 tiaPortal.Authentication 事件(V17+)可在打开受保护工程时以程序方式提供认证方式,替代传凭据的 UmacDelegate。
打开 UMAC 保护工程的官方 API:Projects.Open 的重载接受 UmacDelegate 参数;UmacCredentials 含 Name(string)、Type(UmacUserType) 两个属性与 SetPassword(SecureString) 方法;UmacUserType.Project 表示 project 作用域的 UMAC,UmacUserType.Global 表示应用作用域(由 UMAC 服务器控制)。
4.7.6 安全红线(不可逾越)
官方明令禁止:
- 禁止用 TIA Portal Openness API 执行安全系统的检查或生成验收/认可数据。安全验收只能用 STEP 7 Safety 附加包的安全打印(safety printout)或功能测试完成,Openness API 不是替代品。
- SIL/PL 安全相关逻辑不要用 Openness 生成 F-block——手工在 TIA 里写,Openness 只做非安全外围。
来源:本文综合自 raw 目录下的《西门子官方手册》(5.1.2 安全说明、5.3.1 UMAC、7.1 Major Changes UMAC 章、V15.1 手册安全红线)、《GitHub 开源》(官方 snippets ProgramBlocksSnippets.cs / SecuritySnippets.cs / WithExtensions ProgramBlocksSnippets.cs)、《英文技术博客》(Exporting and Importing FB 篇)、《中文社区实战》等抓取结果。
4.8 类型化库与版本兼容性
4.8.1 三种共享 FB 的方法
| 方法 | 适用 | 代价 |
|---|---|---|
| 类型化全局库(typed global library) | 跨项目/跨机队共享 FB | 类型版本化;接口漂移会在编译期而非运行期暴露 |
| 外部 SCL 源文件 | 需要 Git review 的代码 | 只能 SCL/STL;无版本化类型概念 |
| 两个 TIA 实例间拖拽 | 单机临时 | ❌ 禁止——块号重复、KWP 密码丢失、实例 DB 不一致 |
另有:引用项目(Ctrl+9)、SIMATIC Automation Compare Tool。
4.8.2 类型化库的核心价值
“类型版本化,接口漂移会在编译期而非运行期暴露”——这是类型化库相对 SCL 文本最大的优势。
LibraryComposition 的层级(官方对象模型):
GlobalLibrary / ProjectLibrary
└── MasterCopies ── MasterCopiesSystemFolder
├── MasterCopiesSystemFolder (abstract)
│ └── Folder → LibraryTypeFolder → LibraryType → LibraryTypeVersion
└── MasterCopiesUserFolder (abstract)
└── Folder → LibraryTypeFolder → LibraryType → LibraryTypeVersion
LibraryType 具 Abstract 抽象属性;可访问 Types / Versions / MasterCopies
创建全局库(官方片段):
using Siemens.Engineering.Library;
DirectoryInfo destination = new(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()));
// 泛型参数 UserGlobalLibrary 决定库类型(用户库 vs 系统库)
var globalLibrary = tiaPortal.GlobalLibraries.Create<UserGlobalLibrary>(destination, "newLibrary");
Console.WriteLine($"Global library could be created: {globalLibrary != null}");
⚠️
GlobalLibraries挂在 TiaPortal 实例上而非 Project 上——这是最容易拿错引用来源的地方。 ⚠️ 泛型参数传错类型会失败(UserGlobalLibraryvs 系统库类型)。
4.8.3 版本兼容性检查(⚠️ typed library 的隐性坑)
typed library 文件不记录目标固件版本。 社区明确警告:
“为每个块记录 TIA Portal 与 CPU 固件版本(typed library 文件不记录目标固件,V18 上能编的块在 V16 SP1 上可能因 attribute 变化失败)”
具体表现:改 S7_Optimized_Access 或 VERSION 后需重新编译才生效;接口与目标 CPU 固件不匹配(例如 S7-1200 FW 4.0 不支持 {S7_m_c := 'true'})会在编译日志里报 Block consistency error。
库版本管理清单(官方 5.6.x 章节):
| 动作 | 官方章节 |
|---|---|
| 设 / 取默认版本 | 5.6.29 / 5.6.30 |
| 判断实例是否过期 | 5.6.21 Determining out-of-date type instances |
| 更新项目 | 5.6.22 Updating the project |
| 更新库 | 5.6.23 Updating a library |
| 更新库时改结构 | 5.6.26 Updating Structure during Library Update |
| 更新库类型 | 5.6.27 Updating libraries type |
| 改进类型一致性状态 | 5.6.31 Improved types consistency state |
| 清理库 | 5.6.24 Cleaning up library |
| 从项目库协调项目 | 5.6.25 Harmonize project from project library |
| 比较库 | 5.6.6 Comparing libraries |
| 归档 / 取回库 | 5.6.10 |
| 从项目库创建主副本 | 5.6.18 Create master copy from a project in library |
| 从主副本创建对象 | 5.6.19 |
跨版本共享 FB 的推荐做法:导成 typed Global Library(.al14),库存类型版本与接口指纹;同一大版本族(如 V18 ↔ V20)可打开;SCL 文本格式在 V14+ 世代内前后兼容。
4.8.4 库与版本控制的真实边界
VCI(Version Control Interface)+ Git + Automation Compare Tool 三层结构:
| 层 | 承担什么 |
|---|---|
| TIA VCI 工作区 | 把 OB/FB/FC/DB/PLC 数据类型/PLC 变量表导出为文本文件并与工作区文件关联;状态一致即表示与离线工程完全相同 |
| Git | 仓库根目录即 VCI 工作区目录(git init 产生 .git);⚠️ git checkout 在 VCI 中没有对应命令且该命令会重写工作区,属危险命令 |
SIMATIC Automation Compare Tool(Entry 109797235) | 比较 SimaticML 格式对象 |
可导出到工作区的对象仅限:OB、FB、FC、DB、PLC 数据类型、PLC 变量表。
⚠️ LAD/FBD 无法直接版本化——因为 Git 不能处理图形化语言,所以必须靠 Openness 把块转成
*.SimaticML(西门子专有 XML)才能被 Git 使用,这个转换由 VCI 自动执行。⚠️ 真正的瓶颈不是 Git,是 Automation Compare Tool:它不支持 CFC 和 SCL/Graph 编写的程序。
TIA Portal Openness 是 VCI 的前置依赖——安装 TIA 时 Openness 默认安装,VCI 依赖它做格式转换。可选插件 “VCI Git Connector / VCI Add-in”(Entry 10085470 附近)可在 TIA 内直接调 Git 的 Add/Push。
来源:本文综合自 raw 目录下的《西门子官方手册》(5.1.2 LibraryComposition、5.6.x Libraries 章节)、《英文技术博客》(Exporting and Importing FB 篇、Project Bases 篇)、《进阶专题》(版本控制:VCI 工作区 + Git + Automation Compare Tool 的三层结构、SIMATIC SD 格式)、《GitHub 开源》(官方 snippets LibrarySnippets.cs)等抓取结果。
4.9 标签表与变量
4.9.1 对象模型
PlcSoftware
└── TagTableGroup (PlcTagTableSystemGroup)
├── TagTables (PlcTagTableComposition)
│ └── PlcTagTable (PlcTagComposition)
│ ├── Tags (PlcTagComposition) ── PlcTag
│ ├── UserConstants ── PlcUserConstant
│ └── SystemConstants(只读)
└── Groups (PlcTagTableUserGroupComposition)
└── PlcTagTableUserGroup ── 可嵌套 .Groups
PlcTagTable 的属性(官方表):
| 属性 | 类型 | 访问 | 说明 |
|---|---|---|---|
IsDefault | bool | 只读 | 默认表不可删除 |
ModifiedTimeStamp | DateTime | 只读 | UTC 格式 |
Name | string | 只读 | — |
PlcTagTable 的动作:
| 动作 | 返回 | 说明 |
|---|---|---|
Delete() | void | IsDefault 为 true 时抛异常 |
Export(FileInfo) | void | 导出该变量表的 Simatic ML |
ShowInEditor() | void | 在 PLC 变量表编辑器中显示 |
PlcTag 的三个核心属性:Name(string)、DataTypeName(string)、LogicalAddress(string)。
4.9.2 创建标签表与变量(官方 API)
// 官方签名:Create 只接受表名
PlcTagTable myTable = plc.TagTableGroup.TagTables.Create("myTable");
// 创建变量(⚠️ 三种写法都出现过,见下方说明)
var newTag = myTable.Tags.Create("tag_1");
newTag.DataTypeName = "Bool";
newTag.LogicalAddress = "%I0.0";
newTag.Comment.Items["zh-CN"] = "中文注释";
// 查
var expectedTag = myTable.Tags.Find("tag_1");
// 删
newTag.Delete();
⚠️ 签名差异需核实:官方手册片段里是
tagTable.Tags.Create("tag_1")(只接受名字),随后设置DataTypeName/LogicalAddress。而社区代码写的是table.Tags.Create(name, type)(两参数)或tagTable.Tags.Create("M0_0", "Bool", "%M0.0")(三参数)。请以本机 IntelliSense 为准——最终都要设置DataTypeName与LogicalAddress。
变量表 XML 架构中的属性(官方):DataTypeName(如 Bool)、LogicalAddress(如 %I0.0)、ExternalAccessible / ExternalVisible / ExternalWritable(true)、Name、以及嵌套 MultilingualText → MultilingualTextItem(Culture = en-US,Text)。
⚠️
LogicalAddress为可选(Optional),且以国际助记符格式(international mnemonics format)导入导出。
4.9.3 递归枚举变量表分组(官方示例)
// 枚举所有 PLC 变量表用户组(含子组)—— 子文件夹按递归处理
private static void EnumeratePlcTagTableUserGroups(PlcSoftware plcSoftware)
{
foreach (PlcTagTableUserGroup g in plcSoftware.TagTableGroup.Groups)
EnumerateTagTableUserGroups(g);
}
private static void EnumerateTagTableUserGroups(PlcTagTableUserGroup group)
{
foreach (PlcTagTableUserGroup sub in group.Groups)
EnumerateTagTableUserGroups(sub); // 递归
}
4.9.4 ⚠️【关键坑】软件单元内的变量表会被漏掉
官方 snippets 揭示的真相:变量表不只在 software.TagTableGroup——每个 Unit 下还有自己的 TagTableGroup,必须 Concat 合并,否则会漏掉软件单元内的变量。
using Siemens.Engineering.SW.Units;
if (plcDevice.DeviceItems.Single(x => x.Classification == DeviceItemClassifications.CPU)
.GetService<SoftwareContainer>().Software is not PlcSoftware software)
throw new InvalidOperationException("Device does not contain any software instance (null).");
// 主块组下的变量表
var tagTables = software.TagTableGroup.AllTagTables(); // AllTagTables() 是扩展包方法
// ★ 关键:软件单元内的变量表也要抓,用 Concat 合并
var units = software.GetService<PlcUnitProvider>().UnitGroup.Units;
var unitTagTables = units.SelectMany(x => x.TagTableGroup.AllTagTables());
var allTags = tagTables.Concat(unitTagTables).SelectMany(x => x.Tags).ToList();
⚠️ 这是 V17+ 多软件单元工程的必要操作。漏掉软件单元会导致”变量明明在工程里但脚本读不到”。
4.9.5 标签表 XML 导入导出(批量路径)
为什么必须走 XML:批量建变量用 XML Import 而非逐个 Tags.Create()——千条变量逐个创建可超 10 分钟。
官方 XML 根结构:
<Document>
<Engineering version="V16" />
<SW.Tags.PlcTagTable ID="0">
<!-- 每个 ID 都必须唯一,包括 tag / comment / 多语言文本对象 -->
</SW.Tags.PlcTagTable>
</Document>
API 路径:
// 导入(PlcTagTableGroup 层)
var softwareContainer = plcItem.GetService<SoftwareContainer>();
PlcSoftware plcSoftware = (PlcSoftware)softwareContainer.Software;
PlcTagTableGroup group = plcSoftware.TagTableGroup;
group.TagTables.Import(new FileInfo(@"D:\IO MAP.xml"), ImportOptions.Override);
⚠️ 不要用
project.Devices[0].DeviceItems[1]索引——这只是 S7-1516 那个案例的索引,不要假设它对其他型号/项目结构成立。
官方片段的”导出 → 改 XML → 导回”套路(可复用代码):
// 导出 → 改 XML → 导回(批量修改变量表的通用套路)
var myTagTable = plcSoftware?.TagTableGroup.TagTables
.FirstOrDefault(x => x.Name == "DemocaseAppTagTable");
var tempPath = Path.Combine(Path.GetTempPath(), "DemocaseAppTagTable_export.xml");
myTagTable?.Export(new FileInfo(tempPath), ExportOptions.WithDefaults);
var xdoc = XDocument.Parse(File.ReadAllText(tempPath));
var plcTags = xdoc.Descendants("SW.Tags.PlcTag").ToList();
var nameElement = plcTags.FirstOrDefault()?.Element("AttributeList")?.Element("Name");
if (nameElement != null) nameElement.Value = "NewTagName";
xdoc.Save(tempPath);
plcSoftware?.TagTableGroup.TagTables?.Import(new FileInfo(tempPath), ImportOptions.Override);
⚠️ XML 元素名是关键坑点:用
xdoc.Descendants("SW.Tags.PlcTag")定位变量、Element("AttributeList")?.Element("Name")定位名称。这套命名空间前缀必须以实际导出文件为准,换版本时不要硬编码。
⚠️ Openness XML 导入的能力边界(明确界定):Openness XML 导入只配置标签定义与属性(表成员关系、数据类型、外部访问设置、逻辑地址、名字、注释),不写入/强制/改变实时 I/O 值。
4.9.6 标签与变量的其他能力
读取时间戳(官方):
PlcTagTable plcTagTable = plc.TagTableGroup.TagTables.Find("MyTagTable");
DateTime modifiedTagTableTimeStamp = plcTagTable.ModifiedTimeStamp; // UTC
系统常量 / 用户常量:
PlcUserConstantComposition plcUserConstants = tagTable.UserConstants;
PlcUserConstant plcUserConstant = plcUserConstants.Find("Constant XYZ");
// PlcSystemConstantComposition plcSystemConstants = tagTable.SystemConstants; // 只读
⚠️ 社区高频误区(会导致”变量加错表”):TagTableGroup.TagTables.Create(name) 返回的表对象与”加标签时用的对象”可能不是同一个引用(尤其在有子组的情况下)。规避:迭代 Tags 前先 TagTables.Find(name) 重新取一次。
4.9.7 多语言注释与 HMI 文本列表
问题根源:GetSymbolPath 返回的是标签名,不是多语言注释;PLC tag 注释在 Runtime 不可直接访问。
官方确认的 API 索引表(可当 API 索引用):
| 类 | 成员 |
|---|---|
TiaPortal | Projects |
Project | Devices、LanguageSettings |
LanguageSettings | ProjectLanguages、ActiveProjectLanguage |
ProjectLanguage | CultureInfo、LanguageType |
Device | DeviceItems、GetService<>() |
PlcSoftware | TagTables、BlockGroup |
PlcTagTable | Tags |
PlcTag | Name、Comment(返回 MultilingualText) |
MultilingualText | Items、Default |
HmiTarget | TextListComposition |
TextListComposition | CreateTextList、Find |
TextList | Name、Entries |
TextListEntry | Text、DefaultValue |
⚠️ 来源可靠性标注:该表在 raw 资料中同时出现了
TiaPortal.GetProcess()这一不存在的 API(官方是GetProcesses()/GetProcess(int)),且整篇被列入 raw 的《已知内容质量问题》。上表的类/成员结构与官方对象模型一致,可参考;但入口写法请按官方TiaPortal.GetProcesses().First().Attach()纠正。
多语言注释的实操写法:
// 遍历项目语言下所有 tag 的 Comment(MultilingualText),推送到 HMI 文本列表
var language = project.LanguageSettings.ActiveProjectLanguage;
foreach (PlcTagTable table in plc.TagTableGroup.TagTables)
foreach (PlcTag tag in table.Tags)
{
MultilingualText comment = tag.Comment; // → MultilingualText
MultilingualTextItem item = comment.Items.Find(language);
string text = item.Text;
// → 写入 HmiTarget.TextListComposition.CreateTextList(...) → TextList.Entries
}
语言设置的版本行为:V18 起 WinCC Unified Screen Editor 中所有 MultilingualText 项从 unformatted 改为 formatted——所有文本必须以格式化形式设置,纯文本会被拒绝并抛异常:
Language language = project.LanguageSettings.Languages.Find(new CultureInfo("en-US"));
MultilingualText toolTip = ((HmiButton)screenItem1).ToolTipText;
MultilingualTextItem item = toolTip.Items.Find(language);
item.Text = "<body><p>Modified button text from Openness</p></body>"; // ★ 必须带 HTML 标记
多语言块编译失败:Openness 默认使用项目主语言,需在 Project.LanguageSettings 设置,或源码中用 { Language := 'zh-CN' } 声明。
⚠️ WinCC Unified 日志变量的固定 Schema 缺陷(必须知道的坑):
TagTable.Export(...) 输出的 CSV/XLSX schema 固定为:Name、Path、DataType、Connection、PLC tag、Length、Initial value、Comment、Acquisition mode / Acquisition cycle、Substitute value。
schema 中根本没有 Logging、LoggingCycle、Persistence、LoggingTrigger、LimitMonitoring 列。 因此:
- 导入时 TIA Portal 无法重建原始日志配置;
- 运行时每个 HMI tag 的
LoggingEnabled标志被清空,原本在记录的变量被静默降级为不记录; - 同样缺陷存在于”从 Excel 粘贴行到 HMI 变量编辑器”的操作。
受支持的解法是 TIA Portal Openness API,需同时引用三个程序集:Siemens.Engineering.dll、Siemens.Engineering.Hmi.dll、Siemens.Engineering.HmiUnified.dll(Unified 扩展单独成程序集)。
类型链:HmiTarget → HmiSoftware → HmiTagTable(支持 Export/Import,但仅限上述 schema)→ HmiTag(Logging 属性 : HmiTagLogging)→ HmiTagLogging(LoggingMode、LoggingCycle、LoggingTrigger、Persistence、LimitMonitoring)。
⚠️ 明确的坑:不要用 UI Automation(键盘/鼠标模拟)驱动 HMI 变量编辑器——TIA 的 UI 自动化树不暴露日志列,且点击/SendKeys 约 5 tags/sec 后会触发限流并可能损坏项目数据库。
⚠️ V18 的关键限制:TIA Portal V18 的完整 Openness 安装不含
Siemens.Engineering.HmiUnified程序集,Unified HMI 工具在 V18 构建里必须隐藏——V18 只能用 Classic/Comfort HMI 家族。
4.9.8 绝对寻址 vs 优化访问的取舍
| 维度 | 优化访问(S7_Optimized_Access = 'TRUE') | 标准/绝对寻址(= 'FALSE') |
|---|---|---|
| 访问效率 | 更快(编译器可优化访问路径) | 较慢(固定字节偏移) |
| 固定字节偏移 | ❌ 无固定偏移,不能与绝对地址混用 | ✅ 有固定偏移 |
HMI / SCADA 绝对寻址(如 DB300.DBD4) | ❌ 不可用 | ✅ 可用 |
| 符号寻址 | ✅ | ✅ |
| 改结构时 | ✅ 编译器自动调整 | ❌ 绝对地址可能失效 |
| 典型用途 | 纯 PLC 内部控制数据 | 需要外部系统按固定地址访问的参数块 |
⚠️ 重要提醒:
LogicalAddress是PLC 变量表(tag table)中的 I/O 地址(如%I0.0),它本身就依赖绝对寻址概念——但给变量表里的 tag 设LogicalAddress与 DB 的S7_Optimized_Access是两个不同层面的事:前者是 I/O 映射地址,后者是 DB 内部存储方式。给变量表的 tag 设了LogicalAddress,它指向的 DB 仍可以是优化访问的(此时只能通过符号引用,不能用DB300.DBD4)。
导出属性时始终包含:Name、Number、ProgrammingLanguage、Interface、代码体、Know-How-Protection hash、实例 DB 引用。
4.9.9 CSV / Excel 通道(标签的轻量路径)
TIA Portal 的标签另有 CSV 通道,不必用 Openness:
| 方向 | 路径 |
|---|---|
| 导出 | PLC tags table → 右键 → Export(或 Export > Microsoft Excel / Export > CSV) |
| 导入 | 同一路径回导入 |
注意事项:
- 分隔符:德语区是分号,英语是逗号;
- Excel 误读时用
Data > From Text/CSV重开。
“从 Excel 清单生成 DI/DO 梯级”的推荐路径排序:
- 首选:Openness + C# 读 Excel 直接建/改块;
- 次选:逻辑写 SCL、导出、外部改、再导回;
- 最简:先用 CSV 导标签,再由 Openness 生成逻辑块。
Excel 读取技术选型:csv 用 StreamReader + 分隔符切分;xlsx 用 OpenXML(SpreadsheetDocument.Open → OpenXmlReader 流式读 → CellValues.SharedString 走 SharedStringTablePart)。原文承认 OpenXML 读写效率不高,相比 Python pandas 复杂不少。
SIMATIC ML 的一个实用技巧:短条目可在 C# 里写死替换(如 DB_Sensor.OP[C_OP10].bSignal_1 := "OP10_Sensor1" 对应约 30 行 <Access Scope="GlobalVariable"> / <Symbol> / <Component> / <Token Text=":=" /> 结构);带 IO 关联、FB 调用、REGION 的代码独立成 xml,用 StreamReader 读模板 + StreamWriter 写目标文件,不推荐 XmlWriter(层级多不好把控)。
来源:本文综合自 raw 目录下的《西门子官方手册》(5.11.5 Creating PLC tag table、PlcTagTable 属性表)、《GitHub 开源》(官方 snippets PlcTagTableSnippets.cs / TagTableSnippets.cs)、《英文技术博客》(PLC I/O Tags via XML、Multilingual Tag Comments、WinCC Unified Logging Tags、L5K 等价工作流)、《中文社区实战》(深度开发篇)、《进阶专题》(HMI 章)、CODESYS 应用指南第 8 章等抓取结果。
4.10 交叉引用、监视表与 Force 表
4.10.1 交叉引用(CrossReference)
// 官方片段:谁引用了这个 DB(影响面分析的核心手段)
var db = plcSoftware?.BlockGroup.Blocks.First(x => x.Name == "Axis_red_DB");
var crossReferenceService = db?.GetService<CrossReferenceService>();
var sources = crossReferenceService?
.GetCrossReferences(CrossReferenceFilter.AllObjects).Sources.ToList();
foreach (var s in sources) Console.WriteLine(s);
| 项 | 说明 |
|---|---|
| 服务 | CrossReferenceService(从 PlcBlock 取) |
| 方法 | GetCrossReferences(CrossReferenceFilter.AllObjects) |
CrossReferenceFilter | 枚举,AllObjects 取全部;另有按类型过滤的用法可精准定位调用点 |
实用场景:做影响面分析、批量改名前确认影响范围。
DB 类型区分与接口成员枚举(官方片段):
if (plcSoftware.BlockGroup.Blocks.First(x => x.Name == "Axis_red_DB") is not InstanceDB instanceDb)
throw new InvalidOperationException("Axis_red_DB is not a InstanceDB");
if (plcSoftware.BlockGroup.Blocks.First(x => x.Name == "GlobalBlock") is not GlobalDB globalDb)
throw new InvalidOperationException("GlobalBlock is not a GlobalDB");
Console.WriteLine(string.Join("\n", instanceDb.Interface.Members.Select(m => m.Name)));
Console.WriteLine(string.Join("\n", globalDb.Interface.Members.Select(m => m.Name)));
⚠️
InstanceDB/GlobalDB均在Siemens.Engineering.SW.Blocks命名空间下,与PlcBlock同级。必须精确类型判断,两者都有.Interface.Members。
V17+ 的 tag 比较工具:Project Tree → PLC_1 → PLC tags 下的 Cross-reference list / Assignment list 提供可见性,但不强制标准——命名规范、与电气图的 I/O 一致性、编程风格必须自建 Openness 方案。
4.10.2 监视表(WatchTable)与 Force 表
官方对象模型位置:
PlcSoftware
└── PlcWatch ── PlcWatchTable(监视表)
└── ForceTableSystemGroup ── Force Table
⚠️ 官方对象模型图有明确脚注:“The Force Table must be in
PlcWatchandForceTableSystemGroup” —— 即 Force Table 挂在PlcWatch下的ForceTableSystemGroup,不是独立的顶层组。这是个容易找错位置的细节。
V13 SP1 时代的能力:Openness 就能改 watch table。但 Openness 不提供按块选择性下载过滤器、不提供 Unforce、不提供读诊断缓冲区。
顺序:先离线 → 再改 → 再编译 → 再保存:
// 标准序列:强制离线 → 修改 → 编译 → 保存
OnlineProvider? op = plcItem.GetService<OnlineProvider>();
if (op != null && op.State != OnlineState.Offline) op.GoOffline();
// ... 修改块 / 标签表 / 监视表 ...
ICompilable compilable = (ICompilable)project; // 或 plcSoftware
CompilerResult result = compilable.Compile();
if (result.State != CompilerResultState.Successful) { /* throw */ }
project.Save(); // Save 与 Dispose 是两件事:Save 落盘,Dispose 释放内存锁
⚠️ 编译对象要选对:
PlcSoftware与Project都实现ICompilable——编译软件用PlcSoftware,编译整个工程用Project。
4.10.3 编译诊断信息
// 官方惯用法:读编译结果
ICompilable compilable = (ICompilable)project;
CompilerResult result = compilable.Compile();
foreach (CompilerMessage msg in result.Messages)
Console.WriteLine($"[{msg.Severity}] {msg.ErrorCode} in {msg.Path}: {msg.Description}");
if (result.State != CompilerResultState.Successful) { /* throw */ }
project.Save();
CompilerMessageSeverity 枚举值:Error | Warning | Information。
社区的更完整读法:result 含 State / Messages / Warnings / Errors;读 State / ErrorCount / WarningCount / Messages[].Severity。
CI 门禁的实践:warning 也算失败。
错误码语义速查:
| 码段 | 含义 |
|---|---|
0x8000xxxx | 多为缺硬件配置 |
0x8001xxxx | 指向程序内类型/实例不匹配 |
0x80003001 | Projects.Create 目标目录已存在且非空 |
0x8000FFFF | 数据类型名拼错(bool vs Bool)——API 区分大小写 |
0x80010002 | PLCopen XML 来自不同 TIA 版本 |
0x8000FFFE | 符号地址歧义(符号地址大小写敏感) |
来源:本文综合自 raw 目录下的《西门子官方手册》(5.1.1 对象模型图与 Force Table 脚注、5.3.13 Compiling a project)、《GitHub 开源》(官方 snippets DataBlockSnippets.cs)、《英文技术博客》(Project Bases 篇、TIA Portal Automated Project Verification 篇)、《进阶专题》等抓取结果。
4.11 遍历所有块并导出(完整工具)
4.11.1 递归遍历所有块
官方对象模型:PlcBlockSystemGroup → PlcBlockUserGroupComposition → PlcBlockUserGroup(可嵌套)→ PlcBlockComposition → PlcBlock。
using System.Collections.Generic;
using System.Linq;
using Siemens.Engineering.SW.Blocks;
/// <summary>递归收集块组(含子组)下的全部 PlcBlock。</summary>
public static class BlockCollector
{
public static List<PlcBlock> AllBlocks(PlcBlockSystemGroup root)
{
var result = new List<PlcBlock>();
CollectFromGroup(root, result);
return result;
}
private static void CollectFromGroup(PlcBlockSystemGroup g, List<PlcBlock> acc)
{
foreach (PlcBlock b in g.Blocks) acc.Add(b); // 先收本层
foreach (PlcBlockUserGroup sub in g.Groups) // 再递归子组
CollectFromUserGroup(sub, acc);
}
private static void CollectFromUserGroup(PlcBlockUserGroup g, List<PlcBlock> acc)
{
foreach (PlcBlock b in g.Blocks) acc.Add(b);
foreach (PlcBlockUserGroup sub in g.Groups) CollectFromUserGroup(sub, acc);
}
public static List<PlcBlock> AllUserBlocks(PlcBlockSystemGroup root) =>
AllBlocks(root).Where(b => !b.IsBuiltIn).ToList(); // 过滤系统内置块
}
4.11.2 程序结构框架自动生成(社区范式)
慧兰博的 DPS 框架示例展示了如何用三个类递归建组——这是”程序结构自动化”的标准范式:
private void CreateDPS(PlcSoftware ps)
{
PlcBlockSystemGroup systemGroup = ps.BlockGroup;
PlcBlockUserGroupComposition groupComposition = systemGroup.Groups;
PlcBlockUserGroup cg = groupComposition.Create("01.Common");
cg.Groups.Create("01._Main");
cg.Groups.Create("01.Valve");
cg.Groups.Create("01.Motor");
cg = groupComposition.Create("02.Area01");
cg.Groups.Create("02._Main"); cg.Groups.Create("02.SEQ"); cg.Groups.Create("02.Simulation");
cg = groupComposition.Create("03.Area02");
cg.Groups.Create("03._Main"); cg.Groups.Create("03.SEQ"); cg.Groups.Create("03.Simulation");
cg = groupComposition.Create("Library");
}
自动获取项目中所有 PLC(靠 TypeIdentifier 判定):
foreach (Device device in project.Devices)
{
if (device.TypeIdentifier != "System:Device.S71200" &&
device.TypeIdentifier != "System:Device.S71500") continue;
foreach (DeviceItem deviceItem in device.DeviceItems)
{
var container = deviceItem.GetService<SoftwareContainer>();
if (container?.Software is PlcSoftware plc)
plcList.Add(plc); // 只有 PLC 需要插程序
}
}
删除组的官方写法:
PlcBlockUserGroup group = plcSoftware.BlockGroup.Groups.Find("myGroup");
PlcBlockUserGroupComposition subgroups = group.Groups;
PlcBlockUserGroup subgroup = subgroups.Find("myUserGroup");
if (subgroup != null) subgroup.Delete();
4.11.3 完整的”导出全部块为 SCL”工具
/// <summary>把一个 PLC 的全部可导出块生成 SCL 源文件(进 Git)。</summary>
public static class BlockExporter
{
/// <summary>导出块为 .scl(仅 STL / SCL 且非 KnowHow 保护)。</summary>
public static int ExportBlocks(PlcSoftware plc, string outDir)
{
Directory.CreateDirectory(outDir);
var blocks = plc.BlockGroup.Blocks
.Where(b => !IsKnowHowProtected(b)
&& b.ProgrammingLanguage is ProgrammingLanguage.SCL
or ProgrammingLanguage.STL)
.ToList();
if (blocks.Count == 0) return 0;
// ★ 官方限制:目标位置不能有同名文件(否则抛异常)
var target = new FileInfo(Path.Combine(outDir, "blocks.scl"));
if (target.Exists) target.Delete();
plc.ExternalSourceGroup.GenerateSource(
blocks, target, GenerateOptions.WithDependencies); // 含全部依赖
return blocks.Count;
}
}
Know-how 保护判定(拼写兜底):
/// <summary>⚠️ 拼写需核实:官方片段写 IsKnowHowProtection,另有资料写 IsKnowHowProtected。</summary>
private static bool IsKnowHowProtected(PlcBlock b)
{
var pi = typeof(PlcBlock).GetProperty("IsKnowHowProtected")
?? typeof(PlcBlock).GetProperty("IsKnowHowProtection");
return pi?.GetValue(b) as bool? ?? false;
}
}
导出 UDT 为 .udt(★ 仅 .udt 扩展名):
public static int ExportTypes(PlcSoftware plc, string outDir)
{
var types = plc.BlockGroup.Types.Cast<PlcType>().ToList(); // 按本机实际 API 调整
if (types.Count == 0) return 0;
var target = new FileInfo(Path.Combine(outDir, "types.udt"));
if (target.Exists) target.Delete();
plc.ExternalSourceGroup.GenerateSource(
types, target, GenerateOptions.WithDependencies);
return types.Count;
}
4.11.4 批量导入的性能策略
| 策略 | 收益 | 来源 |
|---|---|---|
| 文件越少导入越快 | 把需生成的外设尽量放在一起 | CODESYS 应用指南第 8 章 |
中小项目:在模板里规划 OP10~OP200 目录结构再按需删 | 极大节省导入时间 | 同上 |
| 模板主体结构尽量不动,只改工站标号 | 避免结构差异导致导入失败 | 同上 |
| 一个 XML 支持多 root 对象 | 官方 5.2.24:可以创建一个 XML 里的多个文本列表,而不是每个列表一个文件 | 官方 5.2.24 |
| 首次调用最慢 | 官方 5.2.24:第一次调用某功能可能比后续慢得多 | 官方 5.2.24 |
| 多项目并行 | 多进程而非多线程(API 非线程安全) | 中文社区 |
来源:本文综合自 raw 目录下的《西门子官方手册》(5.2.24 Notes on performance、5.11.3.18 Generate source from block)、《GitHub 开源》(官方 snippets
WithExtensions.Step7/ProgramBlocksSnippets.cs、慧兰博《白话 TIA Openness(4)》框架示例)、《中文社区实战》、《英文技术博客》、CODESYS 应用指南第 8 章等抓取结果。
4.12 块号与版本号管理
4.12.1 块号的四条硬规则
| # | 规则 | 说明 |
|---|---|---|
| 1 | 块号范围 1..65535 | 官方导出属性表明确 |
| 2 | AutoNumber = true 时 Number 仅在空闲时生效 | 官方 ProDiag 创建参数说明:若 “auto number flag” 为 true,给定的块号会在空闲时设置,否则生成新块号;若为 false,则强制设为给定块号 |
| 3 | 改 Number 前必须先 SetAttribute("AutoNumber", false) | 官方示例的明确前置步骤 |
| 4 | 重编号后必须重新编译 | 官方示例注释原文:// 重编号后必须重新编译 |
ProDiag-FB 的官方创建语义(同源规则):name → auto number flag → Number → ProgrammingLanguage;用 ProDiag 语言创建则生成无 IDB 的 FB;用 ProDiag 的 IDB 创建则同时生成 IDB;其他不支持的组合抛可恢复异常。
4.12.2 块号管理的工程化方案
方案 A:生成时不指定块号,事后统一编排(推荐)
① 用外部源生成块(不写 DB 号)—— 让 TIA 自动分配
② DumpBlockNumbers() 导出块号现状(见 4.6.3)
③ 人工 / 脚本按规则重排(SetAttribute("AutoNumber", false) → SetAttribute("Number", n))
④ 统一重新编译 + 校验 ErrorCount == 0
方案 B:把块号写进配置,生成时占位替换
Excel / CSV 维护:块名、目标块号、编程语言、类型
↓ 字符串替换
SCL 模板里的 DB 号占位 → 具体数字(★ 会被静默忽略,所以只能事后 SetAttribute)
↓
ExternalSource 生成 → RenumberBatch() → 编译校验
⚠️ 方案 B 的关键限制:因为 DB 号在 SCL 源里会被静默忽略,不能靠改 SCL 文本实现,必须走
SetAttribute("Number", …)。这是 4.6 那个坑的直接后果。
4.12.3 版本的四层含义(不要混淆)
| 层 | 载体 | 控制点 |
|---|---|---|
| 块版本(VERSION) | SCL 源文件头 VERSION : 1.0 | 改后需重新编译才生效 |
| 编程语言版本 | ProgrammingLanguage | LAD / FBD / SCL / STL / GRAPH |
| TIA 版本 | .ap16 / .ap17 / .ap20 文件扩展名 + Openness 版本 | 工程文件格式在 V15~V18 间不互通 |
| CPU 固件版本 | FirmwareVersion 属性 / TypeIdentifier 段 | typed library 不记录目标固件,这是隐性坑 |
4.12.4 跨版本管理的红线
| 红线 | 说明 |
|---|---|
| 工程文件格式不互通 | V15~V18 用 V17 Openness 开 V18 项目大概率报错 |
| 一个应用实例只能加载一个 TIA 版本 | 试图加载第二版本抛 “You cannot open a project in V if you already have a project open in another version. You need to restart the Application!” |
| 只有类名长期稳定 | CompilerResult / CompareResult / DownloadResult / UploadResult / TransferResult |
| 不要假设结果类的层级与顺序 | 跨版本会变 |
| V19 交付范围含 V16/V17/V18 的 dll,不含 V15.1 | 基于 V16~V18 编译的应用在 V19 上无需修改即可运行 |
| V21 是硬性分水岭 | 必须重编译 + 替换程序集引用 |
| 每个块都要记录 TIA Portal 与 CPU 固件版本 | typed library 不记录目标固件 |
4.12.5 官方最佳实践:把脚本 + 模板放 Git
社区共识做法:
“.ap 是二进制大文件,很多集成商把 脚本 + XML 模板 放 Git,按需重新生成 .ap,保持模板可 diff。”
配套原则:
| 原则 | 说明 |
|---|---|
.ap 用 Git LFS / SVN / Perforce | 二进制大文件,不适合普通 Git |
| diff 走 API 而非文本比较 | 官方没有文本 diff 手段 |
| 订货号放 catalog map,TIA 升级时 review | 硬件目录会随版本变 |
| pre-commit hook 校验 | CODEOWNERS、GPG 签名、关联工单号 |
| 脚本 + 模板版本与 TIA 版本一起锁 | 避免跨版本误用 |
CERN ICALEPCS 2025 的工程架构(值得借鉴的完整分层):
| 层 | 内容 | Git 友好性 |
|---|---|---|
| specification file | Excel:定义 I/O / 执行器 / 报警 / 参数实例 | ✅ 文本 |
| user sources | SCL 的 FC / FB / DB | ✅ 文本 |
| logic templates | Python | ✅ 文本 |
| base project | 含硬件与网络配置的 TIA 工程 | ❌ 二进制 |
核心洞察:UNICOS-CPC 通过生成外部源文件规避了”TIA 工程对 Git 不透明”的问题——能检测变更但无法解释变更、无法 merge。残留局限是 monolithic base project 与 Excel 变更难追踪。
遗留问题的解法:早期 UAB 项目结构不支持跨项目共享代码,被迫复制代码;长期用外部目录 + 系统级符号链接;UNICOS-CPC 开发者已实现 UAB 项目内原生定义外部路径。
来源:本文综合自 raw 目录下的《西门子官方手册》(5.11.3.11、5.11.3.12 ProDiag、5.1.2 导出属性表)、《英文技术博客》(Exporting and Importing FB、Project Bases、Global Find and Replace)、《GitHub 开源》(
DotNetSiemensPLCToolBoxLibrary的 TiaGitHandler 设计)、《进阶专题》(版本控制 CI 专题)、ICALEPCS 2025 论文等抓取结果。
4.13 ⚠️ 本章内容的质量说明
按本书质量要求,显式标注 raw 资料中API 签名存疑的条目:
| 存疑内容 | 出现来源 | 官方实际应为 |
|---|---|---|
GenerateOptions.Without | 英文博客 Python 教程、GitHub 第 8 条 | 官方只列 None 与 WithDependencies |
ExternalSourceService.CreateExternalSource(BlockGroup, "…") + CreateTextStream() + CompilerService.Compile(IEnumerable<>) | 中文社区 gooderp 篇 | 官方章节是 CreateFromFile + GenerateBlocksFromSource() / ICompilable.Compile() |
tagTable.Tags.Create(name, type) / Tags.Create(name, type, address) | 中文社区多篇 | 官方片段是 Tags.Create("tag_1")(只接受名字),随后设 DataTypeName / LogicalAddress |
PlcBlock.IsKnowHowProtected vs IsKnowHowProtection | GitHub 官方片段用后者,其他资料用前者 | ⚠️ 两处资料不一致,以 IntelliSense 为准(本章代码用反射兜底) |
TiaPortal.GetProcess()(无参,返回可 Attach() 对象) | 英文博客 Multilingual Tag Comments | GetProcesses() 或 GetProcess(int) |
PlcBlock.ExportToFile(string, ExportOptions) / Blocks.ImportFromDocuments(string, ImportOptions) | 英文博客 Exporting and Importing FB | PlcBlock.Export(FileInfo, ExportOptions) / Blocks.Import(FileInfo, ImportOptions) |
project.FCBlocks / block.Instructions / inst.Text | 英文博客 Global Find and Replace(原文已标注为 pseudocode) | 不存在——Openness 无逐行编辑能力 |
ImportOptions.Override 在 V17 | 社区版本矩阵称 V18 才加 | ⚠️ 官方 V19 手册 5.11.7 等多处直接使用 ImportOptions.Override;V18 是否新增需核实 |
Siemens.Collaboration.Net.TiaPortal.Openness.Resolver 的用法 Api.Global.Openness().Initialize() | GitHub NuGet 包说明 | ⚠️ 该包与 API 名称需在 nuget.org 核对;手写 AssemblyResolve 方案有官方手册背书,更稳妥 |
CreateSegmentImport / IDeviceExport(见第 3 章) | 英文博客 Adding Third-Party Hardware | 官方 CAx 路径是 project.GetService<CaxProvider>() |
raw 目录《英文技术博客》文末已自述:“多篇 IMD 文章存在明显 AI 生成痕迹与 API 幻觉……落地时须以官方 Programming Manual 为准。” 本章凡涉及官方手册确证的签名,均直接引用手册章节号。
本章速查
- Openness 不能逐行编辑 LAD/FBD/STL 逻辑——这是架构性硬边界。根因:对象模型里
PlcBlock是叶子节点,根本没有网络/语句层级的对象;LAD 是带坐标的图形对象模型;Openness 不是编译器,从 C# 源码生成代码不被支持。LAD 唯一可行路径:导出原生 XML → 只改<NetworkSource>注入<FlgNet>→ 导入重编译。命名空间随版本变化(v1/v4 都出现在 V18 资料里),必须以本机导出模板为准。 - 唯一官方可编程的”生成程序”路径是外部源:
ExternalSources.CreateFromFile(name, path)→ 写.scl→GenerateBlocksFromSource(GenerateBlockOptions.KeepOnError)。支持格式仅 4 种(.scl/.awl/.db/.udt),必须是 ASCII/UTF-8 文本,其他扩展名直接抛异常,且前置条件是 PLC 不在线。 GenerateBlockOptions语义:KeepOnError→ 不抛异常、不返回生成结果信息、返回含错误块的列表;None+ 出错 → 抛可恢复异常 + 删除已生成的块/UDT + 工程回滚到调用前状态。已存在的块会被覆盖;V18 起错误消息从 256 字符扩到 4096 字符。导出方向只有None与WithDependencies两个选项,且目标文件同名会抛异常。- 重复生成的幂等策略:生成前
ExternalSources.Find(name)?.Delete();或依赖官方”已存在块被覆盖”语义。推荐前者。 - SCL 文件头四件套缺一不可:
{Attribute 'pack_mode' := '0'}、TITLE、{ S7_Optimized_Access := 'TRUE' }、VERSION : 1.0。S7_Optimized_Access属性行必须紧跟块头之后,否则报S7_Optimized_Access attribute missing。所有变量与 FOR 循环变量都要加#前缀。 只有 SCL 编写的 FB/FC 才能导出为源;含嵌套块的文件导入新工程会报「功能块不存在」。 - ⚠️ DB 编号不能硬编码:SCL 里写
DATA_BLOCK "MyDB" DB 300会被解析但静默忽略(V12 → V17 Upd4 已确认,西门子热线确认至 V17 无原生修复)。唯一脚本化解法:block.SetAttribute("AutoNumber", false)然后block.SetAttribute("Number", n),再重新编译(需 TIA V15+,SIOS 109798623)。根治办法是让 SCADA 改符号寻址(OPC UA 或 TIA 符号导出)。 - Know-how 保护块是导入的头号坑:筛选导入时必须加
&& !IsKnowHowProtected,否则因无法读取保护块而失败。密码规则**≥8 ≤120,含数字/大小写/特殊字符**,必须先GetInvalidPasswordCharacters()过滤非法字符;V19 起不合规会抛异常。禁止在同版本两实例间拖拽块(块号重复 + KWP 密码丢失 + 实例 DB 不一致)。无密码导出的 KWP 块导入后连原作者都打不开。 - 标签表必须
Concat软件单元:software.TagTableGroup之外,每个Unit下还有自己的TagTableGroup,漏掉会导致”变量在工程里但脚本读不到”。批量建变量走 XMLImport而非逐个Tags.Create()——千条逐个创建可超 10 分钟。 Openness XML 导入只改标签定义与属性,不写实时 I/O 值。TagTables.Create()返回的引用可能与后续加标签用的不是同一个,迭代 Tags 前先TagTables.Find(name)重取。 Force Table挂在PlcWatch下的ForceTableSystemGroup(官方对象模型图有明确脚注),不是独立顶层组。编译对象要选对:PlcSoftware编译软件、Project编译整个工程,两者都实现ICompilable。Save()与Dispose()是两件事——Save 落盘,Dispose 释放内存锁。InstanceDB/GlobalDB与PlcBlock同在SW.Blocks命名空间,必须精确类型判断。- 块号与版本管理四条硬规则:块号范围 1..65535;
AutoNumber = true时Number仅在空闲时生效;改Number前必须先关AutoNumber;重编号后必须重新编译。跨版本红线:V19 交付范围含 V16/V17/V18 的 dll 但不含 V15.1;一个应用实例只能加载一个 TIA 版本;V21 是硬性分水岭必须重编译。最佳实践:把脚本 + XML 模板放 Git,.ap当二进制大文件处理(TIA 工程对 Git 不透明——能检测变更但无法解释、无法 merge)。
第5章 HMI(WinCC)自动化
第 5 章 HMI(WinCC)自动化
本章导读
TIA Portal Openness 对 HMI 的支持是两套完全独立的类型体系:经典 WinCC(WinCC Professional / Advanced / Comfort)走 Siemens.Engineering.Hmi 命名空间的 HmiTarget,而 WinCC Unified(RT Unified)走 Siemens.Engineering.HmiUnified 命名空间的 HmiSoftware——两者的根对象、屏幕集合、控件类名互不通用,代码也无法互相混用。官方 V19 手册把这两块内容分在 5.12 节(Functions for accessing the data of an HMI device) 与 5.13 节(Functions for accessing the data of an HMI Unified device / RT Unified),前者篇幅明显小于后者,说明 Unified 的可自动化面更宽。本章按官方手册章节号组织,先讲两套对象模型的取法,再逐类展开画面、变量、列表、脚本、连接与周期的 API,最后落到 CI 场景的实战价值判断与官方 GitHub 示例。
必须记住的一条官方限制:手册 5.2.8 节明确写明,通过 TIA Portal 界面接口使用 Openness 时不能使用 HMI 编辑器(“you cannot use the HMI editor”),但仍可通过 Openness API 或手工打开 “Devices & Networks” 编辑器与编程编辑器。这意味着无 UI 的 CI 流水线可以完整生成 HMI 对象数据,但不能依赖 HMI 编辑器做任何交互式补全。
5.1 HMI 自动化对象模型:两套命名空间的根本分裂
5.1.1 根对象与程序集
| 维度 | 经典 WinCC(HMI device) | WinCC Unified(RT Unified) |
|---|---|---|
| 根对象类型 | HmiTarget | HmiSoftware |
| 命名空间 | Siemens.Engineering.Hmi | Siemens.Engineering.HmiUnified |
| 手册章节 | 5.12 | 5.13 |
| 屏幕集合 | ScreenComposition(类型名 Screen) | HmiScreenComposition(类型名 HmiScreen) |
| 变量集合 | TagComposition(类型名 Tag) | HmiTagComposition(类型名 HmiTag) |
| 变量表 | TagTable | HmiTagTable |
| 文本列表 | TextList / TextListComposition | 见 5.13 节 TextsFolder 体系(Unified 侧主要通过 OpcUaUserManagement 之外的 TextlistRules 等 SiVArc 规则访问) |
| 参考手册对象模型层级 | HmiTarget → Connection → TagsFolder(Tag → TagTable)/ TextsFolder(Text → TextList)/ ScreenFolder(Screen → TagFolder/GraphicFolder/TextListFolder) | HmiSoftware → Screens / HmiTags / HmiTagTables / AlarmClasses / AlarmLogs / AnalogAlarms / AuditTrails / Connections / DataLogs / DiscreteAlarms / LoggingTags / RuntimeSettings / SystemTags / ScreenGroups |
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.1.1 节对象模型层级、5.12 节、5.13.1.1 节(RT Unified 对象清单);PLC技术网《TIA Portal Openness 概述》。
⚠️ 命名空间别名是必需的第一行代码。官方手册在给出 using 列表时明确使用别名以避免歧义:
using HmiTarget = Siemens.Engineering.Hmi.HmiTarget; // 命名空间别名避免歧义
原因是 Siemens.Engineering.HW 下也存在与 HMI 相关的类型,直接 using 两个命名空间会产生 HmiTarget 等名字的歧义编译错误。
5.1.2 取得根对象:官方 GetHmiSoftware 与 GetHmiTarget
官方手册 5.13.1.2 节给出的 RT Unified 根对象获取代码,是所有 Unified 自动化脚本的起点:
using System;
using System.IO;
using Siemens.Engineering;
using Siemens.Engineering.HmiUnified;
using Siemens.Engineering.HW;
using Siemens.Engineering.HW.Features;
private HmiSoftware GetHmiSoftware()
{
HmiSoftware hmiSoftware = null;
Project project = null;
IList<TiaPortalProcess> tiaProcessList = TiaPortal.GetProcesses();
// 若无 TIA 实例在运行则启动一个;若已运行则 attach
TiaPortal tiaApp = tiaProcessList.Count > 0 ? tiaProcessList[0].Attach()
: new TiaPortal(TiaPortalMode.WithUserInterface);
if (tiaApp.Projects.Count > 0)
project = tiaApp.Projects[0];
else
{
FileInfo file = new FileInfo(@"D:\Automation\Project1\Project1.apx");
if (!file.Exists) throw new FileNotFoundException("Project1.apx not found");
project = tiaApp.Projects?.Open(file);
}
var devices = project.Devices;
if (devices != null)
{
var device = devices[0]; // 非首位时需改 devices[X]
var deviceItems = device.DeviceItems;
if (deviceItems != null)
{
foreach (DeviceItem deviceItem in deviceItems)
{
SoftwareContainer softwareContainer = deviceItem.GetService<SoftwareContainer>();
hmiSoftware = softwareContainer?.Software as HmiSoftware;
}
}
}
return hmiSoftware;
}
手册原注:
.apx中的x代表当前安装的 TIA Portal 版本。
经典 WinCC 侧的对应写法在 5.7.2 节,官方给出的是一个判定式函数——同一个 Device 上既可能是 PLC 也可能是 HMI,唯一的区分手段是 SoftwareContainer.Software 能 as 成什么类型:
// 官方手册 5.7.2 节:判断 Device 是否为 HMI target
private HmiTarget GetHmiTarget(Device device)
{
DeviceItemComposition deviceItemComposition = device.DeviceItems;
foreach (DeviceItem deviceItem in deviceItemComposition)
{
SoftwareContainer softwareContainer = deviceItem.GetService<SoftwareContainer>();
if (softwareContainer != null)
{
Software softwareBase = softwareContainer.Software;
HmiTarget hmiTarget = softwareBase as HmiTarget;
return hmiTarget;
}
}
return null;
}
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.13.1.2 节、5.7.2 节。
⚠️ 注意官方示例代码本身的缺陷:GetHmiTarget / GetPlcSoftware 里 return 写在 foreach 内部,因此实际只检查了第一个带 SoftwareContainer 的 DeviceItem 就返回。生产代码应写成 foreach { … if (x != null) return x; } return null;。这是官方示例的疏漏,不是 API 语义。
5.1.3 命名空间层面的硬边界
| 事实 | 依据 | 影响 |
|---|---|---|
TIA Portal V18 的完整 Openness 安装不含 Siemens.Engineering.HmiUnified 程序集 | 社区 MCP 项目 1748477582/tia-portal-openness-mcp 的 V18 构建说明:V18 通过 #if !TIA_V18 编译符号自动隐藏 23 个 Unified HMI 工具 | V18 工程只能用 Classic/Comfort HMI 家族,代码需条件编译 |
V20 起 PublicAPI\V20\ 目录通常只有 Siemens.Engineering.dll 与 Siemens.Engineering.Hmi.dll 两个文件 | en-blogs 关于 V20 胖程序集的记录 | Unified 扩展(Siemens.Engineering.HmiUnified.dll)在 V20 已并入主程序集 |
V21 改为模块化程序集:Siemens.Engineering.Base.dll、Siemens.Engineering.Step7.dll 等 | 腾讯云开发者社区转载「Hello工控」文章 | 跨 V20→V21 必须重编译,<HintPath> 也要更新 |
| 通过 TIA Portal 界面接口使用 Openness 时不能使用 HMI 编辑器 | 官方手册 5.2.8 节(原文:若不能使用 HMI 编辑器 WithoutUserInterface,你不能用 HMI 编辑器,但可以用 Openness API 或手动打开 “Devices & Networks” 编辑器与编程编辑器) | CI 流水线必须用 TiaPortalMode.WithoutUserInterface;官方工程桌面机模式为 WithUserInterface |
来源:github.md 第 41 条(
1748477582/tia-portal-openness-mcp)、第 25 条(V20 胖程序集)、en-blogs(V20 DLL 解析)、official.md 5.2.8 节。
5.2 经典 WinCC:画面、画面模板与脚本的创建与删除
5.2.1 官方 5.12 节的实际能力边界
必须先说清一个事实:官方 V19 手册 5.12 节只覆盖”删除”操作,没有”创建”操作。5.12 节的完整小节清单如下:
| 小节 | 操作 | 类型 |
|---|---|---|
| 5.12.1.1 | Creating user-defined screen folders | 创建 |
| 5.12.1.2 | Deleting a screen from a folder | 删除 |
| 5.12.1.3 | Deleting a screen template from a folder | 删除 |
| 5.12.1.4 | Deleting all screens from a folder | 批量删除 |
| 5.12.2.1 | Deleting a cycle | 删除 |
| 5.12.3.1 | Deleting a text list | 删除 |
| 5.12.4.1 | Deleting a graphic list | 删除 |
| 5.12.5.1 | Deleting a connection | 删除 |
| 5.12.6.1 | Creating user-defined folders for HMI tags | 创建 |
| 5.12.6.2 | Enumerating tags of an HMI tag table | 枚举 |
| 5.12.6.3 | Deleting an individual tag from an HMI tag table | 删除 |
| 5.12.6.4 | Deleting a tag table from a folder | 删除 |
| 5.12.7.1 | Creating user-defined script folders | 创建 |
| 5.12.7.2 | Deleting a VB script from a folder | 删除 |
| 5.12.7.3 | Deleting a user-defined folder of an HMI device | 删除 |
结论:经典 WinCC 侧的 Openness API 在手册层面是**“以删除与枚举为主、创建为辅”**。画面创建的官方主路径是 Simatic ML 导入导出(第 6 章),不是对象模型 API。
5.2.2 画面文件夹与画面的删除
using Siemens.Engineering;
using HmiTarget = Siemens.Engineering.Hmi.HmiTarget;
// 官方 5.12.1.1:创建用户自定义画面文件夹
private static void CreateScreenFolder(HmiTarget hmitarget)
{
ScreenUserFolder myCreatedFolder = hmitarget.ScreenFolder.Folders.Create("myScreenFolder");
}
// 官方 5.12.1.2:从指定文件夹删除画面
public static void DeleteScreenFromFolder(HmiTarget hmiTarget)
{
ScreenUserFolder screenUserFolder = hmiTarget.ScreenFolder.Folders.Find("myScreenFolder");
ScreenComposition screens = screenUserFolder.Screens;
Screen screen = screens.Find("myScreenName");
if (screen != null) screen.Delete();
}
// 官方 5.12.1.4:清空一个文件夹下的所有画面(必须先快照再删除)
private static void DeleteAllScreensFromFolder(HmiTarget hmitarget)
{
ScreenUserFolder folder = hmitarget.ScreenFolder.Folders.Find("myScreenFolder");
ScreenComposition screens = folder.Screens;
List<Screen> list = new List<Screen>(); // 手册示例:先收集到临时 List
foreach (Screen screen in screens) list.Add(screen);
foreach (Screen screen in list) screen.Delete();
}
⚠️ 两条官方约束:
- 不能删除 permanent area(永久画面)——手册原文:“You cannot delete a permanent area. A permanent area is a system screen that is always present.”
- 批量删除必须先快照到临时集合再遍历删除,手册示例正是这么写的——在遍历集合时直接删除会破坏迭代器。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.12.1.1–5.12.1.4 节。
5.2.3 画面模板(Screen Template)
画面模板在经典 WinCC 中有独立的文件夹与集合体系(ScreenTemplateFolder / ScreenTemplateComposition),与普通画面不通用:
// 官方 5.12.1.3:从指定文件夹删除画面模板
private static void DeleteScreenTemplateFromFolder(HmiTarget hmiTarget)
{
string templateName = "MyScreenTemplate";
ScreenTemplateUserFolder folder = hmiTarget.ScreenTemplateFolder.Folders.Find("myScreenTemplateFolder");
ScreenTemplateComposition templates = folder.ScreenTemplates;
ScreenTemplate template = templates.Find(templateName);
if (template != null) template.Delete();
}
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.12.1.3 节。
5.2.4 全局画面、弹出画面、滑入画面:官方确认可导出/导入
手册 6.1.1 节的 Table 6-3(HMI)给出了官方可导出/导入对象的权威清单,这是判断”HMI 哪些东西能走 Simatic ML”的唯一依据:
| 对象 | Export | Import |
|---|---|---|
| Screens | X | X |
| Screen templates | X | X |
| Global screens | X | X |
| Pop-up screens | X | X |
| Slide-in screens | X | X |
| Scripts | X | X |
| Text lists | X | X |
| Graphic lists | X | X |
| Cycles | X | X |
| Connections | X | X |
| Tag table | X | X |
| Tags | X | X |
对应的第 6 章小节(每一种都有独立的 Export / Import 一对 API):
| 画面类型 | 导出小节 | 导入小节 |
|---|---|---|
| 全设备所有画面 | 6.3.7.2 Exporting all screens of an HMI device | 6.3.7.4 Importing screens to an HMI device |
| 单个画面(从文件夹) | 6.3.7.3 Exporting a screen from a screen folder | 6.3.7.4 |
| Permanent area | 6.3.7.5 Exporting permanent areas | 6.3.7.6 Importing permanent areas |
| 画面模板 | 6.3.7.7 Exporting all screen templates of an HMI device / 6.3.7.8 Exporting screen templates from a folder | 6.3.7.9 Importing screen templates |
| 弹出画面 | 6.3.7.10 Exporting a pop-up screen | 6.3.7.11 Importing a pop-up screen |
| 滑入画面 | 6.3.7.12 Exporting a slide-in screen | 6.3.7.13 Importing a slide-in screen |
| 带 faceplate instance 的画面 | 6.3.7.14 Exporting a screen with a faceplate instance | 6.3.7.15 Importing a screen with a faceplate instance |
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 6.1.1 节 Table 6-3、6.3.7 节目录。
工程结论:“创建画面”在经典 WinCC 上的官方可靠路径是”从模板导出 → 外部改 XML → Import(..., ImportOptions.Override) 覆盖导入”,而不是直接 Screens.Create()。这条结论也直接支撑了社区 faceplate 批量配置的做法(见 5.9.1)。
5.2.5 VB 脚本的脚本化
经典 WinCC 的脚本在对象模型里有独立的 VBScriptFolder 体系:
// 官方 5.12.7.1:创建脚本子文件夹
private static void CreateFolderInScriptfolder(HmiTarget hmitarget)
{
VBScriptSystemFolder vbScriptFolder = hmitarget.VBScriptFolder;
VBScriptUserFolderComposition vbScriptFolders = vbScriptFolder.Folders;
VBScriptUserFolder vbScriptSubFolder = vbScriptFolder.Folders.Create("mySubfolder");
}
// 官方 5.12.7.2:从文件夹删除 VB 脚本(注意 null 检查)
private static void DeleteVBScriptFromScriptFolder(HmiTarget hmitarget)
{
VBScriptUserFolder vbscriptfolder = hmitarget.VBScriptFolder.Folders.Find("MyScriptFolder");
var vbScripts = vbscriptfolder.VBScripts;
if (null != vbScripts)
{
var vbScript = vbScripts.Find("MyScript");
vbScript.Delete();
}
}
// 官方 5.12.7.3:删除用户自定义文件夹
HmiTarget hmiTarget = ...;
ScreenUserFolder screenUserGroup = hmiTarget.ScreenFolder.Folders.Find("MyUserFolder");
screenUserGroup.Delete();
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.12.7.1–5.12.7.3 节。
⚠️ 版本错配警告:VB 脚本是经典 WinCC(Advanced/Professional) 的技术。WinCC Unified 用的是 JavaScript,不是 VB。官方培训条目 109782872 名为 “WinCC Unified Scripting (JavaScript)“。Unified 侧脚本的 Openness 支持在手册 5.13.2.10 节 Working with dynamization & events for screen/screen items using scripts (RT Unified)。
5.3 WinCC Unified(RT Unified):屏幕与控件的完整对象模型
5.3.1 Screens 集合的四种访问方式
手册 5.13.1.13 节给出四种标准访问方式,官方示例全部照抄如下:
// 遍历全部画面
private void ScreenBrowse()
{
HmiSoftware hmiSoftware = GetHmiSoftware();
HmiScreenComposition objScreens = hmiSoftware.Screens;
foreach (HmiScreen ObjHmiScreen in objScreens) { /* Working with Screens */ }
}
// 按名查找
private void ScreenSearch()
{
HmiSoftware hmiSoftware = GetHmiSoftware();
string hmiScreenName = "HMI_Screen_1";
HmiScreenComposition objScreens = hmiSoftware.Screens;
HmiScreen objHmiScreen = objScreens.Find(hmiScreenName);
}
// 按索引访问
private void SearchScreenByIndex()
{
HmiScreen objHmiScreen = GetHmiSoftware().Screens[0];
}
// Contains() 判断存在性(注意官方示例此处有拼写错误:用了未声明的 objScreen)
private void IsScreenExist()
{
HmiScreenComposition objScreens = GetHmiSoftware().Screens;
HmiScreen screen1 = objScreens.Find("TestScreen1");
bool isExist = objScreens.Contains(screen1);
}
画面的可操作任务清单(手册 5.13.1.13 节明确列出):
Screens.Create()(P696) /Screens.Delete()(P696)- 画面属性(Accessing screens properties,P697)
- 画面的交叉引用服务(Accessing cross reference service on screens,P702)
- 事件处理器属性
EventHandlers(P703) - 控件集合属性
ScreenItems(P705)
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.13.1.13 节。
5.3.2 画面属性全表(官方 5.13.1.13 节)
| 属性名 | 数据类型 | 说明 | 访问 |
|---|---|---|---|
BackColor | Color | 画面背景色 | R/W |
AlternateBackColor | Color | 填充图案用的备用背景色 | R/W |
Name | String | 画面名 | R/W |
ScreenNumber | UInt16 | 画面编号 | R/W |
BackFillPattern | HmiFillPattern | 背景填充图案 | R/W |
BackGraphic | String | 背景图形 | R/W |
HorizontalAlignment | HmiHorizontalAlignment | 父窗口(顶层画面)中的水平对齐 | R/W |
VerticalAlignment | HmiVerticalAlignment | 父窗口中的垂直对齐 | R/W |
BackgroundFillMode | HmiBackgroundFillMode | 背景填充是仅画面还是整个窗口视图 | R/W |
BackGraphicStretchMode | HmiGraphicStretchMode | 背景图形是否拉伸 | R/W |
DisplayName | String | 画面显示名 | R/W |
Width | uint | 画面宽(DIU,设备无关单位) | R/W |
Height | uint | 画面高(DIU) | R/W |
Enabled (AllowOperatorControl) | Boolean | 画面是否可操作 | R/W |
Dynamizations | DynamizationBaseComposition | 动态化集合 | R |
Parent | IEngineeringObject | 父对象 | R |
ScreenItems | HmiScreenItemBaseComposition | 控件集合 | R/W |
EventHandlers | HmiScreenEventHandlerComposition | 事件处理器集合 | R |
批量读写属性的官方写法(GetAttributes / SetAttributes):
private void SettingPropertiesScreen()
{
HmiSoftware hmisoftware = GetHmiSoftware();
HmiScreen objHmiScreen = hmisoftware.Screens.Find("TestScreen");
objHmiScreen.Width = 50;
objHmiScreen.ScreenNumber = 1;
objHmiScreen.BackGraphic = "DownArrow";
objHmiScreen.Enabled = true;
objHmiScreen.Height = 123;
}
private void GettingPropertiesScreen()
{
HmiScreen objHmiScreen = GetHmiSoftware().Screens.Find("TestScreen");
List<string> getlststring = new List<string>
{
"AlternateBackColor","BackColor","BackFillPattern","BackGraphic",
"BackGraphicStretchMode","BackgroundFillMode","DisplayName",
"Enabled","Height","HorizontalAlignment","Name","ScreenNumber",
"VerticalAlignment","Width"
};
// 官方示例中被注释掉的项:Dynamization / EnabledExplicitRelease /
// Layers / Parent / ScreenElements / ScreenItems —— 这些不参与批量 Get
var getallpropValue = objHmiScreen.GetAttributes(getlststring);
foreach (var item in getallpropValue)
{
Console.Write(item.ToString());
Console.Write("\n");
}
}
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.13.1.13 节 Accessing screens properties。
5.3.3 控件(ScreenItems)全类型清单
手册 5.13.1.13 节 “Screenitem objects (RT Unified)” 给出了按命名空间分组的完整控件类型表——这是判断某个 UI 控件能否被脚本化的一览表:
| 命名空间 | 控件名 | 类型名 |
|---|---|---|
Siemens.Engineering.HmiUnified.UI.Shapes / .UI.Base | Circle | HmiCircle |
| Circular arc | HmiCircularArc | |
| Circle Segment | HmiCircleSegment | |
| Ellipse | HmiEllipse | |
| Elliptical arc | HmiEllipticalArc | |
| Ellipse Segment | HmiEllipseSegment | |
| Graphic view | HmiGraphicView | |
| Line | HmiLine | |
| Polyline | HmiPolyline | |
| Polygon | HmiPolygon | |
| Rectangle | HmiRectangle | |
| Text Box | HmiTextBox | |
Siemens.Engineering.HmiUnified.ModernUI.Widgets / .ModernUI.BaseElements | Bar | HmiBar |
| Button | HmiButton | |
| Checkbox | HmiCheckBoxGroup | |
| Clock | HmiClock | |
| Gauge | HmiGauge | |
| IO Field | HmiIOField | |
| Listbox | HmiListBox | |
| RadioButton | HmiRadioButtonGroup | |
| Slider | HmiSlider | |
| Switch | HmiToggleSwitch | |
| Symbolic IOField | HmiSymbolicIOField | |
| TouchArea | HmiTouchArea | |
Siemens.Engineering.HmiUnified.ModernUI.Controls / .ModernUI.BaseControls | Alarm Control | HmiAlarmControl |
| Function Trend Control | HmiFunctionTrendControl | |
| Faceplate Container | HmiFaceplateContainer | |
| Media Player | HmiMediaControl | |
| Process Control | HmiProcessControl | |
| Parameter Set Control | HmiDetailedParameterControl | |
| Screen Window | HmiScreenWindow | |
| System Diagnosis Control | HmiSystemDiagnosisControl | |
| Trend Control | HmiTrendControl | |
| Trend Companion | HmiTrendCompanion | |
| Web Control | HmiWebControl | |
| My controls | Custom Web Control Container | HmiCustomWebControlContainer |
| Dynamic widgets | Custom Widget Container | HmiCustomWidgetContainer |
创建控件的官方写法(注意是泛型 Create<T>(),不是经典 WinCC 的 Create(type, size)):
// 创建 HmiLine
private void ScreenItemCreate()
{
HmiSoftware hmiSoftware = GetHmiSoftware();
HmiScreen hmiScreen = hmiSoftware.Screens.Create("Screen_1");
HmiScreenItemBaseComposition screenitems = hmiSoftware.Screens[0].ScreenItems;
HmiLine hmiLine = screenitems.Create<HmiLine>("ScreenItem_1");
}
// 创建 HmiIOField
private void CreateScreenItem()
{
HmiScreen hmiScreen = GetHmiSoftware().Screens.Find("Screen_1");
HmiScreenItemBaseComposition screenitems = hmiScreen.ScreenItems;
HmiIOField hmiIOField = screenitems.Create<HmiIOField>("ScreenItem_1");
}
// 创建 HmiAlarmControl
private void ControlScreenItemCreate()
{
HmiScreen hmiScreen = GetHmiSoftware().Screens.Find("Screen_1");
HmiScreenItemBaseComposition screenitems = hmiScreen.ScreenItems;
HmiAlarmControl hmiAlarmControl = screenitems.Create<HmiAlarmControl>("ScreenItem_12");
}
// 创建 HmiFaceplateContainer
private void createFaceplateContainer(HmiSoftware hmiSoftware,
string screenName = "screen_1", string faceplatecontainerName = "Faceplate container_1")
{
HmiScreen hmiScreen = hmiSoftware.Screens.Find(screenName);
HmiScreenItemBaseComposition screenItems = hmiSoftware.ScreenItems; // 注意:手册此处用的是 HmiSoftware 级别
HmiFaceplateContainer faceplate = screenItems.Create<HmiFaceplateContainer>(faceplatecontainerName);
}
手册原注:“To create other screenitems, you can provide the screenitems type name supported in TIA Portal Openness.”
⚠️ 官方示例的一处不一致:createFaceplateContainer 里用的是 hmiSoftware.ScreenItems(HmiSoftware 级别),而其他示例用的是 hmiScreen.ScreenItems(画面级别)。两处都出现在官方手册中,落地时建议统一用画面级 hmiScreen.ScreenItems,并对 hmiSoftware.ScreenItems 标注「需核实」。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.13.1.13 节 Screenitem objects / ScreenItems.Create()。
5.3.4 控件属性示例:以 HmiCircle 为准
手册给出的控件属性表结构完全一致(以 Circle 为例,Property 类型 + Access 级别):
| 属性名 | 属性类型 | 说明 | 访问 |
|---|---|---|---|
BorderColor / AlternateBorderColor | Color | 边框色 / 备用边框色 | R/W |
BackColor / AlternateBackColor | Color | 背景色 / 备用背景色 | R/W |
Enabled | bool | 是否允许操作控制 | R/W |
CurrentQuality | HmiQuality | 控件的连接状态 | R |
RotationCenterPlacement | HmiRotationCenterPlacement | 旋转中心 | R/W |
BorderWidth | byte | 边框宽度 | R/W |
BackFillPattern | HmiFillPattern | 背景填充图案 | R/W |
FillLevel | byte | 填充水平 | R/W |
FillDirection | HmiFillDirection | 填充方向 | R/W |
DashType | HmiDashType | 线型 | R/W |
ShowFillLevel | bool | 是否显示填充水平 | R/W |
Radius | uint | 半径 | R/W |
CenterX / CenterY | int | 圆心坐标 | R/W |
RotationAngle | short | 旋转角度 | R/W |
RotationCenterX / RotationCenterY | float | 旋转支点 | R/W |
Opacity | float | 不透明度,取值应在 0–1 之间 | R/W |
Name | string | 控件名,长度 1–128 字符 | R/W |
Visible | bool | 可见性 | R/W |
TabIndex | ushort | Tab 索引 | R/W |
ToolTipText | MultilingualText | 工具提示文本 | R/W |
Authorization | string | 授权级别 | R/W |
EventHandlers | HmiCircleEventHandlerComposition | 事件处理器 | R |
ShowFocusVisual | bool | 是否显示焦点视觉 | R/W |
Dynamizations | DynamizationBaseComposition | 动态化 | R |
PropertyEventHandlers | PropertyEventHandlerComposition | 属性事件处理器 | R |
官方示例代码:
private void CircleScreenItemsPropertiesAccess()
{
HmiSoftware hmiSoftware = GetHmiSoftware();
HmiScreen hmiScreen = hmiSoftware.Screens.Find("Screen_1");
HmiScreenItemBaseComposition screenitems = hmiScreen.ScreenItems;
HmiCircle circle = screenitems.Create<HmiCircle>("Default Value55647");
circle.Name = "Default Value6834e";
circle.BorderColor = Color.FromArgb(0xCC, 0xF0, 0x80, 0x80);
circle.BorderWidth = 10;
// ToolTipText 等同理
}
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.13.1.13 节 Basic objects / Accessing circle properties。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.13.1.13 节。
5.4 WinCC Unified 变量(HmiTag)与变量表
5.4.1 HmiTag 访问的六种官方路径
手册 5.13.1.15 节列出四条,加上创建/删除共六种:
// 1) 遍历设备全部变量
private void TagBrowse()
{
HmiTagComposition hmiTags = GetHmiSoftware().Tags;
foreach (HmiTag hmiTag in hmiTags) { /* working with tags */ }
}
// 2) 遍历指定变量表内的变量
private void TagSearch()
{
string tagTableName = "SpecificTagTable";
HmiTagComposition hmiTags = GetHmiSoftware().HmiTagTables.Find(tagTableName).Tags;
foreach (HmiTag hmiTag in hmiTags) { /* working with tags */ }
}
// 3) 按名访问单个变量(HmiSoftware 级)
private void AccessTag()
{
String hmiTagName = "HMI_Tag_1";
HmiTag hmiTag = GetHmiSoftware().HmiTags.Find(hmiTagName);
if (hmiTag != null) { /* working with tags */ }
}
// 4) 表名 + 变量名两级定位
private void AccessTagByName()
{
HmiTag hmiTag4 = GetHmiSoftware().HmiTagTables.Find("TagTable1").HmiTags.Find("Tag1");
}
// 5) 创建(默认表 / 指定表两个重载)
private void CreateTag()
{
HmiSoftware hmiSoftware = GetHmiSoftware();
HmiTagComposition hmiTagscomp1 = hmiSoftware.HmiTags;
HmiTag hmiTag1 = hmiTagscomp1.Create("Tag1"); // 默认变量表
HmiTag hmiTag2 = hmiTagscomp1.Create("Tag2", "TagTable1"); // 指定表
// 手册注:TagTable1 必须已存在,否则抛可恢复异常
}
// 6) 删除
private void DeleteTag()
{
HmiTagComposition hmiTags = GetHmiSoftware().HmiTags;
HmiTag hmiTag1 = hmiTags.Find("Tag1");
hmiTag1.Delete();
}
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.13.1.15 节 Tags (RT Unified)。
5.4.2 HmiTag 属性全表(官方 5.13.1.15 节,27 个属性)
| 属性名 | 数据类型 | 说明 | 访问 |
|---|---|---|---|
AccessMode | HmiAccessMode | 访问模式 | R/W |
AcquisitionCycle | String | 采集周期属性 | R/W |
AcquisitionMode | HmiTagAcquisitionMode | 采集模式 | R/W |
Comment | MultilingualText | 变量注释 | R/W |
Connection | String | 变量所属连接 | R/W |
DataType | String | 数据类型(可为 simple / UDT / array) | R/W |
MaxLength | UInt32 | 长度 | R/W |
Address | String | 地址 | R/W |
InitialMinValue | LowerRange | 起始下限值 | R |
Name | String | 变量名 | R/W |
Persistent | Boolean | 保持性 | R/W |
PlcName | String | 所属 PLC 名 | R |
PlcTag | String | PLC 变量名 | R/W |
InitialValue | Object | 起始值 | R/W |
SubstituteValue | HmiSubstituteValue | 替代值 | R |
UpdateId | UInt32 | 更新 ID | R/W |
InitialMaxValue | UpperRange | 起始上限值 | R |
TagTableName | String | 所属变量表名 | R |
HmiDataType | String | HMI 数据类型 | R/W |
LinearScaling | Boolean | HMI 变量与 PLC 变量线性映射 | R/W |
HmiStartValue | Object | 线性映射 HMI 侧起始值 | R/W |
HmiEndValue | Object | 线性映射 HMI 侧终止值 | R/W |
PlcStartValue | Object | 线性映射 PLC 侧起始值 | R/W |
PlcEndValue | Object | 线性映射 PLC 侧终止值 | R/W |
GmpRelevant | Boolean | 变量值变化是否记入审计跟踪 | R/W |
TagType | Enum -> TagType | 变量类型(simple / UDT / array) | R/W |
Members | HmiTagComposition | UDT 成员或数组元素的子变量集合 | R/W |
嵌套结构属性:
| 父对象 | 属性名 | 数据类型 | 说明 | 访问 |
|---|---|---|---|---|
LowerRange / UpperRange | Value | Object | 上下限值 | R/W |
ValueType | HmiLimitValueType | 值类型 | R/W | |
SubstituteValue | Value | Object | 替代值 | R/W |
SubstituteValueUsage | HmiSubstituteValueUsage | 何时使用替代值 | R/W |
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.13.1.15 节 Accessing tag properties。
5.4.3 ⚠️ 日志变量:HmiTag.Logging 还是 HmiTag.LoggingTags?——一处必须澄清的资料冲突
这是 raw 资料里唯一一处博客与官方手册直接矛盾的地方,必须写清楚。
| 来源 | 说法 | 判定 |
|---|---|---|
| 官方 V19 系统手册 5.13.1.10 节 | HmiTag 的日志是独立子对象集合 LoggingTags(类型 HmiLoggingTagComposition),元素类型 HmiLoggingTag;日志配置属性(LoggingMode、Cycle、TriggerMode 等)挂在 HmiLoggingTag 上,不在 HmiTag 上 | ✅ 以此为准 |
| en-blogs 收录的 Industrial Monitor Direct 知识库文章 | 声称 HmiTag 暴露一个 Logging 属性,类型为 HmiTagLogging,承载 LoggingMode / LoggingCycle / LoggingTrigger / Persistence / LimitMonitoring | ⚠️ 该来源 API 签名存疑,需对照官方手册核实——官方 V19 手册中 HmiTagLogging 与 HmiTag.Logging 均为 0 命中 |
官方正确写法(手册 5.13.1.10 节):
// 遍历某变量的全部日志变量
private void LoggingTagBrowse()
{
HmiSoftware hmiSoftware = GetHmiSoftware();
String hmiTagName = "HMI_Tag_1";
HmiTagComposition hmiTags = hmiSoftware.Tags;
HmiTag hmiTag = hmiTags.Find(hmiTagName);
HmiLoggingTagComposition loggingTags = hmiTag.LoggingTags; // 注意:不是 .Logging
foreach (HmiLoggingTag loggingTag in loggingTags) { /* Working with logging tags */ }
}
// 创建日志变量
private void LoggingTagCreate()
{
HmiSoftware hmiSoftware = GetHmiSoftware();
HmiTag hmiTag = hmiSoftware.Tags.Find("Tag1"); // Tag1 必须已存在
HmiLoggingTagComposition loggingTags = hmiTag.LoggingTags;
HmiLoggingTag loggingTag = loggingTags.Create("LoggingTag1");
}
// 读写日志变量属性
private void LoggingTagPropertiesAccess()
{
HmiTag hmiTag = GetHmiSoftware().Tags.Find("Tag_1");
HmiLoggingTagComposition loggingTags = hmiTag.LoggingTags;
HmiLoggingTag loggingTag = loggingTags.Find("LoggingTag_1");
string name = loggingTag.Name;
HmiSmoothingMode smoothingMode = loggingTag.SmoothingMode;
loggingTag.SmoothingMode = HmiSmoothingMode.SwingingDoor;
HmiLimitScope limitScope = loggingTag.LimitScope;
loggingTag.LimitScope = HmiLimitScope.WithinLimits;
object lowLimit = loggingTag.LowLimit;
loggingTag.LowLimit = "-32768";
object highLimit = loggingTag.HighLimit;
loggingTag.HighLimit = "-3";
TimeSpan tsMin = TimeSpan.Parse("5:00:00");
loggingTag.SmoothingMinTime = tsMin;
TimeSpan tsMax = TimeSpan.Parse("1000");
loggingTag.SmoothingMaxTime = tsMax;
loggingTag.SmoothingDeltaValue = 5;
}
HmiLoggingTag 属性全表(官方 5.13.1.10 节,18 个属性):
| 属性名 | 数据类型 | 说明 | 访问 |
|---|---|---|---|
AggregationDelay | System.TimeSpan | 聚合延迟 | R/W |
AggregationMode | HmiAggregationMode | 聚合模式 | R/W |
Cycle | System.String | 日志周期 | R/W |
CycleFactor | System.UInt32 | 日志周期因子 | R/W |
DataLog | System.String | 所属数据日志 | R/W |
HighLimit | System.Object | 上限 | R/W |
LimitScope | HmiLimitScope | 限值作用域 | R/W |
LoggingMode | HmiLoggingMode | 日志模式 | R/W |
LowLimit | System.Object | 下限 | R/W |
Name | System.string | 日志变量名 | R/W |
SmoothingDeltaValue | System.Double | 平滑增量值 | R/W |
SmoothingMaxTime | System.TimeSpan | 平滑最大时间 | R/W |
SmoothingMinTime | System.TimeSpan | 平滑最小时间 | R/W |
SmoothingMode | HmiSmoothingMode | 平滑模式 | R/W |
Source | System.string | 数据源 | R/W |
TriggerMode | HmiTriggerMode | 触发模式 | R/W |
TriggerTag | System.String | 触发变量 | R/W |
TriggerTagBitNumber | System.UInt32 | 触发变量位号 | R/W |
仍然有效且重要的现实缺陷(来自博客但与官方一致的部分):TagTable.Export(...) 导出的 CSV/XLSX schema 是固定的(Name、Path、DataType、Connection、PLC tag、Length、Initial value、Comment、Acquisition mode / Acquisition cycle、Substitute value),schema 中没有 Logging、LoggingCycle、Persistence、LoggingTrigger、LimitMonitoring 列。因此导入时 TIA Portal 无法重建原始日志配置,运行时每个 HMI tag 的日志标志被清空——原本在记录的变量被静默降级为不记录。同一缺陷也存在于”从 Excel 粘贴行到 HMI 变量编辑器”的操作。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.13.1.10 节 LoggingTags (RT Unified);en-blogs(Industrial Monitor Direct 关于
TagTable.Export固定 schema 的记录,已标注 ⚠️ API 签名存疑)。补充事实(博客侧):该来源还称日志变量表导入导出对应手册章节号为 5.13.1.8 与 5.13.1.10,RT Unified 固定 schema 见《Exporting and importing tags (RT Unified)》;前提为 TIA Portal V18+(推荐 V20)、
.NET Framework 4.8,用户须在 Windows 组 “Siemens TIA Openness”,否则new TiaPortal(tiaProcess)抛EngineeringSecurityException。
5.4.4 经典 WinCC 变量表操作
// 官方 5.12.6.1:创建 HMI 变量的用户子文件夹
private static void CreateUserfolderForHMITags(HmiTarget hmitarget)
{
TagSystemFolder folder = hmitarget.TagFolder;
TagUserFolder myCreatedFolder = folder.Folders.Create("MySubFolder");
}
// 官方 5.12.6.2:枚举变量表中的变量(另可访问默认表 DefaultTagTable)
private static void EnumerateTagsInTagtable(HmiTarget hmitarget)
{
TagTable table = hmitarget.TagFolder.TagTables.Find("MyTagtable");
// TagTable defaulttable = hmitarget.TagFolder.DefaultTagTable;
TagComposition tagComposition = table.Tags;
foreach (Tag tag in tagComposition) { /* Add your code here */ }
}
// 官方 5.12.6.3:删除单个变量
private static void DeleteATag(HmiTarget hmiTarget)
{
string tagName = "MyTag";
TagTable defaultTagTable = hmiTarget.TagFolder.DefaultTagTable;
TagComposition tags = defaultTagTable.Tags;
Tag tag = tags.Find(tagName);
tag.Delete();
}
// 官方 5.12.6.4:删除变量表(注意:不能删除默认变量表)
private static void DeleteTagTable(HmiTarget hmiTarget)
{
string tableName = "myTagTable";
TagSystemFolder tagSystemFolder = hmiTarget.TagFolder;
TagTableComposition tagTables = tagSystemFolder.TagTables;
TagTable tagTable = tagTables.Find(tableName);
tagTable.Delete();
}
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.12.6.1–5.12.6.4 节。
5.4.5 HMI 变量导入导出的两条特殊规则
手册 6.3.2.5 节列出两类特殊 HMI 变量,导入前必须满足前置条件:
① 带集成连接的外部 HMI 变量:导出时只保存到 PLC 变量的链接,不保存 PLC 变量数据。导入前必须确保 PLC、对应 PLC 变量、集成连接在项目中已存在。
<Hmi.Tag.Tag ID="1" CompositionName="Tags">
<AttributeList>
<Name>MyIntegratedHmiTag_1</Name>
</AttributeList>
<LinkList>
<AcquisitionCycle TargetID="@OpenLink">
<Name>1 s</Name>
</AcquisitionCycle>
<Connection TargetID="@OpenLink">
<Name>HMI_Connection_MP277_300400</Name> <!-- Must exist in the project -->
</Connection>
<ControllerTag TargetID="@OpenLink">
<Name>Datablock_1.DBElement1</Name> <!-- Must exist in the project -->
</ControllerTag>
</LinkList>
</Hmi.Tag.Tag>
⚠️ 外部 HMI 变量名必须在工程所有变量表中唯一;导入时若未指定合适的变量表,导入被取消。
② UDT 数据类型的 HMI 变量:只导出到数据类型的链接,导入只支持版本化数据类型,且数据类型必须存在**工程库(project library)**中——全局库(global library)中的数据类型不支持。
- 引用的数据类型必须在 project library 中,否则导入终止
- 引用的数据类型必须已版本化(版本化自 V13 SP1 起支持),否则抛异常
- 解析引用时使用找到的第一个数据类型:先搜 project library 根目录,再搜子文件夹
<Hmi.Tag.Tag ID="1" CompositionName="Tags">
<LinkList>
<DataType TargetID="@OpenLink">
<Name>HmiUdt_1 V 1.0.0</Name> <!-- Must exist in the project library -->
</DataType>
</LinkList>
</Hmi.Tag.Tag>
⚠️ 设备差异:Comfort / Advanced 设备不支持把结构化标签赋给 ‘Elements of structure tags’ 的上下限属性(结构化标签的限值属性可以赋简单标签,反之不行)。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 6.3.2.5 节。
5.5 文本列表、图形列表、周期与连接
5.5.1 经典 WinCC 侧(5.12.2–5.12.5)
// 官方 5.12.2.1:删除周期(不能删除标准周期)
public static void DeleteCycle(HmiTarget hmiTarget)
{
CycleComposition cycles = hmiTarget.Cycles;
Cycle cycle = cycles.Find("myCycle");
cycle.Delete();
}
// 官方 5.12.3.1:删除文本列表(连带删除所有列表项)
public static void DeleteTextList(HmiTarget hmiTarget)
{
TextListComposition textLists = hmiTarget.TextLists;
TextList textList = textLists.Find("myTextList");
textList.Delete();
}
// 官方 5.12.4.1:删除图形列表
private static void DeleteGraphicList(HmiTarget hmiTarget)
{
GraphicListComposition graphicLists = hmiTarget.GraphicLists;
GraphicList graphicList = graphicLists.Find("myGraphicList");
graphicList.Delete();
}
// 官方 5.12.5.1:删除通信连接
public static void DeleteConnection(HmiTarget hmiTarget)
{
ConnectionComposition connections = hmiTarget.Connections;
Connection connection = connections.Find("HMI_connection_1");
connection.Delete();
}
手册对周期删除的判别方式有明确说明:只能通过对象模型中相应 CycleComposition 的组合计数(composition count)判断周期是否真被删除,删除后无法再访问。
文本列表的创建在对象模型 API 中不是 5.12 的主题,官方路径同样是导入(6.3.4.1 导出 / 6.3.4.2 导入 / 6.3.4.3 高级 XML 格式)。社区给出了一条生成链(见 5.9.1):
TiaPortal.GetProcess().Attach() / TiaPortal.GetProcess().OpenProject(...)
project.LanguageSettings.ActiveProjectLanguage -> ProjectLanguage.CultureInfo
plcTag.Comment -> MultilingualText.Items / .Default
hmiTarget.TextListComposition.CreateTextList(...) -> TextList.Entries
⚠️ 该链中的 TiaPortal.GetProcess() 与 hmiTarget.TextListComposition 写法与官方 V19 手册不一致(官方为 new TiaPortal(TiaPortalMode.WithUserInterface) / tiaPortal.Projects.Open(...) 与 hmiTarget.TextLists),en-blogs 末尾「已知内容质量问题」已把 TiaPortal.GetProcess() 列为需核对的写法。照抄前必须按官方手册改正。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.12.2–5.12.5 节;en-blogs(多语言 PLC tag 注释文章,⚠️ 已标注 API 签名存疑)。
5.5.2 Simatic ML 的 XML 结构:为什么 HMI 导入导出能work
手册 6.3.1.1–6.3.1.2 节定义了 HMI 导出文件的结构,这是自己写生成器时必须遵守的 schema:
文件顶层结构:
<Information> <!-- 你自己的信息,导入时被忽略;可放 <IntegrityInformation> 做校验 -->
<Object> <!-- 被导出的元素 -->
- 可以插入
<IntegrityInformation>...</IntegrityInformation>块承载校验信息,转发文件后接收方可在导入前检查文件是否被改动 - 每个对象以类型 + ID 开始,例如
Hmi.Screen.Button;ID 在导出时自动生成 - 除根对象外,每个对象还含一个
CompositionNameXML 属性(预置值)。改按钮按下/释放时的 label 等场景必须改这个属性 - 每个对象含
AttributeList段,每个属性建模为 XML 元素(如BackColor),值建模为 XML 内容(如204, 204, 204) - 需要引用对象时含
LinkList段,每条链接含TargetID:- 目标对象在 XML 文件内 → 值是被引用对象的 ID 前加
# - 目标对象不在 XML 文件内 → 值是
@OpenLink,实际引用建模为下级 XML 元素
- 目标对象在 XML 文件内 → 值是被引用对象的 ID 前加
官方给出的层级关系示例(画面对象 → 链接 → 其他对象):
AttributeList of the screen object
/ Links from the screen object to other objects
Lower level objects
Screen object -> "Hmi.Screen.Screen", ID "1"
/AttributeList/
/Active/ ActiveDir!
/BackColor! Color!
/BackFillPattern! FillPattern!
/Name! Screen1!
/LinkList/
Hmi.Screen.Screen!
/LinkList/
/Template Target,ID "#2"
/LinkList/
Template! Name! Template1!
/LinkList/
/ObjectList/
/ObjectList/ /ObjectList/
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 6.3.1.1–6.3.1.2 节。
5.6 HMI 设备数据访问与工程期-运行期分工
5.6.1 能力边界:Openness 不是 HMI 通信库
这条必须写在最前面,因为它决定了架构:
| 事实 | 依据 |
|---|---|
| Openness 操作的是博途软件与离线工程文件;读写 PLC 实时变量 / DB 数据 / 在线监控不属于 Openness,推荐 Sharp7、S7netPlus、OPC UA | cn-community(七天学会 PLC 文章,明确区分 Openness 与 PLC 通信协议) |
下载/在线连接用 Openness 内置 DownloadProvider / OnlineProvider | 同上 |
| Openness API 不是 PLC 通信协议,也不是 S7 通信库,官方 V21 定义为 “API for automation of engineering workflows” | 腾讯云开发者社区转载「Hello工控」 |
| 官方 V15.1 手册明确安全红线:禁止用 Openness API 执行安全系统的检查或生成验收/认可数据 | official.md(V15.1 系统手册 PDF 条目) |
| 通过 TIA Portal 界面接口使用 Openness 时不能使用 HMI 编辑器 | official.md 5.2.8 节 |
架构结论:
工程期(Engineering time) 运行期(Runtime)
───────────────────────── ─────────────────────────
Openness 负责: 上位系统负责:
· HMI 画面/控件生成 · OPC UA 客户端订阅
· HMI 变量表与变量定义 · S7 通信读写过程值
· 文本/图形列表生成 · SCADA/MES 数据采集
· 连接与周期配置 · 历史归档与报表
· 编译/下载/上传/比较
5.6.2 官方给出的 HMI 目标查询与动态化脚本接口
手册 5.13.2.10 节 Working with dynamization & events for screen/screen items using scripts (RT Unified) 与 5.13.2.11 节 Working with dynamization for screens/screen items (RT Unified) 是把 HMI 行为逻辑也纳入工程的入口——这解决了”Openness 不能改 HMI 逻辑”的固有短板:画面里的动态化与事件脚本仍可在 TIA 中编辑,Openness 负责批量铺设画面结构与变量绑定。
另有 5.13.2.9 节 Getting Cross References for WinCC Unified (RT Unified):HMI 侧也有交叉引用服务,可用于改动前的 影响面分析(与 PLC 侧 CrossReferenceService 对称)。
5.6.3 已知会踩的坑
| 坑 | 现象 | 官方/社区结论 | 来源 |
|---|---|---|---|
| HMI 与 PLC 是不同 DeviceItem、暴露不同服务 | 取错服务导致 as 转换返回 null | HMI tag 与 PLC tag 属不同 DeviceItem、暴露不同服务;须取 PLC item 的 SoftwareContainer,cast 其 Software 为 PlcSoftware | en-blogs(Configuring PLC I/O Tags via XML) |
| 不要用 UI Automation 驱动 HMI 变量编辑器 | 点击/SendKeys 约 5 tags/sec 后触发限流,可能损坏项目数据库 | TIA 的 UI 自动化树不暴露日志列;应用 Openness API | en-blogs(⚠️ 已标注 API 签名存疑,但工程结论与官方 5.13.1 节一致) |
| HMI 没有 Classification | PLC 靠 TypeIdentifier(如 "System:Device.S71500")识别型号,HMI 不行 | 原文建议在模板里加标识符(如设备名含 HMI_TP)来识别 | en-blogs(CODESYS 应用指南第 8 章) |
| HMI 面板运行时映像更小、无等价脚本编辑器 | WinCC Comfort 的 Openness 支持只有子集 | WinCC Professional 与 WinCC Unified 完整暴露;WinCC Comfort 仅有子集(画面名、变量链接、基本属性) | en-blogs(Comparing WinCC HMI Projects,⚠️ 该来源的 new TiaPortal(new TiaPortalStartArguments()) / project.HmiTargets[0] 写法与 V21 官方手册的 TiaPortalMode 枚举不一致,以官方为准) |
| 遗留 WinCC Flexible 2008 SP5 没有可比 Openness 接口 | 归档 .hmi 工程无法脚本化 | 只能靠 Project Differencing(SP3 实验性)、导出 .emf 图形做矢量对比、或 Excel 宏调 WinCCflexible.exe /Automation(未文档化且可能在任意 SP 崩掉);官方建议迁移到 TIA Portal WinCC Comfort | en-blogs(同上) |
| 博途无插件时 Text list 只能用 XLSX 导入导出,Openness 中只能用 XML | 格式受限 | 明确记载 | en-blogs(CODESYS 应用指南第 8 章) |
来源:cn-community、en-blogs、official.md、github.md(对应条目已在正文标注)。
5.7 经典 WinCC vs Unified 能力对照表
下表以官方 V19 系统手册 5.12 / 5.13 两节的实际小节清单为准。“官方 API 支持”列的 ✅/⚠️/❌ 只反映手册层面是否有对应章节,不代表能力深度相同。
| 能力维度 | 经典 WinCC(Siemens.Engineering.Hmi / HmiTarget) | WinCC Unified(Siemens.Engineering.HmiUnified / HmiSoftware) |
|---|---|---|
| 根对象获取 | deviceItem.GetService<SoftwareContainer>().Software as HmiTarget(官方 5.7.2) | deviceItem.GetService<SoftwareContainer>().Software as HmiSoftware(官方 5.13.1.2) |
| 手册章节 | 5.12(共 14 小节) | 5.13.1(共 16 个属性分组小节) |
| 程序集 | Siemens.Engineering.Hmi.dll | Siemens.Engineering.HmiUnified.dll(Unified 扩展单独成程序集,博客实测需同时引用三程序集) |
| 画面枚举/查找 | ✅ ScreenComposition + Find | ✅ HmiScreenComposition + Find / 索引 / Contains() |
| 画面创建(对象模型) | ❌ 手册只给删除;创建走 Simatic ML 导入 | ✅ Screens.Create(name) |
| 画面删除 | ✅ 5.12.1.2 / 5.12.1.4(批量需先快照) | ✅ Screens.Delete(),亦可 (IEngineeringObject)objScreens[0].Invoke("Delete", null) |
| 画面文件夹 | ✅ ScreenFolder.Folders.Create/Find | ❌ 手册无对应小节(HmiSoftware 无 ScreenFolder) |
| 画面属性读写 | ❌ 5.12 未列画面属性表 | ✅ 19 个属性,含 Width/Height(DIU)、BackGraphic、ScreenNumber、Dynamizations |
| 画面模板 | ✅ ScreenTemplateFolder / ScreenTemplateComposition | ❌ 手册无独立模板集合(模板语义由 faceplate 容器承担) |
| 全局画面 / 弹出画面 / 滑入画面 | ✅ 全部支持导出导入(6.3.7.5 / 6.3.7.10 / 6.3.7.12) | ⚠️ 手册未列为独立类型;弹出语义接近 HmiScreenWindow |
| Permanent area | ✅ 6.3.7.5 导出 / 6.3.7.6 导入 | ❌ 无对应概念 |
| 控件集合 | ⚠️ Comfort 仅有子集(画面名、变量链接、基本属性) | ✅ HmiScreenItemBaseComposition,泛型 Create<T>(),40+ 控件类型 |
| 控件事件处理器 | ❌ 手册无 | ✅ EventHandlers(如 HmiCircleEventHandlerComposition)+ PropertyEventHandlers |
| 控件动态化 | ❌ 手册无 | ✅ Dynamizations(DynamizationBaseComposition)+ 5.13.2.11 节专章 |
| 脚本 | ✅ VB Script(VBScriptFolder / VBScriptUserFolder / VBScripts) | ✅ JavaScript(5.13.2.10 节,dynamization & events using scripts) |
| 变量表 | ✅ TagFolder → TagSystemFolder / TagTable / TagTableUserFolder / TagTableSystemFolder;DefaultTagTable | ✅ HmiTagTables / TagTableGroups(5.13.1.16 / 5.13.1.17) |
| 变量 | ✅ TagComposition + Tag(枚举 / Find / Delete) | ✅ HmiTagComposition + HmiTag,27 个属性,含 AcquisitionMode / AcquisitionCycle / LinearScaling / PlcTag / Persistent |
| UDT 变量与成员 | ⚠️ 仅在导入导出时受支持(6.3.2.5,Comfort/Advanced 不支持结构标签限值) | ✅ HmiTag.Members(HmiTagComposition)+ TagType 区分 simple/UDT/array |
| 日志变量 | ❌ 无此概念 | ✅ HmiTag.LoggingTags(HmiLoggingTagComposition → HmiLoggingTag,18 个属性) |
| 数据日志 / 报警 / 审计跟踪 | ❌ 5.12 无 | ✅ 5.13.1.3–5.13.1.6:AlarmClasses / AlarmLogs / AnalogAlarms / DiscreteAlarms / AuditTrails / DataLogs |
| 文本列表 | ✅ TextLists(TextListComposition) | ✅ SiVArc 侧 TextlistRules(5.18.13/5.18.14) |
| 图形列表 | ✅ GraphicLists(GraphicListComposition) | ✅ 通过 Object + DataLog 间接引用 |
| 周期 | ✅ Cycles(CycleComposition);标准周期不可删 | ✅ RuntimeSettings(5.13.1.11) |
| 连接 | ✅ Connections(ConnectionComposition) | ✅ Connections (RT Unified)(5.13.1.7,独立小节) |
| 画面交叉引用 | ❌ 手册无 | ✅ 5.13.1.13 → Accessing cross reference service on screens (P702) |
| 变量交叉引用 | ❌ 手册无 | ✅ 5.13.1.15 → Accessing tag cross reference service on tags (P816) |
| Simatic ML 导入导出 | ✅ 5 类画面 + 模板 + 脚本 + 列表 + 周期 + 连接 + 变量表 + 变量 | ⚠️ 6.3 节标题为 “HMI device”,主要面向经典;Unified 走对象模型 API |
| Excel 批量导入导出 | ❌ 官方无示例 | ✅ 官方应用示例 Entry 109792619(Excel Importer / Excel Exporter,订货号 16AV2151-0XB02-4AA5,基于 WinCC Unified V21) |
| CI 无人值守可用性 | ⚠️ Comfort 子集限制 | ✅ 官方示例专门提供 CLI 式批处理路径 |
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.7.2 / 5.12 / 5.13 章节清单与属性表;github.md 第 25 条(Unified 专有扩展
AllHmiSoftwares()/AsHmiSoftware()/AllHmiTargets()/AsHmiTarget(),并明确”Unified 与 Advanced 是两套不同类型体系,不能混用”);advanced.md(Excel 导入导出示例 109792619、WinCC 批量生成支持度差异)。
5.8 HMI 自动化在 CI 场景的实际价值与局限
5.8.1 官方给出的量化收益
| 数据 | 来源 |
|---|---|
| 单次项目生成从 180 分钟降到 75 分钟(变化率 -80%);总耗时 160 分钟,其中 Openness 占 75 分钟 | 官方统计(PLC技术网概述帖转述) |
官方应用示例 109792619(Excel Importer / Exporter)随附完整代码包 109792619_Excel_Importer_VX_X_CODE.zip(约 691 KB) | 官方应用示例页 |
| 受支持机型规模:汽车焊装线 30+ 工位,每块 faceplate 手动配置 2~3 小时且改错两台返工半天,脚本跑一遍不到一分钟 | cn-community(faceplate 批量配置实战) |
5.8.2 规模选型表(社区实测)
| 工位/画面数量 | 推荐方案 |
|---|---|
| < 5 | 手动 |
| 5 ~ 20 | 复制粘贴 |
| 20 ~ 100 | Openness 脚本 |
| > 100 | 脚本 + 画面模板 |
| 需频繁更新 | 库版本管理 + 脚本 |
⚠️ 派生层级不要超过两层;批量更新前务必整目录备份(更新不可逆)。
来源:cn-community(TIA Portal faceplate 批量配置实战,作者实测)。
5.8.3 官方 Excel 导入导出工作流(Entry 109792619)
这是唯一一条官方发布的、面向 WinCC Unified 的批量工作流,CI 集成价值极高:
| 要点 | 内容 |
|---|---|
| 硬性前提 | TIA Portal Openness 版本必须与 SIMATIC WinCC Unified 版本一致(示例基于 WinCC Unified V21,订货号 16AV2151-0XB02-4AA5;Excel 2010 及以上) |
| Excel 表结构 | 第 1 行是属性名(列顺序任意,Openness 按名字映射);第 2 行起每行一个对象 |
| 第 1 列固定为对象类型 | 如 HmiIOField,其余列为该对象的属性 |
| 映射粒度 | 一个 Excel 文件 = 一块新画面。要生成多块画面必须每个画面一个 Excel 文件;工具会给画面分配通用名称 |
| 重复运行会覆盖已生成的画面 | 官方明确警告 |
| 属性集差异 | 过程值可关联到 I/O 域,但不能关联到线条对象 |
| 特殊列 | 表中会用到 #TagDynamization、#Tag 这类特殊列用于变量动态化 |
| 交付物 | 完整代码包 109792619_Excel_Importer_VX_X_CODE.zip(约 691 KB)+ 文档,可直接复用而非从零开发 |
| 官方文档 | 仅给出 Excel 表结构,未公开 C# 源码正文(源码在配套 ZIP 中) |
来源:advanced.md「HMI 自动化:WinCC Unified 画面与对象的 Excel 批量导入导出」(Siemens 官方应用示例 109792619,英文;另有中文版页面)。
5.8.4 局限清单(务必在方案评审时逐条确认)
| 局限 | 后果 | 出处 |
|---|---|---|
| 不能逐行编辑块内程序逻辑 | LAD/FBD 网络、STL 语句无法程序化改写 | 官方(PLC技术网概述) |
| 面 plate 接口显示顺序与定义顺序可能不一致(有时按字母排序) | 按位置匹配会绑错变量 → 必须按名字匹配;接口名加 i/o 前缀 | cn-community(faceplate 实战) |
| faceplate 接口绑定的变量必须在 HMI 变量表里存在且作用域匹配 | 绑 PLC 侧符号名在绑定时不报错,只有运行时才暴露 | 同上 |
| 项目必须先关闭才能跑脚本 | 项目在 TIA Portal 里开着时脚本报「项目已被占用」(Openness 对项目文件持排他写锁) | cn-community、en-blogs |
| Openness 不能直接改 SCL/LAD 的 FB 代码体 | 只能建块 + 设接口 + 导入 PLCopen XML;从 C# 源码生成代码不被支持 | en-blogs(FAQ) |
| TiaPortal 只支持 Windows,Openness 无 Linux 版本 | CI Runner 必须跑在 Windows VM 上 | 官方 CI 工具集说明 |
| 西门子 Openness 程序集无法与 CI runner 捆绑(许可原因) | 纯公共 CI 无法实现完整构建/测试复现,必须本地验证 | HubWiz(TIA-Portal-CLI 项目作者自述) |
| 第三方 HMI 生态封装能力弱 | tia-openness-api-client 的 USAGE.md 明确列出:HMI 只能创建设备,软件层(画面/变量表)操作很有限,库主要聚焦 PLC | github.md 第 1 条 |
来源:官方手册、cn-community、en-blogs、github.md(各条已在正文标注出处)。
5.9 官方 GitHub 仓库中的 HMI 相关模块
5.9.1 tia-portal-applications 组织:四个 HMI 相关仓库
按 github.md 第 27 条(tia-addin-opc-ua-modelled-interface)与第 28/29/31 条的目录树,官方 HMI 相关仓库如下:
| 仓库 | 星标 | 许可 | 最后推送 | 用途 | 对本章的价值 |
|---|---|---|---|---|---|
tia-portal-applications/tia-addin-opc-ua-modelled-interface | 20 | Other | 2026-05-14 | 用 Openness 遍历项目自动生成 OPC UA 服务器的 user-modelled interface | 性能收益最多快 6 倍;S7-1200 与 S7-1500 R/H 型 PLC 本身不支持默认 SIMATIC server interface,此 Add-In 是它们的自动化出路。分支硬性规则:V17~V20 用 tia-addin-opc-ua-modelled-interface-v20 分支,V21+ 用 main。Add-In 安装路径:C:\Users\<user>\AppData\Roaming\Siemens\Automation\Portal V21\UserAddIns\OpcUa-UserModelledInterface.addin;VS 必须以管理员身份运行 |
tia-portal-applications/TIA-Add-In-ShowScripts | 15 | Other | 2024-12-17 | 把所有画面(screens)的 JavaScript 导出为可读文件到项目 UserFiles 目录,并额外产出一份 Excel 形式的全画面总览 | 最直接的「Openness × Excel」联动官方实现(Excel 只用于输出汇总,导出主体是 JS 文本) |
tia-portal-applications/TIA-Openness-Create-Synoptic-Unified | 5 | Other | 2023-11-20 | 导出 PLC 中各网络信息转为 JSON,供 WinCC Unified Runtime 集成(synoptic / 工艺流程图) | 命名约定是硬性前提:PLC 块注释里必须包含字符串 #Synoptik;输出固定到项目文件夹下 /UserFiles/Synoptic。作者自述这只是原型,目前还不能导出全部内容 |
tia-portal-applications/CWC-in-WinCC-Unified | 13 | Other | 2025-01-21 | 开发 CWC(Custom Web Control) 并导入项目的官方指南 | 属 Web 开发侧(HTML/JavaScript)而非 Openness 侧,但同属 UserFiles 目录机制——正是第 29 条 JSON 的消费端。同组织相关 CWC 实现:GaugeMeter(7★)、TableControl(9★)、Web-Toolbox_CWC(6★)、Custom-AlarmControl-Code(3★) |
三个仓库构成的完整链路:
PLC 块注释含 #Synoptik
→ TIA-Openness-Create-Synoptic-Unified → <项目>/UserFiles/Synoptic/*.json
→ CWC-in-WinCC-Unified 消费
画面 JavaScript
→ TIA-Add-In-ShowScripts → <项目>/UserFiles/*.js + 全画面 Excel 总览
来源:github.md 第 27、28、29、31 条。
5.9.2 siemens/tia-portal-openness-code-snippets 官方片段仓库中的 HMI 部分
第 23 条的目录树(git/trees/main?recursive=1)显示,官方 V20 片段仓库没有独立的 HMI 片段工程——src/ 下的 10 个工程全是 Plain.Setup / Plain.Step7 / Plain.Startdrive / Plain.Startdrive.Dcc / WithExtensions.* / ViewModel。HMI 相关的官方可执行样例在应用示例而非代码片段仓库:
- 官方应用示例 109792619(Excel Importer/Exporter)—— WinCC Unified
- 官方代码片段仓库
siemens/tia-portal-openness-code-snippets(67 stars,License “Other” / Siemens Royalty-free,SPDX 标 NOASSERTION,面向 TIA Portal Openness API V20,用 NUnit[TestFixture]组织且刻意不使用任何断言,只为让每个方法能单独点击 Run)
来源:github.md 第 10、23 条;advanced.md(官方应用示例 109792619)。
5.9.3 社区 faceplate 批量配置实战代码(CSDN)
这是 raw 资料中唯一一份完整的经典/Advanced HMI 画面遍历 + 控件改属性代码,可作参考(⚠️ 其中 project.Devices[0].DeviceItems[0] as HmiTarget 的类型转换写法与官方 GetService<SoftwareContainer>().Software as HmiTarget 不一致,须按官方改正):
using Siemens.Engineering;
using Siemens.Engineering.HW;
using Siemens.Engineering.Hmi;
TiaPortal tiaPortal = TiaPortal.GetProcesses()[0].Attach();
Project project = tiaPortal.Projects.Open(new FileInfo(@"C:\Projects\MyProject.ap16"));
// ⚠️ 官方写法应为:
// HmiTarget hmiTarget = project.Devices[0].GetService<SoftwareContainer>()?.Software as HmiTarget;
HmiTarget hmiTarget = project.Devices[0].DeviceItems[0] as HmiTarget;
HmiScreen screen = hmiTarget.ScreenFolder.Screens.Find("MainScreen");
foreach (var screenItem in screen.ScreenItems)
{
if (screenItem is HmiFaceplateContainer faceplate)
{
string prefix = GetPrefixFromConfig(faceplate.Name);
foreach (var interface in faceplate.Interface)
interface.Value = BuildTagName(prefix, interface.Name); // 按名字匹配,不按位置
}
}
project.Save();
project.Close();
配套的工位前缀表(示例):
InstanceName,VarPrefix
MotorPanel_01,Station01_Motor
MotorPanel_02,Station02_Motor
作者总结的两个隐蔽坑:
- 接口显示顺序与定义顺序可能不一致(有时按字母排序)→ 脚本按名字匹配而非按位置匹配;接口名加
i/o前缀(iStartCmd、oRunStatus) - faceplate 接口绑定的变量必须在 HMI 变量表里存在且作用域匹配;绑 PLC 侧符号名在绑定时不报错,只有运行时才暴露
低门槛替代方案(作者给出):用 Openness 把画面导出成 XML,用 Python/PowerShell 批量替换变量名再导入——门槛低但精度低,适合变量名规律性强、替换规则简单的场景。
来源:cn-community《TIA Portal faceplate 批量配置实战:从手动到 Openness 自动化》(CSDN 博主 weixin_31659095,TIA Portal V16 及以上;⚠️ 原文已声明”API 各版本有差异,需按版本文档核对”)。
5.9.4 第三方封装的 HMI 扩展方法(官方 NuGet 核心扩展包)
github.md 第 25 条(Siemens.Collaboration.Net.TiaPortal.Openness.Extensions,V21 最新版 21.0.1765368043,目标框架 .NET Framework 4.8)列出了两套不能混用的 HMI 扩展:
// WinCC Unified 专有扩展
AllHmiSoftwares() // 遍历全部 Unified HMI 软件
AsHmiSoftware() // Device → HmiSoftware
screens.AddOrGet("Conveyors") // 按名取或建画面
ScreenItems.AddOrGet<HmiSlider>("TargetVelocity") // 按名取或建控件
screen.ParentComposition()
ScreenItems.Typed<HmiTextBox>()
Buttons() / Sliders() / AlarmControls()
// Advanced / Professional 走另一套
AllHmiTargets()
AsHmiTarget()
// 按名取或建的完整写法
Screen conveyorsScreen = screens.AddOrGet("Conveyors");
HmiSlider velocitySlider = conveyorsScreen.ScreenItems.AddOrGet<HmiSlider>("TargetVelocity");
⚠️ V21 硬性分水岭(官方 NuGet 包 README 原文摘录):“TIA Portal Openness Extensions V21 are only compatible with TIA Portal V21 and above. TIA Portal V21 introduces breaking changes in the Openness API. Applications built against TIA Portal versions below V21 are not compatible with V21 and above.” 版本映射规则:20.* = TIA Portal V20, 21.* = TIA Portal V21。
来源:github.md 第 25 条。
本章速查
- 两套 HMI 类型体系不可混用:经典 WinCC =
Siemens.Engineering.Hmi/HmiTarget(手册 5.12,共 14 小节,以删除与枚举为主);WinCC Unified =Siemens.Engineering.HmiUnified/HmiSoftware(手册 5.13,共 16 个属性分组小节,创建能力完整)。第一行代码就写命名空间别名using HmiTarget = Siemens.Engineering.Hmi.HmiTarget;。 - 官方硬限制:通过 TIA Portal 界面接口使用 Openness 时不能使用 HMI 编辑器(手册 5.2.8)。CI 流水线必须
TiaPortalMode.WithoutUserInterface;官方工程桌面机才用WithUserInterface。 - HMI 变量表与变量的两条主线:Unified 侧走对象模型
hmiSoftware.HmiTags.Create(name[, tableName])/HmiTagTables.Find(...).Tags,属性 27 个;批量场景走官方应用示例 Entry 109792619(Excel Importer/Exporter,一个 Excel 文件 = 一块画面,重复运行会覆盖,Openness 版本必须与 WinCC Unified 版本一致)。 - ⚠️ 日志变量的正确 API 是
HmiTag.LoggingTags→HmiLoggingTag(LoggingMode/Cycle/CycleFactor/TriggerMode/TriggerTag/TriggerTagBitNumber/SmoothingMode等 18 个属性),不是博客流传的HmiTag.Logging : HmiTagLogging——后者在官方 V19 手册中 0 命中,⚠️ 该来源 API 签名存疑。且TagTable.Export的固定 schema 不含日志列,导入会静默清空日志标志,必须用 Openness API 重建。 - 画面/模板/全局画面/弹出画面/滑入画面全部支持 Simatic ML 导入导出(手册 6.1.1 Table 6-3 + 6.3.7.5 / 6.3.7.10 / 6.3.7.12 / 6.3.7.13)。经典 WinCC 的画面创建官方路径是”模板导出 → 外部改 XML →
Import(..., ImportOptions.Override)”,不是Screens.Create()。 - UDT 类型 HMI 变量导入必须用工程库(project library)中的版本化数据类型,全局库不支持;未指定变量表时外部 HMI 变量导入会被取消(名必须全工程唯一);Comfort/Advanced 不支持把结构标签赋给结构标签元素的上下限。
- 脚本语言分家:经典 WinCC = VB Script(
VBScriptFolder/VBScripts),WinCC Unified = JavaScript(手册 5.13.2.10 / 5.13.2.11)。两套脚本 API 不可互换。 - 推荐工具链:
tia-portal-applications组织的四个 HMI 仓库构成完整链路——TIA-Openness-Create-Synoptic-Unified(PLC 网络 →UserFiles/SynopticJSON,块注释须含#Synoptik)+CWC-in-WinCC-Unified(消费 JSON)+TIA-Add-In-ShowScripts(画面 JS 导出 + Excel 总览)+tia-addin-opc-ua-modelled-interface(OPC UA 建模接口,客户端读写最多快 6 倍)。 - 三大工程陷阱:项目必须先关闭(Openness 对
.ap持排他写锁);faceplate 接口按名字匹配而非位置(显示顺序可能按字母排序);不要用 UI Automation 驱动 HMI 变量编辑器(约 5 tags/sec 即触发限流,可能损坏项目数据库)。
第6章 在线连接、下载与调试闭环
第 6 章 在线连接、下载与调试闭环
本章导读
本章讲的是 Openness 里唯一一组会真正改变物理设备状态的 API:上线(OnlineProvider.GoOnline / GoOffline)、下载(DownloadProvider.Download)、上传(StationUploadProvider.StationUpload)与比较(CompareTo / CompareToOnline)。这套 API 的核心特征是回调驱动的对话处理——TIA Portal 在下载过程中会抛出 40 余种配置对话框,脚本必须在 pre / post delegate 里逐类 as 分派并给出选择,否则 EngineeringTargetInvocationException 会中止整个下载。本章按官方 V19 手册 5.11 节(Functions for accessing the data of a PLC device)的结构组织:先讲 OnlineProvider 的状态机与连接参数,再讲 DownloadProvider.Download 的四个参数与配置构造链,然后逐一拆解 43 种 Download.Configurations 提示类型的处理方式,最后落到编译前置、停止/启动、上传、比较、能力边界与 PLCSIM 联调。
本章包含两条重要的更正(详见 6.5 与 6.11):
- 不存在
DownloadWithoutStop与SoftwareDownload这两个 API。DownloadWithoutStop在 V21 PublicAPI XML 中检索为 0 命中;“不停机下载”是靠pre/postdelegate 里控制StopModules/StartModules实现的。 DownloadOptions实际是四个值的位标志(None/Hardware/Software/SoftwareOnlyChanges),不是两个。
6.1 OnlineProvider:上线、下线与状态机
6.1.1 服务获取与三个必备检查
// 官方 5.11.2.4 节:判定一个 DeviceItem 的在线状态
public static OnlineState GetOnlineState(DeviceItem deviceItem)
{
OnlineProvider onlineProvider = deviceItem.GetService<OnlineProvider>();
return onlineProvider.State;
}
三条官方约束:
| 约束 | 说明 |
|---|---|
| 可能返回 null | 官方注释原文:“Only cpu device items can provide OnlineProvider service”。非 CPU 设备项(机架、模块、HMI、驱动模块)无此服务 |
上线前必须检查 Configuration.IsConfigured | 官方 5.11 节明确:未配置连接参数的设备项调用 GoOnline() 会失败。IsConfigured 返回 true 时无需再调 ApplyConfiguration |
| 上线/下线是两个显式方法,不是布尔属性赋值 | GoOnline() / GoOffline() |
6.1.2 OnlineState 全部枚举值
官方列举的在线状态(手册 5.11.2.4 节):
| 状态 | 含义 |
|---|---|
| Offline | PLC 处于离线 |
| Connecting | PLC 已连接(连接中) |
| Online | 已在线 |
| Disconnecting | PLC 已断开(断开中) |
| Incompatible | 不兼容 |
| Not reachable | 不可达 |
| Protected | 受保护 |
OnlineProvider.State 是判断”是否已在线”的唯一受支持途径。
6.1.3 批量扫描并上线全部 PLC 的完整实现
这是 CI 场景最需要的骨架。官方手册在 5.11.2.4 节给出的”遍历所有设备判状态”示例只读状态不执行动作,下面在其基础上补齐判空、配置检查与上线/下线,配上重试与状态等待,构成可直接落地的批量实现:
using System;
using System.Collections.Generic;
using System.Threading;
using Siemens.Engineering;
using Siemens.Engineering.HW;
using Siemens.Engineering.Online;
public sealed class PlcConnectionReport
{
public string DevicePath { get; set; }
public string TypeIdentifier { get; set; }
public OnlineState Before { get; set; }
public OnlineState After { get; set; }
public bool WentOnline { get; set; }
public string Error { get; set; }
public override string ToString() =>
$"[{DevicePath}] {TypeIdentifier} {Before} -> {After}" +
(WentOnline ? " ONLINE" : "") +
(string.IsNullOrEmpty(Error) ? "" : $" ERROR: {Error}");
}
/// <summary>递归遍历工程内所有 DeviceItem,对每个能提供 OnlineProvider 的项执行上下线。</summary>
public static class BulkOnline
{
/// <summary>等待某设备进入期望状态的最长时间与轮询间隔。</summary>
private static readonly TimeSpan StateTimeout = TimeSpan.FromSeconds(60);
private static readonly TimeSpan PollInterval = TimeSpan.FromMilliseconds(500);
public static List<PlcConnectionReport> GoOnlineAll(Project project,
Func<DeviceItem, bool> filter = null)
{
var reports = new List<PlcConnectionReport>();
foreach (Device device in project.Devices)
foreach (DeviceItem deviceItem in device.DeviceItems)
{
if (filter != null && !filter(deviceItem)) continue;
OnlineProvider onlineProvider = deviceItem.GetService<OnlineProvider>();
if (onlineProvider == null) continue; // 非 CPU 设备项无此服务
var report = new PlcConnectionReport
{
DevicePath = device.Name + " / " + deviceItem.Name,
TypeIdentifier = SafeTypeIdentifier(deviceItem),
Before = onlineProvider.State
};
try
{
if (onlineProvider.Configuration.IsConfigured)
{
if (onlineProvider.State != OnlineState.Online)
onlineProvider.GoOnline();
report.After = WaitForState(onlineProvider, OnlineState.Online);
report.WentOnline = report.After == OnlineState.Online;
}
else
{
// 未配置连接参数:GoOnline() 会失败,此处直接跳过而不是硬抛
report.After = onlineProvider.State;
report.Error = "Configuration.IsConfigured == false(连接参数未配置)";
}
}
catch (EngineeringException ex)
{
report.After = SafeState(onlineProvider);
report.Error = ex.Message;
}
reports.Add(report);
}
return reports;
}
public static void GoOfflineAll(Project project)
{
foreach (Device device in project.Devices)
foreach (DeviceItem deviceItem in device.DeviceItems)
{
OnlineProvider onlineProvider = deviceItem.GetService<OnlineProvider>();
if (onlineProvider == null) continue;
if (onlineProvider.State == OnlineState.Offline) continue;
onlineProvider.GoOffline();
WaitForState(onlineProvider, OnlineState.Offline);
}
}
/// <summary>只读扫描:不改任何状态,用于报告与前置校验。</summary>
public static List<PlcConnectionReport> ScanAll(Project project)
{
var reports = new List<PlcConnectionReport>();
foreach (Device device in project.Devices)
foreach (DeviceItem deviceItem in device.DeviceItems)
{
OnlineProvider p = deviceItem.GetService<OnlineProvider>();
if (p == null) continue;
reports.Add(new PlcConnectionReport
{
DevicePath = device.Name + " / " + deviceItem.Name,
TypeIdentifier = SafeTypeIdentifier(deviceItem),
Before = p.State,
After = p.State
});
}
return reports;
}
private static OnlineState WaitForState(OnlineProvider provider, OnlineState target)
{
DateTime deadline = DateTime.UtcNow + StateTimeout;
OnlineState s = provider.State;
while (s != target && DateTime.UtcNow < deadline)
{
Thread.Sleep(PollInterval);
s = provider.State;
}
return s;
}
private static OnlineState SafeState(OnlineProvider p)
{
try { return p.State; } catch { return OnlineState.Not reachable; }
}
private static string SafeTypeIdentifier(DeviceItem item)
{
try { return item.TypeIdentifier; } catch { return "?"; }
}
}
⚠️ 已知卡死坑(多来源一致,必须写进 CI 设计):TIA Portal 弹出模态对话框(保存、兼容性、库冲突)时,Openness 调用会无限挂起。因此 CI 场景务必用 WithoutUserInterface,并为每个可能阻塞的调用设超时;OnlineState 里出现 Connecting / Disconnecting 时不要立即重试。
OnlineProvider 在 R/H 冗余系统中有扩展形态:手册 5.11.2.9 节提供 RHOnlineProvider,可 GoOnlineToPrimary() / GoOnlineToBackup(),并用 PrimaryState / BackupState 读两侧状态。冗余下装(5.11.2.11)另有专门 API。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.11.2.4 节(在线状态枚举、遍历示例)、5.11.2.9 节(RHOnlineProvider)、5.11.2.12 节(建立/断开在线连接)、5.11.1.3 节;advanced.md(
OnlineProvider能力边界与卡死坑);cn-community(OnlineProvider.State+GoOffline()强制离线路线)。
6.1.4 连接参数的枚举与设置(ConnectionConfiguration)
下载与上线共用同一套 ConnectionConfiguration 对象模型。结构层级:
OnlineProvider.Configuration / DownloadProvider.Configuration → ConnectionConfiguration
└─ Modes : ConfigurationMode 集合
└─ PcInterfaces : ConfigurationPcInterface 集合
├─ TargetInterfaces : ConfigurationTargetInterface 集合
│ └─ Addresses : ConfigurationAddressComposition
│ └─ Create(ip) → ConfigurationAddress
├─ Subnets : ConfigurationSubnet 集合
│ ├─ Addresses : ConfigurationAddressComposition
│ └─ Gateways : ConfigurationGateway 集合
│ └─ Addresses : ConfigurationAddressComposition
└─ ApplyConfiguration(...) → bool
枚举全部模式、接口与槽位(官方 5.11.2.5 节原样):
public static void EnumerateConnectionModesOfPLC(DeviceItem deviceItem)
{
OnlineProvider onlineProvider = deviceItem.GetService<OnlineProvider>();
if (onlineProvider == null)
{
return; // Only cpu device items can provide OnlineProvider service
}
ConnectionConfiguration configuration = onlineProvider.Configuration;
foreach (ConfigurationMode mode in configuration.Modes)
{
Console.WriteLine("Mode name:{0}", mode.Name);
foreach (ConfigurationPcInterface pcInterface in mode.PcInterfaces)
{
Console.WriteLine("PcInterface name:{0}", pcInterface.Name);
Console.WriteLine("PcInterface number:{0}", pcInterface.Number);
foreach (ConfigurationTargetInterface targetInterface in pcInterface.TargetInterfaces)
{
Console.WriteLine("TargetInterface:{0}", targetInterface.Name);
}
}
}
}
按名取模式 / PC 接口 / 槽位(官方原样,注意 Find 的两参签名):
public static ConfigurationTargetInterface
GetTargetInterfaceForOnlineConnection(OnlineProvider onlineProvider)
{
ConnectionConfiguration configuration = onlineProvider.Configuration;
ConfigurationMode mode = configuration.Modes.Find("PN/IE");
ConfigurationPcInterface pcInterface = mode.PcInterfaces.Find("PLCSIM", 1);
ConfigurationTargetInterface slot = pcInterface.TargetInterfaces.Find("2 X3");
return slot;
}
三种设置连接参数的方式(官方 5.11.2.5 节,全部是”设置后 ApplyConfiguration 再 GoOnline”):
// 方式 1:按槽位
public static void SetConnectionWithSlot(OnlineProvider onlineProvider)
{
ConnectionConfiguration configuration = onlineProvider.Configuration;
ConfigurationMode mode = configuration.Modes.Find(@"PN/IE");
ConfigurationPcInterface pcInterface = mode.PcInterfaces.Find("PLCSIM", 1);
ConfigurationTargetInterface slot = pcInterface.TargetInterfaces.Find("2 X3");
configuration.ApplyConfiguration(slot);
onlineProvider.GoOnline();
}
// 方式 2:按网关地址
public static void SetConnectionWithGatewayAddress(OnlineProvider onlineProvider,
string subnetName, string gatewayAddressName)
{
ConnectionConfiguration configuration = onlineProvider.Configuration;
ConfigurationMode mode = configuration.Modes.Find(@"PN/IE");
ConfigurationPcInterface pcInterface = mode.PcInterfaces.Find("PLCSIM", 1);
ConfigurationSubnet subnet = pcInterface.Subnets.Find(subnetName);
ConfigurationAddress gatewayAddress = subnet.Addresses.Find(gatewayAddressName);
configuration.ApplyConfiguration(gatewayAddress);
onlineProvider.GoOnline();
}
// 方式 3:按子网地址
public static void SetConnectionWithSubnetAddress(OnlineProvider onlineProvider, string subnetName)
{
ConnectionConfiguration configuration = onlineProvider.Configuration;
ConfigurationMode mode = configuration.Modes.Find(@"PN/IE");
ConfigurationPcInterface pcInterface = mode.PcInterfaces.Find("PLCSIM", 1);
ConfigurationSubnet subnet = pcInterface.Subnets.Find(subnetName);
ConfigurationAddressComposition addresses = subnet.Addresses;
configuration.ApplyConfiguration(addresses[0]);
onlineProvider.GoOnline();
}
⚠️ 三条重要官方警告:
- “All the connection parameters previously set are overwritten when you set the connection parameters.”
- 如果已经在 TIA Portal 里直接设置过连接参数,不需要再调
ApplyConfiguration。 - 若
ApplyConfiguration调用时已有到 PLC 的在线连接,会抛异常。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.11.2.5 节(全部代码与警告均为官方原文)。
6.2 DownloadProvider 与 Download 的四个参数
6.2.1 服务获取与”可能为 null”
// 官方 5.11.1.3 节原文
DeviceItem deviceItem = ...;
DownloadProvider downloadProvider = deviceItem.GetService<DownloadProvider>();
if (downloadProvider != null)
{
// ...
}
官方说明:“If a DeviceItem represents a downloadable target, an instance of DownloadProvider will be returned on GetService call, else the service will return null.”
6.2.2 四个参数(手册原表)
⚠️ 这里有一个必须讲清的口径差异:手册正文有一处把参数描述为 “The Download method has four parameters which are IConfiguration object, two delegates and DownloadOptions”——这是四类参数(两个 delegate 算一类)。而任务口径中的”四个参数”指含
onlineAddress的五参数重载。两个重载都存在,下表按官方参数表逐行对照。
| 参数名 | 类型 | 说明 | 必需性 |
|---|---|---|---|
configuration | Siemens.Engineering.Connection.IConfiguration | 到设备的连接配置 | 必需 |
onlineAddress | Siemens.Engineering.Connection.ConfigurationAddress | 可选,用于下载到”已改变 IP 地址”的在线 PLC | 可选(构成五参数重载) |
preDownloadConfigurationDelegate | Siemens.Engineering.Download.DownloadConfigurationDelegate | 下载前配置检查回调 | 必需 |
postDownloadConfigurationDelegate | Siemens.Engineering.Download.DownloadConfigurationDelegate | 下载后配置检查回调 | 必需 |
downloadOptions | Siemens.Engineering.Download.DownloadOptions | 下载选项 | 必需 |
两个重载的官方调用形态:
// 四参数重载
IConfiguration targetConfiguration = ...;
DownloadConfigurationDelegate preDownloadDelegate = ...;
DownloadConfigurationDelegate postDownloadDelegate = ...;
DownloadOptions loadOption = DownloadOptions.Hardware | DownloadOptions.Software;
DownloadResult result = downloadProvider.Download(targetConfiguration,
preDownloadDelegate, postDownloadDelegate, loadOption);
// 五参数重载(在线 IP 与离线组态 IP 不一致时)
ConfigurationAddress onlineAddress = ...;
DownloadResult result = downloadProvider.Download(targetConfiguration, onlineAddress,
preDownloadDelegate, postDownloadDelegate, loadOption);
6.2.3 delegate 的正式签名
// 该 delegate 声明在 Siemens.Engineering.dll 中(官方原文)
public delegate void
Siemens.Engineering.Download.DownloadConfigurationDelegate(
Siemens.Engineering.Download.Configurations.DownloadConfiguration configuration);
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.11.1.9 节。
6.3 IConfiguration 的构造链
6.3.1 两个实现类
手册 5.11.1.3 节原文:IConfiguration 接口由 ConfigurationAddress 与 ConfigurationTargetInterface 两个类实现,两者都从 ConnectionConfiguration 实例获取;ConnectionConfiguration 可从 DownloadProvider.Configuration 或 OnlineProvider.Configuration 属性获得。
6.3.2 标准构造链(四步)
// 官方 5.11.1.3 节原样
DownloadProvider downloadProvider = null;
ConnectionConfiguration configuration = downloadProvider.Configuration;
ConfigurationMode configurationMode = configuration.Modes.Find("PN/IE");
ConfigurationPcInterface pcInterface = configurationMode.PcInterfaces.Find("Intel(R) Ethernet Connection I217-LM", 1);
IConfiguration targetConfiguration = pcInterface.TargetInterfaces[0];
四步语义:
| 步骤 | API | 说明 |
|---|---|---|
| 1 | downloadProvider.Configuration | 拿 ConnectionConfiguration(也可用 OnlineProvider.Configuration) |
| 2 | .Modes.Find("PN/IE") | 按名取连接模式。Find 单参按名 |
| 3 | .PcInterfaces.Find("适配器名", 1) | 两参:名称 + 序号。名称要与 EnumerationConnectionModesOfPLC 打印出的 pcInterface.Name 完全一致 |
| 4 | .TargetInterfaces[0] | 取第一个目标接口(槽位)。也可用 Find("2 X3") 按槽位名取 |
⚠️ 工程建议:PC 接口名(网卡名)在不同工程机上是硬件相关的,直接硬编码
"Intel(R) Ethernet Connection I217-LM"会在换了网卡的机器上失败。生产代码应先跑EnumerateConnectionModesOfPLC打印候选,再按序号或正则选取。
6.3.3 ⚠️ 陷阱:在线 IP 与离线组态 IP 不一致
这是下载事故的高发点,手册用整段文字专门说明:
“If the projected IP-address differs from the already downloaded online IP-address, an additional parameter has to use. This is for identify the correct online PLC.”
必须用 ConfigurationAddress 显式指定在线 IP:
// 官方 5.11.1.3 节原样
DownloadProvider downloadProvider = ...;
ConnectionConfiguration configuration = downloadProvider.Configuration;
ConfigurationMode configurationMode = configuration.Modes.Find("PN/IE");
ConfigurationPcInterface pcInterface = configurationMode.PcInterfaces.Find("Intel(R) Ethernet Connection I217-LM", 1);
IConfiguration targetConfiguration = pcInterface.TargetInterfaces[0];
// Create the address object to reach the PLC online.
ConfigurationTargetInterface targetConfigurationIF = targetConfiguration as ConfigurationTargetInterface;
ConfigurationAddressComposition downloadAddresses = targetConfigurationIF.Addresses;
ConfigurationAddress onlineAddress = downloadAddresses.Create("10.119.57.223");
DownloadResult result = downloadProvider.Download(targetConfigurationIF, onlineAddress,
preDownloadDelegate, postDownloadDelegate, loadOption);
关键点:onlineAddress 必须由同一 ConfigurationTargetInterface 的 Addresses.Create(ip) 生成——不能用别的接口/别的接口卡的地址对象。
判断要不要用五参数重载的实用规则:
| 场景 | 是否需要 onlineAddress |
|---|---|
| 首次下载,PLC 出厂 IP = 组态 IP | 不需要 |
| PLC 已被下过程序,在线 IP 与组态 IP 相同 | 不需要(但显式传参更安全) |
| PLC 在线 IP 与组态 IP 不同(现场改过 IP / 多设备 IP 错位 / 从别的工程下载过) | 必须,否则会下到错误设备 |
| 批量下载多台 PLC,各自 IP 不同 | 必须,且每台都要重建 targetConfiguration 与 onlineAddress |
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.11.1.3 节(
ConfigurationAddress段);advanced.md(同主题,并给出 IP10.119.57.223的官方示例值)。
6.4 下载全流程步骤表
| # | 步骤 | API / 操作 | 关键点与失败模式 |
|---|---|---|---|
| 1 | 确认离线 | 检查 OnlineState;必要时 GoOffline() | 官方编译与 CAx 子模块导出都要求 PLC 离线。GenerateBlocksFromSource() 前置条件也包含 PLC 必须离线 |
| 2 | 取得 DownloadProvider | deviceItem.GetService<DownloadProvider>() | 返回 null 表示该 DeviceItem 不是可下载目标 |
| 3 | (可选)显式编译 | (ICompilable)deviceItem.GetService<ICompilable>().Compile(),或 plcSoftware.GetService<ICompilable>().Compile() | 官方原话:“Since compilation is a part of download, it is recommended to compile before download to get a full feedback about the compile results.” 编译是下载的一部分,但先编一次能拿到完整反馈 |
| 4 | 构造 IConfiguration | Configuration.Modes.Find("PN/IE") → PcInterfaces.Find(name, 1) → TargetInterfaces[0] | 第 3 步的 Find 是两参(名 + 序号) |
| 5 | (按需)构造 ConfigurationAddress | ((ConfigurationTargetInterface)targetConfiguration).Addresses.Create(ip) | 仅当在线 IP ≠ 组态 IP;必须是同一接口的地址对象 |
| 6 | 实现 pre delegate | void Pre(DownloadConfiguration) | 必须处理所有遇到的配置对话框,否则 EngineeringTargetInvocationException 中止下载。手册警告:“You should avoid any modifications other than those required to resolve the download configurations in their delegate implementation.” |
| 7 | 实现 post delegate | void Post(DownloadConfiguration) | 同上;StartModules(启动)出现在这里 |
| 8 | 调用 Download | downloadProvider.Download(cfg[, addr], pre, post, options) | 配置无效时抛 EngineeringTargetInvocationException 并中止;delegate 内未处理异常时抛 EngineeringDelegateInvocationException |
| 9 | 检查 DownloadResult | result.State / ErrorCount / WarningCount / Messages | result.State == DownloadResultState.Error 即失败。Messages 是 CompilerResultMessageComposition 同构的递归结构 |
| 10 | (可选)GoOnline 验证 | onlineProvider.GoOnline() | 上线后再 CompareToOnline() 确认在线版本与离线一致 |
| 11 | 保存并断开 | project.Save();finally 中反订阅事件、tiaPortal.Dispose() | Save 落盘、Dispose 释放内存锁,两件事 |
官方 DownloadResult 读取示例(手册 5.11.1.3 节原样):
[STAThread]
static void Main()
{
...
DownloadProvider downloadProvider = ...;
IConfiguration targetConfiguration = ...;
DownloadConfigurationDelegate preDownloadDelegate = PreConfigureDownload;
DownloadConfigurationDelegate postDownloadDelegate = PostConfigureDownload;
DownloadResult result = downloadProvider.Download(targetConfiguration,
preDownloadDelegate, postDownloadDelegate,
DownloadOptions.Hardware | DownloadOptions.Software);
if (result.State == DownloadResultState.Error)
{
// Handle error state
}
WriteDownloadResults(result);
...
}
private void WriteDownloadResults(DownloadResult result)
{
Console.WriteLine("State:" + result.State);
Console.WriteLine("Warning Count:" + result.WarningCount);
Console.WriteLine("Error Count:" + result.ErrorCount);
RecursivelyWriteMessages(result.Messages);
}
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.11.1.3 节。
6.5 DownloadOptions:位标志与非法组合
6.5.1 官方枚举定义(V19/V21 手册原样)
[Flags]
public enum DownloadOptions
{
None = 0, // Download nothing
Hardware = 1, // Download hardware. Complete download when used alone,
// delta download when used in combination with Software
// or SoftwareOnlyChanges.
Software = 2, // Download software all. Do not combine with SoftwareOnlyChanges
SoftwareOnlyChanges = 4 // Download software only changes. Do not combine with Software
}
6.5.2 组合与系统反应(官方表格原样)
DownloadOptions 组合 | 系统反应 |
|---|---|
| none | exception |
Hardware | HW complete |
Software | SW complete |
Hardware | Software | HW delta, SW complete |
SoftwareOnlyChanges | SW delta |
Hardware | SoftwareOnlyChanges | HW delta, SW delta |
Software | SoftwareOnlyChanges | exception |
Hardware | Software | SoftwareOnlyChanges | exception |
(DownloadOptions)11 | exception |
四条硬约束:
Software与SoftwareOnlyChanges互斥,同时使用直接抛异常Hardware单独用 = HW 完整下载;与Software或SoftwareOnlyChanges组合 = HW 增量下载None也会抛异常(等于没给下载内容)- 任何未列出的位组合(如
(DownloadOptions)11= 1+2+8)都抛异常——不要自己拼位值
6.5.3 ⚠️ 更正:不存在 DownloadWithoutStop 与 SoftwareDownload
这是本章最重要的一条更正,因为它在网络文章与 AI 生成内容中流传很广:
| 流传说法 | 官方事实 |
|---|---|
DownloadOptions.DownloadWithoutStop | ❌ 该 API 不存在。在 V21 PublicAPI XML 中检索 DownloadWithoutStop 为 0 命中 |
DownloadProvider.SoftwareDownload(...) 或 DownloadOptions.SoftwareDownload | ❌ 该 API 不存在。检索 SoftwareDownload 为 0 命中 |
| ”不停机下载”是一个下载选项 | ❌ 不是。不停机下载是通过 pre/post delegate 里控制 StartModules / StopModules 实现的,不是独立选项 |
真正的实现方式(官方 5.11.1.8 节原样):
// 下载前:置 STOP
public void ConfigurePreDownload(DownloadConfiguration downloadConfiguration)
{
StopModules stopModules = downloadConfiguration as StopModules;
if (stopModules != null)
{
// Puts PLC in "Stop" mode
stopModules.CurrentSelection = StopModulesSelections.StopAll;
}
}
// 下载后:置 RUN
public void ConfigurePostDownload(DownloadConfiguration downloadConfiguration)
{
StartModules startModules = downloadConfiguration as StartModules;
if (startModules != null)
{
// Puts PLC in "Start" mode
startModules.CurrentSelection = StartModulesSelections.StartModule;
}
}
⚠️ 注意这段官方示例的一个语义事实:StopModules 出现在 pre delegate(下载前停),StartModules 出现在 post delegate(下载后启)。因此”下载期间不停机”在 Openness 里不是一次调用能实现的——它必然经历”停 → 下载 → 启”三个阶段。若业务上要求”绝不停机”,Openness 做不到,必须走旁路方案(如在线变更、备机切换、或用 S7 通信做在线修改)。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.11.1.3 节(
DownloadOptions枚举与组合表)、5.11.1.8 节 Running and stopping PLC;advanced.md(DownloadWithoutStop在 V21 PublicAPI XML 中 0 命中的核实结论)。
6.6 43 种 Download.Configurations 提示类型与分类处理
6.6.1 类型体系(三层基类)
DownloadConfiguration (基类,属性 Message : string 只读)
├── DownloadSelectionConfiguration (基类,无额外属性;所有子类必须给选择)
│ ├── StartModules CurrentSelection : StartModulesSelections
│ ├── StopModules CurrentSelection : StopModulesSelections
│ ├── AllBlocksDownload CurrentSelection : AllBlocksDownloadSelections
│ ├── OverwriteSystemData CurrentSelection : OverwriteSystemDataSelections
│ ├── ConsistentBlocksDownload CurrentSelection : ConsistentBlocksDownloadSelections
│ ├── AlarmTextLibrariesDownload CurrentSelection : AlarmTextLibrariesDownloadSelections
│ ├── ProtectionLevelChanged CurrentSelection : ProtectionLevelChangedSelections
│ ├── ActiveTestCanBeAborted CurrentSelection : ActiveTestCanBeAbortedSelections
│ ├── ResetModule CurrentSelection : ResetModuleSelections
│ ├── LoadIdentificationData CurrentSelection : LoadIdentificationDataSelections
│ ├── DifferentTargetConfiguration CurrentSelection : DifferentTargetConfigurationSelections
│ ├── InitializeMemory CurrentSelection : InitializeMemorySelections
│ ├── ExpandDownload CurrentSelection : ExpandDownloadSelections
│ ├── ActiveTestCanPreventDownload Get/Set CurrentSelection
│ └── DataBlockReinitialization CurrentSelection : DataBlockReinitializationSelections
├── DownloadCheckConfiguration (基类,属性 Checked : bool)
│ ├── CheckBeforeDownload IsChecked : bool
│ ├── UpgradeTargetDevice IsChecked : bool
│ ├── DowngradeTargetDevice IsChecked : bool
│ ├── OverWriteHMIData IsChecked : bool
│ ├── FitHMIComponents IsChecked : bool
│ ├── TurnOffSequence IsChecked : bool
│ └── OverwriteTargetLanguages IsChecked : bool
└── DownloadPasswordConfiguration (基类,方法 SetPassword(SecureString) : void)
├── ModuleReadAccessPassword + 属性 IsSecureCommunication : bool
├── ModuleWriteAccessPassword + 属性 IsSecureCommunication : bool
├── BlockBindingPassword + 属性 IsSecureCommunication : bool
└── PlcMasterSecretPassword + 属性 IsSecureCommunication : bool
⚠️ 注意两处命名不一致(官方原文如此):
DownloadCheckConfiguration的基类属性名是Checked,而子类(CheckBeforeDownload等)用的是IsChecked。这是官方文档中的原文,不是笔误转述,代码要照抄。
6.6.2 完整枚举值清单(官方 5.11.1.3 节原表)
| 配置类型 | 枚举值 | 语义 |
|---|---|---|
StartModules | NoAction / StartModule | 模块在下载之后被启动(官方注:StartModule 处标注 “Stop module”,文本本身有矛盾,实际语义以枚举名与代码注释为准,落地前建议实测) |
StopModules | NoAction / StopAll | 模块在下载前被停止 |
AllBlocksDownload | DownloadAllBlocks | 下载所有块到设备 |
OverwriteSystemData | NoAction / Overwrite | 删除并替换目标位置已有系统数据 |
ConsistentBlocksDownload | ConsistentDownload | 一致性下载软件 |
AlarmTextLibrariesDownload | ConsistentDownload / NoAction | 下载所有报警文本与文本列表文本 |
ProtectionLevelChanged | NoChange / ContinueDownloading | CPU 保护级别降到下一级后继续下载 |
ActiveTestCanBeAborted | NoAction / AcceptAll | 下载期间取消活动测试与调试功能 |
ResetModule | NoAction / DeleteAll | 复位模块 |
LoadIdentificationData | LoadNothing / LoadData | 向 PROFINET IO 设备及其模块装载标识数据 |
DifferentTargetConfiguration | NoAction / AcceptAll | 接受”组态模块与在线模块不同”的差异 |
InitializeMemory | NoAction / AcceptAll | 初始化存储器 |
ExpandDownload | NoAction / Download | 下载必须超出所选范围(扩展下载) |
ActiveTestCanPreventDownload | NoAction / AcceptAll | 活动测试/调试功能可能阻止下载 |
DataBlockReinitialization | StopPlcAndReInitialize / NoAction | 下载时用起始值初始化全部数据值(含保持值);NoAction 意为”下载不重初始化”——当在线块与离线块的存储区不同时会阻止”不重初始化下载” |
CheckBeforeDownload | IsChecked : bool | 下载前检查 |
UpgradeTargetDevice | IsChecked : bool | 检查配置设备与在线设备的项目版本差异 |
DowngradeTargetDevice | IsChecked : bool | 说明在线/离线项目的数据格式差异 |
OverWriteHMIData | IsChecked : bool | 覆盖在线已存在的对象 |
FitHMIComponents | IsChecked : bool | 在目标设备上安装版本不同的组件 |
TurnOffSequence | IsChecked : bool | 加载前关闭顺序 |
OverwriteTargetLanguages | IsChecked : bool | 区分项目与 PLC 编程语言的设置 |
ModuleReadAccessPassword | SetPassword(SecureString) | 取得模块读访问权的密码 |
ModuleWriteAccessPassword | SetPassword(SecureString) | 取得模块写访问权的密码 |
BlockBindingPassword | SetPassword(SecureString) | 块绑定密码 |
PlcMasterSecretPassword | SetPassword(SecureString) | PLC Master Secret 密码 |
关于”43 种”的来源:V21 手册共列 43 种具体 Download.Configurations 提示类型(V20 为 42 种),涵盖 UserManagementDownload、AlarmTextLibrariesDownload、DownloadCertificate 等。上表是 V19 手册中完整可见的 26 个具体类型(15 选择型 + 7 检查型 + 4 密码型)。V21 新增的约 17 种(如 UserManagementDownload、DownloadCertificate 等)不在 V19 手册的可见清单中——编写 pre delegate 时应采用”默认放行 + 对已知类型显式处理”的模式,避免因未枚举的新类型而中止下载。
6.6.3 IsSecureCommunication(TLS 感知)
四个密码类配置额外有 IsSecureCommunication 属性:为 true 表示与 PLC 之间是”安全通信(TLS 握手)“连接,其他情况为 false。
bool isSecureCommunication = moduleReadAccessPassword.IsSecureCommunication;
if (isSecureCommunication == true)
{
// Secure communication with (Tls handshake)
...
}
⚠️ 官方安全警告:“The API user is responsible for ensuring the security measures of handling passwords through code.” 以及 “Please note that download configurations are similar to configurations encountered in Load preview and Load results dialogs while working with GUI of TIA Portal.” ——密码必须从安全位置读取,绝不能硬编码在源码里。
6.6.4 完整 pre / post delegate 实现模式
这是本章最需要直接复用的代码。模式要点:① as 类型分派 + 早返回;② 显式处理已知类型;③ 兜底分支不抛异常(对未枚举的新类型给默认放行);④ 密码从外部安全源读取;⑤ 所有修改只限于”解决该配置所需的最小改动”。
using System;
using System.Security;
using Siemens.Engineering;
using Siemens.Engineering.Download;
using Siemens.Engineering.Download.Configurations;
/// <summary>
/// 下载配置处理器:把 40 余种 DownloadConfiguration 提示类型收敛为"选择 / 勾选 / 密码"三类动作。
/// 模式:as 分派 + 早返回 + 兜底放行。
/// </summary>
public sealed class DownloadConfigurationHandler
{
/// <summary>各类密码的外部安全源(密钥库 / 环境变量 / 受 ACL 保护的文件)。切勿硬编码。</summary>
private readonly Func<string, SecureString> _passwordSource;
private readonly bool _stopBeforeDownload;
private readonly bool _startAfterDownload;
private readonly Action<string> _log;
public DownloadConfigurationHandler(
Func<string, SecureString> passwordSource,
bool stopBeforeDownload = true,
bool startAfterDownload = true,
Action<string> log = null)
{
_passwordSource = passwordSource ?? throw new ArgumentNullException(nameof(passwordSource));
_stopBeforeDownload = stopBeforeDownload;
_startAfterDownload = startAfterDownload;
_log = log ?? Console.WriteLine;
}
/// <summary>PRE 下载前回调:处理"下载前"出现的配置(含 StopModules)。</summary>
public void Pre(DownloadConfiguration cfg)
{
_log($"[pre] {cfg.GetType().Name}: {cfg.Message}");
// ---- 停止模块(下载前置 STOP)----
if (cfg is StopModules stopModules)
{
stopModules.CurrentSelection = _stopBeforeDownload
? StopModulesSelections.StopAll
: StopModulesSelections.NoAction;
return;
}
// ---- 一致性下载 ----
if (cfg is ConsistentBlocksDownload cbd)
{
cbd.CurrentSelection = ConsistentBlocksDownloadSelections.ConsistentDownload;
return;
}
// ---- 数据块重初始化(在线/离线存储区不同时必须接受)----
if (cfg is DataBlockReinitialization dbr)
{
dbr.CurrentSelection = DataBlockReinitializationSelections.StopPlcAndReInitialize;
return;
}
// ---- DB 重初始化 / 扩展下载 / 活动测试相关 ----
if (cfg is ExpandDownload ed)
{
ed.CurrentSelection = ExpandDownloadSelections.Download;
return;
}
if (cfg is ActiveTestCanPreventDownload atcpd)
{
atcpd.CurrentSelection = ActiveTestCanPreventDownloadSelections.AcceptAll;
return;
}
if (cfg is ActiveTestCanBeAborted atca)
{
atca.CurrentSelection = ActiveTestCanBeAbortedSelections.AcceptAll;
return;
}
// ---- 报警文本库 / 系统数据 / 所有块 ----
if (cfg is AlarmTextLibrariesDownload atl)
{
atl.CurrentSelection = AlarmTextLibrariesDownloadSelections.ConsistentDownload;
return;
}
if (cfg is AllBlocksDownload abd)
{
abd.CurrentSelection = AllBlocksDownloadSelections.DownloadAllBlocks;
return;
}
if (cfg is OverwriteSystemData osd)
{
osd.CurrentSelection = OverwriteSystemDataSelections.Overwrite;
return;
}
// ---- 保护级别变更 / 存储器初始化 / 差异接受 / 复位 / 标识数据 ----
if (cfg is ProtectionLevelChanged plc)
{
plc.CurrentSelection = ProtectionLevelChangedSelections.ContinueDownloading;
return;
}
if (cfg is InitializeMemory im)
{
im.CurrentSelection = InitializeMemorySelections.AcceptAll;
return;
}
if (cfg is DifferentTargetConfiguration dtc)
{
dtc.CurrentSelection = DifferentTargetConfigurationSelections.AcceptAll;
return;
}
if (cfg is ResetModule rm)
{
rm.CurrentSelection = ResetModuleSelections.DeleteAll;
return;
}
if (cfg is LoadIdentificationData lid)
{
lid.CurrentSelection = LoadIdentificationDataSelections.LoadData;
return;
}
// ---- 检查型(IsChecked)----
if (cfg is CheckBeforeDownload cbd2) { cbd2.IsChecked = true; return; }
if (cfg is UpgradeTargetDevice utd) { utd.IsChecked = true; return; }
if (cfg is DowngradeTargetDevice dtd) { dtd.IsChecked = true; return; }
if (cfg is OverWriteHMIData ohmd) { ohmd.IsChecked = true; return; }
if (cfg is FitHMIComponents fhc) { fhc.IsChecked = true; return; }
if (cfg is TurnOffSequence tos) { tos.IsChecked = true; return; }
if (cfg is OverwriteTargetLanguages otl) { otl.IsChecked = true; return; }
// ---- 密码型 ----
if (cfg is ModuleReadAccessPassword mrap)
{
WarnTls(mrap.IsSecureCommunication);
mrap.SetPassword(_passwordSource("ModuleReadAccess"));
return;
}
if (cfg is ModuleWriteAccessPassword mwap)
{
WarnTls(mwap.IsSecureCommunication);
mwap.SetPassword(_passwordSource("ModuleWriteAccess"));
return;
}
if (cfg is BlockBindingPassword bbp)
{
WarnTls(bbp.IsSecureCommunication);
bbp.SetPassword(_passwordSource("BlockBinding"));
return;
}
if (cfg is PlcMasterSecretPassword pmsp)
{
WarnTls(pmsp.IsSecureCommunication);
pmsp.SetPassword(_passwordSource("PlcMasterSecret"));
return;
}
// ---- 兜底:未枚举的新类型(如 V21 的 UserManagementDownload / DownloadCertificate)
// 默认不修改任何属性:手册明确"某些配置只含信息,无需动作",
// 也允许跳过"不阻止下载"的配置。切勿在此抛异常。
_log($"[pre] 未显式处理,按信息类配置放行: {cfg.GetType().Name}");
}
/// <summary>POST 下载后回调:处理"下载后"出现的配置(含 StartModules)。</summary>
public void Post(DownloadConfiguration cfg)
{
_log($"[post] {cfg.GetType().Name}: {cfg.Message}");
// ---- 启动模块(下载后置 RUN)----
if (cfg is StartModules startModules)
{
startModules.CurrentSelection = _startAfterDownload
? StartModulesSelections.StartModule
: StartModulesSelections.NoAction;
return;
}
// post 阶段同样可能出现密码 / 检查型配置
Pre(cfg);
}
private void WarnTls(bool secure)
{
if (!secure)
_log(" [!] 密码将以非安全通信(无 TLS 握手)方式发送,请评估风险");
}
}
把 handler 接到下载调用上:
var handler = new DownloadConfigurationHandler(
passwordSource: key => LoadSecretFromSecureStore(key),
stopBeforeDownload: true,
startAfterDownload: true,
log: msg => Console.WriteLine(msg));
DownloadConfigurationDelegate preDelegate = handler.Pre;
DownloadConfigurationDelegate postDelegate = handler.Post;
try
{
DownloadResult result = downloadProvider.Download(
targetConfiguration, onlineAddress, // 在线 IP ≠ 组态 IP 时用五参数重载
preDelegate, postDelegate,
DownloadOptions.Hardware | DownloadOptions.Software);
if (result.State == DownloadResultState.Error)
{
foreach (var m in result.Messages)
Console.WriteLine($"ERROR {m.State} {m.Path}: {m.Description}");
throw new InvalidOperationException(
$"下载失败:errors={result.ErrorCount}, warnings={result.WarningCount}");
}
Console.WriteLine($"下载成功:errors={result.ErrorCount}, warnings={result.WarningCount}");
}
catch (EngineeringTargetInvocationException ex)
{
// 配置无效 / 未处理的对话框 → 下载中止
Console.WriteLine("下载被中止:" + ex.Message);
throw;
}
catch (EngineeringDelegateInvocationException ex)
{
// delegate 内部抛出未处理异常
Console.WriteLine("delegate 内部异常:" + ex.Message);
throw;
}
官方最小 pre 示例(手册 5.11.1.3 节原样,可作为模式对照):
private static void PreConfigureDownload(DownloadConfiguration downloadConfiguration)
{
StopModules stopModules = downloadConfiguration as StopModules;
if (stopModules != null)
{
stopModules.CurrentSelection = StopModulesSelections.StopAll; // 置 PLC 为 "Stop"
return;
}
AlarmTextLibrariesDownload alarmTextLibraries =
downloadConfiguration as AlarmTextLibrariesDownload;
if (alarmTextLibraries != null)
{
alarmTextLibraries.CurrentSelection =
AlarmTextLibrariesDownloadSelections.ConsistentDownload;
return;
}
BlockBindingPassword blockBindingPassword =
downloadConfiguration as BlockBindingPassword;
if (blockBindingPassword != null)
{
SecureString password = ...; // 从安全位置获取绑定密码
blockBindingPassword.SetPassword(password);
return;
}
}
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.11.1.3 节(配置类型表、枚举表、
IsSecureCommunication、最小 pre 示例、未处理异常的后果)、5.11.1.8 节;advanced.md(43 种 / V20 为 42 种的计数)。
6.7 编译作为下载的前置步骤
6.7.1 可编译对象类型(官方 5.3.13 节全表)
| 对象类型 | 编译内容 |
|---|---|
Device | HW & SW |
Device with failsafe CPU | SW(带 F 激活属性关闭) |
DeviceItem | HW |
CodeBlock | SW |
DataBlock | SW |
HmiTarget | SW |
PlcSoftware | SW |
PlcType | SW |
PlcBlockSystemGroup / PlcBlockUserGroup | SW |
PlcTypeSystemGroup / PlcTypeUserGroup | SW |
⚠️ 前置条件:所有设备必须处于 “Offline” 状态(官方明确两处强调)。另:若安全程序已设密码,编译软件变更前需先登录安全程序,否则过程被取消并抛异常。
6.7.2 编译调用与结果读取
// 官方 5.3.13 节签名
ICompilable compileService = iEngineeringServiceProvider.GetService<ICompilable>();
CompilerResult result = compileService.Compile();
// 三个典型对象的编译
public static void CompileHmiTarget(HmiTarget hmiTarget)
{
ICompilable compileService = hmiTarget.GetService<ICompilable>();
CompilerResult result = compileService.Compile();
}
public static void CompilePlcSoftware(PlcSoftware plcSoftware)
{
ICompilable compileService = plcSoftware.GetService<ICompilable>();
CompilerResult result = compileService.Compile();
}
public static void CompileCodeBlock(PlcSoftware plcSoftware)
{
CodeBlock block = plcSoftware.BlockGroup.Blocks.Find("MyCodeBlock") as CodeBlock;
if (block != null)
{
ICompilable compileService = block.GetService<ICompilable>();
CompilerResult result = compileService.Compile();
}
}
CompilerResult 读取(官方原样,注意 Messages 是递归结构):
private void WriteCompilerResults(CompilerResult result)
{
Console.WriteLine("State:" + result.State);
Console.WriteLine("Warning Count:" + result.WarningCount);
Console.WriteLine("Error Count:" + result.ErrorCount);
RecursivelyWriteMessages(result.Messages);
}
private void RecursivelyWriteMessages(CompilerResultMessageComposition messages,
string indent = "")
{
indent += "\t";
foreach (CompilerResultMessage message in messages)
{
Console.WriteLine(indent + "Path: " + message.Path);
Console.WriteLine(indent + "DateTime: " + message.DateTime); // 注意:全部是 UTC
Console.WriteLine(indent + "State: " + message.State);
Console.WriteLine(indent + "Description: " + message.Description);
Console.WriteLine(indent + "Warning Count: " + message.WarningCount);
Console.WriteLine(indent + "Error Count: " + message.ErrorCount);
RecursivelyWriteMessages(message.Messages, indent);
}
}
⚠️ 时间戳全部是 UTC(官方注:“All time stamps are in UTC. If you want to see the local time you can use DateTime.ToLocalTime().“)
6.7.3 GenerateBlocksFromSource() 的错误语义(下载前的隐形坑)
如果流程里包含”从 SCL 外部源生成块”,必须知道这个不对称:
| 调用方式 | 是否抛异常 | 返回值 | 失败副作用 |
|---|---|---|---|
GenerateBlocksFromSource()(无参) | 失败时抛可恢复异常,错误信息进异常消息 | — | 删除已生成的块/UDT |
GenerateBlocksFromSource(GenerateBlockOptions.None) | 失败时抛可恢复异常 | 成功时返回 IList<IEngineeringObject>(与 KeepOnError 行为相同,不给任何生成结果信息) | 删除已生成的块/UDT |
GenerateBlocksFromSource(GenerateBlockOptions.KeepOnError) | 无论生成结果如何都不抛异常 | 返回含(含错误的)已生成对象列表 | 保留已生成对象(即使有错) |
官方推荐写法(用 KeepOnError 换逐对象编译诊断):
try
{
IList<IEngineeringObject> generatedObjects =
externalSource.GenerateBlocksFromSource(GenerateBlockOptions.KeepOnError);
foreach (IEngineeringObject engineeringObject in generatedObjects)
{
CompilerResult compilerResult = null;
string objectName = null;
if (engineeringObject is PlcBlock block)
{
objectName = block.Name;
compilerResult = block.Compile();
}
else if (engineeringObject is PlcType plcType)
{
objectName = plcType.Name;
compilerResult = plcType.Compile();
}
if (compilerResult != null)
{
if (compilerResult.State == CompilerResultState.Error)
{
Console.WriteLine($"Object '{objectName}' could not be compiled successfully!");
Console.WriteLine($"Number of compiler errors: {compilerResult.ErrorCount}");
foreach (CompilerResultMessage m in compilerResult.Messages)
Console.WriteLine(m.Description);
}
else
{
Console.WriteLine($"Object '{objectName}' could be compiled successfully.");
}
}
}
}
catch (RecoverableException exception) { /* 处理 */ }
典型失败根因(官方列出):引用不存在的类型、缺失的符号。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.3.13 节、5.11.3.19 节;official.md(
GenerateBlocksFromSource()错误语义条目)。
6.7.4 下载到 Windows 文件夹(存储卡镜像)
这也是一条受支持的下载路径,不需要真实 PLC 连接——PLCSIM 联调与离线柜调试都靠它:
// 下载到 Windows 文件夹(存储卡镜像)
DownloadProvider dp = deviceItem.GetService<DownloadProvider>();
dp.Download(new DirectoryInfo(@"C:\PLCSIM\Card"),
PreDownloadDelegate,
new DownloadOptions { TargetForSoftware = true,
OverwriteOnMemoryCard = true });
⚠️ 此处 DownloadOptions 是另一个重载的选项类(带 TargetForSoftware / OverwriteOnMemoryCard 两个布尔属性),与 6.5 节的位标志枚举 DownloadOptions 不是同一个形态——这是官方 API 的重载设计,落地时按参数类型区分。手册对应章节 5.11.1.4 节 Downloading PLC to a Windows folder。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.11.1.4 节;advanced.md(PLCSIM Advanced 联调条目)。
6.8 停止与启动模块
官方 5.11.1.8 节把”运行与停止 PLC”单列为一个章节,其全部内容就是两个 delegate 片段(见 6.5.3)。这里补充社区实测的调用位置差异:
| 动作 | 出现在 | 代码 | 枚举 |
|---|---|---|---|
| 停止模块 | pre delegate | stopModules.CurrentSelection = StopModulesSelections.StopAll; | NoAction / StopAll |
| 启动模块 | post delegate | startModules.CurrentSelection = StartModulesSelections.StartModule; | NoAction / StartModule |
⚠️ 能力边界(已核实,务必记住):Openness 不提供读取/切换 CPU 运行模式(RUN/STOP)的独立 API。官方”运行与停止 PLC”章节只暴露上述两段 delegate 代码,即运行模式变更绑定在下载流程内,不能独立调用。社区也记录了反向证据:设备在线时改程序会抛 This function is not supported in online mode,必须先 GoOffline()。
⚠️ R/H 冗余的例外:手册 5.11.2.9 节提供 RHOnlineProvider.GoOnlineToPrimary() / GoOnlineToBackup(),可分别上线主备 PLC——这是 R/H 系统专有的,不等于通用的运行模式切换。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.11.1.8 节、5.11.2.9 节;advanced.md(能力边界核实结论);cn-community(
This function is not supported in online mode)。
6.9 上传:StationUploadProvider.StationUpload
6.9.1 服务获取与限制
// 官方 5.11.1.12 节
Project myProject = ...;
StationUploadProvider uploadProviderForProject =
myProject.GetService<StationUploadProvider>();
if (uploadProviderForProject != null) { /* ... */ }
⚠️ StationUploadProvider 是从 Project 取,不是从 DeviceItem 取——与 DownloadProvider / OnlineProvider 不同。
⚠️ 官方限制:“The upload station is not supported for device group.”(设备组不支持站点上传)
6.9.2 UploadOptions 是可选的
官方原文:“UploadOptions are optional, because the StationUpload uploads Software, Hardware, and Files.” ——即默认就上传软件、硬件与文件,不需要显式指定。
9.3 上传参数与实现
| 参数名 | 类型 | 说明 |
|---|---|---|
configurationAddress | Siemens.Engineering.Connection.ConfigurationAddress | 待上传设备的地址 |
uploadConfigurationDelegate | Siemens.Engineering.Upload.UploadConfigurationDelegate | 上传前配置回调 |
uploadOptions | Siemens.Engineering.Upload.UploadOptions | 上传选项(可选) |
上传配置对象模型(与下载完全不同的类型体系):
UploadConfiguration (基类,属性 Message : string)
├── UploadPasswordConfiguration (基类,方法 SetPassword(SecureString) : void)
│ ├── ModuleReadAccessPassword
│ ├── ModuleWriteAccessPassword
│ └── PasswordReadAccess
└── UploadSelectionConfiguration (基类,无额外属性)
⚠️ 命名不一致预警:下载侧是 DownloadPasswordConfiguration(Password 在后),上传侧是 UploadPasswordConfiguration(Password 在前)。抄错编译不过。
完整上传实现(手册原样):
// 官方 5.11.1.12 节:创建上传地址(注意在 StationUploadProvider 的 Configuration 上)
StationUploadProvider uploadProvider = ...;
ConnectionConfiguration configuration = uploadProvider.Configuration;
ConfigurationMode configurationMode = configuration.Modes.Find("PN/IE");
ConfigurationPcInterface pcInterface =
configurationMode.PcInterfaces.Find("Intel(R) Ethernet Connection I217-LM", 1);
// "Create an address. This ConfigurationAddress is used as parameter for upload."
ConfigurationAddress uploadAddress = pcInterface.Addresses.Create("192.68.0.1");
// 官方 5.11.1.12 节:执行上传
Device uploadedObject = null;
UploadConfigurationDelegate preUploadDelegate = PreConfigureUpload;
UploadResult result = uploadProvider.StationUpload(uploadAddress, preUploadDelegate);
// The uploaded device
uploadedObject = result.UploadedStation;
if (uploadedObject == null)
{
// 失败处理
}
internal void PreConfigureUpload(UploadConfiguration uploadConfiguration)
{
// 处理上传前配置
}
⚠️ ConfigurationAddress 的创建路径与下载不同:下载是在 ConfigurationTargetInterface.Addresses 上 Create(ip);上传是在 ConfigurationPcInterface.Addresses 上 Create(ip)(少一层 TargetInterfaces)。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.11.1.12 节。
6.10 比较软件与硬件
6.10.1 四种软件比较场景与两个方法
手册 5.11.1.13 节列出可比较的对象组合:
- 比较两台已组态 PLC 的软件
- PLC 软件 vs 项目库
- PLC 软件 vs 全局库
- PLC 软件 vs 主副本(master copy)
- 已组态 PLC vs “Online” 状态下已连接 PLC 的软件
两个方法签名:
public CompareResult CompareTo(ISoftwareCompareTarget compareTarget);
public CompareResult CompareToOnline();
6.10.2 CompareResultElement.ComparisonResult 全部取值
| 枚举值 | 含义 |
|---|---|
FolderContentsDifferent | 被比较文件夹的内容不同 |
FolderContentsIdentical | 被比较文件夹的内容相同 |
ObjectsDifferent | 被比较对象的内容不同 |
ObjectsIdentical | 被比较对象的内容相同 |
LeftMissing | 对象不在发起比较的那个对象里 |
RightMissing | 对象不在被比较的那个对象里 |
CompareIrrelevant | 这两个对象的比较无意义 |
FolderContainsDifferencesOwnStateDifferent | 文件夹内容有差异,且文件夹自身状态也不同 |
FolderContentEqualOwnStateDifferent | 文件夹内容相同,但文件夹自身状态不同 |
递归读取比较结果(官方原样):
private static void WriteResult(CompareResultElement compareResultElement, string indent)
{
Console.WriteLine("{0} <{1}> <{2}> <{3}> <{4}> ",
indent,
compareResultElement.LeftName,
compareResultElement.ComparisonResult,
compareResultElement.RightName,
compareResultElement.DetailedInformation);
WriteResult(compareResultElement.Elements, indent);
}
private static void WriteResult(IEnumerable<CompareResultElement> compareResultElements,
string indent)
{
indent += " ";
foreach (CompareResultElement e in compareResultElements) WriteResult(e, indent);
}
private static void CompareTwoOfflinePlcs(PlcSoftware plcSoftware0, PlcSoftware plcSoftware1)
{
CompareResult compareResult = plcSoftware0.CompareTo(plcSoftware1);
WriteResult(compareResult.RootElement, "");
}
6.10.3 硬件比较
// 官方 5.11.1.15 节
CompareResult CompareTo(IHardwareCompareTarget compareTarget);
官方判读示例:
CompareResult compareResult = plc_1.CompareTo(plc_2);
CompareResultState resultState = compareResult.RootElement.ComparisonResult;
if (resultState == CompareResultState.FolderContainsDifferencesOwnStateDifferent)
{
// 文件夹内容有差异且自身状态不同:
// 可能 PLC 有不同的下级元素(如本地模块),且 PLC 本身也不同(如某个参数)
}
else if (resultState == CompareResultState.FolderContentEqualOwnStateDifferent)
{
// 文件夹内容相同但自身状态不同:
// 可能是文件夹型模块(如 FM 351)下级元素相同但模块本身不同(如参数)
}
else if (resultState == CompareResultState.FolderContentsIdentical) { /* ... */ }
⚠️ compareTarget 为 null 会抛 EngineeringTargetInvocationException(官方原文类名拼写为 Siemens.Enginnering.EngineeringTargetInvocationExceptions,是官方文档的拼写错误)。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.11.1.13 节、5.11.1.15 节。
6.11 能力边界专节:Openness 不能做什么
6.11.1 对照表:能做 / 不能做
| 能力 | Openness 支持 | 依据 | 替代方案 |
|---|---|---|---|
| 上线 / 下线 | ✅ | OnlineProvider.GoOnline() / GoOffline() | — |
| 查询在线状态 | ✅ | OnlineProvider.State(7 种枚举) | — |
| 下载软件 / 硬件 | ✅ | DownloadProvider.Download(...)(含 SoftwareOnlyChanges) | — |
| 下载到存储卡目录 | ✅ | Download(DirectoryInfo, delegate, DownloadOptions) | — |
| 上传(从 PLC 读回) | ✅ | StationUploadProvider.StationUpload(...) | — |
| 比较软件 / 硬件 | ✅ | CompareTo / CompareToOnline | — |
| 编译 | ✅ | ICompilable.Compile()(13 类对象) | — |
| 停止 / 启动模块 | ⚠️ 仅在下载流程内 | 只暴露 StopModules(pre)/ StartModules(post) | 见 6.11.2 |
| 读取/切换 CPU 运行模式(RUN/STOP) | ❌ | 官方”运行与停止 PLC”章节只给 delegate 代码,运行模式变更绑定在下载流程内,不能独立调用 | 走 CPU 的 OPC UA 服务器或 S7 通信 |
| 清除强制(Unforce) | ❌ | 不在 Openness 能力范围内 | 走 OPC UA / S7 通信 |
| 读诊断缓冲区(DiagnosticBuffer)/ 故障缓冲区(FaultBuffer) | ❌ | 同上 | 走 OPC UA / S7 通信 |
| 按块选择性下载过滤器 | ❌ | 下载粒度只能到 DownloadOptions(HW/SW/SW-changes) | 用 SoftwareOnlyChanges 做粗粒度差异下载;块级控制需手工 |
| ”不停机下载” | ❌ | 不存在 DownloadWithoutStop;pre 必停 → 下载 → post 必启 | 旁路方案:在线变更 / 备机切换 / S7 通信 |
| 读写实时过程值 / DB 数据 | ❌ | Openness 不是 PLC 通信协议,也不是 S7 通信库 | Sharp7、S7netPlus、OPC UA |
| 在线监控 / 在线修改变量 | ❌ | 同上 | Watch table 手工;S7 通信 |
| 执行/生成安全系统的验收数据 | ❌ | 官方 V15.1 手册明确禁止用 Openness API 执行安全系统检查或生成验收/认可数据 | STEP 7 Safety 附加包的安全打印(safety printout)或功能测试 |
| 操作”不可读的强制变量” | ❌ | 已知限制 | — |
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.11.1.8 节与 5.11 节整体;advanced.md(
DownloadWithoutStop0 命中核实、运行模式/清强制/诊断缓冲区/按块下载四项”已核实不支持”);official.md(V15.1 手册安全红线);cn-community(Openness vs PLC 通信协议区分)。
6.11.2 架构建议:能力边界的正确绕法
既然运行状态与过程值都不可达,工程上的正确分工是:
工程期(Openness 负责) 运行期(上位系统负责)
──────────────────────── ──────────────────────
生成 / 修改工程数据 OPC UA 客户端订阅与读写
编译(完整错误反馈) S7 通信读写过程值
下载 / 上传 / 比较(工程态一致性) SCADA / MES 数据采集
PLC Master Secret 生命周期管理 运行状态监控与诊断读取
安全程序的"配置"(非验收) 强制变量的清理与审计
推荐通道:
- OPC UA —— S7-1500 的 OPC UA 服务器(默认禁用,须在 CPU 属性 → OPC UA Server General 激活;需运行系统许可证,按 CPU 型号分 Small / Medium / Large 三档)。不能通过 CP 或 CM 经背板总线访问 CPU 的 OPC UA 服务器,只能走 CPU 上集成的 PROFINET 接口。
- S7 通信(Sharp7 / S7netPlus / nodes7 等)—— 需要读写 DB、按块修改变量时的兜底。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册(OPC UA 前提与限制);advanced.md(OPC UA 章节);cn-community、en-blogs(Sharp7 / S7netPlus / nodes7 推荐)。
6.12 PLCSIM Advanced / PLCSIM 联调
6.12.1 三种通信路径(决定下载能否成功)
TIA Portal 与 PLCSIM Advanced 之间有三种通信路径:
| 路径 | 拓扑 | 说明 |
|---|---|---|
| Softbus | 同机 | 接口选 PLCSIM |
| PLCSIM Virtual Eth. Adapter | 同机 TCP/IP | 真实虚拟网卡 |
| PLCSIM Virtual Eth. Adapter + VMware Network Adapter | 跨机分布式 | 虚拟机 + 宿主 |
6.12.2 ⚠️ 能力矩阵关键差异(决定 CI 架构)
| 能力 | Softbus 路径 | 虚拟网卡路径 |
|---|---|---|
| OPC UA 服务器 | ❌ 不支持 | ✅ 支持 |
| Web 服务器 | ❌ 不支持 | ✅ 支持 |
| 与真实 CPU 硬件通信 | ❌ 不支持 | ✅ 支持 |
| 与真实 HMI(V14+)通信 | ❌ 不支持 | ✅ 支持 |
结论:CI 流水线要用 OPC UA 就必须走 TCP/IP 路径。
6.12.3 工程侧必做设置
| 设置 | 内容 |
|---|---|
| 设备属性 → 保护 → 勾选”块编译时支持仿真” | 不勾则下载报仿真不支持 |
| CPU 固件与 PLCSIM Advanced 版本必须匹配 | 如 V3.0 支持 CPU 固件 V4.8 |
| PLCSIM Advanced 提供 API | S7PLCSIMx64.exe 命令行 + Runtime Manager 端口 50000,可被 C++ / C# 程序调用实现联合仿真(Entry ID 109739660) |
| TIA Openness 侧无需特殊 API | 选对 ConfigurationMode / PcInterface 即可把 Download 指向仿真网卡——即在 PcInterfaces.Find(...) 处选虚拟网卡对应的接口名 |
6.12.4 面向 PLCSIM 的下载实现
using Siemens.Engineering;
using Siemens.Engineering.Download;
public static class PlcSimDownloader
{
/// <summary>
/// 下载到 S7-PLCSIM Advanced 虚拟 PLC。
/// pcInterfaceName 传虚拟网卡在 TIA 中显示的连接名;slotName 传 CPU 槽位名(如 "2 X3")。
/// </summary>
public static DownloadResult DownloadToPlcSim(
DeviceItem cpuItem,
string pcInterfaceName,
string slotName,
DownloadConfigurationDelegate pre,
DownloadConfigurationDelegate post)
{
DownloadProvider dp = cpuItem.GetService<DownloadProvider>();
if (dp == null) throw new InvalidOperationException("该 DeviceItem 无 DownloadProvider 服务");
ConnectionConfiguration cfg = dp.Configuration;
ConfigurationMode mode = cfg.Modes.Find("PN/IE");
if (mode == null) throw new InvalidOperationException("未找到 PN/IE 连接模式");
ConfigurationPcInterface pcIf =
mode.PcInterfaces.Find(pcInterfaceName, 1);
if (pcIf == null) throw new InvalidOperationException(
$"未找到 PC 接口 {pcInterfaceName};可用候选:{string.Join(", ", mode.PcInterfaces.Select(x => x.Name))}");
IConfiguration target = pcIf.TargetInterfaces.Find(slotName);
if (target == null) throw new InvalidOperationException(
$"未找到槽位 {slotName};可用候选:{string.Join(", ", pcIf.TargetInterfaces.Select(x => x.Name))}");
// 仿真环境通常用 SoftwareOnlyChanges 省时间;但首次需完整下载
return dp.Download(target, pre, post,
DownloadOptions.Hardware | DownloadOptions.Software);
}
}
PLCSIM 联调在 CI 流水线中的位置(结合官方 CI 工具集与 CERN 实践):
| 阶段 | 动作 | 工具 |
|---|---|---|
| Build | 导入源文件 → 编译成可部署产物 | TIA Openness(CERN 自研 wrapper 驱动 al-paka / Maven) |
| Deploy | 部署到 S7-PLCSIM Advanced 实例:脚本管理虚拟 PLC 的 reset / start / stop / upload | TIA Openness wrapper |
| Setup | 把系统带到已知初始状态:清报警、执行器回零、设初始过程值 | UNICOS-CPC objects |
| Tests | 跑 unit / integration / regression;单测失败不中断,全部跑完给完整失败报告;整体只在全部通过时标成功 | 测试脚本 |
| Release(可选,CD) | 验证过的工程存为版本化构建产物,转存 Octoplant 供排期人工部署 | — |
⚠️ CERN 实践的三条硬约束:① 硬件配置、在线监控、在线调试仍必须在 TIA Portal 里做;② 部署到物理 PLC 是需当前密码的独立人工步骤(流水线只做校验,不下载到物理 CPU);③ 自动化测试的边界:擅长重复、确定性、数据驱动的场景;硬件测试、探索性测试、HMI 可用性测试仍必须人工。
来源:Siemens 官方 PLCSIM Advanced 入门文档(中文)+ S7-PLCSIM Advanced V6.0 功能手册;advanced.md(PLCSIM Advanced 联调条目、三种通信路径与能力矩阵);官方《Continuous Integration with TIA Portal》应用示例(Entry 109801203);en-blogs(CERN ICALEPCS 2025 论文,Comquent 实战 pipeline)。
6.13 断线重连与异常处理
6.13.1 异常分类与处理策略
| 异常 / 现象 | 语义 | 处理策略 |
|---|---|---|
RecoverableException | 可恢复错误(如 GenerateBlocksFromSource 失败) | 读消息、修数据、重跑;注意它会删除已生成的块 |
EngineeringTargetInvocationException | 目标调用失败:订货号不在目录、槽已占用、配置无效、未处理下载对话框、CAx 设备组不支持、HardwareCompare 传 null | 记录 Message + InnerException;下载路径下必须补齐 delegate 分派 |
EngineeringDelegateInvocationException | delegate 内部抛了未处理异常 | 检查 delegate 实现;不要在 delegate 里做重活 |
EngineeringNotSupportedException | 该版本/该设备不支持此 API | 判版本后跳过,不要重试 |
EngineeringSecurityException | 权限/授权问题:用户不在 Siemens TIA Openness 组;连续拒绝防火墙授权三次 | 修用户组 + 注销重登;改白名单注册表 |
LicenseNotFoundException | 许可缺失(SiVArc 数据变更时) | 补许可 |
NonRecoverableException | 不可恢复(如外部关闭 TIA Portal 后再调 API) | 重启 TIA Portal 实例,重建连接 |
CrossReferenceException | 未 PlugNew 就 GetService<IoSystem>() | 先插设备 |
COMException 0x80004005 | 权限不足(用户组缺权限) | whoami /groups 查组 |
HRESULT 0x8001010E RPC_E_SERVER_CANTUNLOAD | TIA 被另一自动化死锁 | 一个项目同时只允许一个 Openness 进程 |
TypeLoadException / FileNotFoundException | 加载了错误版本 DLL / 未设 Copy Local=False | 精确定位 HintPath + AssemblyResolve |
| 界面模态对话框导致无限挂起 | TIA 弹保存/兼容性/库冲突对话框 | CI 必用 WithoutUserInterface + 设超时 |
来源:en-blogs(异常表、
whoami /groups、gacutil、RPC 错误、V19→V21FileLoadException);cn-community(异常分类处理与性能优化表);advanced.md(EngineeringSecurityException、卡死坑);Siemens 官方《TIA Portal Openness》V19 系统手册(EngineeringTargetInvocationException各处、NonRecoverableException)。
6.13.2 断线重连的正确姿势
官方对”断线”没有提供专门的自动重连 API。可靠的模式是外层重试 + 状态探测:
using System;
using System.Threading;
using System.Threading.Tasks;
using Siemens.Engineering;
using Siemens.Engineering.Online;
public sealed class PlcOnlineSession : IDisposable
{
private readonly TiaPortal _tia;
private readonly TiaPortalProcess _process;
private readonly int _maxRetries;
private readonly TimeSpan _retryDelay;
private bool _disposed;
/// <summary>附加到已运行实例的 TIA Portal(不新起进程)。</summary>
public PlcOnlineSession(TiaPortalProcess process, int maxRetries = 3, TimeSpan? retryDelay = null)
{
_process = process ?? throw new ArgumentNullException(nameof(process));
_tia = process.Attach();
_maxRetries = maxRetries;
_retryDelay = retryDelay ?? TimeSpan.FromSeconds(5);
}
/// <summary>
/// 带重试的执行:捕获 NonRecoverableException(连接已死)后重新 Attach 一次再重试。
/// </summary>
public T ExecuteWithReconnect<T>(Func<TiaPortal, T> action, string what)
{
for (int attempt = 1; ; attempt++)
{
try
{
return action(_tia);
}
catch (NonRecoverableException ex) when (attempt < _maxRetries)
{
Console.WriteLine($"[retry {attempt}/{_maxRetries}] {what} 连接已失效:{ex.Message}");
SafeDispose();
Thread.Sleep(_retryDelay);
_tia = _process.Attach(); // 重新挂接
}
catch (EngineeringException ex) when (attempt < _maxRetries)
{
Console.WriteLine($"[retry {attempt}/{_maxRetries}] {what} 失败:{ex.Message}");
Thread.Sleep(_retryDelay);
}
}
}
/// <summary>同步在线状态到目标状态;超时抛 TimeoutException 而非无限等待。</summary>
public OnlineState WaitForState(DeviceItem cpuItem, OnlineState target, TimeSpan timeout)
{
DateTime deadline = DateTime.UtcNow + timeout;
OnlineProvider p = cpuItem.GetService<OnlineProvider>();
if (p == null) throw new InvalidOperationException("非 CPU 设备项无 OnlineProvider 服务");
OnlineState s = p.State;
while (s != target && DateTime.UtcNow < deadline)
{
Thread.Sleep(TimeSpan.FromMilliseconds(500));
s = p.State;
}
if (s != target) throw new TimeoutException(
$"等待 {target} 超时({timeout}),当前状态 {s};IP/接口配置可能有误");
return s;
}
private void SafeDispose() { try { _tia?.Dispose(); } catch { } }
public void Dispose()
{
if (_disposed) return;
_disposed = true;
SafeDispose();
}
}
⚠️ 重试的三条纪律:① 只对可恢复异常重试,NonRecoverableException 必须先重建连接;② 重试之间必须有退避,不可紧密循环(TIA 弹模态框时会挂死);③ 重试次数要有上限——CI 里无限重试等于挂流水线。
6.13.3 事件订阅与 finally 反订阅
// 官方模式:Confirmation 只能设 "Yes" / "YesToAll" / "No",其他值抛异常
using (TiaPortal tiaPortal = TiaPortal.GetProcesses().First().Attach())
{
void Handler(object s, TiaPortalConfirmationEventArgs e) { e.Result = "Yes"; }
tiaPortal.Confirmation += Handler;
try { /* ... */ }
finally { tiaPortal.Confirmation -= Handler; }
}
四个事件(手册 5.2.10 节):Disposed(TIA Portal 在连接期间被关闭)、Notification(属性含 Caption / DetailText / Icon / IsHandled / MessageID / ServiceID / Text)、Confirmation(必须设 e.Result 为 "Yes" / "YesToAll" / "No",其他值抛异常)、Authentication(V17+,UMAC 保护工程)。所有事件必须在 finally 中反订阅。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.2.10 节;advanced.md(四个事件与
finally反订阅)。
6.14 网络配置与地址一致性校验流程
这是 CI 上线前的最后一道闸门:把”工程组态 IP”与”实际网络 IP”在下载前就对齐,避免下载到错误设备。
6.14.1 IP 唯一性与一致性校验(官方思路 + 社区实现)
社区给出了一套可直接落地的 IP 唯一性校验:用 NetworkInterface.Ports 遍历每个端口,取 IIpAddress 的 Value,用 Dictionary 撞键报冲突。
using System;
using System.Collections.Generic;
using System.Linq;
using Siemens.Engineering;
using Siemens.Engineering.HW;
using Siemens.Engineering.HW.Features;
public sealed class IpConflict
{
public string Ip { get; set; }
public string Device1 { get; set; }
public string Device2 { get; set; }
public override string ToString() => $"IP 冲突 {Ip}: [{Device1}] vs [{Device2}]";
}
public static class IpConsistencyChecker
{
/// <summary>遍历工程内所有 NetworkInterface 的所有 Port,收集 IIpAddress.Value 并查重。</summary>
public static List<IpConflict> FindIpConflicts(Project project)
{
var registry = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var conflicts = new List<IpConflict>();
foreach (Device device in project.Devices)
foreach (DeviceItem item in device.DeviceItems)
if (item is NetworkInterface ni)
foreach (Port port in ni.Ports)
if (port.EngineeringNode is IIpAddress ip && ip.Value != null)
{
string key = ip.Value;
string path = $"{device.Name}/{item.Name}/{(port.Name ?? "?")}";
if (registry.TryGetValue(key, out var first))
conflicts.Add(new IpConflict { Ip = key, Device1 = first, Device2 = path });
else
registry[key] = path;
}
return conflicts;
}
}
⚠️ 官方警告:port.EngineeringNode as IIpAddress 拿到的是 ip.Value;该值可能为 null(未配 IP 的端口),必须判空。
6.14.2 下载前的地址一致性校验流程
把 6.1 的连接参数、6.3 的配置构造与本节的校验串成一条流水线:
using System;
using System.Collections.Generic;
using System.Linq;
using Siemens.Engineering;
using Siemens.Engineering.Connection;
using Siemens.Engineering.Download;
using Siemens.Engineering.HW;
using Siemens.Engineering.HW.Features;
using Siemens.Engineering.Online;
/// <summary>下载前的网络与地址一致性校验闸门。任何一项不通过都应中止下载。</summary>
public static class PreDownloadGate
{
public sealed class PlcPlan
{
public DeviceItem CpuItem { get; set; }
public string Path { get; set; }
public string SlotName { get; set; } // TargetInterfaces.Find 的槽位名
public string? OnlineIpOverride { get; set; } // 非 null 时用五参数重载
public DownloadOptions Options { get; set; } = DownloadOptions.Hardware | DownloadOptions.Software;
}
public static void Validate(Project project, IEnumerable<PlcPlan> plans, string pcInterfaceName)
{
// ---- 1. 工程内 IP 唯一性 ----
var conflicts = IpConsistencyChecker.FindIpConflicts(project);
if (conflicts.Count > 0)
throw new InvalidOperationException(
"工程内存在 IP 冲突:\n" + string.Join("\n", conflicts));
// ---- 2. 逐台校验连接配置可达 ----
foreach (PlcPlan plan in plans)
{
OnlineProvider online = plan.CpuItem.GetService<OnlineProvider>();
if (online == null)
throw new InvalidOperationException($"{plan.Path}: 无 OnlineProvider(非 CPU 设备项?)");
ConnectionConfiguration cfg = online.Configuration;
var mode = cfg.Modes.Find("PN/IE")
?? throw new InvalidOperationException($"{plan.Path}: 无 PN/IE 模式");
var pcIf = mode.PcInterfaces.Find(pcInterfaceName, 1)
?? throw new InvalidOperationException(
$"{plan.Path}: 无 PC 接口 {pcInterfaceName};候选:{string.Join(", ", mode.PcInterfaces.Select(x => x.Name))}");
var slot = pcIf.TargetInterfaces.Find(plan.SlotName)
?? throw new InvalidOperationException(
$"{plan.Path}: 无槽位 {plan.SlotName};候选:{string.Join(", ", pcIf.TargetInterfaces.Select(x => x.Name))}");
// ---- 3. 组态 IP 与计划 IP 的一致性 ----
if (plan.OnlineIpOverride is string onlineIp)
{
// 在线 IP 与组态 IP 不一致:必须确认目标接口能创建该地址对象
var ti = slot as ConfigurationTargetInterface;
var addr = ti?.Addresses.Create(onlineIp)
?? throw new InvalidOperationException(
$"{plan.Path}: 无法为 {onlineIp} 创建 ConfigurationAddress(接口类型不符?)");
Console.WriteLine($"[gate] {plan.Path}: 在线 IP 覆盖 = {onlineIp}(组态 IP 不同,已启用五参数重载)");
}
// ---- 4. 状态快照 ----
Console.WriteLine($"[gate] {plan.Path}: slot={plan.SlotName}, " +
$"IsConfigured={cfg.IsConfigured}, State={online.State}");
}
}
}
6.14.3 批量扫描 + 校验 + 下载的组合流程
public static void BatchScanValidateAndDownload(
Project project,
IEnumerable<PreDownloadGate.PlcPlan> plans,
string pcInterfaceName,
DownloadConfigurationDelegate pre,
DownloadConfigurationDelegate post)
{
// Step 1: 只读扫描,把状态打进日志/工单
foreach (var r in BulkOnline.ScanAll(project))
Console.WriteLine("[scan] " + r);
// Step 2: 校验闸门(不通过直接抛,不做任何状态变更)
PreDownloadGate.Validate(project, plans, pcInterfaceName);
Console.WriteLine("[gate] 全部通过");
// Step 3: 逐台下载(单台失败不阻断其余,汇总报告)
var results = new List<(string Path, bool Ok, string Message)>();
foreach (var plan in plans)
{
try
{
var dp = plan.CpuItem.GetService<DownloadProvider>();
if (dp == null) { results.Add((plan.Path, false, "无 DownloadProvider")); continue; }
var cfg = dp.Configuration;
var slot = cfg.Modes.Find("PN/IE").PcInterfaces
.Find(pcInterfaceName, 1).TargetInterfaces.Find(plan.SlotName);
DownloadResult r = plan.OnlineIpOverride is string ip
? dp.Download(slot, ((ConfigurationTargetInterface)slot).Addresses.Create(ip),
pre, post, plan.Options)
: dp.Download(slot, pre, post, plan.Options);
bool ok = r.State != DownloadResultState.Error;
results.Add((plan.Path, ok, $"errors={r.ErrorCount}, warnings={r.WarningCount}"));
}
catch (EngineeringException ex)
{
results.Add((plan.Path, false, ex.GetType().Name + ": " + ex.Message));
}
}
// Step 4: 汇总
foreach (var (path, ok, msg) in results)
Console.WriteLine($"{(ok ? "OK " : "FAIL")} {path} {msg}");
if (results.Any(x => !x.Ok))
throw new InvalidOperationException(
$"{results.Count(x => !x.Ok)} / {results.Count()} 台下载失败");
}
⚠️ 批量下载的三条纪律:① 先只读扫描再动作(ScanAll → Validate → Download),不要边扫边下;② 单台失败不中断整批,但必须汇总报告并在末尾整体判定成败——否则一半成功一半失败会被当成成功;③ 每台 PLC 都要重建 targetConfiguration 与 onlineAddress,IConfiguration 不可跨设备复用。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.11.2.5 节(连接参数)、5.11.1.3 节(
ConfigurationAddress)、5.10.16 节(访问地址);en-blogs(TIA Portal Automated Project Verification with TIA Openness,IP 唯一性校验思路,port.EngineeringNode as IIpAddress+Dictionary撞键)。
本章速查
OnlineProvider三步铁律:先判空(只有 CPU 设备项有此服务)→ 再查Configuration.IsConfigured(未配置就调GoOnline()必失败)→ 最后GoOnline()/GoOffline()(是两个显式方法,不是布尔属性)。State是判断在线的唯一受支持途径,枚举共 7 种(Offline / Connecting / Online / Disconnecting / Incompatible / Not reachable / Protected)。Download的参数:IConfiguration(必需)+onlineAddress(ConfigurationAddress,可选,构成五参数重载)+predelegate +postdelegate +DownloadOptions。IConfiguration由ConfigurationAddress与ConfigurationTargetInterface两个类实现,都从ConnectionConfiguration导航获得。- 配置构造链:
Configuration.Modes.Find("PN/IE")→PcInterfaces.Find("适配器名", 1)(两参:名 + 序号)→TargetInterfaces[0]或Find("2 X3")。PC 网卡名是硬件相关的,硬编码会在换网卡的机器上失败——先枚举后选取。 - ⚠️ 在线 IP 与离线组态 IP 不一致是下载事故的头号来源:必须用同一
ConfigurationTargetInterface的Addresses.Create(实际IP)构造ConfigurationAddress并改用五参数重载,否则会下到错误设备。 - ⚠️ 不存在
DownloadWithoutStop与SoftwareDownload这两个 API(V21 PublicAPI XML 检索均为 0 命中)。“不停机下载”靠 pre/post delegate 里的StopModules.CurrentSelection = StopAll与StartModules.CurrentSelection = StartModule实现——语义上必然经历”停 → 下载 → 启”三个阶段,真要绝不停机必须走旁路方案。 DownloadOptions是四个值的位标志:None=0/Hardware=1/Software=2/SoftwareOnlyChanges=4。Software与SoftwareOnlyChanges互斥,同用直接抛异常;任何未列出的位组合(如(DownloadOptions)11)也抛异常。V21 共 43 种提示类型(V20 为 42 种),delegate 应采用”显式处理已知类型 + 兜底放行未枚举新类型”的模式。- delegate 的官方写法是
as分派 + 早返回 +return,且手册明确要求”避免任何超出解决该配置所需的修改”。未处理或处理不当 →EngineeringTargetInvocationException中止下载;delegate 内抛异常 →EngineeringDelegateInvocationException。密码必须从安全位置读取,四个密码类配置都有IsSecureCommunication属性可用于判断是否为 TLS 安全通信。 - 编译是下载的一部分(官方原话),但建议下载前先显式
Compile()以获得完整反馈。ICompilable.Compile()支持 13 类对象(含HmiTarget),前置条件是所有设备必须 Offline。GenerateBlocksFromSource(GenerateBlockOptions.KeepOnError)不抛异常并返回对象列表,可逐对象Compile()拿诊断——这是唯一能拿到逐块错误的方式。 - 能力边界:Openness 不支持读写 CPU 运行模式、清强制(Unforce)、读诊断/故障缓冲区、按块选择性下载过滤器,也不支持实时过程值读写。运行模式变更只绑定在下载流程内。这些必须改走 OPC UA(S7-1500 OPC UA 服务器默认禁用、需运行系统许可证、只能走 CPU 集成的 PROFINET 接口,不能经 CP/CM 背板总线)或 S7 通信(Sharp7 / S7netPlus / nodes7)。
- PLCSIM Advanced 有三种通信路径,能力矩阵差异是架构决策点:Softbus 路径不支持 OPC UA 服务器 / Web 服务器 / 与真实 CPU 通信 / 与真实 HMI 通信;改用虚拟网卡路径后三项全部支持——CI 要用 OPC UA 就必须走 TCP/IP 路径。工程侧必做:设备属性 → 保护 → 勾选”块编译时支持仿真”,且 CPU 固件与 PLCSIM Advanced 版本必须匹配。
- CI 场景必须
WithoutUserInterface:TIA Portal 弹模态对话框(保存、兼容性、库冲突)时 Openness 调用会无限挂起;同时 Openness 对.ap持排他写锁,工程必须先关闭才能跑脚本;一个项目同时只允许一个 Openness 进程。 - 上传的 API 形状与下载不同:
StationUploadProvider从 Project 取(不是 DeviceItem);ConfigurationAddress在ConfigurationPcInterface.Addresses上创建(少一层TargetInterfaces);配置类型基类是UploadPasswordConfiguration(Password 在前),与下载侧的DownloadPasswordConfiguration(Password 在后)拼写相反;设备组不支持站点上传。
第7章 通信集成与数据格式交换
第 7 章 通信集成与数据格式交换
本章导读
这一章的核心是先把三种交换格式彻底分开——这是 Openness 生态里最常见、代价也最高的混淆点。Simatic ML(西门子基于 XML 的私有标准,服务于 PLC 软件数据与 HMI 数据)、AutomationML / AML(基于 XML 的开放标准,服务于硬件 CAx 数据,与 EPLAN Electric P8 双向交换)、OPC UA XML(OpcUaExportProvider 专有格式,服务于 PLC 数据向 OPC UA 客户端暴露)三者互不通用、互不替代,选错格式的典型后果是导入静默失败或数据丢失。官方手册第 6 章(Export/import)用 6.3 节讲 HMI 数据、6.4 节讲 PLC 数据、6.5 节讲硬件数据,三节各自独立成章,就是这个划分的制度化体现。本章先给出一张完整对照表,再逐一展开 Simatic ML 的版本陷阱、AML 的 CAEX V2.15 结构与 EPLAN 双向流程、OPC UA 三件套(导出 / 安全策略 / 用户管理),最后落到与第三方 SCADA / MES / EPLAN 的集成案例与架构分工建议。
必须记住的一条官方警告(手册 6.1.1 节原文):“The export format is internal and valid exclusively for the current version of TIA Portal Openness. The export format may change in future versions.” ——不要把 Simatic ML 当作长期存档格式。
7.1 三种交换格式的严格区分
7.1.1 格式对照表(全表,务必背下来)
| 维度 | Simatic ML | AutomationML / AML | OPC UA XML |
|---|---|---|---|
| 中文名 | 西门子 XML 交换格式 | AutomationML(自动化标记语言) | OPC UA 数据交换 XML |
| 性质 | 西门子私有标准(基于 XML) | 开放标准(基于 XML),规范站 https://www.automationml.org/ | OPC UA 生态专用格式 |
| 适用对象 | 软件数据:PLC 块、PLC 变量表、变量与常量、UDT、工艺对象;HMI 画面/模板/全局画面/弹出/滑入画面、VB 脚本、文本列表、图形列表、周期、连接、变量表、变量 | 硬件组态数据(CAx):设备、模块、子模块、机架、子网、IO 系统、I/O 地址、扩展机架连接、GSD/GSDML 自定义属性、拓扑 | PLC 数据在 OPC UA 服务器上的节点映射:供 OPC UA 客户端(SCADA/MES/云平台)订阅与读写 |
| 生成方式 | 各对象的 .Export(FileInfo, ExportOptions) 与集合的 .Import(FileInfo, ImportOptions) | CaxProvider.Export(...) / CaxProvider.Import(...) | OpcUaExportProvider.Export(plc, FileInfo) |
| 入口 | 各类对象/集合的 Export / Import 方法 | project.GetService<CaxProvider>() | project.HwUtilities.Find("OPCUAExportProvider") as OpcUaExportProvider |
| Schema | 随 TIA 安装提供:C:\Program Files\Siemens\Automation\Portal Vxx\PublicAPI\Vxx\Schemas\ | CAEX schema Version 2.15(CAEX_ClassModel_V2.15.xsd) | OPC UA 节点模型对应 |
| 文件扩展名 | .xml | .aml | .xml |
| 消费者 | TIA Portal(另一工程 / 同一工程的覆盖导入);外部工具(Git 友好的 diff、批量改写) | EPLAN Electric P8、TIA Selection Tool、TIA Portal;任何支持 AutomationML 的 CAx 工具 | OPC UA 服务器 / 客户端生态 |
| EPLAN 交换方向 | ❌ 不用于 EPLAN | ✅ 双向:TIA → EPLAN(导出 CAx 数据)、EPLAN / Selection Tool → TIA(导入 CAx 数据) | ❌ |
| 版本有效性 | ⚠️ 仅对当前 Openness 版本有效(详见 7.2) | 开放标准,跨工具跨厂商 | 随 OPC UA 服务器配置变化 |
| 许可前提 | 随 TIA Portal | ⚠️ Siemens_TIA_Openness 软件必须与博途版本一致,安装后需重启电脑;用户需加入 Windows 组 “Siemens TIA Openness” | OPC UA 服务器默认禁用,须在 CPU 属性激活;需要运行系统许可证 |
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 6.1.1 节(“The export and import file format is XML. Only CAx data require AML format.”)、6.3 / 6.4 / 6.5 三节划分、6.5.1 节(AML 与 CAEX V2.15)、6.4.2.29 节(OPC UA XML);PLC技术网《TIA Portal Openness 概述》(三种格式辨析);advanced.md(三种格式对照与 AML 前置条件)。
7.1.2 为什么必须区分:三个最典型的误判
| 误判 | 后果 | 正确做法 |
|---|---|---|
| 以为「硬件组态导出也是 Simatic ML」 | 用 ExportToFile 导设备,EPLAN 侧完全读不懂 | 硬件走 CaxProvider + .aml |
| 以为「OPC UA 数据也走 Simatic ML」 | 手动改 Simatic ML 加 OPC UA 节点,服务器不认 | 必须用 OpcUaExportProvider 导出的专用 OPC UA XML |
| 以为「Simatic ML 可以当归档格式」 | 升级 TIA Portal 后历史存档无法导入 | 长期归档用 SIMATIC SD(YAML),自 V20 起跨版本兼容 |
7.2 Simatic ML:版本陷阱与官方警告
7.2.1 ⚠️ 官方警告原文与长期方案
手册 6.1.1 节原文:
“The export format is internal and valid exclusively for the current version of TIA Portal Openness. The export format may change in future versions.”
“仅对当前版本有效”的确切含义(手册 6.1.3 节给出的规则):
| 规则 | 内容 |
|---|---|
| 导出文件的版本对应 TIA Portal 版本,而非 Openness API 版本 | ”The SimaticML file version for export always corresponds with the used TIA Portal version, regardless which Openness API version is used.” |
| V18 及之前 | 导入 SimaticML 只能在”使用的 Openness API 版本 ≥ SimaticML 版本”时进行 |
| V19 起 | 所有 SimaticML 版本都能被所有 Openness API 版本导入,只要该 SimaticML 版本在 LTS 范围内(即 Vxx-3 到 Vxx)。这 enables 长期稳定的往返(roundtrip) |
注:
Vxx指当前安装的 TIA Portal 版本。
长期存档方案:SIMATIC SD(Simatic Source Document)
| 特性 | SIMATIC SD | Simatic ML |
|---|---|---|
| 格式 | YAML(文本化) | XML |
| 起始版本 | TIA Portal V20 起可通过 Openness API 使用 | 一直可用 |
| 跨版本兼容 | ✅ 自 V20 起跨 TIA Portal 版本兼容 | ❌ 仅对当前 Openness 版本有效 |
| 已明确支持 | S7-1200 / S7-1500 的块、LAD 程序代码在 SIMATIC SD 中的表示 | PLC 与 HMI 全类对象 |
| 支持范围 | ”continuously becoming available for more elements”(持续扩大中,须按目标版本核对清单) | 已覆盖手册第 6.3 / 6.4 节全部对象 |
| 配套文档 | 《Exporting and importing blocks in SIMATIC SD format》《Representation of LAD program code in SIMATIC SD format》 | 手册第 6 章 |
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 6.1.1 节、6.1.3 节;advanced.md(SIMATIC SD 条目,Entry 109792902)。
7.2.2 Simatic ML 的对象覆盖全表(官方 6.1.1 节三张表)
Table 6-1 Projects
| 对象 | Export | Import |
|---|---|---|
| Project graphics | X | X |
Table 6-2 PLC
| 对象 | Export | Import |
|---|---|---|
| Blocks | X | X |
| Know-how protected blocks | X | –(不支持导入) |
| Failsafe blocks | X | X |
| System blocks | X | – |
| PLC tag tables | X | X |
| Technology objects | X | X |
| PLC tags and constants | X | X |
| User data types | X | X |
| DBs | X | –(DB 只能出不能进) |
Table 6-3 HMI
| 对象 | Export | Import |
|---|---|---|
| Screens | X | X |
| Screen templates | X | X |
| Global screens | X | X |
| Pop-up screens | X | X |
| Slide-in screens | X | X |
| Scripts | X | X |
| Text lists | X | X |
| Graphic lists | X | X |
| Cycles | X | X |
| Connections | X | X |
| Tag table | X | X |
| Tags | X | X |
⚠️ 两条不可逆约束(决定数据流设计):
- DB 不能导入——只能通过”外部源(
CreateFromFile+GenerateBlocksFromSource)“或”源程序生成”路径创建 - Know-how 保护块不能导入——官方片段代码在筛选导入目标时必须加
&& !plcBlock.IsKnowHowProtected,否则因无法读取保护块而失败 - System blocks 不能导入
7.2.3 “完整导出”与”开放引用”(open references)机制
这是理解 Simatic ML 语义的核心,官方 6.1.1 节定义:
| 规则 | 内容 |
|---|---|
| 同子树完整导出 | 上表列出的对象类型与其同子树的所有对象一起被导出/导入;同子树的被引用对象也适用此规则 |
| 跨子树只能开放引用 | 对其他子树的被引用对象,无法完整导出/导入,改为导出”开放引用(open references)“ |
| 引用对象的属性不写出 | 跨子树被引用对象的对应属性不写入导出文件 |
| 动态化也作为对象 | 对象上的任何 dynamization 在导入/导出时被当作对象处理,随之导出与导入 |
| 导出包含所有”被改过”的属性 | 无论该属性是否被使用,都会被包含。官方例子:配置过 IO 域的”Scroll bar type”属性,在模式改为”Two states”后该属性不可用,但因为改过所以仍进导出文件 |
| 库对象类型不支持导出 | 在库中创建的对象类型,项目中的实例导出时不含类型信息;重新导入时实例被覆盖并与对象类型解绑(detached) |
| 导入时开放引用自动链接 | 若被引用对象在目标工程中存在,开放引用会自动重新链接;要求对象位于相同位置且分配了相同名称。若不存在,开放引用无法解析,也不会额外创建对象 |
唯一官方警告的免责边界(手册 6.1.1 节开头):
“There are no obligations or guarantees of any kind associated with using this description to manually modify and evaluate the source file. Siemens therefore accepts no liability arising from the use of all or part of this description.”
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 6.1.1 节(Table 6-1/6-2/6-3、Complete export or export of open references、库对象类型说明、免责条款)。
7.2.4 ExportOptions 与 ImportOptions 语义
ExportOptions(手册 6.1.5 节)
| 选项 | 语义 |
|---|---|
ExportOptions.None | 只导出被修改的数据或与默认值不同的数据;导出文件也包含后续导入所必需的全部值 |
ExportOptions.WithDefaults | 默认值也导出 |
ExportOptions.WithReadOnly | 只写(防写)属性的值也导出 |
组合语法(官方脚注原文):
Export(path, ExportOptions.WithDefaults | ExportOptions.WithReadOnly);
⚠️ 导出文件内容一律是英文(“The entire contents of the export file are in English”),但其中包含的工程文本会以工程中存在的所有语言导出与导入。
ImportOptions(手册 6.1.6 节)
| 选项 | 语义 | 失败条件 |
|---|---|---|
ImportOptions.None | 不覆盖导入。若导入对象在工程中已存在,导入被中断并抛异常 | 存在同名对象即失败 |
ImportOptions.Override | 自动覆盖导入。工程中已存在的相关对象在导入前先删除、再用默认值重建,随后默认值被导入值覆盖 | 若已存在对象与新对象不在同一 group,覆盖无法进行;为避免命名冲突,导入被取消并抛异常 |
ImportOptions.SkipInactiveCultures | 导入文本时跳过工程中未激活的语种所对应的文本 | — |
ImportOptions.ActivateInactiveCultures | 导入文本时自动激活所需的工程语种 | — |
组合规则(官方原文):
- 可用**按位或(
|)**组合 - ⚠️
ActivateInactiveCultures与SkipInactiveCultures互斥,组合使用会导致导入失败 - ⚠️ 使用按位与(
&)组合会得到ImportOptions.None,无论原本如何组合
导入的其他官方限制:
- 导入文件中所有根对象必须是同一类型(如全是变量表,或全是块)
- ⚠️ 若导入文件含多个根对象且其中一个无效,整个文件都不导入
- 导入文本时目标工程必须已设置对应工程语种,否则导入失败;必要时可先改语种设置或在 Import 时用新的 culture specific 枚举选项
- ⚠️
ImportOptions.ActivateInactiveCultures与ImportOptions.SkipInactiveCultures对任何基于 Excel 的导入 API 都不支持(如ImportSupervisionSettingsFromXlsx),使用会导致导入失败 - 若导入文件中对象含有 TIA Portal 图形界面里无法编辑的非法属性,导入被取消
- 只有”每个连接分别列出”(separately for each connection)字段中列出的区域指针可导入/导出
- 图形属性的设备相关值域:若图形属性值超出有效值域,导入时会被重置为该 HMI 设备的可能最大值
- ⚠️ 库对象类型的导入不支持(同 7.2.3)
Import 失败的诊断路径(官方原文):导入到 TIA Portal 时,数据先做 XML 格式的形式错误检查;检测到错误则取消导入并在异常中显示错误。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 6.1.5 节、6.1.6 节。
7.2.5 “导出 → 外部修改 → 覆盖导入”的完整实现
这是全章最常用的代码路径(Git 友好格式生成、批量 find & replace、结构化批量改写的共同基础)。官方代码片段仓库的 TagTableSnippets.cs 提供了标准套路:
using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Siemens.Engineering;
using Siemens.Engineering.SW;
using Siemens.Engineering.SW.Tags;
// 取 PlcSoftware:官方推荐的 Classification 过滤写法
PlcSoftware plcSoftware = project.Devices
.First(x => x.Name == "PLC_1")
.DeviceItems
.First(x => x.Classification == DeviceItemClassifications.CPU)
.GetService<SoftwareContainer>()
.Software as PlcSoftware;
// === 第一步:导出(WithDefaults 保留默认值,便于 diff 稳定)===
var myTagTable = plcSoftware?.TagTableGroup.TagTables
.FirstOrDefault(x => x.Name == "DemocaseAppTagTable");
var tempPath = Path.Combine(Path.GetTempPath(), "DemocaseAppTagTable_export.xml");
myTagTable?.Export(new FileInfo(tempPath), ExportOptions.WithDefaults);
// === 第二步:外部修改 XML ===
var xdoc = XDocument.Parse(File.ReadAllText(tempPath));
// ⚠️ 关键坑:XML 元素名必须以实际导出文件为准,换版本时不要硬编码
var plcTags = xdoc.Descendants("SW.Tags.PlcTag").ToList();
var nameElement = plcTags.FirstOrDefault()?.Element("AttributeList")?.Element("Name");
if (nameElement != null) nameElement.Value = "NewTagName";
xdoc.Save(tempPath);
// === 第三步:覆盖导入 ===
plcSoftware?.TagTableGroup.TagTables?.Import(new FileInfo(tempPath), ImportOptions.Override);
PLC I/O 标签表的官方 XML 根结构(来自博客对官方导出文件的实测,可用于校验自己生成的文件):
<Document>
<Engineering version="V16" />
<SW.Tags.PlcTagTable ID="0">
<!-- 每个 ID 都必须唯一,包括 tag / comment / 多语言文本对象 -->
...
</SW.Tags.PlcTagTable>
</Document>
标签属性集:DataTypeName(如 Bool)、LogicalAddress(如 %I0.0)、ExternalAccessible / ExternalVisible / ExternalWritable(均 true)、Name,以及嵌套的 MultilingualText → MultilingualTextItem(Culture = en-US,Text)。
⚠️ 两条范围界定(博客明确):
- Openness XML 导入只配置标签定义与属性(表成员关系、数据类型、外部访问设置、逻辑地址、名字、注释),不写入/强制/改变实时 I/O 值
- HMI tag 与 PLC tag 属于不同 DeviceItem、暴露不同服务:须取 PLC item 的
SoftwareContainer,cast 其Software为PlcSoftware,再对TagTableGroup.TagTables调Import(fileInfo, ImportOptions.Override)
来源:github.md 第 14 条(
TagTableSnippets.cs官方片段,SW.Tags.PlcTag元素名与ImportOptions.Override流程);en-blogs(PLC I/O Tags via XML,XML 根结构与标签属性集);Siemens 官方《TIA Portal Openness》V19 系统手册 6.1.5 / 6.1.6 节。
7.2.6 ⚠️ “生成程序”的两条路线(与本节的格式选择直接相关)
| 路线 | 步骤 | 适用 | 限制 |
|---|---|---|---|
| ① 外部源文件(主流) | SCL / STL 文本 → ExternalSourceGroup.ExternalSources.CreateFromFile(name, path) → PlcExternalSource.GenerateBlocksFromSource() | 生成新块、修改程序逻辑 | 只接受文本格式:.awl(STL) / .scl / .db / .udt,其他扩展名直接抛异常;必须是 ASCII/UTF-8 文本 |
| ② Simatic ML 覆盖导入 | Export → 外部改 XML → Import(..., ImportOptions.Override) | 批量改属性、找替来的批量改写、跨工程复用片段 | 不能逐行编辑块内程序逻辑(LAD/FBD 网络、STL 语句)——这是能力硬边界 |
导入顺序坑(极实用,来自社区实测):先导入的程序若其引用的 DB 还没导入会报错。解决方案:
// 导入时忽略缺失引用
if (Path.GetExtension(filePath).Equals(".xml"))
(destination as PlcBlockGroup).Blocks.Import(
fileInfo, importOption,
SWImportOptions.IgnoreMissingReferencedObjects);
⚠️ 块未编译不能导出 XML:报 Inconsistent blocks cannot be exported。必须先 if (!mainBlock.IsConsistent) mainBlock.GetService<ICompilable>().Compile();。
来源:PLC技术网《TIA Portal Openness 概述》(两条路线与外部源扩展名限制);en-blogs(CODESYS 应用指南第 8 章,
SWImportOptions.IgnoreMissingReferencedObjects;blog园 Aifen 的踩坑根因表,Inconsistent blocks cannot be exported)。
7.3 AutomationML / AML:硬件 CAx 数据交换
7.3.1 开放标准定位与规范依据
手册 6.5.1 节原文:
“AutomationML is a neutral data format based on XML for the storage and exchange of plant engineering information, which is provided as open standard. Goal of AutomationML is to interconnect the heterogeneous tool landscape of modern engineering tools in their different disciplines, e.g. mechanical plant engineering, electrical design, HMI, PLC, robot control.”
CAx 导出/导入所依据的 AML 标准白皮书(官方列举):
| 标准 | 版本 / 日期 |
|---|---|
| AutomationML Part 1 – AutomationML Architecture | November 2018 |
| AutomationML Part 2 – AutomationML Role Libraries | October 2014 |
| AutomationML – AutomationML Communication | September 2014 |
| AutomationML – AutomationML and eCl@ss Integration | November 2021 |
| Best Practice Recommendation: Multilingual expressions in AutomationML | March 2017 |
| Best Practice Recommendation: Modelling of Reference Designations | September 2017 |
Schema:“The AutomationML data exchange model is described by the CAEX schema Version 2.15.”
EPLAN 导出对话框中有两种输出格式可选:AML (*.aml) 与 AML 2.0 (*.aml)。
7.3.2 ⚠️ Pruned AML(裁剪 AML)—— 与 EPLAN 交换的关键概念
手册 6.5.2 节定义的裁剪规则(外部工具如 EPLAN 会自动生成 pruned AML):
| # | 规则 |
|---|---|
| 1 | 可插入(pluggable)的 device item 不裁剪 |
| 2 | 类型为 "interface" 或 "port" 的 device item 不裁剪 |
| 3 | 内置(built-in)且位于 rack 级的 device item 不裁剪 |
| 4 | 类型为 "diagnosis" 的 AddressObject 与裁剪算法无关 |
| 5 | 与自动生成的子模块关联的 AddressObject 应挂在其直接父对象下(该父对象须是非自动生成的子模块) |
| 6 | AddressObject 的包含顺序须与 TIA Portal Openness 返回的顺序一致 |
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 6.5.1 节、6.5.2 节;advanced.md(AML 章节,EPLAN 双向操作与部件库/宏文件前置条件)。
7.3.3 AML 文件结构
<?xml version="1.0" encoding="utf-8"?>
<CAEXFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
FileName="Project82.aml"
SchemaVersion="2.15"
xsi:noNamespaceSchemaLocation="CAEX_ClassModel_V2.15.xsd">
<AdditionalInformation>
<WriterHeader>
<WriterName>Totally Integrated Automation Portal</WriterName>
<WriterID>1d4fcebb-1ad6-4881-b01d-bca335d94a46:V1.0</WriterID>
<WriterVendor>Siemens AG</WriterVendor>
<WriterVendorURL>www.siemens.com</WriterVendorURL>
<WriterVersion>15</WriterVersion>
<WriterRelease>1500.0100.0.0</WriterRelease>
<LastWritingDateTime>2018-05-03T11:23:10.3011329Z</LastWritingDateTime>
</WriterHeader>
</AdditionalInformation>
<AdditionalInformation AutomationMLVersion="2.0" />
<AdditionalInformation DocumentVersions="Recommendations">
<Document DocumentIdentifier="AR APC" Version="1.1.0" />
</AdditionalInformation>
<InstanceHierarchy Name="APC Sample Instance Hierarchy">
<InternalElement ID="6cd7f80f-..." Name="Project4">
<Attribute Name="ProjectManufacturer" AttributeDataType="xs:string" />
<InternalElement ID="b27045c4-..." Name="Ungrouped devices">
<InternalElement ID="3f770698-..." Name="ET 200SP station_1">
<Attribute Name="TypeIdentifier" AttributeDataType="xs:string">
<Value>System:Device.ET200SP</Value>
</Attribute>
<InternalElement ID="6f52fbab-..." Name="Rack_0">
<Attribute Name="TypeName" AttributeDataType="xs:string"><Value>Rack</Value></Attribute>
...
<InternalElement ID="f7445c0b-..." Name="BA 2xRJ45">
<Attribute Name="TypeName" AttributeDataType="xs:string"><Value>BA 2xRJ45</Value></Attribute>
<Attribute Name="PositionNumber" AttributeDataType="xs:int"><Value>127</Value></Attribute>
<Attribute Name="BuiltIn" AttributeDataType="xs:boolean"><Value>false</Value></Attribute>
<Attribute Name="TypeIdentifier" AttributeDataType="xs:string">
<Value>OrderNumber:6ES7 193-6AR00-0AA0</Value>
</Attribute>
<Attribute Name="FirmwareVersion" AttributeDataType="xs:string"><Value>V0.0</Value></Attribute>
<InternalElement ID="40f8bbce-..." Name="PROFINET interface">
<Attribute Name="Label" AttributeDataType="xs:string"><Value>X1</Value></Attribute>
<Attribute Name="PositionNumber" AttributeDataType="xs:int"><Value>1</Value></Attribute>
<Attribute Name="BuiltIn" AttributeDataType="xs:boolean"><Value>true</Value></Attribute>
<InternalElement ID="8fb775eb-..." Name="IE1">
<Attribute Name="Type" AttributeDataType="xs:string"><Value>Ethernet</Value></Attribute>
...
结构要点:
| 段 | 内容 |
|---|---|
AdditionalInformation / WriterHeader | 写入工具信息(名称、ID、厂商、URL、版本、release、最后写入时间) |
AdditionalInformation AutomationMLVersion | AML 版本 |
AdditionalInformation DocumentVersions | 引用的文档版本(如 AR APC 1.1.0) |
InstanceHierarchy | 对象树。关键属性:TypeIdentifier(设备与模块标识,形如 System:Device.ET200SP 或 OrderNumber:6ES7 193-6AR00-0AA0)、TypeName、PositionNumber、BuiltIn、FirmwareVersion |
| 子模块层级差异 | 手册 6.5.16 节说明:Bus Adapter 之类子模块在 TIA Portal 与其他应用(如 EPLAN)中的内部层级不同,AML 通过保持共同层级实现往返交换 |
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 6.5.4 节(AML 文件结构)、6.5.16 节(子模块往返交换)。
7.3.4 CaxProvider 服务与完整导出/导入实现
服务获取(三个入口,V17 起支持多用户环境):
using Siemens.Engineering;
using Siemens.Engineering.Cax;
// 方式 1:单用户工程
ProjectBase project = tiaPortal.Projects.Open(...);
// 方式 2:多用户工程的本地会话
MultiuserProject project = tiaPortal.LocalSessions.Open(...).Project;
// 方式 3:多用户工程的服务器工程
MultiuserProject project = tiaPortal.LocalSessions.OpenServerProject(...).Project;
// 方式 4:单用户工程(简写)
Project project = tiaPortal.Projects.Open(...);
CaxProvider caxProvider = project.GetService<CaxProvider>();
if (caxProvider == null)
{
// 服务不适用或项目未完全打开
}
工程级导出(三个 API 版本):
| 版本 | 签名 | 返回值 |
|---|---|---|
| V14 起 | caxProvider.Export(Project, FileInfo aml, FileInfo log) | void |
| V17 起 | caxProvider.Export(ProjectBase, FileInfo aml, FileInfo log) | bool(无错完成返回 true) |
| V19 起(overload API) | caxProvider.Export(ProjectBase, FileInfo aml) | TransferResult(结构化结果,不再生成 log 文件) |
// V19 起:工程级导出 + 结构化结果
private static void CaxTransferAtProjectLevel(Siemens.Engineering.ProjectBase project,
CaxProvider caxProvider)
{
FileInfo exportFilePath = new FileInfo("D:\\temp\\ExportFile.aml");
TransferResult projectExportResult = caxProvider.Export(project, exportFilePath);
PrintCaxResult(projectExportResult);
}
// 设备级导出(同样三个版本,V19 起返回 TransferResult)
TransferResult deviceExportResult = caxProvider.Export(deviceToExport, exportFilePath);
CaxImportOptions 三个冲突解决策略(导入到已存在且非空工程时):
| 选项 | 语义 |
|---|---|
CaxImportOptions.MoveToParkingLot | 保留工程中同名的设备,把 CAx 中的设备导入到一个 parkinglot 文件夹 |
CaxImportOptions.RetainTiaDevice | 保留工程中同名的设备,不导入 CAx 中对应的设备 |
CaxImportOptions.OverwriteTiaDevice | 用 CAx 中的设备覆盖工程中同名的设备 |
// V19 起:导入 + 结构化结果
private static void ImportCaxTransfer(Siemens.Engineering.ProjectBase project,
CaxProvider caxProvider)
{
FileInfo importFilePath = new FileInfo("D:\\temp\\ImportFile.aml");
CaxImportOptions importOption = Siemens.Engineering.Cax.CaxImportOptions.RetainTiaDevice;
TransferResult importResult = caxProvider.Import(importFilePath, importOption);
PrintCaxResult(importResult);
}
// 旧版三参数形式(带 log 文件)
caxProvider.Import(new FileInfo(@"D:\Temp\ProjectImport.aml"),
new FileInfo(@"D:\Temp\ProjectImport_Log.log"),
CaxImportOptions.MoveToParkingLot);
TransferResult 结果结构(V19 起,官方 6.5.14/6.5.15 节原表):
| 类型 | 属性名 | 类型 | 访问 |
|---|---|---|---|
TransferResult | ErrorCount | int | Read |
WarningCount | int | Read | |
State | TransferResultState | Read | |
Messages | TransferResultMessageComposition | Read | |
TransferResultMessageComposition | this[int] | TransferResultMessage | Read |
TransferResultMessage | DateTime | DateTime | Read |
ErrorCount | Int | Read | |
WarningCount | Int | Read | |
State | TransferResultState | Read | |
Message | String | Read | |
| (递归) | Messages | TransferResultMessageComposition | Read |
TransferResultState 四个取值:
| 枚举 | 语义 |
|---|---|
TransferResultState.Success | 传输成功完成 |
TransferResultState.Information | 传输完成,有信息 |
TransferResultState.Warning | 传输完成,有警告 |
TransferResultState.Error | 传输完成,有错误 |
官方结果打印函数(可直接复用):
private static void PrintCaxResult(Siemens.Engineering.Cax.TransferResult result)
{
Console.WriteLine($"CAx result summary: {result.State} " +
$"(errors: {result.ErrorCount}, warnings: {result.WarningCount})");
PrintCaxDetailResult(result.Messages);
}
private static void PrintCaxDetailResult(
Siemens.Engineering.Cax.TransferResultMessageComposition messages, int nestingDepth = 0)
{
foreach (Siemens.Engineering.Cax.TransferResultMessage message in messages)
{
string indent = new string(' ', nestingDepth * 2);
Console.WriteLine($"{indent}{message.State} {message.Message} {message.DateTime} " +
$"(errors: {message.ErrorCount}, warnings: {message.WarningCount})");
PrintCaxDetailResult(message.Messages, nestingDepth + 2); // 递归
}
}
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 6.5.14 节(导出三版本 +
TransferResult表 +PrintCaxDetailResult官方代码)、6.5.15 节(导入三参数与 overload、CaxImportOptions表)。
7.3.5 ⚠️ CAx 的四条官方限制
① 不支持导出/导入的设备类型(手册 6.5.14 节原文):
- HMI 设备(push button panels 与 key panels 除外)
- Drives(驱动)
② 不支持的部分 Scalance 设备项(V17 起其规范化 TypeIdentifier 同样适用):
6GK5 602-0BA10-2AA3 6GK5 612-0BA10-2AA3 6GK5 623-0BA10-2AA3
6GK5 627-2BA10-2AA3 6GK5 602-0BA00-2AA3 6GK5 612-0BA00-2AA3
6GK5 613-0BA00-2AA3 System:Device.Scalance/S627
6GK5 495-8BA00-8AA2 6ES7 451-3AL00-0AE
6AV2 104-0xxxx-xxxx 6AV2 155-xxxxx-xxxx
System:Rack.Scalance/X200BA-Y V4.2
System:PC.DCSPlus.LogicalServer
System:Device.DCS_ReduMCStation
System:Device.DCS_MCStation
System:Device.DCS_SimulationStation
System:Device.OPCUAPackageUnit
System:Device.IEC60870
System:Device.S7Com
System:Device.CS3000
⚠️ “Drives 不支持”这一条影响巨大:SINAMICS 驱动无法通过 CAx 交换——这是 7.7 节 EPLAN 集成中必须绕开的边界。
③ V18 起的许可检查(手册原文):
- 从 TIA Portal V18 起,CAx Import 在缺少产品许可时显示错误消息;不需要产品许可的配置则无错创建
- 例:导入以
TypeIdentifier(即 AML 文件中的订货号)配置的 S7-1500 PLC 模块,在缺'Step7'产品许可时失败;而导入 Digital Input/Output 模块(同样以 TypeIdentifier 配置)成功 - 例:导入基于
TemplateIdentifier的配置(Library based configuration 不强制产品许可检查)——如 S7-1500 PLC、数字 I/O 模块等——即使缺'Step7'许可也成功 - 缺许可对 CAx Export 无影响,Export 行为不变
④ 权限检查的变化(V17 起):
- 自 TIA Portal Openness V17 起,CAx export/import 功能的 UI 与 API 不再检查:
Siemens TIA Openness组是否存在、当前 Windows 用户是否为其成员 - 但官方 Note 明确:调用客户端应用的 CAx API 时,仍须应用已用适当的 Openness 证书签名,或
Siemens TIA Openness组存在且用户是成员
7.3.6 EPLAN Electric P8 双向集成完整流程
方向一:TIA → EPLAN
- 项目树右键主 PLC 站点 → 导出 CAx 数据 → 选设备/格式/路径
- EPLAN 侧走
项目数据 → PLC → 导入数据 - 导入后
项目数据 → PLC → 导航器可见地址,无需再建地址
方向二:EPLAN / Selection Tool → TIA
- TIA Selection Tool 导出
*.aml - TIA 中
工具 → 导入 CAx 数据(无需先做任何硬件组态)
⚠️ 前置条件:
| 条件 | 说明 |
|---|---|
Siemens_TIA_Openness 软件必须与博途版本一致 | 安装后需重启电脑 |
| 用户需加入 Windows 组 “Siemens TIA Openness” | compmgmt.msc → 本地用户和组 → 组 |
| EPLAN 侧需要对应的部件库文件与宏文件 | 否则导入失败 |
| Selection Tool 导出:无法自定义保存文件名 | 保存后默认为”项目.aml” |
⚠️ 常见故障点(社区实战总结):
- IM155-6 服务器模块部件表问题
- ET 200AL 非放置主功能导致 PLC 导航器显示异常
- 高发场景报错原文:“CAx操作无法启动。请检查是否以 ‘Siemens TIA Openness’ 组成员身份登录”(诊断命令:
whoami /groups | findstr "Siemens",无输出即未加入)
来源:advanced.md(AML 与 CAx 数据交换条目,EPLAN 双向流程与故障点);cn-community(Win10 用户组配置条目的报错原文与诊断命令);Siemens 官方《TIA Portal Openness》V19 系统手册 6.5 节。
7.4 OPC UA:导出、用户管理与安全策略
7.4.1 OpcUaExportProvider:专用 XML 格式
官方说明:手册单列 6.4.2.29 节 Export of data in OPC UA XML format(位于”PLC 数据导出”章内),说明 OPC UA 数据不走 Simatic ML。
前置条件(官方 Requirements):
- 应用已连接 TIA Portal
- 工程已打开
- ⚠️ PLC 不在线(“PLC is not online”)
实现(官方原样):
// 6.4.2.29 节官方代码
private static void OpcUaExport(Project project, DeviceItem plc)
{
OpcUaExportProvider opcUaExportProvider =
project.HwUtilities.Find("OPCUAExportProvider") as OpcUaExportProvider;
if (opcUaExportProvider == null) return; // 未安装 OPC UA 选项包时为 null
opcUaExportProvider.Export(plc,
new FileInfo(string.Format(@"D:\OPC UA export files\{0}.xml", plc.Name)));
}
⚠️ 三条硬约束:
project.HwUtilities.Find("OPCUAExportProvider")可能返回 null,必须判空(官方注释:“未安装 OPC UA 选项包时为 null”)- path 必须是绝对目录路径(官方原文:“As input parameter for the action you need an absolute directory path, where the xml file will be saved.”)
- PLC 必须不在线
7.4.2 CPU 侧 OPC UA 服务器前提
| 前提 | 内容 |
|---|---|
| 默认禁用 | S7-1500 的 OPC UA 服务器默认禁用,须在 CPU 属性 → OPC UA Server General 中激活 |
| 需要运行系统许可证 | 按 CPU 型号分档(见下表) |
| 起始版本 | TIA Portal V14 + S7-1500 V2.0 起支持 |
| 适用 CPU 型 | 含 1500F/T/C、1500pro、ET 200SP CPU、S7-1500 软件控制器、PLCSIM Advanced |
| ⚠️ 不能经 CP / CM 访问 | 不能通过 CP 或 CM 经背板总线访问 CPU 的 OPC UA 服务器;只能走 CPU 上集成的 PROFINET 接口 |
| 应用名称写入证书 | 改应用名后可能需重新生成证书 |
| 管理客户端证书 | 必须先启用项目级全局安全设置(项目树 → 安全设置 → 用户名/密码)+ CPU 属性 → 防护与安全 → 证书管理器 |
| 用户认证 | 分访客认证与用户名密码认证两种;要限定权限须禁用访客认证 |
运行系统许可证分档(官方):
| 档位 | 适用 CPU |
|---|---|
| Small | 1511 / 1512 / 1513、ET 200SP CPU、1515SP PC |
| Medium | 1515 / 1516、CPU 1507、1516pro-2 PN |
| Large | 1517 / 1518 |
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 6.4.2.29 节;advanced.md(OPC UA 数据交换条目,CPU 侧前提与许可证分档)。
7.4.3 OPC UA 服务器接口(OpcUaProvider)
手册 5.17.4 / 5.17.5 节提供了两条不同的接口路径——SIMATIC 接口 与用户自定义服务器接口。
SIMATIC 接口(SimaticInterface)方法表:
| 方法 | 返回类型 | 定义 |
|---|---|---|
Create() | Siemens.Engineering.SW.OpcUa.SimaticInterface | 创建 PLC 的 OPC UA SIMATIC 接口。参数:name(System.String) |
Export() | System.Void | 把 SIMATIC 接口内容写入 XML 文件(参数 filePath,System.IO.FileInfo)。⚠️ 接口无数据时抛异常 |
Delete() | System.void | 从所在 Simatic Interface Composition 移除并释放对象 |
SimaticInterface 属性表:
| 属性名 | 数据类型 | 说明 | 访问 |
|---|---|---|---|
Author | System.String | 作者 | Read/Write |
Comment | Siemens.Engineering.Multilingual.Text | 注释 | Read/Write |
CreationTime | System.DateTime | 创建时间 | Read-only |
Enabled | System.Boolean | 启用 SIMATIC 接口并下装到 PLC | Read/Write |
LastModified | System.DateTime | 最后修改时间 | Read-only |
Name | System.String | 名称 | Read-only |
官方注:“The S7-1200 FW V4.4 will now include support for OPC UA SIMATIC Interfaces.”
用户自定义服务器接口(ServerInterface):
// 从 PlcSoftware 取 OpcUaProvider
PlcSoftware software = ...;
OpcUaProvider provider = software.GetService<OpcUaProvider>();
if (provider != null)
{
OpcUaCommunicationGroup commGroup = provider.CommunicationGroup;
ServerInterfaceGroup serverInterfaceGroup = commGroup.ServerInterfaceGroup;
ServerInterfaceComposition serverInterfaces = serverInterfaceGroup.ServerInterfaces;
// 遍历
foreach (ServerInterface serverInterface in serverInterfaces) { /* ... */ }
// 新建
ServerInterface serverInterface2 = serverInterfaces.Create("New Interface");
serverInterface2.Author = "James Cornett";
// 从 XML 文件设置内容
ServerInterface si = serverInterfaces.Create(name);
if (si != null) si.Import(new FileInfo(importFilePath));
// 导出到 XML
String exportFilePath = Path.Combine(Directory.GetCurrentDirectory(), "ExportInterface.xml");
ServerInterface si2 = ...;
si2.Export(new FileInfo(exportFilePath));
// 删除
ServerInterface si3 = ...;
si3.Delete();
}
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.17.4 节、5.17.5 节(方法表、属性表、全部代码均为官方原文)。
7.4.4 OPC UA 安全策略(OpcUaSecurityPolicies)
作为动态属性(dynamic attribute)实现,类型为 flagged enum OpcUaSecurityPolicies。
⚠️ 前置条件与异常:安全策略只在 OPC UA 服务器已激活时才在 TIA Portal Openness 中可用;若 OPC UA 服务器未激活,访问该属性(及任何其他不可用属性)会抛 EngineeringNotSupportedException。
完整策略枚举表(官方 V19 手册原表):
| TIA UI 名称 | 枚举项 | 值 | 备注 |
|---|---|---|---|
| - | NoneSelected | 0 | 等价于 UI 上未勾选任何复选框 |
| No security | OpcUaSecurityPoliciesNone | 1 | |
| Basic128Rsa15 - Sign | OpcUaSecurityPolicies128RSAS | 2 | |
| Basic128Rsa15 - Sign & Encrypt | OpcUaSecurityPolicies128RSASE | 4 | |
| Basic256 - Sign | OpcUaSecurityPolicies256S | 8 | |
| Basic256 - Sign & Encrypt | OpcUaSecurityPolicies256SE | 16 | |
| Basic256Sha256 - Sign | OpcUaSecurityPolicies256SHAS | 32 | |
| Basic256Sha256 - Sign & Encrypt | OpcUaSecurityPolicies256SHASE | 64 | |
| Aes128 - Sha256 - RsaOaep - Sign | OpcUaSecurityPolicies256RSAOAEPS | 128 | |
| Aes128Sha256 - RsaOaep - Sign & Encrypt | OpcUaSecurityPolicies256RSAOAEPSE | 256 | |
| Aes256Sha256RsaPss - Sign | OpcUaSecurityPolicies256RSAPSSS | 512 | |
| Aes256Sha256RsaPss - Sign & Encrypt | OpcUaSecurityPolicies256RSAPSSSE | 1024 |
WinCC Professional 场景的安全策略配置文件(手册 5.17.1 节):需在 XML 文件 <Project>\OPC\uaserver\OPCUaServerWinCCPro.xml 中按 ES 设备配置设置 security policy 与 security policies;默认值为 Enabled / Sign / Sign and Encrypt;安全策略 "Basic256Sha256" 需先加到 Runtime Settings。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.17.1 节、5.17.2 节(策略表为官方原表)。
7.4.5 Web 服务器与 OPC UA 用户管理
Web 服务器用户(WebServerUserManagement)
结构(官方原样):
WebServerUserManagement
{
Navigators WebServerUsers
{
WebServerUserComposition
}
}
WebServerUserComposition
{
WebServerUser Find(string username);
void Create(string username, WebserverUserPermissions permissions, SecureString password);
}
WebServerUser
{
string UserName { get; }
WebserverUserPermissions Permissions { get; set; }
void Delete();
void SetPassword(SecureString password);
}
⚠️ 操作前提:只有在模块上 Web 服务器已激活时才能执行添加/删除/设置密码操作。未激活时增删改抛 EngineeringTargetInvocationException,但读操作仍可用。 服务只在支持该功能的 PLC 实例上可用,其他设备返回 null。
OPC UA 用户(OpcUaUserManagement)
OpcUaUserManagement
{
Navigators OpcUaUsers
{
OpcUaUserComposition
}
}
OpcUaUserComposition
{
OpcUaUser Find(string username);
void Create(string username, SecureString password);
}
OpcUaUser
{
string UserName { get; }
void Delete();
void SetPassword(SecureString password);
}
⚠️ 操作前提:只有 OPC UA 服务器已激活且启用了用户名/密码认证时才能增删改。条件不满足时抛 EngineeringTargetInvocationException,错误详情说明需要启用认证。服务在所有具备 OPC UA 子模块的设备项上可用,其他情况返回 null。
⚠️ 注意两者差异:Web 服务器用户的 Create 需要一个 WebserverUserPermissions 参数(官方示例值为 WebserverUserPermissions.ReadTagStatus),OPC UA 用户的 Create 不需要权限参数。
完整实现:
using System;
using System.Security;
using Siemens.Engineering;
using Siemens.Engineering.HW;
using Siemens.Engineering.HW.Features;
// ---------- Web 服务器用户管理 ----------
public static void ManageWebServerUsers(DeviceItem plcWebModule, SecureString password)
{
WebServerUserManagement mgmt = plcWebModule.GetService<WebServerUserManagement>();
if (mgmt == null) { Console.WriteLine("该设备不支持 Web 服务器用户管理"); return; }
WebServerUserComposition users = mgmt.WebServerUsers;
// 查询
WebServerUser user1 = users.Find("user1");
// 创建(需 Permissions 参数)
if (users.Find("scadaReader") == null)
users.Create("scadaReader", WebserverUserPermissions.ReadTagStatus, password);
// 改密码
WebServerUser u = users.Find("scadaReader");
if (u != null) u.SetPassword(password);
// 删除
WebServerUser toRemove = users.Find("obsoleteUser");
toRemove?.Delete();
}
// ---------- OPC UA 用户管理 ----------
public static void ManageOpcUaUsers(DeviceItem plcOpcUaModule, SecureString password)
{
OpcUaUserManagement mgmt = plcOpcUaModule.GetService<OpcUaUserManagement>();
if (mgmt == null) { Console.WriteLine("该设备无 OPC UA 子模块"); return; }
OpcUaUserComposition users = mgmt.OpcUaUsers;
OpcUaUser existing = users.Find("mesClient");
if (existing == null)
users.Create("mesClient", password); // 不需要 Permissions 参数
users.Find("mesClient")?.SetPassword(password);
users.Find("obsolete")?.Delete();
}
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.11.2.24 节(结构定义、前提条件、全部代码均为官方原文)。
7.4.6 OPC UA 建模接口 Add-In:性能最优路径
官方 GitHub 仓库 tia-portal-applications/tia-addin-opc-ua-modelled-interface(20 stars,License “Other”,当前版本 V2.0.0,最后推送 2026-05-14)提供了比手写 ServerInterface XML 更高效的路径:
| 维度 | 内容 |
|---|---|
| 用途 | 用 Openness 遍历项目自动生成 OPC UA 服务器的 user-modelled interface,替代默认 SIMATIC server interface |
| 性能收益 | modelled interface 下 OPC UA 客户端创建 monitored item、读写数据最多快 6 倍 |
| 适配性价值 | S7-1200 与 S7-1500 R/H 型 PLC 本身不支持默认 SIMATIC server interface,此 Add-In 是它们的自动化出路 |
| 分支硬性规则 | V17~V20 用 tia-addin-opc-ua-modelled-interface-v20 分支;V21+ 用 main 分支 |
| 安装路径 | C:\Users\<user>\AppData\Roaming\Siemens\Automation\Portal V21\UserAddIns\OpcUa-UserModelledInterface.addin;构建后事件自动拷贝;Visual Studio 必须以管理员身份运行 |
| 工作机制 | 本质是 XML 模板工程:导入 XML 基础模板为 XDocument → 扫描所有系统/用户数据类型加入 UADataTypes 与 UAObjects → 遍历变量表加 Tags → 遍历全局与实例 DB → 导出 XML 再导入 TIA |
| 使用流程 | ① 复制 .addin 到 UserAddIns ② TIA 打开项目 → 右侧 Add-Ins 选项卡激活 ③ 右键某设备 → Add-In → “Create”(重建默认接口,全部参数预置)或 “Extend create”(自定义过滤/优化/去空 DB/保留目录结构)④ 右键 Software Unit → “Create server interface for SW Units” |
| 已知限制(README 原样) | 无法在不启用 “optimize” 模式的前提下使用;NodeID 不能配为数字标识符;⚠️ 嵌套实例化的 FB 被建模为 “UAObjects” 而非 “UAVariables”,其内部变量无法通过生成的接口访问 |
| 其它选项 | 访问级别过滤器(安全参数默认只读)、移除空 DB、保留项目文件夹结构(增加节点数但更易浏览);生成 log 文件记录未纳入接口的节点 |
同组织另有 SiOME-Base-Plugin(12 stars,MIT,最后推送 2026-08-11)——这是外部 OPC UA Modeling Editor(SiOME)插件开发基座,官方 SIOS 条目 109755133。它与前者层次不同:前者(tia-addin-opc-ua-modelled-interface)在 TIA 内生成服务器接口,本条做外部 OPC UA 建模工具的插件开发。
来源:github.md 第 27 条、第 32 条。
7.5 Profinet 组态与设备 XML 交换
7.5.1 能力现状:raw 资料未覆盖 PRONPRO
⚠️ 资料缺口声明:raw 资料(advanced.md / official.md / en-blogs.md / github.md / cn-community.md)以及 V19 系统手册全文(txt_api.txt)中,PRONPRO 与 “PROFINET 设备 XML” 均为 0 命中。因此本章不给出 PRONPRO 相关 API,避免编造。
可以确认的 PROFINET 相关 Openness 能力(来自 V19 手册,均为 Siemens.Engineering.HW 命名空间):
| 能力 | API / 章节 |
|---|---|
| 创建子网 | 5.8.1 Creating a subnet |
| 访问子网 / 内部子网 / 子网 TypeIdentifier / 子网属性 / 删除全局子网 | 5.8.2 – 5.8.6 |
| 枚举子网全部参与方 / IO 系统 | 5.8.7 / 5.8.8 |
| 访问节点 / 节点属性 / 连接节点到子网 / 断开 | 5.8.9 – 5.8.12 |
| 创建 IO 系统 / 访问 IO 系统属性 / 连接 IO 连接器 / 取主站或 IO 系统 / 取 IO Controller / 取 IO Connector / 断开 | 5.8.13 – 5.8.19 |
| DP 主站系统属性 / PN Driver 属性 / PROFINET IO 系统属性 / 删除 DP 主站与 PN IO 系统 / 创建 DP 主站系统 | 5.8.20 – 5.8.25 |
| 端口互连信息(port interconnection) | 5.8.26 – 5.8.28:Attributes of port inter-connection、Attributes of the port |
| 枚举 DP 主站系统 / 已分配 IO 连接器 / 连接 DP IO 连接器 | 5.8.29 – 5.8.31 |
| AS-i profile 与参数属性(虚拟从站) | 5.8.32 |
| CM DP 作为 DP 从站与传输区 | 5.8.33 |
| 1516 等时中央 + 分布式 PROFINET | 5.8.34:动态属性 IsochronousMode(bool, rw)、SourceCycleTime(int)、SendClock(float)、IsochronousTiToCalculationMode(enum: FromOB / AutomaticMinimum / Manual)、IsochronousTi(double)、IsochronousTo(double) |
| CP 1604/1616/1626 的传输区 | 5.8.35;NetworkInterface.TransferAreas → Create(name, TransferAreaType.MS) |
| PnPn 耦合器传输区 | 5.8.36 |
R/H 冗余模式 RedundancyMode | 5.8.39:S2 能力设备可作为 Switched S1 运行;属性在 S2/R1 IO 设备的 PROFINET 接口上,可读可写 |
| 扩展机架连接(extension rack connection) | 5.10.22:ImConnection imConnection = portDeviceItem.GetService<ImConnection>(); → Connect(partnerport) / Disconnect() / GetPartnerPort() / OwnedBy;为 CAx 导出/导入提供扩展机架连接支持 |
⚠️ StartAddress 改写的连带风险(官方 5.7.3 节原文,PROFINET 网络配置时必读):
| 风险 | 说明 |
|---|---|
| 对向 I/O 类型地址被隐式改变 | 设置 StartAddress 可能隐式改变同名模块上对向 I/O 类型的起始地址(改输入地址会改输出地址) |
| 打包地址(Packed addresses)Openness 不支持 | — |
| 改地址不会重新连线已分配的变量 | 通过 Openness 改地址不会重新连线已分配的变量 |
| 并非所有设备都支持写 | 写访问不是所有设备都支持 |
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.8 / 5.10.22 节;advanced.md(冗余与细分能力、通信处理器与等时同步条目)。
7.5.2 .psc 文件:PC 系统配置(唯一官方的”设备配置外发”格式)
手册 5.10.21 节提供的 .psc(PC system configuration)导出,是除 AML 之外唯一可外发的硬件配置格式:
// 官方 5.10.21 节原样
// 前置条件
Device ipcDevice = myProject.Devices.Find("IPC427D");
FileInfo exportFile = new FileInfo(@"C:\Users\Ertan\Documents\Automation\PC system configuration74.psc");
// 从 hardware utilities 取 card reader provider
HardwareUtilityComposition utilities = myProject.HwUtilities;
HardwareUtility utility = utilities.Find("CardReaderPscProvider");
CardReaderPscProvider crp = (CardReaderPscProvider)utility;
// 执行导出
crp.Export(ipcDevice, exportFile);
⚠️ 三条官方约束:
- 创建与打开
.psc文件的分开的命令不支持 - 若传入已存在的
.psc文件,该文件不会被覆盖,并抛异常;若文件不存在,则创建并最终完成下载 - 必须确保工程能无错编译,否则抛异常
- V18 及以下版本不支持对 F 激活设备(f-activated devices)执行 Export(导出期间抛异常)
服务在命名空间 Siemens.Engineering.HW.Utilities。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.10.21 节。
7.6 SiVArc / OPCiVArc:电气接线数据
7.6.1 定位与许可
SiVArc 是 Siemens 的电气接线数据工具,其 Openness 接口在 V19 手册中独占 5.18 节(5.18.1 – 5.18.16,共 16 小节)。
| 维度 | 内容 |
|---|---|
| 定位 | 通过 Openness 实例化 SiVArc(“TIA portal openness application allows you to instantiate SiVArc”) |
| 服务获取 | myProject?.GetService<Sivarc>() |
| 许可 | 通过 Openness API 打开工程并做任何 SiVArc 数据变更(Rules 与 Generation)时,系统校验许可;无许可则抛 LicenseNotFoundException(可恢复异常) |
| 前提 | ① 应用已连接 TIA Portal;② 工程中已存在 Screen rules / screen rule groups / screen rule master copy / screen rule group master copy |
服务获取(官方原样,含原文的拼写问题):
// 官方 5.18.1 节原文(注意 Sivarc / TiaPortaMode 的拼写)
using (TiaPortal tia = new TiaPortal(TiaPortaMode.WithUserInterface))
{
Project myProject = tia.Projects.Open(new FileInfo(@"D:\Project\Project.ap19"));
Sivarc sivarc = myProject?.GetService<Sivarc>();
if (sivarc != null) { /* ... */ }
}
⚠️ 官方示例中 TiaPortaMode(缺 l)与 Sivarc(首字母大写)是手册原文的拼写问题。实际 API 是 TiaPortalMode.WithUserInterface(枚举)与 Sivarc(类名,需对照本地 IntelliSense 确认大小写)。
7.6.2 SiVArc 实例属性全表(官方 5.18.3 节)
| 属性名 | 说明 | 数据类型 |
|---|---|---|
AlarmRules | 所有报警规则对象的锚点 | AlarmRulesBrowsable |
ScreenRules | 所有画面规则对象的锚点 | ScreenRulesBrowsable |
TextlistRules | 所有文本列表规则对象的锚点 | TextlistRulesBrowsable |
TagRules | 所有变量规则对象的锚点 | TagRulesBrowsable |
CopyRules | 所有复制规则对象的锚点 | CopyRulesBrowsable |
TextlistRules | 枚举所有第一层文本列表规则 | TextlistRuleComposition |
AlarmRules | 枚举所有第一层报警规则 | AlarmRuleComposition |
ScreenRules | 枚举所有第一层画面规则 | ScreenRuleComposition |
TagRules | 枚举所有第一层变量规则 | TagRuleComposition |
CopyRules | 枚举所有第一层复制规则 | CopyRuleComposition |
Groups | 枚举所有第一层报警规则组 | AlarmRuleGroupComposition |
ScreenRulesGroups | 枚举所有第一层画面规则组 | ScreenRuleGroupComposition |
TextlistGroups | 枚举所有第一层文本列表规则组 | TextlistRuleGroupComposition |
TagRulesGroups | 枚举所有第一层变量规则组 | TagRuleGroupComposition |
CopyRulesGroups | 枚举所有第一层复制规则组 | CopyRuleGroupComposition |
⚠️ 官方注:(
AlarmRules/ScreenRules/TextlistRules/TagRules/CopyRules/Groups等属性名在表中各出现两次,一次是可浏览锚点、一次是枚举组合——这是官方表格的排版,实际以 IntelliSense 为准。)
规则集合的方法表(以 AlarmRule / AlarmRuleGroup 为例,其余 SiVArc 对象同理):
| 方法名 | 参数 | 说明 | 数据类型 |
|---|---|---|---|
Find | String — 报警规则/规则组名 | 从集合中查找规则或规则组 | AlarmRule |
CreateFrom | MasterCopy — 规则/规则组主副本 | 从库把主副本复制到工程(默认替换操作) | AlarmRule |
CreateFrom | MasterCopy, CreateOptions — Rename/Replace | 带创建选项的复制;已存在时重命名(Rename)或替换(Replace) | AlarmRule |
从库复制规则到规则表(官方原样):
// 复制规则主副本到规则表
MasterCopy screenRuleMasterCopy =
project.ProjectLibrary.MasterCopyFolder.MasterCopies.Find("Screen rule_1");
if (screenRuleMasterCopy != null)
{
var rule = sivarc.ScreenRules.Tables.Find("Default screen rule table")
.Rules.CreateFrom(screenRuleMasterCopy);
if (rule != null)
{
Console.WriteLine("Copied screen rule name - " + rule.Name);
Console.WriteLine("Copied screen rule comment - " + rule.Comment);
Console.WriteLine("Copied screen rule enabled - " + rule.Enabled);
Console.WriteLine("Copied screen rule condition - " + rule.Condition);
}
}
⚠️ 默认行为是替换(“By default, the behaviour will be replaced.”)
SiVArc 完整能力域(5.18 节小节清单):
| 小节 | 内容 |
|---|---|
| 5.18.1 – 5.18.3 | 引言 / 许可处理 / 实例属性 |
| 5.18.4 | 从库复制规则或规则组 |
| 5.18.5 | 查找画面规则组与画面规则 |
| 5.18.6 | 为规则与规则组赋属性 |
| 5.18.7 | 创建规则对象与规则组 |
| 5.18.8(8 小节) | 画面规则表与文件夹:创建规则表 / 创建规则文件夹 / 访问规则与组 / 删除规则表与用户自定义规则文件夹 / 访问库类型中的规则表 / 创建规则表实例 |
| 5.18.9 | 访问画面主副本/类型中的文件夹 |
| 5.18.10 | 访问库对象 |
| 5.18.11 | 配置 PLC 与 HMI 设备 |
| 5.18.12 | 删除规则与规则组 |
| 5.18.13 | 配置 Tag 规则 |
| 5.18.14 | 配置 Tag/Text 定义 |
| 5.18.15 | SiVArc 的 UMAC 设置 |
| 5.18.16 | SiVArc 生成(generation) |
官方明确:“Openness supports generation and configuration of alarms through alarm rules for Unified devices.”
⚠️ 关于 OPCiVArc:raw 资料中仅在 official.md 条目标题层出现”SiVArc”字样,
OPCiVArc作为独立术语在全部 raw 资料与 V19 手册中均无命中。本节只写 SiVArc,OPCiVArc 相关能力标为需核实。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.18.1 – 5.18.16 节(属性表、方法表、代码均为官方原文)。
7.7 与第三方 SCADA / MES / EPLAN 的集成案例
7.7.1 EPLAN 集成(AML 路径)
见 7.3.6 节完整流程。此处补充工程化要点:
| 要点 | 内容 |
|---|---|
| 导入方向不需要先做硬件组态 | TIA 中 工具 → 导入 CAx 数据 直接可用 |
| 选择工具的 AML 导出无法自定义文件名 | 默认存为”项目.aml” |
| EPLAN 侧必须先备齐部件库文件与宏文件 | 否则导入失败 |
| CAx 不支持 Drives | SINAMICS 驱动无法走此路径 |
| V18 起的许可差异 | 用 TypeIdentifier 配 S7-1500 需 'Step7' 许可;用 TemplateIdentifier(Library based)不强制许可检查 |
| 社区推荐工作流(先导出再导入) | ① UI 手工建参考项目并配好设备名/IP/子网/模块/I/O 地址 ② 对参考项目跑 CAx export 拿到 schema-valid、版本正确的 XML ③ UI 改一个参数再 export,diff 两份 XML 反推哪个元素控制哪个设置 ④ 把已知 good 的 export 改成模板并参数化 ⑤ 导入目标项目 ⑥ 记录每次导入结果 |
| ⚠️ CAx import 会”部分成功” | 不抛异常 ≠ 导入成功 |
| 开发期纪律 | 务必在项目副本上操作,每次测试前归档——畸形文档可能把项目搞到难以回滚的状态 |
来源:en-blogs(Installing GSDML Files via TIA Openness CAx Import API,完整工作流与排障表);advanced.md(AML 条目);Siemens 官方《TIA Portal Openness》V19 系统手册 6.5.14 节。
7.7.2 GSDML 设备的两阶段概念(与第三方设备集成)
博客给出了一个对理解第三方设备集成非常关键的两阶段划分:
| 阶段 | 内容 | Openness 可否 |
|---|---|---|
| Stage 1:目录安装 | GSDML 解析并注册进硬件目录 | ❌ Openness 未直接暴露。GSDML 放置路径 C:\ProgramData\Siemens\Automation\Simatic.StationConfiguration\GSD,放完需重启 TIA Portal |
| Stage 2:项目实例化 | 把目录中的设备放进项目拓扑并配参数与 I/O | ✅ 通过 CAx import 完成 |
⚠️ Openness 没有直接对应 UI 里 Options > Manage general station description files 的”install GSDML”方法。
⚠️ 核心限制(决定第三方设备集成路线):
| 限制 | 内容 |
|---|---|
| 不能从零创建第三方硬件 | 不支持用 Device.CreateWithItem() / Device.Create() 从零创建第三方硬件(SEW Movitrac/Movidrive、SICK 编码器、FANUC 机器人等)。西门子目录设备(G120、SINAMICS、ET200)才可用 |
| 根因 | 设备目录基于 Siemens.Plattorm.Configuration.Contracts.dll(原文拼写为 Plattorm),第三方厂商未提供 Openness 兼容的 DeviceItemTypeId GUID |
| 官方解法:模板导出法 | 手工配置一次 → 导出模板 → 脚本导入 |
| 模板导入路径 | tiaPortal.EngineeringInstance.CreateSegmentImport("ThirdPartyImport") → import.Import(templatePath) |
| 模板导入后需单独设 IP | i.Properties["IPAddress"].Value = "192.168.1.100" |
| 模板 XML 版本特定 | 升级后需重新生成;设备名必须唯一;无法通过 Openness 设置第三方安全设备的安全参数 |
| 验证流程 | Ctrl+Shift+B 编译 → 检查编译日志 → 确认设备在正确子网 → 核对波特率/循环时间 → 在线访问测试 |
⚠️ 上述 CreateSegmentImport / IDeviceExport / IGSDCatalog 等类型在 en-blogs 末尾「已知内容质量问题」中被明确列为需核对项。若要落地必须以官方手册为准。
来源:en-blogs(Adding Third-Party Hardware via API、Installing GSDML Files via CAx Import API,均标注部分类型需核对)。
7.7.3 SCADA / MES 的 OPC UA 路径(推荐路线)
为什么 OPC UA 是运行期集成的首选:
| 优势 | 说明 |
|---|---|
| 不需要知道绝对地址 | OPC UA 用节点路径而非 DB300.DBD4 绝对寻址——直接解决”SCL 里 DB 编号被静默忽略”这个硬伤(外部 SCL 源里写 DATA_BLOCK "MyDB" DB 300 会被解析但静默忽略,TIA 分配下一个可用编号,对绝对寻址的 SCADA/HMI 是硬伤,西门子热线确认截至 V17 无原生修复) |
| S7-1200 与 1500 R/H 需 modelled interface | 这两类 PLC 本身不支持默认 SIMATIC server interface,须用 tia-addin-opc-ua-modelled-interface |
| 性能 | modelled interface 下客户端创建 monitored item、读写数据最多快 6 倍 |
| S7-1500 支持 | OPC UA 服务器 + TIA Portal V14 / S7-1500 V2.0 起;V21 支持 Aes128Sha256RsaOaep 与 Aes256Sha256RsaPss 安全策略 |
若需要模拟量/脉冲量连接:AxisEncoderHardwareConnectionInterface.Connect(SW.Tags.PlcTag outputTag) 支持把驱动与模拟量设定值接口连到 PLC 变量,可连接 “analog output”、“enable output”、“ready input” 三类标签。
CERN 的生产版本管控实践(值得借鉴的对比方案):生产版本管控用 AUVESY-MDT Octoplant——变更管理与版本历史、自动连生产设备、上传运行中程序并与服务器上最后版本做详细比对、立即标记差异、并对 PLC 中运行版本做定时备份,用来检测未授权/未文档化的现场改动。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.17.2 节(安全策略)、5.11.4.18 节(
AxisEncoderHardwareConnectionInterface)、6.4.2.29 节;github.md 第 27 条;en-blogs(SCL Creating Data Blocks from External Sources,DB 编号静默忽略)、en-blogs(CERN ICALEPCS 2025 论文,Octoplant)。
7.7.4 案例:手写/AI 写 SCL 导入 AI 工具写的 SCL 文件
西门子中国论坛的官方解答给出了 Openness SCL 生成链路的”手工基线”,这是理解自动化与手工对应关系的关键:
三步手工基线:
- 从 TIA Portal 导出模板:创建 FC/FB 块编写基础框架 → 右键该块 →「从块生成源」→「仅所选块」→ 保存为
.scl(本质是文本格式) - 外部编辑器配置:VS Code 装
Siemens SCL插件(语法高亮);Notepad++ 直接打开.scl,装自定义 SCL 语法高亮插件 - 导入 TIA Portal:项目树「外部源文件」→ 右键「添加新的外部文件」→ 选择编辑好的
.scl→ 右键「从源生成块」,同名块会自动覆盖
Openness 做的事情就是把上述三步自动化:「写文本、删同名源、CreateFromFile、GenerateBlocksFromSource」。
常见生成错误(官方):
| 错误 | 原因 |
|---|---|
| 错误 1201:源文件语法错误 | 缺分号/括号/拼写;用 TIA 打开源文件按行号查 |
| 错误 1203:块版本不兼容 | 在相同或更高版本 TIA Portal 中重新执行「从块生成源」再导出 |
Block "FB_name" not found | 实例 DB 引用的 FB 必须已存在于工程中 |
S7_Optimized_Access attribute missing | 属性行必须紧跟 DATA_BLOCK 头之后 |
Duplicate block name | 重复块名 |
| 块显示灰色不可编辑 | 编译未完成或存在警告 |
语言相关坑:多语言块编译失败时,Openness 默认使用项目主语言,需在 Project.Language 设置,或在源码中用 { Language := 'zh-CN' } 声明。
来源:cn-community(西门子中国论坛「SCL外部编程工具应用」官方解答;「如何导入AI工具写的SCL文件」;鸿鹄创新文章的多语言块编译失败)。
7.8 集成架构建议:工程期与运行期的分工
7.8.1 分工原则
┌──────────────── 工程期(Engineering Time)────────────────┐
│ 唯一执行者:TIA Portal Openness │
│ · Simatic ML:块 / 变量表 / HMI 数据(PLC + HMI) │
│ · AML:硬件 CAx 数据(与 EPLAN / Selection Tool 交换) │
│ · 编译(完整错误反馈) │
│ · 下载 / 上传 / 比较 │
│ · 通信参数组态:OPC UA 命名空间、安全策略、用户管理、 │
│ 证书、PLC 系统日志、UMAC 访问权限等级 │
│ 产物:.ap 工程 + .zap 归档 + 版本化的交换文件 │
└───────────────────────┬───────────────────────────────────┘
│ ↓ 工程交付
┌──────────────── 运行期(Runtime)────────────────────────┐
│ 执行者:上位系统(OPC UA 客户端 / S7 通信 / SCADA / MES) │
│ · OPC UA:订阅与读写、审计跟踪、历史归档 │
│ · S7 通信:过程值读写、按块修改变量、强制值清理 │
│ · WinCC Runtime:HMI 画面与脚本(JavaScript) │
│ · SIWAREX 等模块的 Web Server 用户体系 │
└───────────────────────────────────────────────────────────┘
7.8.2 官方给出的两条架构硬建议
| 建议 | 原文要点 | 出处 |
|---|---|---|
| 不要把 Openness 塞进 Web 后端 | TIA Portal 是 Windows 工程软件,Openness 依赖本机安装路径、注册表、用户权限、首次授权弹窗。AI 平台/后端只做任务编排并把任务发队列,真正调 Openness 的 Worker 跑在装了 TIA Portal 的 Windows 工程机上 | 腾讯云开发者社区转载「Hello工控」 |
| 按工程闭环拆能力而非只做”能打开 TIA”的 Demo | OpenProject / ImportBlocks(SCL/XML/SimaticML)/ CompileSoftware / ExportDiagnostics / SaveAndArchive,形成 “生成 → 导入 → 编译 → 反馈错误 → 再修复” 链路。核心观点:不要把导入成功等同于工程正确 | 同上 |
7.8.3 集成自检清单
| # | 检查项 | 判据 |
|---|---|---|
| 1 | 格式选择正确 | 软件数据 → Simatic ML;硬件 → AML;OPC UA → OpcUaExportProvider |
| 2 | CaxProvider 非 null | project.GetService<CaxProvider>() 判空 |
| 3 | OpcUaExportProvider 非 null | 未装 OPC UA 选项包时为 null |
| 4 | 路径是绝对目录路径 | OpcUaExportProvider.Export 明确要求 |
| 5 | PLC 不在线 | OpcUaExport 的官方 Requirements |
| 6 | 用户在 Siemens TIA Openness 组 | whoami /groups | findstr "Siemens" 有输出 |
| 7 | 工程可无错编译 | .psc 导出与 CAx 导入都要求 |
| 8 | 根对象类型统一 | 一个 Simatic ML 导入文件里不能混块与变量表 |
| 9 | ImportOptions 组合合法 | 不能 Activate + Skip;不要用 & |
| 10 | 覆盖导入的 group 一致 | 不在同一 group 会取消导入并抛异常 |
| 11 | Know-how 块不在导入目标里 | 筛选时加 && !IsKnowHowProtected |
| 12 | UDT 类型在工程库且已版本化 | 全局库不支持;未版本化抛异常 |
| 13 | 外部 HMI 变量名全工程唯一 | 否则导入被取消 |
| 14 | TransferResult.State 已检查 | 不抛异常 ≠ 导入成功 |
| 15 | 长期归档用 SIMATIC SD | 不要把 Simatic ML 当存档格式 |
来源:官方手册 6.1.1 / 6.1.3 / 6.3.2.5 / 6.4.2.29 / 6.5.14 / 6.5.15 节;github.md 第 13、14、27 条;en-blogs(GSDML/CAx 工作流、DB 编号静默忽略);cn-community(SCL 三步基线与错误码);腾讯云开发者社区(架构建议)。
本章速查
- 三种格式严格区分,一句话记住:Simatic ML = 软件数据(PLC 块/变量/工艺对象 + HMI 全类,XML);AML = 硬件 CAx 数据(CAEX V2.15,
.aml,与 EPLAN / Selection Tool 交换);OPC UA XML = PLC 数据在 OPC UA 服务器上的节点映射(OpcUaExportProvider专用)。官方原文:“The export and import file format is XML. Only CAx data require AML format.” - ⚠️ 官方警告:Simatic ML 仅对当前 Openness 版本有效(“The export format is internal and valid exclusively for the current version of TIA Portal Openness.”)。V18 及之前导入要求”Openness API 版本 ≥ SimaticML 版本”;V19 起只要在 LTS 范围内(Vxx-3 到 Vxx)即可跨版本导入。长期归档用 SIMATIC SD(YAML,V20 起,跨版本兼容,支持 S7-1200/1500 块与 LAD 表示)。
- Simatic ML 的三条不可逆约束:DB 只能导出不能导入(创建 DB 只能走外部源);Know-how 保护块不能导入(筛选时必须加
!IsKnowHowProtected);System blocks 不能导入。跨子树的被引用对象只导出”开放引用”,其属性不写出。 ExportOptions:None(只导修改过的,默认值不导)/WithDefaults(默认值也导)/WithReadOnly(只写属性也导),可用|组合。导出文件内容一律英文,但工程文本按所有语种导出。ImportOptions:None(不覆盖,同名即中断抛异常)/Override(先删后建再覆盖;不同 group 时取消导入并抛异常)/ActivateInactiveCultures/SkipInactiveCultures(互斥,同用导入失败;且对所有 Excel 导入 API 都不支持)。⚠️ 用&组合会得到ImportOptions.None。CaxProvider是硬件交换的唯一入口(project.GetService<CaxProvider>(),判空)。导出三版本:V14Export(Project, FileInfo, FileInfo)/ V17Export(ProjectBase, ...)返回bool/ V19Export(ProjectBase, FileInfo)返回TransferResult。导入冲突三策略:MoveToParkingLot(同名设备进 parkinglot)/RetainTiaDevice(保留工程设备,不导入)/OverwriteTiaDevice(覆盖)。V19 起不再生成 log 文件,改用TransferResult的State/ErrorCount/WarningCount/ 递归Messages。- CAx 的四条硬限制:不支持 HMI 设备(push button panel 与 key panel 除外);不支持 Drives(驱动)——SINAMICS 无法走此路;17 类 Scalance/DCS/IEC60870 等设备项不支持;V18 起缺
'Step7'产品许可时导入会失败(但TemplateIdentifier形式的 Library based configuration 不强制许可检查,且缺许可对 Export 无影响)。V17 起 CAx export/import 不再检查Siemens TIA Openness用户组。 - ⚠️ CAx import 会”部分成功”——不抛异常 ≠ 导入成功。必须读
TransferResult(Success/Information/Warning/Error四态)。EPLAN 侧还必须备齐部件库文件与宏文件,否则导入失败;Selection Tool 导出无法自定义文件名。 - OPC UA 三件套:
OpcUaExportProvider.Export(plc, FileInfo)(PLC 必须不在线、路径必须绝对、Find("OPCUAExportProvider")可能为 null);OpcUaSecurityPoliciesflagged enum(共 12 个策略,值 0/1/2/4/8/16/32/64/128/256/512/1024;OPC UA 服务器未激活时访问该属性抛EngineeringNotSupportedException);WebServerUserManagement/OpcUaUserManagement(Web 服务器用户Create需WebserverUserPermissions参数,OPC UA 用户不需要;两者在服务未激活时增删改抛EngineeringTargetInvocationException,但读操作仍可用)。 - CPU 侧 OPC UA 服务器默认禁用,须在 CPU 属性激活,需运行系统许可证(Small: 1511/1512/1513、ET 200SP CPU、1515SP PC;Medium: 1515/1516、1507、1516pro-2 PN;Large: 1517/1518)。不能通过 CP 或 CM 经背板总线访问,只能走 CPU 集成的 PROFINET 接口。改应用名后可能需重新生成证书。S7-1200 与 S7-1500 R/H 不支持默认 SIMATIC server interface,须用官方 Add-In 生成 modelled interface(客户端读写最多快 6 倍)。
- SiVArc(电气接线数据)在命名空间外的独立服务:
myProject?.GetService<Sivarc>(),能力覆盖 alarm/screen/textlist/tag/copy 五类规则与规则组、库主副本复制、Tag/Text 定义、UMAC 设置、SiVArc 生成。任何数据变更都校验许可,无许可抛LicenseNotFoundException。⚠️ 官方示例中TiaPortaMode/Sivarc的拼写有问题,实际为TiaPortalMode.WithUserInterface。 - ⚠️ 资料缺口:raw 资料与 V19 手册中
PRONPRO与 “PROFINET 设备 XML” 均 0 命中,本章不给该部分 API。PROFINET 组态能力实际走 5.8 节(子网 / IO 系统 / 端口互连 / PN Driver / 等时同步动态属性 / PnPn 传输区 / CP 1604-1626 传输区)。改StartAddress会隐式改变对向 I/O 类型地址、不会重新连线已分配变量、打包地址不支持——这三条是网络组态的高发事故源。 - 架构铁律:Openness 只做工程期,运行期交给 OPC UA / S7 通信。官方两条硬建议:① 不要把 Openness 塞进 Web 后端——后端只做任务编排发队列,真正调 Openness 的 Worker 跑在装了 TIA Portal 的 Windows 工程机上;② 按闭环拆能力(
OpenProject→ImportBlocks→CompileSoftware→ExportDiagnostics→SaveAndArchive),不要把导入成功等同于工程正确。 - 第三方设备的硬边界:Openness 不能从零创建第三方硬件(根因是第三方厂商未提供 Openness 兼容的
DeviceItemTypeIdGUID)。唯一可行路径是模板导出法(手工配一次 → CAx export → 参数化 → import),且 GSDML 目录安装阶段 Openness 未直接暴露(需手工把 GSDML 放到C:\ProgramData\Siemens\Automation\Simatic.StationConfiguration\GSD后重启 TIA Portal)。
第8章 SINAMICS Startdrive 传动自动化
第 8 章 SINAMICS Startdrive 传动自动化
本章导读
Startdrive 是 TIA Portal 中自动化程度最低、也最需要 Openness 的模块——传动对象的层级(CU / 电机模块 / 直线模块)、报文(telegram)、BICO 链接、电机与编码器配置、RAM→ROM 备份,每一项在界面上都是几十步手工操作。官方为此在 V19 手册中独占 5.23 节(Functions for Startdrive),包含 5.23.4 节的 32 个参考类与 5.23.5 节的 29 个代码示例。全部 API 集中在 Siemens.Engineering.MC.Drives 一个命名空间,程序集仍编在 Siemens.Engineering.dll 里(不单独引用 DLL),DFI 子能力在 .DFI,枚举在 .Enums,安全相关在 .Security。本章按官方 5.23 节结构组织:先讲命名空间与离线/在线两套对象模型的差异,再讲 TypeIdentifier 的获取与开启显示方法,然后逐一展开参数读写(含 pXXX.Bits 按名称升序这个反直觉点)、报文 telegram 全套方法、BICO、技术对象连接、电机/编码器配置,最后落到官方工具矩阵与 NuGet 扩展包清单。
本章包含三条必须标出的重要更正与澄清:
pXXX.Bits[2] == pXXX.3,不是pXXX.2——Bits是按可用 bit 值的名称升序排列的数组,不是按 bit 序号索引(官方 5.23.5.16 节有明文注释)。PossibleDriveObjectTypes是官方拼写,少一个s——Possible而非Possible,代码里必须照抄。- ⚠️
DeviceItemClassifications.HM在官方 V19 手册中的语义与社区说法不同——手册明确写HM= “header module”,且只有 SINAMICS Integrated、NX module、ADI4 三类在 Openness 对象模型中充当 header module,其他情况都是None (0)。因此用Classification == DeviceItemClassifications.HM定位 G120 主控模块的做法与官方手册不符,详见 8.4 节。
8.1 命名空间体系与程序集
8.1.1 命名空间清单(官方 5.23 节各类的 Namespace 声明)
| 命名空间 | 用途 | 包含的类(官方手册标注) |
|---|---|---|
Siemens.Engineering.MC.Drives | 主命名空间 | AddressComposition、AddressContext、AddressIoType、DriveObject、DriveObjectContainer、DriveObjectComposition、DriveParameter、DriveParameterComposition、Telegram、TelegramComposition、TelegramType、SafetyTelegram、TorqueTelegram、OnlineDriveObject、OnlineDriveObjectContainer |
Siemens.Engineering.MC.Drives.DFI | DFI(Drive Function Interface)子能力 | DriveObjectActivation、DriveObjectTypeHandler、DriveDomainFunctions、HardwareProjection、MotorConfiguration、EncoderConfiguration |
Siemens.Engineering.MC.Drives.Enums | 枚举 | DriveObjectActivationState、DriveObjectType、DriveObjectTypeComposition、ConnectOption、MotorType、EncoderType、EncoderInterface、AbsoluteIncrementalFlag、RotaryLinearFlag |
Siemens.Engineering.MC.Drives.Security | 安全(FW V6.1+) | Security(更高层的类,后台访问所有涉安全 API)、UmacConfiguration、DriveDataEncryption、UpdateCheckSums |
Siemens.Engineering.MC.Drives.SecurityObjects | — | DriveDataEncryption(在 OnlineDriveObjectContainer 的导航器中出现) |
程序集(官方原文):
Namespace: Siemens.Engineering.MC.Drives
Assembly: Siemens.Engineering.MC.Drives in Siemens.Engineering.dll
⚠️ 不单独引用 DLL——Siemens.Engineering.MC.Drives 是编在 Siemens.Engineering.dll 里的命名空间,不是独立程序集。
8.1.2 标准 using 组合(官方示例)
// 官方 5.23.5.16 节的 using
using Siemens.Engineering.MC.Drives;
// 官方 5.23.5.6 / 5.23.5.18 / 5.23.5.29 节的 using(三行组合)
using Siemens.Engineering.MC.Drives;
using Siemens.Engineering.MC.Drives.DFI;
using Siemens.Engineering.MC.Drives.Enums;
// 官方 5.23.5.19 节(下载)额外需要
using Siemens.Engineering.Download;
using Siemens.Engineering.Online;
// 官方 5.23.5.27 / 5.23.5.28 节(UMAC / DDE)额外需要
using Siemens.Engineering.MC.Drives.Security;
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.23.4 节各类的 Namespace/Assembly 声明、5.23.5 节各示例的 using 块;advanced.md(Startdrive 条目一)。
8.1.3 SINUMERIK 的命名空间陷阱(对比参考)
手册 5.21.5.2 节给出一个重要区分:SINUMERIK ONE 场景下有两个易混命名空间。
| 命名空间 | 服务 | 官方建议 |
|---|---|---|
Siemens.Engineering.MC.DriveConfiguration | DriveObjectCollection(可创建、删除、重排 drive object) | ✅ 新应用用这个 |
Siemens.Engineering.MC.Drives | DriveObjectContainer(只读访问,功能较少) | 仅为兼容性保留 |
官方原文:“For new applications, use the namespace Siemens.Engineering.MC.DriveConfiguration (and thus the DriveObjectCollection service) because the namespace Siemens.Engineering.MC.Drives (and thus the DriveObjectContainer service) is still supported for compatibility reasons, but offers less functionality.”
⚠️ 本表针对 SINUMERIK ONE。SINAMICS Startdrive 场景(5.23 节)用的是 Siemens.Engineering.MC.Drives + DriveObjectContainer,不要与 SINUMERIK 混淆。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.21.5.2 节。
8.2 离线 / 在线两套对象模型
8.2.1 类层次对照
| 维度 | 离线 | 在线 |
|---|---|---|
| 驱动对象容器 | DriveObjectContainer | OnlineDriveObjectContainer |
| 容器导航器 | DriveObjects(DriveObjectComposition) | OnlineDriveObjects(OnlineDriveObjectComposition) |
| 驱动对象 | DriveObject(public sealed class) | OnlineDriveObject(public sealed class) |
| 功能接口 | DriveFunctionInterface | OnlineDriveFunctionInterface |
| 参数集合 | driveObject.Parameters(DriveParameterComposition) | onlineDriveObject.Parameters |
| 报文集合 | driveObject.Telegrams(TelegramComposition) | 通过 DFI 间接;参数与报文的读写 API 完全相同 |
| 激活状态 | dfi.DriveObjectFunctions.DriveObjectActivation | onlinedfi.DriveObjectActivation(⚠️ 少一层 DriveObjectFunctions) |
| 驱动对象类型切换 | dfi.DriveObjectFunctions.DriveObjectTypeHandler | 不支持(DriveObjectTypeHandler 只能施加于 DriveFunctionInterface) |
| 工厂复位 / RAM→ROM | ❌ 不支持 | ✅ onlinedfi.DriveDomainFunctions.PerformFactoryReset() / PerformRAMtoROMCopyAllDriveObject() |
| UMAC 激活/去激活 | ✅ driveObject.Security.UmacConfiguration.Activate() | ❌ 仅离线 |
| Drive Data Encryption (DDE) | ✅ driveObject.Security.DriveDataEncryption.Activate(pwd) | ✅ cuOnlineDriveObject.Security.DriveDataEncryption.Activate(pwd) |
| 电机/编码器配置 | ✅ G120 与 S120 均可离线配置 | ✅ G120 可在线;S120 只能离线 |
8.2.2 四个可能为 null 的服务(官方原注释)
| 服务 | 官方注释 | 返回 null 的条件 |
|---|---|---|
DriveFunctionInterface | ”dfi can be null in case of the actual driveobject does not support it.” | 该驱动对象不支持 |
OnlineDriveFunctionInterface | ”onlineDfi can be null in case of the actual onlineDriveObject does not support it or if the device is offline.” | 不支持 或设备离线 |
DriveObjectActivation | ”driveObjectActivation can be null in case of the actual driveobject does not support activation.”(在线版:“…does not support activation in online.”) | 该驱动对象不支持激活 |
DriveObjectTypeHandler | ”driveObjectTypeHandler can be null, if the actual driveObject does not support it.” | 该驱动对象不支持类型切换 |
⚠️ 四层判空是 Startdrive 编程的常态:DriveObjectContainer → DriveObject → DriveFunctionInterface → DriveObjectFunctions.XxxFunctionHandler,每一层都可能为 null。
8.2.3 官方执行驱动功能的基础写法
// 离线(官方 5.23.5.2 节原样)
using Siemens.Engineering.MC.Drives;
DriveObject driveObject = ...;
DriveFunctionInterface dfi = driveObject.GetService<DriveFunctionInterface>();
// dfi can be null in case of the actual driveobject does not support it.
// 在线(官方 5.23.5.2 节原样)
using Siemens.Engineering.MC.Drives;
OnlineDriveObject onlineDriveObject = ...;
OnlineDriveFunctionInterface onlineDfi =
onlineDriveObject.GetService<OnlineDriveFunctionInterface>();
// onlineDfi can be null in case of the actual onlineDriveObject
// does not support it or if the device is offline.
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.23.4.8 / 5.23.4.9 / 5.23.4.10 / 5.23.4.12 / 5.23.4.18 / 5.23.4.19 节、5.23.5.2 节。
8.3 TypeIdentifier:驱动组件的唯一编号
8.3.1 官方定义
手册 5.23.3 节原文:
“Each version of any Startdrive component comprises a unique number, which is called the TypeIdentifier. In the Openness program code, you can use these TypeIdentifiers in order to uniquely identify and name a component.”
⚠️ 关键点:TypeIdentifier 是**“每个版本”的唯一编号——同一订货号的不同固件版本对应不同的 TypeIdentifier**。
8.3.2 ⚠️ 开启 TypeIdentifier 显示的完整步骤(官方三步,缺一不可)
Startdrive 中默认是隐藏的,必须手动开启:
| 步骤 | 操作 |
|---|---|
| 1 | 在 Startdrive 工程视图中选菜单 Options > Settings,打开 “Settings” 配置区 |
| 2 | 在二级导航中选择 Hardware configuration |
| 3 | 勾选 Activate display of the Type Identifier for devices and modules |
官方原文:“In Startdrive, the display of the TypeIdentifier is optional, and deactivated as default.”
8.3.3 读取 TypeIdentifier 的两个位置
位置一:所有组件(Inspector 窗口)
- 在 Startdrive 工程的设备视图中双击所需组件 → Inspector 窗口打开,显示当前激活的组件版本
- 关联的 TypeIdentifiers 列在最右侧一栏
- 示例:
OrderNumber:6SL3131-7TE23-6Axx - 复制到 Openness 应用中
位置二:Control Unit(创建设备时)
- 在 Startdrive 工程导航中双击 “Add new device” → 同名对话框打开
- 从选择列表中选所需控制模块
- TypeIdentifier 显示在详细显示区右侧、订货号与固件版本之下
- 示例:
OrderNumber:6SL3040-1MA01-0Axx/V5.2/S120 - 复制到 Openness 应用中
8.3.4 TypeIdentifier 的两种格式(与创建方法对应)
G120(三段,末尾空串):
// 官方 5.23.5.3 节原样:创建 G120
TiaPortal portal = new TiaPortal(TiaPortalMode.WithUserInterface);
Project tiaproject = portal.Projects.Open("..."); // 工程路径
Device s120Device = tiaproject.Devices.CreateWithItem(
@"OrderNumber:6SL3246-0BA22-1FA0/4.7.6/", "Device_0", null);
格式:CreateWithItem(@"OrderNumber: mlfb / FirmwareVersion /", "NameOfTheDevice", positionNumber)
S120 / S150 / S200 / S210 / MV / G130 / G150 / G220(四段,第四段是 AdditionalTypeIdentifier):
// 官方 5.23.5.3 节原样:创建 S120
Device s120Device = tiaproject.Devices.CreateWithItem(
@"OrderNumber:6SL3040-1MA01-0Axx/V4.8/S120", "Device_0", null);
AdditionalTypeIdentifier 的全部合法取值(官方列表):
| 值 | 适用 |
|---|---|
| Empty string(空串) | G120 等 |
S120 | S120 |
S150 | S150 |
S200 | S200 |
S210 | S210 |
MV | MV |
G130 | G130 |
G150 | G150 |
G220 | G220 |
8.3.5 创建设备组件:PlugNew
// 官方 5.23.5.4 节原样:创建电机模块
DeviceItem subModul = sdrDevice.PlugNew(@"OrderNumber:6SL3xxx-xxxxx-xxxx",
"MotorModul", 65535);
⚠️ positionNumber 传 65535 表示”自动 / 追加位置”。
CU 位置的官方规律(5.23.5.9 节注释):
// 官方 5.23.5.9 节原样:编辑 DRIVE-CLiQ 连接
DeviceItem subModul = device.PlugNew(@"OrderNumber:6SL3x2x-1xxxx-xxxx", "", 65535);
DeviceItem cu = device.DeviceItems[1]; // The CU is always the 1 indexed in the DeviceItems of the Device
DeviceItem subModulDQInterface = subModul.DeviceItems[0]; // 电机模块的 DQ 接口
DeviceItem cuDQInterface = cu.DeviceItems[3]; // CU 的 DQ 接口
NetworkPort subModulDQ =
((IEngineeringServiceProvider)subModulDQInterface.DeviceItems[0]).GetService<NetworkPort>();
NetworkPort cuDQ =
((IEngineeringServiceProvider)cuDQInterface.DeviceItems[0]).GetService<NetworkPort>();
cuDQ.DisconnectFromPort(subModulDQ); // 删除自动创建的连接
cuDQ.ConnectToPort(subModulDQ); // 建立新连接
官方注释:
Device cu = device.DeviceItems[1];—— “The CU is always the 1 indexed in the DeviceItems of the Device”(CU 恒为 Device.DeviceItems[1])。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.23.3 节、5.23.5.3 节、5.23.5.4 节、5.23.5.9 节(步骤与代码均为官方原文);advanced.md(TypeIdentifier 默认隐藏与开启路径)。
8.4 服务获取条件:S120 / G120 对照表
8.4.1 官方给出的三条路径(原文照抄)
离线 DriveObject(官方 5.23.5.5 节):
using Siemens.Engineering.MC.Drives;
// G devices
Project project = portal.Projects.Open("...");
DeviceItem item = project.Devices[0].Items[0].Items[0]; // ← 三层
DriveObject driveObject = item.GetService<DriveObjectContainer>().DriveObjects[0];
// S devices
Project project = portal.Projects.Open("...");
DeviceItem item = project.Devices[0].Items[0]; // ← 两层
DriveObject driveObject = item.GetService<DriveObjectContainer>().DriveObjects[0];
在线 OnlineDriveObject(官方 5.23.5.5 节):
using Siemens.Engineering.MC.Drives;
// G devices
DeviceItem item = project.Devices[0].Items[0].Items[0]; // ← 三层
OnlineDriveObject onlineDriveObject =
item.GetService<OnlineDriveObjectContainer>().OnlineDriveObjects[0];
// S devices
DeviceItem item = project.Devices[0].Items[0]; // ← 两层
OnlineDriveObject onlineDriveObject =
item.GetService<OnlineDriveObjectContainer>().OnlineDriveObjects[0];
8.4.2 S120 / G120 服务获取条件对照表
| 维度 | SINAMICS G120 | SINAMICS S120 | SINAMICS S210 |
|---|---|---|---|
| DeviceItem 路径深度 | Devices[0].Items[0].Items[0](三层) | Devices[0].Items[0](两层) | 同 S120(两层,推荐用 CU) |
CU 在 Device.DeviceItems 中的索引 | DeviceItems[1](见 8.3.5 官方注释) | DeviceItems[1] | DeviceItems[1] |
创建 TypeIdentifier 格式 | 三段:@"OrderNumber:mlfb/FirmwareVersion/"(末尾空串) | 四段:@"OrderNumber:mlfb/FirmwareVersion/S120" | 四段,第四段 S210 |
取 OnlineDriveObject 的模块要求 | ⚠️ 必须用 CU | ✅ 可用任意模块(CU / MotorModule / LineModule),但推荐 CU——因为参数集实际由 CU 执行 | ⚠️ 必须用 CU |
p96(Application class) | ✅ 存在(Find("p96") 返回非 null;无数组参数,索引为 -1) | ❌ 不存在(Find("p96") 返回 null) | — |
| 电机类型设置方式 | ✅ HardwareProjection.SetMotorType(MotorType, driveDataSet)(仅 G120) | ❌ SetMotorType 对 S120 不可用;S120 的电机类型通过硬件目录 PlugNew() 设置 | — |
| 编码器配置 | ✅ 在线与离线均可 | ⚠️ 只能离线 | — |
| 电机配置 | ✅ 在线 + 离线 | ✅ 仅离线 | — |
工厂复位 PerformFactoryReset() | ✅(必须用 CU) | ✅(可用任意模块,推荐 CU) | ✅(必须用 CU) |
RAM→ROM PerformRAMtoROMCopyAllDriveObject() | ✅(必须用 CU;必须已在线) | ✅(推荐 CU) | ✅(必须用 CU) |
Commissioning / SetSimoGearMlfb() | ✅(G115D) | ❌ “Commissioning can be null if the actual driveobject does not support the Commissioning (e.g. SINAMICS S devices)“ | ❌ |
API 文档中的 Devices[1] 写法 | 5.23.5.15 / 5.23.5.18 / 5.23.5.29 用 m_Device.DeviceItems[1] | 同 | 同 |
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.23.5.5 节(G/S 路径深度差异)、5.23.5.15 节(
SetMotorType仅 G120;S120 用PlugNew)、5.23.5.16 节(p96索引 -1)、5.23.5.11 节(p96存在性判断)、5.23.5.18 节(G120/S210 必须用 CU;S120 任意模块但推荐 CU)、5.23.4.15 节(G120 需 Power Module 插入)、5.23.4.5 / 5.23.4.17 节;advanced.md(Startdrive 条目一、条目三)。
8.4.3 ⚠️ 更正:DeviceItemClassifications.HM 不能用来定位 G120 的 CU
这是一个必须澄清的流传错误。
官方 V19 手册 5.10 节(Classification of the device element)原文:
| 值 | 描述 |
|---|---|
DeviceItemClassifications.None (0) | No classification. |
DeviceItemClassifications.CPU (1) | The device element is a CPU. |
DeviceItemClassifications.HM (2) | The device element is a header module. |
紧接着官方给出一句决定性的限定:
“In the Openness object model, the following components act as header modules: SINAMICS Integrated / NX module / ADI4. In all other cases, the value of the classification attribute is
None(0).”
官方在 5.20 节(SINUMERIK 840D sl 章节)给出的”用 header module 属性查找设备元素”示例也是这个语境:
foreach (Device device in project.Devices)
{
foreach (DeviceItem deviceItem in device.DeviceItems)
{
if (deviceItem.Classification == DeviceItemClassifications.HM)
{
var driveObjectContainer = deviceItem.GetService<DriveObjectContainer>();
// do something
}
}
}
结论与正确做法:
| 说法 | 判定 |
|---|---|
「G120 走 DeviceItems.Single(x => x.Classification == DeviceItemClassifications.HM)(HM=主控模块)」 | ❌ 与官方手册不符。HM 的官方语义是 “header module”,且 Openness 对象模型中只有 SINAMICS Integrated、NX module、ADI4 三类充当 header module,G120 的 CU 在 Openness 中应落在 None (0) |
| 官方 G120 的定位方式 | ✅ device.DeviceItems[1](5.23.5.15 / 5.23.5.18 / 5.23.5.29 节一致使用 DeviceItems[1] 取 CU),配合 Devices[0].Items[0].Items[0] 的路径深度 |
| 官方 S120 的定位方式 | ✅ Devices[0].Items[0](两层)或按 DeviceItem.Name == "BlueAxis" 之类的设备名(官方代码片段仓库的 Telegram 示例用这种方式) |
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.10 节(Classification of the device element,官方原表与原句)、5.20 节(示例)、5.23.5.15 / 5.23.5.18 / 5.23.5.29 节(G120 用
DeviceItems[1]);github.md 第 20 条(DeviceItems.Single(x => x.Classification == DeviceItemClassifications.HM)的社区写法,已判定与官方不符)。
8.4.4 S120 多轴系统:按 p107 或 DeviceItem 名区分 DriveObject
⚠️ 资料缺口声明:raw 资料中没有官方关于”S120 多轴系统按 p107 区分 DriveObject”的明文表述。Startdrive V15.1 手册(64 页)也未收录该内容。
基于原理可谨慎给出的建议(⚠️ 需核实):
S120 的一个 CU 上可挂多个 DriveObject(多轴系统)。区分它们有两种可行思路:
| 思路 | 依据 | 状态 |
|---|---|---|
按 DriveObject 序号 | GetService<DriveObjectContainer>().DriveObjects[i] 的索引 | ✅ 官方 API 支持,但索引顺序不保证稳定,不宜作为持久标识 |
按 DeviceItem.Name | 官方代码片段仓库的 Telegram 示例用 device.DeviceItems.First(x => x.Name == "BlueAxis") 定位 | ✅ 官方片段实证;命名由工程决定 |
| 按 p107(驱动对象号) | p107 是 SINAMICS 的”驱动对象号”参数 | ⚠️ raw 资料无官方 API 层面证据。V20 官方版本演进记录中提到”读取 Drive Object 号”作为 V20 新增能力,⚠️ 具体参数与 API 名称需核实 |
来源:github.md 第 19、20 条(官方代码片段的
DeviceItems[3]+Name == "BlueAxis"定位);official.md(V20 新增能力含”读取 Drive Object 号”,无 API 细节)。
8.5 参数读写:数组索引、bit 位、BICO
8.5.1 DriveParameter 属性全表(官方 5.23.4.13 节)
| 属性 | 类型 | 说明 |
|---|---|---|
ArrayIndex | Int32 | 数组参数的索引,取值范围 0–7FFF;无数组参数的索引为 -1 |
ArrayLength | Int32 | 数组元素个数;无数组参数的索引为 0 |
Bits | DriveParameterComposition | 返回该参数的某一个 bit 对应的 DriveParameter 对象 |
EnumValueList | IDictionary<int, string> | 枚举参数的候选值列表;非枚举参数时为 null |
MaxValue | Object | 当前所选单位下的最大值 |
MinValue | Object | 当前所选单位下的最小值 |
Name | string | 参数名,如 "p108[0].2" |
Number | Int32 | 参数号,如 p108[0].2 返回 108 |
ParameterText | string | 参数短说明文本 |
Unit | string | 参数单位文本 |
Value | Object | 读 / 写参数值。写出错时抛 EngineeringTargetInvocationException |
Value 的三种形态(这是 BICO 读写的基础):
| 场景 | Value 的类型 |
|---|---|
| 普通参数 | 具体值(数值 / 字符串 / 枚举索引) |
BICO 源参数(如 r19) | 只能读 |
| BICO 信号汇(sink) | 可能是 0、1 或一个 DriveParameter 对象。返回 DriveParameter 对象表示该 BICO 信号汇已连接到另一个参数 |
| 无效连接 / 源参数 Openness 不可访问 | null |
8.5.2 ⚠️ 反直觉点:pXXX.Bits 按名称升序而非 bit 序号
官方 5.23.5.16 节的注释原文(务必逐字理解):
“Note that in general, pXXX.Bits[YYY] is not necessarily equivalent to pXXX.YYY.
pXXX.Bitsis an array of available bit values in ascending order by their names and not an array of bit values indexed by their names. For example: let the available bit values be[pXXX.0, pXXX.2, pXXX.3], thenpXXX.Bits[2] == pXXX.3, notpXXX.2”
⚠️ 注意官方这句话本身用词有矛盾(“in ascending order by their names” 但又说”not indexed by their names”)。结论是明确的:Bits 是按 bit 名称字符串升序排列的数组,索引不是 bit 号。
| 可用 bit 值 | Bits[0] | Bits[1] | Bits[2] |
|---|---|---|---|
[pXXX.0, pXXX.2, pXXX.3] | pXXX.0 | pXXX.2 | pXXX.3 ← 不是 pXXX.2 |
官方 5.23.4.13 节的另一处示例:
DriveParameter param133 = cu.Parameters.Find(133, 0);
DriveParameter param133Bit1 = param133.Bits[1];
String paramName = param133Bit1.Name;
⚠️ 官方这段示例代码没有说明 param133 有哪些可用 bit,因此无法从手册断言 param133Bit1 具体是 p133.1 还是别的——这正是上一条注释要警告的陷阱。
8.5.3 数组索引的两条规则(官方注释)
规则一:数组参数用数组索引
// 官方 5.23.5.16 节
DriveParameter r947_6 = driveObject.Parameters.Find(947, 6); // 返回 r947[6]
规则二:纯 bit 数组参数不算数组参数,索引也是 -1
“Note that - arrayless parameters (e.g. p96 on G120) are indexed by -1; parameters that consist of only a bit array do not count as array parameters, they are indexed by -1 as well and the further bit values can be accessed by the ‘Bits’ property of the given parameter (e.g. r2139 on G120)“
| 参数类型 | 数组索引 | 访问方式 |
|---|---|---|
数组参数(如 r947) | 实际索引(0、1、…) | Find(947, 6) 或 Find("r947[6]") |
无数组的参数(如 G120 的 p96) | -1 | Find(96, -1) 或 Find("p96") |
纯 bit 数组参数(如 G120 的 r2139) | -1(不算数组参数) | Find("r2139") 后用 .Bits[...] |
DriveParameterComposition.Find 两个重载(官方 5.23.4.14 节):
Find(string) // 例:Find("P108[1]");找不到返回 null
Find(UInt16, Int32) // 例:cu.Find(108, 1)、cu.Find(51, -1);找不到返回 null
⚠️ 官方 Find(UInt16, Int32) 的第一参是 UInt16,不是 Int32——参数号超 65535 时需注意。
8.5.4 完整参数读写实现(官方示例 + 防御性增强)
using System;
using System.Linq;
using Siemens.Engineering;
using Siemens.Engineering.MC.Drives;
public static class DriveParamAccess
{
/// <summary>官方 5.23.5.16 节示例:按名访问 + 读属性 + 写值。</summary>
public static void AccessByName(DriveObject driveObject)
{
DriveParameter parameter = driveObject.Parameters.Find("p5391[0]");
if (parameter != null)
{
Console.WriteLine("The Name of the parameter is : " + parameter.Name);
Console.WriteLine("The value of the parameter is : " + parameter.Value.ToString());
Console.WriteLine("The minvalue of the parameter is : " + parameter.MinValue);
Console.WriteLine("The MaxValue of the parameter is : " + parameter.MaxValue);
Console.WriteLine("The Unit of the parameter is : " + parameter.Unit);
parameter.Value = 60;
}
}
/// <summary>官方 5.23.5.16 节示例:数组参数 Find(number, arrayIndex)。</summary>
public static void AccessByNumber(DriveObject driveObject)
{
DriveParameter r947_6 = driveObject.Parameters.Find(947, 6); // 返回 r947[6]
if (r947_6 != null)
{
Console.WriteLine("The Name of the parameter is : " + r947_6.Name);
Console.WriteLine("The value of the parameter is : " + r947_6.Value.ToString());
}
}
/// <summary>⚠️ bit 访问:Bits 按名称升序,绝不等于 pXXX.YYY。</summary>
public static void AccessBits(DriveObject driveObject)
{
DriveParameter p2720 = driveObject.Parameters.Find(2720, 0);
if (p2720 == null) return;
// 官方示例:p2720.Bits[1]
DriveParameter p2720Bit1 = p2720.Bits[1];
if (p2720Bit1 != null)
{
Console.WriteLine("The name of the parameter is : " + p2720Bit1.Name);
Console.WriteLine("The value of the second bit of the parameter is : "
+ p2720Bit1.Value.ToString());
}
// ★ 防御性增强:先读 Name 确认拿到的是哪个 bit,再决定是否写值
DriveParameter bit0 = p2720.Bits[0];
Console.WriteLine($"[安全] p2720.Bits[0].Name = {bit0?.Name}");
// 只有确认 Name 与目标 bit 一致(如 "p2720.0")时才可以安全赋值
}
/// <summary>枚举参数:遍历 EnumValueList(官方 5.23.5.16 节)。</summary>
public static void AccessEnum(DriveObject driveObject)
{
DriveParameter r47 = driveObject.Parameters.Find("r47");
if (r47 == null || r47.EnumValueList == null) return;
foreach (var enumItem in r47.EnumValueList)
Console.WriteLine("Enum value: " + enumItem.Key + " = " + enumItem.Value);
}
/// <summary>按 bit 名称安全定位(规避 Bits 索引反直觉陷阱)。</summary>
public static DriveParameter FindBitByName(DriveObject driveObject,
string paramName, string bitSuffix)
{
DriveParameter p = driveObject.Parameters.Find(paramName);
if (p == null) return null;
return p.Bits.FirstOrDefault(b => b.Name.EndsWith(bitSuffix, StringComparison.Ordinal));
}
}
生产建议:由于 Bits 索引反直觉,不要硬编码 Bits[n]。正确做法是遍历 Bits 并按 Name 匹配:
// 推荐:按名称后缀定位,规避索引陷阱
DriveParameter p2819 = driveObject.Parameters.Find("p2819");
DriveParameter bitToWrite = p2819?.Bits
.FirstOrDefault(b => b.Name == "p2819.9");
if (bitToWrite != null) bitToWrite.Value = 1;
else Console.WriteLine("p2819.9 不在可用 bit 列表中");
8.5.5 在线参数访问(与离线完全相同)
官方 5.23.5.17 节原话:“The read and write access to individual online parameters from the parameter list is identical to that in the Reading and writing parameters example.”
// 官方 5.23.5.17 节原样
using Siemens.Engineering.MC.Drives;
DeviceItem item = ...; // device item of the CU (e.g. project.Devices[0].Items[0].Items[0])
OnlineDriveObject onlineDriveObject =
item.GetService<OnlineDriveObjectContainer>().OnlineDriveObjects[0];
if (onlineDriveObject != null)
{
var parameters = onlineDriveObject.Parameters;
}
⚠️ OnlineDriveObject.Parameters 的官方说明:“Returns a list with the available parameters of the online drive object. null, if the mode is Offline. In offline mode, an exception is triggered when a method is called or for a write access to a parameter.”
8.5.6 BICO 链接:读是对象,写也是对象
读(官方 5.23.5.7 节原样,注意官方示例中有一处笔误):
using Siemens.Engineering.MC.Drives;
DriveParameter bicoSink = driveObject.Parameters.Find("p681");
if (bicoSink != null)
{
if (bicoSink.Value is DriveParameter)
{
DriveParamter bicoSourceValue = bicoSink.Value as DriveParameter; // ⚠️ 官方拼写笔误
Console.WriteLine("The value of parameter " + bicoSink.Name + ": " +
bicoSource.Name + " " + bicoSource.ParameterText); // ⚠️ 变量名不一致
}
else if (bicoSink.Value == null)
{
Console.WriteLine("Value contains an invalid connection or the source " +
"parameter is not accessible via Openness");
}
else
{
Console.WriteLine("The value of parameter " + bicoSink.Name + ": " +
bicoSink.Value.ToString());
}
}
⚠️ 官方示例中 DriveParamter(缺 a)与 bicoSourceValue / bicoSource 名称不一致,是手册的排版错误。正确写法:
DriveParameter bicoSource = bicoSink.Value as DriveParameter;
Console.WriteLine($"The value of parameter {bicoSink.Name}: " +
$"{bicoSource.Name} {bicoSource.ParameterText}");
写(官方 5.23.5.7 节原样):
using Siemens.Engineering.MC.Drives;
DriveParameter bicoSource = driveObject.Parameters.Find("r19");
DriveParameter bicoSink = driveObject.Parameters.Find("p738");
if (bicoSource != null)
{
try
{
bicoSink.Value = bicoSource; // ★ 对象赋值,不是字符串
}
catch (UserException ex)
{
Console.WriteLine("Write failure :" + ex.Message);
}
}
BICO 三条核心结论:
| # | 结论 |
|---|---|
| 1 | BICO 参数的读写是对象引用赋值,不是字符串 |
| 2 | 读:bicoSink.Value is DriveParameter 判断后 as DriveParameter 取出源参数对象,再读其 Name 与 ParameterText |
| 3 | Value == null 表示”连接无效或源参数不可通过 Openness 访问”——不是”未连接” |
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.23.4.13 / 5.23.4.14 节(类定义与属性表)、5.23.5.16 节(参数读写全部示例与
Bits注释原文)、5.23.5.17 节(在线参数)、5.23.5.7 节(BICO 读/写原样代码);advanced.md(Startdrive 条目二)。
8.6 报文(Telegram)管理
8.6.1 Telegram 方法清单表
TelegramComposition(public sealed class)—— 报文集合的全部方法:
| 方法 | 参数 | 返回 | 说明 | 错误时 |
|---|---|---|---|---|
CanInsertAdditionalTelegram | (Int32, Int32) — 输入尺寸, 输出尺寸 | bool | 返回 true 表示可按给定尺寸创建扩展 | — |
InsertAdditionalTelegram | (Int32, Int32) — 输入尺寸, 输出尺寸 | bool | 创建扩展报文;成功返回 true | 抛 EngineeringTargetInvocationException |
CanInsertSupplementaryTelegram | (Int32) — 报文号 | bool | 返回 true 表示可创建该附加报文 | — |
InsertSupplementaryTelegram | (Int32) — 报文号 | bool | 创建附加报文 | 抛 EngineeringTargetInvocationException |
CanInsertSafetyTelegram | (Int32) — 报文号 | bool | 返回 true 表示可生成该安全报文 | — |
InsertSafetyTelegram | (Int32) — 报文号 | bool | 创建 Safety Integrated 报文 | 抛 EngineeringTargetInvocationException |
CanInsertTorqueTelegram | (Int32, telegramNumber) | bool | 添加已有报文号的力矩报文时检查是否可行 | — |
InsertTorqueTelegram | (Int32, telegramNumber) | bool | 添加带指定报文号的力矩报文 | 抛 EngineeringTargetInvocationException |
EraseTelegram | (TelegramType) | bool | 按已知类型删除报文。标准报文不能删除。若类型为力矩报文则删 torqueTelegram 对象;若为 Safety Integrated 报文则删 safetyTelegram 对象 | 抛 EngineeringTargetInvocationException |
Find | (TelegramType) | Telegram | 按类型(或 Safety Integrated 报文类型)找报文,找不到返回 null。力矩报文类型返回 torqueTelegram 对象(若存在);Safety Integrated 类型返回 safetyTelegram 对象(若存在) | — |
IndexOf | (TelegramType) | int | 返回该实例在集合中的索引 | — |
Contains | — | bool | 判定实例是否在集合中 | — |
GetEnumerator | — | — | 迭代报文集合 | — |
Telegram(public sealed class)—— 单个报文的方法与属性:
| 成员 | 类型 | 说明 |
|---|---|---|
TelegramNumber | Int32(r/w) | 主报文号或指定报文号。自由可配置报文编号为 999 |
Type | TelegramType(r) | 报文类型枚举 |
Addresses | AddressComposition(r) | 报文地址信息 |
PKW | Telegram(r) | PKW 通道;无 PKW 部分时为 null(如报文 353 有 PKW) |
HardwareIdentifier | IEnumerable<IBrowsable>(r) | 硬件标识符列表 |
CanChangeTelegram(Int32) | bool | 返回 true 表示可改为参数化的标准类型 |
GetSize(AddressIoType) | — | 返回报文输入或输出的尺寸 |
CanChangeSize(AddressIoType, Int32, bool) | bool | 返回 true 表示可按参数改变尺寸。标准报文只能扩大。第三参为 true 时会考虑保留原报文地址 |
ChangeSize(AddressIoType, Int32, bool) | bool | 按参数改变尺寸。第三参为 true 时保留原报文地址 |
GetOutputSize() | — | 返回设定值通道的报文尺寸(官方 5.23.5.25 节示例中直接调用) |
TelegramType 枚举(官方 5.23.4.28 节):
| 枚举值 | 描述 |
|---|---|
MainTelegram | 主报文(如报文 136) |
SupplementaryTelegram | 附加报文(如报文 701) |
AdditionalTelegram | 扩展报文(自由报文) |
⚠️ ⚠️ 力矩报文的类型差异(重要):
- Startdrive 场景(5.23 节):
Find(TelegramType.TorqueTelegram)可用,官方 5.23.4.27 / 5.23.4.29 节均列出TorqueTelegram与CanInsertTorqueTelegram/InsertTorqueTelegram - SINUMERIK 场景(5.21.5.2 节):官方明确 “The torque telegram (TorqueTelegram) is not supported, although it is available in the TIA Portal Openness API in the SINUMERIK context.”
- SINUMERIK 的
TelegramType只有三个值:MainTelegram/SupplementaryTelegram/AdditionalTelegram/SafetyTelegram(没有TorqueTelegram)
SafetyTelegram 与 TorqueTelegram 的属性(官方 5.23.4.21 / 5.23.4.29 节,两者相同):
| 属性 | 类型 | 说明 |
|---|---|---|
TelegramNumber | Int32 | 报文号。自由报文编号为 999 |
Type | TelegramType | 报文类型枚举 |
Addresses | AddressComposition | 全部地址的组合 |
PKW | Telegram | PKW 通道;无 PKW 部分时为 null |
⚠️ 官方注:写属性出错时抛 EngineeringTargetInvocationException。
⚠️ TelegramComposition 的对象失效警告(官方 5.23.4.27 节 Note):
“Note that referenced objects may become invalid through class TelegramComposition. For example, object Telegram becomes invalid after the telegram size is changed.”
这意味着:改变报文尺寸后,之前拿到的 Telegram 引用会失效,必须重新 Find!
8.6.2 安全报文(Safety Integrated)的属性表
Safety Telegram 的额外属性(官方 5.23.4.21 节前的属性表,全部通过 GetAttribute / SetAttribute 访问):
| 属性名 | 类型 | 访问 | 说明 |
|---|---|---|---|
Failsafe_FSourceAddress | UInt32 | r | PROFIsafe 源地址 |
Failsafe_FDestinationAddress | UInt32 | r/w | PROFIsafe 目标地址 |
Failsafe_FIODBNumber | UInt32 | r/w | 安全 DB 号;⚠️ 仅当 Failsafe_ManualAssignmentFIODBNumber 为 1 时可写 |
Failsafe_FIODBName | string | r | 安全 DB 名 |
Failsafe_ManualAssignmentFIODBNumber | bool | r/w | 手动指定 Failsafe_FIODBNumber 的开关 |
Failsafe_FMonitoringtime | UInt32 | r/w | 安全监控时间;⚠️ 仅当 Failsafe_ManualAssignmentFMonitoringtime 为 true 时可写 |
Failsafe_ManualAssignmentFMonitoringtime | bool | r/w | 手动指定 Failsafe_FMonitoringtime 的开关 |
官方说明:“The additional properties are accessed via
GetAttributeandSetAttribute, e.g.GetAttribute("Failsafe_FSourceAddress"). Uint32 is expected as return value.”
⚠️ 官方代码片段仓库给出的三个实操属性名(V20 片段,是上述表的具体落地示例):
safetyTelegram.SetAttribute("Failsafe_FDestinationAddress", 10);
safetyTelegram.SetAttribute("Failsafe_ManualAssignmentFMonitoringtime", true);
safetyTelegram.SetAttribute("Failsafe_FMonitoringtime", 100);
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.23.4.21 / 5.23.4.26 – 5.23.4.29 节、5.23.5.21 / 5.23.5.25 / 5.23.5.26 节;github.md 第 20 条(官方 V20 Telegram 片段的三个 SetAttribute 实操);advanced.md(Startdrive 条目三)。
8.6.3 报文操作完整实现
插入扩展 + 改变主报文尺寸(官方 5.23.5.25 节原样):
using Siemens.Engineering.MC.Drives;
TelegramComposition telegrams = drvObj.Telegrams;
Telegram telegram = telegrams.Find(TelegramType.MainTelegram);
Console.WriteLine("The Cu has the telegram: " + telegram.TelegramNumber);
Console.WriteLine("The Setpoint channel-specific size of the telegram is: "
+ telegram.GetOutputSize());
foreach (var address in telegram.Addresses)
{
if (address.IoType == AddressIoType.Output)
Console.WriteLine("The Setpoint channel-specific starting address of "
+ "the connected PLC is: " + address.StartAddress);
else if (address.IoType == AddressIoType.Input)
Console.WriteLine("The Actual value channel-specific starting address of "
+ "the connected PLC is: " + address.StartAddress);
}
// 添加一个 additional telegram
if (drvObj.Telegrams.CanInsertAdditionalTelegram(2, 4))
drvObj.Telegrams.InsertAdditionalTelegram(2, 4);
// 给主报文加 3 个字的扩展(Input 通道 = 实际值)
Telegram mainTelegram = drvObj.Telegrams.Find(TelegramType.MainTelegram); // ★ 重新 Find
Int32 newSize = mainTelegram.GetSize(AddressIoType.Input) + 3;
if (mainTelegram.CanChangeSize(AddressIoType.Input, newSize, true))
mainTelegram.ChangeSize(AddressIoType.Input, newSize, true);
⚠️ 官方示例中有一处笔误:Telegram mainTelegram == drvObj.Telegrams.Find(...) 用了 == 而非 =,且 ChangeSize(...) 后缺分号。必须改正(见上方代码)。
安全报文全流程(官方 5.23.5.21 节原样):
using Siemens.Engineering.MC.Drives;
TelegramComposition telegrams = drvObj.Telegrams;
// 添加安全报文
const int tgrmNumber = 30;
drvObj.Telegrams.InsertSafetyTelegram(tgrmNumber); // ★ 此处官方未先 Can 检查
// 找到安全报文
Telegram safetyTgrm = drvObj.Telegrams.Find(TelegramType.SafetyTelegram);
// 读写安全报文属性
uint watchDogTime = (uint)safetyTgrm.GetAttribute("Failsafe_FMonitoringtime");
safetyTgrm.SetAttribute("Failsafe_FMonitoringtime", 300);
// 改安全报文号
const int newSafetyTelegramNumber = 900;
if (safetyTgrm.CanChangeTelegram(newSafetyTelegramNumber))
safetyTgrm.TelegramNumber = newSafetyTelegramNumber;
// 删除安全报文
drvObj.Telegrams.EraseTelegram(TelegramType.SafetyTelegram);
力矩报文(官方 5.23.5.26 节原样):
using Siemens.Engineering.MC.Drives;
const int torqueTelegramNumber = 750;
TelegramComposition telegrams = drvObj.Telegrams;
// 添加新的力矩报文
if (drvObj.Telegrams.CanInsertTorqueTelegram(torqueTelegramNumber))
drvObj.Telegrams.InsertTorqueTelegram(torqueTelegramNumber);
// 找力矩报文
Telegram torqueTelegram = drvObj.Telegrams.Find(TelegramType.TorqueTelegram);
⚠️ 必须先 Can… 再 Change… —— 所有报文变更 API 都是”能力查询 + 执行”两段式。官方片段仓库的 Telegram 示例印证了这一点:
// 先问能力,再写
if (telegrams.CanInsertSafetyTelegram(30)) telegrams.InsertSafetyTelegram(30);
var safetyTelegram = telegrams.Single(x => x.Type == TelegramType.SafetyTelegram);
main.ChangeSize(AddressIoType.Input, 10, false); // 第三参 = 是否自动调整
⚠️ 官方 5.23.5.21 节的安全报文示例没有先调
CanInsertSafetyTelegram就直接InsertSafetyTelegram。工程上应补上Can检查(官方片段仓库的写法是对的)。
工程化封装(规避对象失效):
public static class TelegramManager
{
/// <summary>改变主报文尺寸后自动重新 Find,规避 TelegramComposition 的对象失效规则。</summary>
public static bool ExtendMainTelegram(DriveObject drvObj, AddressIoType ioType, int words)
{
Telegram main = drvObj.Telegrams.Find(TelegramType.MainTelegram);
if (main == null) return false;
int newSize = main.GetSize(ioType) + words;
if (!main.CanChangeSize(ioType, newSize, true)) return false;
bool ok = main.ChangeSize(ioType, newSize, true);
// ★ 关键:ChangeSize 之后原 Telegram 引用已失效,必须重新 Find
Telegram refreshed = drvObj.Telegrams.Find(TelegramType.MainTelegram);
Console.WriteLine($"扩展后主报文号 = {refreshed?.TelegramNumber}, " +
$"{ioType} 尺寸 = {refreshed?.GetSize(ioType)}");
return ok;
}
/// <summary>读取全部报文的地址信息(In/Out 双向)。</summary>
public static void DumpAddresses(DriveObject drvObj)
{
foreach (Telegram t in drvObj.Telegrams)
{
Console.WriteLine($"报文 {t.TelegramNumber} ({t.Type})");
foreach (var addr in t.Addresses)
Console.WriteLine($" IoType={addr.IoType}, Context={addr.Context}, " +
$"StartAddress={addr.StartAddress}, Length={addr.Length}");
}
}
}
8.6.4 报文地址的 AddressComposition 与两个枚举
AddressComposition(public sealed class,官方 5.23.4.1 节):
| 属性 | 类型 | 说明 |
|---|---|---|
IoType | AddressIoType | 地址类型 |
Context | AddressContext | 地址上下文 |
StartAddress | Int32 | 报文的起始地址,或指定起始地址 |
Length | Int32 | 报文长度 |
AddressIoType 枚举(5 个值):
| 枚举值 | 描述 |
|---|---|
AddressIoType.None | The IO type cannot be used |
AddressIoType.Input | Type is an input address(实际值 Actual value) |
AddressIoType.Output | Type is an output address(设定值 Setpoint) |
AddressIoType.Diagnosis | Type is a diagnosis address |
AddressIoType.Substitute | Type is a substitute address |
⚠️ 方向语义务必记牢(官方 5.23.5.25 节示例的中文注释):
Input= 实际值(Actual value) —— 从驱动到 PLCOutput= 设定值(Setpoint) —— 从 PLC 到驱动GetOutputSize()取的是设定值通道的报文长度
AddressContext 枚举(3 个值):
| 枚举值 | 描述 |
|---|---|
AddressContext.None | No context has been found for the address |
AddressContext.Device | Context is a device address |
AddressContext.Head | Context is a head address |
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.23.4.1 / 5.23.4.2 / 5.23.4.3 节、5.23.5.25 节;github.md 第 20 条。
8.7 驱动对象类型切换与激活状态
8.7.1 激活状态(离线 / 在线)
离线(官方 5.23.5.1 节原样):
using Siemens.Engineering.MC.Drives;
DriveFunctionInterface dfi = ...;
DriveObjectActivation driveObjectActivation = dfi.DriveObjectFunctions.DriveObjectActivation;
// driveObjectActivation can be null in case of the actual driveobject does not support activation.
// 改变激活状态
driveObjectActivation.ChangeActivationState(DriveObjectActivationState.Deactivate);
// 读激活状态
DriveObjectActivationState activationState = driveObjectActivation.ActivationState;
// 读 IsActive 属性
bool isActive = driveObjectActivation.IsActive;
在线(官方 5.23.5.1 节原样,注意路径少一层):
using Siemens.Engineering.MC.Drives;
OnlineDriveFunctionInterface onlinedfi = ...;
DriveObjectActivation driveObjectActivation = onlinedfi.DriveObjectActivation;
// driveObjectActivation can be null in case of the actual driveobject does not support
// activation in online.
driveObjectActivation.ChangeActivationState(DriveObjectActivationState.Deactivate);
DriveObjectActivationState activationState = driveObjectActivation.ActivationState;
bool isActive = driveObjectActivation.IsActive;
DriveObjectActivation 类定义(官方 5.23.4.9 节):
| 成员 | 说明 |
|---|---|
ChangeActivationState(DriveObjectActivationState) | 改变激活状态。操作无法完成时返回 false。三个可能值:Deactivate / Activate / DeactivateAndNotPresent |
ActivationState(属性,DriveObjectActivationState) | 返回当前状态值 |
IsActive(属性,Boolean) | 对已激活的驱动对象返回 true |
⚠️ 关键差异:离线路径是 dfi.DriveObjectFunctions.DriveObjectActivation,在线路径是 onlinedfi.DriveObjectActivation(少一层 DriveObjectFunctions)。这是官方两个示例的原文差异,容易写错。
8.7.2 驱动对象类型切换(DriveObjectTypeHandler)
官方 5.23.5.6 节原样:
using Siemens.Engineering.MC.Drives;
using Siemens.Engineering.MC.Drives.DFI;
DriveObject driveObject = ...;
DriveFunctionInterface dfi = driveObject.GetService<DriveFunctionInterface>();
DriveObjectTypeHandler driveObjectTypeHandler = dfi.DriveObjectFunctions.DriveObjectTypeHandler;
// dfi can be null in case of the actual driveobject does not support it.
// driveObjectTypeHandler can be null, if the actual driveObject does not support it.
// 取得所有可用的驱动对象类型
DriveObjectTypeComposition possibleDriveObjectTypes =
driveObjectTypeHandler.PossibleDriveObjectTypes;
// 取得当前驱动对象类型
DriveObjectType currentDriveObjectType = driveObjectTypeHandler.CurrentDriveObjectType;
// ★ 方法参数是"目标类型对象",不是索引
driveObjectTypeHandler.ChangeDriveObjectType(possibleDriveObjectTypes[0]);
⚠️ 两条必须照抄的官方细节:
PossibleDriveObjectTypes的官方拼写少一个s——正确拼写就是Possible(不是Possible)。这是官方文档中的原文拼写。ChangeDriveObjectType(possibleDriveObjectTypes[0])的参数是DriveObjectType对象,不是索引数字。 官方注释原文:“Call the ChangeDriveObjectType method with the current drive object type. The method parameter should be the target drive object type.”
官方片段仓库的索引定位写法(可作为”按名称反查索引”的范例):
var s120 = Project.Devices.First(x => x.Name == "S120Democase");
var dfi = s120.DeviceItems[3].GetService<DriveObjectContainer>()
.DriveObjects.First().GetService<DriveFunctionInterface>();
var typeHandler = dfi.DriveObjectFunctions.DriveObjectTypeHandler;
var currentType = typeHandler.CurrentDriveObjectType;
// 注意官方 API 拼写:PossibleDriveObjectTypes(少一个 s)
var types = typeHandler.PossibleDriveObjectTypes.Select(x => x.Name).ToList();
var idx = types.IndexOf(types.Single(x => x == currentType.Name));
// 去激活驱动对象
dfi.DriveObjectFunctions.DriveObjectActivation
.ChangeActivationState(DriveObjectActivationState.Deactivate);
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.23.4.9 / 5.23.4.12 节、5.23.5.1 / 5.23.5.6 节;github.md 第 19 条(官方 V20 片段);advanced.md(Startdrive 条目一、条目三)。
8.8 电机与编码器配置:HardwareProjection 与 ConfigurationEntry
8.8.1 HardwareProjection 类定义(官方 5.23.4.15 节)
命名空间(三行全在):
Namespace: Siemens.Engineering.MC.Drives
Siemens.Engineering.MC.Drives.DFI
Siemens.Engineering.MC.Drives.Enums
Assembly: Siemens.Engineering.MC.Drives in Siemens.Engineering.dll
用途:“Class HardwareProjection is responsible for commissioning the motor and the encoder. The object can be found for DriveFunctionInterface and OnlineDriveFunctionInterface.”
⚠️ 两条设备差异(官方明文):
| 差异 | 内容 |
|---|---|
| G120 vs S120 | ”For G120 drives, motors and encoders can be configured both online and offline. On the other hand, for S120 drives, configuration is only possible offline.” |
| G120 的 Power Module 前提 | ”For G120 drives, the HardwareProjection object can only be accessed if the Power Module is inserted in the drive device. Otherwise, when calling functions HardwareProjection, a null or an exception is returned.” |
| 离线/在线的入口不同 | ”For an offline configuration, use the hardware configuration of the DriveFunctionInterface. For an online configuration, use the hardware configuration of the OnlineDriveFunctionInterface.” |
方法表:
| 方法 | 参数 | 返回 | 说明 | 限制 |
|---|---|---|---|---|
SetMotorType | MotorType type, ushort driveDataSet | bool | 在 Control Unit 上设置电机类型 | ⚠️ 仅 G120 可用 |
GetCurrentMotorConfiguration | ushort motorDataSet | MotorConfiguration | 依驱动数据组号取当前配置区(S120 适用) | — |
ProjectMotorConfiguration | MotorConfiguration motConfig, ushort driveDataSet | bool | 依驱动数据组号配置电机 | — |
SetEncoder | EncoderType type, EncoderInterface interfaceType, AbsoluteIncrementalFlag absIncFlag, RotaryLinearFlag rotLinFlag, ushort encoderNumber | bool | 在 Control Unit 上设置编码器 | ⚠️ 仅 G120 可用 |
GetCurrentEncoderConfiguration | ushort encoderNumber | EncoderConfiguration | 依编码器数据组号取当前配置区 | — |
ProjectEncoderConfiguration | EncoderConfiguration encConfig, ushort encoderNumber | bool | 依编码器数据组号配置编码器 | — |
8.8.2 设置编码器类型(离线,官方 5.23.5.11 节原样)
using Siemens.Engineering.MC.Drives
using Siemens.Engineering.MC.Drives.DFI
using Siemens.Engineering.MC.Drives.Enums;
DeviceItem cuDeviceItem = m_Device.DeviceItems[1];
DriveObject cuDriveObject = cuDeviceItem.GetService<DriveObjectContainer>().DriveObjects[0];
DriveFunctionInterface cuDriveFunctionInterface =
cuDriveObject.GetService<DriveFunctionInterface>();
HardwareProjection hardwareProjection = cuDriveFunctionInterface.HardwareProjection;
// ★ 关键前置:要把 p96(Application class)设为 0([0] Expert)才能设置 EncoderType
DriveParameter p96 = cuDriveObject.Parameters.Find("p96");
if (p96 != null)
{
p96.Value = 0;
}
// 设置驱动上的编码器 1
// 编码器需要设置几个枚举来定义类型:EncoderInterface、EncoderType、
// AbsoluteIncrementalFlag、RotaryLinearFlag
// 注意:给定的枚举组合可能无效,此时必须有反馈
hardwareProjection.SetEncoder(
EncoderInterface.Terminal,
EncoderType.HTLTTL,
AbsoluteIncrementalFlag.Incremental,
RotaryLinearFlag.Rotary,
1);
// 一个电机可设 2 个编码器:encoderNumber == 1 与 encoderNumber == 2
⚠️ 在线版本(官方 5.23.5.11 节)完全相同,只是入口改为 OnlineDriveObjectContainer + OnlineDriveFunctionInterface:
DeviceItem cuDeviceItem = m_Device.DeviceItems[1];
OnlineDriveObject cuOnlineDriveObject =
cuDeviceItem.GetService<OnlineDriveObjectContainer>().OnlineDriveObjects[0];
OnlineDriveFunctionInterface cuDriveFunctionInterface =
cuOnlineDriveObject.GetService<OnlineDriveFunctionInterface>();
HardwareProjection hardwareProjection = cuDriveFunctionInterface.HardwareProjection;
DriveParameter p96 = cuOnlineDriveObject.Parameters.Find("p96");
if (p96 != null) { p96.Value = 0; }
hardwareProjection.SetEncoder(
EncoderInterface.Terminal, EncoderType.HTLTTL,
AbsoluteIncrementalFlag.Incremental, RotaryLinearFlag.Rotary, 1);
8.8.3 MotorConfiguration(官方 5.23.4.17 节)
西门子电机 vs 非西门子电机的两套用法:
| 电机类型 | 做法 |
|---|---|
| 西门子电机 | 必须先调 SetMotortype(MotorConfiguration.SetMotorType)设置电机类型,该方法包含 MotorType 枚举与一个表示 DriveDatasetNumber 的数字。⚠️ SetMotortype 对 S120 不可用——S120 的电机类型用硬件目录的 PlugNew() 方法设置 |
| 非西门子电机 | MotorConfiguration 用于保存非西门子电机的数据,含 2 个 ConfigurationEntryComposition,调试时必须填充。RequiredConfigurationEntries 必须填充;OptionalConfigurationEntries 不必填充 |
MotorType 枚举全表(官方给出的值):
| 枚举名 | 值 | 描述 |
|---|---|---|
NoMotor | 0 | No motor |
InductionMotor | 1 | Induction motor |
SynchronousMotor | 2 | Synchronous motor |
NoCodeNumber1LE1InductionMotor | 10 | 1LE1 induction motor (not a code number) |
NoCodeNumber1LG6InductionMotor | 13 | 1LG6 induction motor (not a code number) |
NoCodeNumber1xx1SIMOTICSFDInductionMotor | 14 | 1xx1 SIMOTICS FD induction motor (not a code number) |
NoCodeNumber1LA7InductionMotorNoCodeNumber | 17 | 1LA7 induction motor (not a code number) |
MotorSeriesNumber1LA81PQ8StandardInduction | 18 | 1LA8 / 1PQ8 standard induction motor series |
NoCodeNumber1LA9InductionMotor | 19 | 1LA9 induction motor (not a code number) |
MotorConfiguration 属性:RequiredConfigurationEntries(ConfigurationEntryComposition)、OptionalConfigurationEntries(ConfigurationEntryComposition)、Parent(IEngineeringObject,仅 S120 驱动)。
8.8.4 EncoderConfiguration(官方 5.23.4.16 节)—— 第三方编码器
用途:“Class EncoderConfiguration saves data of non-Siemens encoders.”
两条填充要求(官方原话):
- ⚠️ “The user must populate the
ConfigurationEntryCompositionobject.” - ⚠️ “Object
RequiredConfigurationEntriesmust also be populated.”
属性表:
| 属性 | 类型 | 访问 | 说明 |
|---|---|---|---|
RequiredConfigurationEntries | ConfigurationEntryComposition | ReadOnly | EncoderConfiguration 的可访问配置输入 |
RequiredConfigurationEntries | IEnumerable<IBrowsable> | Writeable | 返回所有可用的编码器参数,但排除 p404.0 和/或 p404.1 |
EncoderTypes | IEnumerable<IBrowsable> | ReadOnly | 依编码器返回参数 p404.0 和/或 p404.1 |
Parent | IEngineeringObject | ReadOnly | 父对象 |
SetEncoderType 两个重载:
| 重载 | 说明 | 限制 |
|---|---|---|
SetEncoderType(RotaryLinearFlag) | 定义非 DRIVE-CLiQ 编码器类型为旋转或直线 | — |
SetEncoderType(RotaryLinearFlag, AbsoluteIncrementalFlag) | 定义 DRIVE-CLiQ 编码器为旋转/直线 + 绝对/增量 | ⚠️ 不适用于非 DRIVE-CLiQ 编码器。⚠️ G220 驱动固件 V6.2 只支持绝对或增量编码器类型,可用此 API 设置 |
8.8.5 ConfigurationEntry(官方 5.23.4.5 节)
用途:“Class ConfigurationEntry is used to save parameter data, which can be determined from the ConfigurationEntryCompositions of a motor or encoder configuration.”
属性表:
| 属性 | 类型 | 说明 |
|---|---|---|
EnumValueList | IDictionary<int, string> | 枚举参数的候选值列表 |
MaxValue | object | ConfigurationEntry 的最大值 |
MinValue | object | ConfigurationEntry 的最小值 |
Name | string | ConfigurationEntry 的名称 |
Number | int | ConfigurationEntry 名称的数值表示 |
Description | string | ConfigurationEntry 的描述 |
Parent | IEngineeringObject | 对象模型父元素 |
Unit | string | ConfigurationEntry 的单位 |
Value | object | ConfigurationEntry 的值 |
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.23.4.5 / 5.23.4.15 / 5.23.4.16 / 5.23.4.17 节、5.23.5.11 节;official.md(V15.1 起支持 G120 电机与编码器数据录入)。
8.9 技术对象连接:与报文对象直连
8.9.1 官方 Connect 方法族(手册 5.11.4.18 节 AxisEncoderHardwareConnectionInterface)
AxisEncoderHardwareConnectionInterface 提供一组 Connect 重载,按 Startdrive 配置的驱动必须用 MC.Drives.Telegram 重载:
| 重载 | 适用 | 官方说明 |
|---|---|---|
void Connect(HW.DeviceItem moduleInOut) | GSD 文件配置的驱动 | 连到同一模块的输入与输出地址 |
void Connect(HW.DeviceItem moduleIn, HW.DeviceItem moduleOut) | GSD 文件配置的驱动 | 连到不同模块的输入与输出地址 |
void Connect(HW.DeviceItem moduleIn, HW.DeviceItem moduleOut, ConnectOption connectOption) | 同上 + 附加选项 | — |
void Connect(HW.Channel channel) | 技术模块 | 连到一个通道 |
void Connect(MC.Drives.Telegram telegram) | ✅ Startdrive V19 起配置的驱动 | 连到 Startdrive 驱动对象提供的报文 |
void Connect(MC.Drives.Telegram telegram, ConnectOption connectOption) | 同上 + 附加选项 | — |
void Connect(int addressIn, int addressOut, ConnectOption connectOption) | 任意场景 | 直接指定位地址。⚠️ 驱动用 Startdrive < V19 配置时必须用此方法。从硬件组态读报文的字节地址再用对应位地址:位地址 = 字节地址 × 8 |
void Connect(string pathToDBMember) | 通过数据块连接 | 连接的数据块必须已存在 |
void Connect(SW.Tags.PlcTag outputTag) | 模拟量设定值接口 | 可连 “analog output”、“enable output”、“ready input” 三类标签 |
void Disconnect() | — | 断开已存在的连接 |
ET200SP PTO 2 的位偏移表(手册明确给出):
| Channel | Telegram | 位偏移到 InputAddress | 位偏移到 OutputAddress |
|---|---|---|---|
| 0 | 1 | 0 | 0 |
| 0 | 81 | 32 | 32 |
| 1 | 1 | 128 | 64 |
| 1 | 81 | 160 | 96 |
判断连接状态的只读属性:
| 属性 | 类型 | 说明 |
|---|---|---|
IsConnected | bool | TRUE: 接口已连接;FALSE: 未连接 |
InputOutputModule | HW.DeviceItem | 含输入与输出地址的已连接模块 |
InputModule | HW.DeviceItem | 含输入地址的已连接模块(即使连的是同时含输入输出的模块也会被设置) |
OutputModule | HW.DeviceItem | 含输出地址的已连接模块(同上) |
InputAddress | int | 已连接对象的逻辑输入地址,如 256 |
OutputAddress | int | 已连接对象的逻辑输出地址 |
ConnectOption | ConnectOption | 连接时设置的 ConnectOption 值(Default 只允许被识别为有效连接伙伴的模块;AllowAllModules 对应 UI 上的 “Show all modules”) |
Channel | HW.Channel | 已连接通道 |
Telegram | MC.Drives.Telegram | 已连接驱动对象的报文 |
PathToDBMember | string | 已连接技术对象的数据块标签 |
OutputTag | SW.Tags.PlcTag | 已连接 PLC 标签(模拟量连接) |
SensorIndexInActorTelegram | int | 已连接的编码器接口在 actor 报文中的位置。仅对 sensor 接口相关:0 = 编码器未连接;1 = 接到 actor 报文第 1 个 sensor 接口;2 = 第 2 个。 actor 接口该值恒为 0 |
8.9.2 官方连接代码(手册 5.23.5.24 节原样)
private bool Test()
{
Device plcStation = m_Project.Devices[0];
DriveObject driveObject = m_Project.Devices.ElementAt(1).DeviceItems[2]
.GetService<DriveObjectContainer>().DriveObjects[0];
Telegram telegram = driveObject.Telegrams[0];
DeviceItem plcDevice = plcStation.DeviceItems[1];
SoftwareContainer softwareContainer = plcDevice.GetService<SoftwareContainer>();
Software software = softwareContainer.Software;
TechnologicalInstanceDB techObj0 =
((Siemens.Engineering.SW.PlcSoftware)software)
.TechnologicalObjectGroup.TechnologicalObjects[0];
AxisHardwareConnectionProvider axisHardwareConnectionProvider0 =
techObj0.GetService<AxisHardwareConnectionProvider>();
AxisEncoderHardwareConnectionInterface axisEncoderHardwareConnectionInterfaces0 =
axisHardwareConnectionProvider0.ActorInterface;
try
{
axisEncoderHardwareConnectionInterfaces0.Connect(telegram); // Connect API
}
catch (Exception e) { }
}
带 ConnectOption 的版本(官方原样):
axisEncoderHardwareConnectionInterfaces0.Connect(telegram,
Siemens.Engineering.MC.Drives.Enums.ConnectOption.Default); // Default = 0, AllowAllModules = 1
// 或者用数字 0 也可以
axisEncoderHardwareConnectionInterfaces0.Connect(telegram, 0);
ConnectOption 取值(官方 5.11.4.18 节):
| 值 | 语义 |
|---|---|
Default(0) | 只有被识别为有效连接伙伴的模块才能被选中 |
AllowAllModules(1) | 对应 UI 上的 “Show all modules”(显示全部模块) |
8.9.3 ⚠️ 关于 ConnectToTechnologyObject / ConnectActor / ConnectSensor / ConnectTorque
这四个方法名出现在 raw 资料中的 NuGet 扩展包清单(github.md 第 24 条):
// TO 连接(来自 Siemens.Collaboration.Net.TiaPortal.Openness.Startdrive.Extensions 包)
driveObject.ConnectToTechnologyObject(redAxis);
redAxis.ConnectActor(telegram);
redAxis.ConnectSensor(...);
redAxis.ConnectTorque(...);
⚠️ 它们在官方 V19 手册中检索为 0 命中(ConnectToTechnologyObject 0 次、ConnectActor 0 次、ConnectSensor 0 次;ConnectTorque 仅 1 次且非方法定义)。
判定:这四个是官方 NuGet 扩展包提供的扩展方法,不是 Siemens.Engineering.MC.Drives 命名空间的原生 API。使用前须确认已引用 Siemens.Collaboration.Net.TiaPortal.Openness.Startdrive.Extensions 且版本大版本与 TIA Portal 对齐。原生 API 的等价路径是 8.9.2 节的 AxisEncoderHardwareConnectionInterface.Connect(Telegram)。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.11.4.18 节(
Connect方法族与连接属性表)、5.23.5.24 节(连接代码);github.md 第 24 条(NuGet 扩展包清单)。
8.10 Startdrive 传动下载与工厂复位
8.10.1 三种配置类型(官方 5.23.5.8 节)
Startdrive 下载启动后需在 PreDownload delegate 中按类型分派处理三种配置对象:
| 配置类型 | 基类 | 处理方式 |
|---|---|---|
StartDriveDownloadCheckConfiguration | 派生自 DownloadCheckConfiguration(属性 Checked) | 置 Checked = true |
DownloadPasswordConfiguration | DownloadConfiguration | 用 SecureString 调 SetPassword |
DownloadSelectionConfiguration | DownloadConfiguration | 设 SelectedIndex = 0 |
StartDriveDownloadCheckConfiguration 类定义(官方 5.23.4.20 节):“Class StartDriveDownloadCheckConfiguration is derived from the class DownloadCheckConfiguration and has the same properties. The class provides the configuration settings via checkboxes from the user.” 唯一属性:Checked(bool)。
8.10.2 官方 PreDownload delegate(5.23.5.8 节原样)
using Siemens.Engineering.Download;
using Siemens.Engineering.Online;
static void PreDownload(DownloadConfiguration configuration)
{
Console.WriteLine(configuration.Message);
StartDriveDownloadCheckConfiguration sdcc = configuration as StartDriveDownloadCheckConfiguration;
if (sdcc != null)
{
sdcc.Checked = true;
return; // ★ as 分派 + 早返回
}
DownloadPasswordConfiguration downloadPasswordConfiguration =
configuration as DownloadPasswordConfiguration;
if (downloadPasswordConfiguration != null)
{
SecureString s = new SecureString();
string passwordText = "password";
foreach (var str in passwordText) { s.AppendChar(str); }
downloadPasswordConfiguration.SetPassword(s);
return;
}
DownloadSelectionConfiguration downloadSelectionConfiguration =
configuration as DownloadSelectionConfiguration;
if (downloadSelectionConfiguration != null)
{
downloadSelectionConfiguration.SelectedIndex = 0;
return;
}
}
⚠️ 密码硬编码在示例中是演示,官方另有专门章节警告(见 8.10.5)。
8.10.3 官方下载实现(5.23.5.8 节原样)
using Siemens.Engineering.Download;
using Siemens.Engineering.Online;
try
{
DeviceItem item = ...; // CU 的 device item(如 project.Devices[0].Items[0].Items[0])
DownloadProvider downloadProvider = item.GetService<DownloadProvider>();
DownloadConfigurationDelegate pre = PreDownload;
DownloadConfigurationDelegate post = PostDownload;
ConnectionConfiguration connConfiguration = downloadProvider.Configuration;
ConfigurationMode configurationMode = connConfiguration.Modes.Find("PN/IE");
ConfigurationPcInterface pcInterface = configurationMode.PcInterfaces[0];
ConfigurationSubnet subnet = pcInterface.Subnets.Find(/* subnet name */);
IConfiguration configuration = subnet.Addresses.Find(/* IP address of the device */);
downloadProvider.Download(configuration, pre, post, DownloadOptions.Software);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
// 下载前配置处理
static void PreDownload(DownloadConfiguration configuration)
{
Console.WriteLine(configuration.Message);
StartDriveDownloadCheckConfiguration dcc = configuration as StartDriveDownloadCheckConfiguration;
if (dcc != null) { dcc.Checked = true; }
}
// 下载后配置处理
static void PostDownload(DownloadConfiguration configuration)
{
Console.WriteLine(configuration.Message);
}
⚠️ 注意 Startdrive 下载的配置构造链与普通 PLC 不同:
| 场景 | 配置构造链 |
|---|---|
| 普通 PLC(第 6 章) | Modes.Find("PN/IE") → PcInterfaces.Find(name, 1) → TargetInterfaces[0] |
| Startdrive 驱动(官方 5.23.5.8 节) | Modes.Find("PN/IE") → PcInterfaces[0] → Subnets.Find(子网名) → Addresses.Find(IP) |
⚠️ 用 DownloadOptions.Software——Startdrive 示例只下软件,不下硬件。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.23.5.8 节(PreDownload 与下载代码均为官方原样)、5.23.4.20 节;advanced.md(Startdrive 条目四)。
8.10.4 FW V6.1+ 的附加下载前置条件(UMAC + TLS 证书)
官方明确的前置条件:
“If you add a drive with SINAMICS FW V6.1 or higher to a project, then the security settings for this drive are in the factory setting. It is only possible to download from a drive such as this if UMAC is activated for the project.”
① 配置 UMAC(官方原样):
// 配置 UMAC
UmacConfigurator UmacConfiguratorService = project.GetService<UmacConfigurator>();
var projectUsers = UmacConfiguratorService.ProjectUsers;
// 创建工程用户
ProjectUser projectUser = projectUsers.Create("AdminUser", "AdminUser123#".ToSecureString());
// 添加系统角色
projectUser.Roles.Add(systemRole);
② 设置有效 TLS 证书(官方原样):
private static OnlineConfigurationDelegate m_OnlineConfigurationDelegate = null;
private bool TryDownloadToDeviceTest()
{
String newAddress = ...; // network ip
try
{
Device device = ...; // device
DeviceItem item = device.Items[0].Items[0];
DownloadProvider downloadProvider = item.GetService<DownloadProvider>();
ConnectionConfiguration conf = downloadProvider.Configuration;
}
return true;
}
// 激活 TLS 事件通知
if (m_OnlineConfigurationDelegate == null)
{
m_OnlineConfigurationDelegate = OnlineCallBackMethod;
conf.OnlineLegitimation += m_OnlineConfigurationDelegate; // ★ 必须订阅
}
private static void OnlineCallBackMethod(OnlineConfiguration onlineConfiguration)
{
TlsVerificationConfiguration verificationConfiguration =
onlineConfiguration as TlsVerificationConfiguration;
if (verificationConfiguration != null)
verificationConfiguration.CurrentSelection =
TlsVerificationConfigurationSelection.Trusted;
}
⚠️ 漏掉 conf.OnlineLegitimation += ... 订阅是 FW V6.1+ 下载失败的常见原因。
8.10.5 密码与 SecureString(官方 5.23.2.1 / 5.23.5.19 节)
三条官方安全规则:
| # | 规则 |
|---|---|
| 1 | 只有在”把 Openness 函数下载到 TIA Portal”这个方向才能用 SecureString 密码,反方向不行 |
| 2 | ⚠️ 绝不要从 String 构造 SecureString——敏感数据已经因不可变 String 类而承受内存持久化后果。最佳做法是从逐字符的非托管源构造,如 Console.ReadKey |
| 3 | API 用户负责代码中的密码处理安全措施 |
官方交互式读取密码的完整示例(5.23.5.19 节原样):
public void SetPassword(DownloadPasswordConfiguration downloadPasswordConfiguration)
{
if (downloadPasswordConfiguration != null)
{
SecureString s = GetSecureString();
downloadPasswordConfiguration.SetPassword(s);
return;
}
}
private SecureString GetSecureString()
{
SecureString securePwd = new SecureString();
ConsoleKeyInfo key;
Console.Write("Enter password: ");
do
{
key = Console.ReadKey(true);
// 忽略超范围的按键
if (((int)key.Key) >= 65 && ((int)key.Key <= 90))
{
securePwd.AppendChar(key.KeyChar);
Console.Write("*");
}
// 按 Enter 退出
} while (key.Key != ConsoleKey.Enter);
return securePwd;
}
⚠️ CI 场景必须改造:官方这个交互式示例在无人值守流水线上不可用。正确做法是从密钥库 / 环境变量 / 受 ACL 保护的文件读取,并逐字符 AppendChar 进 SecureString(不要先拼成 string 再转换)。
8.10.6 RAM → ROM 备份与工厂复位(官方 5.23.5.18 / 5.23.5.29 节)
RAM → ROM(参数化持久化):
using Siemens.Engineering.MC.Drives;
using Siemens.Engineering.MC.Drives.DFI;
using Siemens.Engineering.MC.Drives.Enums;
// G120 情形
DeviceItem cuDeviceItem = m_Device.DeviceItems[1];
OnlineDriveObject cuDriveObject =
cuDeviceItem.GetService<OnlineDriveObjectContainer>().OnlineDriveObjects[0];
// S120 与 S210 情形:一般应取 CU 的 drive object
DeviceItem cuDeviceItem = m_Device.DeviceItems[0];
OnlineDriveObject cuDriveObject =
cuDeviceItem.GetService<OnlineDriveObjectContainer>().OnlineDriveObjects[0];
// 必须用 OnlineDriveFunctionInterface
OnlineDriveFunctionInterface cuDriveFunctionInterface =
cuDriveObject.GetService<OnlineDriveFunctionInterface>();
DriveDomainFunctions driveDomainFunctions = cuDriveFunctionInterface.DriveDomainFunctions;
// 对所有设备执行 RAM → ROM 复制
bool result = DriveDomainFunctions.PerformRAMtoROMCopyAllDriveObject();
⚠️ 三条官方警告:
- ⚠️ CU 必须在 online 状态才能调用此函数,否则程序会抛异常
- ⚠️ 此调用可能需要几秒才完成
- ⚠️
onlineDfi可能为 null(不支持或设备离线);DriveDomainFunctions也可能为 null
DriveDomainFunctions 类定义(官方 5.23.4.7 节):
| 成员 | 说明 |
|---|---|
PerformFactoryReset | 恢复出厂设置 |
PerformRAMtoROMCopyAllDriveObject | 把所有驱动对象的数据从 RAM 写入存储卡/硬盘 |
Parent(IEngineeringObject,只读) | 父对象 |
⚠️ G120 专属限制:“For G120 drives, the DriveDomainFunctions object can only be accessed if the Power Module is connected to the device. Otherwise, null or an exception is returned.”
⚠️ 与 G120 专属的 HardwareProjection 限制不同:DriveDomainFunctions 要求 Power Module 已连接;HardwareProjection 要求 Power Module 已插入(inserted)。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.23.4.7 节、5.23.5.18 节、5.23.5.29 节、5.23.2.1 节、5.23.5.19 节(安全规则与代码均为官方原文);advanced.md(Startdrive 条目四)。
8.11 官方工具矩阵:三个工具的用途差异
8.11.1 三个官方工具的定位对照
| 工具 | Entry ID | Application ID | 类型 | 最新版 | 核心用途 |
|---|---|---|---|---|---|
| SINAMICS Serial Drive Commissioner (SDC) | 109774753 | Siemens.Sinamics.SDC | TIA Portal Openness Application | V2.1.0.0 | 复制 / 更新 / 下装多个驱动单元 |
| SINAMICS Startdrive Edit parameters in several drives (EPISD) | 109777633 | Siemens.Sinamics.EditParametersInSeveralDrives | TIA Portal Add-In(⚠️ 不是外部应用) | V2.0.0.0 | 把一个驱动对象的既有配置复制到其他驱动对象;参数值可离线或在线写入;可离线/在线比较多个驱动的参数值;支持硬件参数(DFI) |
| SINAMICS Startdrive Parameter Comparer | 109814528 | Siemens.Sinamics.DriveParameterComparer | TIA Portal Openness Application | V21.0.2601.332 | 比较两台 SINAMICS 驱动的参数表 |
| TIA Portal Mass Operation Tool (MOT) | 109986645 | Siemens.TiaPortal.MassOperationTool | TIA Portal Openness Application | V2.0.3.0 | 批量复制驱动与技术对象、连接驱动与 TO、批量下装、批量设参 |
| TIA Portal Drive Technology Commissioner (DTCom) | 109779879 | Siemens.DTCom | TIA Portal Openness Application | V19.1.0.0 | 修改技术对象与驱动的参数和/或标签 |
8.11.2 三者的关键差异(选型依据)
| 维度 | SDC | EPISD | Parameter Comparer |
|---|---|---|---|
| 形态 | 外部 Openness 应用 | TIA 内 Add-In | 外部 Openness 应用 |
| 方向 | 主驱动 / 模板 → 多个副本(创建、更新、下装) | 一个驱动 → 其他驱动(复制配置、设参数、比较参数) | 两个驱动之间(只比较,不修改) |
| 支持机型 | SINAMICS G 产品族 + S210 + S200(S200 自 V1.5 起) | S120、S210、G120、G110;G120X 自 TIA Portal V17 起 | 两个 SINAMICS 驱动(CSV 模式不限机型) |
| 版本要求 | SINAMICS Startdrive ≥ V21 Update 1 + TIA Portal V21 Update 2+ | 同左 | 同左 |
| 核心技术变更 | ⚠️ V2.1 移除 UI Automation,改用原生 Openness 功能——从”界面自动化”迁移到”官方 API”的标志性版本 | — | — |
| 保留的驱动专属属性 | PROFINET 设置、PROFIsafe 地址、I/O 地址、partner port interconnection(拓扑) | — | — |
| 两种技术路线 | — | — | ① CSV 比较:基于 Startdrive 导出的 CSV 参数文件,无需安装 Startdrive(适合非工程机);② S120 工程直连比较:直接连接 Startdrive 比较两个 S120 驱动,无需导出 CSV,但必须安装 Startdrive |
| 跨工程比较 | — | — | ⚠️ Cross-Project Comparison(S120 跨工程)需 V18 及以上 |
| 在线/离线 | Online(从离线工程加载全部驱动配置到硬件,随后复制安全参数并执行强制的 RAM→ROM 强制复制) | 离线与在线均可 | ⚠️ 自 V17.0.2402.296 起增加 Online/Offline 支持 |
来源:official.md(109774753 / 109777633 / 109814528 / 109986645 / 109779879 五个官方条目)。
8.11.3 EPISD 的弃用声明与 multiuser 限制(⚠️ 重要)
| 要点 | 内容 |
|---|---|
| 官方弃用声明 | EPISD “只继续接受关键修复,不再新增功能”;官方推荐新项目改用 TIA Portal Mass Operation Tool(109986645)。两者目前都仍有效,按需求选 |
| ⚠️ multiuser 限制 | 因为该 Add-In 使用 Openness,在 TIA Portal multi-user 功能下使用时存在限制,详见 FAQ Entry 109755940(“What do you need to consider when using Startdrive’s multiuser engineering option?”) |
| 典型场景量级 | 物流/汽车行业常见单 PLC 控制 100+ 驱动,需要为 10~50 个驱动反复调整同一组参数 |
8.11.4 MOT 的四条官方明示限制
| # | 限制 |
|---|---|
| 1 | 仅支持 Profinet 设备 |
| 2 | FCPU 只有在安全程序未变更时才能用 “Software (only changes)” 下装;安全程序被改过则必须经 TIA Portal 完整下装 |
| 3 | 当前不支持带密码保护的设备 |
| 4 | 当前不支持把技术对象(TO)值赋值为对象参数 |
MOT 的能力覆盖:复制驱动与技术对象、连接驱动与 TO、批量下装设备、批量设置驱动参数、批量设置技术对象参数。同时提供 GUI 与 CLI 两种操作面(CLI 支持批处理/自动化模式下无界面执行 routines,可集成到脚本流水线)。
来源:official.md(各条目);github.md(NuGet 包与 GitHub 仓库条目)。
8.11.5 官方迁移工具(Entry 109822030)
| 维度 | 内容 |
|---|---|
| Entry ID | 109822030 |
| Entry type | Application example,Entry date 07/27/2026 |
| 产品树 | Drive Technology → Selection and engineering tools → SINAMICS Startdrive commissioning tool(pnid=13438);Drive Technology → Converters → Low voltage converters → Servo converter → SINAMICS S210 servo drive system(pnid=24672) |
| 语言 | 提供德语版本;挂在总览页 109821826 的 “TIA Portal Extensions” 分组下 |
| ⚠️ 资料缺口 | 本次抓取中该页正文(Application ID / 版本 / Features 列表)在多次重试中始终只返回导航与安全提示骨架,未取得其应用元数据与下载链接——因此本条只记录确实抓到的条目元信息,不推测其功能细节。⚠️ 该工具的具体 API 与功能清单需核实 |
与之相关的 Startdrive 版本能力演进(官方 109763491 逐版列举,是判断”某个 API 从哪个版本起可用”的权威依据):
| 版本 | 新增能力 |
|---|---|
| V20 | ① Activate/Deactivate “Function in use”(V6.x 及以上驱动,如 EPOS、PID 控制器);② 导出备份文件(需 Startdrive Extended license);③ 新服务可读取访问级别 3 的全部参数而不再受白名单限制;④ 扩展参数写入白名单;⑤ 按字节读取 telegram 长度(应对字数不等长的新 Safety telegram);⑥ 读取 Drive Object 号;⑦ 处理 telegram 1100 |
| V19 | ① 第三方编码器配置(CU3x0-2、G220);② G220 主 telegram 插入/删除;③ 离线 Safety 校验和计算(V6.x 及以上驱动);④ UMAC 激活/去激活(V6.x 及以上);⑤ Drive Data Encryption 激活/去激活(V6.x 及以上) |
| V18 | DCC 图表扩展:插入图表、创建图表、编辑图表内容 |
| V17 | 支持 SINAMICS G115D;支持所有具备该功能的驱动单元的 MRP 通信属性写访问 |
| V16 | 新硬件 CU310-2 / CU adapter / DQ hub 的 Openness 支持;SIMATIC Drive Controller 上的 SINAMICS Integrated 支持;DCC 的 Openness 支持;图表的导入/优化/删除;导入 DCB Extension 库 |
| V15.1 | 按完整 MLFB 添加驱动硬件组件(全系列);全系列 PROFIsafe telegram 配置;G120 电机与编码器数据录入;S210 硬件配置与参数访问;从 EPLAN 导入 AML 或数据导入导出生成 TIA Portal 工程(G120 支持 CU+Power Module 配 EPLAN V2.8;S120/G130/G150/S150/MV 仅 CU;S210 仅变频器) |
各版本编程手册 Entry ID 映射(官方):
| Startdrive 版本 | 编程手册 Entry ID |
|---|---|
| V19 | 109826886 |
| V18 | 109815199 |
| V17 | 109798533 |
| V16 | 109773802 |
| V15.1 | 单独的 Startdrive_V15.1_Openness_EN/DE.pdf 附件(Entry 109763491) |
| 通用文档总览页 | 109792902 |
来源:official.md(109763491 逐版功能演进、109822030 元信息与缺口说明、109975682 TIA Package Manager)。
8.12 NuGet 扩展包:Siemens.Collaboration.Net.TiaPortal.Openness.Startdrive.Extensions
8.12.1 版本规则(README 原文)
“17.* = TIA Portal V17, 18.* = TIA Portal V18”
⚠️ 版本号大版本必须与 TIA Portal 版本对齐。
已发布的全部历史版本与下载量:
| 版本 | 下载量 | 版本 | 下载量 |
|---|---|---|---|
| 21.0.1765368573 | 714 | 18.0.1725480535 | 365 |
| 20.0.1744193698 | 1043 | 18.0.1685432506 | 735 |
| 19.0.1725480563 | 874 | 18.0.1685386093 | 359 |
| 17.0.1725480506 | 303 | 16.0.1725480463 | 286 |
| 17.0.1685432475 | 428 | 16.0.1685432427 | 337 |
| 16.0.1685386021 | 324 |
- 目标框架 .NET Framework 4.8(net48 兼容 net481)
- 包名”Prefix Reserved”(前缀已由微软保留),非官方无法仿冒
- 自动级联安装三个依赖,不得单独安装:
Siemens.Collaboration.Net.TiaPortal.Packages.Openness(全部 Siemens.Engineering 程序集)Siemens.Collaboration.Net.TiaPortal.Openness.ResolverSiemens.Collaboration.Net.TiaPortal.Openness.Extensions(本包依赖Extensions >= 18.0.1685432504)
8.12.2 Resolver 的用法:替代手写程序集解析器
// 运行时一次性解析全部 Siemens.Engineering 程序集
// 这是官方 NuGet 包 Siemens.Collaboration.Net.TiaPortal.Openness.Resolver 的用法,
// 也是手写注册表解析器(github.md 第 11 条)的官方替代方案
Api.Global.Openness().Initialize();
⚠️ 不做这一步的症状:V20+ 报
System.IO.FileNotFoundException: Could not load file or assembly 'Siemens.Engineering.HW.dll';V21 报FileLoadException: Unable to connect to TIA Portal. Please ensure that "Copy Local" is set to false for Siemens.Engineering assemblies in your application.
8.12.3 扩展方法清单(Startdrive 侧)
// 包安装(版本大版本 = TIA 版本)
// dotnet add package Siemens.Collaboration.Net.TiaPortal.Openness.Startdrive.Extensions --version 18.0.1685432506
Api.Global.Openness().Initialize();
IEnumerable<DriveObjectContainer> containers = tiaProject.AllDriveObjectContainers();
bool isSinamics = device.IsSinamics();
DriveObjectContainer doc = deviceItem.AsDriveObjectContainer();
driveObject.ChangeTelegramNumber(5, TelegramType.MainTelegram);
DriveParameter parameter = driveObject.Parameters.Get("p9316.1").Bit(1);
bool isBico = parameter.IsBiCo();
float referenceSpeed = parameters.P2000().Get();
parameters.G120().P3981().SetAndWaitUntilReset(1);
扩展方法与用途对照表:
| 扩展方法 | 用途 |
|---|---|
AllDriveObjectContainers() | 遍历工程内全部 DriveObjectContainer |
IsSinamics() | 判断设备是否为 SINAMICS |
AsDriveObjectContainer() | DeviceItem → DriveObjectContainer |
ChangeTelegramNumber(n, TelegramType) | 改报文号(一步完成,绕过 CanChangeTelegram + TelegramNumber 两段式) |
Parameters.Get("p9316.1") | 按完整位路径(含 bit)取参数——原生 API 做不到,须靠 Bits 遍历 + 名称匹配 |
parameter.Bit(1) | 取 bit 参数 |
IsBiCo() | 判断参数是否为 BICO 参数 |
P2000().Get() | 类型安全访问:取特定参数并直接返回其类型值 |
Parameters.G120().P3981().SetAndWaitUntilReset(1) | G120 专用访问器 + 带复位等待的写值 |
Api.Global.Openness().Initialize() | Resolver:运行时解析全部程序集 |
⚠️ 所有 DriveObjectContainer 扩展同样适用于 OnlineDriveObjectContainer(在线变体),做在线调试时无需另写一套。
⚠️ 扩展方法需 using 对应命名空间。官方片段仓库的源码里只 using Siemens.Engineering.SW.ExternalSources; 就直接调 AsPlc() / AllFBs(),说明这些扩展位于 Siemens.Engineering 根命名空间下。
8.12.4 ⚠️ V21 硬性分水岭
Siemens.Collaboration.Net.TiaPortal.Openness.Extensions(V21 最新版 21.0.1765368043)README 的兼容性警告原文摘录:
“TIA Portal Openness Extensions V21 are only compatible with TIA Portal V21 and above. TIA Portal V21 introduces breaking changes in the Openness API. Applications built against TIA Portal versions below V21 are not compatible with V21 and above.”
版本映射规则:20.* = TIA Portal V20, 21.* = TIA Portal V21。
来源:github.md 第 24 条(Startdrive.Extensions NuGet 包页,含全部版本下载量与扩展方法清单)、第 25 条(核心扩展包 V21 破坏性变更警告)、第 11 条(手写注册表解析器)。
8.13 版本与安全前置条件速查
8.13.1 官方 Openness 手册明确的 UMAC 权限要求(5.23.1 节)
“If you want to edit a project for which a project protection is activated via “Openness API”, you need to have the corresponding access rights for this purpose. Your user account must have the following function rights:
- Change project via Openness API
- Open and edit the project
- Open the project read-only
Additional function rights are required for technology objects. If you do not have any access rights, contact your administrator. For drives as of SINAMICS FW V6.1 or higher, you require the same access rights for your user account to access the drive online as in Startdrive.”
8.13.2 三大安全类的适用条件
| 类 | 命名空间 | 适用固件 | 离线 | 在线 | 方法 |
|---|---|---|---|---|---|
UmacConfiguration | .Security | SINAMICS FW V6.1+ | ✅ 仅离线 | ❌ | Activate() / Deactivate() |
DriveDataEncryption(DDE) | .Security | SINAMICS FW V6.1+ | ✅ | ✅ | Activate(SecureString) / Deactivate(SecureString) |
UpdateCheckSums | .Security | SINAMICS FW V6.1+ | ✅ 仅离线 | ❌ | UpdateCheckSums() — 重新计算并覆盖 Safety Integrated 参考参数的校验和 |
Security 类:官方注 “The Security class is a higher-level class. The class is used in the background to access all APIs that are involved in security.”
⚠️ Security 可能为 null:官方 5.23.5.27 节注释——“For G120 drives (CU250S-2_PN), as UmacConfiguration is not supported for drives with SINAMICS Firmware version older than V6.1, the value of ‘Security’ object will be null”。
UpdateCheckSums 的前置:官方 5.23.5.20 节明确 “Before accessing UpdateCheckSums, the drive object must first be determined via DriveObject”,且 “This function is only available in the offline mode”。
DriveDataEncryption 也有容器级入口:OnlineDriveObjectContainer 的导航器中直接提供 DriveDataEncryption(类型 Siemens.Engineering.MC.Drives.SecurityObjects.DriveDataEncryption)。
8.13.3 Technology Extensions(V19+ 新能力)
安装包(TechnologyExtensionInstallationProvider,从 TiaPortal 取):
// 安装 TechnologyExtensionPackage
string packageFilePath = @"Y:\\TRCDATA_V1_1_0_1.tec";
TechnologyExtensionInstallationProvider tecInstallationProvider =
TiaPortal.GetService<TechnologyExtensionInstallationProvider>();
FileInfo packageFile = new FileInfo(packageFilePath);
try
{
tecInstallationProvider.Install(packageFile); // 参数:.tec 文件的完整路径
}
catch (EngineeringException ex)
{
Console.WriteLine(ex.Message + "\n" + ex.StackTrace);
}
// 激活
TechnologyExtension tecExtension = ...;
tecExtension.Activate();
bool activated = tecExtension.IsActivated; // 注意:属性名是 IsActivated
// 去激活
TechnologyExtension tecExtension2 = ...;
tecExtension2.Deactivate();
⚠️ TechnologyExtension 的属性名是 IsActivated(布尔),而 TechnologyExtensionComposition 的属性是 Count。
⚠️ Uninstall(packageIdentifier, confirmUninstallInUse) 的两个参数:包标识符 + 是否确认卸载打开工程中正在使用的 Technology Extension;失败抛 EngineeringException。
TechnologyExtensionPackage.Identifier 的语法:<name>/<version>。
来源:Siemens 官方《TIA Portal Openness》V19 系统手册 5.23.1 节(UMAC 权限)、5.23.4.6 / 5.23.4.22 – 5.23.4.25 节(安全类与 Technology Extension 类)、5.23.4.31 / 5.23.4.32 节(UmacConfiguration / UpdateCheckSums)、5.23.5.20 / 5.23.5.23 / 5.23.5.27 / 5.23.5.28 节(代码)。
本章速查
- 命名空间与程序集:全部 API 在
Siemens.Engineering.MC.Drives;程序集编在Siemens.Engineering.dll里,不单独引用 DLL。DFI 子能力在.DFI,枚举在.Enums,安全(FW V6.1+)在.Security,安全对象在.SecurityObjects。⚠️ SINUMERIK ONE 是例外:新应用应用Siemens.Engineering.MC.DriveConfiguration(DriveObjectCollection,可创建/删除/重排 drive object),MC.Drives(DriveObjectContainer)仅为兼容性保留、功能较少——但 SINAMICS Startdrive 场景用的是MC.Drives+DriveObjectContainer,不要混淆。 - ⚠️ 四层判空是常态:
DriveObjectContainer→DriveObject→DriveFunctionInterface→DriveObjectFunctions.XxxFunctionHandler,每层都可能为 null。官方注释原文:dfi can be null in case of the actual driveobject does not support it;onlineDfi can be null ... **or if the device is offline**。 - 离线 / 在线两套对象模型:离线是
DriveObjectContainer/DriveObject/DriveFunctionInterface/dfi.DriveObjectFunctions.Xxx;在线是OnlineDriveObjectContainer/OnlineDriveObject/OnlineDriveFunctionInterface/onlinedfi.Xxx(少一层DriveObjectFunctions)。参数与报文的读写 API 完全相同(官方原话 “identical”)。OnlineDriveObject.Parameters在离线模式下返回null。 TypeIdentifier是”每个版本”的唯一编号,默认隐藏。开启三步:Options > Settings→ 二级导航Hardware configuration→ 勾选Activate display of the Type Identifier for devices and modules。读取两处:① 设备视图中双击组件 → Inspector 窗口最右栏(如OrderNumber:6SL3131-7TE23-6Axx);② 工程导航中双击 “Add new device” → 详细显示区右侧、订货号与固件版本之下(如OrderNumber:6SL3040-1MA01-0Axx/V5.2/S120)。- 创建驱动的
TypeIdentifier格式分两段:G120 三段@"OrderNumber:mlfb/FirmwareVersion/"(末尾空串);S120/S150/S200/S210/MV/G130/G150/G220 四段,第四段AdditionalTypeIdentifier取值为空串 / S120 / S150 / S200 / S210 / MV / G130 / G150 / G220。CU 恒为Device.DeviceItems[1](官方注释)。 - S120 / G120 服务获取条件对照(详见 8.4.2 表):G 设备路径三层
Devices[0].Items[0].Items[0],S 设备两层Devices[0].Items[0]。G120 与 S210 必须用 CU 取OnlineDriveObject;S120 可用任意模块(CU / MotorModule / LineModule),但推荐 CU——因为参数集实际由 CU 执行。p96(Application class)只在 G120 存在(无数组,索引 -1),必须在设编码器前把它设为 0([0] Expert)。SetMotorType与SetEncoder仅 G120 可用;S120 的电机类型只能用硬件目录PlugNew()设置,配置只能离线。G120 的HardwareProjection与DriveDomainFunctions都要求 Power Module 已插入/已连接,否则返回 null 或抛异常。 - ⚠️ 更正:
DeviceItemClassifications.HM不能用来定位 G120 的 CU。官方 V19 手册明确HM= “header module”,且只有 SINAMICS Integrated、NX module、ADI4 三类在 Openness 对象模型中充当 header module,其他情况都是None (0)。官方定位 G120 CU 的方式是Device.DeviceItems[1]。这条与社区流传的DeviceItems.Single(x => x.Classification == DeviceItemClassifications.HM)写法冲突,以后者为准会取不到对象。 - ⚠️
pXXX.Bits按 bit 名称升序而非 bit 序号索引——官方注释原文:“let the available bit values be[pXXX.0, pXXX.2, pXXX.3], thenpXXX.Bits[2] == pXXX.3, notpXXX.2”。且”纯 bit 数组参数不算数组参数,索引也是 -1”(如 G120 的r2139),需通过Bits属性访问。生产代码不要硬编码Bits[n],应遍历Bits按Name匹配。 Find两个重载:Find(string)(如Find("P108[1]"))与Find(UInt16, Int32)(如Find(108, 1)、Find(51, -1))——第一参是UInt16不是Int32。无数组参数与纯 bit 数组参数索引都是 -1。写值出错抛EngineeringTargetInvocationException。- BICO 读写都是对象引用赋值,不是字符串。读:
bicoSink.Value is DriveParameter→as DriveParameter后读Name与ParameterText;Value == null表示”连接无效或源参数不可通过 Openness 访问”,不是”未连接”。写:bicoSink.Value = bicoSource,需try/catch (UserException)。 - 报文变更全部是”能力查询 + 执行”两段式:
CanInsertSafetyTelegram→InsertSafetyTelegram、CanInsertTelegram→TelegramNumber、CanChangeSize→ChangeSize、CanInsertAdditionalTelegram→InsertAdditionalTelegram、CanInsertTorqueTelegram→InsertTorqueTelegram、CanInsertSupplementaryTelegram→InsertSupplementaryTelegram。⚠️ChangeSize之后原Telegram引用立即失效,必须重新Find(官方 Note:“object Telegram becomes invalid after the telegram size is changed”)。ChangeSize第三参为true时保留原报文地址;标准报文只能扩大;EraseTelegram不能删标准报文。 AddressIoType方向语义:Input= 实际值(Actual value,驱动→PLC);Output= 设定值(Setpoint,PLC→驱动);另有None/Diagnosis/Substitute。GetOutputSize()取设定值通道尺寸。AddressContext只有三个值:None/Device/Head。- ⚠️ 两处官方拼写必须照抄:①
PossibleDriveObjectTypes少一个s(正确的就是Possible);②ChangeDriveObjectType(possibleDriveObjectTypes[0])的参数是DriveObjectType对象,不是索引数字。DriveObjectTypeHandler只能施加于DriveFunctionInterface(在线不支持)。 - Startdrive 下载的配置构造链与普通 PLC 不同:普通 PLC 是
Modes.Find("PN/IE")→PcInterfaces.Find(name, 1)→TargetInterfaces[0];Startdrive 是Modes.Find("PN/IE")→PcInterfaces[0]→Subnets.Find(子网名)→Addresses.Find(IP),且用DownloadOptions.Software。delegate 里按三种类型分派:StartDriveDownloadCheckConfiguration(Checked = true)、DownloadPasswordConfiguration(SetPassword(SecureString))、DownloadSelectionConfiguration(SelectedIndex = 0),模式是as分派 + 早返回。 - ⚠️ FW V6.1+ 驱动的两个下载前置条件:① 工程必须已激活 UMAC(
UmacConfigurator.ProjectUsers.Create(name, secureString)+projectUser.Roles.Add(systemRole));② 必须订阅conf.OnlineLegitimation += ...并在回调里把TlsVerificationConfiguration.CurrentSelection设为Trusted——漏掉订阅是 FW V6.1+ 下载失败的常见原因。 - RAM→ROM 与工厂复位只能用
OnlineDriveFunctionInterface,且 CU 必须已在线(否则抛异常),调用可能耗时数秒。DriveDomainFunctions两个方法:PerformFactoryReset()/PerformRAMtoROMCopyAllDriveObject()。⚠️ 绝不要从String构造SecureString(敏感数据已因不可变String承受内存持久化后果),官方推荐逐字符AppendChar。 - ⚠️ 安全类有版本门槛:
UmacConfiguration/DriveDataEncryption/UpdateCheckSums均要求 SINAMICS FW V6.1+;UmacConfiguration与UpdateCheckSums仅离线可用,DriveDataEncryption离线在线都可用。固件低于 V6.1(如 G120 CU250S-2_PN)时Security对象为null。修改工程需 UMAC 权限:Change project via Openness API / Open and edit the project / Open the project read-only,技术对象另需额外权限;SINAMICS FW V6.1+ 驱动的在线访问需要与 Startdrive 中相同的权限。 - 三个官方工具的用途不可混用:SDC(109774753,V2.1.0.0)= 主驱动/模板 → 多副本的创建、更新、下装,支持 G 产品族 + S210 + S200,V2.1 已移除 UI Automation 改用原生 Openness;EPISD(109777633,V2.0.0.0,是 Add-In 不是外部应用)= 一个驱动 → 其他驱动的配置复制与参数比较,支持 S120/S210/G120/G110(G120X 自 V17),官方已声明只做关键修复不再新增功能,推荐新项目改用 MOT,且在 multi-user 下有使用限制(FAQ 109755940);Parameter Comparer(109814528,V21.0.2601.332)= 只比较不修改,两种模式(CSV 模式无需装 Startdrive / S120 直连模式必须装 Startdrive),跨工程比较需 V18+。MOT(109986645,V2.0.3.0)有四条限制:仅 Profinet 设备、FCPU 安全程序变更时必须完整下装、不支持带密码保护的设备、不支持 TO 值赋为对象参数。
- NuGet 扩展包:版本大版本必须与 TIA 对齐(
18.* = V18),目标框架 .NET Framework 4.8,必须通过Api.Global.Openness().Initialize()一次性解析程序集(不做会报FileNotFoundException/FileLoadException)。Startdrive 扩展:AllDriveObjectContainers()/IsSinamics()/AsDriveObjectContainer()/ChangeTelegramNumber(n, TelegramType)/Parameters.Get("p9316.1")/parameter.Bit(1)/IsBiCo()/P2000().Get()/Parameters.G120().P3981().SetAndWaitUntilReset(1)。所有DriveObjectContainer扩展同样适用于OnlineDriveObjectContainer。⚠️ V21 是硬性分水岭,官方原文警告跨 V21 必须重编译。 - ⚠️
ConnectToTechnologyObject/ConnectActor/ConnectSensor/ConnectTorque不是原生 API——它们来自Siemens.Collaboration.Net.TiaPortal.Openness.Startdrive.ExtensionsNuGet 包(在官方 V19 手册中检索为 0 命中)。原生 API 的等价路径是AxisEncoderHardwareConnectionInterface.Connect(Telegram)(配合AxisHardwareConnectionProvider.ActorInterface/SensorInterface[m]与ConnectOption.Default/AllowAllModules)。判断连接状态用只读属性IsConnected/InputAddress/OutputAddress/Telegram/SensorIndexInActorTelegram等。
第9章 版本控制与 CI/CD 自动化
第 9 章 版本控制与 CI/CD 自动化
本章导读
TIA Portal 的工程文件 .apXX 是复合二进制文档,不是文本格式,因此「把工程丢进 Git」这条最直觉的路线在 TIA 场景里基本走不通。本章先解释为什么难以纳入版本控制,再给出四条已被验证可行的替代路线:VCI 工作区 + Git + SIMATIC Automation Compare Tool 三层结构、TiaGitHandler 的 SimaticML 中间格式导出、SIMATIC SD(YAML)跨版本格式、以及只把 SCL/外部源纳入版本控制。随后本章把官方 CI 工具链(Continuous Integration 工具集、TIA Test Suite Advanced、JUNIT 转换器)与社区实践(Comquent 的德文 Jenkins 实战、CERN ICALEPCS 2025 论文的 GitLab CI 流水线)并列拆解,给出五阶段流水线模型与可直接改造的 Jenkinsfile / GitLab CI 片段。章节后半单独用一节讨论 MCP / AI 工具链——这是 2026 年 Openness 生态最显著的新形态,包括把 Openness 封装成 180+ MCP 工具的项目、把 TIA 预热时间从 340 秒压到 1 秒内的工程手法,以及 V18/V20/V21 多版本工具集构建策略。最后以西门子官方 Mass Operation Tool(Entry 109986645)与 Project Bases(项目基线)作为官方给出的运维侧批量能力收尾。
星标与版本数据说明:本章所有星标数、版本号、Entry ID 均来自抓取于 2026-10-04 的 raw 原始资料(GitHub REST API、NuGet 包页、SIOS 条目页),未做二次估算。抓取失败或仅有摘要的条目已在正文中逐条标注。
9.1 为什么 TIA Portal 项目难以纳入 Git
9.1.1 .apXX 是复合文件,diff 完全不可读
| 障碍 | 具体表现 | 来源类别 |
|---|---|---|
| 二进制大文件 | .apXX(如 .ap17、.ap20)是二进制归档,.zapXX(.zap21)是 ZIP 归档但内部条目仍为二进制 | GitHub / 英文博客 |
| 无全项目文本格式 | TIA Portal 没有 L5K 式的全项目文本格式。Copy Project 只是二进制复制,不是文本 | 英文博客 |
| XML 导出格式不稳定 | SimaticML 导出格式在不同 TIA Portal 版本间不同;官方手册明确声明导出格式仅对当前 Openness 版本有效,不要把 SimaticML 当长期存档格式 | 官方手册 / 中文社区 |
| diff 噪声淹没提交历史 | 直接提交 .apXX 时,任何一次编译/保存都会产生巨大二进制 diff | GitHub(TiaGitHandler 设计动机) |
| 必须关闭 TIA Portal | Openness 对 .apXX 持排他写锁;项目在 TIA 设计器中打开时脚本无法打开该项目,下一次打开会强制终止残留进程 | 英文博客 / 中文社区 |
来源:GitHub(
dotnetprojects/DotNetSiemensPLCToolBoxLibraryREADME、industrial monitor direct 多篇、PLC技术网《TIA Portal Openness 概述》)
核心判断:.apXX 提交进 Git 的价值只剩「历史留档」,diff 与 code review 能力为零。因此工程界的共识是把「可 diff 的源码」与「不可 diff 的工程文件」分离——前者进 Git,后者由构建产物或专用分支管理。
9.1.2 TIA Portal 独占锁:CI 机器上的硬约束
- Openness 对项目文件持排他写锁;
Projects.Open的前置条件是「该工程不能同时在另一个 TIA Portal 实例中打开」。 - 脚本崩溃/被 kill 后未 Dispose 会残留锁,下次打开工程时 TIA 提示强制终止进程;可用 Process Explorer 查
S7OI.exe句柄并强杀孤儿进程。 - 官方对 CI 的明确要求:TIA Portal 只支持 Windows,Openness 无 Linux 版本,CI Runner 必须跑在 Windows VM 上。
来源:官方 V21 系统手册 5.3.1、advanced.md(CI/CD 专题)、en-blogs.md(Project Bases 篇)
9.2 四条可行路线
9.2.1 路线 A:VCI 工作区 + Git + SIMATIC Automation Compare Tool(三层结构)
这是西门子官方文档中描述的版本控制机制,由 VCI-1 主题作者整理成三层:
| 层 | 组件 | 作用 | 关键限制 |
|---|---|---|---|
| 第 1 层 | VCI(Version Control Interface) | 项目树「版本控制接口」节点添加工作区(Workspace),把 PLC 对象导出为文本文件并与工作区文件建立关联 | 可导出对象仅限 OB、FB、FC、DB、PLC 数据类型、PLC 变量表;LAD/FBD 无法版本化 |
| 第 2 层 | Git | 仓库根目录即 VCI 工作区目录(git init 产生 .git) | git checkout 在 VCI 中没有对应命令且该命令会重写工作区,属危险命令,需用其他方式切分支 |
| 第 3 层 | SIMATIC Automation Compare Tool(Entry 109797235) | TIA 内置比较只能比较项目内的块,转成 .SimaticML 后 TIA 自己就不能比了,该工具专为比较 SimaticML 格式对象而设计 | 用 CFC 和 SCL/Graph 编写的程序目前不行 |
LAD/FBD 为什么要靠 Openness 转:因为 Git 不能处理图形化语言,必须靠 Openness 把块转成 *.SimaticML(西门子专有 XML)才能被 Git 使用,这个转换由 VCI 自动执行。因此 Openness 是 VCI 的前置依赖(TIA 安装时 Openness 默认安装,VCI 依赖它做格式转换)。
⚠️ 注意:「CFC 和 SCL/Graph 编写程序不能比较」这一条是真正的版本控制瓶颈,而不是 Git 本身的问题。规划技术选型时应优先确认目标工程是否含 CFC / SCL Graph。
可选插件「VCI Git Connector / VCI Add-in」可在 TIA 内直接调 Git 的 Add/Push;熟悉 Git 者不用插件也能工作。
来源:西门子中国技术支持论坛(VCI-1 主题)+ 阿里云开发者社区转载 / advanced.md 版本控制CI 专题
9.2.2 路线 B:TiaGitHandler(SimaticML 中间格式入库)
dotnetprojects/DotNetSiemensPLCToolBoxLibrary(477 stars / 210 forks / LGPL-2.1 / C# / 最后推送 2026-08-26)是抓取清单中星标最高的仓库,其 TiaGitHandler.exe 就是本主题最直接的 Git 版本控制方案。
| 能力 | 实现细节 |
|---|---|
| Git 友好导出 | 把 TIA 项目(V13~V20)导出为 Git 友好格式,关键做法是剥离导出文件中的时间戳与易变元数据,避免无意义的 diff 淹没提交历史 |
| 多版本隔离机制 | 每个 TIA 版本一个独立程序集 —— DotNetSiemensPLCToolBoxLibrary.TIAV13 到 .TIAV21(含 V14SP1/V15/V15_1/V16~V20),主工程用反射按版本动态加载对应 DLL 与 Step7ProjectVxx 类 |
| 注册表白名单机制 | 按文件扩展名 .ap13~.ap20 推断版本,写入 HKLM\SOFTWARE\Siemens\Automation\Openness\ 下的 TiaOpennessWhitelist.EnsureWhitelistEntry(version)——这解决了 Openness 的经典权限坑 |
| 报警文本转换 | .xlsx 报警文本列表(来自 plcalarmtextlistgroup)转分号分隔 CSV;以 UTF-8 BOM 写出 |
| 可配置清洗 | removeCodeFromXml(去掉编译后代码段)、resetSetpoints、空行处理三档(removeAllBlanks / removeOnlyOneBlank / removeNoBlanks) |
| 密码保护工程 | V15.1+ 支持,命令行 user / password 参数 |
| GUI 双模式 | 无参数时弹 WPF 对话框,支持 V14SP1~V20 各版本的「附加到运行实例」按钮 |
⚠️ 版本互斥坑(本主题最关键的约束之一):一个应用实例只能加载一个 TIA 版本,试图加载第二个版本会抛异常——
“You cannot open a project in V if you already have a project open in another version. You need to restart the Application!”
这意味着多版本并行处理必须拆成多个进程,而不是在一个进程里循环。
### TiaGitHandler 命令行用法
TiaGitHandler.exe <projectFile> [attach] [removeCode] [user] [password] [exportPath]
### 例:附加到已运行的 V17 实例、去除编译代码、导出到指定目录
TiaGitHandler.exe "D:\prj\My.ap17" true true
### 版本互斥(源码逻辑)
### .ap13~.ap20 → "13.0"~"20.0" → TiaOpennessWhitelist.EnsureWhitelistEntry(version)
### 反射加载 DotNetSiemensPLCToolBoxLibrary.TIAV17.dll → Step7ProjectV17
### 重复加载不同版本会抛:You cannot open a project in V if you already have
### a project open in another version. You need to restart the Application!
来源:GitHub
dotnetprojects/DotNetSiemensPLCToolBoxLibrary(2026-10-04 抓取,477★)
仓库同时提供 S7ProjectBlockComparer、TiaImporter、TiaXmlCombiner、BackupRestoreBlocks,并发布 NuGet 包 DotNetProjects.DotNetSiemensPLCToolBoxLibrary(从 Codeplex 迁到 GitHub)。
9.2.3 路线 C:SIMATIC SD(YAML)——唯一官方跨版本格式
| 维度 | SimaticML | SIMATIC SD |
|---|---|---|
| 格式 | XML(西门子专有) | YAML(文本化) |
| 起始版本 | 一直可用(V13 SP1 起) | TIA Portal V20 起 |
| 跨版本兼容 | ❌ 仅对当前 Openness 版本有效,不可作长期存档 | ✅ 自 V20 起跨 TIA Portal 版本兼容 |
| 适用定位 | 交换格式、工具链中间态 | Git 里的长期源码形态 |
已明确支持:S7-1200 / S7-1500 的块,以及 LAD 程序代码在 SIMATIC SD 中的表示。支持导入导出的元素范围在持续扩大中(官方原文 “continuously becoming available for more elements”),落地时须按目标版本核对当前支持清单。配套官方文档:《Exporting and importing blocks in SIMATIC SD format》与《Representation of LAD program code in SIMATIC SD format》。
结论:要长期可 diff + 跨 TIA 版本,就用 SIMATIC SD;要工具链中间态与全语言覆盖,就用 SimaticML。
来源:Siemens Industry Mobile Support 109792902
9.2.4 路线 D:只把 SCL / 外部源纳入版本控制
这是成本最低、收益最高的路线,也是 CERN 与大量集成商的共同选择。
- 外部源文件只接受文本格式:
.awl(STL)、.scl、.db、.udt,其他扩展名直接抛异常;源文件必须是 ASCII/UTF-8 文本。 - 关键限制:LAD / FBD 无法导出为源,必须先转 SCL。官方 WithExtensions 片段的筛选条件写死为
ProgrammingLanguage is ProgrammingLanguage.SCL or ProgrammingLanguage.STL。 - SCL 源文件头必须保留(否则导入失败):
{Attribute 'pack_mode' := '0'}、TITLE = '...'、{ S7_Optimized_Access := 'TRUE' }、VERSION : 0.1。 - 推荐规范:
.scl纯文本 UTF-8 无 BOM(⚠️ 注意与部分中文教程「UTF-8 BOM」写法冲突,见第 11 章对应条目)。
CERN UNICOS-CPC 的做法是这条路线的完整样板:核心逻辑已是文本文件,适合 Git——specification file(Excel,定义 I/O/执行器/报警/参数实例)、user sources(SCL 的 FC/FB/DB)、logic templates(Python)、base project(含硬件与网络配置的 TIA 工程)四层分离。残留局限是 monolithic base project 与 Excel 变更难追踪。
来源:advanced.md(CERN ICALEPCS 2025)、中文社区(导入 AI 写的 SCL 文件)、en-blogs.md(SCL Creating Data Blocks)
9.3 官方与社区的 CI 实践
9.3.1 官方:Continuous Integration 工具集(Entry 109801203)
西门子提供基于 TIA Portal Openness 的自动化构建与测试工具集,这是官方推荐的 CI 路线而非第三方方案。
典型五段式架构:
| 角色 | 机器 | 职责 |
|---|---|---|
| 工程桌面机 | 每用户一台 Win10 | 编辑 TIA 项目 |
| 版本控制服务器 | 跑 TIA Project Server | 广播变更并导出项目版本 |
| CI 服务器 | Linux 或 Windows | 跑 Jenkins |
| 自动化桌面机 | Windows | 跑 Jenkins agent,导出并测试项目(可横向扩展以伸缩测试环境) |
| ↺ 反馈 | — | 回到工程桌面机 |
关键组件:Project Server Listen Console 定期轮询 Project Server,发现新项目/新修订后通过 Jenkins API 在 Jenkins 中创建测试任务。
官方工具链清单:
- Test Suite Advanced(示例能力:应用测试、编程风格符合性检查)
- TestRunner
- CodeCoverage
- XML → JUNIT 格式转换器(把各种 XML 格式转成 Jenkins 使用的 JUNIT 格式)
分工:TIA Openness 在此扮演「导出与编译」的执行引擎(导入源文件、编译项目),Jenkins 负责编排。
Test Suite 的能力边界必须清楚:它不校验命名规范、与电气图的 I/O 一致性、编程风格——这些必须自建 Openness 方案。
来源:SIOS 109801203《通过 TIA Portal 进行持续集成》/ advanced.md
9.3.2 社区实战一:Comquent(德文)Jenkins 流水线
版本基线(作者标注 Stand 2026-10-03):TIA Portal V21 · PLCSim Advanced V6 · Jenkins LTS 2.504 · SIMATIC AX (Early Access);Jenkinsfile 示例用 TIA_VERSION = 'V18';通用最低要求 TIA Portal V18+、PLCSIM Advanced V5+;MCP 接入需 V20/V21,工程站要求 .NET Framework 4.8。
SPS 流水线与经典软件流水线的硬差异(这是设计 CI 前必须认清的):
| 维度 | SPS 流水线 | 经典软件 |
|---|---|---|
| Build 系统 | TIA Openness API (Headless) | Maven / Gradle / npm |
| OS | 必须 Windows | Linux 可行 |
| 测试环境 | PLCSim Advanced / SIMIT | Docker |
| Deployment | 只能在计划维护窗口 | 随时 |
| Rollback 单位 | 分钟(SPS 下载) | 秒(容器) |
六个 stage:/01 Checkout(Git + TIA Openness API,TIA 工程以目录结构或导出 XML 版本化)、/02 Build(V18+ headless 编译)、/03 Simulate(PLCSIM Advanced V5+ / SIMIT)、/04 Test(TIA Test Suite + 自定义脚本 + JUnit XML reporter)、/05 Quality Gate(自定义分析器 + IEC 62443 检查 + SonarQube,违规即中断)、/06 Deploy(Artifactory/Nexus + 部署脚本)。
// Comquent Jenkinsfile 片段(抓取自 comquent.de,2026-10-03)
stage('Build') {
steps {
powershell '''
$tiaPath = "C:\\Program Files\\Siemens\\Automation\\Portal ${env:TIA_VERSION}"
Import-Module "${tiaPath}\\PublicAPI\\Siemens.Engineering.dll"
$project = [Siemens.Engineering.TiaPortal]::Open(${env:PROJECT_PATH})
$project.CompileAll()
if ($project.CompileResult.HasErrors) { throw "Build fehlgeschlagen" }
'''
}
}
Jenkinsfile 结构约定:agent { label 'windows-tia' },options { timeout(time: 60, unit: 'MINUTES'); timestamps(); disableConcurrentBuilds() },PLCSIM_TIMEOUT = '300'。Deploy 到 Staging 用 input message: 'Deployment auf Staging-SPS?' 人工闸门 + when { branch 'release/*' }。
⚠️ 作者明确声明:
Start-PLCSimInstance、Download-ToPlcSim、Invoke-PlcTests、Test-NamingConventions、Test-SecurityCompliance、Test-UnusedVariables、Deploy-ToStagingPlc这些 PowerShell Cmdlet 是简化示意,实际需要能正确调用 TIA Openness API 并做错误处理的 wrapper 脚本。照抄会失败。⚠️ API 签名存疑,需对照官方手册核实:该片段中的
[Siemens.Engineering.TiaPortal]::Open(...)静态方法、$project.CompileAll()、$project.CompileResult.HasErrors均不符合官方手册的 API 形态(官方为new TiaPortal(TiaPortalMode.WithoutUserInterface)→Projects.Open(FileInfo)→(ICompilable)project.Compile()返回CompilerResult,判State == CompilerResultState.Successful)。此片段仅可作为「PowerShell 阶段该长什么样」的示意,不可作为 API 参考。
来源:Comquent GmbH 德文技术博客 https://comquent.de/ressourcen/blog/cicd-sps-tia-portal-jenkins
9.3.3 社区实战二:CERN ICALEPCS 2025 论文(GitLab CI)
CERN EPC 组 Converters Controls Software 段用 Siemens S7-1500 + UNICOS-CPC + WinCC OA 做高功率系统(主磁体电源、静止无功补偿器 SVC)控制,替代原外协模式。混合架构 = 原生 Siemens API(TIA Openness API)+ 标准 DevOps 工具 + 虚拟化测试环境;选 GitLab CI 因为与自建 GitLab 源码库无缝。
Runner 约束:跑在 CERN OpenStack 上的 Windows VM,预装 TIA Portal + TIA Openness,保证构建环境一致可复现,消除 “it works on my computer”。默认每次 git push 触发;MR 必须流水线通过才能 approve;也可配定时(每周回归)或手动指定应用与目标 PLC。
六个 pipeline stage:
| Stage | 做什么 | 关键设计 |
|---|---|---|
| Build | Python 脚本用 CERN 的 al-paka 包驱动 Apache Maven:① 生成 UNICOS-CPC 源文件(instances + logic)② 导入 TIA Portal 工程 ③ 编译成可部署产物 | 导入与编译两步均用 CERN 自研的 TIA Openness API wrapper;构建失败即中断并通知开发者,错误日志在 GitLab CI 可见 |
| Deploy | 部署到 Siemens S7-PLCSIM Advanced 虚拟 PLC | 脚本管理虚拟 PLC 的 reset / start / stop / upload;同一脚本也可从终端部署到实验室物理 PLC |
| Setup | 把系统带到已知初始状态 | 必须:清报警、执行器回零、设初始过程值,否则测试不可重复 |
| Tests | unit / integration / regression | 单测失败不中断,全部跑完给出完整失败报告;流水线整体只在全部通过时标成功 |
| Release(可选,CD 部分) | 存为 GitLab CI 版本化构建产物 | 转存 Octoplant 仓库,供排期人工部署 |
| Report | 各 stage 完整终端输出附到 pipeline run | 摘要化,便于快速定位失败原因 |
生产版本管控:用 AUVESY-MDT Octoplant 做变更管理与版本历史、自动连生产设备、上传运行中程序并与服务器上最后版本做详细比对、立即标记差异、对 PLC 中运行版本做定时备份——用来检测未授权/未文档化的现场改动。
日常编码边界(重要):搬到 VS Code(文本 SCL/Python + GitLab workflow),但硬件配置、在线监控、在线调试仍必须在 TIA Portal 里做。
自动化测试的边界:擅长重复、确定性、数据驱动的场景;硬件测试、探索性测试、HMI 可用性测试仍必须人工。
来源:ICALEPCS 2025 会议论文 TUBG002,JACoW proceedings,作者 CERN J. M. de Paco / W. Niinimaeki / M. Marin / O. Minaiev
9.4 流水线阶段划分(推荐模型)
综合官方与社区实践,推荐把 SPS 流水线拆成五段。「静态检查」是 Openness 生态的特色段,也是最容易被忽略的价值点。
| 阶段 | 目标 | 关键技术 | 失败处理 | 产物 |
|---|---|---|---|---|
| ① 生成(Generate) | 从文本源码生成工程 | ExternalSourceGroup.ExternalSources.CreateFromFile(name, path) → GenerateBlocksFromSource(GenerateBlockOption.KeepOnError);先 Find(name)?.Delete() 删同名源 | KeepOnError 返回含出错块的 IList<IEngineeringObjects>,逐块报告 | 工程文件 + 源文件快照 |
| ② 编译(Compile) | 取得完整编译反馈 | (ICompilable)plcSoftware.Compile() → 遍历 result.Messages 打印 Severity / ErrorCode / Path;断言 ErrorCount == 0 | CompilerResultState.Successful 判定;Comquent 采用「warning 也算失败」的严格策略 | CompilerResult 报告(转 JUNIT XML) |
| ③ 静态检查(Lint) | 规范/命名/安全/一致性校验 | 自建 Openness 校验器:IP 唯一性(NetworkInterface.Ports → IIpAddress.Value 用 Dictionary 撞键)、I/O 命名规范(正则 ^[A-Z][a-zA-Z0-9]*$ 卡 PascalCase)、未使用变量、IEC 62443 检查 | 违规即中断(/05 Quality Gate 语义) | 违规明细(JSON/XML) |
| ④ 归档(Archive) | 生成可追溯快照 | TiaGitHandler 导出(剥离时间戳)、报警文本 .xlsx → 分号分隔 CSV(UTF-8 BOM)、removeCodeFromXml / resetSetpoints / 空行三档清洗 | 导出后 Assert 文件存在且非空 | Git 可 diff 的文本树 |
| ⑤ 部署下载(Deploy / Download) | 下装到 PLCSIM 或真实 PLC | DownloadProvider.Download(config, address, pre, post, DownloadOptions.Hardware | Software);下载到 PLCSIM 用 DownloadProvider.Download(DirectoryInfo, delegate) + TargetForSoftware | pre/post delegate 必须处理所有对话框,否则中止下载;配置无效抛 EngineeringTargetInvocationException | DownloadResult + 设备在线状态 |
⚠️ 「导入成功 ≠ 工程正确」。真正有价值的是导入后自动编译、提取错误、回传修复。官方 snippets 仓库也印证这条规则:导出前先
block?.GetService<ICompilable>().Compile(),再用result?.ErrorCount == 0判断一致性。
9.4.1 可用的 GitLab CI 骨架
### 骨架:阶段划分遵循第 9.4 节模型,Windows Runner + 无头 Openness
stages: [generate, compile, lint, archive, deploy]
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_PIPELINE_SOURCE == "schedule" # 每周回归
.tia_windows_runner:
tags: [windows-tia] # 必须预装 TIA Portal + Openness 的 Windows VM
interruptible: true
parallel: 1 # 一个项目同时只允许一个 Openness 进程(排他写锁)
compile: # ② 编译:官方 API 形态
extends: .tia_windows_runner
script:
- dotnet run --project src/BuildTool -- --mode WithoutUserInterface
- powershell -File scripts/Assert-CompilerResult.ps1 # ErrorCount==0 且 WarningCount==0
artifacts:
when: always
paths: [reports/compiler-result.json]
reports: junit: reports/compiler-result.xml # 对应官方 XML→JUNIT 转换器
lint: # ③ 静态检查:自建 Openness 校验器
extends: .tia_windows_runner
script:
- dotnet run --project src/LintTool -- --rules naming,ip-unique,unused-vars
artifacts:
when: always
paths: [reports/lint-report.json]
deploy_sim: # ⑤ 部署到 S7-PLCSIM Advanced
extends: .tia_windows_runner
needs: [compile, lint]
rules:
- if: $CI_COMMIT_BRANCH == "main"
script:
- dotnet run --project src/DeployTool -- --target plcsim --plcsim-timeout 300
9.4.2 无头运行(WithoutUserInterface)在 CI 中的用法
| 场景 | 推荐模式 | 理由 |
|---|---|---|
| CI 流水线全部阶段 | TiaPortalMode.WithoutUserInterface | TIA Portal 弹出模态对话框(保存、兼容性、库冲突)时 Openness 调用会无限挂起——这是多来源一致确认的卡死坑 |
| 本地调试 | TiaPortalMode.WithUserInterface | 能看到界面、能在 Confirmation 事件里点确认 |
| 连接已有实例 | TiaPortal.GetProcesses() → process.Attach() | 避免重复启动;官方 BaseClass 片段的标准做法 |
无头模式的 UI 限制(官方明确):不能使用 HMI 编辑器;但可以用 Openness API 或手动打开 “Devices & Networks” 编辑器与编程编辑器。
⚠️ 无头模式的额外敏感性:WithoutUserInterface 不会弹 GUI,但对 HSP 安装的阻塞对话框更敏感——缺 HSP 的驱动组态应前置预检,否则整个流水线会挂死而不是快速失败。
事件反订阅纪律:Disposed / Notification / Confirmation / Authentication 四个事件必须在 finally 中反订阅;Confirmation 的 e.Result 只能设 "Yes" / "YesToAll" / "No",其他值直接抛异常。
来源:官方 V21 系统手册 5.2.8/5.2.9、advanced.md 进程模型篇、en-blogs.md(Adding Hardware Programmatically 篇)
9.5 构建产物与报告
| 产物 | 格式 | 生成方式 | 用途 |
|---|---|---|---|
| Git 友好工程树 | 文本(XML / CSV / SCL) | TiaGitHandler 导出,剥离时间戳与易变元数据 | 版本控制主体,diff 可读 |
| 报警文本清单 | 分号分隔 CSV,UTF-8 BOM | .xlsx 报警文本列表(plcalarmtextlistgroup)转换 | 跨工具消费(IEC 62443 检查、翻译) |
| 编译器报告 | CompilerResult → JSON / JUNIT XML | 遍历 result.Messages;用官方 XML→JUNIT 转换器 | CI 门禁、趋势看板 |
| 诊断/差异报告 | CompareResult | 官方只保证类名 CompareResult 长期稳定 | 与基线工程对比 |
| 部署产物 | GitLab/Jenkins artifact | Release 阶段归档 TIA 工程 | 排期人工部署的交付物 |
| 静态检查报告 | JSON / XML | 自建 Openness 校验器 | 质量门禁 |
| 代码覆盖率 | CodeCoverage 格式 | 官方 CodeCoverage 工具 | 测试充分度 |
⚠️ 官方长期稳定性铁律:应使用
Projects.OpenWithUpgrade而非Projects.Open;不要假设编译/比较/上下传结果类的层级与顺序(跨版本会变)。只有类名本身长期稳定:CompilerResult、CompareResult、DownloadResult、UploadResult、TransferResult。这意味着报告解析代码必须做版本兼容。
来源:官方 V19/V21 系统手册、Comquent、advanced.md
9.6 MCP / AI 工具链(2026 新形态)
本节资料全部来自 GitHub 与中文社区抓取(2026-10-04)。这些项目迭代极快,星标数为抓取时刻实时值;部分条目仅取到 topics 页面摘要与搜索摘要(已逐条标注),落地前须核对仓库 README 与代码。
9.6.1 生态全景
| 项目 | 星标 | 语言 | 许可 | 定位 | 关键工程亮点 |
|---|---|---|---|---|---|
bulaofen0036-coder/TIA_Portal_Openness_MCP | 280★ / 73 forks | C# | MIT | 把 TIA Portal V20/V21(STEP 7 + WinCC Unified) Openness API 封装成 MCP 工具 | 预热(warm-up)常驻 headless 实例:连接时间 200~340 秒 → 不到 1 秒;提供预构建运行时,无需自己写 Openness 代码 |
1748477582/tia-portal-openness-mcp | 社区快照 91★/36 fork(未能通过 API 抓取实时值) | C# | MIT | 多版本分支构建(V18/V20/V21) | 一个 csproj 对应一个 TIA 版本;bin-v18/ / bin-v20/ 输出隔离;#if !TIA_V18 自动隐藏 28 个工具 |
huahaizo/tia-portal-openness-ai | 70★ | PowerShell | — | AI skill + C# 脚手架,覆盖 V15~V21 | 版本跨度最宽;以「skill」提示词形式而非 MCP 工具协议提供 |
StaniB88/AnyAutomationStudio | 11★(前身 TIAOpennessManager 43★) | — | 未标注(显示 “Sponsor”) | AI 驱动的工程 IDE,面向 TIA Portal 与 CODESYS | 在 PLCSIM Advanced 上跑单元测试(本清单中唯一强调测试验证闭环);支持本地 Ollama |
renanlido/tia-harness | 8★ | — | — | V21 Claude Code 原生插件(skills + subagents + tia MCP) | 唯一以 Claude Code 插件而非独立 MCP server 形式出现 |
| TiaCommander(Czarnak) | 18 工具 / 205 actions | — | — | MCP 桥接 V17–V20 | 覆盖 FB/FC/OB/DB、SCL、LAD、Tag、Watch Table、Cross Reference、Library |
| Gangdolf Automation TIA Portal MCP | — | — | — | V17/V18/V19/V20/V21 版本构建 | 扩展 WinCC Unified(PLC Tag 导出、HMI Text List、Screen、Screen Item) |
TIA-Portal-CLI(HubWiz 介绍) | — | C# | v2.0.0 前 MIT,main 分支声明 AGPL-3.0 | 100+ 动词的 CLI,V19–V21 离线工程 | 默认预运行(dry-run):--apply 才落盘;禁止的在线路径技术上不可达 |
⚠️ 注意
TIA_Portal_Openness_MCP的星标在不同来源有差异(280 vs 社区检索快照 197),为抓取时刻实时值差异,迭代很快。
9.6.2 最有价值的两个工程手法
手法一:预热(warm-up),把 340 秒压到 1 秒内
TIA Portal 启动慢是所有自动化脚本的痛点。TIA_Portal_Openness_MCP 的做法是:用一个常驻 headless TIA 实例把后续命令的连接时间从 200~340 秒降到不到 1 秒。
这条经验可脱离该仓库直接复用:
// 预热模式:进程启动时先拉起一个无头 TIA 实例并挂住,后续命令 Attach 复用
// 关键收益:把 TiaPortal 冷启动的 200~340 秒摊到进程生命周期的一次成本上
using var warmup = new TiaPortal(TiaPortalMode.WithoutUserInterface);
// 之后所有命令不再 new TiaPortal,而是枚举 + Attach
foreach (var proc in Siemens.Engineering.TiaPortal.GetProcesses())
{
using var tia = proc.Attach(); // 毫秒级连接
// ... 业务操作 ...
}
⚠️ 预热实例的代价是独占一个 TIA Portal 进程与一份内存,且受「一个项目同时只允许一个 Openness 进程」的排他锁约束。适合长驻的 Agent/MCP 服务,不适合短命 CLI。
手法二:一个 csproj 对应一个 TIA 版本
### 三版本分别构建(TiaPortalLocation 必须指向对应版本安装目录)
dotnet build TiaMcpServer.V18.csproj -c Release ^
-p:TiaPortalLocation="C:/Program Files/Siemens/Automation/Portal V18" ^
-p:BaseOutputPath=bin-v18/ -p:BaseIntermediateOutputPath=obj-v18/
dotnet build TiaMcpServer.V20.csproj -c Release ^
-p:TiaPortalLocation="D:/Program Files/Siemens/Automation/Portal V20" ^
-p:BaseOutputPath=bin-v20/ -p:BaseIntermediateOutputPath=obj-v20/
dotnet build TiaMcpServer.csproj -c Release # V21(拆分程序集)
工具数量与版本边界(关键事实):
- V18 版 203 个工具,V20/V21 版 231 个。V18 通过
#if !TIA_V18编译符号自动隐藏 28 个工具(23 个 Unified HMI + 5 个 VCI)。 - TIA Portal V18 的完整 Openness 安装不含
Siemens.Engineering.HmiUnified程序集,所以 Unified HMI 工具在 V18 构建里被隐藏;V18 只能用 Classic/Comfort HMI 家族。 Export/Import*Documents与 S7DCL 人类可读文本导出是 V20+ 才有;V18 构建里只能给提示(guided hint),替代方案是ExportBlockSourceUtf8+RegenerateBlockFromSource(SimaticML XML,UTF-8 + BOM)。- 常见问题答疑:工具缺失是客户端工具描述符缓存问题,不是打包裁剪,重启客户端或清缓存即可;运行时权威是
tools/list。
9.6.3 AI 集成的安全边界(必须照抄的设计)
TIA-Portal-CLI 公开的四条边界,是目前工业自动化 AI 工具里最值得借鉴的约束设计:
| 边界 | 具体做法 | 对应到 Openness 的实现要点 |
|---|---|---|
| ① 默认预运行 | 变更类 verb 正常执行只返回「计划更改的内容」,只有加 --apply 才真正落盘 | 读操作与写操作分两个 verb;写操作前先出 diff |
| ② 不操作在线 PLC | 联机、下载到 PLC、Multiuser 签入明确不在范围 | 不调用 OnlineProvider.GoOnline() / DownloadProvider.Download();CI 流水线只下载到 PLCSIM,不下载到物理 CPU |
| ③ 本地/本地部署 | 没有把项目数据发云服务的配置;在 Windows 上与 TIA Portal 同交互会话运行 | 不外传客户标签名、设备结构、DB 内容。PLC 工程含工厂设备信息、网络配置、IP、程序、HMI 画面、工艺逻辑——上传云端是企业无法接受的 |
| ④ 变更后串联编译、审计、交叉引用 | 重执行时把「已是目标状态」视为 no-op,接近声明式协调 | 每次变更后自动 Compile() 并提取诊断;用 CrossReferenceService 产出影响面报告 |
⚠️ 硬约束(来自 HubWiz 对该工具的分析):因许可原因,西门子 Openness 程序集无法与 CI runner 捆绑,因此纯公共 CI 无法实现完整构建/测试复现,必须本地验证。这一条对所有自建 CI 方案都成立。
同类项目横向对比(各自选的「边界」不同):tia-portal-mcp(MCP 桥接 V21)、TwinCAT-Agent(Beckhoff ST 规则 + 技能 + 本地/远程 MCP + 语法检查)、NiRuLogic(IEC 61131-3 梯形图编辑器 + 仿真器 + Arduino MCP)。
三条技术路线分类(HubWiz 提出,值得作为自建 AI 平台的架构参考):
- Engineering MCP(操作工程)——本节讨论的 TIA MCP 都属此类
- Knowledge-Context MCP(拿可靠知识)——如
TIA Portal Docs MCP提供 2800+ 编程章节、get_docs语义检索 - Runtime MCP(观察甚至操作运行中 PLC)——如
MCP PLC Core走nodes7读DB10.DBW20
作者判断:成熟系统应三者结合;MCP 只是「接口层」,真正的下一层是理解项目结构、调用关系、变量依赖、硬件关系与工程历史的 PLC Context Layer。
来源:GitHub(github.md 第 40~44 条)、HubWiz《TIA-Portal-CLI》《PLC 编程必备的 7 个 MCP Server》《TiaLink》
9.7 批量运维工具:官方 TIA Portal Mass Operation Tool
Entry ID 109986645 是西门子在 2026 年给出的官方批量运维答案,是「什么该用 TIA 自身批量工具而非自研脚本」这一决策的关键参考。
| 属性 | 值 |
|---|---|
| Application ID | Siemens.TiaPortal.MassOperationTool |
| Type | TIA Portal Openness Application |
| 最新版本 | V2.0.3.0(2026-04-22) |
| 运行环境 | SINAMICS Startdrive ≥ V21 Update 1 + TIA Portal V21 Update 2+ |
| 操作面 | GUI + CLI 双模式;CLI 支持批处理/自动化模式下无界面执行 routines,可集成到脚本流水线 |
批量能力:复制驱动与技术对象(copying drives and technology objects)、连接驱动与技术对象、批量下装设备、批量设置驱动参数、批量设置技术对象参数。
官方明示限制(4 条,必须提前确认):
- 仅支持 Profinet 设备
- FCPU 只有在安全程序未变更时才能用 “Software (only changes)” 下装;安全程序被改过则必须经 TIA Portal 完整下装
- 当前不支持带密码保护的设备
- 当前不支持把技术对象(TO)值赋值为对象参数
下载:优先用 TIA Package Manager(Entry 109975682)一键下载/解压/建快捷方式并获得更新通知;另有手工 ZIP:V2.0.3.0 for V21(11.9 MB)、V1.2.3.0 for V20(11.3 MB)、V1.2.3.0 for V19(11.3 MB),全部附件带 SHA-256 校验。
同类官方工具对照(做技术选型时按「是否已官方提供」排序):
| 工具 | Entry ID | Type | 用途 |
|---|---|---|---|
| Mass Operation Tool | 109986645 | Openness Application | 批量复制/连接驱动与 TO、批量下装、批量设参(GUI+CLI) |
| SINAMICS Serial Drive Commissioner (SDC) | 109774753 | Openness Application | 用主驱动或模板批量创建/更新/下装多台驱动(G 产品族 + S210 + S200) |
| Edit parameters in several drives (EPISD) | 109777633 | TIA Portal Add-In | 把一个驱动配置复制到多个,官方已声明只接受关键修复、不再新增功能,推荐新项目改用 Mass Operation Tool |
| Startdrive Parameter Comparer | 109814528 | Openness Application | CSV 比较(无需装 Startdrive)与 S120 跨工程比较(需 V18+) |
| Drive Technology Commissioner | 109779879 | Openness Application | 离线/在线批量改 Motion Control TO 与 SINAMICS/SIMOTION 驱动的参数与标签 |
| Democase creator | 109799613 | Add-In | 数秒内创建 S120/S210/S200/V90/G220 演示用例并插入现有工程 |
⚠️ EPISD 的 multiuser 限制:因为该 Add-In 使用 Openness,在 TIA Portal multi-user 功能下使用时存在限制(FAQ Entry 109755940)。Mass Operation Tool 作为 Openness 应用同样需评估此限制。
来源:SIOS 109986645 / 109774753 / 109777633 / 109814528 / 109779879 / 109799613 / 109975682 / 109755940 / official.md
9.8 项目基线(Project Bases)
「项目基线」在 Openness 语境下有三层含义,规划 CI 时需区分:
| 层次 | 机制 | Entry / API | 用途 |
|---|---|---|---|
| ① 生成基线(Project Bases 脚本化生成) | 用 Openness 脚本从「基础工程模板 + Excel 规格表」生成完整工程实例 | Projects.Create(DirectoryInfo, name);命名空间分层:Siemens.Engineering / .HW / .SW / .SW.Tags / .SW.Blocks / .Compiler | 非标行业一条产线数十工站、内容重复只改数组序号的经典场景 |
| ② 版本基线(VCI Workspace 作比较基线) | 把 VCI 工作区导出的文本树作为 diff 基线 | VCI + SIMATIC Automation Compare Tool(109797235) | 判断「这次改动是否只改了该改的」 |
| ③ 多用户基线(Project Server) | TIA Project Server 维护单一工程文件 + 块级 check-out/check-in + 冲突按块 merge | V18+ ProjectServer.OpenProject / SaveProject;109792902 文档索引含 Project Server 入口 | 并行脚本须自行串行化 check-out/check-in(排他写锁由 server 强制) |
⚠️ 多用户与外部 Git 的取舍:TIA Portal V18+ Multiuser Server 维护单一工程文件 + 块级 check-out/check-in + 冲突按块 merge;外部 Git 方案里
.ap是二进制不可 diff,但 commit 历史仍有意义——因此外部 Git 方案的正确用法是 pre-commit hook 校验(CODEOWNERS、GPG 签名、关联每日密码工单号),而不是指望 diff。
来源:en-blogs.md(Project Bases 篇、Password Rotation 篇)、advanced.md(VCI 专题)、official.md(109792902)
9.9 CI 落地检查清单
| # | 检查项 | 依据 |
|---|---|---|
| 1 | CI Runner 是预装 TIA Portal + Openness 的 Windows VM(Openness 无 Linux 版) | advanced.md CI 专题 |
| 2 | 运行账号已加入 Siemens TIA Openness(V21 还需 TIA Engineer),且注销重登过 | en-blogs.md V19→V21 迁移篇 |
| 3 | 流水线用 WithoutUserInterface,且已对模态对话框风险做前置预检 | advanced.md 进程模型篇 |
| 4 | 输出目录零个 Siemens.*.dll(Copy Local=False) | en-blogs.md + 官方手册 |
| 5 | 白名单已写:HKLM\SOFTWARE\Siemens\Automation\Openness\xx.x\Whitelist\<exe>\Entry,含 Path / DateModified / FileHash | 官方手册 5.2.9 |
| 6 | 注意白名单以 exe 的 FileHash + DateModified 双重校验——重新编译会让白名单失效,需重新授权 | 官方手册 5.2.9 |
| 7 | 同一项目同时只允许一个 Openness 进程(disableConcurrentBuilds) | en-blogs.md 异常表 RPC_E_SERVER_CANTUNLOAD |
| 8 | 编译门禁策略明确(是否「warning 也算失败」) | Comquent / 密码轮换篇 |
| 9 | .apXX 与 Git 的关系已明确(当二进制归档用 Git LFS/SVN/Perforce,diff 走 API) | en-blogs.md |
| 10 | 流水线只下载到 PLCSIM,不下载到物理 CPU;晋级到物理机是需密码的独立人工步骤 | en-blogs.md 密码轮换篇 |
本章速查
.apXX不可 diff 是事实而非懒惰——它是复合二进制文档,且 Openness 对它持排他写锁。正确的做法是把「可 diff 的源码(SCL / SIMATIC SD / SimaticML)」与「不可 diff 的工程文件」彻底分离。- 四条可行路线:VCI + Git + Automation Compare Tool(109797235,含 LAD/FBD 与 CFC/SCL-Graph 的硬限制)、TiaGitHandler(477★,SimaticML 导出 + 剥离时间戳,版本互斥必须拆进程)、SIMATIC SD(V20 起的 YAML,唯一官方跨版本格式)、只把 SCL/外部源入库。
- VCI 的真正瓶颈不是 Git,而是 Compare Tool 能力边界——用 CFC 和 SCL/Graph 写的程序目前无法比较;LAD/FBD 则根本无法版本化。
- 官方 CI 路线是 Continuous Integration 工具集(Entry 109801203):五段式架构 + Project Server Listen Console 轮询 + TIA Test Suite Advanced / TestRunner / CodeCoverage / XML→JUNIT 转换器。Test Suite 不查命名规范与编程风格,须自建。
- CI 流水线必须用
WithoutUserInterface:TIA 弹模态对话框会让 Openness 调用无限挂起;但无头模式对 HSP 阻塞对话框更敏感,须前置预检。 - CERN 的六段式(Build → Deploy → Setup → Tests → Release → Report)中最容易被忽略的是 Setup——不清报警、不回零、不设初值,测试就不可重复。
- MCP/AI 生态已可用但要用对:预热常驻 headless 实例能把连接时间从 200~340 秒压到 1 秒内;多版本构建用「一个 csproj 对一个 TIA 版本」,V18 是 203 工具、V20/V21 是 231 工具。V18 完整安装不含
Siemens.Engineering.HmiUnified,Unified 工具在 V18 构建中被隐藏。 - AI 集成的四条安全边界必须照抄:默认预运行(
--apply才落盘)、不操作在线 PLC、纯本地部署不外传工程数据、每次变更后串联编译与审计。⚠️ 西门子 Openness 程序集因许可原因无法与 CI runner 捆绑,纯公共 CI 无法完整复现构建。
来源:SIOS 109801203 / 109797235 / 109986645 / 109792902 / 109975682 / 109755940 / 109774753 / 109777633 / 109814528 / 109779879 / 109799613;官方 V19(1692 页)/ V21 系统手册、V15.1 Startdrive 手册;GitHub
dotnetprojects/DotNetSiemensPLCToolBoxLibrary、siemens/tia-portal-openness-code-snippets、bulaofen0036-coder/TIA_Portal_Openness_MCP、1748477582/tia-portal-openness-mcp、huahaizo/tia-portal-openness-ai、renanlido/tia-harness、StaniB88/AnyAutomationStudio;Comquent GmbH 德文技术博客;ICALEPCS 2025 论文 TUBG002(CERN);HubWiz 汇智网;PLC技术网论坛;西门子中国技术支持论坛。
第10章 开源生态、第三方库与选型指南
第 10 章 开源生态、第三方库与选型指南
本章导读
Openness 的第三方生态呈现「官方包族 + Python 封装 + 社区工具 + AI/MCP 新形态」四层格局。本章先厘清 Python 生态——重点澄清一个高频陷阱:搜索 tia-openness-api-client 会同时命中三个 0★ fork(mgrillo75、OsamaS99、MeKo-Tech),真实维护方是 Repsay(52★),选错分支等于选到停更代码。随后是 C# / NuGet 生态:Siemens.Collaboration.Net.TiaPortal.Openness.Extensions / .Resolver / .References / .Startdrive.Extensions 四件套,版本大版本必须与 TIA 主版本对齐(17.* = V17、18.* = V18),且 V21 是硬性分水岭,跨版本必须重编译。本章还逐个拆解西门子官方仓库 siemens/tia-portal-openness-code-snippets(67★,V20/V21)的模块结构,说明「TestFramework 仅为演示、非断言」的定位、.slnf 解决方案筛选器用法,以及 AssemblyResolve 在无 TIA 环境下的注册表四级下钻解析。章节最后给出两张核心表:主选型决策表(按一次性脚本 / 团队工程化 / CI 集成 / AI 集成四类场景给出技术路线)与开源项目清单表(项目名 / 星标 / 语言 / 许可证 / 用途 / 推荐度),并明确标注无 License 的仓库不可商用这一风险。
星标与版本数据说明:所有星标数、许可证、推送时间均来自抓取于 2026-10-04 的 GitHub REST API 与 NuGet 包页,为抓取时刻真实值。迭代极快的项目(如 MCP 类)星标变化很快,引用时请注意时效。
10.1 Python 生态
10.1.1 上游本体与 fork 辨析(重要避坑)
| 项目 | 星标 | 许可 | 最后推送 | 判定 |
|---|---|---|---|---|
Repsay/tia-openness-api-client | 52★ / 12 forks | MIT | 2026-01-08 | ✅ 上游本体,tia_portal 包的来源仓库 |
mgrillo75/tia-openness-api-client | 0★ / 0 forks | MIT | 2024-09-12 | ❌ fork,未做任何实质性修改 |
OsamaS99/tia-openness-api-client | 0★ / 0 forks | MIT | 2024-04-15(创建当天即停更) | ❌ fork,fork 后未做任何提交 |
MeKo-Tech/tia-openness-api-client | 0★ / 0 forks | MIT | 2026-01-05 | ⚠️ fork,但 README 比上游多一段 USAGE.md 摘要,有增量文档价值 |
⚠️⚠️ 明确提示:不要选错分支。GitHub API 的
fork: true字段明确指向 parent 为Repsay/tia-openness-api-client,且mgrillo75与OsamaS99的 README 与上游逐字相同。若要用 Python 封装,直接用Repsay上游。
上游 API 形态:
| 层次 | 内容 |
|---|---|
| 入口 | tia_portal.config.load() + tia_portal.Client() 两步;Client 负责 attach/start TIA Portal |
| 核心类层次 | Client → Project → Device / DeviceItem → PLCSoftware → PLCBlock;另有 GlobalLibraries / GlobalLibrary |
| 关键能力命名 | devices.create_PLC(article_no, version, name, device_name)、devices.create_HMI(...)、blocks.create(path, name, labels)(用 __NAME__ 占位符替换 XML 模板)、blocks.create_instance_database(name, fb_name)、blocks.create_prodiag_block(name) |
create_prodiag_block 便利点 | 自动在 IDB 组里生成配套实例 DB |
| 配套仓库 | Repsay/siemens-tia-openness-python-stubs(3★ / MIT / 最后更新 2023-03-07)提供 .pyi 风格类型 stub,补齐 pythonnet 无类型提示的痛点;规模不大,对 V21 新 API 覆盖可能不完整 |
USAGE.md 明确列出的 5 条限制(最关键的两条加粗):
- 不支持直接 import 完整硬件组态 / 项目
- HMI 只能创建设备,软件层(画面/变量表)操作很有限——库主要聚焦 PLC
踩坑点(USAGE.md「Tips and Best Practices」三条):
- 导出 block 前必须先
project.compile(),否则块不一致会抛异常 - 递归取块要传
recursive=True - Windows 路径要用双反斜杠或
r"..."原始字符串
### Repsay/tia-openness-api-client 标准用法(抓取自仓库 README)
import tia_portal.config as tia_config
from tia_portal import Client
tia_config.load()
tia_client = Client()
project = tia_client.open_project(
path=r"C:\Users\user\Documents\Automation", name="MyProject")
project.compile() # 必须先编译,才能 export
plcs = project.get_plcs()
plc = plcs[0]
software = plc.get_software()
for block in software.get_all_blocks(recursive=True):
try:
print(block.name, block.export())
except Exception as e:
print(f"Failed to export {block.name}: {e}")
tia_client.close() # 必须显式关闭以释放 TIA 会话
来源:GitHub
Repsay/tia-openness-api-client(2026-10-04 抓取,52★)
10.1.2 pythonnet 入门示例与版本坑
| 项目 | 星标 | 许可 | 定位 |
|---|---|---|---|
Maroder1/TIA-openness | 43★ / 8 forks | MIT | 生态里被引用最多的入门示例(西门子官方论坛 JK_NO 于 2019-06-13 发帖推荐,10 人点谢);定位是「只有 20 行的最小 demo」 |
Maroder1/Openness_examples_python | 35★ / 10 forks | ⚠️ 无 License | Jupyter Notebook 版进阶示例(compile、export/import、networking 三类演示) |
Maroder1/TIA-openness 的环境钉死条件(environment.yml):
| 项 | 值 | 说明 |
|---|---|---|
| Python | python=3.7.3 | 3.7 稳;3.8 装不上 pythonnet 2.4.0 |
| pythonnet | pythonnet==2.4.0 | .NET Framework 3.5/4.x 时代运行时 |
| 运行时 | vs2015_runtime=14.15.26706 | — |
| TIA | V15.1 Professional 且勾选 Openness(默认勾选) | — |
| 权限 | 当前用户加入 Windows 本地组 “Siemens TIA Openness” | 否则 attach/操作会被拒 |
踩坑点:
- README 明确写
conda install pythonnet无效,必须pip install pythonnet - 作者推荐用 Jupyter Notebook 开发(
conda install jupyter nb_conda ipykernel),因为可以单元格级重跑而不用整体重执行——这与官方代码片段仓库用 NUnit[TestFixture]逐方法运行的思路一致 - 代码用
clr.AddReference(...)直接指向PublicAPI\V15.1\Siemens.Engineering.dll——这是 pythonnet 侧最经典的 DLL 路径写法,也是该生态所有后续项目的共同起点
⚠️ Openness_examples_python 的风险:仓库 9 个 open issue 长期未关闭,GitHub API license: null(明确没有 LICENSE 文件)。复用其代码需注意授权不明,商用前应先向作者确认。
### Maroder1/TIA-openness 的 20 行最小 demo(抓取自仓库 README)
import clr
clr.AddReference(r'C:\Program Files\Siemens\Automation\Portal V15_1\PublicAPI\V15.1\Siemens.Engineering.dll')
from System.IO import DirectoryInfo
import Siemens.Engineering as tia
mytia = tia.TiaPortal(tia.TiaPortalMode.WithUserInterface) # 带 UI 启动
myproject = mytia.Projects.Create(DirectoryInfo(r'C:\Jonas\TIA'), 'PythonTest')
### Devices.CreateWithItem 的第一个参数是 order number 字符串
station1 = myproject.Devices.CreateWithItem(
'OrderNumber:6ES7 515-2AM01-0AB0/V2.6', 'station1', 'station1')
10.1.3 pythonnet 侧两个必知陷阱
| 陷阱 | 现象 | 解决 |
|---|---|---|
| 漏引 Contract 程序集 | 只 AddReference Siemens.Engineering.dll 会抛 FileNotFoundException: Could not load file or assembly 'Siemens.Engineering.Contract, Version=1800.100.4201.1, ...' | 额外手动引用 Siemens.Engineering.Contract.dll(在 Bin\PublicAPI\ 目录下) |
| V18 路径不同 | V18 的两个 DLL 路径不同 | Contract 在 Portal V18\Bin\PublicAPI\,主 DLL 在 Portal V18\PublicAPI\V18\ |
| 泛型集合不兼容 | ExternalSourceGroup.GenerateSource() 抛 TypeError: No method matches given arguments for GenerateSource: (, , ) | Python 原生 list 不兼容 .NET 泛型集合,必须用 System.Collections.Generic.List[T] |
### ① pythonnet 侧必须双引用(V18 路径示例)
import clr
from System.IO import DirectoryInfo, FileInfo
### (1) 规避 Jupyter 导入 DLL 的问题,必须先引 Contract
clr.AddReference(r'C:\Program Files\Siemens\Automation\Portal V18\Bin\PublicAPI\Siemens.Engineering.Contract.dll')
### (2) 再引主 DLL
clr.AddReference(r'C:\Program Files\Siemens\Automation\Portal V18\PublicAPI\V18\Siemens.Engineering.dll')
import Siemens.Engineering, Siemens.Engineering.Compiler, Siemens.Engineering.HW.Features
### ② GenerateSource 泛型坑修正:必须用 .NET 泛型 List,不能用 python list
from System.Collections.Generic import List
from Siemens.Engineering.SW.Blocks import GenerateOptions
blocks = List[object]()
blocks.Add(block)
external_source.GenerateSource(blocks, FileInfo(target_filename), GenerateOptions.Without)
Python 侧还有一个能力硬边界:Openness 没有直接往 OB/FB 的 Network 里插入 FB 的 API,唯一可行路径是先导出为 XML、在 XML 层面改、再导回。要查 Python 侧 API 需用 ILSpy 反编译 Siemens.Engineering.dll——因为西门子官方 Openness 文档对 Python 侧不友好,这是社区通用做法。
⚠️ 注意
GenerateOptions的取值差异:本条 tutorial 用GenerateOptions.Without,而官方 C# 片段用GenerateOptions.None(GenerateSource(exportableBlocks, fileInfo, GenerateOptions.None))。⚠️ 该来源 API 签名存疑,需对照官方手册核实枚举成员名;跨语言枚举成员不可互推。
来源:GitHub
Maroder1/*、Repsay/*、0x42697262 个人技术笔记站、industrial monitor direct Python tutorial
10.2 C# / NuGet 生态:官方包族四件套
10.2.1 包清单与版本对应规则
| 包 | 最新版本 | 目标框架 | 作用 |
|---|---|---|---|
Siemens.Collaboration.Net.TiaPortal.Packages.Openness | — | — | 全部 Siemens.Engineering 程序集(被其他三个包依赖) |
Siemens.Collaboration.Net.TiaPortal.Openness.Resolver | — | — | 运行时一次性解析全部 Siemens.Engineering 程序集 |
Siemens.Collaboration.Net.TiaPortal.Openness.Extensions | 21.0.1765368043 | .NET Framework 4.8 | 核心扩展方法库(硬件遍历、属性读写、WinCC) |
Siemens.Collaboration.Net.TiaPortal.Openness.Startdrive.Extensions | 21.0.1765368573(另有 18.0.1685432506 等历史版本) | .NET Framework 4.8(net48 兼容 net481) | Startdrive 扩展(参数、报文、TO 连接) |
版本对应规则(README 原文):17.* = TIA Portal V17, 18.* = TIA Portal V18,20.* = TIA Portal V20, 21.* = TIA Portal V21。
⚠️ 不得单独安装:Startdrive.Extensions 会自动级联安装三个依赖(
Packages.Openness+.Resolver+.Extensions,本包依赖Extensions >= 18.0.1685432504)。手动装单个包会导致运行时解析失败。ℹ️ 包名 “Prefix Reserved”(前缀已由微软保留),非官方无法仿冒——这是一个可信度信号。
Startdrive.Extensions 历史版本下载量(可作为生态活跃度参考):21.0.1765368573(714)、20.0.1744193698(1043)、19.0.1725480563(874)、18.0.1725480535(365)、18.0.1685432506(735)、18.0.1685386093(359)、17.0.1725480506(303)、17.0.1685432475(428)、16.0.1725480463(286)、16.0.1685432427(337)、16.0.1685386021(324)。
10.2.2 ⚠️ V21 是硬性分水岭
Siemens.Collaboration.Net.TiaPortal.Openness.Extensions V21 页面原文摘录:
“TIA Portal Openness Extensions V21 are only compatible with TIA Portal V21 and above. TIA Portal V21 introduces breaking changes in the Openness API. Applications built against TIA Portal versions below V21 are not compatible with V21 and above.”
结论:跨 V21 边界必须重编译,不能只换 DLL。这与 V19 手册的向后兼容矩阵形成对比——V19 交付范围包含 V16/V17/V18 的 Siemens.Engineering.dll,基于 V16~V18 编译的应用在 V19 上无需修改即可运行(要 V19 新特性才需重编译);但 V15.1 的 dll 不再随 V19 交付。
10.2.3 Resolver:一行代码替代手写程序集解析器
// 官方替代方案:替代手写 AssemblyResolve 与注册表四级下钻
// dotnet add package Siemens.Collaboration.Net.TiaPortal.Openness.Startdrive.Extensions --version 18.0.1685432506
Api.Global.Openness().Initialize();
// 之后即可正常使用扩展方法
IEnumerable<DriveObjectContainer> containers = tiaProject.AllDriveObjectContainers();
bool isSinamics = device.IsSinamics();
DriveObjectContainer doc = deviceItem.AsDriveObjectContainer();
driveObject.ChangeTelegramNumber(5, TelegramType.MainTelegram);
DriveParameter parameter = driveObject.Parameters.Get("p9316.1").Bit(1);
bool isBico = parameter.IsBiCo();
float referenceSpeed = parameters.P2000().Get();
driveObject.Parameters.Parameters.G120().P3981().SetAndWaitUntilReset(1);
Resolver vs app.config vs AssemblyResolve 三选一(官方给出三种方式,推荐优先级如下):
| 方式 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
① app.config | 多数场景推荐 | 会校验强名称,engineering.dll 被恶意篡改会导致加载错误 | 需手工维护 dependentAssembly |
② AssemblyResolve | copy deploy 等场景 | 灵活,可反射解析 | 无法检测篡改;且有 JIT 崩溃陷阱(见下) |
| ③ 直接复制 DLL 到应用目录 | — | — | ❌ 官方不推荐:Copy Local: True 不受支持 |
AssemblyResolve 的 JIT 陷阱(官方专门警告):若在 Main 中使用了 engineering assembly 的类型,JIT 会在程序启动时就编译 Main 并因找不到程序集而崩溃;在 Main 里注册 resolver 无效。官方解法是把业务逻辑放进独立方法,让 JIT 编译 Main 时所有类型都已知。
app.config 写法:
<configuration><runtime><assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Siemens.Engineering" culture="neutral"
publicKeyToken="d29ec89bac048f84"/>
<codeBase version="xx.x.x.x"
href="FILE://C:\Program Files\Siemens\Automation\Portal Vxx\PublicAPI\Vxx\Siemens.Engineering.dll"/>
</dependentAssembly>
<!-- Hmi 需另写一个 dependentAssembly,publicKeyToken 相同 -->
</assemblyBinding></runtime></configuration>
10.2.4 Extensions 包最有价值的部分:属性操作
Openness 原生 API 没有属性读写能力,这是扩展包最不可替代的价值:
// ① 跨设备复制属性(原生 Openness 做不到)
var sourceAttributeInfo = deviceA.GetAllAttributeInfos(tiaPortal, accessMode, null, excludeTypes);
var targetObjectAttributeInfo = sourceAttributeInfo.Copy(targetDevice);
targetObjectAttributeInfo.ApplyValuesFrom(sourceAttributeInfo);
// ② 安全读写属性(Try 模式,失败不抛异常)
if (deviceItem.TrySetAttribute("Name", "My Device Item"))
logger.Information("Name was set");
// ③ WinCC Unified:按名取或建画面/控件
Screen conveyorsScreen = screens.AddOrGet("Conveyors");
HmiSlider velocitySlider = conveyorsScreen.ScreenItems.AddOrGet<HmiSlider>("TargetVelocity");
Extensions 包完整方法清单:
| 类别 | 方法 |
|---|---|
| 硬件递归遍历 | AllDevices()(递归穿设备/设备组/未分组设备)、AllDeviceItems()、AllPlcSoftwares()、AllStartdriveDevices()、AllHmiDevices() |
| IO 地址 | ioSystem.GetIoAddresses()、ioSystem.GetIoAddressInfos()(返回 (Device, IEnumerable<(DeviceItem, IEnumerable<Address>)>)) |
| 属性 | tAttribute<string>("Name")、TrySetAttribute(...)、TryGetAttribute(...);跨设备搬属性 GetAllAttributeInfos(...) → Copy(targetDevice) → ApplyValuesFrom(sourceAttributeInfo) |
| 父级遍历 | Parents()、Parent<Device>()、TiaPortalInstance() |
| 块与变量表 | AsPlc()(Device→PlcSoftware)、AllBlocks()(递归全部)、AllUserBlocks()、AllFBs()、AllTagTables() |
| WinCC Unified | AllHmiSoftwares()、AsHmiSoftware()、screens.AddOrGet(...)、ScreenItems.AddOrGet<HmiSlider>(...)、screen.ParentComposition()、ScreenItems.Typed<HmiTextBox>()、Buttons() / Sliders() / AlarmControls() |
| WinCC Advanced/Professional | AllHmiTargets()、AsHmiTarget() |
⚠️ WinCC Unified 与 Advanced 是两套不同类型体系,不能混用。Unified 走
AllHmiSoftwares()/AsHmiSoftware(),Advanced/Professional 走AllHmiTargets()/AsHmiTarget()。
来源:NuGet
Siemens.Collaboration.Net.TiaPortal.Openness.Extensions/.Startdrive.Extensions包页(2026-10-04 抓取)+ 官方 V19/V21 手册 5.2 节
10.3 社区库
10.3.1 清单
| 库 | 星标 | 许可 | 语言 | 能力 | 关键提示 |
|---|---|---|---|---|---|
mking2203/CodeGeneratorOpenness | 166★ / 51 forks | GPL-3.0 | C# | 基于 Openness 的 TIA 代码生成器:构建 group tree;导入任意类型块(FB/FC/OB/数据类型);以 graph 形式生成顺序流程(step sequence) | 基于 TIA V16,经小幅改动可用于 14SP1/15/15.1;最后推送 2023-02-13,已停更 |
Industrialcarbon-basedorganisms/TIA-Portal-Openness-Project | 41★ / 11 forks | ⚠️ 无 License | C# | Modbus 梯形图自动生成器:输入波特率/校验/站号/读写/地址/长度/数据类型/名称,生成含轮询逻辑与变量表的完整程序 | 支持 V14 SP1~V19(已测试);需 .NET Framework 4.8.1 + VS 2022;家庭版 OS 不支持 |
Parozzz/TiaUtilities | 38★ / 7 forks | GPL-3.0 | C# | ① SimaticML XML 解析/改值/重新生成(WIP,仅支持 LAD);② 梯形图 FC 代码生成工具;③ 导入/导出块为 .xml 的 TIA Add-In;④ 版本支持 V16~V19 | 最后推送 2026-10-02,活跃维护中;更早版本差异过大明确不实现 |
caprican/SimaticML | 12★ | ⚠️ 无 License | C# | 解释并配套实现 SimaticML 与 AML 两种 XML 交换标准 | 理解所有 Openness 导入导出格式的基础;README 直给 V16/V17/V18/V19 四份官方手册链接 |
datadiode/webview2 | 16★ | — | JavaScript | OpennessWebView2 ActiveX 控件,让 TIA Portal / WinCC Comfort 内嵌 WebView2(HTML/JS) | 目录含 activex/winccflex/webview2;面向 WinCC Flexible/Comfort 嵌入 |
caprican/TIA-HMI-Alarms | 9★ | License 未标注 | C# | 从全局 DB 注释中提取信息,自动构建 HMI 报警(Add-In 形态) | 「注释驱动生成」思路;与 TiaUtilities 同作者 |
BrunoSantos-Git/TiaPortalOpenness | — | — | C# | TiaOpennessHelper/Import.cs 参考实现 | 被 CODESYS 应用指南作为参考实现引用 |
10.3.2 TiaUtilities 的建模 API 与头号坑
SimaticML 建模 API 有清晰的对象层次:BlockFC / BlockFB / BlockDB / BlockInstanceDB / BlockUDT,通过 AttributeList 设置块名 / 编号 / 编程语言。
变量必须先选 Section 才能创建,Section 与块的组合有严格规则:
| Section | 适用的块 |
|---|---|
| INPUT / OUTPUT / INOUT | BlockFC、BlockFB |
| STATIC | BlockFB、BlockDB、BlockInstanceDB |
| TEMP / CONSTANT / RETURN | BlockFC、BlockFB |
| NONE | BlockUDT |
段(Segment)内元素抽象为 Part,分 ContactPart(触点)、CoilPart(线圈)、Block 调用三类。段标题/注释用 segment.Title[CultureInfo.CurrentCulture] 按语言索引。支持安全梯形图 SimaticProgrammingLanguage.SAFE_LADDER。第三方依赖:FastColoredTextBox(JS 编辑器)、Jint(JavaScript 解析)、RJControls。
var fc = new BlockFC();
fc.Init();
fc.AttributeList.BlockName = "FC_TEST";
fc.AttributeList.BlockNumber = 123;
fc.AttributeList.ProgrammingLanguage = SimaticProgrammingLanguage.LADDER; // 或 SAFE_LADDER
// 先在 Section 下建变量,再建触点/线圈并绑定
var contactVars = new List<SimaticVariable>();
for (int i = 0; i < 10; i++)
contactVars.Add(fc.AttributeList.TEMP.AddVariable($"tContact{i}", SimaticDataType.BOOLEAN));
var coil1Var = fc.AttributeList.TEMP.AddVariable("tCoil1", SimaticDataType.BOOLEAN);
var segment = new SimaticLADSegment();
segment.Title[CultureInfo.CurrentCulture] = "Segment Title!";
// ⚠️ 注意:C# 中 & 优先级高于 |,括号必须加,否则逻辑会错(代码注释明写)
var _ = segment.Powerrail & (contactVars[0] & coil1Var) & coil1Var;
segment.Create(fc);
var xmlDocument = SimaticMLAPI.CreateDocument(fc);
xmlDocument.Save(path);
⚠️⚠️ 头号坑(代码注释明写):C# 的
&优先级高于|,不加括号逻辑会错。这个坑在本书第 11 章有独立排查条目。
10.3.3 CodeGeneratorOpenness 的技术诚实性
该项目的 README 有两点值得注意:
- 已知限制:不支持跨块/类型的全局搜索;项目语言不存在时导入会失败;导出项目文本不能覆盖已存在文件。
- 技术诚实性:作者明确说顺序图生成代码是「逆向工程」得来——因为官方无文档,他用 V14 的 Openness Scripter 导出若干 graph 来反推结构,脚本放在
OPNS目录。实现原理:基于 V14 空顺序的 XML,在 static area 中加入 transitions 与 steps(作者注明 “later versions 会变”)。
选型提示:166★ 但已停更(2023-02)且仅基于 V16,用它做生产环境需自行移植到新版本;参考其逆向思路比直接用它的二进制更有价值。
来源:GitHub(github.md 第 34~39 条,2026-10-04 抓取)
10.4 西门子官方仓库
10.4.1 siemens/tia-portal-openness-code-snippets(67★)
| 属性 | 值 |
|---|---|
| 星标 | 67★ / 8 forks |
| 许可 | License 为 “Other”(Siemens 自有 Royalty-free 协议,SPDX 标为 NOASSERTION)——不是 MIT/OSI |
| 语言 / 版本 | C# / 面向 TIA Portal Openness API V20;最后推送 2026-09-24 |
| 规模 | Plain(6 个 csproj 目录)+ WithExtensions(3 个)+ ViewModel 共 10 个工程,共 41 个 .cs 文件 |
⚠️ 许可提示:Siemens 的 “Royalty-free Software provided by Siemens on sharing platforms” 条款不是 OSI 开源许可,商用前应法务确认。
⭐ 关键定位说明:「TestFramework 仅为演示、非断言」
官方用 NUnit [TestFixture] 组织代码,但刻意不使用任何断言——只为让每个方法能单独点击 Run 便于实验。
这意味着:
- ✅ 可以放心把单个方法当 API 范例抄
- ❌ 不能把它当测试套件用(没有断言就没有回归保护)
- ❌ 不要试图
dotnet test跑它
10.4.2 三种构建组合对应三种安装(.slnf 解决方案筛选器)
| 已安装的 TIA 组件 | 应打开的解决方案文件 |
|---|---|
| 只装 Step7 | src/Step7ProjectSubset.slnf(553 B) |
| Step7 + Startdrive | src/StartdriveProjectSubset.slnf(801 B) |
| Step7 + Startdrive + Dcc | 完整 src/TiaPortal.Openness.CodeSnippets.sln(5257 B) |
.slnf(Solution Filter)是现代 VS 的方案筛选器,用于在庞大解决方案中只加载所需项目。在多版本 TIA 环境里这是最实用的构建入口。
10.4.3 命名规则即文档
| 前缀 | 含义 |
|---|---|
.Plain.* | 直接裸调 Siemens.Engineering 原生 API |
.WithExtensions.* | 使用 NuGet 扩展包简化写法 |
.Plain.Setup | 公共装配 / 测试基类 |
完整模块清单(41 个 .cs 文件):
src/
├── StartdriveProjectSubset.slnf
├── Step7ProjectSubset.slnf
├── TiaPortal.Openness.CodeSnippets.sln
├── TiaPortal.Openness.CodeSnippets.sln.DotSettings ← ReSharper 规则
├── Plain.Setup/ → BaseClass.cs, GlobalSetup.cs, OpennessAssemblyResolverSnippet.cs
├── Plain.Startdrive/ → DriveObject/Encoder/HardwareCatalog/Hardware/Motor/Network/
│ OnlineDrive/Online/Parameter/Safety/Startdrive/
│ TechnologyObject/Telegram Snippets.cs (13 个)
├── Plain.Startdrive.Dcc/ → DccSnippets.cs
├── Plain.Step7/ → HardwareCatalog/Library/Network/Online/Security/
│ TiaProcess/TransferArea Snippets.cs (7 个)
│ └── PLC/ → DataBlock/PlcAlarmTextList/Safety/SoftwareUnits/
│ TagTable/TechnologyObject Snippets.cs (6 个)
│ └── ProgramBlocks/ → ProgramBlockSnippets.cs
├── WithExtensions.Setup/ → BaseClass.cs, GlobalSetup.cs
├── WithExtensions.Startdrive/ → AttributePath/Network/Parameter/TechnologyObject (4 个)
├── WithExtensions.Step7/ → OnlineSnippets.cs, Step7Snippets.cs
│ └── PLC/ → PlcTagTableSnippets.cs, ProgramBlockSnippets.cs
└── ViewModel/
docs/images/ (仅截图,无 markdown 文档)
对比 Plain vs WithExtensions 可看出扩展包的价值定位:WithExtensions.Startdrive 四个模块中 AttributePathSnippets.cs 独有——对比 Plain 版可见扩展包把「按属性路径定位参数」作为核心增强。
10.4.4 ⭐ AssemblyResolve 在无 TIA 环境下的注册表四级下钻
Plain.Setup/OpennessAssemblyResolverSnippet.cs 演示了运行时程序集解析——这是官方 NuGet 包 Siemens.Collaboration.Net.TiaPortal.Openness.Resolver 的手工实现版。四个关键设计点:
- 注册方式:
ResolveEventArgs回调挂AppDomain.CurrentDomain.AssemblyResolve,按args.Name解析 - 注册表路径固定:
HKLM\SOFTWARE\Siemens\Automation\Openness(RegistryHive.LocalMachine+RegistryView.Registry64) - 四级下钻:版本号子键 →
PublicAPI→ 完整版本号 →net48 - 只拦截一个程序集名:
Siemens.Engineering.Base,其余返回 null 交回默认解析
版本号提取方式很「土」但有效:从 args.Name 里取含 Version= 的片段再 .Split('.')[0] 得到主版本号,用于挑选 TIA 版本子键。
private const string SiemensEngineeringDllName = "Siemens.Engineering.Base";
private const string SubKeyName = @"SOFTWARE\Siemens\Automation\Openness";
public static Assembly ResolveSiemensEngineeringAssembly(object sender, ResolveEventArgs args)
{
var assemblyName = new AssemblyName(args.Name);
if (assemblyName.Name != SiemensEngineeringDllName) return null;
var assemblyVersion = args.Name.Split(',').FirstOrDefault(x => x.Contains("Version")).Split('=')[1].Split('.')[0];
using var regBaseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
using var opennessBaseKey = regBaseKey.OpenSubKey(SubKeyName);
using var versionKey = opennessBaseKey?.OpenSubKey(
opennessBaseKey.GetSubKeyNames().FirstOrDefault(x => x.Contains(assemblyVersion)));
var path = versionKey?.OpenSubKey("PublicAPI")?.OpenSubKey(assemblyName.Version.ToString())
?.OpenSubKey("net48")?.GetValue(SiemensEngineeringDllName).ToString();
// ⚠️ 坑:路径取自注册表,若返回 null 或 !File.Exists 必须返回 null(而不是抛异常),
// 否则整个 AppDomain 解析链会崩
return path == null || !File.Exists(path) ? null : Assembly.LoadFrom(path);
}
⚠️ 注意 V21 的差异:
Siemens.Engineering.Base这个程序集名是 V21 模块化程序集拆分后的产物(V21 起从单一Siemens.Engineering.dll拆成Siemens.Engineering.Base.dll/Siemens.Engineering.Step7.dll等)。这段代码面向 V20/V21,不适用于 V17~V19(那些版本目录下的键值指向Siemens.Engineering)。
10.4.5 Plain.Setup/BaseClass.cs:三个值得抄的技巧
① Attach 优先,找不到才新建实例:
var tiaProcesses = Siemens.Engineering.TiaPortal.GetProcesses();
if (tiaProcesses.Count > 0)
{
foreach (var proc in tiaProcesses)
{
TiaPortalInstance = proc.Attach(); // 复用已有 TIA 实例
foreach (var p in TiaPortalInstance.Projects)
if (p.Name != tiaArchiveName.Remove(tiaArchiveName.IndexOf(".zap", StringComparison.Ordinal)))
continue; else { Project = p; return; }
}
}
TiaPortalInstance = new Siemens.Engineering.TiaPortal(TiaPortalMode.WithUserInterface);
② 归档释放到临时目录,避免污染原归档——这是批量跑测试的关键技巧:
var sourcePath = new FileInfo(Path.Combine(TestContext.CurrentContext.TestDirectory, "resources", tiaArchiveName));
_tempProjectDirectory = new DirectoryInfo(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()));
Project = TiaPortalInstance.Projects.Retrieve(sourcePath, _tempProjectDirectory);
③ 官方自己都注释掉的清理代码(暴露真实痛点):[OneTimeTearDown] 里 Project?.Close()、杀进程、_tempProjectDirectory.Delete(true) 三步默认都被注释——因为进程强杀会留下 TIA 残留状态,临时目录删除也常因文件占用失败。
④ 依赖测试工程与 resources/*.zap21 归档项目文件,[TestFixture("Step7.zap21")] 把归档名作为参数注入基类。
10.4.6 值得单独学习的两个片段
① Know-How 保护是导入的硬边界(ProgramBlockSnippets.cs):
// 过滤 KnowHow 保护块 + 并行导入
var blocksToImport = _blockComposition.Where(b =>
editedBlockNames.Contains(b.Name) && !b.IsKnowHowProtected).ToList();
Parallel.ForEach(blocksToImport, plcBlock =>
{
_blockComposition.Import(new FileInfo(Path.Combine(importFolderPath, plcBlock.Name)),
ImportOptions.Override);
editedBlockNames.Remove(plcBlock.Name);
});
⚠️ 筛选导入时必须加
&& !plcBlock.IsKnowHowProtected,否则会因为无法读取保护块而失败。这是导入别人项目时最常踩的坑。 ℹ️ 批量导入用了Parallel.ForEach+HashSet<string>并行移除——属实机验证过的并发写法。
② 块保护密码规则(西门子两处独立代码都写了一遍,可确认是硬性要求):
var provider = _blockComposition.First(b => b.Name == "Axis_blue").GetService<PlcBlockProtectionProvider>();
var invalidChars = provider.GetInvalidPasswordCharacters().ToList(); // ⚠️ 必须先过滤非法字符
var pwd = new SecureString();
foreach (var c in "@ValidPassword123") pwd.AppendChar(c);
pwd.MakeReadOnly();
provider.Protect(pwd); // 密码需 ≥8 ≤120,含数字/大小写/特殊字符
③ 软件单元(Software Unit)变量表必须 Concat 合并(WithExtensions.Step7/PlcTagTableSnippets.cs):
using Siemens.Engineering.SW.Units;
if (plcDevice.DeviceItems.Single(x => x.Classification == DeviceItemClassifications.CPU)
.GetService<SoftwareContainer>().Software is not PlcSoftware software)
throw new InvalidOperationException("Device does not contain any software instance (null).");
// 主块组下的变量表
var tagTables = software.TagTableGroup.AllTagTables();
// 关键:软件单元内的变量表也要抓,用 Concat 合并
var units = software.GetService<PlcUnitProvider>().UnitGroup.Units;
var unitTagTables = units.SelectMany(x => x.TagTableGroup.AllTagTables());
var allTags = tagTables.Concat(unitTagTables).SelectMany(x => x.Tags).ToList();
核心发现:变量表不只在
software.TagTableGroup,每个 Unit 下还有自己的 TagTableGroup,必须用Concat合并,否则会漏掉软件单元内的变量。这是 V17+ 多软件单元工程的必要操作。
④ 硬件目录遍历——Find("") 表示「从根开始、找全部」:
// "" 表示从根节点查找全部条目
var hardwareCatalog = TiaPortalInstance.HardwareCatalog.Find(string.Empty);
var catalogEntries = hardwareCatalog.ToList();
Console.WriteLine($"Total catalog entries: {catalogEntries.Count}");
foreach (var entry in catalogEntries)
Console.WriteLine($"TypeIdentifier: {entry.TypeIdentifier}, CatalogPath: {entry.CatalogPath}");
// TypeIdentifier 可直接喂给 Devices.CreateWithItem(...)
这是「批量组态」类工具的前置步骤:先扫目录拿到可用型号与版本,再驱动创建。
TypeIdentifier可直接复制,避免手写错版本号。
⑤ 生成 SCL 源(进入 Git 的关键路径):
using Siemens.Engineering.SW.ExternalSources;
var software = Project.Devices.First(x => x.Name.Equals("PLC_S120Democase")).AsPlc();
FileInfo fileInfo = new(Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.scl"));
// 只挑 SCL/STL 且非 KnowHow 保护的 FB
var exportableBlocks = software.BlockGroup.AllFBs().Where(b =>
!b.IsKnowHowProtected && b.ProgrammingLanguage is ProgrammingLanguage.SCL or ProgrammingLanguage.STL);
software.ExternalSourceGroup.GenerateSource(exportableBlocks, fileInfo, GenerateOptions.None);
Console.WriteLine($"File exists: {fileInfo.Exists}");
ℹ️ 这段与第 10.1.3 节的 Python 泛型坑互为印证:C# 侧用扩展方法
AllFBs()拿IEnumerable<PlcBlock>直接喂给GenerateSource,绕过了 Python 侧.NET List<T>泛型坑。 ℹ️ 扩展方法位于Siemens.Engineering根命名空间下——源码里只using Siemens.Engineering.SW.ExternalSources;就直接调AsPlc()/AllFBs()。
⑥ 一个官方片段的瑕疵(示范「不要这样做」):TagTableSnippets.cs 取 PlcSoftware 用的是索引写法 DeviceItems[1],比其他片段的 Classification == DeviceItemClassifications.CPU 写法脆弱——索引随设备组态变化会失效。
来源:GitHub
siemens/tia-portal-openness-code-snippets(第 10~23 条,2026-10-04 抓取,9 个.cs文件实抓)
10.4.7 其他官方子项目
| 仓库 | 星标 | 许可 | 语言 | 用途 |
|---|---|---|---|---|
tia-portal-applications/tia-portal-openness-unified-library | 13★ | Other | C# | 做新 Openness 工具的起手式基础库(原文:“Use this library in case of creating a new TIA Openness tool”);整个库只有一个类 UnifiedOpennessConnector,且是 IDisposable |
tia-addin-opc-ua-modelled-interface | 20★ | Other | C# | 用 Openness 遍历项目自动生成 OPC UA 服务器的用户建模接口 |
TIA-Add-In-ShowScripts | 15★ | Other | C# | 把所有画面的 JavaScript 导出为可读文件到 UserFiles 目录,并额外产出一份 Excel 形式的全画面总览 |
CWC-in-WinCC-Unified | 13★ | Other | HTML/JS | 开发 CWC(Custom Web Control)并导入项目的官方指南;同组织相关:GaugeMeter(7★)、TableControl(9★)、Web-Toolbox_CWC(6★)、Custom-AlarmControl-Code(3★) |
SiOME-Base-Plugin | 12★ / 7 forks | MIT(该组内少见 OSI 许可) | HTML | SiOME 插件入门基座,SIOS 条目 109755133 |
tia-addin-build-package | 11★ | Other | — | 创建/管理 TIA Add-In 开发所需全部文件;最后更新 2022-06,版本较老,新版 TIA(V20+)可能需要调整 |
TIA-Openness-Create-Synoptic-Unified | 5★ | Other | C# | 导出 PLC 各网络信息转 JSON,供 WinCC Unified synoptic;命名约定是硬性前提:PLC 块注释里必须包含字符串 #Synoptik;输出到 /UserFiles/Synoptic;作者自述只是原型,目前还不能导出全部内容 |
UMC-config-scripts | 4★ | Other | PowerShell | 自动化 UMC(User Management Component)环境的安装与配置;最后推送 2026-08,仍在维护 |
OPC UA modelled interface 的两个量化卖点:
- 性能收益明确量化:modelled interface 下 OPC UA 客户端创建 monitored item、读写数据最多快 6 倍
- 适配性价值:S7-1200 与 S7-1500 R/H 型 PLC 本身不支持默认 SIMATIC server interface,此 Add-In 是它们的自动化出路
- 分支对应版本是硬性规则:V17~V20 用
tia-addin-opc-ua-modelled-interface-v20分支;V21+ 用main分支 - 已知限制:无法在不启用 “optimize” 模式的前提下使用;NodeID 不能配为数字标识符;嵌套实例化的 FB 被建模为 “UAObjects” 而非 “UAVariables”,其内部变量无法通过生成的接口访问
// UnifiedOpennessConnector 用法:只有这一个类,但强制 using
using (var connector = new UnifiedOpennessConnector())
{
var tiaPortal = connector.TiaPortal; // 内部会 Dispose
var project = connector.Project;
}
// 命令行:TiaGitHandler.exe -h / --help
ℹ️
UnifiedOpennessLibrary的集成方式不是 NuGet 而是直接复制 DLL 加为引用,仓库自带 demo 工程。exe 传-h/--help打印帮助。
来源:GitHub
tia-portal-applications/*(第 26~33 条)
10.5 选型决策表
10.5.1 主选型决策表(按场景)
| 场景 | 推荐技术路线 | 推荐技术栈 | 关键理由 | 主要风险 |
|---|---|---|---|---|
| ① 一次性脚本(验证 API 可行性、一次性批量操作) | C# + Siemens.Collaboration.Net.TiaPortal.Openness.Extensions + .Resolver | .NET Framework 4.8 / VS 2022;NuGet 引用;Api.Global.Openness().Initialize() 免手写解析器 | 官方包族版本对应规则清晰(18.* = V18);Extensions 提供 AllFBs() / TrySetAttribute() 等高频扩展;不需与 .apXX 抢锁(脚本独占运行) | NuGet 包的 TIA 版本必须与机器上 TIA 一致;.Extensions V21 起不向下兼容 |
| ①’ 一次性脚本(Python 偏好 / 数据处理为主) | Repsay/tia-openness-api-client(52★) | Python 3.7.3 + pythonnet==2.4.0(pip install 而非 conda)+ Jupyter Notebook | 生态最活跃;Client 两步接入;Notebook 可单元格级重跑不必重启 TIA | ⚠️ 不要选 0★ fork;不支持 import 完整硬件组态;HMI 能力有限;pythonnet 3.8 装不上;必须双引 Contract 程序集 |
| ①” 一次性脚本(只做导入导出 / 格式转换) | TiaUtilities(38★,GPL-3.0,活跃) 或 mking2203/CodeGeneratorOpenness(166★,已停更) | C# + .NET Framework 4.8 | TiaUtilities SimaticML 建模 API 层次清晰(LAD 生成);CodeGeneratorOpenness 支持顺序图逆向生成 | ⚠️ TiaUtilities SimaticML 解析仅支持 LAD(WIP);⚠️ CodeGeneratorOpenness 已停更且仅基于 V16;⚠️ 两者均为 GPL-3.0,商用需评估传染性 |
| ② 团队工程化(多人协作、长期维护) | C# + 官方 NuGet 三件套 + 分层架构(配置/服务/模型/日志) | .NET Framework 4.8;app.config(多数场景推荐);ICompilable 编译门禁;自建 JUNIT 报告 | app.config 会校验强名称(AssemblyResolve 无法检测篡改);官方 snippets 提供 41 个模块的现成范例;Test Suite Advanced 可做编程风格检查 | V21 是硬性分水岭,必须重编译;.apXX 排他写锁要求串行化;官方 snippets 是 “Other” 非 MIT 许可 |
| ②’ 团队工程化(版本控制为第一诉求) | TiaGitHandler(DotNetSiemensPLCToolBoxLibrary,477★,LGPL-2.1) | TiaGitHandler.exe CLI;TiaOpennessWhitelist.EnsureWhitelistEntry(version) 白名单;多版本必须拆进程 | 唯一现成的「TIA → Git 友好文本」方案;剥离时间戳与易变元数据;报警文本转 CSV;.apXX → 版本号推断自动化 | ⚠️ 一个应用实例只能加载一个 TIA 版本,加载第二版抛异常;V15.1+ 才支持密码保护工程;LGPL-2.1 商用需注意 |
| ②” 团队工程化(TIA 界面内交互) | TIA Add-In(.addin 放 Portal Vxx\AddIns 或 %ProgramData%\Siemens\Automation\Portal V<ver>\Addins\) | Siemens.Engineering.AddIn 命名空间;MessageBoxProvider / ProgressProvider / FeedbackProvider | Add-In 在新 TIA 版本中无需修改即可继续运行(TIA 自带当前 + 3 legacy 共 4 个 Siemens.Engineering.dll);可扩展项目树右键菜单 | Add-In 必须已有打开的 TIA Portal GUI 和项目;⚠️ 在 OnUpdateStatus 回调中 GetService() 返回 null,只能在 action 回调里用;默认处于禁用状态须逐一启用 |
| ③ CI 集成 | 官方 Continuous Integration 工具集(Entry 109801203)+ TIA Test Suite Advanced + TestRunner + CodeCoverage + XML→JUNIT 转换器 | Windows VM Runner(预装 TIA + Openness);WithoutUserInterface;Jenkins 或 GitLab CI;PLCSIM Advanced | 官方支持的路线;五段式架构 + Project Server Listen Console 轮询建任务;JUnit 报告直接进 CI 门禁 | TIA Portal 只支持 Windows;模态对话框会让 Openness 无限挂起;⚠️ 西门子 Openness 程序集因许可原因无法与 CI runner 捆绑,纯公共 CI 无法完整复现;白名单以 exe 的 FileHash + DateModified 双重校验,重新编译即失效 |
| ③’ CI 集成(批量运维) | 官方 TIA Portal Mass Operation Tool(Entry 109986645) | Siemens.TiaPortal.MassOperationTool V2.0.3.0;GUI + CLI 双模式(CLI 可无界面执行 routines);TIA V21 U2 + Startdrive ≥ V21 U1 | 官方出品,已覆盖驱动/TO 的复制、连接、批量下装、批量设参 | ⚠️ 仅支持 Profinet 设备;FCPU 安全程序变更时须完整下装;不支持密码保护设备;不支持 TO 值赋为对象参数 |
| ④ AI 集成 | MCP 封装路线:TIA_Portal_Openness_MCP(280★,MIT)或 tia-portal-openness-mcp(V18 203 工具 / V20-21 231 工具) | 预构建运行时(无需自己写 Openness 代码);常驻 headless 实例预热;一个 csproj 对一个 TIA 版本;.mcp.json | 预热把连接时间从 200~340 秒降到不到 1 秒;工具数量大;docs/ + templates/ + 能力矩阵齐全 | ⚠️ V18 完整安装不含 Siemens.Engineering.HmiUnified,Unified 工具在 V18 构建中被隐藏;工具缺失是客户端缓存问题不是打包裁剪;必须本地部署不外传工程数据 |
| ④’ AI 集成(CLI / 干跑优先) | TIA-Portal-CLI(⚠️ v2.0.0 前 MIT,main 分支声明 AGPL-3.0) | tia <verb> --json(默认预运行)/ --apply(真正落盘);init.ps1 -Check -Json / doctor / tree / snapshot | 默认预运行是最值得借鉴的工程手法;四条安全边界清晰;实测 5 命令 20.1 s vs 批处理 8.1 s | ⚠️ AGPL-3.0 企业采用需与法务确认;明确不支持在线 PLC 操作 |
| ④” AI 集成(测试验证闭环) | StaniB88/AnyAutomationStudio(前身 TIAOpennessManager 43★) | TIA Portal V15~V21 + CODESYS;Claude / GPT / Gemini 或本地 Ollama;PLCSIM Advanced 上跑单元测试 | 本清单中唯一强调测试验证闭环的项目,区别于只做生成/导出 | ⚠️ License 未标注(GitHub 显示 “Sponsor”),复用前需确认 |
| ⑤ HMI 画面批量生成 | 官方 Excel Importer/Exporter 应用示例(Entry 109792619) | 配套代码包 109792619_Excel_Importer_VX_X_CODE.zip(约 691 KB);一个 Excel 文件 = 一块新画面 | 官方维护,有完整代码包可直接复用而非从零开发;表结构:第 1 行属性名(列顺序任意)、第 2 行起每行一个对象、第 1 列固定为对象类型 | ⚠️ Openness 版本必须与 WinCC Unified 版本一致;⚠️ 重复运行会覆盖已生成画面;WinCC Comfort 仅有子集(画面名、变量链接、基本属性) |
| ⑥ 知识检索(不操作工程) | TIA Portal Docs MCP(2800+ 编程章节,get_docs 语义检索) | MCP 协议 | 解决「AI 如何准确理解 TIA 指令与官方文档」 | 覆盖面限于文档,不能操作工程;建议与 Engineering MCP + Runtime MCP 三者结合 |
10.5.2 决策树(简化版)
你的需求是什么?
│
├─ 操作 TIA 工程(生成/导入/编译/下载)
│ ├─ 一次性验证 → C# + 官方 NuGet 三件套(首选)
│ ├─ 数据处理为主 / 熟悉 Python → Repsay 52★(⚠️ 勿选 0★ fork)
│ └─ 长期维护 → 分层架构 + app.config + 编译门禁
│
├─ 在 TIA 界面里交互(用户点右键)
│ └─ TIA Add-In(跨版本免修改,⚠️ 但必须有已打开的 TIA + 项目)
│
├─ 让代码可 diff / 版本控制
│ ├─ 全语言 + 可视化工具 → VCI + Git + Automation Compare Tool(⚠️ CFC/SCL-Graph 不支持)
│ ├─ 要跨 TIA 版本 → SIMATIC SD(V20 起的 YAML)
│ └─ 只要 SCL → 外部源文件入 Git(CERN 路线)
│
├─ 批量运维(驱动/TO/下装)
│ ├─ 官方已覆盖 → Mass Operation Tool(109986645,GUI+CLI)
│ └─ 官方未覆盖 → 自研 Openness 应用
│
├─ CI 集成
│ ├─ 官方路线 → Continuous Integration 工具集(109801203)
│ ├─ 只做编译门禁 → Windows VM + WithoutUserInterface + JUNIT
│ └─ ⚠️ 公共 CI runner 无法捆绑 Siemens 程序集 → 必须自备 Windows VM
│
└─ AI / Agent 集成
├─ 要工程操作能力 → TIA_Portal_Openness_MCP(280★,预热 <1s)
├─ 要安全边界 → TIA-Portal-CLI(默认预运行,⚠️ AGPL-3.0)
└─ 要测试闭环 → AnyAutomationStudio(PLCSIM Advanced 跑单测)
10.5.3 许可证与停更风险速查
| 风险等级 | 项目 | 风险说明 |
|---|---|---|
| 🔴 不可商用风险 | Maroder1/Openness_examples_python(35★)、Industrialcarbon-basedorganisms/...(41★)、caprican/SimaticML(12★) | 无 LICENSE 文件(GitHub API license: null)。无 License 意味着版权保留,授权不明,商用前必须向作者确认 |
| 🔴 传染性风险 | CodeGeneratorOpenness(166★)、TiaUtilities(38★) | GPL-3.0。若你的工具需要闭源分发或与 TIA 集成进商业产品,必须评估 GPL 传染性 |
| 🟡 非 OSI 许可 | siemens/tia-portal-openness-code-snippets(67★)、tia-portal-applications/* 全部 | Siemens “Royalty-free Software provided by Siemens on sharing platforms”,SPDX 标为 NOASSERTION,不是 OSI 开源许可 |
| 🟡 许可变更风险 | TIA-Portal-CLI | v2.0.0 之前是 MIT,main 分支声明对后续版本应用 AGPL-3.0——企业采用需与法务确认所用版本、修改、分发与网络服务行为 |
| 🟡 许可未标注 | StaniB88/AnyAutomationStudio(11★)、caprican/TIA-HMI-Alarms(9★) | License 未标注(前者 GitHub 显示 “Sponsor” 标识,可能为私有/赞助协议) |
| 🔴 已停更 | CodeGeneratorOpenness(最后推送 2023-02-13)、mgrillo75/*(2024-09)、OsamaS99/*(2024-04)、tia-addin-build-package(2022-06) | 仅基于 TIA V16;新版 TIA(V20+)需自行移植 |
| 🟢 低风险(活跃 + 许可明确) | DotNetSiemensPLCToolBoxLibrary(477★,LGPL-2.1,最后推送 2026-08-26)、TiaUtilities(38★,GPL-3.0,最后推送 2026-10-02)、TIA_Portal_Openness_MCP(280★,MIT,2026-09-27)、Repsay/tia-openness-api-client(52★,MIT,2026-01-08)、Repsay/siemens-tia-openness-python-stubs(3★,MIT) | — |
| ⚠️ 需持续观察 | Repsay/siemens-tia-openness-python-stubs 最后更新 2023-03 | 规模不大,对 V21 的新 API 覆盖可能不完整 |
⚠️ LGPL-2.1 与 GPL-3.0 的差别在动态链接:LGPL 允许你链接其 DLL 而你的代码保持闭源(需提供 LGPL 库源码可得性);GPL 对分发有更强传染性。若只是内部工具不分发,两者风险都低;随产品分发则必须评估。
来源:GitHub API 元数据、NuGet 包页、HubWiz 分析(2026-10-04 抓取)
10.6 开源项目清单表
星标数抓取于 2026-10-04。「推荐度」综合考虑活跃度、许可、版本覆盖、技术文档完整度四个维度。
| # | 项目 | 星标 | 语言 | 许可证 | 用途 | 推荐度 |
|---|---|---|---|---|---|---|
| 1 | dotnetprojects/DotNetSiemensPLCToolBoxLibrary | 477★ / 210 forks | C# | LGPL-2.1 | 多版本 Openness 封装 + TiaGitHandler(Git 版本控制方案) + 直连 S7-300/400/S5 PLC | ⭐⭐⭐⭐⭐ 活跃(2026-08-26);版本隔离与白名单机制是本主题最工程化的方案 |
| 2 | bulaofen0036-coder/TIA_Portal_Openness_MCP | 280★ / 73 forks | C# | MIT | 把 V20/V21 Openness 封成 180+ MCP 工具;预热把连接 200~340s 降到 <1s;预构建运行时 | ⭐⭐⭐⭐⭐ AI 集成首选;⚠️ README 未完整抓取 |
| 3 | mking2203/CodeGeneratorOpenness | 166★ / 51 forks | C# | GPL-3.0 | TIA 代码结构生成器、STEP 顺序图逆向生成 | ⭐⭐ 仅作参考——已停更(2023-02)且仅基于 V16;⚠️ GPL-3.0 |
| 4 | siemens/tia-portal-openness-code-snippets | 67★ / 8 forks | C# | Other(Siemens Royalty-free,SPDX NOASSERTION) | 官方模块化代码片段,41 个 .cs,覆盖 Plain/WithExtensions × Step7/Startdrive/Setup | ⭐⭐⭐⭐⭐ 官方一手范例;⚠️ TestFramework 无断言,不能当测试套件;非 OSI 许可 |
| 5 | Repsay/tia-openness-api-client | 52★ / 12 forks | Python | MIT | tia_portal 包本体;Client→Project→Device→PLCSoftware→PLCBlock | ⭐⭐⭐⭐⭐ Python 生态唯一应选的仓库;⚠️ 不支持 import 完整硬件组态,HMI 能力有限 |
| 6 | Maroder1/TIA-openness | 43★ / 8 forks | Python | MIT | 20 行最小可运行示例(官方论坛推荐) | ⭐⭐⭐⭐ 入门首选;⚠️ Python 3.7.3 + pythonnet 2.4.0 钉死,3.8 装不上 |
| 7 | Industrialcarbon-basedorganisms/TIA-Portal-Openness-Project | 41★ / 11 forks | C# | ⚠️ 无 License | Modbus 梯形图自动生成器(含轮询逻辑与变量表) | ⭐⭐ 功能完整但 ⚠️ 无 License 不可商用;需 .NET FW 4.8.1;家庭版 OS 不支持 |
| 8 | Parozzz/TiaUtilities | 38★ / 7 forks | C# | GPL-3.0 | SimaticML 解析(仅 LAD,WIP)+ 梯形图 FC 代码生成 + TIA Add-In | ⭐⭐⭐ 活跃维护(2026-10-02);⚠️ GPL-3.0;⚠️ **C# &/` |
| 9 | Maroder1/Openness_examples_python | 35★ / 10 forks | Jupyter Notebook | ⚠️ 无 License | compile / export-import / networking 演示 | ⭐⭐ ⚠️ 无 License 不可商用;9 个 issue 长期未关闭 |
| 10 | tia-portal-applications/tia-addin-opc-ua-modelled-interface | 20★ / 3 forks | C# | Other | 用 Openness 生成 OPC UA 用户建模接口,客户端读写最多快 6 倍 | ⭐⭐⭐⭐ S7-1200 与 1500 R/H 的唯一出路;⚠️ V17~V20 用 -v20 分支,V21+ 用 main |
| 11 | datadiode/webview2 | 16★ | JavaScript | — | OpennessWebView2 ActiveX 控件,TIA/WinCC Comfort 内嵌 WebView2 | ⭐⭐ 参考实现;仅 topics 摘要,未抓 README |
| 12 | TIA-Add-In-ShowScripts | 15★ / 1 fork | C# | Other | 导出所有画面 JS 到 UserFiles + Excel 全画面总览 | ⭐⭐⭐ 最直接的「Openness × Excel」官方实现;⚠️ main 分支 README 404 |
| 13 | tia-portal-applications/tia-portal-openness-unified-library | 13★ / 2 forks | C# | Other | 新 Openness 工具的起手式基础库;单类 UnifiedOpennessConnector(IDisposable) | ⭐⭐⭐ 结构简单但只有 1 个类,直接复制 DLL 加引用(非 NuGet) |
| 14 | CWC-in-WinCC-Unified | 13★ / 1 fork | HTML/JS | Other | CWC(Custom Web Control)开发与导入官方指南 | ⭐⭐ Web 侧参考;同 UserFiles 目录机制 |
| 15 | caprican/SimaticML | 12★ | C# | ⚠️ 无 License | SimaticML + AML(AutomationML)标准说明与参考实现 | ⭐⭐⭐ 标准说明价值高,含 V16~V19 四份官方手册链接;⚠️ 无 License |
| 16 | SiOME-Base-Plugin | 12★ / 7 forks | HTML | MIT | SiOME(OPC UA Modeling Editor)插件入门基座 | ⭐⭐⭐ 该组内唯一 MIT 许可且唯一带真实 fork |
| 17 | tia-addin-build-package | 11★ | — | Other | TIA Add-In 工程脚手架,装上即可开发 | ⭐⭐ 最后更新 2022-06,版本老,V20+ 可能需调整 |
| 18 | caprican/TIA-HMI-Alarms | 9★ | C# | 未标注 | 从全局 DB 注释提取信息自动构建 HMI 报警(Add-In) | ⭐⭐ 「注释驱动生成」思路;仅 topics 摘要 |
| 19 | renanlido/tia-harness | 8★ | — | — | V21 Claude Code 原生插件(skills + subagents + tia MCP) | ⭐⭐ 仅 V21,不向下兼容;仅 topics 摘要 |
| 20 | tia-portal-applications/TIA-Openness-Create-Synoptic-Unified | 5★ | C# | Other | PLC 网络信息 → JSON,供 Unified synoptic | ⭐⭐ 作者自述只是原型,不能导出全部内容;⚠️ 块注释必须含 #Synoptik |
| 21 | Repsay/siemens-tia-openness-python-stubs | 3★ / 1 fork | Python(.pyi) | MIT | pythonnet 类型 stub,补齐 IDE 提示 | ⭐⭐⭐ 解决真实痛点;⚠️ 最后更新 2023-03,对 V21 覆盖可能不完整 |
| 22 | UMC-config-scripts | 4★ / 1 fork | PowerShell | Other | 自动化 UMC 环境安装与配置 | ⭐⭐⭐ 最后推送 2026-08,仍在维护;PowerShell 路线参考 |
| 23 | mgrillo75/tia-openness-api-client | 0★ | Python | MIT | — | ❌ fork,README 与上游逐字相同,未做任何修改 |
| 24 | OsamaS99/tia-openness-api-client | 0★ | Python | MIT | — | ❌ fork,创建当天即停更,fork 后无任何提交 |
| 25 | MeKo-Tech/tia-openness-api-client | 0★ | Python | MIT | fork(README 多一段 USAGE.md 摘要) | ❌ fork;唯一价值是那份文档摘要,代码仍应走上游 |
10.6.1 NuGet 官方包(非 GitHub,但必须一并纳入选型)
| 包 | 最新版本 | 目标框架 | 依赖 | 推荐度 |
|---|---|---|---|---|
Siemens.Collaboration.Net.TiaPortal.Packages.Openness | — | — | — | ⭐⭐⭐⭐⭐ 被其他三个包依赖,不得单独安装 |
Siemens.Collaboration.Net.TiaPortal.Openness.Resolver | — | — | — | ⭐⭐⭐⭐⭐ Api.Global.Openness().Initialize() 替代手写程序集解析 |
Siemens.Collaboration.Net.TiaPortal.Openness.Extensions | 21.0.1765368043 | .NET Framework 4.8 | Resolver + Packages | ⭐⭐⭐⭐⭐ 属性操作是原生 API 没有的能力;⚠️ V21 破坏性变更,跨版本必须重编译 |
Siemens.Collaboration.Net.TiaPortal.Openness.Startdrive.Extensions | 21.0.1765368573 | .NET Framework 4.8(兼容 net481) | 依赖 Extensions >= 18.0.1685432504 | ⭐⭐⭐⭐ 传动自动化必备;⚠️ 不得单独安装;⚠️ 注意 PossibleDriveObjectTypes 官方拼写少一个 i |
来源:GitHub REST API(15+ 仓库元数据)、raw README(11 篇)、raw 源码(9 个
.cs/.py)、NuGet 包页(2 个)。星标数抓取于 2026-10-04。
本章速查
- ⚠️⚠️ Python 侧头号陷阱:搜索
tia-openness-api-client会同时命中 三个 0★ fork(mgrillo75、OsamaS99、MeKo-Tech),README 与上游逐字相同。真实维护方是Repsay(52★),选错分支等于选到停更代码。 - pythonnet 环境钉死在 Python 3.7.3 + pythonnet 2.4.0(必须
pip install,conda 无效);只引Siemens.Engineering.dll会抛FileNotFoundException,必须额外引Siemens.Engineering.Contract.dll(在Bin\PublicAPI\下)。 - NuGet 官方包版本大版本 = TIA 主版本(
17.* = V17、18.* = V18、20.* = V20、21.* = V21),四个包不得单独安装(自动级联)。⚠️ V21 是硬性分水岭,跨版本必须重编译。 Api.Global.Openness().Initialize()(Resolver 包)可替代手写AssemblyResolve;但若用AssemblyResolve,业务逻辑必须放进独立方法——在Main里注册 resolver 会因 JIT 崩溃。- 官方 snippets 仓库(67★)是 API 范例金矿,但「TestFramework 仅为演示、非断言」——只能抄方法,不能当测试套件。按已装组件选
.slnf(只 Step7 用Step7ProjectSubset.slnf)。 - 软件单元的变量表必须
Concat合并——每个 Unit 下还有自己的TagTableGroup,只读software.TagTableGroup会漏掉软件单元内的变量。 - Know-How 保护块是导入的硬边界——筛选导入必须加
&& !plcBlock.IsKnowHowProtected;块保护密码需 ≥8 ≤120 且含数字/大小写/特殊字符,且必须先GetInvalidPasswordCharacters()过滤。 - 🔴 无 License = 不可商用:
Openness_examples_python(35★)、Modbus 生成器(41★)、SimaticML(12★) 均无 LICENSE 文件;GPL-3.0(CodeGeneratorOpenness166★、TiaUtilities38★)需评估传染性;TIA-Portal-CLIv2.0.0 后声明 AGPL-3.0;官方 snippets 是 Siemens Royalty-free(非 OSI,SPDX NOASSERTION)。
第11章 异常、报错与故障排查手册
第 11 章 异常、报错与故障排查手册
本章导读
本章把散落在 5 份 raw 资料中的报错、异常与陷阱归并为可检索的排查条目。核心是一张 「症状 → 根因 → 解决」大表(30 行),覆盖版本迁移、权限、安全、程序集、源文件生成、导出、编译、命名、资源释放、并发锁、第三方设备、官方工具限制共 12 类问题;随后每条给出详解小节,写清「怎么确认 + 怎么修 + 修完验证什么」。本章还给出异常处理最佳实践(EngineeringException 分类处理、EngineeringTargetInvocationException 内层异常、ex.ErrorCode 记录)、日志与诊断位置、以及超时与断线的处置原则。
⚠️ 关于错误码与 API 签名的核校声明:本章所有报错原文(含异常消息文本、错误数字)均来自抓取于 2026-10-04 的 raw 原始资料,逐字保留。但需注意:
en-blogs.md末尾「已知内容质量问题」明确列出多篇 IMD 文章存在 AI 生成痕迹与 API 幻觉(如Siemens.TiaPortal.Openness命名空间、TiaPortal.GetTiaPortalContext()、TiaPortal.GetProcess()变体、PlcBlock.ExportToFile/ImportFromDocuments参数形态)。凡引用这些来源的条目,本章正文均以 ⚠️ 标注「该来源 API 签名存疑,需对照官方手册核实」。- 官方 8 步编程流程、V21 系统手册、V19 系统手册(1692 页)为权威来源,其条目不设 ⚠️。
11.1 核心排查大表:症状 → 根因 → 解决
使用方式:先按「症状」列精确匹配或关键词匹配;命中后看「根因」列判断是否同源;再按「解决步骤」列执行。同类报错的不同根因必须区分(如
FileLoadException有 4 种根因,泛化程度不同)。
| # | 症状(报错原文 / 现象) | 根因 | 解决步骤 | 来源类别 |
|---|---|---|---|---|
| 1 | System.IO.FileLoadException: Unable to connect to TIA Portal. Please ensure that "Copy Local" is set to false for Siemens.Engineering assemblies in your application. | V19→V21 迁移期四因之一:① 残留 V19 程序集 / V19 bindingRedirect 仍在运行时解析;② 用 gacutil /i 把 V21 程序集装进 GAC;③ 缺少本地组 TIA Engineer + TIA Openness(V21 需两个组,缺组时报的是同一个泛化 FileLoadException 而非 access denied);④ 试用版/生产版混装导致 assembly hash mismatch | ① 删掉所有 Siemens.Engineering.* 引用 + app.config 里 V19 的 dependentAssembly/bindingRedirect,从 Portal V21\PublicAPI\V21\ 重新 Add Reference,设 Copy Local=False、Specific Version=True;② Clean + 删 bin\、obj\,构建后输出目录必须零个 Siemens.*.dll;③ gacutil /l Siemens.Engineering 检查、gacutil /u Siemens.Engineering,Version=<ver>,PublicKeyToken=<token> 卸载(原文强调「永远不要 GAC 注册 Siemens.Engineering 程序集」);④ compmgmt.msc 把运行用户同时加入 TIA Engineer 和 TIA Openness,注销重登;⑤ 兜底:订阅 AppDomain.CurrentDomain.AssemblyResolve 手动 LoadFrom V21 目录,必须在首次 TiaPortal.GetInstance() 之前注册 | 英文博客 ⚠️ |
| 2 | 同上 FileLoadException,但确认组已加、Copy Local 已 False、输出目录干净 | 试用版/USB 许可同样强制绑定规则——不是 license 问题 | 不要往许可方向排查,按 #1 的 ①②③⑤ 逐项排除;确认 Win10 支持(Win11 无关) | 英文博客 |
| 3 | 安全错误 Security error. Can't access this process to acquire needed information. / Security error. Owner 'xx' of this process is not member of the Windows group 'Siemens TIA Openness'. Please contact your administrator. | 当前用户不在本地组 Siemens TIA Openness(V13 SP1 Update 9 + VS 2015 + Win7 环境) | compmgmt.msc → Local Users and Groups → Groups → 双击 Siemens TIA Openness → Add → 加入 DOMAIN\username;必须注销重登或重启;必须 Build 成独立 exe(Ctrl+Shift+B)从 bin\Debug\ 或 bin\Release\ 直接运行,不能直接在 VS 调试器里跑(成功跑过一次 exe 后通常才能调试)。备选:VS 以管理员运行、Debug 切 Release、运行前关闭所有 TIA Portal 实例 | 英文博客 |
| 4 | CAx 操作无法启动,请检查是否以 'Siemens TIA Openness' 组成员身份登录 | Openness 组件安装时自动创建了本地组,但安装程序不会自动把当前账户加入其中 | 诊断命令(PowerShell):whoami /groups | findstr "Siemens",无输出即表示未加入权限组。图形界面:计算机管理控制台 → 本地用户和组 → 组 → Siemens TIA Openness → 添加用户。⚠️ Win10 家庭版需改用命令行方案;加组后必须注销/重启 | 中文社区 |
| 5 | 初始化 Siemens.Engineering.TiaPortal 类即报错(已把用户加入 Windows 10 用户组仍失败) | 「加组」不是充分条件——可能缺管理员权限、安装/组件损坏、Openness 版本不兼容、.NET Framework 问题、环境变量 PATH 错误、注册表项损坏、第三方安全软件/防火墙阻止 | 按官方最佳答案 9 条顺序排查:① 权限不足(加组后仍需以管理员身份运行程序)② 博途 V19 未正确安装/组件损坏 ③ Openness 版本不兼容(确认与 V19 匹配)④ .NET Framework 版本 ⑤ 环境变量 PATH 未指向 V19/Openness 安装路径 ⑥ 注册表项损坏或缺失 ⑦ 第三方安全软件或防火墙冲突(暂时禁用排查)⑧ 日志文件分析(博途和 Openness 日志通常在安装目录的 Logs 文件夹)⑨ 联系 Siemens 技术支持并提供错误详情与日志 | 中文社区(西门子中国找答案,V19/Win10) |
| 6 | 官方示例 108716692_TIA_PortalOpenness_Demo_V20 也无法调用,「打开项目时出现错误」(PLC 1200G2 与 1500 都不行) | 示例与 Openness 版本不匹配(TIA_PortalOpenness_Demo_V20 对应 V20);两型号都失败说明问题不在 CPU 而在 Openness 侧(用户组/权限/版本) | 换用与已装 TIA 版本匹配的官方示例;按 #3/#4/#5 排查用户组与权限 | 中文社区(西门子中国找答案,抓取到的正文只有提问、无答复) |
| 7 | TypeError: No method matches given arguments for GenerateSource: (, , ) | Python 原生 list 不兼容 .NET 泛型集合 | 必须用 System.Collections.Generic.List[T]:from System.Collections.Generic import List; blocks = List[object](); blocks.Add(block); external_source.GenerateSource(blocks, FileInfo(target_filename), GenerateOptions.Without)。⚠️ 注意该来源用 GenerateOptions.Without,官方 C# 片段用 GenerateOptions.None——枚举成员名需对照官方手册核实,跨语言枚举不可互推 | 英文博客 / github.md |
| 8 | FileNotFoundException: Could not load file or assembly 'Siemens.Engineering.HW.dll' or one of its dependencies.(V20 上) | V20 改成 fat assembly(胖程序集)——Siemens.Engineering.HW.dll / .Core.dll / .SW.dll / .Automation.API.dll 都不存在,四个命名空间全编进单个 Siemens.Engineering.dll。PublicAPI\V20\ 下 dir /b 通常只有 Siemens.Engineering.dll 和 Siemens.Engineering.Hmi.dll | csproj 改 HintPath:<Reference Include="Siemens.Engineering"><HintPath>C:\Program Files\Siemens\Automation\Portal V20\PublicAPI\V20\Siemens.Engineering.dll</HintPath><Private>False</Private></Reference>。配套修 CS0246 "The type or namespace name 'HW' could not be found"。V17~V20 strong-name 依次 17.0.0.0/18.0.0.0/19.0.0.0/20.0.0.0,只有路径字符串变 | 英文博客 |
| 9 | FileNotFoundException: Could not load file or assembly 'Siemens.Engineering.Contract, Version=1800.100.4201.1, ...'(pythonnet) | 只 AddReference 了 Siemens.Engineering.dll,漏了 Contract 程序集 | 额外手动引用 Siemens.Engineering.Contract.dll(在 Bin\PublicAPI\ 目录下,注意不在 PublicAPI\V{n}\)。⚠️ V18 两个 DLL 路径不同:Contract 在 Portal V18\Bin\PublicAPI\,主 DLL 在 Portal V18\PublicAPI\V18\ | github.md(0x42697262 笔记) |
| 10 | Inconsistent blocks cannot be exported | 块从未编译,状态不一致 | 导出前先编译:if (!mainBlock.IsConsistent) mainBlock.GetService<ICompilable>().Compile();。Python 侧对应 project.compile() 必须先于 block.export()。官方片段同样用 result?.ErrorCount == 0 判一致性 | 中文社区(博客园 8 条速查)/ github.md / en-blogs.md |
| 11 | Missing 'Namespace' identifier attribute | 手写 XML 缺 V18 命名空间 | 基于原生导出 XML 修改,不要从零手写。V18 的 LAD 命名空间固定为 http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4;⚠️ 命名空间随版本变化(另有来源记为 .../FlgNet/v1),必须以本机导出模板为准 | 中文社区(博客园 8 条速查) |
| 12 | 梯形图导出后触点显示 ??? | 变量未提前创建 / XML 引用异常 | 先建 PLC 变量表与变量再生成梯形图:if (tagTable.Tags.Find("M0_0") == null) tagTable.Tags.Create("M0_0", "Bool", "%M0.0"); | 中文社区(博客园 8 条速查) |
| 13 | 导出 XML 中无 <FlgNet> 节点 | 原块网络为空,导出结果不含网络节点 | 手动向 <NetworkSource> 注入 FlgNet 节点及其 Parts + Wires;常开触点做法是删除 <Negated Name="operand"/>(示例模板里带 <Negated> 是常闭触点) | 中文社区(博客园) |
| 14 | This function is not supported in online mode | 博途在线(连 PLCSIM 或真机)时改程序 | 用 OnlineProvider.State 判断 + GoOffline():OnlineProvider op = item.GetService<OnlineProvider>(); if (op != null && op.State != OnlineState.Offline) op.GoOffline(); | 中文社区(gitcode / 博客园) |
| 15 | 错误 1201「源文件语法错误」 | .scl 语法错(缺分号/括号/拼写) | 用 TIA Portal 打开源文件,按日志提示行号逐行检查 | 中文社区(西门子中国论坛) |
| 16 | 错误 1203「块版本不兼容」 | 源文件来自其他 TIA 版本 | 在相同或更高版本 TIA Portal 中重新执行「从块生成源」再导出 | 中文社区(西门子中国论坛) |
| 17 | 程序块显示为灰色不可编辑 | 编译未完成或存在警告 | 解决所有问题后重新生成;确认日志区显示「0 错误,0 警告」,程序块图标由灰转亮即就绪 | 中文社区(西门子中国论坛) |
| 18 | Block "FB_name" not found | 实例 DB 引用的 FB 必须已存在于项目中 | 生成实例 DB 之前先把 FB 生成到项目里。实例 DB 语法:把 FB 名放双引号、独立一行,取代 STRUCT...END_STRUCT | en-blogs.md / 中文社区 |
| 19 | S7_Optimized_Access attribute missing | 属性行必须紧跟 DATA_BLOCK 头之后 | 模板首行必须含 { S7_Optimized_Access := 'TRUE' }(优化 DB)或 'FALSE'(绝对寻址),否则与项目优化块访问设置冲突或报错 | en-blogs.md / 中文社区(鸿鹄) |
| 20 | Duplicate block name | 外部源中已存在同名块 | ① 导入用 ImportOptions.Override;② 生成前先 ExternalSources.Find(name)?.Delete() 删同名源;③ TIA 界面操作时右键源文件「从源生成块」→ 提示是否覆盖 | en-blogs.md / 中文社区 |
| 21 | 外部 SCL 源里写 DATA_BLOCK "MyDB" DB 300 被解析但DB 编号被静默忽略,TIA 分配下一个可用编号 | 已确认限制(V12 → V17 Upd4),西门子热线确认截至 V17 无原生修复 | 绝对寻址的 SCADA/HMI(如 DB300.DBD4)走此路是硬伤。四条重编号路径:① TIA Portal Addin(DB Renumberer),.addin 放 %ProgramData%\Siemens\Automation\Portal V<ver>\Addins\,重启后 Extras → Addins 出现;② Openness API(推荐) block.SetAttribute("Number", targetNumber),需 TIA V15+,引 SIOS 109798623;③ GUI 手工 Properties → General 改号(交叉引用自动更新);④ 永久解法:SCADA 改符号寻址(OPC UA 或 TIA 符号导出) | en-blogs.md |
| 22 | 所有 Siemens.Engineering 程序集必须 Copy Local=False,但本地调不通 | ❌ 常见错误修法(全部标注为错误,不得采用):① 手动复制 DLL 到输出目录(Copy Local: True 官方明确不受支持);② 用 gacutil /i 做 GAC 安装(原文强调「永远不要 GAC 注册 Siemens.Engineering 程序集」);③ 降级目标框架(V18 必须 .NET Framework 4.8,4.7.2 及以下大概率加载 DLL 失败) | 正确做法(三选一):app.config(多数场景推荐,会校验强名称)/ AssemblyResolve(copy-deploy 等场景,无法检测篡改,且必须把业务逻辑放进独立方法避免 JIT 崩溃)/ NuGet Api.Global.Openness().Initialize()。⚠️ 验证:输出目录零个 Siemens.*.dll | 官方 V19/V21 手册 5.2 / 英文博客 / 中文社区 |
| 23 | 脚本崩溃/被 kill 后项目文件被锁,下次打开强制终止进程 | 从未调用 Dispose(进程被 kill) | 终止持文件的 S7OI.exe(用 Process Explorer 查句柄),重建锁文件。根本对策:① 始终 using 包裹 TiaPortal;② 异常路径显式 project.Close(false);③ CI 脚本加超时与 finally 释放 | 中文社区(与非网 / HubWiz) |
| 24 | 项目在 TIA 设计器中已打开,脚本 Projects.Open 失败或提示「项目已被占用」 | Openness 对 .apXX 持排他写锁;设计器打开时不能跑同一项目脚本 | 关掉 TIA 里的该工程再跑脚本。官方前置条件:该工程不能同时在另一个 TIA Portal 实例中打开 | 中文社区 / 英文博客 |
| 25 | 验证权限不足:需要确认当前用户是否在 Siemens TIA Openness 组,但图形界面步骤太长 | — | PowerShell 一行验证:whoami /groups | findstr "Siemens",无输出即表示未加入权限组。⚠️ 另:域控组策略可能覆盖本地组 | 中文社区 / 英文博客 |
| 26 | 生成的梯形图/FC 逻辑不对,条件表达式结果异常 | **TiaUtilities 的 C# & / ` | 运算符优先级坑**——**C# 的&优先级高于 | `,不加括号逻辑会错**(该库代码注释明写) |
| 27 | 第三方设备(SEW Movitrac/Movidrive、SICK 编码器、FANUC 机器人)无法用 Device.CreateWithItem() / Device.Create() 从零创建 | 设备目录基于 Siemens.Plattorm.Configuration.Contracts.dll(原文拼写为 Plattorm),第三方厂商未提供 Openness 兼容的 DeviceItemTypeId GUID | 官方解法是「模板导出法」:手工配置一次 → device.GetService<IDeviceExport>()?.Export(exportFile, ExportOptions.IncludeSubmodules) 导出模板 → 脚本 tiaPortal.EngineeringInstance.CreateSegmentImport("ThirdPartyImport") 后 import.Import(templatePath) 导入 → 单独设 IP i.Properties["IPAddress"].Value = "192.168.1.100"。⚠️ 模板 XML 版本特定,升级后需重新生成;设备名必须唯一;无法通过 Openness 设置第三方安全设备的安全参数。⚠️ 该来源的 IDeviceExport / IGSDCatalog / CreateSegmentImport 类型需核对。GSDML 放置路径:C:\ProgramData\Siemens\Automation\Simatic.StationConfiguration\GSD,放完需重启 TIA Portal | 英文博客 ⚠️ |
| 28 | EngineeringException: 0x80003001(Projects.Create) | 目标目录已存在且非空 | 换新目录,或改用 Projects.Open。另:CreateWithOrder 抛 ArgumentException = MLFB 错或固件版本限定符不对,从 Industry Mall 逐字复制 | 英文博客 / HubWiz ⚠️ |
| 29 | 新建标签编译错 0x8000FFFF | 数据类型名拼写错误(如 bool 而非 Bool)——API 区分大小写 | 统一用 Bool / Int / DInt 等官方精确拼写 | 英文博客 / HubWiz ⚠️ |
| 30 | FB 导入编译错 0x80010002 | PLCopen XML 由别的 TIA 版本导出 | 用匹配版本重导,或 ImportOptions.None 接受迁移。错误码语义:0x8000xxxx 多为缺硬件配置;0x8001xxxx 指向程序内类型/实例不匹配 | 英文博客 / HubWiz ⚠️ |
| 31 | 符号地址歧义,编译报 0x8000FFFE;或 Projects.Open 抛异常 | 符号地址大小写敏感;访问了旧版本工程 | 统一大小写;旧版本工程改用 Projects.OpenWithUpgrade。DB 成员地址形如 "DB1".Motor1.Speed | 官方 V19 手册 + 英文博客 ⚠️ |
| 32 | TiaPortalException: Openness is not licensed | 缺 6AV6 671-2CA00-0AX0 或等效 bundle key | ⚠️ 与 #3 的许可问题不同:本条是产品许可缺失,需联系西门子补料号。注意官方资料也有「Openness 随 TIA Portal 提供、不需要独立 license key」(Professional/Professional 场景)的表述,两者冲突,需按实际安装介质确认 | en-blogs.md ⚠️ |
| 33 | COMException 0x80004005 | 用户组缺权限 | 管理员运行或 dcomcnfg 授权;用 whoami /groups 查。⚠️ DLL 没问题 | 英文博客 ⚠️ |
| 34 | HRESULT 0x8001010E RPC_E_SERVER_CANTUNLOAD | TIA 被另一自动化死锁 | 一个项目同时只允许一个 Openness 进程。检查是否有残留进程;CI 加 disableConcurrentBuilds | 英文博客 ⚠️ |
| 35 | FileLoadException 0x80131040 | V19 SDK 的二进制在构建路径上,而实际跑 V20 TIA | 更新 HintPath 指向 V20 | 英文博客 ⚠️ |
| 36 | COMException: Class not registered | 未安装 Openness 或版本不匹配 | 检查注册表中 Siemens.Engineering 版本 | 中文社区(鸿鹄) |
| 37 | CS0246 - The type or namespace 'Siemens' could not be found | 引用缺失或指向了别的版本目录 | 核对 HintPath 指向 PublicAPI\V<n> | 英文博客 |
| 38 | The signature is invalid / StrongNameValidationException | Defender / SmartScreen 隔离,或被第三方工具重签名 | 放行并加排除项 C:\Program Files\Siemens\Automation。⚠️ 绝不要重签 Siemens 程序集 → 只能重装 | 英文博客 ⚠️ |
| 39 | TiaPortalNotFoundException | 注册表键不存在 | 验 HKLM\SOFTWARE\Siemens\Automation\Portal\20.0 存在 | 英文博客 ⚠️ |
| 40 | BadImageFormatException | 编译平台不是 x64 | 目标平台改 x64 | 英文博客 |
| 41 | 项目编译错但报的是空符号(not supported in online mode 变体)或 CrossReferenceException | 未 PlugNew 就 GetService<IoSystem>() | 先 PlugNew 再取 IoSystem 服务。⚠️ 该来源的 Devices.CreateDeviceItem(...) / CreateAndPlugNewSubmodule(...) API 签名存疑,需对照官方手册核实 | 英文博客 ⚠️ |
| 42 | EngineeringSecurityException | 连续三次拒绝 Openness firewall 授权提示 | 连接确认框选 “Yes to all” → UAC 对话框点 “Yes” 即写入白名单。无人值守场景可直接编辑注册表:HKEY_LOCAL_MACHINE\SOFTWARE\Siemens\Automation\Openness\xx.x\Whitelist\<exe>\Entry,含 Path、DateModified、FileHash(Base64 SHA-256)。⚠️ 白名单以 exe 的 FileHash + DateModified 双重校验,重新编译或改动 exe 会导致白名单失效 | 官方手册 5.2.9 / 中文社区 / 英文博客 |
| 43 | 已加组、已加管理员,仍报权限/无法调用 | 需在 TIA Portal 里额外勾选专家设置:以管理员启动 TIA Portal →【选项】→【设置】→【常规】→【专家设置】→ 勾选「通过外部程序启用 TIA Portal 的访问」→ 完全退出并重启 TIA Portal 使注册表生效 | 宿主机(VS 调试进程或编译后 EXE)也需管理员身份。⚠️ Win10 开启 UAC 时,外部程序以普通权限调 Openness 可能启动卡住或抛无厘头 COM 异常,根因是博途 COM 组件注册需管理员权限;或给 HKEY_CLASSES_ROOT\Siemens.Engineering 当前用户「完全控制」后重启博途后台进程 | 中文社区(与非网 / CSDN) |
| 44 | 第一次实例化 Openness 时本机已开博图界面,Openness 连到一个新建的隐式实例,任务管理器可见多个 Siemens.Automation.Portal 进程,界面却看不到 | Openness 在检测到已有实例时会启动新的隐式实例 | 调试时改用 TiaPortal.GetProcesses() → process.Attach() 显式附着;或先关闭所有 TIA Portal 实例 | 中文社区(CSDN) |
| 45 | VCI 工作区导出/导入时报「功能块不存在」 | 导出到工作区的程序必须是没有嵌套其他程序块的文件;若导出块内包含嵌套的、未在当前程序块内的程序文件,导入到新工程即报错 | 把嵌套块一并导出/导入,或改走外部源(SCL)路线。⚠️ 注意这与 GenerateBlocksFromSource 的 Block "FB_name" not found 是不同机制(前者是 VCI 的限制,后者是生成时序问题) | 中文社区(博图 PLC 程序的两种导入导出方法) |
| 46 | 用 Parallel.ForEach 并行导入块时部分块未落地 / 报冲突 | 官方片段确实用了 Parallel.ForEach + HashSet<string> 并行移除,但那是导入不同文件到同一 PlcBlockComposition;若涉及创建同一 PLC 的多个对象则Openness API 非线程安全 | API 非线程安全,勿多线程共享同一 TiaPortal 实例;多项目用多进程而非多线程并行 | 中文社区(与非网) |
| 47 | ExternalSourceGroup 导入先于其依赖 DB/FB,生成失败 | 导入顺序坑:先导入的程序若其引用的 DB 还没导入会报错 | 加忽略选项:Blocks.Import(fileInfo, importOption, SWImportOptions.IgnoreMissingReferencedObjects);或严格保证DB/FB 先于依赖它的块导入 | en-blogs.md(CODESYS 应用指南) |
| 48 | CAx(AML)导入跑完没报错但也没设备;或 caxProvider 为 null | ① caxProvider 为 null = 服务不适用或项目未完全打开(服务从 Project 取,项目必须先打开);② 工程站上没装 GSDML;③ schema 校验被拒(CAx XML 按别的 schema 版本写的) | ① 先 Projects.Open 再取服务并判空;② 把 GSDML 装进该机器的 TIA 目录;③ 用同一 TIA build 重新导出参考。⚠️ CAx import 会报「部分成功」,不抛异常 ≠ 导入成功——必须记录每次导入结果 | 英文博客 |
| 49 | 驱动参数 pXXX.Bits[n] 取到错误的 bit | Bits 不是按 bit 序号索引,而是按可用 bit 值的名称升序排列。若可用值为 [pXXX.0, pXXX.2, pXXX.3],则 Bits[2] == pXXX.3,不是 pXXX.2 | 用 PossibleDriveObjectTypes 类似的名称比对思路,或读 Bits 的元数据确认映射 | advanced.md(Startdrive V15.1 手册 3.14/3.15) |
| 50 | 写驱动参数失败抛 UserException | BICO 参数写入是对象引用赋值,失败需捕获 | try { bicoSink.Value = bicoSource; } catch (UserException ex) { ... }。⚠️ UserException 属 Startdrive 命名空间(Siemens.Engineering.MC.Drives),与 STEP 7 侧的 EngineeringException 体系不同 | advanced.md |
补充:日志与诊断位置
- TIA Portal 与 Openness 日志通常在 TIA 安装目录的
Logs文件夹(西门子官方最佳答案第 8 条)。- OPC UA modelled interface Add-In 会生成 log 文件记录未纳入接口的节点。
- 官方 OPC UA Add-In 的选项包含「移除空 DB」「保留项目文件夹结构」等,会影响接口可读性——排查「客户端看不到节点」时先查该 log。
来源:官方 V19/V21 系统手册 5.2.9、中文社区(20+ 篇)、英文博客(27 篇)、github.md、advanced.md;日志位置见中文社区「初始化 TiaPortal 类报错」条(西门子官方最佳答案第 8 条)
11.2 分类详解
11.2.1 权限与安全类(#3 #4 #5 #25 #33 #36 #42 #43)
这一类是 Openness 最高频的故障来源,且症状高度泛化——同一个 FileNotFoundException / FileLoadException 可能同时对应「缺组」「GAC 注册」「版本残留」三种完全不同的根因。必须按顺序逐项排除,不要跳步。
权限检查的标准三步:
### ① 组 membership 验证(无输出 = 未加入)
whoami /groups | findstr "Siemens"
### ② TIA Portal 侧专家设置(必须在 TIA 里勾选并完全重启 TIA Portal)
### 管理员启动 TIA Portal → 选项 → 设置 → 常规 → 专家设置
### → 勾选「通过外部程序启用 TIA Portal 的访问」
### ③ 宿主机侧:VS / 编译后的 EXE 也需管理员身份
「加组不是充分条件」的四种情形(#5):
| 情形 | 验证方法 |
|---|---|
| 权限不足(加组后仍需管理员运行) | 右键 EXE →「以管理员身份运行」 |
| TIA 安装/组件损坏 | 控制面板「程序和功能」核对 TIA Portal Openness 条目与更新级别,不得标记为 patched/broken;Get-ChildItem "...\PublicAPI\V15.1\*.dll" 确认程序集清单存在(Siemens.Engineering.dll 约 6-8 MB) |
| Openness 版本不兼容 | 核对 Openness 版本与 TIA 主版本 |
| 第三方安全软件/防火墙 | 临时禁用排查;防火墙需放行 s7epatsrv.exe |
| 注册表项损坏 | 检查 HKLM\SOFTWARE\Siemens\Automation\Openness 权限、C:\Program Files\Siemens\Automation\Portal V17 NTFS 权限 |
⚠️ 域控环境特别提示:域控组策略可能覆盖本地组;Win10 家庭版不支持 Windows 用户组机制(Modbus 生成器 README 明确「家庭版操作系统不支持」,需域环境)。
11.2.2 程序集与版本迁移类(#1 #8 #9 #22 #35 #37 #38)
V19 手册的向后兼容矩阵(权威依据):
| 源版本 | 在 V19 上运行 | 备注 |
|---|---|---|
| V16 / V17 / V18 | ✅ 无需修改即可运行 | V19 交付范围包含这三版的 Siemens.Engineering.dll |
| V15.1 | ❌ 不再随 V19 交付 | 需自备 |
| V19 | ✅ | 要 V19 新特性必须换 V19 dll 重编译 |
V21 的断裂点:官方原文「for reasons of cybersecurity, the Openness API is restructured in V21」——架构重构,模块化程序集(Siemens.Engineering.Base.dll / .Step7.dll)替代单一 Siemens.Engineering.dll,且官方 NuGet Extensions V21 明确声明只兼容 V21 及以上。跨 V21 必须重编译,没有例外。
迁移 V19→V21 的五步法(顺序不可乱):
| 步骤 | 动作 | 验证点 |
|---|---|---|
| 1 | 删所有 Siemens.Engineering.* 引用 + app.config 里 V19 的 dependentAssembly / bindingRedirect;从 Portal V21\PublicAPI\V21\ 重新 Add Reference | 设 Copy Local=False、Specific Version=True |
| 2 | Clean + 删 bin\、obj\ | 构建后输出目录零个 Siemens.*.dll |
| 3 | gacutil /l Siemens.Engineering 检查;gacutil /u Siemens.Engineering,Version=<ver>,PublicKeyToken=<token> 卸载 | ⚠️ 永远不要 GAC 注册 Siemens.Engineering 程序集 |
| 4 | compmgmt.msc 同时加入 TIA Engineer + TIA Openness | 注销重登 |
| 5(兜底) | 订阅 AppDomain.CurrentDomain.AssemblyResolve 手动 LoadFrom V21 目录 | 必须在首次 TiaPortal.GetInstance() 之前注册 |
// 第 5 步兜底:手动 AssemblyResolve(必须在首次使用 TiaPortal 之前注册)
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
var name = new AssemblyName(args.Name).Name;
if (!name.StartsWith("Siemens.Engineering")) return null;
string path = $"C:\\Program Files\\Siemens\\Automation\\Portal V21\\PublicAPI\\V21\\{name}.dll";
return File.Exists(path) ? Assembly.LoadFrom(path) : null;
};
11.2.3 导出与生成类(#10 #11 #12 #13 #14 #15 #16 #17 #18 #19 #20 #21 #45 #47)
「先编译后导出」是本主题最高频的通则。三处独立来源印证:
| 来源 | 表述 |
|---|---|
Repsay USAGE.md | 导出 block 前必须先 project.compile(),否则块不一致会抛异常 |
官方 ProgramBlockSnippets | 导出前先 block?.GetService<ICompilable>().Compile(),再用 result?.ErrorCount == 0 判断一致性 |
| 博客园 8 条速查 | Inconsistent blocks cannot be exported → 块未编译 → 先 Compile() |
SCL 外部源文件的硬约束:
| 约束 | 内容 |
|---|---|
| 扩展名 | 仅 .awl(STL) / .scl / .db / .udt,其他扩展名直接抛异常 |
| 编码 | 必须是 ASCII/UTF-8 文本 |
| 文件头 | 必须保留 {Attribute 'pack_mode' := '0'}、TITLE = '...'、{ S7_Optimized_Access := 'TRUE' }、VERSION : 0.1,否则导入失败 |
S7_Optimized_Access 位置 | 属性行必须紧跟 DATA_BLOCK 头之后 |
| 重复生成 | 生成前先 ExternalSources.Find(name)?.Delete();或用 ImportOptions.Override |
GenerateBlocksFromSource 失败语义 | 默认重载失败时抛可恢复异常、把生成错误写进异常消息、并删除已生成的块/UDT;用 GenerateBlockOption.KeepOnError 重载时不抛异常、不返回块的生成结果,返回含出错块的 IList<IEngineeringObjects>。前置条件:PLC 必须离线 |
⚠️ 编码写法的资料冲突(必须本地实测):
| 来源 | 编码要求 |
|---|---|
| 中文社区(gitcode / 与非网) | 写文件用 UTF-8 BOM |
| en-blogs.md(SCL Creating Data Blocks) | .scl 纯文本 UTF-8 无 BOM |
1748477582/tia-portal-openness-mcp | SimaticML XML 用 UTF-8 + BOM(ExportBlockSourceUtf8) |
建议:把编码作为项目配置项固定下来,不要在不同脚本里混用;遇到「从源生成块」报语法错误且内容看着没问题时,第一件事就是试另一种编码。
导入顺序依赖的两种解法:
// 解法 A:忽略缺失引用(推荐,容错)
if (Path.GetExtension(filePath).Equals(".xml"))
(destination as PlcBlockGroup).Blocks.Import(
fileInfo, importOption, SWImportOptions.IgnoreMissingReferencedObjects);
// 解法 B:严格保证顺序(DB/FB 先于依赖它们的块)
11.2.4 命名与编号类(#18 #20 #21 #28 #29 #30 #31)
| 现象 | 根因 | 关键约束 |
|---|---|---|
Block "FB_name" not found | 生成实例 DB 时 FB 尚不存在 | 生成实例 DB 前 FB 必须已存在 |
S7_Optimized_Access attribute missing | 属性行位置错 | 紧跟 DATA_BLOCK 头 |
Duplicate block name | 同名块已存在 | ImportOptions.Override 或先删源 |
| DB 编号被静默忽略 | Openness 生成路径的已知限制(V12~V17 Upd4 确认) | 需 SetAttribute("Number", n) 重编号(V15+,SIOS 109798623);SCADA 绝对寻址必须永久改符号寻址 |
0x8000FFFF | 数据类型名大小写 | API 区分大小写(Bool 而非 bool) |
0x80010002 | PLCopen XML 跨版本 | 用匹配版本重导 |
0x8000FFFE | 符号地址大小写歧义 | 符号地址大小写敏感 |
0x80003001 | 目标目录已存在且非空 | 换目录或改用 Open |
梯形图触点 ??? | 变量未提前创建 | 先建变量表与变量 |
重编号代码:
// Requires: Siemens.Engineering.dll, TIA Portal V15+
// 引 SIOS 109798623
TiaPortal tia = new TiaPortal(TiaPortalMode.WithUserInterface);
Project project = tia.Projects.Open(new FileInfo(@"C:\Projects\MyProject.ap17"));
foreach (PlcBlock block in blockGroup.Blocks)
if (block.Name == "MBC_dev03_test_Write")
block.SetAttribute("Number", 8449); // ⚠️ 重编号后必须重新编译
project.Save();
⚠️
blockGroup来源未在原文中给出完整上下文;⚠️SetAttribute("Number", ...)属动态属性调用,具体可用性须按 CPU 型号与 TIA 版本对照官方手册核实。
11.2.5 资源释放、锁与并发类(#23 #24 #34 #44 #46 #42)
Openness 对象的 IDisposable 契约(所有 IEngineeringObject 都是 IDisposable):
| 规则 | 说明 |
|---|---|
| 释放顺序 | 必须按创建反序释放;先释放 Project 再释放 TiaPortal |
C# using 的坑 | using 按源顺序释放(等同 try/finally 的嵌套),但业务上需要的是反序——两者不一致时必须手写 try/finally |
Save ≠ Dispose | Save 落盘,Dispose 释放内存锁——两件事都要做 |
Dispose 语义有条件 | 以无头模式启动且无其他 Openness 客户端附着时 Dispose() 关闭 TIA Portal;以 GUI 启动或有其他客户端附着时 Dispose() 仅断开 |
| 外部关闭后调用 API | 抛 NonRecoverableException |
| 事件反订阅 | Disposed / Notification / Confirmation / Authentication 必须在 finally 中反订阅 |
| 排他锁 | 一个项目同时只允许一个 Openness 进程(RPC_E_SERVER_CANTUNLOAD) |
| 非线程安全 | API 非线程安全,勿多线程共享同一 TiaPortal 实例;多项目用多进程而非多线程并行 |
| 隐式实例 | 检测到已有 TIA 实例时会启动新的隐式实例,任务管理器可见多个 Siemens.Automation.Portal 进程 |
11.2.6 官方工具的已知限制(#21 #32 #42 + 批量工具限制)
| 工具 | 限制 | 规避 |
|---|---|---|
| TIA Portal Mass Operation Tool(109986645) | ① 仅支持 Profinet 设备;② FCPU 只有在安全程序未变更时才能用 “Software (only changes)” 下装;③ 当前不支持带密码保护的设备;④ 当前不支持把 TO 值赋值为对象参数 | 逐项确认后再排批量任务 |
| OPC UA modelled interface Add-In | ① 无法在不启用 “optimize” 模式的前提下使用;② NodeID 不能配为数字标识符;③ 嵌套实例化的 FB 被建模为 “UAObjects” 而非 “UAVariables”,其内部变量无法通过生成的接口访问 | 查 Add-In 生成的 log 文件确认哪些节点未纳入 |
| EPISD(109777633) | 官方声明只继续接受关键修复,不再新增功能,推荐新项目改用 Mass Operation Tool | 新项目用 MOT |
| SIMATIC Automation Compare Tool(109797235) | 用 CFC 和 SCL/Graph 编写的程序目前不行 | 版本控制范围排除 CFC / SCL-Graph |
| VCI 工作区 | LAD/FBD 无法版本化;导出对象仅限 OB/FB/FC/DB/PLC 数据类型/PLC 变量表;嵌套块导入会报「功能块不存在」 | LAD/FBD 走外部源或 SIMATIC SD(V20+) |
| Openness 能力硬边界 | 不能逐行编辑块内程序逻辑(LAD/FBD 网络、STL 语句);不提供读取/切换 CPU 运行模式(RUN/STOP)的 API;不支持清除强制、读诊断缓冲区、按块选择性下载过滤器;没有直接往 OB/FB Network 插入 FB 的 API;不能逐行编辑 | 需 RUN/STOP 切换走 StopModules / StartModules(绑定在下载流程内);诊断读取走 CPU 的 OPC UA 服务器或 S7 通信 |
| SIL/PL 安全 | 禁止用 Openness 生成 F-block;禁止用 Openness 执行安全系统的检查或生成验收/认可数据(安全验收只能用 STEP 7 Safety 的 safety printout 或功能测试) | 安全相关逻辑手工在 TIA 里写,Openness 只做非安全外围 |
OpennessScripter | 只支持 V13 SP1、V13 SP2、V14、V14 SP1、V15、V15.1 | V16+ 必须写 C#/PowerShell |
11.3 异常处理最佳实践
11.3.1 异常体系与分类处理
| 异常类型 | 语义 | 处置 |
|---|---|---|
EngineeringException | Openness 基础异常,基类 | 分级处理:消息含 "already exists" 可忽略;其他按业务语义 |
EngineeringNotSupportedException | 版本不支持该 API | 不要重试,属能力边界问题;记录版本号 |
EngineeringTargetInvocationException | TIA 内部错误——必须看 InnerException | 常见根因:订货号不在已加载目录(装 HSP)、槽已占用、配置无效 |
EngineeringSecurityException | 连续三次拒绝 firewall 授权 | 见 #42 |
NonRecoverableException | 外部关闭 TIA Portal 后再调用 API | 重建 TiaPortal 实例 |
CrossReferenceException | 未 PlugNew 就 GetService<IoSystem>() | 见 #41 |
TiaPortalException | 许可相关(如 Openness is not licensed) | 见 #32 |
TiaPortalNotFoundException | 注册表键不存在 | 见 #39 |
UserException | Startdrive 专用(Siemens.Engineering.MC.Drives) | 驱动参数写入失败,如 BICO 赋值 |
UserGroupException | 设备组/权限相关 | 群组操作失败 |
⚠️ 注意命名空间差异:
UserException/UserGroupException属 Startdrive 侧命名空间,与 STEP 7 侧的EngineeringException体系是两套。catch 时不要混。
11.3.2 错误码与诊断信息记录
ICompilable compilable = (ICompilable)project;
CompilerResult result = compilable.Compile();
foreach (CompilerMessage msg in result.Messages)
Console.WriteLine($"[{msg.Severity}] {msg.ErrorCode} in {msg.Path}: {msg.Description}");
if (result.State != CompilerResultState.Successful) { /* throw */ }
project.Save(); // Save 与 Dispose 是两件事:Save 落盘,Dispose 释放内存锁
错误码语义速查:
| 范围 | 含义 |
|---|---|
0x8000xxxx | 多为缺硬件配置 |
0x8001xxxx | 指向程序内类型/实例不匹配 |
0x8000FFFF | 数据类型名拼错(API 区分大小写) |
0x80010002 | PLCopen XML 来自不同 TIA 版本 |
0x80003001 | 目标目录已存在且非空 |
0x8000FFFE | 符号地址歧义(大小写敏感) |
0x80004005 | 用户组缺权限(COMException) |
0x80131040 | 程序集版本不匹配(FileLoadException) |
0x8001010E | RPC_E_SERVER_CANTUNLOAD,TIA 被另一自动化死锁 |
必须记录的诊断字段:CompilerMessage.Severity / .ErrorCode / .Path / .Description;EngineeringException.Message + .InnerException(EngineeringTargetInvocationException 必须看 InnerException);CompilerResult.State / .ErrorCount / .WarningCount / .Messages / .Warnings / .Errors。
11.3.3 预/post 委托:不下处理就中止下载
DownloadProvider.Download(...) 的 pre / post delegate 必须处理所有遇到的配置对话框,否则中止下载。V21 中有 43 种具体的 Download.Configurations 提示类型(V20 为 42 种),涵盖 UserManagementDownload、AlarmTextLibrariesDownload、DownloadCertificate 等,处理方式各不相同(枚举选择 / Checked / SetPassword)。
官方推荐的类型分派 + 早返回模式(Startdrive 场景):
using Siemens.Engineering.Download;
using Siemens.Engineering.Online;
static void PreDownload(DownloadConfiguration configuration)
{
Console.WriteLine(configuration.Message);
if (configuration is StartDriveDownloadCheckConfiguration sdcc) { sdcc.Checked = true; return; }
if (configuration is DownloadPasswordConfiguration pw)
{
SecureString s = new SecureString();
foreach (var ch in "password") s.AppendChar(ch);
pw.SetPassword(s); return;
}
if (configuration is DownloadSelectionConfiguration sel) { sel.SelectedIndex = 0; return; }
}
模式要点:任一命中即
return(不再往下判断)。漏掉任何一个类型分支,遇到该对话框时下载会中止。
11.3.4 不要吞异常
中文社区总结的三条硬规则:
- 不要吞异常——异常路径显式
project.Close(false)并记录ErrorCode。 EngineeringException且 message 含"already exists"可忽略(幂等重跑的必要条件)。- API 非线程安全,勿多线程共享同一 TiaPortal 实例。
官方 snippets 暴露的一个真实痛点值得注意:BaseClass.cs 里 [OneTimeTearDown] 的 Project?.Close()、杀进程、_tempProjectDirectory.Delete(true) 三步默认都被注释掉——因为进程强杀会留下 TIA 残留状态,临时目录删除也常因文件占用失败。这不是偷懒,是实测后的无奈;自建框架时要有心理准备。
11.4 日志与诊断
| 来源 | 位置 / 手段 |
|---|---|
| TIA Portal 与 Openness 日志 | TIA 安装目录下的 Logs 文件夹(西门子官方最佳答案第 8 条) |
| OPC UA modelled interface Add-In | 生成的 log 文件记录未纳入接口的节点 |
| 编译诊断 | CompilerResult.Messages → 转 JSON/XML/JUNIT |
| 硬件目录核对 | HardwareCatalog.Find("") 遍历 TypeIdentifier / CatalogPath |
| 交叉引用分析 | db.GetService<CrossReferenceService>().GetCrossReferences(CrossReferenceFilter.AllObjects).Sources |
| 引用完整性 | 程序块图标由灰转亮即就绪;日志区「0 错误,0 警告」 |
| 变量/数据一致性 | CAx import 后必须再 export 回来与源模板 diff——任何意外差异说明某字段被静默默认了 |
| 进程与锁 | Process Explorer 查 S7OI.exe 句柄;任务管理器查 Siemens.Automation.Portal 进程数 |
11.5 断线、超时与挂起
| 现象 | 根因 | 处置 |
|---|---|---|
| Openness 调用无限挂起 | TIA Portal 弹出模态对话框(保存、兼容性、库冲突) | CI 场景务必用 WithoutUserInterface;WithUserInterface 场景用 Confirmation 事件处理 |
Confirmation 事件抛异常 | e.Result 被设成 "Yes" / "YesToAll" / "No" 之外的值 | 只能设这三个值 |
| 首次连接被拒三次 | 连续拒绝 firewall 授权 | 抛 EngineeringSecurityException;选 “Yes to all” 或直接写注册表白名单 |
Attach() 后程序阻塞 | 博途未授权——切到博途点「是」 | CI 用无头 + 预置白名单 |
| 无头模式下启动卡住 / 抛无厘头 COM 异常 | Win10 UAC 下博途 COM 组件注册需管理员权限 | VS / EXE 以管理员运行;或给 HKEY_CLASSES_ROOT\Siemens.Engineering 当前用户完全控制后重启博途后台进程 |
| 无头模式下 HSP 缺失导致挂死 | 无头模式不会弹 GUI,但对该阻塞对话框更敏感 | 缺 HSP 的驱动组态应前置预检(扫 HardwareCatalog 确认 TypeIdentifier 存在再创建) |
OnlineProvider 状态异常 | 设备在线/离线状态与预期不符 | OnlineProvider.State 是判断「是否已在线」的唯一受支持途径;GetService<OnlineProvider>() 可能返回 null(非 CPU 设备项无此服务),必须判空 |
| 下载无响应 | 在线 IP 与离线组态 IP 不一致 | 用 ConfigurationAddressComposition.Create("10.119.57.223") 构造 ConfigurationAddress 传入,否则会下到错误设备 |
| TIA 启动慢导致调用失败 | 进程可能尚未准备响应服务请求 | 官方示例用 Thread.Sleep(TimeNeededToStartUp) 后再连;或用常驻预热实例(见第 9.6.2 节,340s→<1s) |
| RAM→ROM 备份抛异常 | onlineDfi 与 saveParametrization 都可能为 null;且前置条件:CU 必须已在线 | 先判空并检查 CU 在线状态 |
超时经验值参考:Comquent 的 Jenkinsfile 用 timeout(time: 60, unit: 'MINUTES') + PLCSIM_TIMEOUT = '300'(秒)。
本章速查
FileLoadException: "Copy Local" is set to false有 4 种根因(V19 残留程序集 / GAC 注册 / 缺TIA Engineer+TIA Openness两个组 / 试用与正式混装)——V21 缺组时报的是同一个泛化异常而非 access denied,这是最容易误判的地方。- 验证权限只需一行:
whoami /groups | findstr "Siemens",无输出即未加入权限组;但「加组」不是充分条件——还需管理员运行、TIA 侧勾选专家设置「通过外部程序启用 TIA Portal 的访问」、注销重登。 - 三个「必须 False / 绝对不要」:
Copy Local=False(True官方明确不支持)、永远不要 GAC 注册 Siemens 程序集、绝不要重签 Siemens 程序集(只能重装)。降级目标框架也是错误修法。 - 「先编译后导出」是铁律——
Inconsistent blocks cannot be exported的唯一解是GetService<ICompilable>().Compile();Repsay与官方 snippets 双处印证。 - 生成实例 DB 前 FB 必须已存在,否则
Block "FB_name" not found;S7_Optimized_Access属性行必须紧跟DATA_BLOCK头之后;导入可用SWImportOptions.IgnoreMissingReferencedObjects忽略顺序依赖。 - ⚠️ 外部 SCL 源里
DATA_BLOCK "MyDB" DB 300的 DB 编号会被静默忽略(V12~V17 Upd4 确认,热线称 V17 前无修复)——SCADA 走绝对寻址(DB300.DBD4)时必须改用SetAttribute("Number", n)重编号,并永久改符号寻址。 - S7 编程语言对 Openness 的三道硬限制:LAD/FBD 无法导出为源、CFC 与 SCL/Graph 程序 Automation Compare Tool 比不了、不能逐行编辑块内程序逻辑——版本控制与批量修改都绕不开这三条。
- ⚠️ 两条铁律不可越:「不能把导入成功等同于工程正确」;「禁止用 Openness 生成 F-block 或执行安全系统检查」——安全逻辑必须手工写,Openness 只做非安全外围。
- 排他锁是 CI 的头号敌人:一个项目同时只允许一个 Openness 进程(否则
0x8001010E RPC_E_SERVER_CANTUNLOAD);设计器打开时脚本打不开工程;崩溃后残留S7OI.exe句柄要用 Process Explorer 强杀。 TiaUtilities的&/|优先级坑是库级陷阱——C# 的&优先级高于|,用该库拼梯形图逻辑表达式时必须手动加括号。
第12章 工程化最佳实践与实战路线
第 12 章 工程化最佳实践与实战路线
本章导读
本章把 5 份 raw 资料中的实战教训汇总成面向生产的工程化建议,每一条都有明确依据(来源类别在每节末尾标注)。内容覆盖十个主题:资源释放契约与 IDisposable 顺序、项目文件锁与并发模型、进程模式选择(CI 用 WithoutUserInterface、调试用 WithUserInterface)、性能优化(含中文社区「千条变量逐个 Tags.Create() 超 10 分钟」这条硬教训及其两种解法)、错误处理与 pre/post delegate、MLFB 订货号空格陷阱、类型化库与接口版本兼容性、S7_Optimized_Access 取舍(绝对寻址必须 FALSE)、源文件规范、项目模板与代码组织、安全、测试与验证。本章提供三段完整可用的代码模板(资源释放、批量优化的两种写法、错误处理)、一张选型决策树(ASCII + Mermaid 双版)、以及一张「从零到生产」的 8 步落地路线表。章末的「本章速查」把不可违反的红线单列。
来源声明:本章每节末尾标注来源类别。所有 API 名称、方法签名、行为描述均来自 raw 原始资料;凡
en-blogs.md「已知内容质量问题」中列出的来源(IMD 系列的部分 API 命名),本章已按要求以 ⚠️ 标注「该来源 API 签名存疑,需对照官方手册核实」。
12.1 资源释放:IDisposable 契约
12.1.1 三条不可违反的规则
| 规则 | 内容 | 依据 |
|---|---|---|
| ① 先释放 Project,再释放 TiaPortal | 所有 Openness 对象实现 IDisposable,必须按创建反序释放 | 中文社区(en-blogs Project Bases 篇) |
② C# using 按源顺序释放 | using 等价于 try/finally 的嵌套结构,因此按源顺序释放。当业务需要「反序」时,using 表达不了,必须手写 try/finally | 语言语义 + 官方 UnifiedOpennessConnector 要求 using 的反例 |
③ Save ≠ Dispose | Save 落盘,Dispose 释放内存锁——两件事都要做 | 英文博客 |
Dispose 语义有条件(官方 V21 手册 5.2.8):
| 启动方式 | 有其他 Openness 客户端附着 | Dispose() 行为 |
|---|---|---|
| 无头模式启动 | 否 | 关闭 TIA Portal |
| 无头模式启动 | 是 | 断开 |
| GUI 启动 | — | 仅断开 |
外部关闭 TIA Portal 后再调用 API 抛
NonRecoverableException。
12.1.2 完整代码模板:资源释放骨架
using System;
using System.IO;
using System.Linq;
using Siemens.Engineering;
using Siemens.Engineering.Compiler;
using Siemens.Engineering.HW.Features;
using Siemens.Engineering.SW;
namespace MyTiaTool
{
/// <summary>
/// Openness 资源释放骨架。
/// 关键点:
/// 1) 事件必须在 finally 中反订阅
/// 2) Project 先于 TiaPortal 释放(反序)
/// 3) Save 与 Dispose 是两件事
/// 4) 附加工厂白名单,保证重复运行幂等
/// </summary>
public static class TiaRunner
{
public static void Run(string projectPath, Action<Project> business)
{
// ① 先注册 AssemblyResolve(若用此方案),必须早于任何 TiaPortal 类型使用
AppDomain.CurrentDomain.AssemblyResolve += ResolveSiemensEngineering;
TiaPortal tiaPortal = null;
Project project = null;
try
{
tiaPortal = new TiaPortal(TiaPortalMode.WithoutUserInterface);
tiaPortal.Notification += OnNotification;
tiaPortal.Confirmation += OnConfirmation;
project = tiaPortal.Projects.Open(new FileInfo(projectPath));
// ② 业务逻辑
business(project);
// ③ 编译门禁:零错误才允许继续
var compilable = (ICompilable)project;
CompilerResult result = compilable.Compile();
foreach (CompilerMessage msg in result.Messages)
Console.WriteLine($"[{msg.Severity}] {msg.ErrorCode} in {msg.Path}: {msg.Description}");
if (result.State != CompilerResultState.Successful)
throw new InvalidOperationException(
$"编译未通过:ErrorCount={result.ErrorCount}, WarningCount={result.WarningCount}");
project.Save(); // ④ Save 落盘
}
catch (EngineeringException ex)
{
// ⑤ 分类处理:already exists 可忽略(保证重跑幂等)
if (!ex.Message.Contains("already exists"))
{
Console.Error.WriteLine($"[EngineeringException] {ex.Message}");
// EngineeringTargetInvocationException 必须看 InnerException
if (ex.InnerException != null)
Console.Error.WriteLine($" Inner: {ex.InnerException.GetType().Name}: {ex.InnerException.Message}");
throw;
}
}
finally
{
// ⑥ 事件必须在 finally 中反订阅
if (tiaPortal != null)
{
tiaPortal.Notification -= OnNotification;
tiaPortal.Confirmation -= OnConfirmation;
}
// ⑦ 反序释放:先 Project 再 TiaPortal
// C# using 是源顺序,故此处手写 try/finally
if (project != null)
{
try { project.Close(); } catch (NonRecoverableException) { /* 已关闭 */ }
((IDisposable)project).Dispose();
}
if (tiaPortal != null)
{
try { ((IDisposable)tiaPortal).Dispose(); } catch (NonRecoverableException) { }
}
}
}
private static void OnNotification(object sender,
Siemens.Engineering.NotificationEventArgs e)
{
Console.WriteLine($"[Notify] {e.Caption} / {e.Text} (ID={e.MessageID})");
}
private static void OnConfirmation(object sender,
Siemens.Engineering.ConfirmationEventArgs e)
{
// ⚠️ 只能设 "Yes" / "YesToAll" / "No",其他值直接抛异常
e.Result = "Yes";
}
private const string SubKeyName = @"SOFTWARE\Siemens\Automation\Openness";
private static Assembly ResolveSiemensEngineering(object sender, ResolveEventArgs args)
{
var assemblyName = new AssemblyName(args.Name);
if (!assemblyName.Name.StartsWith("Siemens.Engineering")) return null;
using var regBase = Microsoft.Win32.RegistryKey.OpenBaseKey(
Microsoft.Win32.RegistryHive.LocalMachine,
Microsoft.Win32.RegistryView.Registry64);
using var openness = regBase.OpenSubKey(SubKeyName);
if (openness == null) return null;
foreach (var verName in openness.GetSubKeyNames())
{
using var verKey = openness.OpenSubKey(verName);
var path = verKey?.OpenSubKey("PublicAPI")
?.GetValue(assemblyName.Name + ".dll") as string;
if (!string.IsNullOrEmpty(path) && File.Exists(path))
return Assembly.LoadFrom(path);
}
// ⚠️ 必须返回 null 而不是抛异常,否则整个 AppDomain 解析链会崩
return null;
}
}
}
配套的官方单类基座:若不想自己写骨架,
tia-portal-applications/tia-portal-openness-unified-library(13★,Other 许可)提供单类UnifiedOpennessConnector(IDisposable),README 明确要求用using声明式确保释放。集成方式是直接复制UnifiedOpennessLibrary.dll加为引用(非 NuGet)。
来源:官方 V21 手册 5.2.8、english blogs(Project Bases 篇)、中文社区(与非网)、github.md(Unified Library 条)
12.2 项目文件锁与并发
| 事实 | 影响 | 应对 |
|---|---|---|
Openness 对 .apXX 持排他写锁 | 同一项目同时只允许一个 Openness 进程;否则 0x8001010E RPC_E_SERVER_CANTUNLOAD | CI 加 disableConcurrentBuilds() / parallel: 1 |
| 设计器打开时不能跑脚本 | 项目在 TIA 里开着时脚本报「项目已被占用」 | 跑脚本前关掉 TIA 中的该工程;或改用 GetProcesses() + Attach() 附着到已有实例 |
Projects.Open 前置条件 | 该工程不能同时在另一个 TIA Portal 实例中打开 | 同上 |
| API 非线程安全 | 勿多线程共享同一 TiaPortal 实例 | 多项目用多进程而非多线程并行 |
| 隐式实例 | 检测到已有 TIA 实例时会启动新的隐式实例,界面互不可见 | 调试时用 GetProcesses() + Attach() |
| 崩溃残留锁 | 下次打开强制终止进程 | 始终 using / try-finally;CI 加超时;用 Process Explorer 查 S7OI.exe 句柄 |
| V18+ 多用户 | ProjectServer.OpenProject / SaveProject 维护单一工程文件 + 块级 check-out/check-in | 并行脚本须自行串行化 check-out/check-in(排他写锁由 server 强制) |
来源:官方 V19/V21 手册、中文社区、英文博客(Project Bases / Password Rotation 篇)
12.3 进程模型选择
| 场景 | 模式 | 理由 / 约束 |
|---|---|---|
| CI 流水线全部阶段 | WithoutUserInterface | TIA 弹模态对话框(保存、兼容性、库冲突)时 Openness 调用会无限挂起;⚠️ 但无头模式对 HSP 阻塞对话框更敏感,缺 HSP 的驱动组态应前置预检 |
| 本地调试、需要看界面 | WithUserInterface | 能处理 Confirmation 事件 |
| 复用已有实例(最省时间) | TiaPortal.GetProcesses() → process.Attach() | 官方 BaseClass 片段的标准做法;⚠️ Attach 后可能阻塞,需在博途点「是」授权 |
| 官方 snippets 批量跑测试 | WithUserInterface + Retrieve 到 Path.GetTempPath() + Guid.NewGuid() | 避免污染原归档;⚠️ 官方自己的 [OneTimeTearDown] 清理代码是注释掉的 |
事件模型(四个事件,必须在 finally 中反订阅):
| 事件 | 触发时机 | 关键点 |
|---|---|---|
Disposed | TIA Portal 在连接期间被关闭 | — |
Notification | 仅需确认的提示 | 属性含 Caption / DetailText / Icon / IsHandled / MessageID / ServiceID / Text |
Confirmation | 需确认的对话框 | e.Result 只能设 "Yes" / "YesToAll" / "No",其他值抛异常 |
Authentication | V17+,UMAC 保护工程 | 可在打开受保护工程时以程序方式提供认证,替代传凭据的 UmacDelegate |
来源:官方 V21 手册 5.2.8/5.2.9、github.md(BaseClass.cs)、中文社区
12.4 性能:批量操作的代价与两种解法
12.4.1 那条必须记住的教训
中文社区实测数据:批量建变量时,直接 Blocks.Create() 逐个添加程序块极慢;用 XML Import 而非逐个 Tags.Create()——千条变量逐个创建可超 10 分钟。
配套的完整性能优化表(原文):
| 优化项 | 做法 |
|---|---|
| 批量建变量 | 用 XML Import 而非逐个 Tags.Create()(千条变量逐个创建可超 10 分钟) |
| 属性读取 | 结果缓存到本地 |
| 保存 | 批量操作完成后再统一 Save() |
| 进程模式 | 批量处理用 WithoutUserInterface |
| 并行 | 多项目用多进程而非多线程并行 |
| 文件组织 | 需自动生成的外设尽量放在一起,文件越少导入越快 |
| 模板规划 | 中小项目可在模板里规划 OP10~OP200 目录结构再按需删,极大节省导入时间;模板主体结构尽量不动,只改工站标号 |
官方收益数据(西门子官方统计):首次执行 vs 重复执行对比,Openness 自动化把单次项目生成从 180 分钟降到 75 分钟,总耗时 160 分钟、其中 Openness 占 75 分钟(变化率 -80%)。
⚠️ 另一条容易忽略的开销:文件越少导入越快——大量小 XML 文件的导入开销远高于一个大文件。
12.4.2 解法一:SimaticML / XML 导入(推荐路线)
为什么这是首选:Tags.Create() 是「一次一个 IPC 往返」,XML Import 是「一次批量」。
// 变量表:先导出 → 改 XML → 导回(官方 TagTableSnippets 的标准套路)
var myTagTable = plcSoftware?.TagTableGroup.TagTables
.FirstOrDefault(x => x.Name == "DemocaseAppTagTable");
var tempPath = Path.Combine(Path.GetTempPath(), "DemocaseAppTagTable_export.xml");
_lastTempPath = tempPath;
myTagTable?.Export(new FileInfo(tempPath), ExportOptions.WithDefaults);
var xdoc = XDocument.Parse(File.ReadAllText(tempPath));
var plcTags = xdoc.Descendants("SW.Tags.PlcTag").ToList();
var nameElement = plcTags.FirstOrDefault()?.Element("AttributeList")?.Element("Name");
if (nameElement != null) nameElement.Value = "NewTagName";
xdoc.Save(tempPath);
plcSoftware?.TagTableGroup.TagTables?.Import(new FileInfo(tempPath), ImportOptions.Override);
XML 元素名是关键坑点:用 xdoc.Descendants("SW.Tags.PlcTag") 定位变量、Element("AttributeList")?.Element("Name") 定位名称。这套命名空间前缀必须以实际导出文件为准,换版本时不要硬编码。
⚠️ V17+ 多软件单元必须 Concat(否则漏变量):
// 软件单元(Software Unit)级别的变量表汇总 —— V17+ 多软件单元工程的必要操作
using Siemens.Engineering.SW.Units;
if (plcDevice.DeviceItems.Single(x => x.Classification == DeviceItemClassifications.CPU)
.GetService<SoftwareContainer>().Software is not PlcSoftware software)
throw new InvalidOperationException("Device does not contain any software instance (null).");
var tagTables = software.TagTableGroup.AllTagTables(); // 主块组下
var units = software.GetService<PlcUnitProvider>().UnitGroup.Units; // 软件单元下
var unitTagTables = units.SelectMany(x => x.TagTableGroup.AllTagTables()); // ⚠️ 别漏
var allTags = tagTables.Concat(unitTagTables).SelectMany(x => x.Tags).ToList();
⚠️ 软件单元变量表是官方片段里被明确指出的坑:变量表不只在 software.TagTableGroup,每个 Unit 下还有自己的 TagTableGroup,必须用 Concat 合并。
12.4.3 解法二:SCL 外部源批量生成(对程序块更有效)
程序块(FB/FC/OB/DB)的批量生成不走 XML,走 SCL 外部源——这是官方与社区一致的主流做法。
// 写法 A:保守型 —— 先删同名源,再生成,失败保留现场
var oldSource = plc.ExternalSourceGroup.ExternalSources.Find(blockName);
oldSource?.Delete(); // ⚠️ 必须先删,否则 Duplicate block name
PlcExternalSource source = plc.ExternalSourceGroup.ExternalSources
.CreateFromFile(blockName, sclFile);
var result = source.GenerateBlocksFromSource(GenerateBlockOption.KeepOnError);
if (result != null && result.Count > 0)
foreach (var bad in result)
Console.Error.WriteLine($"[生成失败] {bad.Name}");
project.Save();
两种 GenerateBlockOption 的语义差异(关键):
| 调用方式 | 失败时行为 | 返回值 | 适用 |
|---|---|---|---|
GenerateBlocksFromSource() 默认重载 | 抛可恢复异常,把生成错误写进异常消息,并删除已生成的块/UDT | — | 生产:失败要立刻停下 |
GenerateBlocksFromSource(GenerateBlockOption.KeepOnError) | 不抛异常 | 返回含出错块的 IList<IEngineeringObjects> | 开发/批量:一次跑完看全部错误 |
前置条件(两者都要求):PLC 必须离线。
写法 B:批量型 —— 一个大源文件承载多个块
// 写法 B:把多个块写进一个 .scl,文件越少导入越快
using (var writer = new StreamWriter(sclPath, false, new UTF8Encoding(false))) // ⚠️ UTF-8 无 BOM
{
foreach (var spec in specs) // specs: List<BlockSpec>,含 Name / Number / Body
{
writer.WriteLine($"FUNCTION_BLOCK \"{spec.Name}\"");
writer.WriteLine("TITLE = \"\"");
writer.WriteLine("VERSION : 0.1");
writer.WriteLine("VAR_INPUT");
foreach (var p in spec.Inputs)
writer.WriteLine($" {p.Name} : {p.DataType};");
writer.WriteLine("END_VAR");
writer.WriteLine(spec.Body);
writer.WriteLine("END_FUNCTION_BLOCK");
writer.WriteLine();
}
}
var old = plc.ExternalSourceGroup.ExternalSources.Find("BatchGenerated");
old?.Delete();
var src = plc.ExternalSourceGroup.ExternalSources.CreateFromFile("BatchGenerated", sclPath);
src.GenerateBlocksFromSource(GenerateBlockOption.KeepOnError);
⚠️ 编码策略必须固定(资料有冲突,见第 11 章 #15 相关讨论):本代码用 UTF-8 无 BOM(new UTF8Encoding(false)),但部分中文资料要求 UTF-8 BOM。建议做成配置项并固定,不要在不同脚本里混用。
来源:中文社区(与非网性能优化表、gitcode SCL 生成路线)、github.md(TagTableSnippets / PlcTagTableSnippets)、官方 V19 手册(
GenerateBlocksFromSource错误语义)、官方 V21 手册 5.11 节(Openness 收益统计)
12.5 错误处理
12.5.1 完整代码模板:分级错误处理 + 编译门禁
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Siemens.Engineering;
using Siemens.Engineering.Compiler;
using Siemens.Engineering.HW;
using Siemens.Engineering.HW.Features;
using Siemens.Engineering.SW;
using Siemens.Engineering.SW.Blocks;
using Siemens.Engineering.SW.ExternalSources;
using Siemens.Engineering.SW.Tags;
namespace MyTiaTool
{
public static class GuardedGenerator
{
public sealed record CompileResult(
bool Success,
int ErrorCount,
int WarningCount,
IReadOnlyList<string> Messages);
public static CompileResult GenerateAndVerify(PlcSoftware plc, string projectPath)
{
// ── ① 不要吞异常:分级捕获,每级对应不同处置 ──────────────
try
{
GenerateBlocks(plc);
// ── ② 编译门禁:零错误才允许 Save ──────────────────────
var compilable = plc.GetService<ICompilable>();
var result = compilable.Compile();
var lines = result.Messages
.Select(m => $"[{m.Severity}] {m.ErrorCode} in {m.Path}: {m.Description}")
.ToList();
var ok = result.State == CompilerResultState.Successful
&& result.ErrorCount == 0;
if (!ok)
{
foreach (var l in lines) Console.Error.WriteLine(l);
// ⚠️ 错误码语义:0x8000xxxx 缺硬件配置;0x8001xxxx 类型/实例不匹配
return new CompileResult(false, result.ErrorCount, result.WarningCount, lines);
}
return new CompileResult(true, 0, result.WarningCount, lines);
}
// ── ③ 版本不支持该 API:不要重试,属能力边界 ──────────────
catch (EngineeringNotSupportedException ex)
{
Console.Error.WriteLine($"[不支持] 当前 TIA 版本不支持该 API:{ex.Message}");
throw;
}
// ── ④ TIA 内部错误:必须看 InnerException ────────────────
catch (EngineeringTargetInvocationException ex)
{
Console.Error.WriteLine($"[TIA 内部错误] {ex.Message}");
if (ex.InnerException != null)
Console.Error.WriteLine($" Inner → {ex.InnerException.GetType().Name}: "
+ $"{ex.InnerException.Message}");
// 常见根因:订货号不在已加载目录(装 HSP)、槽已占用、配置无效
throw;
}
// ── ⑤ 幂等:already exists 可忽略 ──────────────────────
catch (EngineeringException ex) when (ex.Message.Contains("already exists"))
{
Console.WriteLine("[跳过] 对象已存在,视为 no-op(保证重跑幂等)");
return new CompileResult(true, 0, 0, Array.Empty<string>());
}
// ── ⑥ 落基类:记录 ErrorCode 后继续抛 ────────────────────
catch (EngineeringException ex)
{
Console.Error.WriteLine($"[EngineeringException] {ex.Message}");
throw;
}
// ── ⑦ TIA 被外部关闭 ───────────────────────────────────
catch (NonRecoverableException ex)
{
Console.Error.WriteLine("[不可恢复] TIA Portal 已被外部关闭,需重建实例");
throw;
}
}
private static void GenerateBlocks(PlcSoftware plc)
{
foreach (var (name, path) in PlannedSources)
{
// 外部源只支持 .awl/.scl/.db/.udt,其他扩展名直接抛异常
var old = plc.ExternalSourceGroup.ExternalSources.Find(name);
old?.Delete();
PlcExternalSource src = plc.ExternalSourceGroup.ExternalSources
.CreateFromFile(name, path);
// KeepOnError:不抛异常,返回含出错块的 IList<IEngineeringObjects>
var bad = src.GenerateBlocksFromSource(GenerateBlockOption.KeepOnError);
if (bad != null && bad.Count > 0)
Console.Error.WriteLine($"[生成错误] {name}: {bad.Count} 个块未生成");
}
}
private static readonly (string Name, string Path)[] PlannedSources = Array.Empty<(string, string)>();
}
}
12.5.2 pre / post delegate 模板
pre / post delegate 必须处理所有遇到的配置对话框,否则中止下载。V21 有 43 种具体提示类型(V20 为 42 种),处理方式不同(枚举选择 / Checked / SetPassword)。
using Siemens.Engineering.Download;
// 官方推荐的「as 分派 + 早返回」模式:任一命中即 return,不继续往下判断
static void PreDownload(DownloadConfiguration configuration)
{
Console.WriteLine(configuration.Message);
if (configuration is StartDriveDownloadCheckConfiguration sdcc)
{ sdcc.Checked = true; return; }
if (configuration is DownloadPasswordConfiguration pw)
{
SecureString s = new SecureString();
foreach (var ch in "password") s.AppendChar(ch);
pw.SetPassword(s);
return;
}
if (configuration is DownloadSelectionConfiguration sel)
{ sel.SelectedIndex = 0; return; }
// 43 种类型逐一补齐;漏一个遇到该对话框即中止下载
}
⚠️
StartDriveDownloadCheckConfiguration属 Startdrive 命名空间(Siemens.Engineering.MC.Drives),DownloadPasswordConfiguration/DownloadSelectionConfiguration属Siemens.Engineering.Download。跨命名空间时需补 using。
**下载前的 IP 校验(见 12.10 节)**应在 pre 之前完成,不要放进 delegate——delegate 里抛异常同样会中止下载。
来源:官方 V21 手册 5.11 节与下载章节、advanced.md(Startdrive 下载专题)、中文社区(与非网异常分类表)
12.6 MLFB 订货号:空格陷阱
这是最容易被低估的一个坑,且报错信息完全不指向根因。
| 事实 | 说明 |
|---|---|
| 订货号格式必须与硬件目录完全一致,包括空格位置 | S7-1200 CPU 1214C 正确格式 "6ES7 214-1AG40-0XB0";错误写法 "6ES72141AG400XB0"(去掉空格)会抛 EngineeringException 提示设备未找到 |
| 建议校验正则 | Regex.IsMatch(orderNumber, @"\s")——订货号必须含空格 |
| 最佳来源是硬件目录 | 用 HardwareCatalog.Find("") 遍历,打印 TypeIdentifier / CatalogPath,直接复制(TypeIdentifier 形如 OrderNumber:6ES7 515-2AM01-0AB0/V2.6) |
CreateWithOrder 抛 ArgumentException | = MLFB 拼错或固件版本限定符不对 → 从 Industry Mall 逐字复制 |
| S120 等系列需第四段 | 格式 @"OrderNumber:<mlfb>/<FirmwareVersion>/",S120/S150/MV/G130/G150 还需 AdditionalTypeIdentifier(空串 / S120 / S150 / MV / G130 / G150) |
| GSDML 缺失的报错 | EngineeringTargetInvocationException = 订货号不在已加载目录(装 HSP) |
最佳实践:把订货号放进 catalog map,TIA 升级时 review。(来自英文博客的工程建议)
来源:中文社区(与非网 MLFB 格式校验、CSDN 订货号格式必须含空格)、en-blogs.md(
CreateWithOrderArgumentException)、advanced.md(Startdrive TypeIdentifier 格式)、github.md(HardwareCatalogSnippets)
12.7 类型化库与接口版本兼容性
12.7.1 ⚠️ 改 FB 接口的破坏性
改 FB 接口会破坏所有调用点与实例 DB。 必须先做全局查找替换与影响面分析,再动手改。
影响面分析的唯一受支持手段:
// 交叉引用:谁引用了这个 DB —— 改名/改接口前必做
var db = plcSoftware?.BlockGroup.Blocks.First(x => x.Name == "Axis_red_DB");
var crossReferenceService = db?.GetService<CrossReferenceService>();
var sources = crossReferenceService?
.GetCrossReferences(CrossReferenceFilter.AllObjects).Sources.ToList();
foreach (var s in sources)
Console.WriteLine($"{s.Name} → {s.Path}");
CrossReferenceFilter 是枚举,示例用 AllObjects;另有按类型过滤的用法可用于精准定位调用点。
12.7.2 跨工程共享 FB 的正确做法
| 方案 | 版本化 | 接口漂移暴露时机 | 推荐度 |
|---|---|---|---|
typed global library(.alXX) | 类型版本化 | 编译期(而非运行期) | ⭐⭐⭐⭐⭐ 跨机队共享 FB 的正解 |
.scl 外部源工作流 | 无 | 编译期 | ⭐⭐⭐⭐ 产出适合 Git review 的人类可 diff 产物 |
| 两个 TIA 实例间拖拽 | — | ❌ 禁止:会导致块号重复、KWP 密码丢失、实例 DB 不一致 | ❌ 改用引用项目(Ctrl+9) |
typed library 的三条注意事项:
- 库类型不记录目标 CPU 固件版本——V18 上能编的块在 V16 SP1 上可能因 attribute 变化失败
- 同一大版本族(如 V18 ↔ V20)可打开
- 库存类型版本与接口指纹,接口漂移会在编译期暴露
KWP(Know-How 保护)密码管理:KWP 密码集中到项目级 secret manager——无密码导出的 KWP 块导入后连原作者都打不开。
为每个块记录 TIA Portal 与 CPU 固件版本——这是 typed library 缺失的元信息,必须自己维护。
12.7.3 两条硬性禁止
- 禁止在同版本两个实例间拖拽块(会导致块号重复、KWP 密码丢失、实例 DB 不一致)
- 禁止用 Openness 生成 F-block(安全)——安全逻辑手工在 TIA 里写,Openness 只做非安全外围。HubWiz 明确建议:SIL/PL 安全相关逻辑不要用 Openness 生成。
来源:en-blogs.md(Exporting and Importing FB 篇)、github.md(DataBlockSnippets 交叉引用)、中文社区(HubWiz 安全代码建议)、官方 V19 手册(
ProjectBase长期稳定性)
12.8 S7_Optimized_Access 取舍
这一个属性决定了 SCADA 能不能按固定偏移读数据,决策必须在项目初期做。
S7_Optimized_Access | 寻址能力 | 适用场景 |
|---|---|---|
'TRUE'(优化 DB) | 无固定字节偏移;符号名可访问 | 符号访问 / OPC UA 访问 / HMI 符号绑定 |
'FALSE'(非优化 DB) | 有固定字节偏移,可按 DB300.DBD4 绝对寻址 | SCADA 按 DB 号 + 字节偏移读值(经典 WinCC / 组态软件)、PLC 间交换数据区、HMI 与外部系统按偏移通信 |
结论:
- 绝对寻址(SCADA 按
DB300.DBD4读)必须'FALSE' - 符号 / OPC UA 访问才用优化 DB(
'TRUE')
属性写法要求:
- 模板首行必须含
{ S7_Optimized_Access := 'TRUE' }(或'FALSE'),否则与项目优化块访问设置冲突,或报S7_Optimized_Access attribute missing - 属性行必须紧跟
DATA_BLOCK头之后 - 改
S7_Optimized_Access或VERSION后需重新编译才生效
⚠️ 与 DB 编号的连锁坑:外部 SCL 源里 DATA_BLOCK "MyDB" DB 300 的编号会被静默忽略(V12~V17 Upd4 确认)。若 SCADA 依赖 DB300.DBD4,则同时依赖「非优化 DB」+ 「DB 编号确实是 300」两个条件——而后者 Openness 不可靠。永久解法是改符号寻址。
// 全局 DB 语法(绝对寻址):{ S7_Optimized_Access := 'FALSE' }
// DATA_BLOCK "MyDB"
// { S7_Optimized_Access := 'FALSE' }
// DB300 ← ⚠️ 编号会被静默忽略,需事后 SetAttribute("Number", 300)
// Var1 : Bool; // 偏移 0.0
// END_VAR
// 实例 DB 语法:FB 名放双引号、独立一行,取代 STRUCT...END_STRUCT
// DATA_BLOCK "MyInstanceDB"
// { S7_Optimized_Access := 'FALSE' }
// "FB_Motor" ← 引用的 FB 必须已存在,否则 Block "FB_name" not found
来源:en-blogs.md(SCL Creating Data Blocks 篇)、中文社区(西门子中国论坛 SCL 导入篇)
12.9 源文件规范
| 规范项 | 要求 | 违反后果 |
|---|---|---|
| 扩展名 | 仅 .awl(STL) / .scl / .db / .udt | 其他扩展名直接抛异常 |
| 编码 | ASCII / UTF-8 文本。固定一种并做成配置项 | ⚠️ 资料有 UTF-8 BOM 与无 BOM 两种说法(见 12.4.3) |
| 文件头 | 必须保留 {Attribute 'pack_mode' := '0'}、TITLE = '...'、{ S7_Optimized_Access := 'TRUE' }、VERSION : 0.1 | 导入失败 |
S7_Optimized_Access 位置 | 紧跟 DATA_BLOCK 头之后 | S7_Optimized_Access attribute missing |
| 变量前缀 | S7 里变量引用需加 # 前缀(wTemp := WS[I].var1; → #wTemp := #WS[#I].var1;);FOR 循环变量也要加(FOR #I:=1 TO 10 DO) | 编译错误 |
| DB 头格式 | DATA_BLOCK "DB_example" + 独立行 FB 名 | — |
| 可选属性 | 可嵌 AUTHOR : name / FAMILY : name | 导出时才携带 |
NON_RETAIN | TIA 新增关键字(VERSION 之后单独一行) | — |
| DB / FB 存在性依赖顺序 | 先生成 FB/DB,再生成实例 DB | Block "FB_name" not found |
| 重复生成策略 | ① 生成前 ExternalSources.Find(name)?.Delete();② 或用 ImportOptions.Override;③ 「已是目标状态」视为 no-op(声明式协调) | Duplicate block name |
| ⚠️ FTL 迁移差异 | 从 Step 7 v5.5 迁到 TIA:变量加 #、DB 头改格式、必须加 { S7_Optimized_Access := 'FALSE' }、DB 编号从「被接受」变成「被忽略」 | 一次迁完再验证 |
| 语言声明 | 多语言块编译失败时:用 { Language := 'zh-CN' } 声明 | 编译失败 |
| ⚠️ 接口与固件匹配 | 例如 S7-1200 FW 4.0 不支持 {S7_m_c := 'true'} | 编译日志报 Block consistency error |
来源:中文社区(与非网 SCL 规范、SCL 导入篇)、en-blogs.md(SCL Creating Data Blocks)、github.md(ProgramBlocks 片段)
12.10 项目模板与代码组织
12.10.1 分层结构(可复用的工程范式)
来自西门子中国论坛的官方入门项目 StartOpenness 五部分(可作为分层参考):
| 层 | 职责 | 对应 StartOpenness 模块 |
|---|---|---|
| 配置层 | 项目路径、订货号、IP、模板选择、密码等(绝不硬编码) | —(ProjectsConfig.json 模式) |
| 服务层 | 与 TIA 交互的封装(打开/连接/保存/关闭) | ① TIA 博途(界面打开/关闭)② 项目(打开、连接、保存、关闭) |
| 模型层 | 业务对象与设备组件(标准框架 HwLib.GCF 提供阀门、电机等) | ③ 添加设备(PLC/HMI 设备添加) |
| 操作层 | 编译、生成、导出 | ④ 编译(软硬件编译) |
| 日志层 | 执行状态与错误输出 | ⑤ 文本显示(程序执行状态显示) |
程序集解析:app.config 或程序集解析器二选一
| 方案 | 何时选 |
|---|---|
| app.config | 多数场景推荐——会校验强名称,engineering.dll 被恶意篡改会导致加载错误 |
| AssemblyResolve | copy-deploy 等场景——无法检测篡改;⚠️ 必须把业务逻辑放进独立方法(Main 里注册 resolver 会因 JIT 崩溃) |
NuGet Api.Global.Openness().Initialize() | 已用官方 NuGet 包族时的最简方案 |
Excel 模板驱动的生成器架构(西门子中国论坛的落地范式):
Excel 模板(程序结构配置)
→ 读取模板数据的软件
→ TIA Portal Openness API
→ 生成 背景数据块 / FC / 组件调用并赋予实参
工具四步:选项目模板 → 选新项目路径与名称 → 点「创建新项目」
→ 选 Excel 模板 → 点「更新项目」
收益:自动调用 + 参数赋值,至少再省 70% 编程工作量
定位原则:程序生成器替代简单基础工作,顺控、闭环调节等仍手工完成。
⚠️ 凭证管理规范(来自 CSDN 文库自动登录工具篇):不用硬编码,用配置文件 + AES 加密辅助类(16 字节 Key + 16 字节 IV),字段含 Name / PathPattern(如 *LineA*.ap16)/ Username / EncryptedPassword。
来源:中文社区(西门子中国论坛 StartOpenness 与 HwLib.GCF 篇、CSDN 文库两篇)
12.10.2 模板规划的两条性能建议
- 模板主体结构尽量不动,只改工站标号(
OP10~OP200目录结构先规划好再按需删) - 需自动生成的外设尽量放在一起,文件越少导入越快
来源:en-blogs.md(CODESYS 应用指南第 12 条性能经验)
12.11 安全
| 建议 | 具体要求 | 依据 |
|---|---|---|
| 不要从 TIA 界面启动 Openness 时不能用 HMI editor | 官方原话:若不能使用 HMI 编辑器(WithoutUserInterface),你不能用 HMI 编辑器,但可以用 Openness API 或手动打开 “Devices & Networks” 编辑器与编程编辑器 | 官方 V19 手册 5.2.8 |
| Add-in 与 TIA 同权限运行 | Addin 与 TIA 同权限运行;安装 .addin 前先审源码 / 杀毒扫描(尤其 .txt 改名绕过邮件过滤器时) | 英文博客(Addin 安全实践) |
| 不要在生产机上放开权限组 | Siemens TIA Openness 组仅管理员可配置;以普通用户权限运行 Openness 应用;以管理员权限安装到程序目录;避免从用户目录动态加载程序集/DLL | 官方 V19 手册 Readme 安全建议 |
| Openness 严禁用于安全系统 | 禁止用 Openness API 执行安全系统的检查或生成验收/认可数据。安全验收只能用 STEP 7 Safety 附加包的安全打印(safety printout)或功能测试完成,Openness API 不是替代品 | 官方 V19/V15.1 手册 |
| Openness firewall 授权 | 首次连接弹授权框;连续拒绝三次会抛 EngineeringSecurityException | 官方手册 5.2.9 / 中文社区 |
| 白名单双重校验的 CI 含义 | 白名单以 exe 的 FileHash + DateModified 双重校验,重新编译或改动 exe 会导致白名单失效 | 官方手册 5.2.9 |
| 密码下发不走 Openness | 调 Siemens Automation Tool(SAT):-set_plc_password -ip <ip> -oldpassword "<old>" -newpassword "<new>";调度器用仅有 Log on as batch job 权限的服务账号,禁止交互式登录 | 英文博客(密码轮换篇) |
| 工程数据不出厂区 | PLC 工程含工厂设备信息、网络配置、IP、程序、HMI 画面、工艺逻辑、设备参数、企业知识——上传云端是企业无法接受的 | HubWiz(TiaLink 篇) |
来源:官方 V19/V15.1 手册、官方 5.2.9、en-blogs.md(密码轮换 / Addin 安全)、中文社区、HubWiz
12.12 测试与验证
12.12.1 三层验证
| 层 | 手段 | 判据 |
|---|---|---|
| ① 编译零错误 | (ICompilable)project.Compile() | result.State == CompilerResultState.Successful 且 ErrorCount == 0;Comquent 采用「warning 也算失败」的严格策略 |
| ② 生成后核对块是否落地 | 检查程序块图标由灰转亮;日志区「0 错误,0 警告」;核对块是否真的出现在项目树里 | 块存在 + 亮度正确 |
| ③ 下载前离线 IP 校验 | 遍历 NetworkInterface.Ports → IIpAddress 取 Value,用 Dictionary 撞键报冲突 | 无 IP 冲突 |
官方 snippets 交叉印证的导出/编译顺序:
// 块未编译无法导出 XML:先编译再导出
PlcTagTable tagTable = plc.TagTableGroup.TagTables.Find("OpennessTags") as PlcTagTable
?? plc.TagTableGroup.TagTables.Create("OpennessTags");
if (tagTable.Tags.Find("M0_0") == null) tagTable.Tags.Create("M0_0", "Bool", "%M0.0");
PlcBlock mainBlock = plc.BlockGroup.Blocks.Find("Main") as PlcBlock;
if (!mainBlock.IsConsistent) mainBlock.GetService<ICompilable>().Compile();
mainBlock.Export(new FileInfo(exportPath), ExportOptions.WithDefaults);
IP 唯一性校验思路(可直接用于 CI 静态检查):
foreach (Device device in _project.Devices)
foreach (DeviceItem item in device.DeviceItems)
if (item is NetworkInterface ni)
foreach (Port port in ni.Ports)
if (port.EngineeringNode is IIpAddress ip && ipRegistry.ContainsKey(ip.Value))
conflicts.Add(new IPConflict { IP = ip.Value, Device2 = $"{device.Name}/{item.Name}" });
下载前的标准化验证清单(6 项,HubWiz):
- 匹配版本打开无迁移提示
- 设备配置树含预期型号 + 槽位模块
- 变量表数据类型与 I/O 地址正确
- 程序块含导入的 FB 及实例 DB
- UI 触发「编译 → 软件(全部重建)」0 错误 0 警告
- 导出项目主文件用 TIA Portal Project Comparer 与上一合同基线 diff
CAx 导入的验证 6 步(最严格的一份):
- 设备视图出现预期名字
- network view 确认子网与 IP(非默认)
- 展开确认子模块在正确槽序
- I/O address 页确认输入/输出字节范围无重叠
- 跑一次编译,clean compile 才是真正的通过标准(树里看着对也可能过不了一致性检查)
- 再 CAx export 回来与源模板 diff——任何意外差异说明某字段被静默默认了
来源:中文社区(与非网、与非网 SCL)、HubWiz(Openness API 创建项目篇)、英文博客(CAx GSDML 篇、项目校验篇、密码轮换篇)、github.md(ProgramBlockSnippets)
12.13 选型决策树
12.13.1 ASCII 版
你要做的事是什么?
│
├─【操作 TIA 工程】(生成块 / 改变量 / 建硬件 / 编译 / 下载)
│ └─ Openness 外部应用(C# / PowerShell)
│ ├─ 一次性脚本验证 → C# + 官方 NuGet 三件套(首选)
│ ├─ 数据处理为主 / 懂 Python → Repsay 52★(⚠️ 勿选 0★ fork)
│ └─ 团队长期维护 → 分层架构 + app.config + 编译门禁 + 交叉引用分析
│
├─【用户在 TIA 界面里点右键】(交互式、上下文相关)
│ └─ TIA Add-In(.addin 放 Portal Vxx\AddIns 或 %ProgramData%\...\Addins\)
│ ├─ 跨 TIA 版本免修改?→ ✅ Add-In(TIA 自带当前 + 3 legacy 共 4 个 DLL)
│ └─ ⚠️ 代价:必须已有打开的 TIA Portal GUI 和项目
│
├─【让代码可 diff / 版本控制】
│ ├─ 全语言 + TIA 内集成 → VCI + Git + Automation Compare Tool (109797235)
│ │ ⚠️ LAD/FBD 无法版本化;CFC/SCL-Graph 比不了
│ ├─ 要跨 TIA 版本 → SIMATIC SD(V20 起的 YAML,官方唯一跨版本格式)
│ ├─ 只要 SCL(最省事) → 外部源入 Git(CERN 路线)
│ └─ 想现成方案 → TiaGitHandler(DotNetSiemensPLCToolBoxLibrary 477★)
│ ⚠️ 版本互斥,必须拆进程
│
├─【批量运维】(复制驱动/TO、批量下装、批量设参)
│ ├─ 官方已覆盖 → TIA Portal Mass Operation Tool (109986645,GUI+CLI)
│ │ ⚠️ 仅 Profinet;不支持密码保护设备;不支持 TO 值赋为对象参数
│ └─ 官方未覆盖 → 自研 Openness 应用
│
├─【CI 集成】
│ ├─ 官方路线 → Continuous Integration 工具集 (109801203)
│ │ + TIA Test Suite Advanced + TestRunner + CodeCoverage + XML→JUNIT
│ ├─ 只做编译门禁 → Windows VM + WithoutUserInterface + 零错误断言
│ └─ ⚠️ 硬约束:公共 CI runner 无法捆绑 Siemens 程序集 → 必须自备 Windows VM
│ 白名单以 exe FileHash+DateModified 校验,重新编译即失效
│
├─【AI / Agent 集成】
│ ├─ 工程操作能力 → TIA_Portal_Openness_MCP(280★,预热 340s→<1s)
│ ├─ 要安全边界 → TIA-Portal-CLI(默认预运行,⚠️ AGPL-3.0)
│ ├─ 要测试闭环 → AnyAutomationStudio(PLCSIM Advanced 跑单测)
│ └─ 绝对红线 → 不操作在线 PLC + 本地部署不外传工程数据
│
└─【不需要写代码】
├─ 画 HMI 画面 → 官方 Excel Importer/Exporter (109792619)
│ ⚠️ 重复运行会覆盖已生成画面
├─ 装 GSDML / 导硬件 → TIA Selection Tool 导出 .aml → TIA「导入 CAx 数据」
└─ 上传运行程序比对 → AUVESY-MDT Octoplant(生产版本管控)
12.13.2 Mermaid 版
graph TD
Start["你要做的事是什么?"] --> Q1
Q1{"操作 TIA 工程<br/>(生成/编译/下载)"} -->|是| A1["Openness 外部应用<br/>C# / PowerShell"]
Q1{"用户在 TIA 界面点右键<br/>(交互式)"} -->|是| A2["TIA Add-In<br/>✅ 跨版本免修改<br/>⚠️ 需已打开 TIA + 项目"]
Q1{"代码可 diff / 版本控制"} -->|是| A3["VCI+Git+CompareTool 109797235<br/>或 SIMATIC SD(V20+,YAML)<br/>或只把 SCL 入 Git"]
Q1{"批量运维<br/>(复制/下装/设参)"} -->|是| A4{"官方已覆盖?"}
A4 -->|是| A5["Mass Operation Tool 109986645<br/>GUI + CLI"]
A4 -->|否| A6["自研 Openness 应用"]
Q1{"CI 集成"} -->|是| A7["Continuous Integration 工具集 109801203<br/>Windows VM + WithoutUserInterface"]
Q1{"AI / Agent 集成"} -->|是| A8["MCP 封装 / CLI 干跑<br/>⚠️ 不操作在线 PLC"]
Q1{"不需要写代码"} -->|是| A9["官方应用示例<br/>Excel Importer 109792619<br/>AML 导入 / Octoplant"]
A1 --> A1a{"场景?"}
A1a -->|一次性脚本| A1b["C# + 官方 NuGet 三件套"]
A1a -->|数据处理为主| A1c["Repsay tia-openness-api-client 52★<br/>⚠️ 勿选 0★ fork"]
A1a -->|团队长期维护| A1d["分层架构 + app.config<br/>+ 编译门禁 + 交叉引用分析"]
style A5 fill:#d4edda
style A8 fill:#fff3cd
style A3 fill:#d1ecf1
来源:官方选型指南 Entry 109816879(Add-In / Openness 应用 / MAC Equipment Module 三者取舍)+ 综合全部 raw 资料整理
12.14 从零到生产的 8 步落地路线
| 步骤 | 目标 | 关键动作 | 交付物 / 验收标准 | 常见陷阱 |
|---|---|---|---|---|
| 1. 环境与权限 | 打通最小闭环 | ① 确认 TIA 版本与 Openness 组件已装(Get-ChildItem "...\PublicAPI\V<n>\*.dll");② 用户加入 Siemens TIA Openness(V21 还需 TIA Engineer);③ 注销重登;④ TIA 侧勾选专家设置「通过外部程序启用 TIA Portal 的访问」并完全重启 TIA | whoami /groups | findstr "Siemens" 有输出;冒烟测试打印 portal.Version 与 portal.Projects.Count 成功 | 加组不是充分条件;平台必须 x64(否则 BadImageFormatException);目标框架不得低于 4.7.2(4.7.2 及以下在 V18 加载 DLL 大概率失败) |
| 2. 程序集解析 | 确定加载方案 | app.config / AssemblyResolve / NuGet Api.Global.Openness().Initialize() 三选一;验证输出目录零个 Siemens.*.dll | 干净构建后 bin/ 无 Siemens.*.dll | ❌ 手动复制 DLL(Copy Local:True 官方不支持);❌ GAC 注册;❌ 降级目标框架;⚠️ AssemblyResolve 必须在 Main 之外的方法里注册 |
| 3. 只读探查 | 摸清目标工程 | 用只读操作遍历:HardwareCatalog.Find("") 拿 TypeIdentifier、project.Devices / DeviceItems 树、PlcSoftware 定位、变量表枚举、CrossReferenceService 拿影响面 | 一份「工程结构清单 + 订货号 catalog map」 | ❌ 不要在设计器正打开该工程时跑脚本;⚠️ DeviceItems[1] 这类索引写法随设备组态变化会失效,用 Classification == DeviceItemClassifications.CPU 判定 |
| 4. 生成能力打通 | 跑通 SCL 链路 | ① 手写一个最小 SCL 块(含 { S7_Optimized_Access := 'TRUE' } 文件头);② 外部源 CreateFromFile + GenerateBlocksFromSource;③ 核对块是否真的落地(图标由灰转亮、日志 0 错误 0 警告);④ 再做一次导入变量表 XML | 一次改动 → 块出现在项目树 → 编译零错误 | ⚠️ 导出成功 ≠ 工程正确;⚠️ 生成前先删同名源;⚠️ V17+ 别忘 Concat 软件单元的变量表 |
| 5. 编译与静态检查门禁 | 建立质量闸门 | (ICompilable).Compile() + 遍历 Messages;自建命名规范(正则 ^[A-Z][a-zA-Z0-9]*$)、IP 唯一性、未使用变量、IEC 62443 检查;报告转 JUNIT XML | 一条命令产出质量报告,warning 是否算失败已明确 | ❌ 不要用官方 snippets 当测试套件(无断言);⚠️ Test Suite 不查命名规范与编程风格 |
| 6. 版本控制落地 | 代码可 diff | 选定路线(VCI / SIMATIC SD / 只入 SCL / TiaGitHandler);git init 于工作区根;pre-commit hook 校验 CODEOWNERS、GPG 签名、关联工单号 | git diff 读得懂;.apXX 当二进制归档(Git LFS / SVN / Perforce) | ⚠️ git checkout 会重写 VCI 工作区,属危险命令;⚠️ LAD/FBD 无法版本化;⚠️ CFC / SCL-Graph 比不了 |
| 7. CI 集成 | 流水线化 | ① Windows VM Runner(预装 TIA + Openness);② WithoutUserInterface;③ 预设白名单(FileHash + DateModified);④ 六段流水线(Build→Deploy→Setup→Tests→Release→Report);⑤ 部署到 PLCSIM Advanced | 一次 git push 全自动跑完,产出可下载 artifact | ⚠️ 模态对话框会让调用无限挂起;⚠️ HSP 缺失应前置预检(无头模式对此更敏感);⚠️ Setup 阶段不可省(清报警/回零/设初值);⚠️ 公共 CI 无法捆绑 Siemens 程序集 |
| 8. 生产运维与批量 | 规模化 | ① 官方工具优先:Mass Operation Tool / SDC / Parameter Comparer;② 自建工具沉淀为分层 CLI 工具(配置/服务/模型/日志);③ 加固:白名单、非交互式服务账号、密码集中管理、审计日志;④ 流水线只下到 PLCSIM,下到物理机是需密码的独立人工步骤 | 运维手册 + 回滚预案 + 审计追溯链 | ❌ 禁止用 Openness 生成 F-block 或执行安全系统检查;❌ 不要在生产机放开权限组;⚠️ MOT 仅支持 Profinet、不支持密码保护设备;⚠️ 重新编译会让白名单失效 |
收益参考(西门子官方统计 + 社区实测):
| 来源 | 收益 |
|---|---|
| 官方(Openness 自动化项目生成) | 单次项目生成 180 分钟 → 75 分钟(-80%),总耗时 160 分钟 |
| Comquent | 40–60% 更短调试时间、消除人工传输错误 |
| 西门子中国论坛 HwLib.GCF | 自动调用 + 参数赋值,至少再省 70% 编程工作量 |
| 中文社区 faceplate 批量配置 | 手动 2~3 小时 → 脚本不到一分钟(30+ 台机器人工作站场景) |
TIA-Portal-CLI 实测 | 5 个命令单独调 20.1 s vs 批处理附加一次 8.1 s;项目完整快照 219.5 KB vs markdown outline tree 2.6 KB |
来源:官方 V19/V21 手册、官方 CI 示例 109801203、Comquent、HubWiz、中文社区(与非网、西门子中国论坛、CSDN faceplate 篇)、en-blogs.md
12.15 不可违反的红线(汇总)
| # | 红线 | 依据 |
|---|---|---|
| 1 | 禁止用 Openness 生成 F-block,禁止用 Openness 执行安全系统的检查或生成验收/认可数据 | 官方 V19/V15.1 手册;安全验收只能用 STEP 7 Safety 的 safety printout 或功能测试 |
| 2 | 禁止在同版本两个 TIA 实例间拖拽块(会导致块号重复、KWP 密码丢失、实例 DB 不一致) | en-blogs.md |
| 3 | Copy Local=True 官方明确不受支持;永远不要 GAC 注册 Siemens.Engineering 程序集;绝不要重签 Siemens 程序集 | 官方手册 / en-blogs.md |
| 4 | 一个项目同时只允许一个 Openness 进程;API 非线程安全 | en-blogs.md 异常表 / 中文社区 |
| 5 | CI 流水线只下载到 PLCSIM,不下载到物理 CPU;晋级到物理机是需密码的独立人工步骤 | en-blogs.md 密码轮换篇 |
| 6 | Openness 程序集不能随产品分发(许可原因)——公共 CI 无法完整复现构建 | HubWiz(TIA-Portal-CLI 篇) |
| 7 | 白名单以 exe 的 FileHash + DateModified 双重校验,重新编译即失效 | 官方手册 5.2.9 |
| 8 | 不要把导入成功等同于工程正确——必须编译 + 提取错误 + 回传修复 | 中文社区 / en-blogs.md |
| 9 | 不要把 SimaticML 当长期存档格式(仅对当前 Openness 版本有效) | 官方手册明确警告 |
| 10 | 不要从用户目录动态加载程序集/DLL;以普通用户权限运行 Openness 应用 | 官方 V19 手册 Readme 安全建议 |
本章速查
- 资源释放三条铁律:先 Project 后 TiaPortal(反序);C#
using是源顺序,反序需求必须手写 try/finally;Save与Dispose是两件事。四个事件必须在finally中反订阅。 - 性能第一课:千条变量逐个
Tags.Create()可超 10 分钟——必须用 XMLImport。且 V17+ 每个 Software Unit 下还有自己的TagTableGroup,必须Concat合并。文件越少导入越快。 GenerateBlocksFromSource两种重载语义相反:默认重载失败抛异常并删除已生成的块;KeepOnError不抛异常,返回含出错块的列表。两者都要求 PLC 离线。- MLFB 订货号必须含空格(
6ES7 214-1AG40-0XB0),去掉空格就报「设备未找到」;最佳来源是HardwareCatalog.Find("")的TypeIdentifier。S120 等系列需第四段 AdditionalTypeIdentifier。 - ⚠️ 外部 SCL 源的 DB 编号会被静默忽略——若 SCADA 依赖
DB300.DBD4,必须{ S7_Optimized_Access := 'FALSE' }+SetAttribute("Number", 300),并永久改符号寻址。优化 DB('TRUE')只适合符号/OPC UA 访问。 - 改 FB 接口前必须先做交叉引用分析(
CrossReferenceService.GetCrossReferences(CrossReferenceFilter.AllObjects).Sources)——它会破坏所有调用点与实例 DB。跨工程共享 FB 用 typed global library,接口漂移在编译期暴露。 - pre/post delegate 必须处理所有对话框否则中止下载——V21 有 43 种类型(V20 有 42 种),用
as分派 + 早返回模式逐一补齐。 - CI 六段流水线里 Setup 最容易被省掉,也最不该省——不清报警、不回零、不设初值,测试就不可重复(CERN 经验)。CI 必须用
WithoutUserInterface,但要前置预检 HSP。 - 选型口诀:交互式用 Add-In(跨版本免修改,但需已打开 TIA);工程化用 外部 Openness 应用;批量运维先用官方 MOT (109986645);可 diff 用 SIMATIC SD(V20+);AI 集成用 MCP 但不碰在线 PLC。
- 🔴 五条安全红线:不生成 F-block、不执行安全系统检查;不
Copy Local=True、不 GAC 注册、不重签 Siemens 程序集;不在生产机放开权限组;不把 Siemens 程序集随产品分发;不下到物理 CPU 的 CI 视为未完成。
附录 引用索引 · Entry ID 速查 · 术语表
引用索引(自动生成)
本附录由脚本从
raw/目录下 146 篇抓取条目自动提取生成,抓取日期 2026-10-04。 所有 URL 均为抓取当日可访问的有效地址;标注「仅元数据」的条目表示页面为 JS 渲染,未取得正文。
A. 官方西门子资料索引(27 篇)
| # | 标题 | 来源 | 链接 |
|---|---|---|---|
| A1 | SIMATIC TIA Portal Openness: API for automation of engineering workflows(系统手册 V19,1692 页完整 PDF) | - | 链接 |
| A2 | Openness: API for automation of engineering workflows — 对象模型与设备项操作(同一 PDF 的对象模型/设备项/代码生成章节) | - | 链接 |
| A3 | Openness: API for automation of engineering workflows — 打开/创建/导出工程与 UMAC(工程数据操作章节) | - | 链接 |
| A4 | SIMATIC Openness: Automating creation of projects(V15.1 系统手册,656 页完整 PDF) | - | 链接 |
| A5 | SIMATIC TIA Portal Openness: Automating creation of projects(官方条目页,V21/V20/V19…11/2019 各版手册汇总) | - | 链接 |
| A6 | Openness for SINAMICS Startdrive and DCC V15.1–V20(Startdrive/DCC Openness 官方产品说明,含编程手册入口) | - | 链接 |
| A7 | Openness for SINAMICS Startdrive and DCC — 应用示例清单(EPISD / Serial Drive Commissioner / Getting Started / Export | - | 链接 |
| A8 | SINAMICS/SIMATIC – Automation of engineering tasks in machine building(Openness 应用示例总览页) | - | 链接 |
| A9 | TIA Portal Mass Operation Tool(MOT,批量操作工具) | - | 链接 |
| A10 | SINAMICS Serial Drive Commissioner(SDC,串行驱动调试器) | - | 链接 |
| A11 | SINAMICS Startdrive Edit parameters in several drives(EPISD,多驱动参数批量编辑 Add-In) | - | 链接 |
| A12 | SINAMICS Startdrive Parameter Comparer(驱动参数比较器) | - | 链接 |
| A13 | TIA Portal Drive Technology Commissioner(驱动技术调试器) | - | 链接 |
| A14 | SINAMICS Democase creator(演示用例自动生成 Add-In) | - | 链接 |
| A15 | SINAMICS S120 Motor Module Change(电机模块单/双模块互换 Add-In) | - | 链接 |
| A16 | SIMATIC Torque Pre Control Assistant(转矩预控助手 Add-In) | - | 链接 |
| A17 | SINAMICS Drive Coupling Assistant(机械耦合轴频响优化 Add-In) | - | 链接 |
| A18 | SIMATIC Modular Application Creator(模块化应用创建器,主程序) | - | 链接 |
| A19 | SINAMICS Migration Tool(驱动迁移工具) | - | 链接 |
| A20 | TIA Portal Advanced AML Importer(高级 AML 导入器) | - | 链接 |
| A21 | SIMATIC LRWDB Generator | - | 链接 |
| A22 | TIA Portal Openness – Overview of the Most Important Documents and Links(官方文档总览入口) | - | 链接 |
| A23 | TIA Portal Add-Ins(官方 Add-In 总览) | - | 链接 |
| A24 | SINAMICS S120 Synchronism Applications with DCB Extension(带 DCB 扩展的 S120 同步应用) | - | 链接 |
| A25 | SINAMICS DCC: Winder(DCC 卷绕应用) | - | 链接 |
| A26 | TIA Portal Openness 防火墙与白名单机制(官方手册 5.2.9 章节) | - | 链接 |
| A27 | TIA Portal Openness 程序集加载与部署策略(官方手册 5.2.8 章节) | - | 链接 |
B. 中文社区文章索引(24 篇)
| # | 标题 | 来源 | 链接 |
|---|---|---|---|
| B1 | TIA Openness V16 使用1 | - | 链接 |
| B2 | Win10专业版下,TIA Portal Openness安装后必做的用户组配置(保姆级图文) | - | 链接 |
| B3 | 七天学会PLC 从零打通 TIA Portal Openness:环境配置、连接博图到自动生成工程代码 | - | 链接 |
| B4 | TIA Portal Openness概述(非常详细的能力边界梳理) | - | 链接 |
| B5 | 如何导入AI工具写的SCL文件 | - | 链接 |
| B6 | c# NET API 自动化生成西门子PLC的st代码写入项目文件中 | - | 链接 |
| B7 | Startdrive Openness 助力装备制造业传动设备的一体化工程实现 | - | 链接 |
| B8 | TIA Portal Openness 无法运行 | - | 链接 |
| B9 | TIA Openness开发入门(1) | - | 链接 |
| B10 | TIA Portal Openness工业自动化开发实战指南 | - | 链接 |
| B11 | TIA_Portal_Openness深度开发自动生成PLC程序 | - | 链接 |
| B12 | TIA Portal faceplate批量配置实战:从手动到Openness自动化 | - | 链接 |
| B13 | TIA Portal Openness实战:从环境配置到PLC代码自动生成 | - | 链接 |
| B14 | 基于标准框架的程序生成器(TIA PORTAL自动编程) | - | 链接 |
| B15 | 西门子 TIA Portal 也能被 AI 调用?Openness API 接入全流程来了 | - | 链接 |
| B16 | 七天学习plc加机器视觉之 openness 接口开发,Visual Studio 2022中配置c# 支持openness 开发西门子复杂工程 | - | 链接 |
| B17 | TIA博途自动执行工程任务——Openness入门篇 | - | 链接 |
| B18 | 不懂西门子TIA Portal Openness——看这篇就够了 | - | 链接 |
| B19 | 博图PLC程序的两种导入导出方法 | - | 链接 |
| B20 | 坚持学习第十五天:博途程序块的导入和导出 | - | 链接 |
| B21 | 告别手动输入!用C#和TIA Openness API为博图V16项目打造专属启动器 | - | 链接 |
| B22 | 告别手动输入!用C#和TIA Openness API给博图V16项目做个自动登录小工具 | - | 链接 |
| B23 | 专家帮忙看看,openness打开博途报错。 | - | 链接 |
| B24 | SCL外部编程工具应用 | - | 链接 |
C. 英文博客文章索引(27 篇)
| # | 标题 | 来源 | 链接 |
|---|---|---|---|
| C1 | How to Use TIA Portal Openness with Python (Complete Guide) | - | 链接 |
| C2 | TIA Portal Openness: Adding Third-Party Hardware via API(第三方设备无法脚本化创建) | - | 链接 |
| C3 | Fix TIA Portal Openness Security Error — Compile as EXE | - | 链接 |
| C4 | Resolving TIA Portal Openness V19 to V21 Migration(FileLoadException) | - | 链接 |
| C5 | Build TIA Portal Project Bases with Openness API Scripts | - | 链接 |
| C6 | Openness API 创建 TIA Portal 项目(HubWiz 中文版) | - | 链接 |
| C7 | TIA Portal Openness: Adding Hardware Programmatically via API(版本能力矩阵) | - | 链接 |
| C8 | Installing TIA Portal Openness V15.1: Modify Existing TIA Setup | - | 链接 |
| C9 | Resolving Missing TIA Portal Openness V20 Siemens.Engineering DLL References | - | 链接 |
| C10 | Installing GSDML Files via TIA Openness CAx Import API | - | 链接 |
| C11 | TIA Portal SCL: Creating Data Blocks from External Sources | - | 链接 |
| C12 | Exporting and Importing Function Blocks in TIA Portal: Complete Guide | - | 链接 |
| C13 | TIA Portal Openness: Configuring PLC I/O Tags via XML | - | 链接 |
| C14 | TIA Portal Automated Project Verification with TIA Openness(IP/命名/编程规范自动校验) | - | 链接 |
| C15 | Exporting and Importing TIA Portal Projects as Text Files: L5K Equivalent Workflows | - | 链接 |
| C16 | TIA Portal Global Find and Replace: How to Work Around Missing Feature | - | 链接 |
| C17 | Daily PLC Password Rotation on S7-1500 with TIA Portal Openness | - | 链接 |
| C18 | Displaying Multilingual PLC Tag Comments in WinCC Runtime(Openness 批量生成 HMI 文本列表) | - | 链接 |
| C19 | CI/CD for PLC Development: TIA Portal with Jenkins(Comquent,实战 pipeline) | - | 链接 |
| C20 | Leveraging IT-Inspired Workflows for PLC Software(CERN ICALEPCS 2025,TIA Openness + GitLab CI) | - | 链接 |
| C21 | 附录:博图 Openness(从零开发 PLC 程序生成器的工程化实践) | - | 链接 |
| C22 | TIA Openness 开发入门(1)(慧兰博 HwLib) | - | 链接 |
| C23 | 白话 TIA Openness(4):基于框架模板自动生成程序结构 | - | 链接 |
| C24 | TIA-Portal-CLI:PLC AI 编程工具(Openness 的 dry-run 边界化封装) | - | 链接 |
| C25 | PLC 编程必备的 7 个 MCP Server(Openness 作为 AI Agent 接入层) | - | 链接 |
| C26 | TiaLink:给 Agent 一双操作 TIA Portal 的手(本地执行层设计) | - | 链接 |
| C27 | 西门子 TIA Portal 也能被 AI 调用?Openness API 接入全流程 | - | 链接 |
D. GitHub 开源项目索引(45 篇)
| # | 标题 | 来源 | 链接 |
|---|---|---|---|
| D1 | 1. Repsay/tia-openness-api-client(Python 客户端的上游本体) | - | 链接 |
| D2 | 2. mgrillo75/tia-openness-api-client(fork) | - | 链接 |
| D3 | 3. OsamaS99/tia-openness-api-client(fork) | - | 链接 |
| D4 | 4. MeKo-Tech/tia-openness-api-client(fork,补充了 USAGE.md 索引) | - | 链接 |
| D5 | 5. Maroder1/TIA-openness(pythonnet 最小可运行示例) | - | 链接 |
| D6 | 6. Maroder1/Openness_examples_python(Jupyter Notebook 版进阶示例) | - | 链接 |
| D7 | 7. 第三方踩坑笔记:0x42697262 的 TIA Portal Openness Python 记录 | - | 链接 |
| D8 | 8. GenerateSource 的 pythonnet 泛型坑(industrialmonitordirect 教程实证) | - | 链接 |
| D9 | 9. Repsay/siemens-tia-openness-python-stubs(类型 stub) | - | 链接 |
| D10 | 10. siemens/tia-portal-openness-code-snippets(官方代码片段总仓,V20) | - | 链接 |
| D11 | 11. 官方片段模块:Plain.Setup — Openness 程序集解析器(Assembly Resolver) | - | 链接 |
| D12 | 12. 官方片段模块:Plain.Setup — 测试基类与项目归档加载 | - | 链接 |
| D13 | 13. 官方片段模块:Plain.Step7 / ProgramBlocks — 程序块导入导出与KnowHow 保护 | - | 链接 |
| D14 | 14. 官方片段模块:Plain.Step7 / PLC — 变量表直接改与「导出改XML 再导回」 | - | 链接 |
| D15 | 15. 官方片段模块:Plain.Step7 / PLC — DataBlock 交叉引用与接口成员读取 | - | 链接 |
| D16 | 16. 官方片段模块:Plain.Step7 — 硬件目录(HardwareCatalog)遍历 | - | 链接 |
| D17 | 17. 官方片段模块:Plain.Step7 — 全局库(Global Library)创建 | - | 链接 |
| D18 | 18. 官方片段模块:Plain.Step7 — 安全与密码保护(UAC / UMAC / 主密码) | - | 链接 |
| D19 | 19. 官方片段模块:Plain.Startdrive — 驱动对象类型与激活状态 | - | 链接 |
| D20 | 20. 官方片段模块:Plain.Startdrive — 报文(Telegram)配置与地址读取 | - | 链接 |
| D21 | 21. 官方片段模块:WithExtensions.Step7 / PLC — 用扩展包生成 SCL 源与遍历块 | - | 链接 |
| D22 | 22. 官方片段模块:WithExtensions.Step7 / PLC — 跨软件单元(Software Unit)汇总变量 | - | 链接 |
| D23 | 23. 官方代码片段仓库的其他模块清单(已抓取目录,源码未逐篇抓取) | - | 链接 |
| D24 | 24. Siemens.Collaboration.Net.TiaPortal.Openness.Startdrive.Extensions(官方 NuGet 包,Startdrive 扩展) | - | 链接 |
| D25 | 25. Siemens.Collaboration.Net.TiaPortal.Openness.Extensions(官方 NuGet 核心扩展包,含 V21 破坏性变更警告) | - | 链接 |
| D26 | 26. tia-portal-applications/tia-portal-openness-unified-library(官方 Unified 封装库) | - | 链接 |
| D27 | 27. tia-portal-applications/tia-addin-opc-ua-modelled-interface(官方 OPC UA 用户建模接口 Add-In) | - | 链接 |
| D28 | 28. tia-portal-applications/TIA-Add-In-ShowScripts(官方 HMI 脚本导出 + Excel 总览) | - | 链接 |
| D29 | 29. tia-portal-applications/TIA-Openness-Create-Synoptic-Unified(官方 PLC 网络 → JSON,同步到 Unified) | - | 链接 |
| D30 | 30. tia-portal-applications/tia-addin-build-package(官方 Add-In 工程脚手架) | - | 链接 |
| D31 | 31. tia-portal-applications/CWC-in-WinCC-Unified(官方 Custom Web Control 集成指南) | - | 链接 |
| D32 | 32. tia-portal-applications/SiOME-Base-Plugin(官方 OPC UA 建模编辑器插件基座) | - | 链接 |
| D33 | 33. tia-portal-applications/UMC-config-scripts(官方 UMC 配置脚本,PowerShell) | - | 链接 |
| D34 | 34. mking2203/CodeGeneratorOpenness(TIA 代码结构生成器,STEP 顺序图) | - | 链接 |
| D35 | 35. JokeYellow / Industrialcarbon-basedorganisms:Modbus 梯形图自动生成器 | - | 链接 |
| D36 | 36. Parozzz/TiaUtilities(SimaticML 解析库 + LAD 代码生成 Add-In) | - | 链接 |
| D37 | 37. caprican/SimaticML(SimaticML + AutomationML 文档与工具集) | - | 链接 |
| D38 | 38. caprican/TIA-HMI-Alarms(从全局 DB 注释提取生成 HMI 报警) | - | 链接 |
| D39 | 39. Parozzz 生态外的Add-In:WebView2 ActiveX 控件(datadiode/webview2) | - | 链接 |
| D40 | 40. bulaofen0036-coder/TIA_Portal_Openness_MCP(把 Openness 封成 180+ MCP 工具) | - | 链接 |
| D41 | 41. 1748477582/tia-portal-openness-mcp(多版本 V18/V20/V21 分支构建) | - | 链接 |
| D42 | 42. huahaizo/tia-portal-openness-ai(V15~V21 的 AI skill + C# 脚手架) | - | 链接 |
| D43 | 43. StaniB88/AnyAutomationStudio(原 TIAOpennessManager) | - | 链接 |
| D44 | 44. renanlido/tia-harness(TIA Portal V21 的 Claude Code 插件) | - | 链接 |
| D45 | 45. dotnetprojects/DotNetSiemensPLCToolBoxLibrary(含 TiaGitHandler,Openness 多版本 + Git 版本控制) | - | 链接 |
E. 进阶专题资料索引(23 篇)
| # | 标题 | 来源 | 链接 |
|---|---|---|---|
| E1 | Openness 在线连接与扫描:GoOnline/GoOffline、状态机与重连策略 | - | 链接 |
| E2 | 下载全流程:DownloadProvider.Download 的四个参数与 Configuration 构造链 | - | 链接 |
| E3 | PLCSIM Advanced 联调:三种通信路径与 API 协同仿真 | - | 链接 |
| E4 | 版本控制:VCI 工作区 + Git + Automation Compare Tool 的三层结构 | - | 链接 |
| E5 | SIMATIC SD:YAML 文本交换格式与跨版本版本控制 | - | 链接 |
| E6 | CI/CD:TIA Test Suite Advanced 与 Openness 自动化测试流水线 | - | 链接 |
| E7 | AutomationML / AML 与 CAx 数据交换:与 EPLAN Electric P8 双向集成 | - | 链接 |
| E8 | SimaticML 数据交换:块/变量/HMI 的导入导出与格式版本陷阱 | - | 链接 |
| E9 | OPC UA 数据交换:OpcUaExportProvider 与 PLC 侧 OPC UA 服务器组态 | - | 链接 |
| E10 | HMI 自动化:WinCC Unified 画面与对象的 Excel 批量导入导出 | - | 链接 |
| E11 | WinCC Unified 日志变量:TagTable.Export 的固定 Schema 缺陷与 Openness 绕过 | - | 链接 |
| E12 | HMI 画面批量生成:WinCC Professional/Comfort 的 Openness 支持度差异 | - | 链接 |
| E13 | Startdrive 传动自动化(一):驱动对象创建、服务获取与激活状态 | - | 链接 |
| E14 | Startdrive 传动自动化(二):参数读写、数组索引、bit 位与 BICO 链接 | - | 链接 |
| E15 | Startdrive 传动自动化(三):报文 Telegram 管理、安全报文与力矩报文 | - | 链接 |
| E16 | Startdrive 传动下载:DownloadConfiguration 三种子配置与工厂复位 | - | 链接 |
| E17 | 冗余与细分能力(一):R/H 系统专用动态属性与 S2/SwitchedS1 冗余模式 | - | 链接 |
| E18 | 冗余与细分能力(二):ET 200SP PN HF 虚拟模块与 CCDX/DDX 传输区 | - | 链接 |
| E19 | 通信处理器与等时同步:CP 1604/1616/1626 传输区 + 1516 等时耦合 | - | 链接 |
| E20 | 程序集解析:app.config vs AssemblyResolve 与 JIT 崩溃陷阱 | - | 链接 |
| E21 | 进程模型与工程化:WithUserInterface/WithoutUserInterface、多实例与事件处理 | - | 链接 |
| E22 | Addin 与 Openness 的分工:官方选型对照与 Addin SDK | - | 链接 |
| E23 | TIA Openness 能力边界与”生成程序”的工程路线选择 | - | 链接 |
索引合计:146 篇一手资料条目。
F. 官方 Entry ID 速查表
抓取自 raw/official.md 与 raw/advanced.md,2026-10-04。Entry ID 是西门子 Industry Online Support 的永久条目号,可在
support.industry.siemens.com/cs/document/<EntryID>/直接访问。
| Entry ID | 内容 | 类型 |
|---|---|---|
| 109826886 | TIA Portal Openness: API for automation of engineering workflows(核心手册,1692 页) | 官方手册 PDF |
| 109477163 | TIA Portal Openness: Automating creation of projects(656 页) | 官方手册 PDF |
| 109773802 | TIA Portal Openness: 上传硬件/软件/文件到 PLC 设备 | 官方条目 |
| 109763491 | Openness for SINAMICS Startdrive and DCC | 官方手册 PDF |
| 109779415 | TIA Add-In Getting Started — Startdrive Helper 类库 | 官方手册 PDF |
| 109762852 | Modular Application Creator(模块化应用创建器) | 官方条目 |
| 109792902 | Openness 文档总览 | 官方条目 |
| 109773999 | TIA Portal Add-In 总览 | 官方条目 |
| 109821826 | Openness Automated Engineering Application Examples(应用示例总入口) | 官方条目 |
| 109986645 | TIA Portal Mass Operation Tool(批量运维工具,GUI+CLI) | 官方应用 |
| 109774753 | SINAMICS SDC: Serial Drive Commissioner(单轴驱动批量串行调试) | 官方应用 |
| 109814528 | SINAMICS Startdrive: Drive Parameter Comparer(驱动参数比较器) | 官方应用 |
| 109822030 | SINAMICS Migration Tool(S120/S210/STARTER 迁移与固件升级) | 官方应用 |
| 109999915 | SINAMICS: Edit parameters in several drives(多驱动参数批量编辑) | 官方应用 |
| 109799613 | 官方应用示例条目 | 官方应用 |
| 109972123 | 官方应用示例条目 | 官方应用 |
| 109955411 | 官方应用示例条目 | 官方应用 |
| 110006149 | 官方应用示例条目 | 官方应用 |
| 109779879 | 官方应用示例条目 | 官方应用 |
| 109757232 | 官方应用示例条目(仅元数据,正文未取得) | 官方应用 |
| 38043750 | 官方应用示例条目(仅元数据,正文未取得) | 官方应用 |
| 109762849 | 官方应用示例条目(仅元数据,正文未取得) | 官方应用 |
| 109816879 | 官方应用示例条目(部分正文) | 官方应用 |
| 109792619 | 官方应用示例(被 advanced.md 引用) | 官方应用 |
| 109801203 | 官方应用示例(被 advanced.md 引用) | 官方应用 |
| 86140384 | TIA Portal Addin SDK Documentation | 官方手册 |
| 79168964 | 导入外部源文件并从源生成块(含操作视频) | 官方条目 |
G. 术语表
| 术语 | 全称 / 含义 | 首次出现 |
|---|---|---|
| Openness | TIA Portal Openness,西门子用于自动化工程流程的 .NET API | 第1章 |
| TIA Portal | 西门子博途(STEP 7 / WinCC / Startdrive 的集成工程平台) | 第1章 |
| MLFB | Machine Level Function Block,机器功能块,即订货号(如 6ES7 515-2AM02-0AB0) | 第3章 |
| TypeIdentifier | 设备/模块的固件级唯一标识,Openness 创建设备的关键参数 | 第3章 |
| PublicAPI | Openness 程序集安装目录名,路径规律 ...\Portal V<版本>\PublicAPI\V<版本>\ | 第2章 |
| Copy Local = False | 引用程序集时必须设置的属性,否则程序集被复制到输出目录导致加载失败 | 第2章 |
| AssemblyResolve | .NET 程序集解析事件,Openness 的两种程序集定位方式之一 | 第2章 |
| ExternalSource | 外部源,TIA Portal 中承载 SCL/STL 文本并生成程序块的容器 | 第4章 |
| GenerateSource | 从外部源生成程序块的核心方法,Python 调用需 .NET 泛型 List | 第4章 |
| Simatic ML | TIA 的 XML 交换格式,用于块与 HMI 数据交换 | 第7章 |
| AML / AutomationML | CAEX V2.15 模式的硬件组态交换格式,与 EPLAN 等 ECAD 工具对接 | 第7章 |
| OPC UA XML | OpcUaExportProvider 导出的 OPC UA 数据交换格式 | 第7章 |
| SiVArc | 电气接线数据(Simulation of Electrical Arc)自动化接口 | 第7章 |
| Telegram | 驱动报文,SINAMICS 与上位机/PLC 的数据交换报文结构 | 第8章 |
| BICO | SINAMICS 的信号互连机制,BINator + ConnectOr 的组合逻辑 | 第8章 |
| DriveObject | Startdrive 中的驱动对象,S120 多轴系统含多个 | 第8章 |
| DriveObjectContainer | 容纳 DriveObject 的服务容器,通过 GetService 获取 | 第8章 |
| OnlineProvider | 提供 PLC 上线/下线(GoOnline/GoOffline)的服务 | 第6章 |
| DownloadProvider | 提供 PLC 下载的服务,核心方法 Download | 第6章 |
| StationUploadProvider | 提供”站点上传”(从 PLC 反向读取组态)的服务 | 第6章 |
| StartModules / StopModules | 下载前后控制 CPU 模块启停的设置类,绑定在下载流程内 | 第6章 |
| Project Bases | 项目基线,标准化交付模板的工程化概念 | 第9章 |
| MOT | Mass Operation Tool,西门子官方批量运维工具(Entry 109986645) | 第9章 |
| MCP | Model Context Protocol,让 AI Agent 调用 Openness 的协议封装 | 第9章 |
| pythonnet | Python 调用 .NET 托管代码的桥接库,Openness 的 Python 路线基础 | 第2章 |
| S7_Optimized_Access | DB 属性,决定是否启用优化访问(无固定字节偏移) | 第4章 |
| EngineeringException | Openness 的基础异常类型,含 ErrorCode 属性 | 第11章 |
| TIA Portal Add-In | TIA 插件,运行在 TIA 进程内、与 TIA 同权限,与 Openness 互补 | 第12章 |
H. 本书资料来源与质量说明
抓取总量:146 篇一手资料,分布如下:
| 类别 | 篇数 | 说明 |
|---|---|---|
| A 官方西门子资料 | 27 | 官方手册 PDF 全文提取(含 V21 系统手册 1692 页)、应用示例条目 |
| B 中文社区文章 | 24 | CSDN、西门子中国论坛、PLC技术网、博客园、与非网等 |
| C 英文博客文章 | 27 | industrial monitor direct、HubWiz、Comquent、论文等 |
| D GitHub 开源项目 | 45 | 官方代码片段仓库、NuGet 包族、社区库、AI/MCP 项目 |
| E 进阶专题资料 | 23 | HMI、下载调试、通信集成、Startdrive、CI/CD、性能工程化、冗余、Addin |
质量控制说明(重要):
- ⚠️ API 签名存疑标注:部分英文博客(尤其 industrial monitor direct)存在明显的 AI 生成 API 幻觉,例如使用
Siemens.TiaPortal.Openness命名空间(正确为Siemens.Engineering)、Devices.CreateDeviceItem()(正确为CreateWithOrder/CreateWithItem)、TiaPortal.GetTiaPortalContext()等非标准 API。全书凡引用此类来源处均已标注 ⚠️,落地前必须对照官方 Programming Manual 核实。 - 已核实的关键更正(与社区流传说法相反,以官方为准):
- 不存在
DownloadWithoutStop与SoftwareDownloadAPI —— V19/V21 手册全文检索 0 命中,不停机下载通过 pre/post delegate 控制StartModules/StopModules实现。 DownloadOptions实际是四值位标志(None/Hardware/Software/SoftwareOnlyChanges),不止社区描述的两个。DeviceItemClassifications.HM官方定义为 “header module”,仅 SINAMICS Integrated / NX module / ADI4 三类充当,G120 的 CU 落在None(0)——社区用Classification == HM定位 G120 的写法与官方冲突。- HMI 日志变量官方 API 为
HmiTag.LoggingTags→HmiLoggingTag,非社区流传的HmiTag.Logging。 pXXX.Bits按位名称字母升序索引而非位序号(官方注释:Bits[2] == pXXX.3),此点反直觉须特别注意。- 官方拼写:
PossibleDriveObjectTypes(少一个s)、RedunancyMode(字符串字面量内拼写错误),照抄官方写法方能工作。
- 不存在
- 未能取得正文的条目已在索引中标注「仅元数据」,共 5 个官方应用示例(109757232、38043750、109762849、109822030、109999915)。原因:西门子部分页面为 JS 渲染的 SPA,静态抓取仅返回导航骨架。
- GitHub 星标数与仓库状态均为 2026-10-04 抓取当日快照,会随时间变化。已特别标注
mgrillo75与OsamaS99两个仓库均为Repsay/tia-openness-api-client的 0★ fork 且 README 逐字相同,请勿误选停更分支。 - 订货号缺口:CPU 1515-2 PN、DI 32x24VDC HF 等已有 raw 确证;DQ 32x24VDC/0.5A ST、AI 4xU/I 2x4..20mA ST、AQ 4xU/I 2x0..10V ST 三个模块的订货号后缀未获确证,书中已逐行标 ⚠️,须在 TIA 硬件目录核对。