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.
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] package require TclRingBuffer if {[llength argv] != 1} { puts stderr "Usage:" puts stderr " simplescript ring-uri" exit -1 } set ringUri [lindex $argv 0] ring attach $ringUri for {set i 0} {$i < 100} {incr i} { set ringItem [ring get $ringUri] # 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 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 } puts ""; # Separate items with a blank line. } ring detach $ringUri
If the correct number of command line arguments is supplied,
the URI is stored in the ringUri
variable for
later use.
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.
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.
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
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:
Creates a new thread and returns a handle to it called the threadid.
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.
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:
Create a thread that will acquire data from a ring and send it back to us.
Push commands into the thread to load the ring access package into that thread (remember packages are loaded into interpreters, not applications).
Push a proc into the thread that will do the get/event post.
Push a call to that proc with the -async option so that the main thread won't block on that command but can continue to run and, eventually enter its event loop.
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} { set acqThread [thread::create -joinable] if {[thread::send $acqThread [list lappend auto_path $::libdir] result]} { puts "Could not extend thread's auto-path" exit -1 } if {[thread::send $acqThread [list package require TclRingBuffer] result]} { puts "Could not load RingBuffer package in acqthread: $result" exit -1 } if {[thread::send $acqThread [list ring attach $ringUrl] result]} { 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] set getItems "proc getItems {tid uri} { while 1 { set ringItem \[ring get \$uri {1 2 20}] thread::send \$tid \[list handleData \$ringItem] } } getItems $myThread $ringUrl " thread::send -async $acqThread $getItems return $acqThread }
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.
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.
startAcqThread
. This thread
will be used as the target of
thread::send commands in the
parent thread.
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.
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.
-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.
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.