简介:一份面向自动化工程师与PLC开发者的西门子TIA Portal与Openness联合开发示例项目,集成TIA Portal编程环境和Openness开放接口,能够大幅降低重复性编程工作。项目覆盖从基础PLC逻辑设计到Openness自动化生成的关键路径,适合有一定PLC基础、希望提升工程效率的工程师学习与二次开发。资源共421个文件,以312个PNG截图、72个C#源码、20个XAML界面文件为主,另有配置文件、解决方案、阅读说明等,其中PNG截图展示运行流程与界面效果,C#文件承载Openness调用与业务逻辑,XAML用于界面布局,目录结构清晰,便于按模块查阅;压缩包约1.28MB。目前已有598人学习下载。通过学习,读者可掌握启动TIA Portal、检查版本、管理日志、设计ViewModel与交互界面等核心实现,结合源码和截图理解Openness API的调用方式,快速搭建自己的自动化程序生成工具,也可作为毕业设计或企业工具开发的基础框架。 先说结论:Openness 这东西,用好了就是提效神器。我这次做的小示例,就是用 C# 调用 TIA Portal Openness 接口,实现“一键批量创建数据块(DB)”。平时在博图里手动建一个 DB 不麻烦,麻烦的是几十个结构类似、只有地址和名称不同的 DB 一个个点鼠标。把这种重复劳动交给脚本去跑,才是自动化工程师该干的事。这篇博客就把我这次从零开始写示例的过程、踩过的坑、以及几个关键代码片段完整记录下来,给准备入坑 Openness 的朋友做个参考。
1. 这个示例到底做了什么
1.1 先说说 Openness 是什么
Openness 是 TIA Portal 提供的一套 .NET 编程接口,说白了就是西门子给博图开的“后门”——允许我们自己写程序去操作博图里的工程文件。通过它,你可以不打开博图界面,在后台完成打开项目、创建 PLC、添加数据块、读写变量、编译、下载等操作。
这里有个概念必须先理清:Openness 不是一个独立软件,它是博图安装包里的一个可选组件。装博图的时候如果没勾选,后面安装目录里是没有相关 DLL 的。我见过不少人折腾半天代码,最后发现是组件没装,白费劲。
它典型的应用场景包括:批量生成 PLC 变量表、批量创建数据块、自动生成 HMI 画面、项目标准化模板的搭建、CI/CD 里做自动化编译验证。一句话概括:凡是你在博图里需要重复操作的事情,几乎都可以用 Openness 写脚本批量完成。
1.2 为什么选择“自动生成 DB”作为切入口
我这次拿“自动生成数据块”开刀,原因有三。一是数据块在工程里最常见,几乎每个项目都有批量建 DB 的需求;二是 DB 的结构相对规整,适合用表格数据驱动生成,容易验证结果;三是官方文档对 DB 创建的示例还算完整,遇到问题好查资料。
这个小示例要实现的效果很简单:我准备一份 Excel 配置表,里面写好每个数据块的名称、编号、变量名、数据类型、初始值,然后运行程序,程序自动打开一个指定路径的博图项目,把这些 DB 全部创建好,最后保持项目。整个过程不用人工触碰博图界面,跑完打开项目一看,DB 全部到位。

