首页 / 资讯中心 / 文章详情

WinCC历史数据报表自动化方案:VBScript脚本与Excel VBA实现

WinCC历史数据报表自动化方案:VBScript脚本与Excel VBA实现 ★ FEATURED ARTICLE
1. 为什么非要做这套WINCC历史数据报表系统做工业自动化项目这些年几乎每个现场项目走到收尾阶段都会遇到同一个问题客户要报表。不是要画面上看一眼趋势而是要把过去一个班、一天、一个月的历史数据导出成Excel再按照厂里的固定格式整理成交班记录、产量统计、设备运行分析。最原始的干法是操作员定时从WinCC的趋势控件里截图或者用WinCC自带的变量记录导出功能把CSV拉出来再用Excel手工套格式。数据量小的时候还好一旦涉及几百个变量、跨几天甚至几个月的查询这套流程的效率和准确性就完全没法看了。我做过一个化工项目的交班报表客户要求每天早上七点自动生成前一天三个班次的产量、温度峰值、设备运行时长统计还要附带几条关键曲线的趋势图。WinCC自带的报表功能不是不能做但做出来的格式非常死板想插入厂里的Logo、调整合并单元格、按产品批次分组、把几个时间段的数据拼到同一张表里几乎要跟组态脚本较劲半天。后来我干脆绕开WinCC的报表组件直接在WinCC里写脚本抓历史归档数据落到中间文件再用Excel模板和VBA把数据填进去。这套系统做完之后报表生成时间从原来的人工四十分钟压缩到三分钟以内而且格式几乎不用改。这里想分享的就是这套方案的整体设计思路和核心实现细节。适用对象是那些已经在用WinCC、被报表需求反复折磨的工程师如果你刚接触WinCC也可以借此理解它的历史数据归档机制到底能做什么、不能做什么。整个系统不依赖额外购买的报表软件完全用WinCC自带的VBScript脚本、Windows的计划任务和Excel的VBA就能搭起来投入成本几乎为零。2. 系统架构与数据链路先想清楚数据怎么流2.1 三层分工避免一锅烩很多人在做这类功能时容易犯一个错误想在一个脚本里把取数、算数、生成Excel全部干完。这在数据量小的时候确实能跑但一旦变量多、查询跨度长脚本运行时间会成倍增加WinCC的画面操作也会跟着卡顿。我在这套系统里把整个流程拆成了三个独立环节每个环节只专注一件事。第一层是数据采集层跑在WinCC内部。核心任务是从WinCC的归档数据库里读取指定时间段的指定变量值把结果整理成结构化的中间文件我习惯用CSV或轻量级的SQLite库。第二层是报表生成层跑在装有Office的工程师站或服务器上由一个独立的VBScript或批处理脚本触发。它读取中间文件套用已经做好的Excel模板利用VBA宏填充数据、绘制图表、完成汇总计算。第三层是展示调度层负责定时触发和人工干预。定时触发用Windows任务计划程序每天凌晨或者交接班前自动跑一遍人工干预则在WinCC画面上加一个按钮操作员点一下就能立刻导出当前的数据。这种拆分的好处非常明显。WinCC脚本只处理取数和落盘写完就结束不会长时间占用组态的脚本引擎Excel层面的操作即使出错也不会影响WinCC本身的运行。中间文件作为数据交换层还方便调试——数据不对时先打开CSV看看采集层有没有问题不用一上来就查VBA。2.2 为什么用中间文件而不是直接让WinCC连Excel有人会问既然WinCC支持VBScript为什么不直接在脚本里创建Excel对象、写单元格一步到位我在第一个版本里确实是这么做的结果碰到两个问题。第一个是性能问题。WinCC的VBScript运行在组态软件的脚本宿主里频繁调用Excel的COM对象接口每一次单元格写入都有巨大的跨进程开销。几百个数据点写入几十行要花十几秒。如果日报表涉及上千个变量、上万条记录脚本可能会跑几分钟期间画面操作明显迟滞严重时甚至会触发WinCC的脚本超时保护把进程杀掉。第二个是健壮性问题。COM方式调用Excel时WinCC脚本和Excel进程是绑定在一起的中途只要Excel弹出任何一个对话框比如是否保存更改脚本就挂起在那等用户操作。现场的操作员可不会像开发人员一样小心处理这些弹窗他们大概率直接关掉后果是两个进程全都卡死。用中间文件解耦后即使Excel生成失败数据采集任务仍然完成了重新触发一下生成环节就行不需要把整个取数流程再跑一遍。2.3 数据文件命名与归档策略中间文件的命名和归档看着是小问题实际用起来却非常关键。我建议按Tag_YYYYMMDD_HHMM.csv的格式命名每个查询周期一个文件。如果查询跨度很长文件名里带上开始和结束时间比如Report_20250101_0000_to_20250131_2359.csv避免覆盖。文件存放路径建议放在非系统盘、且WinCC和Office都有读写权限的目录下。这里提醒一下很多现场工程师站为了安全会设置用户权限隔离WinCC服务和Office可能运行在不同账户下中间文件如果放在某个人账户的桌面另一个人可能根本没权限访问。最稳的做法是在D盘建一个公共目录明确设置Users完全控制权限。这个细节我踩过坑某次报表无法生成排查了半天最后发现是服务账户没有中间文件的读取权限。3. WinCC侧历史数据采集脚本怎么写才稳3.1 读归档数据的三种常见途径从WinCC里拿历史数据日常能用的主要有三种方式WinCC在线表格控件、WinCC提供的VBScript函数GetTagMultiWait配合HMIRuntime.Tags、以及直接查询WinCC后台的SQL Server归档数据库。在线表格控件是最直观的方式拖到画面上配置一下就能显示数据但它本质上是个浏览器控件不适合把数据导出到其他程序。SQL Server直接查询是最灵活的方式WinCC的历史归档数据存储在SQL Server数据库里懂SQL就能写任意复杂的查询。但这种做法要求对WinCC的归档表结构非常熟悉而且一旦查询不当会给正在工作的归档数据库带来额外负载有影响现场稳定性的风险。我在实际项目里最常用的是第二种方式也就是通过WinCC的VBScript脚本配合自动化接口来读取。它的优势是官方支持、接口稳定不需要关心底层数据库表结构也不会直接碰SQL Server数据文件安全性最高。3.2 核心采数脚本骨架下面是一段我在项目里实际用过的WinCC VBScript采数脚本核心逻辑。它的工作流程是定义要查的变量列表和时间范围依次读取每个变量的归档数据把时间戳和数值写到CSV文件。Dim objTag Dim objArchive Dim objResult Dim strVarName Dim dtStart, dtEnd Dim fso, fileOut Dim csvLine 需要查询的变量名列表 Dim arrVars arrVars Array(ProcessTag_Temperature, ProcessTag_Pressure, ProcessTag_Flow) 查询时段默认查昨天全天 dtStart DateSerial(Year(Now), Month(Now), Day(Now) - 1) dtEnd DateSerial(Year(Now), Month(Now), Day(Now)) dtStart DateAdd(h, 0, dtStart) dtEnd DateAdd(h, 0, dtEnd) - 1 准备输出文件 Set fso CreateObject(Scripting.FileSystemObject) Dim strPath strPath D:\ReportData\Data_ Year(Now) _ Month(Now) _ Day(Now) .csv Set fileOut fso.CreateTextFile(strPath, True, True) fileOut.WriteLine Time,TagName,Value For Each strVarName In arrVars 打开变量归档对象 Set objTag HMIRuntime.Tags(strVarName) objTag.Read Set objArchive objTag.Archive 按时间范围查询间隔单位是秒 这里用1分钟取一个均值对长周期报表足够 objArchive.DeleteAllData objArchive.TimeBegin dtStart objArchive.TimeEnd dtEnd objArchive.Interval 60 objArchive.ReadData Dim i, ts, val For i 0 To objArchive.DataCount - 1 ts objArchive.Time(i) val objArchive.Value(i) csvLine ts , strVarName , val fileOut.WriteLine csvLine Next Next fileOut.Close3.3 脚本背后的几个关键认知Interval参数决定了归档数据的抽稀粒度。WinCC原始归档数据可能每隔一两秒甚至更快存一条直接全部导出会让CSV膨胀到几万行Excel处理起来很慢。我的习惯是日报表用1分钟间隔月报表用10分钟或1小时间隔这样既保留了趋势形态又控制了数据量。这里的取舍逻辑是报表目的是反映工艺趋势和设备状态不是事故追忆所以抽稀不会丢信息反而让表更清爽。objArchive.DeleteAllData这一步很多人不理解。它的作用不是删除归档库里的历史数据而是清空这次查询对象内部缓存的数据集避免上一次查询的结果混进来。每次循环查询不同变量之前都调用一次相当于把查询工作区的污渍擦掉。读出来的数据没有做单位换算。如果现场是压力变送器原始信号是0到100千帕的百分比值换算系数在WinCC变量属性里已经设置了线性校准读出来的数值是转换后的工程量这个没问题。但有一种情况需要注意如果你用内部变量做累计量比如流量累积读出来的值可能是原始计数器值需要再乘以脉冲当量。这类转换我不建议塞进采集脚本里而是放到Excel模板的公式里去处理原因一是在脚本里写太多业务逻辑难维护二是Excel里改系数直观现场仪表工自己就能调整。4. Excel模板化生成核心是样式与数据分离4.1 模板怎么设计Excel模板是本系统里最容易被低估的部分。很多人的做法是在VBA里逐行设置字体、边框、合并单元格结果宏代码写得比业务逻辑还长稍一改动就要重新调代码。我的做法正好相反把能静态设好的全部在模板里做掉VBA只负责往预留区域里填数值。我一般会准备三个sheet。第一个sheet叫参数设置放着本次报表的时间范围、班次编号、装置名称等基础信息。第二个sheet叫数据明细是纯粹的原始数据区第一行是列标题下面按行排列时间戳、变量名、数值不设置任何复杂的格式因为数据行数可能每次不一样格式设了也套不齐。第三个sheet叫报表展示这才是真正给人看的版面标题、Logo、统计表格框架、图表、签字栏都在这里预先画好数据区只留出固定的起始行。VBA打开模板后先往参数设置里写入本次的查询范围再把CSV内容批量填入数据明细最后用公式把明细区的数据引用到报表展示区域。这里有个重要的实践原则在模板单元格里写公式而不是在VBA里计算后填值。比如平均温度直接用AVERAGE公式引用明细区域最大压力用MAX公式开机时长用SUMIF加条件。这样做的好处是如果数据范围变了公式区域需要做动态调整这个可以通过少量VBA代码修改公式引用的行号实现而如果计算口径变了现场的工艺工程师自己就能打开模板改公式不用求他改代码。4.2 批量写入用数组别用单元格循环VBA操作Excel最大的性能杀手就是逐单元格读写。动不动几百行上万行数据逐个Cells(r,c)赋值会慢到让人怀疑人生。正确做法是先用采集脚本把数据整理成二维数组然后一次性赋值给Range对象。下面是一个典型的数组批量写入方式。 从CSV文件读取逻辑 简化处理假设arrData是已经解析好的二维数组 arrData(0,0)是第一个数据点的时间字符串 Dim arrData() As Variant ... 解析CSV填充arrData的逻辑在此省略 ... Dim targetSheet As Worksheet Set targetSheet ThisWorkbook.Sheets(数据明细) 一次性写入选定区域 Dim startRow As Integer startRow 2 假设标题占第一行 targetSheet.Range(targetSheet.Cells(startRow, 1), _ targetSheet.Cells(startRow UBound(arrData, 1), _ UBound(arrData, 2) 1)).Value arrDataRange.Value接数组的机制是Excel连续赋值里最快的路径之一。实测下来上万行三列数据写入时间不超过半秒而逐格写入可能要几十秒。这一条是整套系统能跑得流畅的关键基础值得反复强调。4.3 常见数据处理场景的表态方案根据经验工业报表里反复出现的数据处理需求是固定的把它们归纳好模板和VBA就能写得十分通用。下面这个表格总结了我常用的场景及对应做法。场景表现解决方案缺失值停机期间没数据表格出现空行不用Null填充保留空单元格AVERAGE类公式自动忽略空值避免人为填0拉低平均值时间对齐各变量采样时刻不完全一致在明细区用时间戳作为第一列展示区用LOOKUP函数按整点对齐取值单位换算不同变量量纲不一致在模板参数区维护换算系数单元格公式引用系数实现联动班次统计一日三班需要各自统计明细区增加班次辅助列用HOUR函数判断时段再用SUMIFS按班次汇总越限统计需要统计超温次数和超温时长用COUNTIFS判断超限点数持续超限时长通过在明细区用IF前后行时间差计算4.4 生成后的自动保存与命名报表生成完成后VBA代码需要处理一个容易被忽略的细节以当前日期时间命名文件并且不能覆盖当月历史文件。我的命名规则是产品线A_日报_2025年1月15日.xlsx。文件名不建议用report.xlsx这种固定名因为第二天生成就会覆盖前一天的文件也不建议文件名里带时分秒因为同一个班的报表补跑几次会产生多个副本现场归档时容易混乱。按天命名最合理一天一份如果需要补跑覆盖反而符合最终版本的直觉。保存时还有一点特别重要一定要用ActiveWorkbook.SaveAs完整路径加文件名不要用ThisWorkbook.Path相对路径因为模板文件很可能被放在某个受保护目录而输出目录是另一个位置。我在一个项目里因为模板和输出目录不在同一个路径结果VBA在保存时把生成结果写到了模板所在目录现场归档时发现所有报表都生成了但找不到文件排查了很久才发现路径引用问题。5. 实时数据展示给操作员看的活面板5.1 只靠Excel不算实时要在WinCC画面上做预览报表系统做的是事后数据但现场操作员真正天天盯着的是当前这一刻的数据。如果只做定时报表操作员想确认现在温度是不是正常还得等下一次报表生成那这套系统就没有真正解决现场问题。我的做法是在WinCC操作画面上加一个实时数据展示区域它不替代原有的趋势曲线和过程画面而是作为报表系统的预览窗口。具体来说这个区域显示当前几个核心变量的实时值、今日累计值、以及与昨天同一时刻的对比差值。数据来源不是Excel而是WinCC本身的实时变量通过画面周期触发脚本刷新。5.2 画面刷新脚本的思路下面是一段典型的画面刷新脚本片段。它会在画面运行时周期性执行把几个关键变量的当前值写入到画面中的IO域或文本对象里。Sub OnCycle() Dim tempValue, pressureValue, flowValue tempValue HMIRuntime.Tags(ProcessTag_Temperature).Read pressureValue HMIRuntime.Tags(ProcessTag_Pressure).Read flowValue HMIRuntime.Tags(ProcessTag_Flow).Read 写入画面对象 HMIRuntime.Screens(MainScreen).ScreenItems(IO_Temp_Cur).OutputValue tempValue HMIRuntime.Screens(MainScreen).ScreenItems(IO_Press_Cur).OutputValue pressureValue HMIRuntime.Screens(MainScreen).ScreenItems(IO_Flow_Cur).OutputValue flowValue End Sub这个脚本的触发方式是在画面属性里配置周期触发器一般设1秒或2秒刷新一次。需要注意IO域的OutputValue属性只支持显示字符串所以读取到的数值要先做格式化再赋值比如用FormatNumber(tempValue, 1)保留一位小数。如果直接把浮点数塞进去画面上可能显示成一长串小数。还有一个更隐蔽的坑脚本里读取变量后一定要检查返回状态因为变量读取失败时返回的可能是空值或错误标志直接赋值会让画面显示出-#-#之类的乱码。比较稳妥的写法是先判断读取结果状态失败则显示--。5.3 实时面板与报表生成的一键联动实时面板除了展示数值之外还可以兼任报表的触发入口。我在画面上加了一个立即生成报表按钮操作员点击后调用一个WinCC脚本这个脚本并不直接在WinCC内部生成Excel而是通过写入一个文本标记来触发计划任务程序。具体做法是WinCC脚本往指定路径写一个trigger.txt里面记录now和当前时间Windows计划任务每分钟检查一次这个文件是否存在如果存在就启动报表生成批处理批处理先删除trigger.txt再调Excel生成VBA。这套机制绕开了WinCC对COM对象调用可能带来的卡死风险又给了操作员一个直观的入口。实测效果是操作员点完按钮后大约30秒到1分钟就能在共享文件夹里看到新生成的报表文件整个过程自动化不需要去服务器上手动跑任务。6. 实测踩坑记录这些问题不写出来你迟早会遇到6.1 WinCC归档时间范围闭口段的坑WinCC的归档查询接口TimeBegin和TimeEnd包含两个边界值本身。第一次做的时候我以为查昨天全天是把开始时间设为昨天0点0分0秒、结束时间设为今天0点0分0秒结果发现今晨0点那一瞬间的归档数据也被包含了进来。当时朋友提醒我这不算bug是接口设计如此。正确做法是把结束时间减去一秒比如查昨天全天就设为今天0点0分0秒 - 1秒。这个边界差造成的误差在日报表里通常不明显但如果是精确到秒的交接班报表前一班最后一秒的数据会出现在后一班的统计里几次叠加就会被现场人员发现数据对不上。6.2 Excel进程残留导致文件锁死VBA操作完Excel后如果没有正确释放对象引用Excel进程会驻留在后台把生成的文件锁住。下次计划任务再跑时SaveAs会报文件被占用任务执行失败连锁反应是后面的任务全部失败直到有人手动杀掉Excel进程。这个问题在开发机上偶尔出现在服务器上特别容易累积。我的解决办法有两层。第一层是VBA代码里用Application.DisplayAlerts False抑制不必要的弹窗并在ThisWorkbook.Close之前显式赋值释放对象释放。第二层是在批处理启动VBA之前加一段taskkill /F /IM EXCEL.EXE命令利用Windows批处理主动清理可能残留的旧进程。这两层配合起来基本能保证任务链稳定执行但要注意批处理里加taskkill前要判断当日报表是否已经生成否则会把刚生成的文件句柄也杀掉。6.3 CSV解析时的时间格式陷阱CSV中间文件里记录的时间戳格式WinCC导出的默认格式可能是2025/1/15 0:00:00也可能带毫秒。Excel的VBA在解析时如果直接把字符串当日期处理毫秒部分会导致类型错误。我的处理方式是先对CSV做预处理把所有时间戳统一转成yyyy-mm-dd hh:mm:ss的文本格式再进行字符串分割提取小时、分钟、秒。这样做虽然多了一步转换但彻底规避了Excel对不同地区日期格式的自动识别问题。尤其是现场机器如果开了不同区域格式设置同样的CSV可能被解析成不一样的日期提前统一格式后这个问题就不复存在。6.4 大数据量写入Excel的隐形上限Excel单sheet的最大行数是1048576行看起来很多但工业报表经常出现百万行级的数据量。有一次月报表需要输出所有设备每秒的运行日志明细采集层没做抽稀直接导出了80多万行VBA的数组批量写入也花了近十秒文件体积膨胀到几十兆。后来我把月报表的数据粒度调整为分钟级行数降到一万行左右文件大小和生成速度都恢复正常。这里我学到的教训是报表不是数据备份输出粒度一定要跟业务问题匹配不同时间跨度采用不同抽稀策略而不是无脑全量导出。6.5 补报与重跑的幂等性设计最后讲一个使用层面的问题。现场经常会遇到换班后补数据、仪表维修后重算前一天报表的情况。如果生成逻辑没有做幂等设计重跑一次很可能产生脏数据。我的做法是在生成VBA里加一个判断如果目标日期的报表文件已经存在跳出一个输入框让操作员确认是否覆盖并且默认不覆盖。对不同版本报表文件后缀加一个_V2、_V3之类的版本号。同时采集层每次跑完都在CSV文件头加一行生成时间戳这样就算同一份数据被重复处理也能追溯是哪个批次的采集结果。这套机制看着很朴素但在现场维护时非常实用——你可以知道哪个文件是最终版本哪个文件是废稿不用凭文件名猜来猜去。7. 这套方案的边界与后续扩展思路实测跑完这套系统后说点个人体会。这套方法的适用范围最舒服的是中小型项目变量数量在几十到几百个、报表周期以班报日报为主。如果变量上千、精度要求到秒级、还有复杂的自定义报表流程那可以考虑上专门的工业报表中间件或者商业报表平台自己从零搭建的成本那时已经超过了购买它的费用。判断标准很简单当维护这套脚本的时间开始超过手工做报表的时间就说明该换方案了。后面的扩展方向其实不少。比如在中间层引入SQLite或时序数据库把CSV文件替换成结构化存储查询能力会强很多VBA只负责从数据库拉结果不用解析文本。再比如把报表生成任务推到一台独立的报表服务器不占用现场工程师站避免WinCC运行受Office进程干扰。还有一个方向是利用Excel的Power Query直接连接SQLServer归档库连接过程全图形化字段操作也不需要写代码这套系统的VBA部分就可以大幅瘦身只保留模板套用和文件归档。如果你正在为WINCC的报表需求头疼建议先别急着买第三方软件按这套思路把数据链路捋一遍采集层只取数、生成层只套模板、调度层做定时和手动触发你会发现大部分报表需求其实用现有工具栈就能覆盖。整个系统运行稳定之后报表不应该是现场操心的事——它应该像白开水一样每天准时出现在该出现的地方。
阅读完成 · 觉得有帮助?
咨询建站