In this section, we will write an event processor for the packetized event source we wrote in the previous chapter. This event processor will recognize our packet id, and ignore all other packet itds. This will allow it to operate as part of the event unpacking of a larger experiment, as long as all parts of the experiment produce a packetized bunch of data. We will use this process of writing an Event processor and integrating it with SpecTcl as an example that shows how to produce an experiment specific SpecTcl.
![]() | The device physicists that maintain the NSCL supported experimental devices all have and maintain event processors for the packets produced by their devices. If your experiment uses one of these devices, rather than writing an event processor for that device yourself, contact the appropriate device physicist for an event processor and integrate it into the rest of your experiment. |
Obtain the SpecTcl skeleton files.
Write one or more event processors to decode parameters from the raw event, or to produce computed parameters from parameters that have already been decoded.
Modify MySpecTclApp.cpp to register your event processors in the order in which you want them executed.
Modify the Makefile to include any new C++ files you wrote in your build.
Use the make command to compile and link your tailored SpecTcl.
At the NSCL we have several versions of SpecTcl installed at any given time. This provides the needed stability for analyses that are underway to complete without forcing scientists to port their software to every new version of SpecTcl as it is released. At the NSCL, SpecTcl is installed in /usr/opt/spectcl numbered subdirectories below this root identify the version of SpecTcl that is installed there, for example, /usr/opt/spectcl/2.2 is the directory in which SpecTcl version 2.2 is installed. Edit levels of SpecTcl within a specific version are supposed to be object compatible with the shared libraries that make up SpecTcl. This allows repairs of defects within a version to be deployed and the users of a specific version of the software to make use of a fix without any explicit action on their part.
A symbolic link current points to the current preferred version of SpecTcl for new development. At the time of writing this, /usr/opt/spectcl/current is a link to /usr/opt/spectcl/3.0-gcc3.x. This version 3.0 of SpecTcl built by the gcc 3.3 compiler. Version 3.0 is a bridge between versions of SpecTcl that only compiled on the gcc-2.95 compilers and those which compile on 3.0 and later. At the NSCL two versions of 3.0 are installed 3.0-gcc3.x and 3.0-gcc2.95. This allows you to port code from SpecTcl 2.2 to SpecTcl 3.0 separately from any work required to port code from gcc 2.95 to gcc 3.x and later compilers.
Within an installation of SpecTcl, the Skel directory contains skeleton files. Therefore, obtain skeleton software:
Example 3-1. Obtaining the SpecTcl Skeleton
cd ~ mkdir spectcl cd spectcl cp /usr/opt/spectcl/current/Skel/* .Note that the Makefiles that are distributed with the skeleton refer to the SpecTcl directories via the actual directory names rather than through the current link so that as the current default SpecTcl changes, your Makefiles will not need to be adjusted to continue to work.
Event processors are expected to transform a raw event into a set of named parameters that SpecTcl, in turn can refer to to increment spectra. Event processors are registered with SpecTcl and form an orderd list or Event Processing Pipeline. Each event processor has access both to the raw event and the data that has been event processors that executed prior to it in the pipeline.
Wherever possible, later stages in the pipeline should refer to unpacked data rather than re-decoding the raw event. Not only will this result in more efficient execution, but it allows these later stages to be independent of changes in the event structure.
Event processors are classes that inherit from
CEventProcessor. Event processors are
registered with the SpecTcl framework and form an ordered list that
is referred to as the Event Processing Pipeline.
The important part of the
CEventProcessor header is shown
below:CEventProcessor
Example 3-2. CEventProcessor header file main
features.
class CEventProcessor {
public:
virtual Bool_t operator()(const Address_t pEvent, //
CEvent& rEvent,
CAnalyzer& rAnalyzer,
CBufferDecoder& rDecoder);
// Functions:
virtual Bool_t OnAttach(CAnalyzer& rAnalyzer); //
virtual Bool_t OnBegin(CAnalyzer& rAnalyzer,
CBufferDecoder& rDecoder); //
virtual Bool_t OnEnd(CAnalyzer& rAnalyzer,
CBufferDecoder& rBuffer); //
virtual Bool_t OnPause(CAnalyzer& rAnalyzer,
CBufferDecoder& rDecoder); //
virtual Bool_t OnResume(CAnalyzer& rAnalyzer,
CBufferDecoder& rDecoder); //
virtual Bool_t OnOther(UInt_t nType,
CAnalyzer& rAnalyzer,
CBufferDecoder& rDecoder); //
};
#endif


OnAttach function is called at the time
the object is registered as an event processor with the framework.
Initialization that may not be safe at construction time can be done here,
as by the time this function is called it is pretty well assured that all of
SpecTcl is upand running.





The parameters passed into most of the event processor functions are common and will be described below.
![]() | All event processor functions return a Bool_t value (kfTRUE or kfFALSE). If kfFALSE is returned, the event processing pipeline is aborted without further action. A common mistake in writing event processors is to forget to return a value. Be aware that if you do this, you will be randomly throwing out events. Using the -pedantic compilation switch can help you find errors like this. |
The parameters passed to event processors may vary but they are drawn from the following elements:
Name: rAnalyzer
Type: CAnalyzer&
Meaning: A reference to the object that is controlling the
overall flow of data analysis. As we will see,
one thing we will need this object for is to call back
into it to inform it of the size of the event we are
processing in operator()
Name: rDecoder
Type: CBufferDecoder&
Meaning: A reference to SpecTcl's buffer decoder for this run.
Buffer decoders know about the overall structure of
data buffers from a data acquisition system. The provide
services to SpecTcl and event processors to get information
about the buffer as a whole. One key service they provide
to operator() is knowledge of the
byte ordering of the buffer. This will be used to create
an object that can be treated like a pointer but
transparently compensates for any byte order differences
between the system that created the buffer and the system
analyzing the data. Using these allows your code to run
unmodified on systems regardless of their native byte ordering.
Name: rEvent
Type: CEvent&
Meaning: A reference to the unpacked data. SpecTcl processes data
into histograms from a flat array like object called
an CEvent. As we will see,
however, the TreeParameter
classes, originally introduced by Daniel Bazin, and
incorporated into the supported SpecTcl code starting
with version 3.0, allow you to define a structuring on
top of this flat parameter space.
Name: pEvent
Type: Address_t
Meaning: Points to the raw event. The Address_t
data type is a synonym for void*.
Name: nType
Type: UInt_t
Meaning: The type of date discovered when OnOther
was called. This is a data acquisition system
dependent value that describes the type of data
that should be decoded and processed. In the NSCL
data acquisition system, this is the buffer type field.
We will be writing an event processor that unpacks the data from the event source created in the previous chapter. Recall that this data source produces data in a packet with id 0x8100. The body of the packet contains data from a single CAEN V775 TDC that has a geographical address (either due to its position in the VME crate or due to software assignment) of 10.
We will build a fairly general purpose event processor capable of unpacking data from any packet of that general structure. Take a look at the next example. This contains the header for our event processor:
Example 3-3. Event Processor Header
#ifndef __MYEVENTPROCESSOR_H
#define __MYEVENTPROCESSOR_H
#include <EventProcessor.h>
class MyEventProcessor : public CEventProcessor
{
private:
unsigned short m_id; //
unsigned short m_slot; //
unsigned short m_first; //
public:
MyEventProcessor(unsigned short id,
unsigned short slot,
unsigned short first); //
virtual Bool_t operator()(const Address_t pEvent,
CEvent& rEvent,
CAnalyzer& rAnalyzer,
CBufferDecoder& rDecoder); //
};
#endif
Let's look at the key features of this header.

m_id member variable will store the
id of the packet that we will be unpacking. By making
this a member variable, our unpacker can be easily told
to unpack any packet id that has the same general format
described above

m_slot
will hold the geographical address of the adc we are trying
to decode.

rEvent object.
rEvent is an object that acts a lot
like an array. The m_first member
will describe the element of this "array" that will receive
the data from channel 0. We will unpack the digitzer into
a block of 32 parameters starting with the one
specified by m_first.


operator()),
is called for each event. We will write an implementation
of the function call operator that will locate our packet
and decode the data it contains.
Note that since we won't need to take any specific action when
the event processor is attached, and we are not interested in anything
but event data, we don't override any other functions of
CEventProcessor.
The first section of the implementation file is boilerplate that you will typically need for any event processor:
Example 3-4. Event processor boilerplate
#include <config.h>
#include <MyEventProcessor.h>
#include <TCLAnalyzer.h>
#include <Event.h>
#include <TranslatorPointer.h>
#include <iostream>
#ifdef HAVE_STD_NAMESPACE
using namespace std;
#endif
In addition to including various headers (<config> must be included first as with event sources), symbols are imported from the std namespace if the C++ library maintains them there.
The implementation of the constructor is straightforward:
Example 3-5. Implementing the event processor constructor
MyEventProcessor::MyEventProcessor(unsigned short id,
unsigned short slot,
unsigned short first) :
m_id(id),
m_slot(slot),
m_first(first)
{}
The constructor just initializes its member variables from its parameters.
Let's build up the function call operator a bit at a time. The function call operator body will consist of several sections:
Some boilerplate that is required of all event processor unpacking functions.
An outer loop that will hunt for our packet in the event
Code to unpack the data from the digitizer when our packet is found.
Each event processor must do some standard things:
Obtain a translating pointer to the event so that further processing can be byte order independent.
Determine the size of the event and let the analyzer know what it is so that it knows how to locate the next event to be processed.
Return kfTRUE if the event seems like it should be processed completely, or kfFALSE if there is evidence the event structure is invalid or corrupt.
Example 3-6. Event Processor boilerplate code
Bool_t
MyEventProcessor::operator()(const Address_t pEvent,
CEvent& rEvent,
CAnalyzer& rAnalyzer,
CBufferDecoder& rDecoder)
{
TranslatorPointer<UShort_t> p(*(rDecoder.getBufferTranslator()), pEvent);
CTclAnalyzer& rAna(dynamic_cast<CTclAnalyzer&>(rAnalyzer));
UShort_t nWords = *p;
rAna.SetEventSize(nWords*sizeof(UShort_t));
// More code to be inserted here....
return kfTRUE;
}
Let's dissect this code in some detail:

getBufferTranslator() member of the
buffer decodeer to obtain a TranslatorPointer
object. TranslatorPointer objects act like
pointers but either swap or do not swap bytes as needed depending on
the byte ordering of what they point to vs. the byte ordering of the
host system.

CTclAnalyzer.
This specialized version of the CAnalyzer base class
integrates some statistics with the Tcl interpreter as well as maintaining
the event processing pipeline (CAnalyzer only supports
a single event processor and was the analyzer used by
SpecTcl prior to version 2.0. The use of dynamic_cast to
convert the analyzer reference to a CTclAnalyzer reference
uses C++'s Run Time Type Information (RTTI) system to determine if this cast
is, in fact a valid cast (rAnalyzer must be either a reference to a
CTclAnalyzer or a class ultimately derived from it for a
dynamic_cast to succeed.

p prior
to the increment).



![]() |
We have issued this warning already, but it is worth stressing again, that
forgetting to return kfTRUE from |
The next job of our event processor is to locate the packet whose id
matches m_id. This is done by running through the
packets in the event until we either find one that matches or we run out
of words in the event. There is an implicit assumption in this code that the
event is entirely composed of data in the form of packets (that is word count,
id, body). If this is not the case your job will be harder.
Example 3-7. Event processor searching for the right packet.
This code is located just following the comment in the previous example that reads: More code to be inserted here.....
++p; Int_t words = nWords - 1;while (words > 0) {
UShort_t packetSize = *p; UShort_t packetId = p[1];
if (packetId == m_id) {
// More code will go here... } p += packetSize;
words -= packetSize; } // Should hit the end of the event dead on: if (words < 0) {
cerr << "Warning event packet structure broke down discarding event\n"; return kfFALSE; }
Now lets pick this part of the code apart.

p at the
start of the first packet. Converting the remaining word
count to an integer from an unsigned short is an important
piece of defensive programming. The loop over the event
will only terminate if the remaining word count becomes
≤ 0. Unsigned values can never be less than zero, so
using one there would have made the loop exit condition
an exact match for 0, which may not happen if the event
structure is messed up.




words ensures the while
loop exits when we run out of packets in the
event.
This loop contains the implicit assumption that the entire event body consists of nothing but packets. If this is not the case you will need another mechanism to determine where the first packet in an event is and when you are out of packets.

Prior to unpacking the body of data from one of the CAEN digitizers, it is helpful to make some symbolic definitions. The following set of definitions appears prior to implementation of the constructor:
Example 3-8. Defining bits, masks and shift counts for data words
static const UShort_t GEOMASK(0xf800); static const UShort_t GEOSHIFT(11);static const UShort_t TYPEMASK(0700); static const UShort_t TYPE_HEADER(0x0200); static const UShort_t TYPE_DATA(0);
static const UShort_t TYPE_TRAILER(0x0400); static const UShort_t TYPE_OBINVALID(0x0600); static const UShort_t COUNTMASK(0x3f00); static const UShort_t COUNTSHIFT(8);
static const UShort_t CHANMASK(0x3f);
static const UShort_t UNDERFLOWBIT(0x2000); static const UShort_t OVERFLOWBIT(0x1000);
static const UShort_t DATAMASK(0xfff);
![]()





UNDERFLOWBIT and OVERFLOWBIT
could be set in the second word of a data longword indicating that the
conversion values would be suspect.

Armed with these definitions, lets write the unpacking code for the digitizer:
Example 3-9. Unpacking data from the digitizer:
if (packetId == m_id) {
TranslatorPointer<UShort_t> body = p+2;
UShort_t headerHigh = *body;
UShort_t headerLow = body[1];
body += 2;
if ((headerHigh & TYPEMASK) != TYPE_HEADER) {
cerr << "Expected header but got something else\n";
return kfFALSE;
}
if (((headerHigh & GEOMASK) >> GEOSHIFT) != m_slot) {
cerr << "Expected our slot but didn't get it\n";
return kfFALSE;
}
Int_t channelCount = (headerLow & COUNTMASK) >> COUNTSHIFT;
do {
UShort_t dataHigh = *body;
UShort_t dataLow = body[1];
body +=2;
if( (dataHigh & TYPEMASK) != TYPE_DATA) break;
UShort_t channel = dataHigh & CHANMASK;
UShort_t conversion = dataLow & DATAMASK;
if ((!(dataLow & UNDERFLOWBIT)) && (!(dataLow & OVERFLOWBIT))) {
rEvent[m_first + channel] = conversion;
}
channelCount--;
} while (channelCount >= 0);
if (channelCount != 0) {
cerr << "Warning incorrect channel count!\n";
return kfFALSE;
}
if ((body.getOffset() - p.getOffset())/sizef(short) != packetSize) {
cerr << "Warning packet size incorrect\n";
return kfFALSE;
}
return kfTRUE;
}
In order to defend against a number of things that can go wrong with the data structure there is a bit of complexity in the code. As usual, let's take you through this step by step.

p
to point to the beginning of the packet when we are done, but our code \
will need a pointer that can be modified as we step through the packet body.







channelCount still zero.
If, on the other hand, there is no valid trailer in the data,
and there happens to be a longword that looks like a data word,
the loop will fall through to the termination condition again and
channelCount will be -1 allowing us to
detect this condition. See below.

channelCount is not zero, we must have either had
an early non data longword, or have been missing a non data longword
after seeing all the channels. Either case is an error and
we write a message and discard the event.

body
should be pointing just past the end of the packet body. The
getOffset member of TranslatorPointer
objects returns the offset of the pointer
relative to its underlying buffer in bytes
(hence the division by sizeof(short)).
If the offset between p and body is not
different by the packet size, this is an error as well which is reported, and
causes the event to be discarded.
For completeness, here's the full code of the function call operator:
Example 3-10. Full event unpacker function call operator implementation
#include <config.h>
#include <MyEventProcessor.h>
#include <TCLAnalyzer.h>
#include <Event.h>
#include <TranslatorPointer.h>
#include <iostream>
#ifdef HAVE_STD_NAMESPACE
using namespace std;
#endif
static const UShort_t GEOMASK(0xf800);
static const UShort_t GEOSHIFT(11);
static const UShort_t TYPEMASK(0x0700);
static const UShort_t TYPE_HEADER(0x0200);
static const UShort_t TYPE_DATA(0);
static const UShort_t TYPE_TRAILER(0x0400);
static const UShort_t TYPE_OBINVALID(0x0600);
static const UShort_t COUNTMASK(0x3f00);
static const UShort_t COUNTSHIFT(8);
static const UShort_t CHANMASK(0x3f);
static const UShort_t UNDERFLOWBIT(0x2000);
static const UShort_t OVERFLOWBIT(0x1000);
static const UShort_t DATAMASK(0xfff);
MyEventProcessor::MyEventProcessor(unsigned short id,
unsigned short slot,
unsigned short first) :
m_id(id),
m_slot(slot),
m_first(first)
{}
Bool_t
MyEventProcessor::operator()(const Address_t pEvent,
CEvent& rEvent,
CAnalyzer& rAnalyzer,
CBufferDecoder& rDecoder)
{
TranslatorPointer<UShort_t> p(*(rDecoder.getBufferTranslator()), pEvent);
CTclAnalyzer& rAna(dynamic_cast<CTclAnalyzer&>(rAnalyzer));
UShort_t nWords = *p;
rAna.SetEventSize(nWords*sizeof(UShort_t));
// Locate our event packet and unpack it if we find it:
++p;
Int_t words = nWords - 1;
while (words > 0) {
UShort_t packetSize = *p;
UShort_t packetId = p[1];
if (packetId = m_id) {
TranslatorPointer<UShort_t> body = p+2;
UShort_t headerHigh = *body;
UShort_t headerLow = body[1];
body += 2;
if ((headerHigh & TYPEMASK) != TYPE_HEADER) {
cerr << "Expected header but got something else\n";
return kfFALSE;
}
if (((headerHigh & GEOMASK) >> GEOSHIFT) != m_slot) {
cerr << "Expected our slot but didn't get it\n";
return kfFALSE;
}
Int_t channelCount = (headerLow & COUNTMASK) >> COUNTSHIFT;
do {
UShort_t dataHigh = *body;
UShort_t dataLow = body[1];
body +=2;
if( (dataHigh & TYPEMASK) != TYPE_DATA) break;
UShort_t channel = dataHigh & CHANMASK;
UShort_t conversion = dataLow & DATAMASK;
if ((!(dataLow & UNDERFLOWBIT)) && (!(dataLow & OVERFLOWBIT))) {
rEvent[m_first + channel] = conversion;
}
channelCount--;
} while (channelCount >= 0);
if (channelCount != 0) {
cerr << "Warning incorrect channel count!\n";
return kfFALSE;
}
if ((body.getOffset() - p.getOffset())/sizeof(short) != packetSize) {
cerr << "Warning packet size incorrect\n";
return kfFALSE;
}
}
p += packetSize;
words -= packetSize;
}
// Should hit the end of the event dead on:
if (words < 0) {
cerr << "Warning event packet structure broke down discarding event\n";
return kfFALSE;
}
return kfTRUE;
}
Once we have created our event processor class, we must register an instance of it (an object) with SpecTcl so that it can be appended to the event processing pipeline. This is done by editing MySpecTclApp.cpp, a file that is supplied with the skeleton files you copied when we started this work.
Edit MySpecTclApp.cpp, at the top, amongst the #include directives add an #include for MyEventProcessor.h:
Example 3-11. Adding includes for MyEventProcessor.h
#include <config.h>
#include "MySpecTclApp.h"
#include "EventProcessor.h"
#include "TCLAnalyzer.h"
#include <Event.h>
#include <TreeParameter.h>
#include "MyEventProcessor.h"
Locate the function
CMySpecTclApp::CreateAnalysisPipeline.
Edit its implementation so that it looks like the code below.
Example 3-12. Registering the event processor
void
CMySpecTclApp::CreateAnalysisPipeline(CAnalyzer& rAnalyzer)
{
RegisterEventProcessor(*(new MyEventProcessor(0x8100, 10, 100)), "CaenRaw");
}
RegisterEventProcessor appends the event processor
(first argument) passed to it by reference to the event processing
pipeline. Once appeneded, the event processor's OnAttach
function is called. The second argument is optional. If supplied, it
associates a name with the event processor. The SpecTcl API allows you
to dynamically manipulate the event pipeline. Once registered,
the event processor can be located and manipulated by name.
Event processing pipeline manipulation functions are described in the reference material.
We must modify the skeleton Makefile in order to
incorporate our code into our tailored version of SpecTcl. To do this,
locate the OBJECTS definition and append
MyEventProcessor.o to it as shown below:
OBJECTS=MySpecTclApp.o MyEventProcessor.o
Once this is done simply type:
make
to build your tailored SpecTcl.
In this section we will test our tailored SpecTcl by attaching it to the online event source we wrote in the previous chapter. To do this, we must
Create definitions of the parameters we are unpacking and of the spectra we want to create from them.
Start SpecTcl, introduce its various windows, and get it to display our spectra in the Xamine window.
Connect SpecTcl to the online data source that we created, and start analyzing data from that source, watching spectra increment.
SpecTcl is usually configured by creating a configuration file. This configuration file can be created by hand, or through the graphical user interface (GUI). Since it can be tedious to create many spectra using the GUI this section will demonstrate the manual creation of a configuration file. When we begin to discuss the tree parameter package we will examine how to create spectra with the GUI, as these two are closely intertwined for SpecTcl 3.0 (not so with SpecTcl 3.1 which is just leaving pre-release at the time this section is being written).
SpecTcl is an extension to the Tcl/Tk scripting language. The reference material describes the commands that SpecTcl adds to the core Tcl/Tk language. In order to create parameter definitions and spectrum definitions, we need to learn about two of these commands:
Which binds a name to a
numbered slot in the rEvent
object. This parameter allows you to refer to
parameters by name rather than by hard to remember
indices. For historical reasons, the parameter command
can take serveral forms. We will only use the simplest,
and most modern of these.
Which creates named spectra. The spectrum command can be quite complex, depending on the type of spectrum you are creating. In this section, however we will only create simple one and 2 dimensional spectra. For more information about the spectrum command, see the material on SpecTcl in the reference part.
The parameter command is used to establish
a correspondence between string that is meaningful to you and an
index in the rEvent object. The form of the
parameter command we will be using is:
parameter name number [units]
Where:
Is the name you want to assign to the parameter
Is the number of the parameter you are naming. This
number corresponds to an index into the rEvent
array.
Is an optional units of measure of the parameter. This is used to label appropriate axes of spectra created on this parameter.
One nice thing about using Tcl as a command languages is that we are not limited to just listing parameter commands. We can write a script that generates the parameters. In our example, we want to generate parameters named slot10.channel00 through slot10.channel31 which correspond to parameters number 100 through 131. Below is a script that can do this:
Example 3-14. Creating the parameter definitions
set channel0 100for {set i 0} {$i < 32} {incr i} {
set param [expr $channel0 + $i]
set name [format slot10.channel%02d $i]
parameter $name $param ps
}
If you are not familiar with Tcl programming, you may find the following discussion useful. You should also look at one or more of the Tcl resources listed at the end of the chapter before going much further with the NSCL data acquisition and analysis system as the use of Tcl/Tk is widespread within the system.

rEvent
array we are using is element 100. This element maps to
channel 0 of the TDC. This statement sets a Tcl variable
named channel0 to have the value
100 to reflect this decision. We could use the literal
value 100 throughout the remainder of the script, but
best practices are to pull magic numbers
like this out of the code and turn them into symbolic
constants or variables. This makes them easy to change
later on if necessary, as well as making their meaning
clear

Text enclosed by [] is treated as a command that is executed, and whose result is substituted in place on the command line. The expr command evaluates arbitrary expressions.
If a variable name is preceded by a $ sign, the value of the variable is substituted in place on the command line.


parameter [format slot10.channel%02d $i] [expr $channel0 + $i] ps
That we didn't points up another best practice: Write programs to be executed by computers but read by people.
The spectrum command creates spectra. The general form of this command is:
spectrum name type parameters axis-specs
Where:
Is the name of the spectrum we are creating.
Is the type of spectrum we are creating. SpecTcl provides a rich variety of spectrum types. See the reference material for more information about spectrum types. For this discussion we only need to know that type 1 means a 1-d spectrum and type 2 means a 2-d spectrum.
Spectra are histograms defined on a set of parameters. Each spectrum type requires one or more parameters. The parameters are specified as a Tcl list. A Tcl list is a string of elements separated by whitespace. Elements which themselves have whitespace must be quoted either with "" or {}. Lists can be produced as single parameter either by using the list command or by the same quoting mechanims used to specify a list element with whitespace. In fact another core piece of Tcl philosophy is that statements are Tcl lists where the first element is the command being performed and the remaining elements are parameters to the command.
Specifies the axes of the spetrum. Spectra have one or more specifiable axes. axis-specs is a Tcl list. Each element of that list is in turn a three element list whose elements are the low and high limits and the number of channels into which that range is subdivided. The low and high limits are inclusive.
A sample spectrum definition for a 1-d spectrum that histograms channel 0 of the tdc is shown below:
spectrum slot10.channel00 1 slot10.channel00 {{0 4095 4096}}
Note the need for double {}'s the inner brackets
make the single axis specification a list element of the
list that is quoted by the outer {}'s.
Now we can modify our earlier example to make a 1-d spectrum for each parameter. In addition, just to show how to build 2-d spectra, we'll build a 2-d spectrum where the x axis is channel 0 and the y axis is channel 1. The 2-d spectrum will cover the full parameter range, but only have 512 bins per channel on each axis.
Example 3-15. Adding spectrum creation to the configuration file
set channel0 100
for {set i 0} {$i < 32} {incr i} {
set param [expr $channel0 + $i]
set name [format slot10.channel%02d $i]
parameter $name $param ps
spectrum $name 1 $name {{0 4095 4096}}
}
spectrum slot10.channel00-vs-slot10.channel01 2 \
{slot10.channel00 slot10.channel01} \
{{0 4095 512} {0 4095 512}}
sbind -all



To analyze data, SpecTcl must be connected to a data source. A data source is something that can provide event data. SpecTcl understands three data source types, however only two of them are in typical use in "modern times":
The data source is a file that contains event data
The data source is a program whose standard output produces event data
The attach command is used to specify a event source for SpecTcl. SpecTcl will only analyze data from a single event source at a time. Once attached, the start and stop commands start and stop data taking. Reaching the end of an event source (e.g. end of file on a file event source) automatically stops analysis. In the online environment, it is a bad idea to stop data analysis as that can lead to system buffer exhaustion, which in turn will bring data taking to a halt. SpecTcl will not allow you to issue the attach command while data analysis is in progress on the current event source, however.
The attach command is rather complex, but we will only show two simple forms of it at this time. See the reference material for more information. To attach to a file:
attach -file filename
To attach to a pipe data source:
attach -pipe command
SpecTcl connects to the NSCL online system by using a pipe data source that is part of the NSCL DAQ system. This pipe source, spectcldaq, is a program that attaches to the data acquisition system and copies buffers received from that system to its stdout.
spectcldaq actually makes two attachments to the online system:
An unreliable sink for event data. Unreliable means that if spectcldaq is not ready to receive an event data when it becomes available, it will skip buffers until it is ready.
A reliable sink for control and scaler data. Reliable beans that if necessary, the data acquisition system will stop taking data to let the consumer catch up with control and scaler processing.
To attach to the online system, typically the following commands are issued either by hand or attached to a button on a custom GUI:
attach -pipe /usr/opt/daq/current/bin/spectcldaq tcp://hostname:2602
Where hostname is the name of the host that
is taking data.
Remember that analysis must be stopped, and that analysis will not start until the start command is issued.
The code below adds a button to the bottom of the button strip in SpecTcl that will attach to the online system of thechad.nscl.msu.edu and strt analyzing data.
Example 3-16. Minimal gui for attaching online
proc online host {
set url tcp://$host:2602/
catch stop
attach -pipe /usr/opt/daq/current/bin/spectcldaq $url
start
}
button .onl -text {Attach online} -command [list online thechad]
pack .onl



host
parameter.

