摘要:本文以锅炉自动抄表系统为例,探讨了在WINCC中实现自定义报表的方法,核心是巧妙利用了WINCC选件用户归档(User Archive)的各种功能,实现了报表的存储、查询、显示及打印的全过程。
关键词:报表;WinCC;用户归档
Abstract: Taking automatic checking meter system forbioler as an example, this paper introduces a technicalroute to achieve WinCC user defined report, The kernal isto implement the whole process of data storing, checking,displaying and printing by clever use of the functions ofUser Archive of WinCC.
Key words: WinCC; Report; User achive
1 概述
在小型取暖锅炉运行中,许多重要运行参数需要定期记录,在手工年代,需要工人定期读表并记录在表格上,而在自动控制实现之后,则需要用报表来实现,其中包含的内容有:每一个小时整点记录的数据列表,每个工作日,每个班组内对这些数据的统计,统计方法有平均值及累加值,即为日报表或班报表。
例如,有业主要求的理想的报表结构如表1中所示。
表1锅炉运行班报表
除了日报表,另外还有月报表,就是可以查询任一个月的运行情况,其中包含这个月中每一天的数据统计表,以及最终对整个月所有数据做的数据统计,同理如表2所示。
这种应用需求的报表,无法用WinCC或其它组态软件简单地用组态的方式实现,因而必须或多或少地进行一些编程,通过脚本,控制访问数据库,得到查询结果。在WinCC应用中,常把此类报表称做为自定义报表。
西门子论坛 http://www.ad.siemens.com.cn/club/bbs/ 曾于2009年5月至7月间,组织了大型的专题讨论,针对此类自定义报表进行了一个全面的探讨,众多网友提供了各种各样的要求和素材,定时抄表类型的报表是其中的一个大类型,好多报表应用都与此或相同或相关。
这次专题讨论,作者作为版区版主在其中承担了牵线引导的职责,引领网友从提供素材开始,面对对象,整理思路,积极发表看法。而西门子官方的工程师,则在此次讨论过程中及讨论结束后分别整理了技术报告,并制作了简单示范程序,两篇最重要的文献就是《A0296 使用用户归档实现报表简介》和《A0300 WinCC数据报表实现方法介绍》,配套的例子程序则是叫做ForTest.mcp。这两篇技术文献还主要是从原理方面进行的分析,例子程序也着重强调了实现的原理,而对实际应用过程中的细节,则没有侧重考虑。
表2 锅炉运行月报表

