WinCC配方设计-基于用户归档

配方是给机器设定的一组运行参数,当机器要生产不同规格的产品时,可以给机器设定不同的参数。

配方的设计要点之一是如何保存配方。制药行业要求对生产数据一般保存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

  

原文地址:https://www.cnblogs.com/yada/p/12212708.html