5.8. Writing Tcl slow controls device drivers

Using the SWIG generated Tcl wrappers and the tcl control module wrapper you can also write your slow controls drivers in pure Tcl. This section describes:

Before continuing with a detailed discussion of pure Tcl drivers, it is important to understand Tcl command ensembles. A command ensemble is a command that has subcommands. Traditionally, the subcommand is the second keyword on the command line. The Tcl string command is an example ofa command ensemble that is part of the core lanaguage.

All Tcl slow controls drivers must be Tcl command ensembles that implement some pre-defined subcommands. When writing Tcl drivers, it is useful to have an ensemble generator. An ensemble generator is a mechanism that generates command ensembles that follow the form of some template. For example, all Tk user interface creation commands (e.g. canvas) are ensemble generators. The widget they create becomes a Tcl command that at least has the config and cget subcommands.

Tcl object oriented extensions such as IncrTcl, snit and as we transition to Tcl6.0, the Tcl6.0 tcloo object system. Can serve as generators.

For example, in instances of an incrTcl class, public methods of that class are ensemble subcommands for the name of the object. Similarly, for objects generated by snit::type class commands, the methods of that type are ensemble methods of the generated command.

The remainder of this section describes a sample Tcl driver written in Tcl that is similar in functionality to the sample driver written in C++ described in the previous section. We'll use this sample driver as a mechanism to describe and discuss the interfaces between the sample driver and the framework.

Note that sample drivers perform CCUSB operations via the SWIG wrappers for the CCCUSB class. They can generate and perform lists as well by using the SWIG wrapping of the CCUSBReadoutList class. This section assumes you are familiar with those wrappers.

We are going to approach the description of the driver by first showing a skeletal snit::type and then filling in the body of each method. Finally we'll show some script fragments that show how to create instances of the type and how to integrate those instances into the slow controls framework.

The example below is a snit driver with all of the executable code stripped out. This shows the general structure of the driverand its methods.

Example 5-22. CCUSB Tcl slow controls driver skeleton


package require cccusb             (1)
snit::type SampleDriver {          (2)
    option -anint   -default 0    -configuremethod _CheckInt
    option -anintlist -default 0  -configuremethod _CheckIntList (3)
    option -abool   -default true -configuremethod _CheckBool
    
    
    construtor args {}             (4)
    method Initialize crate {}     (5)
    method Update crate {}         (6)
    method Set {crate what value} {} (7)
    method Get {crate what}  {}      (8)
    
    method _CheckInt     {name value} {}
    method _CheckIntList {name value} {}
    method _CheckBool    {name value} {}
}
            

It is no accident that there is a close parallel between the names of the methods for the Tcl driver and those of a C++ class that implements a slow control driver.

(1)
The cccusb package is the SWIG wrapping of the CCCUSB driver class. In order to successfully package require this, the lib subdirectory of the NSCLDAQ installation must be part of the package search path.

Some drivers may want to block up several CCUSB operations into a list. To do this will also require a package require cccusbreadoutlist command to incorporate the SWIG wrapper for the CCCUSBReadoutList

(2)
This line starts the definition of a snit::type. The name of the type is SampleDriver. The definition continues through the final closing } in the example.

snit::type creates an ensemble generating command (in this case SampleDriver) with subcommands defined by the method definitions within the body of the type. The generated command ensembles also automatically have configuration management commands; configure which configures an object's option database, and cget which obtains information about that database.

(3)
snit::type objects can have associated configuration parameters. These are maniuplated externally via the configure and cget subcommands much like for a Tk widget. Internally, methods can manipulate these options via the options array (indexed by option name).

This section of code defines three options much like the C++ sample driver does. The -default option provides a default value for the option, effectively initializing that element of the options array. The -configuremethod option provides a method that is automatically called when an option is configured via the configure sub-command. We will use these methods to validate a proposed new value for the option.

Note that snit::type does not have any concept of a private method. By NSCLDAQ convention, however, methods with names that begin with _ should be considered internal and not called by external clients.

(4)
If a snit::type defines a constructor this method is called when a new object is being created. One reason most snit::types need a constructor is to configure the options that may be specified on the end of the construction command (e.g. SampleDriver adriver -anint 1234).
(5)
This is called after the configuration script has been full processed. It is intended to allow the driver to perform one-time device initialization. Note that the crate parameter is a SWIG wrapped CCCUSB controller that can be used by the driver to perform CAMAC operations.
(6)
This is called in response to an Update request from a client. One common use of this is to ensure that a device with write-only registers has a state that matches an internal shadow state maintained by the driver.
(7)
Called in response to a Set request by a client. crate is a SWIG wrapped CCCUSB object and allows the ensemble to perform CAMAC operations. The driver is suppoed define a set of named parameters the client can modify. what identifies which parameter the client wants to modify and value provides the new proposed value for the parameter.