本文是在A0296基础上完善而来的,因而是对A0296的补充,本文实现的过程,也同样有配套例子实现,但与A0296已有极大的不同,表现在:
(1)A0296强调的是西门子产品的使用方法,本例则着重强调报表实现技术路线和细节,强调在报表实现过程中的细节。特别是包含报表系统实现的WinCC项目的可移植性,强调用户对例子演示操作的首次体验。
(2)A0296着重强调的是报表的打印环节,与作者持观点不同。作者认为,报表最重要的方面不是打印,而是查询。就是说,最终需要实现的是随时随地均可调用查询功能,并分析查询结果。引导用户养成低碳环保的操作习惯,一般操作只进行查询,不需要落实到纸面上。而如果确实需要打印时,则在查询完成之后继续操作打印功能,将结果输出到打印机。
2 实现原理
报表的实现,最重要的是要有一条清晰的数据走向路线图,并要尽量依托数据库的思路来实现,在其基础上,将整个报表路线分割成相对独立的几个单元,一方面,将每一个单元的具体细节实现之后,串联起来,就可以构成整个报表的实现过程。另一方面,每一个独立的单元如果有备选的技术方法实现,则只需将相应部分替换,可以轻松地实现升级,而不影响整个路线全局。
建立在数据库视角的自定义报表实现路线需要的步骤分别为:
(1)数据存储:报表所需数据素材存储到数据库。
(2)数据查询:前台用户通过操作界面接口设定查询条件,后台程序生成查询所需SQL语句,并对数据库中执行查询操作。
(3)查询结果显示:选择合适的表格控件将步骤2查询得到的结果显示在运行画面上。
(4)打印报表或报表导出:将查询结果输出到打印机或导出到办公文件格式。如果步骤3选择的控件能够直接支持打印机和导出,则由表格控件实现,否则从步骤2的查询结果直接输出实现。
3 技术方法选择的原则
在明确了报表实现的原理之后,下一步就是选择每一个节点上可行的技术方法,最后拼接成一整条可行的技术路线。不过在确定技术路线之前,有几个细节的原则问题需要确定,选择技术路线时应以不违背这些原则为标准,如果原则有所违背,则要么更换技术点,要么通过给这个技术方法打补丁的方法绕过缺陷。
• 运行:只要WinCC项目有报表要求,报表功能应尽量无缝嵌入在WinCC项目内,除非大规模运行的项目,有上层MES系统支持,报表是由MES来实现的。
无缝嵌入WinCC项目的好处在于操作者在操作时不必来回切换运行程序。尤其对组态软件来说,如果被切换出去,则原本的全屏方式及对计算机运行权限管理都成为难题。
• 安装:当一个项目被移植到另外一台新电脑时,除了WinCC软件本身的安装要求外,应尽量少的附加安装软件,尤其是尽量减少对付费软件的需求。否则会导致维护人员极度反感。
• 配置:在项目移植过程中或系统崩溃后的重新恢复过程中,报表功能应尽量避免要求手工的设置操作。如要求手工建立一个数据库表或一个ODBC接口等。这些操作一方面对维护人员水平的要求提高了,另一方面也导致系统的健壮性难以保证。
4 本文的技术路线
在综合考虑报表实现的路线,遵循上述的原则,结合本案例的实际要求及WinCC的实际功能,本文提出了实现的解决方法,仍旧按照实现原理中的四个步骤分别描述如下:
(1)数据存储
数据存储使用用户归档(以下均简称UA)实现,如A0296中所描述,使用UA归档的ID和JOB配合来实现。由于UA这段功能实在太重要了,是本文的精华所在,所以仍需着重描述一下。
UA中的每一个归档都可以建立4个控制变量,分别叫做ID,JOB,Field,Value。按照WinCC的帮助系统的描述,JOB=6时,为读作业。读作业是WinCC站在UA的视角内描述问题,从UA的角度,当JOB=6时,把运行的实时变量的值读取并保存在数据库相应的位置,位置按照ID的设定。而我们站在外部的角度观察这个操作,其实就是我们通常所说的写数据库的操作。而相应的JOB=7时则为读取数据库的值到Tag变量中。案例要求的每小时整点时记录数据,则只需每判断到整点到来时运行ID=-1,JOB=6即完成一条数据记录行为。
首先是在UA中建立名字为Boiler的归档,然后编制脚本判断在整点时产生一次增加新条目的操作,及ID=-1,JOB=6。具体可以实现的方法很多,如在全局脚本中通过Hour变量的变化触发,也可以在1S的循环周期的脚本中读取当前时间,当判断时间为整点时记录。或者在PLC中编程以实现可控制的触发。最简单的,可以参考A0296中描述的实现。这些方法都差别不大。
需要注意的细节:
• 归档中的日期和时间域可以在建立之后重新修改格式为日期/时间格式,便于在查询时条件匹配。如果以文本方式记录的日期时间变量,在查询时是以字符方式比较的,有可能出错。
• 日期和时间是分成两个变量域分别存储,但也可以用一个变量来存储,各有利弊。
• 如果选择用一个变量来存储时间,可以直接使用UA表本身带的最后修改选项,自动建立一个LastAccess的域,保存了数据写入时的时间值。同样与通过WinCC变量来保存相比,各有利弊。
• 最后一个域,班次(Turn),是用来记录班次信息,唯有将班次信息正确完整记录,才有可能做出成功的班报表。实时运行班次的确定,需要根据运行现场业主的倒班原则来确定,可以用编程实现倒班表的切换。但最简单的方法是通过WinCC用户管理来实现,相应的班组接班时登入各自的班组用户名,报表系统中自然得到正确的班组名称。
• 关于WinCC启动时会触发一次脚本并产生一个未在整点的数据记录的问题,可以通过脚本中增加判断来绕过一次写操作,但从实际设备运行的角度,如果由于电脑设备的原因,导致长时间的停机,必然导致数据记录的缺失,那么在系统恢复之时,如果能够尽快地补录一次数据,相比之下,时间是否在整点则相对不那么重要了。
(2)数据查询
数据查询的核心是面向数据的SQL查询语言。包括:查询条件输入,查询语句生成以及调用数据库接口进行数据查询的操作。
一个完整的自定义的报表一般至少分为两个部分,比如班日报表,首先是当前班次的所有数据的一个排列列表,然后是对这些数据的数学统计结果。统计方法可能是平均值或者累加值等,但最后的表现形式都是以表格形式体现的。或许有些特殊的结果输出要求为多个表格部分组成,会超过两部分,但原理上来说,要么是简单条件查询的,本文称为A类查询,要么就是带统计的,本文称为B类查询。自定义报表最大的难题在于B类查询。
一个典型的A类SQL查询的语法如下:
Select ThisDay, F1, F2, F3, F4, F5, F6, F7, F8, F9
From UA#Boiler
Where ThisDay = '10-9-16' and Turn=,A,
查询功能提供给用户的操作界面,主要的目的就是来产生这样的查询语法。其中,Select语句之后的为查询需要显示的列,一般为固定的内容,程序中则为固定的字符串。From语法之后为UA中建立的表的名字,同样也为固定的。所以最终要的是Where之后的条件的产生。
为方便用户使用,查询条件的输入日期时间部分使用专用时间控件,而班次则使用下拉菜单实现。尽可能避免手工键盘输入,一方面实现输入直观快捷,最大程度地提高用户的操作体验;另一方面避免了使用过程中容易发生的格式错误,从而在项目交工时也少了专门的培训环节。
本文选择的报表显示形式为UA控件本身,通过将其工具栏和状态栏禁用之后其表格部分就是一个功能完整的Grid控件的主体。而且非常幸运的是,控件本身有一个Filter属性,即用来设定过滤所显示的数据条件。所以在脚本程序中不必生成完整的SQL查询语句,而只需要生成where之后的条件部分即可。
具体的实现,控件的Filter属性绑定一个文本型的tag,查询脚本中将拼接起来的过滤条件类似 ThisDay = '10-9-16' and Turn=,A, 的字符传送给tag即可。
B类查询,对上述的9个变量,F1-F7求平均值,F8,F9求累加值的统计结果,SQL语句如下:
Select ThisDay,AVG(F1) As F1,AVG(F2) As F2,AVG(F3)As F3,AVG(F4) As F4,AVG(F5) As F5,AVG(F6) As F6,AVG(F7) As F7,SUM(F8) As F8,SUM(F9) AS F9
FROM UA#Boiler
Where ThisDay = '10-9-16' and Turn =,A,
Group BY ThisDay
由于条件部分与A查询完全相同,所以用户接口部分使用同一接口,字符串拼接,得到如上的SQL语句。
由于UA控件未提供数据分析的功能,B查询的实现相对麻烦些,需要几个步骤:
• UA中建立一个空的表S,用于存放查询结果
• 通过ADO或者ODBC ADO直接对SQL数据库进行查询,得到统计结果的RecordSet。• 删除S表中的原有数据。可以通过S表的控制变量,ID=-6,JOB=8来实现。多行数据则循环实现。
• 仍旧通过S表的控制变量,遍历RecordSet的结果,通过ID=-1,JOB=6的方式新增数据记录,写入到UA表中。
• 画面增加第2个UA控件,指定到S表,不再指定过滤条件。
核心代码如下:
Dim Adodc1
Set Adodc1 = ScreenItems("Adodc1")
Adodc1.ConnectionString="DSN=" & HMIRuntime.Tags("@DatasourceNameRT").Read
Adodc1.RecordSource=SQL
Adodc1.Refresh
HMIRuntime.Trace vbCrLf & SQL
Dim taglist
taglist="ThisDay_Gr,F1_AVG,F2_AVG,F3_AVG,F4_AVG,"
taglist=taglist & "F5_AVG,F6_AVG,F7_AVG,F8_SUM,F9_SUM"
Dim TagName
TagName=Split(taglist,",",-1,1)
Dim i, Temp1
If Adodc1.Recordset.recordcount<>0 Then
For i=0 To UBound(TagName)
HMIRuntime.Trace vbCrLf & TagName(i) & "="& Adodc1.Recordset.Fields(i)
HMIRuntime.Tags(TagName(i)).Write Adodc1.Recordset.Fields(i)
Next
HMIRuntime.Tags("@UA_S_ID").Write -1
HMIRuntime.Tags("@UA_S_Job").Write 6
ElseHMIRuntime.Trace vbCrLf & "no data found!!!!"
End If

