There is nothing to stop you from creating device support that
does not do anything in its addReadoutList
.
You could do this to implement static controls devices. That is
non data taking devices whose configuration is set up at the start of
a run and cannot be dynamically modified.
Slow control drivers can be dynamically loaded into the TclServer that runs the slow controls software. This section provides an overview to the process of writing these drivers and loading them into your VMUSBReadout program.
Slow control drivers consist of the following pair of classes:
Contains the actual code to translate Set, Get, Update
and Mon operations into actions on the hardware or internal
data or both.
Drivers are classes that are derived from
CControlHardware
They also
contain a configurable object called a CControlModule
which provides support for an option configuration database much like
that of readout drivers.
This object is registered with a module factory and associated with a module type. It provides a method that creates a specific module driver type. The factory is used by the Module command to create specific drivers.
The driver creator must be a subclass of CModuleCreator
The initialization function is called when the driver is loaded either via the Tcl load command or via the package require command if you create a Tcl package index that supports loading your driver in that manner.
The initialization function must have C bindings and is expected to register the creator with the module factory
These three bits of code must be compiled and linked together into a shared object. The resulting shared object must have no dangling external references outside of the VMUSBReadout program or else it cannot be loaded by the Tcl server's interpreter.
What I mean by a dangling reference is best illustrated by an example.
Suppose you need the function crypt
in your driver
(man crypt to know what that does). That function
lives in the libcrypt library. For a library intended
to be linked into other code to make a complete program, it's not necessary
for your libarary to link explicitly to libcrypt so long
as the program itself is linke to libcrypt.
For a shared library used as a slow control driver, however no expclicitly
linking it to libcrypt would leave an undefined reference
that Tcl would not know how to satisfy, so the load of the
library would fail.
A Slow controls device driver is a class that is derived from the CControlHardware abstract base class. In this section I will briefly go over the mandatory and optional requirements of a driver class as well as providing an annotated sample (if silly) driver.
The CControlHardware class is an abstract base class. This means it has some virtual methods that are not implemented and must be implemented by derived classes. In addition the name of the object being created (Module command's name) is maintained by the base class.
These constraints mean that a control driver class must implement the following member functions.
The class must implement a constructor that takes as a parameter
(at least) a std::string
modulename which
it passes to the base class constructor.
onAttach
(CControlModule& configuration
)
onAttach
is called when the driver's
configuration object configuration
is being
attached to the object. The base class provides a protected data
item named m_pConfig
in which a pointer to the
configuration
can be stored for future use.
The onAttach
method is also where you must
define your configuration options and their constraints. Soon after this
method is invoked, the driver is likely to have to respond to
requests to configure values into these parameters.
As with readout drivers, the process of giving values to configuration
options transparent to your driver code. When you need to know the
value of an option, you just ask the configuration
and it's magically there.
Update
(CVMUSB& vme
For some devices it is necessary to maintain an internal state
that describes the device. This is done for devices that are
write only such as the V812. For those devicdes it is useful to be able
to refresh the device state from an internal copy of what the state should
be. Update
is provided for that purpose.
This method must be implemented though the implementation can be empty.
The vme
parameter is a reference to a VMUSB controller
object that can be used to perform VME operations or to execute a
CVMUSBReadoutList
of operations built up by this
method.
Set
(CVMUSB& vme
,
std::string parameter
,
std::string value
)
Fundamentally, slow control device drivers manage some settable
parameters in a device. The driver associates a name with each parameter.
The Set
operation provides a new value for a parameter.
The driver is expected to propagate that value out to the device.
The parameter
argument is the name of the parameter to
modify. Names are assigned by the driver and known to the client via documentation.
value
is the new value for the parameter. The driver is
responsible for all validity checking and error reporting in the event the parameter
value is not valid.
vme
is a reference to a VMUSB controller object that is used
by the driver to perform VME operations or list of VME operations encapsulated in
a CVMUSBReaoutList
Get
(CVMUSB& vme
,
std::string parameter
)
This is expected to return the value of a parameter
in
the hardware. The vme
can be used to perform the
needed VME operations or lists of VME operations encapsulated in a
CVMUSBReadouList
clone
(const CControllerHardware& rhs
)
There are times when the framework needs to create a copy of a driver instance. When
this happens clone
is invoked. The
rhs
object is the object we are copying into ourselves. If the object
has no additional state it can just cop construct the
CControlMdoule
configuration into m_pConfig
.
In addition to the mandatory methods above, you can implement several optional methods if required.
Initialize
(CVMUSB& vme
)If you need to perform any one-time initialization of your hardware you can do that here. An example of where this is used is with the CAEN V812 driver. The V812 registers are write only. Therefore, the driver maintains a configuration file with settings and at initialization time, that configuration file is read and loaded into the device returning it to a known state.
The vme
parameter is a reference to
a VMUSB controller object. It can be used to perform
operations on the VME crate or to execute
CVMUSBReadoutList
objects the
method builds.
addMonitorList
(CVMUSBReadoutList& vmeList
)For some devices, the ability set and get parameters is not the whole story. This is especially the case because in order to set or get a parameter, active data taking must be stopped and restarted which is time-consuming. Consider, for example, a VME Bias supply controller. A control panel would want to monitor the supply for trips periodically and display those trips regardless of whether or not data taking is active. This is accomplisshed with a monitor list.
The monitor list is one of the 8 stacks supported by the VM-USB. It contains a set of operations that are periodically performed by manually triggering the list in the action register. The data routing software forwards data from this list to the Tcl server for processing. Since this list can be triggered just like any other acquisition mode list, no time consuming stop/start is needed when data taking is active. If data taking is inactive the list is periodically issued as an immediate mode list.
addMonitorList
provides an opportunity for a driver to specify a set
of VME operations to perform to gather the needed data to monitor the device.
This is optional and the default method adds nothing to the list.
processMonitorList
(void* pData
,
size_t remaining
)
This is called when the data from a monitorlist becomes available. The drivers
processMonitorList
are called in the same order in which their
addMonitorList
methods were called.
pData
is a pointer to the as yet unprocesssed part of the
data read by the monitor list while remaining
is the number
of bytes of unprocessed data in that list.
The return value from this method should be a pointer to the first unprocessed
byte of monitor list data after the data consumed by this driver.
Normally the driver will pull data out of the monitor list and store it internall for
the next call to getMonitoredData
below. It's up to the driver
how the data are stored.
The base class implementation just returns pData
which is
exactly right for a driver that does not use monitor lists.
getMonitoredData
()This is called when the Mon command is issued for this device. The driver should package up the most recently received data from the monitor list and return it to the caller as a string in the documented form. Note: Since many clients are in Tcl it is convenient to return these data as a properly formatted Tcl list.
Let's look at the code for a rather silly user written driver a piece at a time.
The driver is not going to interact with any hardware. What it will do is provide
the configuration options -anint
which will hold an integer,
-astring
which holds an arbitraty string, and
-alist
which holds a list of integers.
The Set and Get operations will modify and retrieve these options.
The driver won't support monitor lists.
This section provides an annotated view of the driver source code. For clarity, the header and driver are combined in a single file. Comments that might ordinarily be in the source code are in the annotations instead.
Example 4-14. Control driver headers
#include <CControlHardware.h> #include <CControlModule.h> #include <CVMUSB.h> #include <CVMUSBReadoutList.h>
This section of the driver sourcde includes the headers required by the driver itself. The headers are:
CControlHardware.h the base class for the driver class
CControlModule.h the specialized version of the
configurable object that is used by control drivers is a CControlModule
this includes the command dispatching that makes configuration transparent
to your driver code, as well as the harness for dispatching the driver
calls themselves and marshalling them in a way that makes them available
to the clients.
CVMSUB.h Defines the class that provides access to the VMUSB.
CVMUSBReadoutList.h provides a class for constructing
VME lists of operations that can b executed immediately for better
performance than single operations with the CVMUSB
Example 4-15. Control driver class definition
class CUserDriver : public CControlHardware { public: CUserDriver(); virtual void onAttach(CControlModule& configuration); virtual std::string Update(CVMUSB& vme); virtual std::string Set(CVMUSB& vme, std::string parameter, std::string value); virtual std::string Get(CVMUSB& vme, std::string parameter); virtual void clone(const CControlHardware& rhs); };
The key points are that the user driver inherits from the
CControlHardware
base class. Since
CControlHardware
is an abstract base class,
it is necessary to define all abstract methods. Furthermore, since
CControlHardware
has no default constructor,
our driver must define a constructor that is capable of passing the
driver instance name (module name) to the base class.
The only mandatory function of the constructor is to initialize the base class.
Example 4-17. Control driver onAttach
method
void CUserDriver::onAttach(CControlModule& configuration) { m_pConfig = &configuration; m_pConfig->addIntegerParameter("-anint", 0); m_pConfig->addParameter("-astring", NULL, NULL, ""); m_pConfig->addIntListParameter("-alist", 16); }
onAttache
is called when a driver instance
is being created and attached to its configuration object. The
configuration
is passed as a parameter to the
method. The numbers inthe example text are referenced below in the
annotations:
CControlHardware
)
provides a protected data member m_pConfig
that is intended to hold a pointer to the driver instance's
configuration object (CControlModule
).
It is usually important to be able to access your configuration
later in the life cycle of the driver.
onAttache
method. For this toy
driver we just define the options
-anint
which is constrained to be an integer,
-astring
which is unconstrained and
-alist
which is constrained to be a list of
16 integer parameters.
Normal drivers will defined at least a
-base
option which is intended to hold
the base address of the device being controlled.
Some devices have write only registers. Since it is not possible to determine the device state by reading the device registers, it is useful to have an operation that will set the device to a known state. Typcially this state is maintained in a set of shadow registers that are maintained in the driver instance,and possibly read from a file that is also maintained by that instance or by a control panel for the device.
The Update
method is intended to provide a mechanism
for device control panel clients to request the driver set the hardware to a known
state. The method should return the string OK on success or
a streing of the form ERROR - some descriptive error message
normally control panels, on seeing the first return word is ERROR
will parse the error message out and display it for the user.
Example 4-19. Control driver Set
method.
std::string CUserDriver::Set(CVMUSB& vme, std::string parameter, std::string value) { try { m_pConfig->configure(parameter, value); return "OK"; } catch (std::string msg) { // configure reports errors via std::string exceptions. std::string status = "ERROR - "; status += msg; return status; } }
The Set
is intended to provide a new
value for a device parameter. When you write a device driver
you must give names to each device parameter you want to allow
clients to control. These names are used by the client to identify
device parameters both for the Set
and
the Get
we will discuss next.
In our toy driver, we have no hardware to modify so we are accepting the
names of our configuration options as the parameter names. Normal drivers
will need to pull their base address from the configuration
and use them, along with the value of the parameter
parameter to know what to do to change the requested
parameter to value
.
Set
are:
vme
A CVMUSB
object reference
that can be used to either perform direct VME operations
or to execute CVMUSBReadoutList
objects that have been created and stocked this
method.
parameter
The name of the parameter to modify.
value
The stringified version of the new value to
set for the parameter. This parameter is always
a std::string
and therefore
may require conversion by the driver.
The configure
throws
std::string
exceptions
on errors. Hence the configure
operation is done in a try block.
Set
method is either
OK (No error) or
ERROR - followeed by an error
message string.
Example 4-20. Control driver Get
method
std::string CUserDriver::Get(CVMUSB& vme, std::string parameter) { try { return m_pConfig->cget(parameter); } catch (std::string msg) { std::string retval = "ERROR - "; retval += msg; return msg; } }
The Get
method is used by control panel
clients to fetch values of device parameters from the driver.
Get
are:
vme
Reference to a CVMUSB
controller
object that can be used to perform VME operations directly
or to execute CVMUSBReadoutList
objects that are created and filled by this method.
parameter
The name of the parameter to modify.
-base
value to locate our device in VME space.
We would then use the value of parameter
to determine which value to fetch. That value would be
represented as a string and then returned to the caller.
cget
can throw an
exception. We turn this into an error return indicated by
returning a string of the form
ERROR - followed by a descriptive error
string.
Example 4-21. Control driver clone
method
void CUserDriver::clone(const CControlHardware& rhs) { CControlHardware* pRhs = const_cast<CControlHardware*>(&rhs); m_pConfig = new CControlModule(*(pRhs->getConfiguration())); }
All control drivers must supply a clone
method. This method must clone a rhs
object
into this
. The minimal work that must be done
is to clone the rhs
configuration into our
m_pConfig
.
Any internal data the driver has must also be copied. Wether this can be a shallow or deep copy is up to the needs of the driver.
The creator is used by the extensible module factory. It captures knowledge of how to create a driver instance. The factory matches driver type names with creators and uses the creator to produce the correct constructor.
Example 4-22. Control driver creator
#include <CModuleCreator.h> #include <CControlHardware.h> class CUserDriverCreator : public CModuleCreator { public: virtual std::unique_ptr<CControlHardware> operator()(); }; std::unique_ptr<CControlHardware> CUserDriverCreator::operator()() { return std::unique_ptr<CControlHardware>(emasc new CUserDriver() ); }
CModuleCreator
as
a base class. This line includes the header for that class.
CModuleCreator
operator()
). This method
must t return a dynamically
allocated CControlHardware*
whose underlying type is the same as the type we are
supposed to be creating.
The use of the std::unique_ptr
smarpt pointer class provides that the
created object's lifetime can be properly managed.
The initialization function is called automatically by the Tcl load or package require command used to load the driver into the interpreter. The initialization function must have a specific name that matches the name of the library/package it is built into. The name must be of the form described in http://www.tcl.tk/man/tcl8.5/TclCmd/load.htm Unless otherwise specified in the load command, the package name is the part of the name of the library that follows the lib part but before the file type. E.g. for a shared library named libMyControlDriver.so, the initialization function should be Mycontroldriver
Here is the initialization function for our sample driver:
Example 4-23. Control driver initialization
#include <CModuleFactory.h> #include <tcl.h> extern "C" { int Userdriver_Init(Tcl_Interp* pInterp) { CModuleFactory* pFact = CModuleFactory::instance(); pFact->addCreator( "mydriver", std::unique_ptr<CModuleCreator>(new CUserDriverCreator)); return TCL_OK; } }
Defines the CModuleFactory
singleton which is the class in which our creator must
be registered.
Contains interface definitions for the API to the Tcl interpreter.
CModuleFactory::instance()
returns a pointer to the
singleton instance
of the factory.
Compiled drivers must be built as shared libraries. You can then either use the Tcl load command to directly load them or build a pkg_Index.tcl in a Tcl library directory and use package require instead.
Regardless of the choices you make, if you have bundled your driver into a single file you can build it using:
Note that you don't need to specify libraries that are included in the readout program, however you do need to resolve any other undefined symbols that are in libraries not part of the readout program.
To use the driver you need to load it into the interpreter and the use the Module to make an instance and to maniuplate that instance. For example:
Using the SWIG wrappers for the CVMUSB
and
CVMUSBReadoutList
classes and the
tcl wrapper module type you can write
slow control modules using pure Tcl. A Tcl driver is a command ensemble
that defines the following subcommands:
Called by the wrapper's Initialize
to perform initialization.
Called by the wrapper's Update
method.
Called by the wrapper's Set
method.
Called by the wrapper's Get
method.
Called by the wrappers addMonitorList
method. This method must be supplied though it need not do
anything.
Called by the wrappers's processMonitorList
.
Again while this method must be supplied, it need not do anything
(except return 0 indicating it has not processed antyhing from the monitor
list).
Called from the wrapper's getMonitoredData
.
Must be defined but need not do anything useful if the driver
does not make use of monitor lists.
The remainder of this section will pick apart a Tcl driver that performs the same basic function as the C++ driver we wrote in the previous section. We will also show how to incorporate the driver into a controlconfig.tcl file.
For this example we will use a driver written using snit. incrTcl or Xotcl or even namespace ensembles can be used to implement drivers, or even just a Tcl procedure of only one instance is going to be supported.
Let's start by looking at the overall framework of the driver:
Example 4-24. Control drivers - structure of a Tcl driver
package provide Mydriver 1.0 package require cvmusb package require cvmusbreadoutlist snit::type Mydriver { option -anint -configuremethod _validInt option -astring option -alist -configuremethod _validIntList constructor args { ... $self configurelist $args } method Initialize vme {...} method Update vme {...} method Set {vme parameter value} {...} method Get {vme parameter} {...} method addMonitorList vmeList {...} method processMonitorList data {...} method getMonitoredData {...} # Private method: method _validInt {optname value} {...} method _validIntList {optname value} {...} }
CVMUSB
and CVMUSBReadoutList
classes.
-anint
and the -alist
. The
-configuremethod
option in the
option provides the name of a method that
will be called when the option is configured. This provides for
validation.
configurelist
processes a list of
option/value pairs. Precisely what the args
parameter contains.
-configuremethod
. Confifgure methods take a pair of parameters.
optname
is the name of the parameter being configured
while value
is the proposed new value for that parameter.
This parameterization allows configuremethods to be shared between several
options (for example if we had more than one option we wanted to constrain
to be an integer.
The full implementation of the Initialize
is shown
below:
The Initialize
does nothing for our driver.
For a real driver the options might be queried to find the base address
of the hardware as well as other static configuration options. The
vme
argument, which is a Swig wrapped
CVUSB
object would then be used to perform the
VME transactions needed to initialize the device.
In the event of an error while executing this method, the wrapper
will pull the result of the command as a string, and throw it as a
std::string
exception. This is the accepted way
for drivers to indicate errors in their Initialize
methods
The Update
method servesthe same function
in a Tcl driver as in a C++ driver. In our case the implementationis
The vme
is a SWIG encapsulated
CVMUSB
object that, in a real driver,
would be used to perform VME operations or execute lists created
via CVMUSBReadoutList
objects created and
filled in by this method.
The return string of OK indicates a normal completion. Tcl methods have two mechanisms to return errors. Returning a string that begins with ERROR - and concludes with an error message, or simply executing the error with an error message argument. The wrapper will map error returns into an ERROR - string return with the value of the string passed t the error command appended to the end of the error return string.
As with the C++ driver, the Set
method is
used to process client requests to change a device parameter.
The Tcl version of this method is:
Example 4-27. Tcl control driver Set
method
method Set {vme parameter value} { $self configure $parameter $value return "OK" }
The parameters passed into this are the same as those passed into
a C++ driver's set, however the vme
parameter
is a swig wrapped CVMUSB
object.
By using the configure
method rather than
just directly writing to the options
array
we force the call of any -configuremethod
code
that may do validations and throw errors.
The return of OK indicates successful completion,
an error will be converted by the wrapper into a return string of the form
ERROR - intepreter error result string.
This allows errors from the configurmethod
,
including specifying an invalid option name as well as failures
in validation by the -configuremethod
code to be
naturally mapped back to proper error returns.
In a real driver, the vme
parameter will
be used to perform VME operations. In addition you can choose
to explicitly return an error string, or use the
error command to report failures.
As with the C++ driver this is called to fetch the value of a driver parameter. Our toy driver fetches configuration parameters rather than hardware parameters.
Example 4-28. Tcl control driver Get
method
method Get {vme parameter} { if {[array names options $parameter] eq ""} { error "Invalid parameter name: $parameter" } return $options($parameter) }
Our driver ensures the value of parameter
really
is an option name and then returns the option value from the
options
array.
By doing the check for array names options $parameter
we can produce a more meaningful error message than would be
produced if we just let return $options($parameter)
error.
Instead of the error command we could have just as easily done a return "ERROR - Invalid parameter name: $parameter"
A real driver would use the vme
parameter,
which is a Swig wrapped CVMUSB
object
to fetch the value of the parameter from the hardware.
Since we are not using monitor lists, this method is trivial:
The vmeList
is a CVMEReadoutList
that is wrapped by Swig. A driver that uses monitor lists would add operations
to the list as needed to retrieve the data being monitored from the device.
processMonitorList
is called with the data read
by the monitor list marshalled into a Tcl list of bytes (string representation).
The method is expected to process what it needs from the front of the list
and return a count of the number of bytes processed. Since we are not using
monitor lists, our implementation is:
If using a monitor list, this method typically returns the data
last processed by processMonitorList
with
OK - prepended to indicate a successful completion.
As usual an error or script errors will result in
a return of ERROR - with the result string appended.
Since we are not using a monitor list, it's appropriate for us to return an error condition:
The only thing left to do is write the option validation methods
_validInd
and _validIntList
.
Note | |
---|---|
Snit does not have method visibility control, that is all methods are public. By convention, methods that are not intended to be called by a client are given names that start with a _. Therefore these methods should be considered private. |
Example 4-32. Tcl control driver validation
method _validInt {optname value} { if {[string is integer -strict $value]} { set options($optname) $value } else { error "$optname must be configured with an integer was given '$value'" } } method _validIntList {optname value} { if {![info complete $value]} { error "$optname must be given an integer list and '$value' isn't one" } set elements [llength $value] if {$elements != 16} { error "$optname must be given an integer list 16 '$value" as $elements" } foreach item $value { if {![string is integer -strict $item]} { error "$optname list elements must be integer but '$item' in '$value' is not" } } set options($optname) $value }
options
array. This array is used by e.g. cget to
return configuration options, unless overridden with a -cgetmethod
.
value
is a valid list. This works
because complete commands must be valid Tcl lists as well.
options
array.
To use a Tcl control driver you must:
Incorporate the driver into your controlconfig.tcl script either using the source or package require command.
Create an instance of your driver if needed. In the case of our toy driver, this means a command like: Mydriver aninstance to create a command ensemble named aninstance
If necessary configure our driver instance.
Wrap your driver with the tcl module type:
The key point is that the -ensemble
for a
tcl module provides the base name of the
command ensemble that Tcl driver wraps.