57.2. Usage patterns

This section describes two usage patterns. The first usage pattern is the simplest and is best used for simple Tcl scripts (e.g. scripts that don't have a graphical user interface). The second usage pattern is needed when you have an application that requires a live event loop, such as a Tk application.

The examples in this package assume that you've either sourced the daqsetup.bash script or somehow defined the DAQROOT environment variable.

57.2.1. Using the package in a simple script

This section shows a simple script that just takes data from a ring and dumps the string representations of the ring items it gets. If you are not clear on the shape of the data returned by the ring get, this script can be useful, especially if you modify the ring get command to only return the ring item you care about.

Note that since the ring get command blocks until a ring item bcomes available, this usage pattern is not suitable for use in Tk applications or other Tcl scripts that need to maintain a live eventloop. See the next section for a pattern appropriate to that situation.

Example 57-2. Simple usage of the Tcl Ring access package.


lappend auto_path [file join $::env(DAQROOT) TclLibs]   (1)

package require TclRingBuffer                           (2)

if {[llength argv] != 1} {
    puts stderr "Usage:"                               (3)
    puts stderr "  simplescript ring-uri"
    exit -1
}
set ringUri [lindex $argv 0]

ring attach $ringUri                                 (4)

for {set i 0} {$i < 100} {incr i} {
    set ringItem [ring get $ringUri]                (5)

    # Note that Event ring items have a body that is binary.
    # binary scan lets us convert that, in this case, to a list of
    # integer values, one integer for each uint16_t.

    if {[dict get $ringItem type] eq "Event"} {
        binary scan [dict get $ringItem body] su* data  (6)
        puts [dict remove $ringItem body]
        puts "Event body:"
        set hexData [list]
        foreach datum $data {
            lappend hexData [format %04x $datum]
        }
        puts $hexData
    } else {
        # All other ring types are purely textual:

        puts $ringItem                             (7)
    }
    puts "";                        # Separate items with a blank line.
}
ring detach $ringUri                              (8)

            
(1)
The first thing done by the script is to ensure that the Tcl library directory tree for NSCLDAQ packages is part of the package library search path. This line assumes that the DAQROOT environment variable points to the top level of the NSCLDAQ installation script.
(2)
Actually loads and initializes the package. This line adds the ring command ensemble to the script's interpreter.
(3)
The script takes a ring URI as a command line parameter. This section of code prints out an error message if the URI is not on the command line, or if the user supplies additiona, unexpected, arguments.

If the correct number of command line arguments is supplied, the URI is stored in the ringUri variable for later use.

(4)
Attaches to the ring. Note that if the ring does not exist, this command will return an error. This command will also return an error if the number of consumer programs exceeds the consumer limits on the ring.
(5)
This the ring get command returns a dict that contains the next ring item in the ring. Note that if no ring item is available, the command will block the script until one is available. See the next section if this is a problem.

The ring get command can accept an additional optional parameter. If present, it is a list of numerical item types the ring get will return. Any ring item type not in the list will be skipped. See the DataFormat.h for the item types currently defined.

(6)
The only complication in this script is that the Event ring item type will contain a body key that contains binary data. This allows scripts to interpret the body of an event in an application specific manner.

The code that handles the Event item type uses the binary scan Tcl command to interpret the body of the event as a list of unsigned uint16_t words. These are rendered as a list of four digit hexadecimal values after the body item is removed from the ring item dict so that it will not be output.

(7)
Since the contents of the dict keys present in other ring items have good string representations, all ring items that are not of type Event are just output as is.

Below we show what an excerpt of the output from the start of a run might look like:


type {Ring Item format version} major 11 minor 0

type {Begin Run} run 0 timeoffset 0 realtime 1411479570 title {Set New Title}
bodyheader {timestamp -1 source 0 barrier 1}

type {Trigger count} timeoffset 0 divisor 1 realtime 1411479570 triggers 0

type Event size 24
Event body:
000c 0000 68fd 2958 0000 0000 0000 0001 0002 0003 0004 0005

type Event size 24
Event body:
000c 0000 68fe 2958 0000 0000 0000 0001 0002 0003 0004 0005
           

57.2.2. Tcl ring package usage pattern for event loops.

When an event loop is present in the application (such as in Tk applications), the fact that ring get can block can be a problem. Specifically, calls to ring get not only starve the event loop, but you'd need to either arrange for the event loop to execute manually, or schedule the ring get command to run from the event loop (using the after command for example).

A much simpler approach, is to use the Tcl thread package to run the ring get command in a separate interpreter thread and have the completion of that command post an event containing the ring item to the main thread. The new scaler display program uses this technique to not only maintain the liveness of the GUI but, by using a thread per ring, it supports getting data from more than one ring buffer at a time.

Before giving an example, it's useful to first look at the thread package manpage and review a few of the commands we will use. The Tclers Wiki includes a manpage for the thread package at: http://www.tcl.tk/man/tcl/ThreadCmd/thread.htm

The commands we will use are:

thread::create

Creates a new thread and returns a handle to it called the threadid.

thread::send

Sends a command to be executed in another thread (the thread must be running an event loop). Note that if you create a thread with no script it enters an event loop.

thread::id

Returns the id of the currently running thread. This is most commonly done to pass the id of the thread to another thread so that that thread can send a command/event back.

Armed with this background, our strategy will be to:

There can be a bit of trickiness in pushing procs and other commands into a thread since you have to think about when you need to escape Tcl special characters and when you want substitutions done (in the target thread or the main thread). Let's look at the proc startAcqThread used by the new scaler display program to set up a ring reading thread:

Example 57-3. Setting up a ring readout thread in Tcl


    proc startAcqThread {ringUrl} {                                                   (1)
        set acqThread [thread::create -joinable]                                      (2)
        if {[thread::send $acqThread [list lappend auto_path $::libdir] result]} {    (3)
            puts "Could not extend thread's auto-path"
            exit -1
        }
        if {[thread::send $acqThread [list package require TclRingBuffer] result]} { (4)
            puts "Could not load RingBuffer package in acqthread: $result"
            exit -1
        }
    
        if {[thread::send $acqThread [list ring attach $ringUrl] result]} {        (5)
            puts "Could not attach to scaler ring buffer in acqthread $result"
            exit -1
        }
    
        #  The main loop will forward data to our handleData item.
    
        set myThread [thread::id]                                               (6)
        set getItems "proc getItems {tid uri} {
            while 1 {
                set ringItem \[ring get \$uri {1 2 20}]                        (7)
                thread::send \$tid \[list handleData \$ringItem]
            }
        }
        getItems $myThread $ringUrl                                           (8)
        "
        thread::send -async $acqThread $getItems                             (9)
    
    
        return $acqThread                                                   (10)
    }
    
            
