WinCC嵌入式Excel报表:VBS脚本读取归档数据实战
半夜接到值班电话说车间报表抓取不到数据第二天要交班记录。我打开WinCC一看归档数据明明都在趋势图里画得好好的但导出的CSV文件用Excel打开时间列全变成了####或者科学计数法操作工看了直摇头。这种时候你就能明白为什么那么多工控人宁愿自己折腾一个嵌入Excel的报表系统也不愿意用WinCC自带的报表导出功能。这篇内容就是把我做WinCC嵌入式Excel报表的完整思路和代码拆开讲一遍如何让WinCC画面里直接嵌一个Excel界面读取历史归档数据按班次、按日期一键生成报表。适合正在被WinCC报表功能折磨的工程师、调试人员也适合打算自己搭一套轻量报表体系的朋友参考。1. 为什么我坚持在WinCC里嵌Excel做报表两个绕不开的痛点1.1 WinCC自带报表功能的真实体验西门子WinCC本身不是不能做报表只是做起来总觉得差口气。用过WinCC Online Table Control和Online Trend Control的朋友应该深有体会这两个控件查趋势、看历史数据确实没问题在画面上拉一条曲线、摆一张实时表都很方便但一旦涉及把数据导出去加工这个动作体验就断崖式下跌。导出格式虽然可以选CSV或者Excel但实际导出来的文件格式非常原始——列宽不自动调整、时间格式需要手工设置、标题行缺失、同一个变量在不同时间点的数据混在一起。你拿到这个导出文件之后还要在Excel里做大量二次加工才能做成一份能交给领导的报表。更麻烦的是WinCC自带的报表组件比如Report Designer虽然能设计打印模板但它的设计界面跟Excel完全是两套逻辑。组态工程师本来花一小时就能在Excel里排好一张班报表模板放到Report Designer里可能要一下午而且调试预览、打印对齐这些环节特别消耗耐心。对于大多数中小型项目来说为了一套报表功能去专门培训组态人员使用Report Designer投入产出比实在太低。1.2 Excel嵌入式方案的适用边界所以我的做法很直接与其把Excel数据导来导去不如让报表直接长在画面里。用一个嵌入式Excel控件让WinCC操作员画面里直接出现一个Excel界面通过脚本查询归档数据库把历史数据填进指定单元格然后让操作工一键打印或者另存为。操作工不用学任何新东西——他们本来就会用Excel打开画面就能上手。不过先说清楚这个方案的适用范围免得有人拿着它去跟商业报表软件硬碰硬。如果你的项目有几十台PLC、上千个归档变量、需要复杂的权限审批和审计追踪那该上PI System或者AVEVA historian这类专业历史库这不是Excel该干的活。Excel嵌入式报表最适合的场景是单机或小型SCADA系统变量数量在几十到几百个之间报表主要是班报、日报、月报需要的格式灵活多变而且你希望用户自己就能调整模板。这种场景下Excel嵌入式方案无论是开发成本还是后期维护成本都比上商业报表工具划算得多。我自己测试下来的经验是当归档变量的采集周期在1秒以上、单次查询的数据量在几千行以内时VBS脚本连数据库查询并填充Excel整个过程基本可以控制在两秒以内完成完全能接受。如果变量多到上万点、每次查询几十万条记录那Excel处理起来就开始吃力了这时候你就该考虑换方案。2. 整体技术方案与数据链路从归档库到Excel单元格2.1 方案选型为什么最终选了VBS脚本而不是C脚本WinCC支持C脚本和VBScript两种脚本方式很多做WinCC组态的老工程师习惯写C脚本因为WinCC的C脚本功能确实强大能操作画面对象、系统函数、内部变量而且执行效率比VBS高。但在读写Excel报表这个具体场景里我反而建议优先用VBScript。原因很实际Excel本身提供了一套完整的COM自动化接口VBScript作为微软亲儿子调用COM对象时语法更自然代码更简洁。比如创建Excel对象、打开工作簿、写入单元格、设置格式这些操作在VBS里只需要几行代码就能完成。C脚本调用COM当然也能做但需要处理一堆VARIANT类型转换代码量至少翻一倍而且C脚本调试起来没有VBS那么直观。另外还有一个切入点WinCC自带的ANALYSC控件的内部逻辑也是基于VBS的很多现成的查询函数可以直接用VBS调用生态上更顺。所以我最终定的方案是WinCC画面用VBS脚本作为粘合剂一边通过ADO对象连接WinCC的历史归档数据库另一边通过Excel.Application对象操作嵌入画面里的Excel控件。2.2 数据流向与核心架构这套报表系统的数据链路可以画成一条清晰的流水线第一段是数据源。WinCC在运行时会自动把归档变量写入它自带的SQL Server实例数据库里存放了每个变量的历史值和时间戳。第二段是查询层。我们用VBS脚本里的ADODB组件去执行一条SQL查询语句按变量名和时间范围从归档表里取出数据。第三段是展示层。查询出来的记录集通过循环写入Excel单元格Excel负责渲染表格样式、计算统计值、生成图表。最外层就是WinCC的画面操作工在画面里点击一个按钮脚本执行报表生成整个过程都在同一个窗口内完成。这段架构里最关键的决策是数据链路中间不经过任何中间文件。我没用先导出CSV到磁盘再让Excel去打开的方案因为那样会产生临时文件而且在WinCC运行状态下频繁读写文件容易遇到文件占用、权限不足的坑。直接让脚本在内存里把Recordset记录集搬到Excel单元格里干净利落。2.3 环境准备WinCC归档数据库结构速览要写查询脚本必须先搞清楚WinCC把数据存在了哪里。WinCC的历史归档数据库来自它捆绑安装的SQL Server数据库名称的格式一般是CC_计算机名_日期时间这样的动态命名规则每次项目激活都会生成对应的运行库。查询历史数据时我们主要关心两类表一类是变量元数据表在WinCC 7.x里常见的是PDE开头或META前缀相关的表用来根据TAG名字查到对应的内部ID另一类是实际的历史数据表模拟量归档和数字量归档往往分表存放。不同版本的WinCC表结构会有一些差异所以写查询脚本前一定要先用数据库管理工具看一眼实际的表名和字段名。我第一次搞的时候就是照着网上的旧教程写表名结果查询直接报对象名无效后来用管理工具连上去一看表名后缀跟教程里完全不一样。这一点很重要别直接抄我的表名字符串先打开数据库确认你那个版本的实际表结构再改脚本里的表名。另外提醒一句WinCC有一个专门的WinCC OLDE DB Provider也常叫Connectivity Pack通过它可以更规范地访问归档数据。如果项目里装了Connectivity Pack建议优先用这个Provider而不是直接连SQLOLEDB因为它是西门子官方提供的访问接口做了一些内部优化查询大数据量时更稳。我下面的示例脚本先用的是SQLOLEDB加数据库直连的方式这是在没有安装Connectivity Pack时也能跑的最小方案。3. 核心代码拆解VBS脚本如何把归档数据“搬”进Excel3.1 建立数据库连接并查询历史归档表先看一段最核心的查询骨架这段脚本我用的是WinCC Global Script里的VBS直接在WinCC的脚本编辑器中书写即可Dim sConn, sSQL, strComputer Dim oConn, oRS strComputer localhost\WinCC SQL Server实例名注意以实际项目为准 sConn ProviderSQLOLEDB;Data Source strComputer ;Integrated SecuritySSPI; _ Initial CatalogCC_MyProject_2024_06_30; Set oConn CreateObject(ADODB.Connection) oConn.ConnectionString sConn oConn.Open sSQL SELECT ValueName, Value, TimeStamp _ FROM PDE#TAG _ WHERE ValueName YourTagName AND TimeStamp 2024-06-30 00:00:00 Set oRS CreateObject(ADODB.Recordset) oRS.Open sSQL, oConn, 1, 1这段代码有四个地方需要根据实际环境替换Data Source里的SQL Server实例名、Initial Catalog里的数据库名、表名PDE#TAG、以及查询条件里的变量名。我记得在多次项目里卡住大家基本都是卡在这四个参数上。建议第一次写的时候先用管理工具查清楚实际库名和表名再回来改脚本不要凭直觉推断。如果你装了Connectivity Pack连接串可以换成这样原理类似但提供程序会优雅一些sConn ProviderWinCC.OLEDB.1;Data Sourcelocalhost\WinCC; _ Initial CatalogCC_MyProject_2024_06_30;3.2 FILETIME时间戳转换最容易翻车的环节WinCC归档表里的时间戳通常以FILETIME格式存储这是一种从1601年1月1日零点开始按100纳秒为单位计数的Windows内部时间格式。直接把原始数字弄到Excel里你得到的是一个天文数字得先做转换才能变成人可以读的日期时间。善用SQL语句在查询阶段就把时间算好是效率最高的办法。FILETIME转成易于读取的小数核心思路是用FILETIME除以一天的100纳秒总数864000000000得到从1601年算起的天数再减去1601年到1899年12月31日之间的天数差值109205就得到Excel可以直接识别的日期序列号。最后的数值在VBS里用CDate转换一下就能得到日期时间对象。我在脚本里是这样实现的 假设查询结果里有一列是FILETIME的数值 Dim dblFileTime, dblExcelDate, dtmTime dblFileTime oRS.Fields(TimeStamp).Value dblExcelDate dblFileTime / 864000000000# - 109205 dtmTime CDate(dblExcelDate)注意这里的前提是数据库返回的TimeStamp字段是标准FILETIME数值。我在不同版本的WinCC上遇到过字段返回类型不一致的情况有返回双精度浮点的也有返回整数大数的。稳妥起见你可以先调试输出一下这个字段的值和WinCC在线趋势里的时间比对一下确认转换公式是否吻合。如果对不上多半是返回类型有差异需要调整除法因子。如果你不想在VBS里处理这层转换还有一个替代方案直接在SQL查询里用DATEADD函数算好把可读的时间字符串返回给VBS。这样做的好处是脚本里少一段转换逻辑坏处是SQL语句在不同版本SQL Server上的函数行为略有差异调试SQL时要留意。3.3 数据写入Excel并完成基本格式处理数据库查询到记录集之后写Excel的核心就是遍历记录集、把字段值赋给单元格。下面这段代码演示了如何把变量名、时间、数值三列写进Sheet的A、B、C列Dim oExcel, oSheet, iRow Set oExcel CreateObject(Excel.Application) Set oSheet oExcel.Workbooks.Open(D:\ReportTemplate.xlsx).Sheets(1) iRow 2 假设第1行是标题行 Do While Not oRS.EOF oSheet.Cells(iRow, 1).Value oRS.Fields(ValueName).Value oSheet.Cells(iRow, 2).Value CDate(oRS.Fields(TimeStamp).Value / 864000000000# - 109205) oSheet.Cells(iRow, 3).Value oRS.Fields(Value).Value iRow iRow 1 oRS.MoveNext Loop oExcel.ActiveWorkbook.Save oExcel.Quit这段代码只是一个最小示范实际项目里需要补充很多细节写入前先清空目标区域旧数据、设置单元格列宽和字体、为数值列设置数字格式等。我习惯的做法是准备一个制作好的Excel模板在模板里把标题行、边框、公式都预先设好脚本只需要往数据区域填充原始值格式问题交给模板解决。这样可以把脚本里的大量格式代码抽掉脚本逻辑简单很多后期调整报表样式也不容易影响脚本。特别注意操作Excel对象时脚本结束后一定要调用oExcel.Quit释放进程否则时间久了你会发现运行WinCC的机器上残留一大堆Excel.exe进程每个进程占用几十MB内存最终把工控机拖垮。4. Excel嵌入画面的两种落地方式与画面布局细节4.1 方式一OLE对象直接嵌入画面WinCC画面编辑器里可以直接插入OLE对象然后在对象类型里选择Microsoft Excel Worksheet。这种方式最直接在开发模式下你可以直接双击进入Excel编辑状态像操作普通Excel文件一样调整表头、设置公式运行时它就是画面里的一块活的Excel区域。但这种方式有个非常讨厌的限制运行时操作工能看到的Excel菜单栏和工具栏极其有限WinCC OLE容器只暴露了很少的操作接口。如果你的需求仅仅是打开报表看数据、点打印按钮打印那问题不大但如果操作工希望能在画面上直接调整列宽、筛选数据、改公式OLE方式就非常束手束脚。而且OLE对象在画面刷新时有概率出现重绘闪烁这在现场大屏幕上是比较影响观感的。4.2 方式二Spreadsheet控件方式更推荐后来我改用了支持COM调用的嵌入式表格控件方案这种方式本质上是通过第三方表格控件托管在WinCC画面中脚本通过控件暴露的接口访问其工作簿。WinCC本身也自带一个叫Spreadsheet Control的控件有些版本默认安装有些需要在安装时勾选它可以嵌入WinCC画面而且脚本可以直接操作控件的Workbook对象。相比纯OLE嵌入Spreadsheet控件的优势很明显脚本层面能精准控制数据填充、格式设置和刷新运行时画面更干净不会弹出Excel乱七八糟的菜单。操作工看到的就是一个嵌入在画面里的干净表格控件数据由后台脚本填充不需要接触底层的Excel进程。如果你没有装WinCC自带的Spreadsheet控件也可以用MSFlexGrid、VSFlexGrid等第三方表格控件来替代原理都一样——控件负责显示脚本负责数据填充。选择控件时关注两点一是支持单元格合并和边框设置很多报表样式依赖这两个能力二是能调整行高列宽且性能不差几千行数据滚动起来要流畅。4.3 画面布局与启动时的初始化建议在实际工程里做画面布局时我建议不要把报表区域铺满整个画面。一个好的交互是画面底部或右侧留出一块区域放查询条件变量选择框、起始时间输入框、查询/打印按钮报表表格区域占画面主体。操作工的思路是先选条件点查询再看到结果这比一打开画面就全屏刷数据要舒服得多也避免了一启动就大量查询拖慢画面加载速度。这里还有一个非常容易忽略的细节画面进入时不要立刻初始化Excel对象。WinCC画面加载过程中调用外部COM组件的启动速度不可控如果在PictureOpen事件里同步创建Excel对象有可能让整个画面卡住几十秒。我的经验是把初始化动作放到查询按钮的Click事件里或者放到一个单独的初始化按钮上操作工真正需要看报表时才触发创建。这样既保证了画面切换流畅也避免WinCC运行时候因为Excel启动占资源导致报警闪烁卡顿。5. 实测中踩过的坑与应对方案5.1 查询结果为空但曲线图里明明有数据我在项目现场遇到过最诡异的问题WinCC画面里的趋势曲线显示历史数据没问题但报表脚本查询同一时间段的归档记录返回的记录集却是空的。排查下来发现的共性原因是归档变量在WinCC里的存储是有分段条件的有些变量在某个时间段里根本没有归档或者归档变量名与查询语句里的名字大小写不一致。WinCC的归档变量名是区分大小写的你从变量管理里复制出来的名字后面偶尔会带空格或者隐藏字符肉眼看不出来但SQL查询一比对就查不到。排查方法很简单先用数据库管理工具手动执行一遍SQL语句看看能不能查出数据。如果手动查询有数据、脚本查询没有问题几乎肯定出在连接串或者变量名的隐藏字符上。把变量名字符串用Len()函数打印出来数一下字符数和变量管理器里的名字对比就能找出问题。5.2 Excel进程残留导致文件被锁死Excel对象如果脚本报错没有走到Quit那一行会留下一个Excel.exe僵尸进程而且它可能还占用着模板文件句柄。下次脚本再打开同一个模板时文件被锁报Permission denied。很多人在现场被这个问题搞得头大重启WinCC进程也未必能释放因为Excel进程是独立的只能去任务管理器手动杀掉。我在框架里统一加了异常处理不管脚本是否出错都要执行Excel退出动作On Error Resume Next ... 中间是Excel操作 ... If Not oExcel Is Nothing Then oExcel.ActiveWorkbook.Close False oExcel.Quit Set oExcel Nothing End If如果程序已经异常退出留下了残进程可以用一行命令行批量杀掉taskkill /F /IM EXCEL.EXE不过要小心如果工控机上操作工正好开了别的Excel文件在办公这行命令会把他打开的文件也一起关掉。所以这只是应急手段根本解法还是脚本里把错误处理和资源释放写规范。5.3 跨日期查询的时间边界问题做日报、月报时查询时间范围的下边界和上边界经常出问题。比如查某一天的数据习惯上写TimeStamp 2024-06-30 00:00:00 AND TimeStamp 2024-06-30 23:59:59但实际操作中这种写法偶尔会漏掉最后一秒的归档点或者因为时间精度问题把边界卡得太死。保险的写法是右边界用小于下一天零点TimeStamp 2024-06-30 00:00:00 AND TimeStamp 2024-07-01 00:00:00这样不管时间戳的精度是秒还是毫秒都不会漏数据也不会多算。这是一个很小但很实用的经验尤其做跨月报表时时间边界处理不对会导致月底最后一天的数据对不上账。5.4 工控机性能波动导致查询超时有的老工控机配置不高数据库查询本身可能就要消耗一两秒再加上Excel启动、数据填充、单元格格式重算整个报表生成过程可能长达十几秒。期间如果WinCC画面动画还在高频刷新CPU就会飙高甚至造成查不到数据的假象。针对这个情况我在脚本里加了一个查询超时控制并在线程级给查询加上重试机制。有条件的项目也可以把采集周期从500毫秒拉长到1秒归档量直线下降查询性能会明显改善。实在资源紧张还有一个妥协方案报表数据提前在后台线程里查询好并缓存操作工点查询时只做数据加载不做实时SQL查询。6. 再进一步报表模板化与定时导出的扩展思路6.1 把固定报表改造成用户可配置模板脚本如果硬编码了变量名和起始时间后期换项目几乎等于重写。我一开始也是这么干的后来接手第二个项目时发现重写一遍的代价太大了于是改成模板驱动的方式。具体做法是在Excel里单独建一个配置Sheet里面规定哪些单元格放什么变量、查询哪张归档表、时间范围是哪个单元格。脚本启动时先读配置Sheet再动态生成SQL和填充位置。这样换一个项目时只需要改Excel模板脚本本身一行都不用动。这个思路做通之后非常省心。哪怕同一个项目里不同班组可能有各自的报表格式偏好只要复制模板改一下配置Sheet就能快速生成新报表不需要组态工程师介入。我在多个项目里验证下来这个模板即配置的模式比在代码里维护一堆报表格式参数要稳定得多。6.2 定时自动生成Excel报表除了手动点按钮查询还可以加一个定时器实现每班结束自动生成上一班次报表并存到共享目录。具体做法是在WinCC的全局脚本里创建一个周期触发的动作时间到了就调用报表生成函数把生成的Excel文件保存到指定路径并自动加上班次日期作为文件名后缀。不过定时自动执行有个风险凌晨三点生成报表时如果Excel对象创建失败或被杀毒软件拦截报表就会丢失而且操作工未必知道。建议生成成功后在WinCC报警消息里提示一次生成失败也提示一次这样值班人员能第一时间知道报表有没有落盘。我有一次就是忽略了失败提示到月底才发现有三天没生成报表后来补数据补到崩溃。6.3 配套视频教程里的几个补充细节因为这次分享的内容比较多我把完整的演示流程做成了配套视频视频里特意补了几个没在文章里展开的细节。一个是WinCC变量管理器和数据库表字段名的对应关系视频里用管理工具一步一步查给你看这样你就知道该怎么在自己项目里定位表名。另一个是Excel模板制作的过程包括条件格式怎么在模板里预置、数据有效性下拉框怎么设置这些在模板阶段做好脚本阶段就完全不用管格式。还补充了一个WinCC里按钮触发VBS脚本的挂接方式以及定时触发动作在全局脚本编辑器里的配置步骤。视频里演示用的示例工程也可以下载虽然是示例版本但骨架结构可以直接套用。到目前为止这套嵌入式Excel报表系统已经在我手里跑过三个项目从养殖场的环境监控日报到生产线的能耗班报再到水处理厂的水质趋势报表基本都稳定运行。要说最大的体会就是千万不要低估Excel模板设计在报表系统里所占的工作量——工程上真正值钱的往往不是那几行脚本代码而是模板背后对业务逻辑的理解。报表不是把数据堆出来就完了什么时候要累计、什么时候要平均、哪些数据要标红提醒这些业务规则才是报表的灵魂。把业务规则在Excel模板里固化成公式和条件格式让脚本只负责填原始数据这套系统的可维护性会远超你的预期。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →