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