Files
sics/doc/programmer/command.tex
Douglas Clowes ef68c15345 fix some typos
2012-11-28 16:26:52 +11:00

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.