煙臺龍源電力技術股份有限公司 王前厚
張家港市繁昌機械有限公司 王炳輝
在小型取暖鍋爐運行中,許多重要運行參數需要定期記錄,在手工年代,需要工人定期讀表并記錄在表格上,而在自動控制實現之后,則需要用報表來實現,其中包含的內容有:每一個小時整點記錄的數據列表,每個工作日,每個班組內對這些數據的統計,統計方法有平均值及累加值,即為日報表或班報表。
例如,有業主要求的理想的報表結構如表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著重強調的是報表的打印環節,與作者持觀點不同。作者認為,報表最重要的方面不是打印,而是查詢。就是說,最終需要實現的是隨時隨地均可調用查詢功能,并分析查詢結果。引導用戶養成低碳環保的操作習慣,一般操作只進行查詢,不需要落實到紙面上。而如果確實需要打印時,則在查詢完成之后繼續操作打印功能,將結果輸出到打印機。
報表的實現,最重要的是要有一條清晰的數據走向路線圖,并要盡量依托數據庫的思路來實現,在其基礎上,將整個報表路線分割成相對獨立的幾個單元,一方面,將每一個單元的具體細節實現之后,串聯起來,就可以構成整個報表的實現過程。另一方面,每一個獨立的單元如果有備選的技術方法實現,則只需將相應部分替換,可以輕松地實現升級,而不影響整個路線全局。
建立在數據庫視角的自定義報表實現路線需要的步驟分別為:
(1)數據存儲:報表所需數據素材存儲到數據庫。
(2)數據查詢:前臺用戶通過操作界面接口設定查詢條件,后臺程序生成查詢所需SQL語句,并對數據庫中執行查詢操作。
(3)查詢結果顯示:選擇合適的表格控件將步驟2查詢得到的結果顯示在運行畫面上。
(4)打印報表或報表導出:將查詢結果輸出到打印機或導出到辦公文件格式。如果步驟3選擇的控件能夠直接支持打印機和導出,則由表格控件實現,否則從步驟2的查詢結果直接輸出實現。
在明確了報表實現的原理之后,下一步就是選擇每一個節點上可行的技術方法,最后拼接成一整條可行的技術路線。不過在確定技術路線之前,有幾個細節的原則問題需要確定,選擇技術路線時應以不違背這些原則為標準,如果原則有所違背,則要么更換技術點,要么通過給這個技術方法打補丁的方法繞過缺陷。
? 運行:只要WinCC項目有報表要求,報表功能應盡量無縫嵌入在WinCC項目內,除非大規模運行的項目,有上層MES系統支持,報表是由MES來實現的。
無縫嵌入WinCC項目的好處在于操作者在操作時不必來回切換運行程序。尤其對組態軟件來說,如果被切換出去,則原本的全屏方式及對計算機運行權限管理都成為難題。
? 安裝:當一個項目被移植到另外一臺新電腦時,除了WinCC軟件本身的安裝要求外,應盡量少的附加安裝軟件,尤其是盡量減少對付費軟件的需求。否則會導致維護人員極度反感。
? 配置:在項目移植過程中或系統崩潰后的重新恢復過程中,報表功能應盡量避免要求手工的設置操作。如要求手工建立一個數據庫表或一個ODBC接口等。這些操作一方面對維護人員水平的要求提高了,另一方面也導致系統的健壯性難以保證。
在綜合考慮報表實現的路線,遵循上述的原則,結合本案例的實際要求及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
Else
HMIRuntime.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所示。
? 在WinCC中使用UA配合做報表,可以實現較為完善的功能。
? 本文在數據記錄、數據查詢、顯示、打印輸出各方面都充分利用UA內置提供功能,取得較好的效果。
? 用UA來實現報表,優點是需要較少的程序腳本,尤其是數據庫操作代碼。UA控件對數據格式的自動適應與處理,也簡化了程序的工作量。
? 此技術方法的缺點在于查詢時的效率較低,特別是一個月報表查詢,需要先將UA表內的數據逐行清空,然后再將查詢結果逐行寫入。由于使用ID和JOB控制變量來實現讀寫操作,需要等待確認前步驟操作完成之后方可繼續腳本,導致程序運行周期較長。同時程序也不是特別健壯。
? 自定義報表除本文所論及方向和思路之外,還有大量的細節工作要做并且很難用文字來描述。為此作者同時還專門制作了詳盡的WinCC例程,并發布在網絡上,地址為:http://goo.gl/Mze1
? 例程中除使用UA控件實現之外,還演示了另外兩種直接支持ADO方法的表格控件的實現方法。