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