(1)
Defines a proc startAcqThread. This proc is going to create the acquisition thread and arrange for it to post events to the thread of the caller when ring items are available. The parameter ringUrl must be a URI that specifies an existing ring.
(2)
Creates a child thread. The thread::create command is not given a script to execute. This means that the child thread will enter its event loop and be able to respond immediately to thread::send commands that target it. This is one of the common patterns of thread creation/management in Tcl.
(3)
Since package inventories are a property of a thread not of the application the auto_path must be extended in the thread's interpreter to include the TclLibs directory tree of the NSCLDAQ installation. This command pushes a command to do that into the child interpreter.
(4)
Pushes the package require command into the child process needed to load the package into the child thread's interpreter. Recall that package inventories are a per-interpreter item. Thus the package must be required in the context of the child thread.
(5)
Pushes the ring attach command into the child thread. This attaches the child thread to the ring.
(6)
Obtains the thread id of the thread that is calling startAcqThread. This thread will be used as the target of thread::send commands in the parent thread.
(7)
This section of code creates a variable named getItems. That variable will contain the text of a proc named getItems and an invocation of that proc. The proc will loop forever getting items from the ring and using thread::send to schedule the execution of the proc handleData in the thread named by the variable tid. The proc will be passed the dict returned by the ring get command.

For this to work properly, the thread that handleData is executed in must enter the event loop in a timely way.

(8)
Once the proc has been built up in the string, we add an invocation of the proc. Note that in this case we have not escaped the $ substitutions as they provide parameters to the call to getItems that are only known in the parent thread, the parent thread's thread id, and the URI of the ring from which we want the thread to read data.
(9)
Sends the command we built up to the readout thread. Using the -async option means that the thread::send command returns immediately rather than waiting for the sent command to complete. This is a good thing since the command we sent won't complete.
(10)
The id of the created thread is returned. For threads that spend significant time in an event loop, returning the thread of the created thread is useful because it allows other threads to communicate with it. In this case it's not so useful because once the main loop in getItems is running the thread won't re-enter the event loop.

For the proc in the preceeding example to work, you need to have a proc named handleData defined and visible to the thread that calls startAcqThread. That proc must take a single parameter that will be the dict the acquisition thread's ring get commands return. Furthermore, the thread that invokes startAcqThread must mostly live in the event loop. This is normally the case for Tk applications. It is also the case for Tcl applications whose script ends with a call to vwait.