On success the method should return OK. The simplest way to report an error is with the Tcl error command. The error text becomes the string after the ERROR text of an error return.

(8)
Called in response to a Get request by a client. crate is a SWIG encapsulated CCCUSB object that can be used to perform CAMAC operations.

Drivers are expected to define a set of named parameters that can be read. These need not be the same as the set that can be written. what should be the name of one of these defined parameters. On success, the driver should return the value of that parameter. On failure the driver should either execute an error comman or return a string of the form ERROR -Error message

If the driver does use error to indicate an error condition, the string passed to that command will be concatenated to ERROR - and returned to the client.

Let's now look at the implementations of these methods:

construction. Construction should be used to initialize the driver's internal state. Furthermore, the configurelist predefined method can be used to process the configuration arguments passed to the constructor.

Example 5-23. CCUSB Tcl slow controls driver construction


construtor args {
    $self configurelist $args
}
            

Initialization. Since we have no hardware we have no one-type initialization of that hardware. Note that if we did have to initialize hardware the crate parameter can be used to perform the CAMAC operations needed to do that initialization.

Example 5-24. CCUSB Tcl slow controls initialization


method Initialize crate {
}
            

Update. We don't have anything to do on Update. In this case it's best to make Update signal and error because clients should be written not to use it for this device.

Example 5-25. CCUSB Tcl Slow Controls Update


method Update crate {
    error "This device does not support 'Update'"
}
            

In this case we use the error command to signal the error. The Tcl module will catch this error and turn it into the string ERROR - This device does not support 'Update'. We could have also directly returned that string.

If the Update method was legal for this driver, and it executed successfully, we should return the string that we want the client to get. This is normally OK indicating success.

Set. In our case, Set will treat what like a configuration parameter name and value like the value to use to set the parameter. We are just going to do a $self configure and let this produce an error if there are problems (illegal configuration options, illegal values). If we succeed, we'll return OK to indicate that.

Normally we'd need to use the crate parameter to perform actual CAMAC operations.

Example 5-26. CCUSB Tcl Slow controls driver Set


method Set {crate what value} {
    $self configure $what $value
    return "OK"
}
            

Get. The Get will treat the what parameter as the name of a configuration option. If that option exists it will be returned, otherwise an error message will be generated.

Example 5-27. CCUSB Tcl Slow Control driver Get


method Get {crate what} {
    if {[array names options $what] ne ""} {
        return $options($what)
    } else {
        error "No such parameter '$what'"
    }
}
            

Note that in snit::types, the options array contains the configuration options. The Tcl command array names returns the names of array indices that match a glob pattern. We can get fooled into not emitting the correct error message if, for example, we are asked to get *anint. In that case there will be a match with -anint, and the error produced will be: ERROR - can't read "options(*anint)": no such element in array

Option value checking. In our C++ driver we used configuration constraints to enforce correctness in the form and values of the configuration options. Tcl rivers are not able to take advantage of the capabilities of a CConfigurableObject. Therefore, the onus of ensuring that options are correct is placed on the driver.

snit::type provides the ability to intercept the configure (and for that matter the cget) operations on an option by option basis. This is done using the -configuremethod when defining an option.

Configure methods are responsible for updating (or not) the appropriate element of the options array and can therefore reject proposed changes. Configure methods receive as parameters the name of the option being configured and the new proposed value. This allows options to share configuremethods.

We are going to further share code by having a helper function that will throw an error if a string is not an integer. This helper will be used by both _CheckInt and _CheckIntList. For now we will assume that _CheckIntList needs to see a 16 element list. If this method needs to validate several lists that are of different lengths, we would define a variable for the object that would be an array indexed by option name whose values would be the lengths of the list.

Example 5-28. CCUSB Slow controls Tcl driver option validation


method _CheckInt     {name value} {
    $self _IsInt $value
    set options($name) $value
}

method _CheckIntList {name value} {
    if {[string is list $value]} {
        if {[llength $value] == 16} {
            foreach element $value {
                $self _IsInt $element
            }
            set options($name) $value
        } else {
            error "$name option expects a list exactly 16 elements long"
        }
    } else {
        error "$name must be a valid Tcl list"
    }
}
method _CheckBool    {name value} {
    if {[string is boolean -strict $value]} {
        set options($name) $value
    } else {
        error "$name must be a boolean parameter, was: $value"
    }
}
method _IsInt        {value} {
    if {![string is integer -strict $value]} {
        error "Configuration value $value must be an integer and is not"
    }
}
            

Once a driver class/type has been created it can be used in the slow controls configuration script by

  1. Creating an instance of the class/type.

  2. Configuring any options for the instance

  3. Wrapping the object in an tcl driver type by specifying it's name as the -ensemble option of the object.

Example 5-29. Using the Tcl sample driver in a configuration script.


set mydriver [SampleDriver %AUTO% -anint 1234 -abool false]
Module create mydriver tcl
Module config mydriver -ensemble $mydriver