2. 环境准备:版本、授权、开发语言
2.1 版本匹配是个大坑
Openness 最折磨人的不是代码本身,而是版本对应关系。TIA Portal 的版本、Openness 组件的版本、.NET Framework 的版本,三者必须严格匹配。我用的是 TIA Portal V17,对应的 Openness DLL 是 Siemens.Engineering.dll V17 版本,开发环境是 Visual Studio 2019 + .NET Framework 4.7.2。如果你用 V15.1 或者 V16,DLL 路径不一样,API 也有一些细微差异。
注意:Openness 组件的版本必须与博图主程序版本一致,混用版本最常见的结果就是运行时抛异常,提示找不到指定模块或者版本不匹配。踩过这个坑的人应该都懂。
另外还有个关键点:运行 Openness 程序的机器上,必须安装了对应版本的 TIA Portal,而且需要 Openness 授权。注意这个授权和博图的使用授权是两回事,需要单独安装 Openness 选项组件。没有授权的话,程序初始化时就会报错。
2.2 开发环境搭建步骤
我自己搭建环境的步骤如下,照着做基本不会出错。第一步,确认博图版本,在博图安装目录下找 Siemens.Engineering.dll,一般在安装路径的 PublicAPI\V17 文件夹里。找到后记住路径,后面引用要用。
第二步,打开 Visual Studio,创建一个控制台应用程序,目标框架选 .NET Framework 4.7.2 或 4.8。不建议选 .NET Core 或者 .NET 5+,因为 Openness 官方支持的还是 .NET Framework,跨平台的事暂时别想。
第三步,添加引用,右键项目选择“添加引用”,把 Siemens.Engineering.dll 和 Siemens.Engineering.Hmi.dll 都引进来。如果后续要操作 HMI 相关的功能,还需要 Siemens.Engineering.Hmi.dll,这次做 DB 不需要,但建议一起引用。
第四步,在代码文件顶部加上 using Siemens.Engineering; 和 using Siemens.Engineering.HW;,这两个命名空间是最常用的。
3. 核心代码:从打开项目到批量建 DB
3.1 建立连接与打开项目
整个 Openness 程序的骨架就是一个 TiaPortal 对象的生命周期:创建实例、打开项目、操作、保存、退出。先看最基础的连接和打开项目代码:
using Siemens.Engineering; using Siemens.Engineering.HW; // 创建 TiaPortal 实例,不显示界面 using (TiaPortal tiaPortal = new TiaPortal(TiaPortalMode.WithoutUserInterface)) { // 打开指定路径的项目 Project project = tiaPortal.Projects.Open(@"D:\TestProject\MyProject.ap17"); // 获取 PLC 对象(假设项目里只有一个 PLC) PlcSoftware plcSoftware = GetFirstPlcSoftware(project); // TODO: 在这里执行创建 DB 的逻辑 // 保存并关闭项目 project.Save(); project.Close(); }这里有几个细节要提醒。TiaPortalMode 有两种:WithUserInterface 和 WithoutUserInterface。第一次调试的时候建议用 WithUserInterface,这样你能看到博图界面被自动操作的过程,方便理解程序在干什么。调试通过后再改成 WithoutUserInterface,全后台运行,速度更快。
打开项目需要项目文件的绝对路径,注意博图的项目文件是以 .ap17 结尾的文件夹(V17 版本),不是单个文件。路径写错或者权限不够,会直接抛异常。
GetFirstPlcSoftware 这个方法是自己写的辅助方法,用来遍历项目里的设备找到 PLC 的软件对象。核心逻辑是用 project.Devices 遍历设备,再从设备里找 DeviceItems,判断是否包含 PlcSoftware。我直接分享这个辅助方法:
static PlcSoftware GetFirstPlcSoftware(Project project) { foreach (Device device in project.Devices) { foreach (DeviceItem deviceItem in device.DeviceItems) { if (deviceItem.GetType() == typeof(PlcSoftware)) { return (PlcSoftware)deviceItem; } } } return null; }这段代码在只有一个 PLC 的项目里没问题。如果项目里有多个 PLC,你需要在代码里指定设备名称,通过 device.Name 来匹配,否则拿到的是第一个 PLC。
3.2 读取 Excel 配置表
创建 DB 之前,需要把配置数据读进来。我这里用的方式是读取 Excel 文件,也可以改成读取 XML、JSON 甚至数据库,本质都一样。为了不引入额外依赖,我直接用了 NPOI 库来读 Excel,当然你也可以用 COM 方式调 Excel,但 NPOI 不需要装 Office,更省事。
配置表的结构大致是这样的:
| DB编号 | DB名称 | 变量名 | 数据类型 | 初始值 | 注释 |
|---|---|---|---|---|---|
| 1 | DB_Start | MotorRun | Bool | false | 电机运行 |
| 1 | DB_Start | MotorSpeed | Real | 0.0 | 电机转速 |
| 2 | DB_Alarm | AlarmCode | Int | 0 | 报警代码 |
| 2 | DB_Alarm | AlarmTime | DInt | 0 | 报警时间 |
读取 Excel 的代码就不完整贴了,核心就是用 NPOI 打开工作簿,遍历行和单元格,把每一行封装成一个变量定义的类对象。然后按 DB编号分组,一个编号对应一个数据块,组内的行就是这个 DB 里的变量。
这里有个设计上的小心得:Excel 表格设计的时候,尽量把公共属性比如 DB名称、DB编号放在每组的第一行就行,程序里分组时取第一条记录的属性作为 DB 的属性。这样表格更简洁,人工填写不容易出错。
3.3 批量创建数据块
创建数据块的 API 是 PlcSoftware.BlockGroup.Blocks.CreateFrom,方法签名有好几个重载,我用的是传入 BlockType 和创建参数的版本。先看代码:
using Siemens.Engineering.SW.Blocks; using Siemens.Engineering.SW.Tags; using Siemens.Engineering.SW; // 获取 DB 块组(默认的"程序块"文件夹) PlcBlockGroup blockGroup = plcSoftware.BlockGroup; // 创建数据块 PlcBlock createdBlock = blockGroup.Blocks.CreateFrom(blockType, createParams, blockName);更具体一点,创建 DB 的完整写法是:
// 创建数据的参数,传一个空数组即可 var createParams = new object[] { }; // PLC 数据块对应 BlockType.DB PlcBlock dbBlock = plcSoftware.BlockGroup.Blocks.CreateFrom(BlockType.DB, createParams, dbName);前面这段只能创建空 DB,里面没有任何变量。如果要定义变量,需要拿到 DB 的托管对象(PlcBlock 的具体类型),然后往里面添加变量。DB 的变量操作是通过 PlcBlock 下的 AttributeSet 或者成员列表来做的,但不同版本 API 差异比较大。
我换一个更稳定的方式:先把 DB 创建好,然后用 Openness 的 XML 导出导入机制来填充变量。这个思路非常实用,后面会详细讲。
实际上 Openness 对 DB 内部变量的操作一直不算友好,官方推荐的惯用做法是:先在博图里手工做一个模板 DB,导出成 XML,然后程序里替换 XML 中的变量名和数据类型,最后通过 Import 方式把 XML 导入生成新 DB。这种方式稳定可靠,而且可以一次生成几十个变量,效率极高。
// 创建临时空 DB PlcBlock tempDb = plcSoftware.BlockGroup.Blocks.CreateFrom(BlockType.DB, new object[] { }, dbName); // 删除这个临时 DB(如果不需要的话) // plcSoftware.BlockGroup.Blocks.Delete(tempDb);完整流程我后面在实操部分再展开,这里先把 XML 导入导出机制的思路说清楚。这个机制的底层逻辑就是:任何 PLC 块都可以被序列化成一个 XML 文档,也可以从 XML 文档反序列化生成新的块。这就非常适合做“模板套用”。
4. XML 导入导出:真正高效的核心玩法
4.1 导出模板与 XML 结构
为什么说 XML 机制是做批量 DB 的核心?因为 Openness 对块内部变量的操作接口非常有限,很多版本的 API 都不支持直接添加带完整属性的变量。而 XML 机制不受这个限制,你可以定义任意复杂的变量结构,包括数组、结构体、自定义类型。
第一步是准备模板。在博图里手动新建一个 DB,添加所有需要的变量,然后导出。注意:这个模板 DB 不需要填写具体名称和初始值,只需要定义好结构。导出操作可以通过博图界面右键选择“从块生成源”,也可以直接用代码导出。
我直接用代码导出模板 XML,参考代码如下:
// 获取已经存在的一个模板块 PlcBlock templateBlock = plcSoftware.BlockGroup.Blocks.Find("TemplateDB"); // 导出到字符串 string xmlContent = templateBlock.Export();这个方法返回一个字符串,这个字符串就是整个 DB 的 XML 描述。打开这个 XML,你会看到类似这样的结构:
<Document> <Engineering version="V17" /> <SW.Blocks.DB ID="0"> <AttributeList> <AutoNumber>true</AutoNumber> <Interface> <Sections xmlns="http://www.siemens.com/automation/Openness/SW/Interface/v5"> <Section Name="Static" Access="Public"> <Member Name="MotorRun" Datatype="Bool"> <StartValue>false</StartValue> <Comment><MultiLanguageText Lang="zh-CN"><Text>电机运行</Text></MultiLanguageText></Comment> </Member> <Member Name="MotorSpeed" Datatype="Real"> <StartValue>0.0</StartValue> <Comment><MultiLanguageText Lang="zh-CN"><Text>电机转速</Text></MultiLanguageText></Comment> </Member> </Section> </Sections> </Interface> </AttributeList> <ObjectList> <SW.Blocks.CompileUnit ID="1" CompositionName="CompileUnit" /> </ObjectList> </SW.Blocks.DB> </Document>注意 Namespace 里写着 v5,不同版本这个数字可能不同,V15 是 v4,V16 和 V17 是 v5。这个细节很容易被忽略,但你如果跨版本导入导出,就可能遇到兼容性错误。
4.2 用代码生成 XML 并导入
理解了 XML 结构,接下来的思路就清晰了:我们不需要手动拼 XML,而是用代码来操作。我这里的方案是读取 Excel 数据后,动态拼接 XML 字符串,然后调用 Import 方法。
动态生成 XML 我用的方式比较简单——字符串模板填充。先把上面的模板 XML 存成一个文本文件作为模板,里面用特殊占位符标记变量名、数据类型、初始值、注释,然后在程序里用 string.Replace 替换。
string templateXml = File.ReadAllText(@"D:\temp\template.xml"); // 对每个变量定义,生成对应的 Member XML 片段 StringBuilder membersBuilder = new StringBuilder(); foreach (var variable in variableList) { string memberXml = $@"<Member Name=""{variable.Name}"" Datatype=""{variable.DataType}""> <StartValue>{variable.InitialValue}</StartValue> <Comment><MultiLanguageText Lang=""zh-CN""><Text>{variable.Comment}</Text></MultiLanguageText></Comment> </Member>"; membersBuilder.Append(memberXml); } // 替换模板中的占位符 string finalXml = templateXml.Replace("<!--VARIABLES-->", membersBuilder.ToString()); finalXml = finalXml.Replace("{DBName}", dbName); // 导入生成新的 DB PlcBlock newDb = plcSoftware.BlockGroup.Blocks.Import(finalXml);这段代码的核心逻辑就三句话:拼 XML、替换占位符、调用 Import。我还加了变量名称合法性校验,比如名称不能有中文、不能有空格、不能以数字开头,这些规则在博图里也是硬性的,提前校验能避免导入时报错。
提示:字符串拼接 XML 有一个陷阱——XML 特殊字符会被破坏。变量注释里如果包含 &、<、> 这些字符,直接替换会生成非法 XML。我的做法是先调用 SecurityElement.Escape 方法对变量值和注释做转义处理,再拼进模板。
用 Import 方式生成的 DB 是完整的,包含所有变量,打开博图后你会看到每个变量名称、数据类型、初始值、注释全都在,和手工操作结果完全一致,甚至还能直接编译通过。
4.3 批量生成与性能优化
做好了上面两步,批量生成就水到渠成了。外层循环遍历 Excel 中的所有 DB 分组,对每一个分组执行一次“生成 XML + Import”。实际跑一个包含 50 个 DB 的工程,每个 DB 平均 20 个变量,整个过程大概耗时十几秒,比人工操作快太多了。
性能优化方面有两点建议。一是尽量减少项目保存次数,全部 DB 创建完成后再统一保存,而不是每个 DB 创建后都保存,保存操作非常耗时。二是尽量用 WithoutUserInterface 模式运行,界面模式会多消耗不少时间。
我这里把批量主流程框架写出来:
// 按 DB 编号分组 var dbGroups = variableList.GroupBy(v => v.DbNumber); foreach (var group in dbGroups) { string dbName = group.First().DbName; var variables = group.ToList(); try { string finalXml = GenerateDbXml(dbName, variables); plcSoftware.BlockGroup.Blocks.Import(finalXml); Console.WriteLine($"{dbName} 创建成功"); } catch (Exception ex) { Console.WriteLine($"{dbName} 创建失败: {ex.Message}"); } } project.Save();这里我故意在循环里加了 try-catch,单个 DB 创建失败不影响其他 DB 继续创建。实际使用中,一个 DB 的变量定义有问题,不应该拖垮整个批量任务。日志记录也很重要,跑完看控制台输出,失败的任务根据提示单独处理。
5. 编译与常见问题排查
5.1 对生成的 DB 做编译校验
DB 创建完不代表万事大吉。变量有没有拼写错误、数据类型是否合法、地址有没有冲突,这些都要通过编译来验证。Openness 也能做编译,代码很简单:
// 对指定软件编译 var compileResult = plcSoftware.Compile(); Console.WriteLine($"编译状态: {compileResult.State}"); if (compileResult.State == CompileState.Error) { foreach (var message in compileResult.Messages) { Console.WriteLine($"错误: {message.Description}"); } }编译结果里的 State 有三种:Success、Error、Warning。我建议把 Error 和 Warning 都打出来看看,Warning 虽然不影响使用,但往往预示着潜在问题,比如未初始化的变量、隐式地址分配冲突等。
这里还有一个经验:批量导入的 DB,有时候博图会分配重复的符号名(Symbol),编译时不会报错,但下载到 PLC 后会导致寻址异常。我的做法是,导入完成后用代码遍历所有 DB,检查 Symbol 是否存在重复,有重复的自动重命名。
5.2 新手最常见的几个报错
我整理一下新手入门 Openness 时最常遇到的问题,这些我都踩过,写出来给大家避坑。
第一个是“无法加载 DLL 或程序集 Siemens.Engineering”。这个问题九成是引用路径不对或者没安装 Openness 组件。检查两个地方:博图安装目录下有没有 PublicAPI 文件夹;程序的平台目标是否选对。如果没有组件,需要在博图安装程序里添加功能,勾选 Openness。
第二个是“拒绝访问”或“COM 异常”。这个一般是权限问题,程序必须以管理员权限运行。右键 Visual Studio 以管理员身份启动,然后再运行程序。另外,如果是 64 位系统,程序平台目标要选 x64,Openness 的 DLL 是 64 位的,选错了也会报莫名其妙的异常。
第三个是“版本不匹配”错误。程序集加载时校验 TIA Portal 版本,报这个错说明你的 Openness DLL 版本和安装的博图版本对不上。V17 的博图必须用 V17 的 DLL,不能用 V15 的替换。还有一个隐蔽情况:系统里装了多个版本的博图,程序引用的 DLL 被另一个版本的抢占,解决方法是把引用 DLL 的“复制本地”属性设为 True,让程序运行时使用自己的版本。
我把这些常见问题整理成一个速查表,方便大家对照排查:
| 报错信息 | 大概率原因 | 解决方案 |
|---|---|---|
| 无法加载 Siemens.Engineering | Openness组件未安装或引用路径错误 | 安装Openness组件,检查DLL路径 |
| 拒绝访问 / COM异常 | 权限不足 | 以管理员身份运行程序 |
| 版本不匹配 | DLL版本与博图版本不一致 | 使用对应版本DLL |
| 项目文件无法打开 | 路径错误或项目正在被占用 | 检查路径,关闭博图进程 |
| XML导入失败 | XML结构非法或变量重名 | 检查XML转义,检查变量名合法性 |
5.3 一个隐蔽的坑:项目文件占用
我要特别说一个隐蔽问题。Openness 打开项目后,博图项目文件会被锁定。如果你程序里没有正确关闭项目就崩溃了,这个锁会残留,导致下次打开项目时报错“项目正在被其他用户使用”。
解决办法是彻底关闭博图进程。打开任务管理器,把所有 Siemens.Automation.Portal.exe 进程结束掉,然后再运行程序。为了尽量避免这个问题,代码里要把项目关闭的操作写在 finally 块里,确保异常时也能释放项目文件锁。
6. 几个我特别想说的实操心得
第一点,用 Openness 之前,先在博图里把模板做好。模板的质量直接决定生成结果的质量。模板里变量的命名规范、数据类型规范、注释规范,这些标准化工作做好了,批量生成才有意义。我做模板的时候会刻意让结构覆盖各种情况:Bool、Int、Real、数组、结构体,方便后面各种项目套用。
第二点,先小后大,先单体后批量。刚开始写这个示例的时候,我先只处理一个 DB、三个变量,跑通全流程后再扩展到 50 个 DB。这个思路适用于所有 Openness 开发场景。千万别一上来就追求一步到位,出了错根本定位不了。
第三点,日志记录要有。我把每次生成的 XML 都存了一份副本,格式是“生成时间_DB名称.xml”。这样即使导入失败,也能根据 XML 排查是生成逻辑的问题还是导入逻辑的问题。批量任务跑完以后,查日志比跑着看控制台舒服多了。
第四点,别怕看官方文档和论坛。Openness 的官方文档虽然写得不算亲民,但里面有完整的类结构图和 API 清单,遇到问题去查一下类的继承关系,比瞎猜强得多。
最后再分享一个小技巧:如果只是临时验证 Openness 的某个 API 能不能用,不需要跑整个工程。可以单独建一个控制台工程,只写三五行代码,调用目标 API,然后输出结果。这个“最小验证法”帮我省了大量排查时间。Openness 的学习曲线比想象中要陡一点,但一旦跨过了版本和环境这道坎,后面就是一片坦途。整个自动化生成的流程跟生产线一样,输入一份表格,输出一整批完整可用的数据块,这种成就感真的是手点鼠标完全给不了的。
本文还有配套的精品资源,点击获取