
去年年初我接手了一条汽车零部件产线的信息化改造上位机是西门子WinCC 7.5现场128个变量已经稳定跑了一年多。客户的需求其实不复杂——每批产品都要有一张配方报表包含配方名称、参数设定值、实际执行值、起止时间并且能按批次号追溯。麻烦的是客户的生产管理系统用的是SQL ServerMES那边也指定要用数据库对接而WinCC自带的配方组件虽然能存配方但要让它自动生成报表、把数据导给MES就很别扭。传统做法往往是去改WinCC的C脚本甚至有人会在WinCC运行时内部数据库的业务表里做文章一旦系统升级项目就崩。我这次换了个思路底层源码不动只把变量导入和配方报表生成放到运行层的VBS脚本和SQL Server里完成。整个方案上线后稳定运行到现在这篇就把实现过程、表结构设计、脚本模板和踩坑记录完整复盘一遍。1. 为什么我把配方数据从WinCC内部组件搬进SQL Server1.1 传统配方管理在“单机生产”场景够用但一涉及追溯就力不从心WinCC自带的配方组件Recipe确实能完成配方的新建、下载、上载配合PLC做参数切换也很方便。但这类组件有一个天然定位它是给“单机操作员”用的。操作员在WinCC画面上选一个配方点下载PLC参数就变了。这套闭环里没有“历史追溯”的环节。客户这次的需求是两个维度一是每批产品完成后要能输出一张包含所有参数设定值和执行值的报表二是这批数据要能跟MES系统的批次号对上后续要做SPC分析、不良追溯。这些需求靠WinCC自带配方组件很难直接满足——你可以在画面上把数据导成CSV但没人想每天手动导一次更不要说用报表工具自动拉数据了。所以把配方数据落到独立的SQL Server业务库里是最自然的选择。1.2 SQL方案适合哪类产线我说清楚适用边界不是所有WinCC项目都需要上SQL。如果项目只是单机设备、配方数量少、又不需要跟任何系统对接只靠WinCC内部配方组件就够了。我判断一个项目要不要上SQL方案主要看三点第一是否有跨系统数据需求。比如MES、ERP要取配方数据或者工厂层面要做统一报表平台那么数据库是唯一能被多个系统同时访问的接口。第二追溯周期是否跨批次。现场往往要求“三个月后还能查到某批产品的设定值”这种要求用WinCC内部归档去做查询性能和文件管理都不方便而数据库天然适合长期归档。第三配方参数是否已经很多。当配方数量超过几百个、且每个配方包含几十个变量时靠WinCC画面手动翻页管理已经低效SQL查询、批量导入导出都是明显优势。这套方案我建议在WinCC 7.0及以上版本使用因为VBS脚本引擎在这些版本里已经非常稳定。如果是WinCC Flexible时代的老项目脚本能力会受限不太适合直接套用。1.3 为什么这套方案能“不动源码”这里先说清楚一个常见误解。标题说的“无需修改源码”不是指完全不碰任何代码而是指不需要改动WinCC项目的底层实现——比如项目原有的C脚本、画面逻辑所用到的全局函数、内部归档配置等。WinCC工程的底层源码是西门子的框架代码加上工程师在项目里编写的C函数这部分修改起来风险很大。任何一个C脚本的改动都可能要重新编译、重新下载工程甚至在项目运行状态中引起不可预期的问题。我采用的方案是在WinCC运行系统里增加VBS脚本和独立的SQL Server业务库。VBS脚本在WinCC里是应用层的脚本不依赖项目底层框架SQL Server业务库也是独立于WinCC运行时数据库的存在不碰西门子自带的系统表。业务逻辑全部外置原来的画面逻辑、变量归档机制一概不动。这样做最大的好处是以后项目升级、WinCC补丁更新、甚至整站迁移新方案都不会对旧工程产生负面影响。2. 数据模型设计配方表该怎么建变量字段怎么映射2.1 配方头、配方明细、生产批次三张核心表我设计数据库时没有把配方数据塞到一张大宽表里而是拆成了三张表配方头表RecipeHeader、配方明细表RecipeDetail、批次历史表BatchHistory。这个拆分逻辑是每个做数据库的人都会告诉你的事配方模板和实际生产记录是两种不同的东西放进同一张表很快就会被需求逼着返工。配方头表保存的是“一个配方是谁”比如配方名称、产品编码、创建时间。配方明细表保存的是“一个配方里有哪些参数和值”比如变量名、设定值、单位、顺序号。批次历史表保存的是“某一次实际生产用了哪个配方、什么时间段、结果如何”。三张表之间通过RecipeID和BatchNo关联。建表语句我直接给出来字段我都简化过便于理解CREATE TABLE RecipeHeader ( RecipeID int IDENTITY(1,1) PRIMARY KEY, RecipeName nvarchar(64) NOT NULL, ProductCode nvarchar(32) NOT NULL, CreateTime datetime NOT NULL DEFAULT GETDATE(), Status tinyint NOT NULL DEFAULT 0 ); CREATE TABLE RecipeDetail ( ID int IDENTITY(1,1) PRIMARY KEY, RecipeID int NOT NULL, TagName nvarchar(64) NOT NULL, SetValue real NULL, ActualValue real NULL, Unit nvarchar(16) NULL, SortOrder int NOT NULL ); CREATE TABLE BatchHistory ( ID int IDENTITY(1,1) PRIMARY KEY, BatchNo nvarchar(32) NOT NULL, RecipeID int NOT NULL, ProductCode nvarchar(32) NOT NULL, StartTime datetime NOT NULL, EndTime datetime NULL, Qty real NULL, Result nvarchar(16) NULL );这里有个细节RecipeDetail表的ActualValue字段是用来存生产过程中回读的实际执行值的。很多做配方系统的人只存设定值但客户要追溯的时候一定会问“当时设定的是100实际执行到99.8到底算不算达标”所以从设计第一天起就把设定值和实际值分开存后面做报表、做SPC分析都轻松很多。2.2 变量映射规则配置表比硬编码脚本更可靠WinCC变量名通常长得像“Recipe_Temp_Set”、“PLC_Pressure_Actual”这种。如果把这些变量名一条条写死在VBS脚本里脚本会变得极其冗长而且以后现场加一个变量还得打开脚本改一行代码这种做法显然不叫“自动化”。我的做法是在SQL Server里建一张变量映射配置表脚本启动时先读这张表再根据表里的变量列表去读取WinCC标签。映射表大概长这样CREATE TABLE TagMapping ( TagID int IDENTITY(1,1) PRIMARY KEY, TagName nvarchar(64) NOT NULL, DataType nvarchar(16) NOT NULL DEFAULT real, DisplayName nvarchar(64) NULL, IsActive bit NOT NULL DEFAULT 1 );脚本运行时执行一条查询就能拿到所有需要导入的变量清单SELECT TagName, DataType FROM TagMapping WHERE IsActive 1 ORDER BY SortOrder以后需要增加变量只需要往这张表里插入一条记录完全不需要再碰脚本代码。这个设计让“无需修改源码”的效果达到最大——变量增减都只改数据库配置源码层面零改动。2.3 时间戳与主键报表追溯的命根子做配方报表最容易忽略的是时间戳的设计。WinCC变量读出来的值和记录发生的时间必须一一对应。我强烈建议每条写入数据库的记录都包含两个时间概念一个是WinCC侧的时间标签一个是数据库服务器的写入时间。这样可以排查是采集延迟还是数据库本身的问题。批次号是另一个需要提前统一的字段。MES、ERP、设备层往往对批次号的命名规则理解不一致。我在方案里要求客户提供批次号的生成规则通常由PLC通过变量给到WinCC或者由MES下发。批次号是BatchHistory表的主键逻辑标识我在建表时没有把它设成自增主键而是用自增ID做物理主键BatchNo作为逻辑主键并加上唯一索引CREATE UNIQUE INDEX UX_BatchNo ON BatchHistory(BatchNo);这样做的原因是批次号有业务含义而ID仅供关联使用。如果你把BatchNo设成物理主键一旦客户说“批次号规则要改”你就要动主键那是灾难级的返工。3. 变量导入SQL ServerWinCC侧脚本到底怎么写3.1 连接SQL Server的脚本模板WinCC的VBS脚本可以通过ADODB操作任何支持OLEDB的数据库。我这里用的连接字符串是SQLOLEDB.1如果你服务器上装了SQL Server 2012以上版本也可以换成SQLNCLI11或者MSOLEDBSQL后者更稳定一些。连接参数里尤其注意Timeout要设置否则数据库临时故障时脚本会卡死。Dim conn Set conn CreateObject(ADODB.Connection) conn.ConnectionString ProviderSQLOLEDB.1;Data Source192.168.1.20;Initial CatalogRecipeDB;User IDwincc_rw;PasswordYourPassword;Timeout5 conn.Open连接字符串里的User ID不建议用sa。数据库账号要单独建一个具备读写业务库权限的账号这样即使脚本被误改也不会威胁到WinCC系统库和SQL Server其他库。这个权限划分在我后来排查问题时帮了大忙——可以快速定位是权限问题还是脚本问题。3.2 变量读取与写入的整段VBS脚本逻辑核心脚本的逻辑分三步从配置表读取变量清单、循环读取WinCC标签值、批量写入SQL Server。下面是我项目里简化后的可运行模板Dim conn, rs, cmd, tag, value Dim tagName, dataType, strSQL, recipeId, batchNo Set conn CreateObject(ADODB.Connection) conn.ConnectionString ProviderSQLOLEDB.1;Data Source192.168.1.20;Initial CatalogRecipeDB;User IDwincc_rw;PasswordYourPassword;Timeout5 conn.Open 读取变量映射配置 Set rs CreateObject(ADODB.Recordset) rs.Open SELECT TagName, DataType FROM TagMapping WHERE IsActive1, conn, 1, 1 读取当前配方ID和批次号这两个变量由PLC/MES下发 recipeId HMIRuntime.Tags(Recipe_CurrentID).Read batchNo HMIRuntime.Tags(Batch_CurrentNo).Read 循环每个映射变量 Do While Not rs.EOF tagName rs.Fields(TagName).Value dataType rs.Fields(DataType).Value Set tag HMIRuntime.Tags(tagName) value tag.Read 使用参数化方式写入避免引号问题和类型错误 Set cmd CreateObject(ADODB.Command) cmd.ActiveConnection conn cmd.CommandText INSERT INTO RecipeDetail(RecipeID, TagName, SetValue) VALUES (?,?,?) cmd.Parameters.Append cmd.CreateParameter(RecipeID, 3, 1, , recipeId) cmd.Parameters.Append cmd.CreateParameter(TagName, 200, 1, 64, tagName) cmd.Parameters.Append cmd.CreateParameter(SetValue, 4, 1, , CDbl(value)) cmd.Execute rs.MoveNext Loop rs.Close conn.Close这段代码里最关键的一个设计是用ADODB.Command参数化写入而不是拼接字符串。很多人图省事会写成INSERT INTO ... VALUES ( tagName , value )看起来没问题实际上一旦变量的值是字符串且带有单引号或者某个数值变量读取结果是空值脚本就会报错。使用参数化后数据类型的转换和特殊字符的问题都由数据库驱动层处理。3.3 触发方式和“不碰底层源码”的边界WinCC里执行VBS脚本的触发机制有三种方式全局脚本周期触发、变量触发、画面按钮事件调用。我建议根据使用场景组合使用生产开始时把配方设定值写入数据库适合用PLC的状态字触发脚本生产过程中回读实际值适合用周期定时扫描报表生成则用画面按钮或批次结束信号触发。重点说一说环境安全性。以前很多工程师喜欢直接在WinCC运行系统的C脚本里操作数据库一旦写了内存分配不当的代码会导致整个WinCC Runtime崩溃。VBS脚本虽然性能比C脚本低但它是解释型语言有脚本引擎保护不会直接操作内存也不存在编译后无法回退的问题。我的项目在实施过程中经历了WinCC补丁升级升级期间旧工程没有任何改动VBS脚本和SQL脚本在重启后照常运行。这正是“应用层旁路”结构的好处业务逻辑挂在边上底层架构怎么变都影响不到它。4. 配方报表自动生成从数据库到Excel/HTML的落地4.1 报表数据查询逻辑SQL负责汇总脚本只负责展示报表的核心数据查询我的经验是尽量把汇总计算放在SQL里完成而不要在VBS里写循环求和。这样做的好处是性能和可维护性都更好。下面这条查询是报表的主查询SELECT H.RecipeName, H.ProductCode, B.BatchNo, B.StartTime, B.EndTime, D.TagName, D.SetValue, D.ActualValue, D.Unit FROM BatchHistory B INNER JOIN RecipeHeader H ON B.RecipeID H.RecipeID INNER JOIN RecipeDetail D ON B.RecipeID D.RecipeID WHERE B.BatchNo B20240218001 ORDER BY D.SortOrder实际项目中报表经常要统计一批产品的平均值、最大值、最小值以及是否超差。这些统计字段用SQL的聚合函数处理非常方便SELECT TagName, MAX(ActualValue) AS MaxValue, MIN(ActualValue) AS MinValue, AVG(ActualValue) AS AvgValue, COUNT(*) AS SampleCount FROM RecipeDetail WHERE BatchNo B20240218001 GROUP BY TagName脚本需要做的只是把这批结果填充到Excel的表格里。4.2 通过VBS把查询结果导出到Excel工厂环境下客户端一般安装了Office所以Excel导出是通用做法。VBS操作Excel的最简模板如下Dim xl, wb, ws, rs, row, col Set rs CreateObject(ADODB.Recordset) rs.Open SELECT ... FROM ... WHERE BatchNoB20240218001, conn, 1, 1 Set xl CreateObject(Excel.Application) xl.Visible False xl.DisplayAlerts False Set wb xl.Workbooks.Add() Set ws wb.Worksheets(1) ws.Cells(1,1) 配方名称 ws.Cells(1,2) 变量名 ws.Cells(1,3) 设定值 ws.Cells(1,4) 实际值 ws.Cells(1,5) 单位 row 2 Do While Not rs.EOF ws.Cells(row,1) rs.Fields(RecipeName).Value ws.Cells(row,2) rs.Fields(TagName).Value ws.Cells(row,3) rs.Fields(SetValue).Value ws.Cells(row,4) rs.Fields(ActualValue).Value ws.Cells(row,5) rs.Fields(Unit).Value rs.MoveNext row row 1 Loop wb.SaveAs D:\RecipeReports\ batchNo .xlsx, 51 wb.Close xl.Quit Set wb Nothing Set xl Nothing代码里SaveAs的第二个参数51表示xlsx格式这个参数值容易记错写xls是56写xlsx是51。我实际项目中有一台服务器只有WPS没有Office换成WPS后兼容性波动后来干脆在另一台没有办公软件的服务器上改用HTML报表方案了。4.3 没有Office的机器怎么办HTML报表方案如果报表服务器没有安装OfficeExcel导出方案会直接失败。我后来在集团标准化项目中改用HTML报表效果好很多。逻辑也很简单SQL查询结果拼接成一张HTML表格加上一些基础的排版样式保存成.html文件任何人都能用浏览器打开、打印成PDF。Dim fso, outFile, html Set fso CreateObject(Scripting.FileSystemObject) Set outFile fso.CreateTextFile(D:\RecipeReports\ batchNo .html, True, True) html htmlbody html html h2配方报表 batchNo /h2 html html table border1 cellspacing0 cellpadding4 html html trth配方名称/thth变量名/thth设定值/thth实际值/thth单位/th/tr Do While Not rs.EOF html html tr html html td rs.Fields(RecipeName).Value /td html html td rs.Fields(TagName).Value /td html html td rs.Fields(SetValue).Value /td html html td rs.Fields(ActualValue).Value /td html html td rs.Fields(Unit).Value /td html html /tr rs.MoveNext Loop html html /table/body/html outFile.Write html outFile.Close这个方案有一个隐性好处——浏览器对报表的渲染是客户端自己负责的不需要服务器装任何组件也不会出现Excel进程残留问题。5. 128个变量的实测表现和几个必须避开的坑5.1 实测数据别让脚本把WinCC运行时拖垮先给出一组我在WinCC 7.5 SP2、客户端/服务器架构下的实测数据128个变量配方导入频率为每次生产开始一次性写入生产过程中每5秒扫描一次实际值并更新到RecipeDetail表。整个过程中WinCC Runtime的CPU占用率增加约2%到3%内存增加约30MB。这个增量对于现代工控机来说完全可以接受。但是如果你想把变量采集周期缩短到100毫秒级比如当成历史趋势曲线采集来用这套方案就会出问题。VBS脚本和ADODB逐行写入的开销远大于C脚本的批量写入100毫秒频率下CPU占用会飙升甚至会导WinCC画面操作变卡。我建议用这套方案时写入频率不要超过1秒一次确实需要高频采样的点位应该用WinCC自带的变量归档功能然后再从归档表读取而不是直接用VBS高频写SQL。5.2 踩坑一连接字符串选错驱动报“未找到提供程序”这个问题在新电脑部署环境时经常遇到。SQLOLEDB.1是Windows自带的旧驱动一般都有但如果你用了SQLNCLI11目标机器就必须安装SQL Server Native Client否则会报“未找到提供程序”。而且Native Client的版本必须和连接字符串严格对应装错版本同样连不上。我的建议是开发环境用什么驱动部署环境必须保持一致并且把驱动安装包放在部署文档里。不要以为局域网里都是SQL Server就不用装驱动很多瘦客户端上只有最基础的.NET框架没有OLEDB驱动。5.3 踩坑二业务表建到了WinCC系统库里WinCC安装时会自带一个SQL Server实例很多工程师觉得“反正是同一台服务器的SQL Server就直接把业务表建在WinCC的项目库里”。这是我见过风险最高的做法。WinCC系统库的结构是西门子自己维护的里面全是系统视图和存储过程。你往里面建自己的表一时半会没问题但WinCC补丁升级、项目复制、系统自检都可能因为它找不到预期的库结构而出错。我们自己建库时一定要用SQL Server管理工具新建独立的数据库例如RecipeDB并给出独立的账号和权限。5.4 踩坑三VBS的Now和SQL Server的GETDATE()对不上WinCC脚本里用Now取到的是操作系统的本地时间SQL Server记录的时间取决于数据库服务器的设置。如果两台机器时区不一致或者Windows时间同步没配好报表里的开始时间就会错位。我最终的解法是统一以数据库服务器的GETDATE()为唯一时间来源写入时不在脚本里传时间而是调用SQL语句INSERT ... , GETDATE()。这样无论WinCC客户端在哪台电脑上、系统时间准不准最终归档的时间都跟着数据库走报表口径一致了。5.5 踩坑四Excel导出后EXCEL.EXE进程杀不掉VBS操作Excel后如果脚本中途报错往往会在后台留下一堆EXCEL.EXE进程时间长了服务器内存被吃光。我后来在脚本里加了一个保护性的进程处理在脚本开头用WMI查找并清理上次残留的Excel进程。这个操作要谨慎只能清自己脚本创建的实例不能把用户在桌面上打开的Excel也关了。更稳妥的做法是调整脚本里所有可能提前退出的路径确保无论正常结束还是异常结束都执行wb.Close、xl.Quit和Set obj Nothing。我习惯把On Error Resume Next和结束清理组合起来用也比让脚本直接中断好得多。6. 我在这套方案上的一些经验体会6.1 技术选型的核心不是炫技是降低后续维护风险这套方案运行一年半中间经历了产线扩建、点位从128增加到160、WinCC从7.5升级到8.1。因为所有业务逻辑都在应用层的VBS脚本和SQL Server里这些变动都没有影响到原工程只改动了映射表配置和SQL查询条件。这让我更确信在工控项目里技术方案好不好要看将来别人接手时能不能看懂、能不能维护而不是看当初写的时候有多高级。6.2 实际改动需要沿用的几个小习惯我现在做的每个类似项目都会固化成三件事第一变量映射表必须在数据库里留字段说明备注谁改谁写清原因第二SQL脚本统一放到版本管理目录哪怕现场没有用Git至少要有带日期的备份文件夹第三每个报表输出目录按批次号自动建子文件夹避免所有文件堆在一个目录里后面想找历史报表会极其痛苦。6.3 接下去还能扩展的方向这套数据库结构本身是开放的往上有MES/ERP对接往下可以接Kepware采集的非PLC数据横向还能做历史趋势分析。我目前就在把历史趋势曲线脚本也改为从SQL Server取数统一数据口径。如果你也在规划类似改造建议一开始就留好扩展字段比如产品编码、质量判定结果后续加需求时就不用大改表结构。最后提醒一句报表页面尽量别绑定WinCC自带控件用可替换的HTML和Excel方式以后换报表工具时你会感谢自己当初没写死。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。