638 lines
28 KiB
TeX
638 lines
28 KiB
TeX
\chapter{Writing new SICS Commands}
|
|
If you wish to write a new command or add a completely new piece of hardware to
|
|
SICS, this is the chapter to study. There are two ways to implement
|
|
new commands into SICS: through the internal Tcl scripting language and
|
|
in ANSI--C. This section describes command writing in ANSI--C. You
|
|
should consider using Tcl when:
|
|
\begin{itemize}
|
|
\item The new command is very instrument specific. Rather extend a
|
|
SICS command in order to support your script.
|
|
\item The new command is a local syntax fudge.
|
|
\item The hardware device is an auxiliary, such a He level meter
|
|
etc.
|
|
\end{itemize}
|
|
On the other hand there are indications when to write in ANSI--C:
|
|
\begin{itemize}
|
|
\item Complex calculations need to be carried out.
|
|
\item Large amounts of data need to be handled.
|
|
\item The code requires extensive bit twiddling.
|
|
\item A new general SICS facility is provided.
|
|
\end{itemize}
|
|
|
|
|
|
\section{Mapping Object Oriented Concepts into ANSI--C}
|
|
SICS is in principle an object oriented system. However, it is implemented
|
|
in ANSI--C. Therefore object oriented concepts must be mapped into C. The
|
|
three object oriented concepts which need to be mapped are:
|
|
\begin{itemize}
|
|
\item Data Encapsulation.
|
|
\item Polymorphism.
|
|
\item Inheritance.
|
|
\end{itemize}
|
|
|
|
Of these, data encapsulation is by far the most important concept. Objects
|
|
in computer science can be understood as little boxes which contain some
|
|
data describing their state and which understand messages sent by other
|
|
objects. If such a message comes in, the object performs some action,
|
|
perhaps changes its internal state or sends new messages to other objects.
|
|
It is understood that changes to the internal data of the object can be
|
|
achieved only through messages to the object and not by directly manipulating
|
|
variables from the outside. In ANSI--C an object maps to a structure holding
|
|
the objects data and the messages map to functions which act upon the data
|
|
structure. In order to do this, the functions must take a pointer to the
|
|
objects data structure as first parameter. In order to prevent messing with
|
|
an objects data structure, only a pointer to a structure is declared in the
|
|
header file. The actual definition of the data structure happens only in
|
|
the implementation file. All functions belonging to an object are defined in that
|
|
implementation file and have full access to the data structure.
|
|
Users of the object see only the header file and thus only a pointer to the
|
|
objects data structure which prevents
|
|
them from messing with the objects data directly.
|
|
In order to illustrate the concepts lets look at
|
|
a primitive integer object defined in such a way.
|
|
\begin{verbatim}
|
|
/*-----------------------------------------------------------------------
|
|
ExampleInt.h
|
|
------------------------------------------------------------------------*/
|
|
typedef struct __ExampleInt *pExampleInt;
|
|
|
|
int GetInt(pExampleInt self);
|
|
void SetInt(pExampleInt self, int iNew);
|
|
/*------------------- EOF ExampleInt.h---------------------------------*/
|
|
|
|
/*----------------------------------------------------------------------
|
|
ExampleInt.c, Implementation file
|
|
-----------------------------------------------------------------------*/
|
|
typedef struct __ExampleInt {
|
|
int iExample;
|
|
} ExampleInt;
|
|
/*--------------------------------------------------------------------*/
|
|
int GetInt(pExampleInt self)
|
|
{
|
|
return self->iExample;
|
|
}
|
|
/*---------------------------------------------------------------------*/
|
|
void SetInt(pExampleInt self, int iNew)
|
|
{
|
|
self->iExample = iNew;
|
|
}
|
|
\end{verbatim}
|
|
Using this scheme all code changing the internal state of an object lives
|
|
in one file. Changes to the objects data structure affect only the
|
|
implementation file and no other files.
|
|
This scheme is used for almost all SICS objects. A few system objects and
|
|
older SICS objects define their data structures in header files. This is
|
|
either a relic or had to be done for performance reasons.
|
|
|
|
The next concept is polymorphism. This describes the situation when a group
|
|
of objects respond to the same message but doing different things. For
|
|
instance a whole set of objects would implement a write functionality
|
|
which writes the objects state to a file. Higher level would then not need
|
|
to know of which type the actual object is, it just can send the write message
|
|
and the rest is taken care of by the object. This concept is used for all
|
|
hardware drivers in SICS. Mapping this to C requires to expose the objects
|
|
data structure and let the data structure include a pointer to that polymorphic
|
|
function. As an example, the ExampleInteger with a write function:
|
|
\begin{verbatim}
|
|
/*-----------------------------------------------------------------------
|
|
ExampleInt.h
|
|
------------------------------------------------------------------------*/
|
|
typedef struct __ExampleInt{
|
|
int iExample;
|
|
void (*write)(struct __ExampleInt *self, FILE *fd);
|
|
} *pExampleInt, ExampleInt;
|
|
|
|
pExampleInt MakeInt(int iNew);
|
|
|
|
int GetInt(pExampleInt self);
|
|
void SetInt(pExampleInt self, int iNew);
|
|
/*------------------- EOF ExampleInt.h---------------------------------*/
|
|
|
|
/*----------------------------------------------------------------------
|
|
ExampleInt.c, Implementation file
|
|
-----------------------------------------------------------------------*/
|
|
static void ExampleWrite(struct _-ExampleInt *self, FILE *fd)
|
|
{
|
|
fprintf(fd,"INT = %d",self->iExample);
|
|
}
|
|
/*---------------------------------------------------------------------*/
|
|
pExampleInt MakeInt(int iNew)
|
|
{
|
|
pExampleInt pNew = NULL;
|
|
|
|
pNew = (pExampleInt)malloc(sizeof(ExampleInt));
|
|
pNew->iExample = iNew;
|
|
pNew->write = ExampleWrite;
|
|
return pNew;
|
|
}
|
|
.
|
|
.
|
|
.
|
|
\end{verbatim}
|
|
This can then be called:
|
|
\begin{verbatim}
|
|
void SomeFunc()
|
|
{
|
|
pExampleInt pTest;
|
|
|
|
pTest = MakeInt(25);
|
|
.
|
|
.
|
|
.
|
|
pTest->write(pTest,fd);
|
|
}
|
|
\end{verbatim}
|
|
This example also illustrates the concept of a special function which creates
|
|
a new object of the appropriate type and initializes its data structure
|
|
properly.
|
|
|
|
The last concept to discuss is inheritance. Inheritance can be used when
|
|
an object is a derivative of another object. For instance a truck is a
|
|
derivative of a motor car. Much of the behavior of a motor car will be the
|
|
same as for a truck. In order to prevent rewriting of code, the truck
|
|
should use the same data structures and code as the motor car. And add
|
|
or modify only what is special. Inheritance is not much used in SICS. It can
|
|
be implemented by overlaying data structures. This means the derived
|
|
classes data structure has the same fields in the same order as the parent
|
|
class and adds its specials at the end. For example:
|
|
\begin{verbatim}
|
|
typedef struct __MotorCar {
|
|
int iWheels;
|
|
float fSpeed;
|
|
} *pMotorCar, MotorCar;
|
|
|
|
|
|
typedef struct __Truck {
|
|
int iWheels;
|
|
float fSpeed; /* same as MotorCar */
|
|
double dPayLoad; /* special for Truck */
|
|
} *pTruck, Truck;
|
|
|
|
\end{verbatim}
|
|
Thus functions defined for motor car can operate on trucks as well.
|
|
For more details study the relationship between the ITC4 controller and
|
|
general environment controllers. This is the only place where SICS
|
|
currently uses inheritance.
|
|
|
|
|
|
\section{Command Writing Basics}
|
|
\subsubsection{The Object Wrapper Function}
|
|
The first thing needed in order to implement a new command in SICS is
|
|
the object wrapper function. This function has the following
|
|
signature:
|
|
\begin{verbatim}
|
|
int ObjectWrapper(SConnection *pCon, SicsInterp *pSics, void *pData,
|
|
int argc, char *argv[]);
|
|
\end{verbatim}
|
|
The parameters are:
|
|
\begin{description}
|
|
\item[pCon] A pointer to the connection object representing the client
|
|
invoking this command.
|
|
\item[pSics] A pointer to the SICS interpreter.
|
|
\item[pData] A pointer to a user data structure for this object. Can
|
|
be NULL if no such thing exists.
|
|
\item[argc] Number of arguments in argv to this function.
|
|
\item[argv] Arguments to this function. The argc, argv scheme is
|
|
the same as for a C--language main function. The first argument is
|
|
always the name of the object.
|
|
\end{description}
|
|
This object wrapper function must evaluate its arguments, do what it
|
|
is meant to do and write any results to the client connection. In the
|
|
case of an error this function must return 0, in case of success 1.
|
|
|
|
|
|
\subsubsection{The Object Data Structure}
|
|
Only the most primitive objects or commands get away without an own
|
|
data structure. Usually a data structure will be needed by SICS
|
|
objects in order to keep configuration parameters etc. A pointer to
|
|
such a datastructure is passed into the object wrapper function as the
|
|
pointer pData. This object data structure has to fulfill some
|
|
conditions in order to live happily within SICS. And actually, not
|
|
only the datastructure is needed but also a function which is able to
|
|
release any memory allocated by the datastructure. SICS needs this
|
|
function in order to clean things up properly.
|
|
|
|
|
|
A valid SICS object structure has to look like this:
|
|
\begin{verbatim}
|
|
typedef struct __MyObject {
|
|
pObjectDescriptor pDes;
|
|
int iMyExtremlyImportantInt;
|
|
.
|
|
.
|
|
.
|
|
} MyObject;
|
|
\end{verbatim}
|
|
Please note that the first item in the data structure MUST be a
|
|
pointer to an SICS object descriptor. Add your own stuff below
|
|
that. If you do not adhere to this requirement, SICS will dump core on
|
|
you rather sooner than later.
|
|
|
|
|
|
SICS needs this object descriptor for its own internal purposes. The
|
|
object descriptor is again a data structure with this signature:
|
|
\begin{verbatim}
|
|
typedef struct {
|
|
char *name;
|
|
int (*SaveStatus)(void *self, char *name,FILE *fd);
|
|
void *(*GetInterface)(void *self, int iInterfaceID);
|
|
} ObjectDescriptor, *pObjectDescriptor;
|
|
|
|
\end{verbatim}
|
|
\begin{description}
|
|
\item[name] This is a type identifier for the object. SICS uses this
|
|
identifier for run time type identification (RTTI). For example this
|
|
field says Motor for motors, Counter for counters etc.
|
|
\item[SaveStatus] is the function called by the SICS status backup
|
|
mechanism. The default implementation of this function does
|
|
nothing. But if your new object has to store configuration commands
|
|
into the status file you should create an own function with the same
|
|
signature and assign this function to the object descriptors
|
|
SaveStatus. A suitable function will print the necessary commands to
|
|
achieve the same configuration as the current state into the file
|
|
represented by fd.
|
|
\item[GetInterface] SICS objects can have various capabilities: a
|
|
motor can be driven or scanned, you may count on a counter etc. Such
|
|
capabilities are expressed as interfaces(see \ref{interface}) in SICS.
|
|
There is an integer ID for each of those interfaces. GetInterface now
|
|
returns a suitable
|
|
interface in return for a given interface ID or NULL if the object
|
|
does not implement the interface. The default version of this function
|
|
retuns NULL always. If your object implements an interface, this
|
|
function has to be overloaded to return this interface on demand.
|
|
\end{description}
|
|
|
|
A default object descriptor can be created with:
|
|
\begin{verbatim}
|
|
pObjectDescriptor CreateDescriptor(type);
|
|
\end{verbatim}
|
|
with type being the object type identifier. A descriptor can be
|
|
deleted with:
|
|
\begin{verbatim}
|
|
DeleteDescriptor(pDes);
|
|
\end{verbatim}
|
|
|
|
|
|
As already mentioned, a function to release any memory allocated for
|
|
the object data structure is also needed. Its signature is simple:
|
|
\begin{verbatim}
|
|
void KillObject(void *pdata);
|
|
\end{verbatim}
|
|
with pdata being the object to delete.
|
|
|
|
|
|
\subsection{Installing the new Command into the SICS Interpreter}
|
|
A new command can be installed into SICS with the function:
|
|
\begin{verbatim}
|
|
int AddCommand(SicsInterp *pSics,
|
|
char *name,
|
|
ObjectFunc ofunc,
|
|
KillFunc killo,
|
|
void *objectData);
|
|
\end{verbatim}
|
|
with pSics being the interpreter into which to install the command,
|
|
name the name of the command, ofunc its wrapper function, killo the
|
|
object data structure deletion function and objectData being a pointer
|
|
to the object data structure. If no data structure is defined for the
|
|
command, killo and objectData must be NULL.
|
|
|
|
|
|
Now the question arise where AddCommand has to be placed. Various
|
|
cases have to be distinguished: The first case is that the new command
|
|
is relevant to SICS users in all corners of the world. Then the new
|
|
command should be added to the function InitIniCommand in file
|
|
ofac.c.
|
|
|
|
The second case is that the command is specific to a certain
|
|
instrument or a special hardware or implements a local fashion of
|
|
dealing with things. Then the new command should be installed from the
|
|
function AddSiteCommand in the site data structure (see \ref{site}).
|
|
|
|
Another scheme becomes valid when multiple instances of the object
|
|
need to be created. \label{factory} For instance the object represents
|
|
a motor of which you may have many. Then it is useful to create a factory
|
|
command. This is a special SICS command which creates the desired
|
|
objects and installs them in the interpreter. This factory command
|
|
then has to be installed according to the logic given above. Usually
|
|
such objects are installed in the SICS initialization file. After
|
|
processing this initialization file the factory command is not useful
|
|
any longer. Then such factory commands should be removed from the
|
|
interpreter with:
|
|
\begin{verbatim}
|
|
RemoveCommand(SicsInterp *pSics, char *name);
|
|
\end{verbatim}
|
|
This has to be placed into the sites member function RemoveSiteCommand
|
|
or into KillIniCommand in ofac.c, depending where you installed the
|
|
command in the first place.
|
|
|
|
Be careful with commands for deleting objects though. SICS objects may
|
|
be aliased, used by other objects or connected through the callback
|
|
interface (see \ref{inter}). SICS does not implement proper
|
|
bookkeeping on all these relationships and thus deleting a command from
|
|
SICS without taking this into account may cause SICS to dump core on
|
|
you.
|
|
|
|
|
|
\section{Interacting with the Client Connection}\label{gow}
|
|
A SICS command writer needs to interact with the client connection for
|
|
a variety of reasons:
|
|
\begin{itemize}
|
|
\item To write error messages and other output
|
|
\item To prompt for more data
|
|
\item To check if the user has appropriate privileges for the command
|
|
implemented.
|
|
\item To tell upper level code that something went very wrong.
|
|
\end{itemize}
|
|
As these tasks are so common you will find that the connection object
|
|
has to be passed frequently into lower level code as an argument.
|
|
|
|
\subsection{Writing and Reading to the Client}
|
|
All I/O to the client has to be processed through the I/O functions
|
|
for connections defined in conman.h and implemented in conman.c. The
|
|
most common of these are SCWrite and SCPrompt.
|
|
These function will now be inspected in more detail:
|
|
\begin{verbatim}
|
|
int SCWrite(SConnection *pCon, char *pText, int eCode);
|
|
int SCPrompt(SConnection *pCon, char *pPrompt, char *pBuffer, int iBufLen);
|
|
\end{verbatim}
|
|
SCWrite writes the data pText to the connection specified by pCon. The
|
|
parameter eCode denotes the output code of the data in pText. SICS clients
|
|
can choose to suppress some I/O from the SICS server. For instance a
|
|
GUI--client might chooses not to receive scan status reports. For this
|
|
purpose it was necessary to stick an output code onto each message to the
|
|
client. Possible output codes are: eError, eWarning, eValue, eStatus and
|
|
some internal codes. The names are self explaining. eValue denotes a value
|
|
which has been explicitly asked for by the client. The rule specified above
|
|
becomes understandable and thus bearable when looking at all the things
|
|
SCWrite does with the message:
|
|
\begin{itemize}
|
|
\item It is written to the client connection socket, subject to the output
|
|
code specified.
|
|
\item The message is written to all log files configured for the client
|
|
connection.
|
|
\item If the client privilege is user or manager, the data is written
|
|
to the command log.
|
|
\item The message is written to the server log together with the socket
|
|
number of the connection.
|
|
\item SCWrite stores the message into the Tcl macro interpreter in order to
|
|
enable processing of data from SICS commands in Tcl scripts.
|
|
\item SCWrite suppresses all messages to the client while executing a macro.
|
|
This stops spurious output to appear at the client connection when running a
|
|
command defined in the macro language. The exception are messages of type
|
|
eError and eWarning. Such messages are always sent to the client.
|
|
\end{itemize}
|
|
|
|
SCPrompt prompts the user at the client connection for data. The prompt
|
|
string pPrompt is sent. Data entered by the user is returned in buffer
|
|
pBuffer. Maximum iBufLen character are returned. While waiting for client to
|
|
provide data, the SICS task switcher runs.
|
|
|
|
There is another convenience function SCSendOK(SConnection *pCon) which is
|
|
just a wrapper around SCWrite. SCSendOk sends an 'OK' to the client. It is good
|
|
practice to let the user know that the operation requested had been
|
|
performed.
|
|
|
|
There are some more conventions which are useful to adhere to:
|
|
\begin{itemize}
|
|
\item All error messages start with the string ERROR:
|
|
\item All warnings start with the string WARNING:
|
|
\item All requested values are returned in the format name = value.
|
|
\end{itemize}
|
|
|
|
There exist special functions to send mass data through a connection
|
|
in either uuencoded or zipped form. The latter works only through
|
|
plain socket connections, telnet will mess up binary data. Please
|
|
note, that numeric data sent by the SICS server needs to be in network
|
|
byte order in order to be understood by the Java clients. Further
|
|
functions allow to tailor writing behavious further by overloading the
|
|
operations performed by SCWrite. This is documented in conman.h
|
|
|
|
|
|
\subsection{Checking Client Privileges}
|
|
One task any SICS object has to perform is to check if a client,
|
|
represented through a connection object, is privileged to perform a
|
|
requested operation. The most useful function to do this is:
|
|
\begin{verbatim}
|
|
int SCMatchRights(SConnection *pCon, int rights);
|
|
\end{verbatim}
|
|
This function not only checks if the user has at least the user rights
|
|
given as parameter rights but also checks if the SICS server is
|
|
currently performing a scan or something. It is generally a bad idea
|
|
to change parameters while the instrument is taking a measurement. If
|
|
all is good 1 is returned, 0 in case of trouble. All troubles will
|
|
already have been reported to the client by this function. SICS knows
|
|
four levels of user rights:
|
|
\begin{description}
|
|
\item[usSpy] may look at things but change nothing.
|
|
\item[usUser] may do those things a normal user is supposed to do.
|
|
\item[usMugger] may perform configuration tasks.
|
|
\item[usInternal] absolutely no restrictions, used only internally.
|
|
\end{description}
|
|
|
|
|
|
There are further functions for requesting client and setting client
|
|
rights codes. These functions are all defined in conman.h.
|
|
|
|
\subsection{Interrupting}
|
|
On occasion a SICS object may come to the conclusion that an error is
|
|
so bad that the measurement needs to be stopped. Clearly a means is
|
|
needed to communicate this to upper level code. This means is setting
|
|
an interrupt on the connection.
|
|
The current active interrupt is located at the connection object
|
|
and can be retrieved with {\bf SCGetInterrupt} and set with {\bf
|
|
SCSetInterrupt}. Interrupt codes are defined in interrupt.h and are ordered
|
|
into a hierarchy:
|
|
\begin{description}
|
|
\item[eContinue] Everything is just fine.
|
|
\item[eAbortOperation] Stop the current scan point or whatever is done,
|
|
but do not stop altogether.
|
|
\item[eAbortScan] Abort the current scan, but continue processing of further
|
|
commands in R\"unbuffers or command files.
|
|
\item[eAbortBatch] Aborts everything, operations, scans and batch processing
|
|
and leaves the system ready to enter new commands.
|
|
\item[eHaltSystem] As eAbortBatch, but lock the system.
|
|
\item[eFreeSystem] Unlocks a system halted with eHaltSystem.
|
|
\item[eEndServer] Makes the SICS server run down and exit.
|
|
For internal usage only.
|
|
\end{description}
|
|
|
|
Higher level SICS objects may come to the conclusion that the error
|
|
reported by lower level code is actually not that critical and clear
|
|
any pending interrupts by setting the interrupt code to eContinue and
|
|
thus consume the interrupt.
|
|
|
|
\subsection{Communicating with all Clients}
|
|
Two facilities exist which allow one SICS command to reach out to all
|
|
connected clients.
|
|
\begin{itemize}
|
|
\item There is a function which writes a message to all clients. This
|
|
is ServerWriteGlobal(char *text, int outCode);
|
|
\item There exists a global status code which can be set with
|
|
SetStatus and retrieved with GetStatus. See status.h for more
|
|
details. Use this when your SICS command starts lengthy operations
|
|
such as driving or counting.
|
|
\end{itemize}
|
|
|
|
\section{Using other SICS Objects}
|
|
In most cases a new command needs to make use of other SICS
|
|
objects. Before another SICS object can be used, it must be found
|
|
within the SICS interpreter. In order do this the name of the object
|
|
is obviously needed. This must be a configuration parameter or passed
|
|
in as a argument to the command. In general it is also necessary to
|
|
check if this name points to the right type of object. All this can be
|
|
achieved with the function:
|
|
\begin{verbatim}
|
|
void *FindCommandData(SicsInterp *pSics, char *name, char *type);
|
|
\end{verbatim}
|
|
This function tries to find a command name in the interpreter and also
|
|
checks if the objects type descriptor (the name parameter in the
|
|
object descriptor structure) matches type. If this is so, a pointer to
|
|
the objects data structure is returned. If one of the test fails, NULL
|
|
is returned. Suitable parameters for type can be found by searching
|
|
for CreateDescriptor in the implementation file of the desired
|
|
object. After a cast to the proper pointer type, all the functions
|
|
defined for the object and documented in its header file can be used.
|
|
|
|
\subsection{Running Motors and Counters}
|
|
There are special rules which apply if a new command is written which
|
|
coordinates motors and counters. For instance a special scan or drive
|
|
command. It is important that such higher level code starts motors,
|
|
virtual motors and counters through the interfaces defined by the
|
|
device executor. The device executor guarantees proper monitoring of
|
|
the device. The relevant functions are:
|
|
\begin{verbatim}
|
|
int StartMotor(pExeList self, SicsInterp *pSics, SConnection *pCon,
|
|
char *name, float fNew);
|
|
int StartCounter(pExeList self, SicsInterp *pSics, SConnection
|
|
*pCon, char *name);
|
|
|
|
\end{verbatim}
|
|
StartMotor starts the motor name to run to the new value
|
|
fNew. StartCounter starts the counter name. The counter must have been
|
|
loaded with proper presets etc. with the appropriate function
|
|
calls. The device executor hides behind the pExeList pointer. This is
|
|
always accessible through the global pointer: \verb+pServ->pExecutor+.
|
|
|
|
Once a counter or motor has been started, quite often the command can
|
|
only continue if the operation has finished. But during this time the
|
|
SICS server should be responsive to other clients. In order to do this
|
|
we have to wait for the device executor task to finish. A code
|
|
fragment implementing all this for a count operation is shown below:
|
|
\begin{verbatim}
|
|
/*-------------- count */
|
|
iRet = StartCounter(pServ->pExecutor, pSics,
|
|
pCon,
|
|
``ScanCounter'');
|
|
if(!iRet)
|
|
{
|
|
SCWrite(self->pCon,"ERROR: Cannot Count, Scan aborted",eError);
|
|
return 0;
|
|
}
|
|
/* get the ID of the device executor task */
|
|
lTask = GetDevexecID(pServ->pExecutor); /* get ID of device
|
|
executor task */
|
|
if(lTask > 0);
|
|
{
|
|
/* wait for the device executor to finish */
|
|
TaskWait(pServ->pTasker,lTask);
|
|
}
|
|
|
|
/* finished, check for interrupts. Whatever happened, user
|
|
interrupt or HW interrupt, it will be on our connection
|
|
*/
|
|
iInt = SCGetInterrupt(self->pCon);
|
|
switch(iInt)
|
|
{
|
|
case eContinue:
|
|
break;
|
|
case eAbortOperation:
|
|
continue;
|
|
break;
|
|
case eAbortScan:
|
|
SCWrite(self->pCon,"ERROR: Scan aborted",eError);
|
|
/* eat the interrupt, the requested op has been
|
|
done
|
|
*/
|
|
SCSetInterrupt(self->pCon,eContinue);
|
|
return 0;
|
|
break;
|
|
default: /* all others */
|
|
SCWrite(self->pCon,"ERROR: Scan aborted",eError);
|
|
return 0;
|
|
break;
|
|
}
|
|
\end{verbatim}
|
|
This code also shows the necessary error checking. It also shows how
|
|
to check for possible interrupts after such an operation. It is very
|
|
advisable to do this because the user may have interrupted the
|
|
process. And she might not be all to happy if the new command just
|
|
continues with the next step rather than aborting the process.
|
|
|
|
|
|
\section{SICS Interfaces}\label{interface}\label{inter}
|
|
The point about SICS interfaces can best be deduced from an example:
|
|
Everybody expects that a motor can be operated through a drive command
|
|
or scanned in a scan. But there are other things which should be
|
|
operated through a drive or scan commands too: environment controllers
|
|
(temperature), virtual motors, chopper speeds, chopper phases and
|
|
possibly other things. In order to make upper level scan or drive
|
|
commands independent of the implementation of the actual operation it
|
|
is useful to have an abstract interface for everything which can be
|
|
driven or scanned. This is the drivable interface. Any such object
|
|
which should be driven or scanned has to implement this interface.
|
|
|
|
Several of these interfaces exist in SICS:
|
|
\begin{description}
|
|
\item[Drivable] The drivable interface for everything which can be
|
|
moved and takes some time to complete its movement.
|
|
\item[Countable] Everything which counts: counters, histogram memories
|
|
etc.
|
|
\item[Environment] An interface which allows for monitoring of a
|
|
parameter through the environment monitor. Usually these are sample
|
|
environment things but it could also be chopper phases etc.
|
|
\item[Callback] This is an interface which allows object A to call a
|
|
special function, the callback function, in the context of object B
|
|
whenever a certain event in A occurs. This is a way to automatically
|
|
link object together in a component programming manner. This is also
|
|
used for automatic notification of status clients when instrument
|
|
parameters change.
|
|
\end{description}
|
|
|
|
There are several situations when the SICS interfaces have to be
|
|
considered:
|
|
\begin{itemize}
|
|
\item When hacking SICS kernel code or replacing parts of it.
|
|
\item The driveable interface should be implemented by virtual
|
|
motors. Virtual motors are objects which realize complex movements
|
|
possibly involving multiple motors. Examples include driving
|
|
wavelength (theta,two theta and possibly curvature motors have to be
|
|
moved) or omega two theta.
|
|
\item Any time objects are introduced into SICS which repesent
|
|
completely new hardware.
|
|
\item When automatical notifications between objects are needed, use
|
|
the callback interface.
|
|
\end{itemize}
|
|
|
|
Adding any such interface to your new SICS object involves the
|
|
following steps:
|
|
\begin{itemize}
|
|
\item Add a data structure representing the interface to your objects
|
|
data structure.
|
|
\item Write all the functions required by the interface.
|
|
\item Populate the interface data structure with the pointers to your
|
|
function implementations.
|
|
\item Write a new GetInterface function for the object descriptor
|
|
which returns your new interface when requested and assign it to your
|
|
object descriptors GetInterface field. SICS needs this in order to be
|
|
able to find the objects new interface.
|
|
\end{itemize}
|
|
|
|
The interfaces available are documented in the files interface.w,
|
|
interface.h and interface.tex in the main SICS directory and through
|
|
numerous examples in the source code.
|
|
|
|
A not overly complex example for the implementation of an interface is
|
|
the code in o2t.* which implements the coupled driving of two motors
|
|
where the second is always the double of the value of the first. This
|
|
is for omega two-theta scans.
|