start dmh_wish.exe -tclargs "proc start args {}; proc goto args {}; set ::target {%1}; set ::connection {%2}; set ::groupname {%3}; source sub2group.bat" goto EndOfTcl # $Header: /usr/cvsroot/html85/indexes/sub2group.html,v 1.1 2009/05/12 15:20:51 hume Exp $ # # Usage: sub2group [<DMH Groupname> [<OPC connection name> [<OPC Group name>]]] # # <DMH Groupname> := [hostname:]groupname defaults to localhost:OPC # which is equivalent to OPC # # This executable script is an example of subscribing to the OPC data being # collected from an OPC server for a specified group. The application can be # run on any computer - not just the one where the OPC connections are made. # This example uses unique names so you can run multiple instances of it. # This is a production worthy example. It incorporates safety checks # to close the subscriptions and halt the data flow in the event of an unexpected # disconnection. # # With the use of asynchronous message queues, this application has much better performance # than DCOM or web services without the complexity of dealing with multiple threads. # # # Licensed and Supported Software # (C)Copyright 2009, Hume Integration Software # All Rights Reserved # # we use the Hume DMH message system package require dmh # bring DMH commands into the global namespace dmh_import # # This is the "main" procedure - connect to the DMH server/OPC interface process # and setup our data subscriptions. # proc subscribe {} { global target connection groupname pick env MB_TCL MB_REPLY MB_SERVER MB_DATA # in case of editing and re-sourcing, only execute this code once if {[info exists MB_SERVER]} return # decorate the main window wm title . "Sub2group Example" # only Windows has a console command - this code can run on Linux, etc if {[info commands console] eq "console"} {console title "Sub2group Console"} message .msg -aspect 600 -text \ "sub2group.bat - An example of using OPC data in another process which could be\ running on a different computer. The example is coded in Tcl/Tk - the .bat file\ contains the application source code. The example demonstrates setting up a real-time\ data feed. We assume that the OPC interface process is already running, and that it has\ been configured to initialize as a DMH server using the DMH group name \"OPC\". \251 Copyright 2009, Hume Integration Software, http://www.hume.com" pack .msg -side top -padx 4m -pady 4m button .b -text "Exit" -command exit -width 12 pack .b -side top -fill x -expand 0 -padx 40m -pady 10m # connect to the OPC DMH server set group localhost:OPC # You can pass the host:group for the DMH server on the command line. # where it is assigned to global variable target. if { [string trim $target] ne ""} {set group $target} mbx init $group # setup receiving using some unique mailbox names set clientID [mbx clientID] set base SUB2G_${clientID} # process Tcl commands for remote debugging set MB_TCL ${base}_RPC mbx whenmsg $MB_TCL mbx_RPC # print reply messages to the console set MB_REPLY ${base}_REPLY # setup receiving of subscription data mbx whenmsg ${base}_REPLY {mbx whenmsg again; puts $mbxmsg} # the DMH server (OPC connection process) uses the mailbox SERVER_RPC for Tcl commands set MB_SERVER SERVER_RPC # which OPC connection? and which group? # You can pass the connection value on the command line as the second argument # where it is assigned to the global variable connection. set conn [string trim $connection] # You can pass the groupname value on the command line as the 3rd argument # where it is assigned to the global variable groupname. set group [string trim $groupname] if { ($conn eq "") || ($group eq "") } { # put up a choice dialog set cols {ocname groupname cfg_handle} set reply [mbx_xact $MB_SERVER "SQL {select [join $cols ,] from opc_group}"] vset $reply {rc result} if { $rc != 0} { error "unexpected error: $result"} set picks [lindex $result 6] set pick "" dmh_listpick $picks \ "Choose a connection and group from this list of connection, group, and handles:"\ "set ::pick" "OPC Group Subscription" .listpick tkwait window .listpick if { $pick eq ""} exit vset $pick {conn group group_handle} } # update the main window title wm title . "Sub2group Example - $conn $group" # setup receiving and processing of subscription data - use a different mailbox for each # subscription to have independent cleanup. if {![info exists group_handle]} { ;# (cannot pass on command line) # it is nice to use the group handle in the mailbox name - better for diagnostics set reply [mbx_xact $MB_SERVER \ "SQL {select cfg_handle from opc_group where ocname='$conn' and groupname='$group'}"] vset $reply {rc result} if { $rc != 0} { error "unexpected error: $result"} set group_handle [lindex $result 6 0 0] } # use a unique mailbox for this subscription set MB_DATA ${base}_${conn}_${group_handle} mbx whenmsg $MB_DATA {receive_data $mbxmsg ; mbx whenmsg again} # We call a procedure that is expressly for setting up OPC data subscriptions - # it adds safety checking and cleanup if we are disconnected, and # it packs multiple data changes into each DMH message # to conserve on bandwidth and processing. set cmd [list opc_subscribe_connection $clientID $MB_DATA $conn] mbx putr $MB_SERVER $MB_REPLY $cmd # setup some at_exit cleanup code # close the subscription when we exit, and get rid of the mailboxes set cmd [list opc_subscribe_close $clientID $MB_DATA] set cmd2 "foreach mb {$MB_REPLY $MB_DATA $MB_TCL} {mbx flush \$mb ; mbx close \$mb}" set cleanup "$cmd ; $cmd2" # also allow a little time for event processing and sending ::dmh::dp_atexit prepend "[list mbx put $MB_SERVER $cleanup]; wait 200" # destroying the main window escapes calling Tcl exit without this protocol binding wm protocol . WM_DELETE_WINDOW exit } # # This procedure is called as data is received from our subscription. # At the sending end, multiple subscription notices are combined into each message. # Each notice is formatted in the SQL Tcl Reply Format. (see Datahub HTML reference) # To keep this example simple we won't do much with the data - just parse it and set # some global data items. Tcl has an odbc command to work with databases, the file # command for creating log files, etc. There are also applications for SPC and # plotting such as the Hume Data Collection Component. # proc receive_data {message} { global ct data incr ct foreach msg $message { if { $ct < 0} { # change 0 to N to show the format in the console for the first N messages puts "#### Notice #####\n$msg" } vset $msg {notice table cols keys ct err rows stmt} # $table is opc_item - see the documentation to understand the table fields if { $notice eq "create"} { puts "Create statement: \"$stmt\"" # SQL $stmt return } if {$notice eq "select"} { # this notice is an initial selection of all the items that are in groups foreach row $rows { vset $row $cols # global variables can be linked to fields in a window foreach f {ts quality value} { set data(${item_id},$f) [set last_$f] #puts "data(${item_id},$f)=[set last_$f]" } } return } if {$notice eq "update"} { # this is where most the action is foreach row $rows { vset $row $cols # foreach col $cols { puts "\t$col=[set $col]" } # You receive the table keys - item_id and ocname with every notice. # You only receive a field such as last_value if the value has changed. # You probably will not see last_quality except in the initial selection # since it is not likely to change. foreach f {ts quality value} { if {[info exists last_$f]} { set data(${item_id},$f) [set last_$f] puts [format "%40s = %s" ${item_id}:$f [set last_$f]] } } } return } } } ################################################################## # start running the subscribe procedure when the file is sourced subscribe ################################################################## # # \ :EndOfTcl