配方是給機器設定的一組運行參數,當機器要生產不同規格的產品時,可以給機器設定不同的參數。
配方的設計要點之一是如何保存配方。制葯行業要求對生產數據一般保存5年,生產數據包括生產時所用的配方,當打印報表時需要把對應的配方打印出來。這就要求已生產過的配方要能長期保存,不能被修改或刪除。
以下內容介紹如何基於WinCC的用戶歸檔存儲配方。用戶歸檔是對SQL數據庫的一種封裝,與直接讀寫數據庫相比,用戶歸檔可以滿足雙機熱備的要求。
用戶歸檔的讀寫要求
用戶歸檔實質是存儲在SQL Server中的數據庫,優點是基於WinCC軟件實現了兩台電腦里數據庫的雙機熱備。SQL Server也有一些熱備方法,例如“數據庫復制:發布-訂閱”,但是因為WinCC的限制不能使用,用戶歸檔的熱備在此更合適。
要實現用戶歸檔的熱備冗余,不能直接寫數據庫,腳本中只能通過UA API函數、控制變量對用戶歸檔寫入,對用戶歸檔讀取可以直接讀數據庫。UA API函數只在C腳本中支持,用C腳本讀寫配方過於復雜,所以在VB腳本中使用控制變量操作配方。
配方的數據流向
用控制變量操作配方,只能將配方從變量寫入到用戶歸檔的數據庫,或者將特定配方從用戶歸檔的數據庫寫入到變量。實際使用中,需要先查看配方,然后再將配方下載到PLC,因此需要兩套不同的變量,其中一組變量作為中間變量(內部變量)用於查看、編輯,另一組變量是PLC中的實際變量(外部變量),將中間變量的值寫入到這組變量就意味着下發了配方。數據流圖如下。
用戶歸檔數據庫中的特殊字段
紅框中的字段是每個配方數據庫表中都統一的字段,以下是這些字段的說明。
字段名稱 | 類型 | 描述 |
Recipe_ID | 字符串 | 通過Recipe_ID唯一標識一個配方,該字段由Recipe_No和Recipe_Edition拼接而成。 |
Recipe_No | 字符串 | 新建配方時指定唯一配方編號,修改配方將生產新的配方版本,不會覆蓋舊配方,新舊配方的配方編號相同。 |
Recipe_Edition | 字符串 | 配方的版本,新建的配方的版本為1,修改並保存后配方版本自動加1,再與配方編號拼接成新的配方ID,在數據庫中存儲一條新的數據。 |
Recipe_Name | 字符串 | 配方名稱。 |
Recipe_Description | 字符串 | 配方描述,同一配方的不同版本的描述可以不同。 |
IsProduced | 數字(整型) | 已生產標志位,當配方已下發並執行了生產,將這個配方的標志位置1,否則這個標志位為0。 |
IsDeleted | 數字(整型) | 當刪除一個配方時,如果IsProduced為1,則把IsDeleted標志位置1,表示已刪除,查看配方時不再顯示這條配方,但配方依然存儲在數據庫中,打印報表時依然可以調出這個配方的數據;如果IsProduced為0,說明這個配方沒有生產過,將會從數據庫中刪除。 |
使用控制變量操作用戶歸檔,則必須給每個字段值綁定參數變量,然后將參數變量的值寫入到用戶歸檔。
綁定的參數變量如下,“XX_”代表設備前綴,不同的設備指定不同的設備前綴。設備前綴之后的變量名在所有設備中都是統一的,之后的代碼根據這些變量名傳遞配方信息。“M_”代表這是中間變量(內部變量),還有一組不帶“M_”中綴的同名變量,那些是外部變量,下載配方時通過中間變量向外部變量寫值完成。
變量名 | 類型 |
XX_M_RecipeID | 文本變量8位字符集 |
XX_M_RecipeNo | 文本變量8位字符集 |
XX_M_RecipeEdition | 文本變量8位字符集 |
XX_M_RecipeName | 文本變量8位字符集 |
XX_M_RecipeDescription | 文本變量8位字符集 |
XX_M_IsProduced | 二進制變量 |
XX_M_IsDeleted | 二進制變量 |
如何用控制變量操作用戶歸檔
用戶歸檔需要綁定四個變量對其進行操作,這四個變量分別為ID、Job、Field、Value,控制變量的說明如下:
控制變量 |
功能 |
數據類型 |
---|---|---|
ID |
用戶歸檔的數據記錄編號 |
有符號 32 位數 |
Job |
可能存在下列作業: “6”= 讀取變量寫入到用戶歸檔中的數據記錄 “7”= 將數據記錄從用戶歸檔寫入變量 “8”= 刪除用戶歸檔中的數據記錄 執行作業后,“作業”變量將變為以下數值: “0”= 無錯誤 “-1”= 有錯誤 |
有符號 32 位數 |
Field |
用戶歸檔的特定字段 |
文本變量,8 位 |
Value |
特定用戶歸檔字段的值 |
文本變量,8 位 |
控制變量“ID”和“作業”的組合:
ID |
作業 =“6” |
作業 =“7” |
作業 =“8” |
-1 |
讀取變量向用戶歸檔中新增數據記錄 |
- |
刪除最低 ID 的數據記錄 |
-6 |
讀取變量寫入到最低 ID 的數據記錄 |
讀取最低 ID 的數據記錄寫入到變量 |
刪除最低 ID 的數據記錄 |
-9 |
讀取變量寫入到最高 ID 的數據記錄 |
讀取最高 ID 的數據記錄寫入到變量 |
刪除最高 ID 的數據記錄 |
>0 |
讀取變量寫入到ID變量指定的數據記錄 |
讀取ID變量指定的數據記錄寫入到變量 |
刪除ID變量指定的數據記錄 |
0 |
讀取變量寫入到字段變量和值變量指定的數據記錄 | 讀取字段變量和值變量指定的數據記錄寫入到變量 | 刪除字段變量和值變量指定的數據記錄 |
操作用戶歸檔的全局函數
用控制變量操作用戶歸檔的方式不太明晰,寫成全局變量可以使該過程更加簡便,常用的方式是用Field變量和Value變量確定一條記錄,以下是寫在全局腳本中操作用戶歸檔的函數。用控制變量操作用戶歸檔是一個異步的過程,函數中添加了檢查Job變量返回值的代碼,變成了同步過程,函數返回0表示執行成功,返回-1表示執行失敗。
注意:雖然是全局函數,但是在設定了前綴的畫面窗口中調用時,全局函數中使用的變量也會被加上前綴。
Function Delete_UA(strUAName,strField,strValue)'刪除一條信息 HMIRuntime.Tags(strUAName&"_ID").Write 0 HMIRuntime.Tags(strUAName&"_Field").Write strField HMIRuntime.Tags(strUAName&"_Value").Write strValue HMIRuntime.Tags(strUAName&"_Job").Write 8 Do Delete_UA = HMIRuntime.Tags(strUAName&"_Job").Read Loop Until Delete_UA=0 Or Delete_UA=-1 End Function Function Select_UA(strUAName,strField,strValue) '下載一條信息 HMIRuntime.Tags(strUAName&"_ID").Write 0 HMIRuntime.Tags(strUAName&"_Field").Write strField HMIRuntime.Tags(strUAName&"_Value").Write strValue HMIRuntime.Tags(strUAName&"_Job").Write 7 Do Select_UA = HMIRuntime.Tags(strUAName&"_Job").Read Loop Until Select_UA=0 Or Select_UA=-1 End Function Function Insert_UA(strUAName)'添加一條信息 HMIRuntime.Tags(strUAName&"_ID").Write -1 HMIRuntime.Tags(strUAName&"_Field").Write "" HMIRuntime.Tags(strUAName&"_Value").Write "" HMIRuntime.Tags(strUAName&"_Job").Write 6 Do Insert_UA = HMIRuntime.Tags(strUAName&"_Job").Read Loop Until Insert_UA=0 Or Insert_UA=-1 End Function Function Update_UA(strUAName,strField,strValue)'修改一條信息 HMIRuntime.Tags(strUAName&"_ID").Write 0 HMIRuntime.Tags(strUAName&"_Field").Write strField HMIRuntime.Tags(strUAName&"_Value").Write strValue HMIRuntime.Tags(strUAName&"_Job").Write 6 Do Update_UA = HMIRuntime.Tags(strUAName&"_Job").Read Loop Until Update_UA=0 Or Update_UA=-1 End Function
配方界面概覽
操作配方的全局函數
上圖的新建、保存、下載、上載和刪除按鈕中會用到許多腳本,這些腳本並不直接寫在控件中,而是以函數形式定義在VBS全局項目模塊中,然后在控件中調用函數。一個WinCC程序可能要管理許多台設備的配方,也就有許多張配方畫面,假如直接將腳本寫在控件中,當需要修改腳本時,會要修改很多遍。所以將腳本集中定義在全局項目模塊,便於維護代碼。
這些函數中通過控件名稱調用控件,必須在配方畫面中才能使用,並且控件的命名必須依照給定的名稱。
調用函數會用到三個參數:
- tagPrefix:變量前綴。不同設備的變量都帶有不同的變量前綴,避免變量名沖突。
- DSN:數據源名稱。配方數據表的名稱,建議用戶歸檔以“變量前綴+Recipe”命名,實際在數據庫中還會加上“UA#”前綴。例如,假設變量前綴是“XX_”,則DSN為“UA#XX_Recipe”。
- objNameList:這是一個二維數組,第一列是配方數據表字段名和畫面控件的名稱,要求配方數據表字段名和被編輯的控件的名稱一致;第二列是變量名。
全局項目模塊中的代碼如下(建議文件命名為_Recipe.bmo):
'該文件中的函數僅用於當前項目的recipe,目的是為了方便編輯,不具有移植性。 '新建配方 Sub NewRecipe(tagPrefix, DSN, objNameList) '----------------------------------------------------- ' 新建配方 ' 輸入配方編號、配方名和配方描述, ' 檢查配方編號是否與已有的配方重復, ' 將配方信息寫入到界面控件中顯示。 '----------------------------------------------------- '詢問是否新建配方 Dim RecipeNo,RecipeName,RecipeDescription If ScreenItems("BT_Save").Enabled=True Then '檢查是否有配方正在編輯 If Msgbox (TranslateText("配方正在編輯,確定不保存該配方,繼續新建配方嗎?", 2052),vbOKCancel+vbQuestion,"Note") = vbCancel Then Exit Sub End If Else If Msgbox (TranslateText("新建配方嗎?",2052),vbOKCancel+vbQuestion,"Note") = vbCancel Then Exit Sub End If End If '創建數據庫對象 Dim cnn, rs Set cnn = CreateObject("ADODB.Connection") Set rs = CreateObject("ADODB.Recordset") '輸入配方編號 Do While True RecipeNo = Trim(InputBox(TranslateText("請輸入新配方編號:", 2052),"Note")) '檢查輸入的配方編號是否符合要求 If RecipeNo="" Or Check_LawlessChar(RecipeNo)=True Then If MsgBox (TranslateText("配方編號只能包含字母和數字,請重新輸入!", 2052),vbRetryCancel + vbInformation,"Note") = vbCancel Then Exit Sub Else '如果選擇重試則繼續執行循環 End If else '檢查配方編號是否重復 Dim SQL SQL = "select Recipe_No from "& DSN &" where Recipe_No='" & RecipeNo & "' AND IsDeleted = 0" ConnectDatabase cnn,rs,SQL If rs.recordcount>0 Then If MsgBox (TranslateText("配方編號重復!",2052),vbRetryCancel + vbInformation,"Note") = vbCancel Then rs.Close cnn.Close Exit Sub else '如果選擇重試則繼續執行循環 End If Else rs.Close cnn.Close Exit Do End If End If Loop Set rs = Nothing Set cnn = Nothing '輸入配方名稱 Do While True RecipeName = Trim(InputBox(TranslateText("請輸入新配方名稱:",2052),"Note")) If RecipeName="" Then If MsgBox (TranslateText("配方名稱不能為空!",2052),vbRetryCancel + vbInformation,"Note") = vbCancel Then Exit Sub else '如果選擇重試則繼續執行循環 End If Else Exit Do End If Loop '輸入配方描述 RecipeDescription = Trim(InputBox(TranslateText("請輸入新配方描述(可以為空):",2052),"Note")) '清空控件內容 Dim objName For Each objName In objNameList ScreenItems(objName(0)).text = "" Next '向控件填入配方信息 ScreenItems("ComboRecipeList").text = RecipeNo ScreenItems("ComboEditionList").text = "0" ScreenItems("Recipe_Name").text = RecipeName ScreenItems("Recipe_Description").text = RecipeDescription '設置按鈕狀態 ScreenItems("BT_New").Enabled = False ScreenItems("BT_Download").Enabled = False ScreenItems("BT_Save").Enabled = True ScreenItems("BT_Delete").Enabled = False ScreenItems("ComboRecipeList").Enabled = True End Sub '******************************************************************************************************************************************* '讀取數據庫中的配方編號寫入到ComboBox控件 Sub WriteRecipeNoToComboBox(DSN, objName) '創建數據庫對象 Dim cnn, rs Set cnn = CreateObject("ADODB.Connection") Set rs = CreateObject("ADODB.Recordset") '查詢不為空並且未刪除的配方編號 Dim SQL SQL = "Select distinct Recipe_No FROM "& DSN &" where Recipe_No<>'' AND Recipe_No IS NOT NULL AND IsDeleted = 0" ConnectDatabase cnn,rs,SQL '將配方編號寫入到控件 ScreenItems(objName).clear If rs.RecordCount>0 Then rs.MoveFirst Dim i For i = 1 To rs.RecordCount ScreenItems(objName).AddItem rs("Recipe_No") rs.MoveNext Next End If rs.Close Set rs = Nothing cnn.Close Set cnn = Nothing End Sub '******************************************************************************************************************************************* '保存配方 Sub SaveRecipe(tagPrefix, DSN, objNameList) '-------------------------- ' 保存配方: ' 查找配方編號的最大版本號, ' 將最大版本號遞增置為下一版本號, ' 將配方信息寫入到中間變量, ' 將界面上的配方參數寫入到中間變量, ' 將中間變量的值寫入到用戶歸檔中新增一條記錄, ' 保存配方並不會修改已存在的配方,始終是增加一個新版本的配方。 '-------------------------- '檢查是否選擇配方 If ScreenItems("ComboRecipeList").text="" Or ScreenItems("ComboEditionList").text="" Then MsgBOX TranslateText("沒有選擇正確的配方,不能保存",2052) Exit Sub End If '電子簽名 Dim sComments If EsigDialog(sComments,False,"") <> 1 Then Exit Sub End If '創建數據庫對象 Dim cnn, rs Set cnn = CreateObject("ADODB.Connection") Set rs = CreateObject("ADODB.Recordset") '查找數據庫中配方的最大版本號 Dim intMaxID,i intMaxID=0 Dim SQL SQL = "select Recipe_Edition from "& DSN &" where Recipe_No='" & RecipeNo & "'" ConnectDatabase cnn,rs,SQL If rs.recordcount>0 Then rs.MoveFirst For i = 1 To rs.recordcount If intMaxID<CInt(Mid(rs("Recipe_Edition"),1)) Then ' 查找最大配方版本號 intMaxID=CInt(Mid(rs("Recipe_Edition"),1)) End If rs.MoveNext Next End If '關閉數據庫連接 rs.Close cnn.Close Set rs = Nothing Set cnn = Nothing '計算配方下一版本號 NextRecipeEdition = intMaxID + 1 '清空中間變量的已刪除和已生產標志 HMIRuntime.Tags("@NOP::" &tagPrefix& "M_IsProduced").Write 0 HMIRuntime.Tags("@NOP::" &tagPrefix& "M_IsDeleted").Write 0 '獲取配方ID Dim recipeID recipeID = ScreenItems("ComboRecipeList").text &"_"& NextRecipeEdition '將界面顯示的配方信息寫入內部變量 HMIRuntime.Tags("@NOP::" &tagPrefix& "M_RecipeNo").Write ScreenItems("ComboRecipeList").text HMIRuntime.Tags("@NOP::" &tagPrefix& "M_RecipeEdition").Write NextRecipeEdition HMIRuntime.Tags("@NOP::" &tagPrefix& "M_RecipeName").Write ScreenItems("Recipe_Name").text HMIRuntime.Tags("@NOP::" &tagPrefix& "M_RecipeDescription").Write ScreenItems("Recipe_Description").text HMIRuntime.Tags("@NOP::" &tagPrefix& "M_RecipeID").Write recipeID '將界面顯示的配方參數寫入內部變量 Dim objName For Each objName In objNameList If Trim(ScreenItems(objName(0)).text) = "" Then HMIRuntime.Tags("@NOP::" &tagPrefix& "M_"& objName(1)).Write 0 Else HMIRuntime.Tags("@NOP::" &tagPrefix& "M_"& objName(1)).Write ScreenItems(objName(0)).text End If Next '保存配方,把變量值寫入到數據庫 If Insert_UA("@NOP::" &tagPrefix& "UA_Recipe") <> 0 Then Msgbox TranslateText("保存失敗!",2052) Exit Sub End If '記錄Audit CreateOpMsg PrefixToName(tagPrefix), TranslateText("保存配方",2052)&recipeID , "", recipeID, sComments '更新配方編號控件中的內容 Call WriteRecipeNoToComboBox(DSN, "ComboRecipeList") '向界面控件添加配方版本 ScreenItems("ComboEditionList").AddItem NextRecipeEdition ScreenItems("ComboEditionList").text = NextRecipeEdition Msgbox TranslateText("保存升級成功",2052) '設置按鈕狀態 ScreenItems("BT_New").Enabled=True ScreenItems("BT_Download").Enabled=True ScreenItems("BT_Delete").Enabled=True ScreenItems("BT_Save").Enabled=False ScreenItems("ComboRecipeList").Enabled=True End Sub '******************************************************************************************************************************************* '下載配方 Sub DownloadRecipe(tagPrefix, DSN, objNameList) '--------------------------------------- ' 下載配方: ' 從用戶歸檔中讀取配方到中間變量, ' 再把配方從中間變量寫入到外部變量。 '--------------------------------------- ' If HMIRuntime.Tags("A_CommunicationFail").Read=1 Then ' MsgBOX "通訊失敗,不能下載" ' Exit Sub ' End If '檢查是否選擇配方 If ScreenItems("ComboRecipeList").text="" Or ScreenItems("ComboEditionList").text="" Then MsgBOX TranslateText("沒有選擇正確的配方,不能下載!",2052) Exit Sub End If '電子簽名 Dim sComments If EsigDialog(sComments,False,"") <> 1 Then Exit Sub End If '計算配方ID Dim RecipeID RecipeID = ScreenItems("ComboRecipeList").text&"_"&ScreenItems("ComboEditionList").text '更新中間變量 If Select_UA( "@NOP::" &tagPrefix& "UA_Recipe","Recipe_ID",RecipeID) <> 0 Then MsgBOX TranslateText("獲取配方失敗!",2052) Exit Sub end if '將配方從中間變量寫入到外部變量 Dim objName For Each objName In objNameList HMIRuntime.Tags("@NOP::" &tagPrefix& objName(1)).Write HMIRuntime.Tags("@NOP::" &tagPrefix& "M_"&objName(1)).Read Next HMIRuntime.Tags("@NOP::" &tagPrefix& "RecipeNo").Write HMIRuntime.Tags("@NOP::" &tagPrefix& "M_RecipeNo").Read HMIRuntime.Tags("@NOP::" &tagPrefix& "RecipeEdition").Write HMIRuntime.Tags("@NOP::" &tagPrefix& "M_RecipeEdition").Read HMIRuntime.Tags("@NOP::" &tagPrefix& "RecipeName").Write HMIRuntime.Tags("@NOP::" &tagPrefix& "M_RecipeName").Read HMIRuntime.Tags("@NOP::" &tagPrefix& "RecipeDescription").Write HMIRuntime.Tags("@NOP::" &tagPrefix& "M_RecipeDescription").Read HMIRuntime.Tags("@NOP::" &tagPrefix& "RecipeID").Write HMIRuntime.Tags("@NOP::" &tagPrefix& "M_RecipeID").Read '記錄Audit CreateOpMsg PrefixToName(tagPrefix), TranslateText("下載配方",2052)&recipeID , "", recipeID, sComments '設置按鈕狀態 ScreenItems("BT_New").Enabled=True ScreenItems("BT_Download").Enabled=True ScreenItems("BT_Delete").Enabled=True ScreenItems("ComboRecipeList").Enabled=True Msgbox TranslateText("下載成功",2052) End Sub '******************************************************************************************************************************************* '上載配方 Sub Upload(tagPrefix, DSN, objNameList) '檢查是否有配方正在編輯 If ScreenItems("BT_Save").Enabled=True Then If Msgbox (TranslateText("配方正在編輯,確定不保存該配方,繼續上載配方嗎?",2052),vbQuestion+vbOKCancel,"Note") = vbCancel Then Exit Sub End If End If '檢查是否選擇了配方編號,詢問是否上傳到當前配方編號並新建配方版本。 Dim NewRecipeNoFlag If Trim(ScreenItems("ComboRecipeList").text) <> "" And _ Trim(ScreenItems("Recipe_Name").text) <> "" Then ' 判斷是否沿用當前選擇的配方編號 Select Case MsgBOX( TranslateText("是否上傳到當前配方編號並更新配方版本?",2052), vbYesNoCancel + vbQuestion) Case vbYes NewRecipeNoFlag = False Case vbno NewRecipeNoFlag = True Case Else Exit Sub End Select Else NewRecipeNoFlag = True End If '電子簽名 Dim sComments If EsigDialog(sComments,False,"") <> 1 Then Exit Sub End If '創建數據庫對象 Dim cnn, rs, SQL Set cnn = CreateObject("ADODB.Connection") Set rs = CreateObject("ADODB.Recordset") Dim RecipeNo, RecipeName, RecipeDescription If NewRecipeNoFlag = True Then ' 輸入新配方編號和配方名稱 Do While True RecipeNo = Trim(InputBox(TranslateText("請輸入新配方編號:",2052),"Note")) '檢查輸入的配方編號是否符合要求 If RecipeNo="" Or Check_LawlessChar(RecipeNo)=True Then If MsgBox (TranslateText("配方編號只能包含字母和數字,請重新輸入!",2052),vbRetryCancel + vbInformation,"Note") = vbCancel Then Exit Sub Else '如果選擇重試則繼續執行循環 End If else '檢查配方編號是否重復 SQL = "select Recipe_No from "& DSN &" where Recipe_No='" & RecipeNo & "' AND IsDeleted = 0" ConnectDatabase cnn,rs,SQL If rs.recordcount>0 Then If MsgBox (TranslateText("配方編號重復!",2052),vbRetryCancel + vbInformation,"Note") = vbCancel Then rs.Close cnn.Close Exit Sub else '如果選擇重試則繼續執行循環 End If Else rs.Close cnn.Close Exit Do End If End If Loop '輸入配方名稱 Do While True RecipeName= Trim(InputBox(TranslateText("請輸入新配方名稱:",2052),"Note")) If RecipeName="" Then If MsgBox (TranslateText("配方名稱不能為空!",2052),vbRetryCancel + vbInformation,"Note") = vbCancel Then Exit Sub else '如果選擇重試則繼續執行循環 End If Else Exit Do End If Loop Else '使用當前配方編號和配方名稱 RecipeNo = Trim(ScreenItems("ComboRecipeList").text) RecipeName = Trim(ScreenItems("Recipe_Name").text) End If '輸入配方描述 RecipeDescription = Trim(InputBox(TranslateText("請輸入新配方描述(可以為空):",2052),"Note")) '查找特定配方編號下最大版本號 Dim intMaxID,i intMaxID = 0 SQL = "select Recipe_Edition from "& DSN &" where Recipe_No='" & RecipeNo & "'" ConnectDatabase cnn,rs,SQL If rs.recordcount>0 Then rs.MoveFirst For i = 1 To rs.recordcount If intMaxID<CInt(Mid(rs("Recipe_Edition"),1)) Then ' 查找最大配方版本號 intMaxID=CInt(Mid(rs("Recipe_Edition"),1)) End If rs.MoveNext Next End If '設置下一版本號 NextRecipeEdition = intMaxID + 1 '計算配方ID Dim recipeID recipeID = RecipeNo &"_"& NextRecipeEdition '將配方參數從外部變量寫入到中間變量 Dim objName For Each objName In objNameList HMIRuntime.Tags("@NOP::" &tagPrefix& "M_"&objName(1)).Write HMIRuntime.Tags("@NOP::" &tagPrefix& objName(1)).Read Next '清空中間變量已刪除和已生產標志 HMIRuntime.Tags("@NOP::" &tagPrefix& "M_IsProduced").Write 0 HMIRuntime.Tags("@NOP::" &tagPrefix& "M_IsDeleted").Write 0 '將輸入的配方信息寫入內部變量 HMIRuntime.Tags("@NOP::" &tagPrefix& "M_RecipeNo").Write RecipeNo HMIRuntime.Tags("@NOP::" &tagPrefix& "M_RecipeEdition").Write NextRecipeEdition HMIRuntime.Tags("@NOP::" &tagPrefix& "M_RecipeName").Write RecipeName HMIRuntime.Tags("@NOP::" &tagPrefix& "M_RecipeDescription").Write RecipeDescription HMIRuntime.Tags("@NOP::" &tagPrefix& "M_RecipeID").Write recipeID '從中間變量寫入到用戶歸檔 If Insert_UA("@NOP::" &tagPrefix& "UA_Recipe") <> 0 Then Msgbox TranslateText("上載失敗!",2052) Exit Sub End If '記錄Audit CreateOpMsg PrefixToName(tagPrefix), TranslateText("上載配方",2052)&recipeID , "", recipeID, sComments '更新控件 Call WriteRecipeNoToComboBox(DSN, "ComboRecipeList") ScreenItems("ComboRecipeList").text = RecipeNo ScreenItems("ComboEditionList").text = "" Msgbox TranslateText("上載成功並已保存",2052) ScreenItems("BT_New").Enabled=True ScreenItems("BT_Download").Enabled=True ScreenItems("BT_Delete").Enabled=True ScreenItems("ComboRecipeList").Enabled=True ScreenItems("BT_Save").Enabled=False End Sub '******************************************************************************************************************************************* '刪除配方 Sub DeleteRecipe(tagPrefix, DSN, objNameList) '-------------------------------------- ' 刪除配方: ' 檢查需刪除的配方是否生產過, ' 生產過的配方將其IsDeleted字段置1, ' 之后查詢時不再顯示該配方, ' 打印報表時依然可以打印該配方; ' 未生產過的配方則被直接刪除。 '------------------------------------- '檢查是否選擇配方 If ScreenItems("ComboRecipeList").text="" Or ScreenItems("ComboEditionList").text="" Then MsgBOX TranslateText("沒有選擇正確的配方!",2052) Exit Sub End If '電子簽名 Dim sComments If EsigDialog(sComments,False,"") <> 1 Then Exit Sub End If '計算配方ID Dim RecipeID RecipeID = ScreenItems("ComboRecipeList").text&"_"&ScreenItems("ComboEditionList").text '更新中間變量 If Select_UA ("@NOP::" &tagPrefix& "UA_Recipe","Recipe_ID", RecipeID ) <> 0 Then MsgBOX TranslateText("讀取配方失敗!",2052) exit Sub end if '刪除配方 If HMIRuntime.Tags("@NOP::" &tagPrefix& "M_IsProduced").Read = 1 Then HMIRuntime.Tags("@NOP::" &tagPrefix& "M_IsDeleted").Write 1 If Update_UA ("@NOP::" &tagPrefix& "UA_Recipe", "Recipe_ID", RecipeID ) <> 0 Then Msgbox TranslateText("刪除配方失敗!",2052) exit sub end if Else If Delete_UA ("@NOP::" &tagPrefix& "UA_Recipe","Recipe_ID",RecipeID ) <> 0 Then Msgbox TranslateText("刪除配方失敗!",2052) exit sub end if End If '記錄Audit CreateOpMsg PrefixToName(tagPrefix), TranslateText("刪除配方",2052)&recipeID , "", recipeID, sComments '清空配方控件內容 Dim objName For Each objName In objNameList ScreenItems(objName(0)).text = "" Next ScreenItems("Recipe_Name").text = "" ScreenItems("Recipe_Description").text = "" ScreenItems("ComboRecipeList").text = "" ScreenItems("ComboEditionList").clear ScreenItems("ComboEditionList").text = "" '更新控件中的配方編號 Call WriteRecipeNoToComboBox(DSN, "ComboRecipeList") '設置按鈕狀態 ScreenItems("BT_New").Enabled=True ScreenItems("BT_Download").Enabled=False ScreenItems("BT_Delete").Enabled=False Msgbox TranslateText("刪除成功",2052) End Sub '******************************************************************************************************************************************* '獲取配方版本 Sub GetRecipeEdition(tagPrefix, DSN, objNameList) '------------------------------------------------ ' 查詢數據庫中特定配方編號的版本,寫入到下拉控件。 '------------------------------------------------ '創建數據庫對象 Dim cnn,rs,cnnStr, SQL,i Set cnn = CreateObject("ADODB.Connection") Set rs = CreateObject("ADODB.Recordset") '查詢特定配方編號的數據 SQL = "select Recipe_Edition from "& DSN &" where Recipe_No='" & ScreenItems("ComboRecipeList").text & "' AND IsDeleted = 0" ConnectDatabase cnn,rs,SQL '將數據庫中存在的配方版本填入控件 ScreenItems("ComboEditionList").clear If rs.RecordCount>0 Then rs.MoveFirst For i = 1 To rs.RecordCount ScreenItems("ComboEditionList").AddItem rs("Recipe_Edition") rs.MoveNext Next End If '關閉數據庫連接 rs.Close Set rs = Nothing cnn.Close Set cnn = Nothing '清空參數控件內容 For Each objName In objNameList ScreenItems(objName(0)).text = "" Next '清空配方信息控件內容 ScreenItems("Recipe_Name").text = "" ScreenItems("Recipe_Description").text = "" '設置按鈕狀態 ScreenItems("BT_New").Enabled=True ScreenItems("BT_Delete").Enabled=False ScreenItems("BT_Save").Enabled=False ScreenItems("BT_Download").Enabled=False End Sub '******************************************************************************************************************************************* '獲取配方 Sub GetRecipe(tagPrefix, DSN, objNameList) '---------------------------------------------- ' 用配方編號和配方版本拼接得到配方ID, ' 用配方ID指定配方寫入到中間變量, ' 再將中間變量的內容寫入到控件去顯示。 '---------------------------------------------- '獲取配方ID Dim RecipeID RecipeID = ScreenItems("ComboRecipeList").text &"_"& ScreenItems("ComboEditionList").text '將配方從數據庫寫入到中間變量 If Select_UA("@NOP::" &tagPrefix& "UA_Recipe","Recipe_ID",RecipeID) <> 0 Then Msgbox TranslateText("讀取配方失敗!",2052) Exit Sub End If '將配方從中間變量寫入到控件 Dim objName For Each objName In objNameList ScreenItems(objName(0)).text = HMIRuntime.Tags("@NOP::" &tagPrefix& "M_"&objName(1)).Read Next '將配方信息從中間變量寫入到控件 ScreenItems("Recipe_Name").text = HMIRuntime.Tags("@NOP::" &tagPrefix& "M_RecipeName").Read ScreenItems("Recipe_Description").text = HMIRuntime.Tags("@NOP::" &tagPrefix& "M_RecipeDescription").Read '設置按鈕狀態 ScreenItems("BT_New").Enabled=True ScreenItems("BT_Delete").Enabled=True ScreenItems("BT_Save").Enabled=False ScreenItems("BT_Download").Enabled=True End Sub
依賴函數
上述代碼中依賴一些其他的函數。
TranslateText()
TranslateText()函數用於腳本內文本多語言翻譯,函數的設計和使用詳見:WinCC腳本內文本多語言化的一種方法
Check_LawlessChar()
輸入配方編號要求為字母和數據,Check_LawlessChar()函數通過正則表達式判斷輸入的字符是否符合要求,符合則返回1,不符合返回0。代碼如下:
Function Check_LawlessChar(strName) Dim regEx Set regEx = New RegExp regEx.Pattern = "^[a-z0-9A-Z]+$" regEx.IgnoreCase = True regEx.Global = True Validate = regEx.test(strName) Set regEx = Nothing Check_LawlessChar = Not Validate End Function
ConnectDatabase()
ConnectDatabase()函數用於簡化數據庫連接,代碼如下:
Function ConnectDatabase(cnn,rs,SQL) Dim cnnStr cnnStr = "Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog="& HMIRuntime.Tags("@NOTP::@DatasourceNameRT").Read &";Data Source=" & HMIRuntime.Tags("@NOTP::@ServerName").Read & "\WINCC" cnn.ConnectionString = cnnStr cnn.CursorLocation = 3 cnn.Open rs.Open SQL,cnn,1,3 End Function
EsigDialog()和CreateOpMsg()
EsigDialog()和CreateOpMsg()分別用於生產電子簽名和記錄審計追蹤,詳見:WinCC的電子簽名與審計追蹤 2.0
PrefixToName()
審計追蹤需要記錄設備名稱,已知設備的變量前綴,可以得到對應的變量名稱,PrefixToName()就是為了實現這個作用,但要根據項目修改和添加一下代碼:
Function PrefixToName(Prefix) Select Case Trim(Prefix) Case "XX_" PrefixToName = TranslateText("設備1", 2052) Case "XX1_" PrefixToName = TranslateText("設備2", 2052) Case "" PrefixToName = "System" Case Else PrefixToName = Prefix End Select End Function
畫面全局聲明區定義
將函數所需要的三個參數定義在畫面的全局聲明區中,在控件的腳本中就可以直接使用。
打開全局聲明區只需打開畫面中任意一個VBS編輯器,點擊下圖中紅色方框的按鈕,就可顯示全局聲明區。
全局聲明區代碼示例如下:
'數據庫名稱 Dim DSN DSN = "UA#XX_Recipe" '控件名稱\數據庫字段名,變量名稱 Dim objNameList objNameList = Array(_ Array("i16_Set_VfFaninfeed", "i16_Set_VfFaninfeed"),_ Array("i16_Set_VfFanPreheat", "i16_Set_VfFanPre-heat"),_ Array("i16_Set_VfFan1Heat", "i16_Set_VfFan1#Heat"),_ Array("i16_Set_VfFan1Cool", "i16_Set_VfFan1Cool"),_ Array("i16_Set_VfFan2Cool", "i16_Set_VfFan2Cool"),_ Array("Recipe_Setbeltspeed", "Recipe_Setbeltspeed"),_ Array("Recipe_Setheat2", "Recipe_Setheat2"),_ Array("Sv1Height_Upperlimit", "r32_Set_Sv1Height_Upperlimit"),_ Array("Sv2Height_Upperlimit", "r32_Set_Sv2Height_Upperlimit"),_ Array("Sv3Height_Upperlimit", "r32_Set_Sv3Height_Upperlimit") _ ) '變量前綴 Dim TagPrefix TagPrefix = "XX_"
操作批次的函數執行過程
點擊新建、保存、下載、上載、刪除按鈕以及查詢配方,執行流程如下:
新建配方
新建按鈕中的腳本如下:
Sub OnClick(Byval Item) NewRecipe TagPrefix, DSN, objNameList End Sub
點擊“新建”按鈕,調用NewRecipe()函數,將會執行以下操作:
- 檢查“BT_Save”按鈕是否使能,判斷當前是否有正在編輯的配方沒保存,彈出提示框詢問用戶是否繼續操作;
- 彈出對話框要求輸入配方編號、配方名稱和配方描述,檢查輸入的配方編號是否符合要求,和是否與現有的配方編號重復;
- 清空界面中參數控件的值,向配方信息控件寫入用戶輸入的值;
- 更新按鈕的使能狀態。
保存配方
保存按鈕中的腳本如下:
Sub OnClick(Byval Item) SaveRecipe TagPrefix, DSN, objNameList End Sub
點擊“保存”按鈕,調用SaveRecipe()函數,將會執行以下操作:
- 檢查是否有配方編號和配方版本;
- 驗證電子簽名;
- 查找數據庫中該配方編號的最大版本,計算得到下一配方版本;
- 清空中間變量;
- 向中間變量寫入界面上顯示的值;
- 將中間變量的值保存到用戶歸檔;
- 記錄審計追蹤;
- 更新界面上配方版本控件的值;
- 更新按鈕的使能狀態。
下載配方
下載按鈕中的腳本如下:
Sub OnClick(Byval Item) DownloadRecipe TagPrefix, DSN, objNameList End Sub
點擊“下載”按鈕,調用DownloadRecipe()函數,將會執行以下操作:
- 檢查是否選擇了一個配方;
- 驗證電子簽名;
- 通過配方編號和配方版本計算得到配方ID;
- 用配方ID從用戶歸檔查詢配方值寫入到中間變量;
- 將中間變量的寫入到外部變量;
- 記錄審計追蹤;
- 更新按鈕的使能狀態。
上載配方
上載按鈕中的腳本如下:
Sub OnClick(Byval Item) Upload TagPrefix, DSN, objNameList End Sub
點擊“上載”按鈕,調用Upload()函數,將會執行以下操作:
- 檢查“BT_Save”按鈕是否使能,判斷當前是否有正在編輯的配方沒保存,彈出提示框詢問用戶是否繼續操作;
- 如果當前界面已選擇了配方,詢問用戶是否上載到當前配方編號;
- 驗證電子簽名;
- 如果需新建配方編號,彈出對話框要求輸入配方編號、配方名稱和配方描述,檢查輸入的配方編號是否符合要求,和是否與現有的配方編號重復;
- 如果使用當前配方編號,則只需輸入新的配方描述;
- 查找數據庫中該配方編號的最大版本,計算得到下一配方版本;
- 把外部變量的值寫入到中間變量;
- 把控件中的配方信息寫入到中間變量;
- 把中間變量的值保存到用戶歸檔;
- 記錄審計追蹤;
- 更新配方編號和配方版本控件的值;
- 更新按鈕的使能狀態。
刪除配方
刪除按鈕中的腳本如下:
Sub OnClick(Byval Item) DeleteRecipe TagPrefix, DSN, objNameList End Sub
點擊“刪除”按鈕,調用DeleteRecipe()函數,將會執行以下操作:
- 檢查是否選擇了一個配方;
- 驗證電子簽名;
- 通過配方編號和配方版本計算得到配方ID;
- 用配方ID從用戶歸檔查詢配方值寫入到中間變量;
- 檢查“M_IsProduced”是否為1,判斷該配方是否被生產過,以確定是否要刪除該配方;
- 如果“M_IsProduced”是否為1,則把“M_IsDeleted”變量置1,更新用戶歸檔中的值;
- 如果“M_IsProduced”是否為0;則直接在用戶歸檔中刪除該配方;
- 記錄審計追蹤;
- 更新配方編號控件的值。
- 更新按鈕的使能狀態。
編輯配方
編輯配方有以下2點要求:
- 能告知被編輯的參數的最大值和最小值,輸入的值不能超過最大值和最小值;
- 編輯在線配方值時要求有電子簽名和審計追蹤。
要滿足上述要求,就不能直接在Wincc的輸入輸出域控件里輸入值,需要設計一個輸入界面顯示參數的最大值和最小值,用腳本檢查輸入的是否在范圍之內,編輯在線配方值時還要進行電子簽名。輸入界面如下,點擊數值控件時以對話框形式彈出,類似於屏幕鍵盤(以下稱作屏幕鍵盤界面)。
屏幕鍵盤界面是個通過畫面窗口調用的畫面,這個畫面窗口平常隱藏,需要輸入配方值時通過調用函數顯示畫面窗口,編輯數據庫配方值和在線配方值用的是不同的調用函數。
編輯數據庫配方值時調用KeyboardDispScreen()函數顯示屏幕鍵盤界面,示例如下:
Sub OnClick(ByVal Item) KeyboardDispScreen AccessPath, Item.ObjectName, 0, 50, "配方變量:進瓶風機頻率設定(Hz)" End Sub
編輯在線配方值時調用KeyboardDispTag()函數顯示屏幕鍵盤界面,示例如下:
Sub OnClick(Byval Item) KeyboardDispTag parent.TagPrefix, "i16_Set_VfFaninfeed", 0, 50, Item.TooltipText End Sub
屏幕鍵盤界面通過位於根畫面的畫面窗口控件引用,畫面窗口控件名為“Keyboard”。屏幕鍵盤界面中有四個隱藏的文本控件,分別用於存儲變量前綴、變量名、畫面路徑、控件名稱,然后屏幕鍵盤界面里的代碼就可以使用這幾個值。KeyboardDispScreen()函數只傳遞畫面路徑和控件名稱,KeyboardDispTag()函數只傳遞變量前綴和變量名。
KeyboardDispScreen()函數代碼如下:
'--------------------------------------------------------------------------------- ' ScreenPath:調用函數的控件所在的畫面路徑 ' ItemName :調用控件的控件名 ' MinVal :最小值 ' MaxVal :最大值 ' TagNote :被編輯的參數的描述 '--------------------------------------------------------------------------------- Function KeyboardDispScreen(ScreenPath,ItemName,MinVal,MaxVal,TagNote) Dim objScrKeyboard,objScrWindow Set objScrKeyboard=HMIRuntime.Screens(BaseScreenName).ScreenItems("Keyboard") objScrKeyboard.CaptionText=TagNote'TagName objScrKeyboard.Visible=True Set objScrWindow = objScrKeyboard.Screen objScrWindow.ScreenItems("ScreenPath").Text = ScreenPath objScrWindow.ScreenItems("ItemName").Text = ItemName objScrWindow.ScreenItems("ET_MinValue").OutputValue = MinVal objScrWindow.ScreenItems("ET_MaxValue").OutputValue = MaxVal End Function
KeyboardDispTag()函數代碼如下:
'--------------------------------------------------------------------------------- ' Prefix :變量的前綴 ' ItemName :變量名稱 ' MinVal :最小值 ' MaxVal :最大值 ' TagNote :被編輯的變量的描述 '--------------------------------------------------------------------------------- Function KeyboardDispTag(Prefix,TagName,MinVal,MaxVal,TagNote) Dim objScrKeyboard,objScrWindow Set objScrKeyboard=HMIRuntime.Screens(BaseScreenName).ScreenItems("Keyboard") objScrKeyboard.CaptionText=TagNote'TagName objScrKeyboard.Visible=True Set objScrWindow = objScrKeyboard.Screen objScrWindow.ScreenItems("Prefix").Text = Prefix objScrWindow.ScreenItems("TagName").Text = TagName objScrWindow.ScreenItems("ET_MinValue").OutputValue = MinVal objScrWindow.ScreenItems("ET_MaxValue").OutputValue = MaxVal End Function
存儲變量前綴、變量名、畫面路徑、控件名稱的四個控件獲得值后,就會腳本寫入到全局聲明的變量,便於之后使用。
“確認”按鈕中的代碼如下,如果控件名稱(ItemName)變量不為空,就執行寫入到界面控件的腳本,其中第36行至53行代碼會將調用控件同一個畫面的某些按鈕設為啟用或禁用。
如果變量名稱(TagName)變量不為空,驗證電子簽名后就會執行寫入到變量的代碼。
Sub OnClick(Byval Item) Dim objScrKeyboard, InputValue, MaxValue, MinValue, OutputValue ' 獲取畫面窗口控件 Set objScrKeyboard = Parent '檢查輸入內容是否為空 If Trim(ScreenItems("Txt_Value").Text)="" Then objScrKeyboard.Visible=False Exit Sub End If '獲取數據值和限制值 InputValue=CSng(ScreenItems("Txt_Value").Text) MaxValue=CSng(ScreenItems("ET_MaxValue").OutputValue) MinValue=CSng(ScreenItems("ET_MinValue").OutputValue) '檢查值范圍 If InputValue>MaxValue Then OutputValue=MaxValue Elseif InputValue<MinValue Then OutputValue=MinValue Else OutputValue=InputValue End If If ItemName <> "" Then Dim objScrEdit '獲取畫面對象 Set objScrEdit=HMIRuntime.Screens(ScreenPath) '寫入值 objScrEdit.ScreenItems(ItemName).text = CStr(OutputValue) '更新按鈕狀態 Dim EnableButtuns, DisableButtuns, ButttunName EnableButtuns = Array("BT_Save") DisableButtuns = Array("BT_Download", "BT_Delete") Dim ItemNameTemp For Each ItemNameTemp In objScrEdit.ScreenItems If ItemNameTemp.Type = "HMIButton" Then For Each ButttunName In EnableButtuns If ItemNameTemp.ObjectName = ButttunName Then ItemNameTemp.Enabled = True End If Next For Each ButttunName In DisableButtuns If ItemNameTemp.ObjectName = ButttunName Then ItemNameTemp.Enabled = False End If Next End If Next End If If TagName <> "" Then '電子簽名,寫入值 Dim TagNote TagNote = parent.CaptionText TagNewValueES PrefixToName(Prefix), TagNote, Prefix & TagName, OutputValue 'HMIRuntime.Tags(Prefix & TagName).Write OutputValue End If objScrKeyboard.Visible=False End Sub