图1 实际运行后的效果图
注:由于例子程序以分钟为单位定时归档,所以图示数据均为整分钟的。
月报表的准确描述是月度日报表,与前面的班日报表形式上类似,都是分成两部分的查询内容,但内容上有所区别。第一部分的查询内容也为统计后的结果,即A类查询。本例中,在查询部分一需要将某一个月内所有的工作日的数据统计计算,并将结果按日期罗列起来,而查询部分二中的内容则是以这个月份为对象的整体统计计算。
月报表查询一的SQL查询语法为:
Select ThisDay,AVG(F1) As F1,AVG(F2) As F2,AVG(F3) As F3,AVG(F4)As F4,AVG(F5) As F5,AVG(F6) As F6,AVG(F7) As F7,SUM(F8) As F8,SUM(F9) AS F9
From UA#Boiler
Where CONVERT(varchar(10), ThisDay, 120) like '2010-09%'
Group By ThisDay
查询的主体部分与日报表中的查询二相似,也都同为B类查询,但Where后的语法则相差很大。这是因为ThisDay列中保存的是日期数据,而查询条件则是年月,即需要将数据中年月条件符合的数据挑选出来。返回结果为每日一条数据,计算的是当日的统计值。
这里使用转换方法,把日期数据的格式转换为YYYY-MM-DD格式的字符串,然后通过字符串比较的like语法来实现。
查询二的月统计数据的SQL语法:
Select CONVERT(varchar(7),ThisDay,120),AVG(F1) As F1,AVG(F2) As F2,AVG(F3) As F3,AVG(F4) As F4,AVG(F5)As F5,AVG(F6) As F6,AVG(F7) As F7,SUM(F8) As F8,SUM(F9) AS F9
FROM UA#Boiler
WHERE CONVERT(varchar(10), ThisDay,120) like '2010-09%'
GROUP BY CONVERT(varchar(7),ThisDay,120)
这里的WHERE条件与查询一相同,但Group语法又有所不同,因为需要将整个月的所有数据统一统计为一个结果。所以通过转换方式,把日期数据YYYY-MM-DD的前7个字符输出,得到了YYYY-MM的年+月格式。然后既要在统计结果中显示这个年月值,还需要用此作为Group的分组依据。
(3)查询结果显示
查询结果显示在WinCC运行画面上,前文已经透露过,本文主要的方法即使用UA控件,通过禁用工具栏、状态栏并将属性设置为只读,则成为一个简单表格控件的模式,可以用来显示查询结果。其中查询班日报表部分,直接使用源记录数据的归档,通过Filter属性设置过滤条件,而日报表的查询统计则通过单独建立的UA中的归档来实现。而月报表的两部分查询则都是使用和班日报表的统计查询部分一样的手法,通过建立一个空表,在查询到结果时填入数据到归档中,则画面的表格中自动充满数据。
(4)打印报表
客户在查询运行数据后,如需打印,则随时可将查询的结果打印出来。由于本文使用的

