zoukankan      html  css  js  c++  java
  • 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
    

      

  • 相关阅读:
    broncho a1 hack指南-准备硬件
    嵌入式GUI ftk0.1发布
    ASP.net页面防刷新
    C#反射入门教程(转)
    万物生萨顶顶
    [转载内容]C# win程序中主窗体菜单的权限控制
    VB.net技巧更新(一)
    XML与dataset里相互读取操作
    操作EXCEL代码(c#完全版)
    [转载内容]动态创建菜单,menustrip,根据权限显示菜单,控制菜单可用,反射,给窗体传值,反射对象传值,public static Object CreateInstance ( Type type, params Object[] args )
  • 原文地址:https://www.cnblogs.com/yada/p/12212708.html
Copyright © 2011-2022 走看看