图2 打印效果图
UA控件直接支持打印功能,因而可以直接调用系统的打印模板实现。唯一差别的是,我们的报表里面包含2个表格 ,因而需在布局中设计2个“用户归档 DLL运行系统表格”,即可实现完整打印功能,如图2所示。
5 结论
• 在WinCC中使用UA配合做报表,可以实现较为完善的功能。
• 本文在数据记录、数据查询、显示、打印输出各方面都充分利用UA内置提供功能,取得较好的效果。
• 用UA来实现报表,优点是需要较少的程序脚本,尤其是数据库操作代码。UA控件对数据格式的自动适应与处理,也简化了程序的工作量。
• 此技术方法的缺点在于查询时的效率较低,特别是一个月报表查询,需要先将UA表内的数据逐行清空,然后再将查询结果逐行写入。由于使用ID和JOB控制变量来实现读写操作,需要等待确认前步骤操作完成之后方可继续脚本,导致程序运行周期较长。同时程序也不是特别健壮。
• 自定义报表除本文所论及方向和思路之外,还有大量的细节工作要做并且很难用文字来描述。为此作者同时还专门制作了详尽的WinCC例程,并发布在网络上,地址为:http://goo.gl/Mze1
• 例程中除使用UA控件实现之外,还演示了另外两种直接支持ADO方法的表格控件的实现方法。
摘自《自动化博览》2011年第八期
|