806 lines
38 KiB
TeX
806 lines
38 KiB
TeX
\chapter{Guide To SICS Object Writers}
|
|
This chapter describes the necessary steps for adding new objects to the SICS server. In order to live
|
|
happily within SICS an object is obliged to stick to a set of rules and interfaces already defined. Such
|
|
interfaces will be described here as well. For the following text it is assumed, that the reader has studied
|
|
the SICS overview and kernel guide.
|
|
|
|
In general the following steps will be necessary in order to introduce a new object into SICS:
|
|
\begin{enumerate}
|
|
\item Define a new object data structure.
|
|
\item Define a function capable of deleting the object from memory.
|
|
\item Define a set of functions which define the operations on this object.
|
|
\item Create an object wrapper function which defines the user commands
|
|
the object can handle.
|
|
\item Create an object creation function, which is capable of initializing
|
|
the object and creates a new command in the SICS interpreter.
|
|
\item Add the new objects creation command into the IniInitCommand and
|
|
KillIniCommands functions defined in ofac.c.
|
|
\item Document the new object.
|
|
\end{enumerate}
|
|
All these steps will now be discussed together with some additional SICS
|
|
concepts applicable to writing new objects. Not all of the above mentioned
|
|
steps will be necessary in all cases. If a new general SICS object is defined,
|
|
no object creation function may be needed. Then it is sufficient to register
|
|
the new object/command with the SICS interpreter in IniInitCommands, file ofac.c.
|
|
In the rare cases where an object needs no data structure, the system will provide a dumb
|
|
default which will not harm the system. Some objects may have more then one
|
|
command associated with them. Some more care has to be taken when adding a
|
|
new piece of hardware into the system. If it is just a variant of an already
|
|
known type (for instance a new type of motor) it will be sufficient to introduce
|
|
a new driver into the system. The procedures for doing this will be described
|
|
in the reference sections for the appropriate device. Otherwise the SICS rule
|
|
to divide a hardware object into a logical object and a driver should be
|
|
observed. At minimum two drivers are necessary anyway in the first place:
|
|
the driver for the actual device and a simulation driver which permits some
|
|
debugging to take place while the hardware is still unavailable.
|
|
|
|
\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{The Object Data Structure}
|
|
Nearly all SICS objects require some data. This data is collected in a
|
|
C structure typical for the object. This is just a normal C struct with one
|
|
exception:
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
\leftline{{\huge Rule 1}}
|
|
The first item in a SICS object data structure MUST be a pointer to a
|
|
ObjectDescriptor data structure. This looks like:
|
|
\begin{verbatim}
|
|
typedef struct __MyObject {
|
|
pObjectDescriptor pDes;
|
|
int iMyExtremlyImportantInt;
|
|
.
|
|
.
|
|
.
|
|
} MyObject;
|
|
\end{verbatim}
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
|
|
Otherwise the SICS server will crash on you sooner or later. The reason for
|
|
this is that SICS needs a means to identify a object and its capabilities
|
|
from inside its code. For example a user has typed: {\em drive mot1 26} into
|
|
the server. Now drive needs to verify that mot1 is really a motor or
|
|
something else which can be driven. This can be done in the following way:
|
|
\begin{enumerate}
|
|
\item First find the object in the SICS interpreter
|
|
(function FindCommand, SCinter.h).
|
|
\item Get the pointer to the objects data structure.
|
|
\item Assign this pointer to a dummy data structure (defined in obdes.h)
|
|
which has a pointer to the ObjectDescriptor as single element.
|
|
\item Ask the ObjectDescriptor if the object can be driven.
|
|
\end{enumerate}
|
|
In object speak this is called run time type information (RTTI).
|
|
|
|
It is interesting to look at the object descriptor in more detail. It is
|
|
again a data structure and a good example for polymorphism:
|
|
\begin{verbatim}
|
|
typedef struct {
|
|
char *name;
|
|
int (*SaveStatus)(void *self, char *name,FILE *fd);
|
|
void *(*GetInterface)(void *self, int iInterfaceID);
|
|
} ObjectDescriptor, *pObjectDescriptor;
|
|
|
|
\end{verbatim}
|
|
The first field contains a string defining a type name. The next field is a
|
|
pointer to a save function. This function is automatically called for all objects
|
|
in the SICS interpreter when the server shuts down. This function is supposed to
|
|
write all SICS commands necessary to reconfigure the object back into its current state
|
|
into file fd. The middle parameter is the name of the object in the interpreter
|
|
which must not necessarily be known to the object.
|
|
|
|
The next field, a pointer to a function GetInterface is used in order to inquire
|
|
the capabilities of an object. Object capabilities in SICS are defined in terms of
|
|
{\bf {\Large Interfaces}}. An Interface in the SICS sense is again a data structure
|
|
which contains data fields and pointers to functions implementing the capability. As
|
|
an example see the Drivable interface which is implemented by motors, environment
|
|
devices and variables such as lambda which can be driven to a new value:
|
|
\begin{verbatim}
|
|
typedef struct {
|
|
int ID;
|
|
int (*Halt)(void *self);
|
|
int (*CheckLimits)(void *self, float fVal,
|
|
char *error, int iErrLen);
|
|
long (*SetValue)(void *self, SConnection *pCon,
|
|
float fVal);
|
|
int (*CheckStatus)(void *self, SConnection *pCon);
|
|
float (*GetValue)(void *self, SConnection *pCon);
|
|
} IDrivable, *pIDrivable;
|
|
|
|
\end{verbatim}
|
|
Each SICS interface is identified by an integer ID number (defined in interface.h).
|
|
GetInterface returns a pointer to an interface data structure for an ID if the object
|
|
implements that interface. If not a NULL is returned.
|
|
|
|
\section{SICS Interfaces}\label{gow}
|
|
Thus SICS interfaces define the capabilities of an object. The concept of
|
|
Interfaces is similar to interfaces in the Java programming language
|
|
or abstract base classes in C++ or other object oriented languages.
|
|
As can be seen from the example
|
|
given above, the interface implements the most common operations on an object with
|
|
capabilities as defined by the interface. Currently, SICS knows four different kinds of
|
|
Interfaces:
|
|
\begin{description}
|
|
\item[Drivable] The Drivable interface for objects such as motors, adjustable parameters
|
|
and the like. Anything which can be driven to a value.
|
|
\item[Countable] The Countable interface used for everything which can count: single
|
|
counters, histogram memories or whatever may creep up.
|
|
\item[CallBack] The CallBack interface introduces some component programming techniques
|
|
into SICS. This technique lets an object, name it object A, issue events when its internal
|
|
state changes. For example if object A is a motor, any time it drives to a new value
|
|
an event is issued. Then another object, name it object B, can register an interest in
|
|
such events with object A. It does so by giving A the address of a function to call
|
|
when such an event occurs. A now calls all functions thus registered when the event
|
|
occurs. Object B thus gets automatically notified when A changes its state. Currently
|
|
this scheme is only used for implementing automatic notifications of clients when a
|
|
SICS object changes its state. But there is a lot of potential in this scheme.
|
|
\item[Environment] The Environment device is a interface implemented by all devices which
|
|
need to be regularly monitored by the environment monitor.
|
|
\end{description}
|
|
For more details about the SICS interfaces see the reference section: \ref{interref}.
|
|
The list of SICS interfaces can be extended when a novel abstract capability of an object
|
|
is needed. This is also the main reason why this sophisticated scheme was chosen.
|
|
|
|
The question may arise how to use SICS interfaces. For example consider an object defining
|
|
a drivable variable. The following steps are necessary:
|
|
\begin{enumerate}
|
|
\item Define functions with matching parameters to the ones requested by the interface.
|
|
Tip: the first parameter, the pointer to void, is always the pointer to the object data
|
|
structure to operate on. These functions must implement appropriate actions. What these
|
|
are: see the documentation of the interface.
|
|
\item Define a GetInterface function which returns a suitable interface
|
|
data structure when asked for an interface with the Drivable ID. The fields of the interface
|
|
structure returned must be set to the addresses of the functions you defined in step 1.
|
|
\item Create an object initialization function.
|
|
\item In this object initialization function: assign the object descriptors filed GetInterface
|
|
to the address of your GetInterface function.
|
|
\end{enumerate}
|
|
Usually, the interfaces an object implements are made a field in the objects data
|
|
structure. This interface is then initialized with appropriate function pointers when
|
|
creating the object. GetInterface then returns this pointer when the interface is
|
|
requested. Due to this usage a rule exits:
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
\leftline{{\huge Rule 2}}
|
|
Any interface data structure provided by an object belongs to the object providing
|
|
it. The owner object is responsible for deleting it. Never free an interface data structure
|
|
provided by another object.
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
|
|
Note, that the SICS interfaces are not the only interfaces to take care of. As SICS uses
|
|
this duality between logical hardware objects and hardware drivers, the interfaces defined
|
|
by the logical hardware devices can been seen as yet another internal interface. This leads to:
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
\leftline{{\huge Recommendation}}
|
|
Watch your neighbors! Use the interfaces provided by the logical hardware objects for
|
|
your objects business, when applicable.
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
|
|
\section{The Object Deletion Function}
|
|
When the SICS server exits or when a command is removed from the SICS
|
|
interpreter the interpreter will call a function which should free the memory
|
|
associated with the deleted object. This is absolutely necessary in order to
|
|
prevent memory leakage. Such a function has the form:
|
|
\begin{verbatim}
|
|
void ObjectKiller(void *pData);
|
|
\end{verbatim}
|
|
with pData being the pointer to the object to delete.
|
|
|
|
|
|
\section{Writing Object Functions}
|
|
The rules specified in this section apply to all kinds of object functions:
|
|
those which implement functionality as well as the object wrapper
|
|
functions. Please note, that any SICS object has two interfaces: an internal
|
|
C--language interface and a user visible interface defined by the object
|
|
wrapper functions. There are reasons why those two interfaces might not
|
|
provide equivalent functionality.
|
|
|
|
\subsection{Input and OutPut}
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
\leftline{{\huge Rule 3}}
|
|
All input and output to the client executing the command must go through the
|
|
connection object functions SCWrite and SCPrompt.
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
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 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.
|
|
|
|
Given the rule stated above, it follows that connection objects have to
|
|
be passed through the system to nearly everywhere. Especially where
|
|
reportable errors are found.
|
|
|
|
All other I/O, for instance to files sitting at the computer running the
|
|
SICS server, is not subjected to Rule 3.
|
|
|
|
|
|
\subsection{Error Handling}
|
|
Error handling is two thirds of the code written. Errors can be caused by
|
|
faulty users or faulty hardware. In any case, the first thing to do is:
|
|
report it using SCWrite! The next thing is to get the system into a state
|
|
that it can continue execution. This is particularly important for a server
|
|
program which might run for months on end. This means, free all memory used
|
|
before the error occurred, get system variables into safe values and the
|
|
like.
|
|
|
|
Sometimes error conditions arise in lower level code which should cause all
|
|
upper level code to finish execution. Such conditions may be the result of a
|
|
critical hardware fault or may even be requested by a user who wants to
|
|
abort an operation. A standard method for communicating such conditions
|
|
through the system is necessary.
|
|
SICS uses interrupts for such conditions.
|
|
The current interrupt 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}
|
|
|
|
In most cases interrupts shall only effect the client connection executing
|
|
the command. In such cases user code should use SCSetInterrupt in order to
|
|
set an interrupt at the connection executing the command only. Very rarely
|
|
conditions occur when an interrupt should affect the whole system. Then
|
|
SetInterrupt (defined in intserv.h) should be used. SetInterrupt forwards
|
|
the interrupt to all running tasks.
|
|
|
|
Objects implementing higher level commands such as scan commands might be
|
|
able to handle interrupt conditions and thus consume an interrupt. Then the
|
|
interrupt must be released with SCSetInterrupt and setting the interrupt to
|
|
eContinue. The interrupt on a connection will automatically be set to
|
|
eContinue when executing a new command from the command stack.
|
|
|
|
Objects are responsible for checking for interrupt conditions when
|
|
applicable. This is specially true for objects performing complex or
|
|
lengthy
|
|
operations with hardware. Whenever a hardware operation finishes, the
|
|
interrupt must be checked for and handled. The called hardware object or
|
|
the user might have set an interrupt which needs to be honored.
|
|
|
|
Thus two new rules can be defined:
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
\leftline{{\huge Rule 4}}
|
|
Signal critical error conditions with interrupts.
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
\leftline{{\huge Rule 5}}
|
|
Check for pending interrupts wherever applicable.
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
|
|
\subsection{Authorisation}
|
|
Objects are also responsible for checking if the client requesting an
|
|
operation may actually perform the operation. There are two cases when an
|
|
operation may be forbidden:
|
|
\begin{itemize}
|
|
\item The user is not suitably authorized.
|
|
\item The user is about to change critical parameters affecting a running
|
|
measurement.
|
|
\end{itemize}
|
|
Both conditions must be checked for by the objects code. Where else then at
|
|
the object can be known which operations are critical or not? This gives a
|
|
new rule:
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
\leftline{{\huge Rule 6}}
|
|
Objects have to check authorisation!
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
|
|
The user rights code for a client connection can be retrieved with
|
|
SCGetRights. SCGetGrab finds out if a connection has the control token.
|
|
A match with a value can be checked for with SCMatchRights which
|
|
returns true (1) if the connection has a user rights equal or higher then the
|
|
one necessary. SCMatchRights also checks for the status of the control token
|
|
and prints messages. SCMatchRights is the recommended function for checking
|
|
access rights in SICS.
|
|
|
|
An object can find out if some hardware operation is running by calling
|
|
the function isInRunMode prototyped in devexec.h.
|
|
|
|
An object can find out, if it is executing in a macro by calling SCinMacro.
|
|
Might be useful if the code is dependent on this condition. Usually all
|
|
details associated with running in a macro are dealt with by the system.
|
|
|
|
\subsection{Driving Hardware}
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
\leftline{{\huge Rule 7}}
|
|
Use the device executor!
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
With respect to hardware, there are two different kinds of objects in SICS:
|
|
some objects represent hardware. Examples are motors, counters. This group
|
|
also includes drivable variables which implement coordinated movements of
|
|
hardware. Such objects have to implement the necessary interfaces: either
|
|
the drivable or the countable interface. The second class are objects which
|
|
implement commands which use the hardware. For instance a drive command.
|
|
This second class of commands has to find the necessary hardware objects and
|
|
then run the actual hardware through the device executor. The function to
|
|
use is StartDevice or its derivatives, prototyped in devexec.h. After this
|
|
is done, there are tow options. In a scan command it might be needed to wait for
|
|
the hardware to finish before continuing your code. In this case use
|
|
function TaskWait, prototyped in tasker.h.
|
|
In other cases execution of the function may just
|
|
continue. The device executor will the take care of the task of monitoring
|
|
the hardware operation. The following code snippet gives an example how to
|
|
start a counter properly and wait for it to finish:
|
|
\begin{verbatim}
|
|
/*-------------- count */
|
|
pDum = (pDummy)self->pCounterData;
|
|
iRet = StartDevice(pServ->pExecutor, /* the executor, from global pServ */
|
|
"ScanCounter", /* a name */
|
|
pDum->pDescriptor, /* the descriptor of the cter */
|
|
self->pCounterData, /* pointer to counter data */
|
|
self->pCon, /* pointer to connection object */
|
|
self->fPreset); /* count preset */
|
|
if(!iRet)
|
|
{
|
|
SCWrite(self->pCon,"ERROR: Cannot Count, Scan aborted",eError);
|
|
return 0;
|
|
}
|
|
/* wait for finish */
|
|
lTask = GetDevexecID(pServ->pExecutor); /* get ID of device
|
|
executor task */
|
|
if(lTask > 0);
|
|
{
|
|
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}
|
|
The above code (taken from the implementation of the scan command) also
|
|
shows an example for handling interrupts as a followup to running hardware.
|
|
Such a procedure is highly recommended.
|
|
|
|
\subsection{Working with the CallBack Interface}
|
|
Working with the callback interface is usually simple: just use the
|
|
functions as defined in interface.h. Add new event codes to event.h, in
|
|
order to make them known. A problem arises if an object which has callbacks
|
|
registered with other objects gets deleted. For the following discussion:
|
|
let us denote the object registering the callback as the client object and
|
|
the object which generates the event (where the callback was registered) as
|
|
source object. A typical example for a client is a
|
|
connection object which had configured itself to be automatically notified
|
|
of motor movements, variable changes and the like. Typically this is a
|
|
status display client. In such cases the client object has to keep
|
|
track of the callbacks it registered and delete them when the client objects gets
|
|
deleted. Otherwise the source object would invoke a callback on an non
|
|
existing client object. This usually results in a lovely core dump. For the
|
|
connection object this is taken care of automatically. The only thing needed
|
|
to be done, is to register the callback on the connection object with
|
|
SCRegister prototyped in conman.h. If user codes removes a callback later on,
|
|
it may call SCUnregister on the connection object to remove it from the
|
|
connection objects callback database as well. All registered callback will
|
|
be properly removed when the connection object is deleted.
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
\leftline{{\huge Rule 8}}
|
|
Remove registered callbacks when deleting an object. For connection objects:
|
|
register your callbacks!
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
|
|
|
|
\section{The Object Wrapper Function}
|
|
The object wrapper function makes the SICS object visible as a command in
|
|
the interpreter. It has to analyze parameters given to the command and act
|
|
accordingly. The object wrapper function has the following form:
|
|
\begin{verbatim}
|
|
int ObjectWrapper(SConnection *pCon, SicsInterp *pSics, void *pData,
|
|
int argc, char *argv[]);
|
|
\end{verbatim}
|
|
The parameters are:
|
|
\begin{itemize}
|
|
\item A pointer to the connection object and the client invoking the command.
|
|
\item A pointer to the SICS interpreter in which the command was executed.
|
|
As the interpreter also doubles as a database for objects it is often needed
|
|
to locate other objects.
|
|
\item A pointer to the objects data structure (pData).
|
|
\item The number of arguments specified to the command.
|
|
\item The text of the arguments specified. \verb+argv[0]+ is the name of the
|
|
command invoked. The argc, \verb+argv[]+ are almost the same as the argc,
|
|
\verb+argv[]+ pair given to a C main program.
|
|
\end{itemize}
|
|
In order to give the macro interpreter a hint if things are okay, the object
|
|
wrapper function has to return 1 for success and 0 if it fails. The rules
|
|
stated above for normal object functions apply.
|
|
|
|
\section{The Object Creation Function}
|
|
The object creation function has the same form as the object wrapper
|
|
function. Its purpose is to analyze parameters and to create the requested
|
|
object from them. In the end the object creation function will probably
|
|
create a new command in the SICS interpreter. The function to do this is the
|
|
AddCommand function. The prototype for AddCommand looks like this:
|
|
\begin{verbatim}
|
|
int AddCommand(SicsInterp *pSics,
|
|
char *name,
|
|
ObjectFunc pObFunc,
|
|
KillFunc pKill,
|
|
void *pData);
|
|
\end{verbatim}
|
|
The parameters are:
|
|
\begin{itemize}
|
|
\item A pointer to the SICS interpreter into which the new command shall be
|
|
installed.
|
|
\item The name of the new command.
|
|
\item The object function implementing the command.
|
|
\item An optional function which is able to remove the object data structure
|
|
given as last parameter from memory. This is mandatory for any object with
|
|
an own data structure.
|
|
\item A pointer to the object data structure.
|
|
\end{itemize}
|
|
|
|
\section{Writing Data}
|
|
For simple ASCII files at instruments doing scans, see the SICS manager
|
|
documentation for the template file description. Other instruments should
|
|
use the NeXus data format. Especially the NXDICT implementation. For more
|
|
documentation see the napi and nxdict documents. An example for using NXDICT
|
|
is given in the file nxsans.c.
|
|
|
|
\section{Hardware Objects}
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
\leftline{{\huge Rule 9}}
|
|
Obey the division between driver code and logical object for hardware
|
|
objects.
|
|
\begin{center}
|
|
\rule{9cm}{2mm}
|
|
\end{center}
|
|
The concept of separating a hardware object in two components: a driver
|
|
which implements primitive operations and a logical object with which SICS
|
|
interacts internally has proven very valuable. First of all two drivers are
|
|
needed anyway: a simulation driver for simulating hardware and a
|
|
driver for the real hardware. Simulating hardware proved invaluable for
|
|
debugging of higher level code and can be used later on for testing command
|
|
files.
|
|
|
|
Please note that sample environment devices are handled slightly
|
|
differently. There exists a more general sample environment object which
|
|
handles many aspects of such a device. If the logical interface provided by
|
|
this general sample environment objects suffices, a new sample environment
|
|
device requires just a new driver. If the sample environment needs some
|
|
specials the study the relationship between the itc4 object and the general sample
|
|
environment object. Basically such a derived object implements in its
|
|
functions just the specials and calls the appropriate functions of the
|
|
general environment device object at the end. This is a simple form of
|
|
inheritance by delegation. This scheme has been devised in order to reduce
|
|
the need for code duplication when introducing new environment devices. For
|
|
more details see the reference section on environment devices.
|
|
|
|
\section{Examples}
|
|
For a deeper understanding of the concepts explained above it may help to
|
|
look at some source code. A nice example for the whole SICS object setup
|
|
including a callback interface is the implementation of the SICS variables
|
|
in files sicsvar.h and sicvar.c. For the implementation of a hardware object
|
|
including a drivable interface, the code in motor.h, motor.c and the motor
|
|
drivers: simdriv.c, el734dc.c and el734driv.c may be a suitable example. The
|
|
motor object creation function also shows how to handle multiple drivers for
|
|
a logical object.
|
|
SICS knows a omega-two-theta variable o2t. This variable coordinates two
|
|
motors in a way that the second motor has exactly the double value of the
|
|
first. This was meant to implement a omega-two-theta scan. Anyway, o2t is
|
|
a good example for implementing a drivable interface and how to handle such
|
|
coordinated movements. An example for the implementation of a countable
|
|
interface can be found in counter.h, counter.c for a single counter.
|
|
|
|
\section{Documentation}
|
|
If a new object has been defined two documentation files should be provided:
|
|
\begin{itemize}
|
|
\item A html file which describes the user interface to the object.
|
|
\item A latex file which describes the data structures used to implement the
|
|
object, the interface to the object and as much internal knowledge about the
|
|
working of the object as you care to provide. Being generous with
|
|
information helps maintaining the code later on!
|
|
\end{itemize}
|
|
Furthermore it is required to update the SICS managers documentation so that
|
|
the object creation function is known.
|
|
|
|
\section{Introducing a new Hardware Driver}
|
|
A new hardware driver for an existing objects can be introduced into the
|
|
system with the following two steps:
|
|
\begin{itemize}
|
|
\item Write the new driver. Use an existing driver as template.
|
|
\item Locate the objects factory function. Modify it so that it knows about
|
|
the new driver and how to initialise it.
|
|
\item Depending of the implementation of the object, the object deletion
|
|
function may need to know about the new driver as well in order to delete it
|
|
properly.
|
|
\end{itemize}
|
|
Do not forget to update the SICS manager documentation to include the new
|
|
driver!
|
|
|
|
\section{Coding Considerations}
|
|
The header file {\em sics.h} includes all the SICS kernel header files in
|
|
the right order. It is recommended to include sics.h at the top of
|
|
your implementation file.
|
|
|
|
A server program has to run for times on end. Thus a server program is more
|
|
sensitive than any other program to memory leakage or memory overwrites.
|
|
Therefore the current SICS implementation utilizes a memory debugging tool
|
|
called fortify. It is highly recommended to include fortify.h into your
|
|
implementation file in order to enable memory debugging. More information
|
|
about the inner working and usage of fortify can be found in the file fortify.doc in
|
|
the sics source code directory. Fortify has given some false alarms already
|
|
but it also helped to eliminate a couple of nasty problems at a very early
|
|
stage. A nasty feature of fortify is that its header file, fortify.h, has
|
|
to be included in ALL relevant source files. Otherwise nasty pseudo bugs
|
|
creep up when fortify is enabled. Please note, that fortify is only enabled
|
|
when all source files have been compiled with the flag -DFORTIFY. Otherwise
|
|
fortify compiles to nothing. More information about fortify can be found in
|
|
file fortify.h.
|
|
|
|
Names in the SICS source code are often made up along the following pattern:
|
|
\begin{itemize}
|
|
\item Use the full name of the thing or sensible and command abbreviations.
|
|
Write the variable as one word, with word boundaries being marked by capital
|
|
letters.
|
|
\item Most variables are prefixed with a letter denoting type. Letters used
|
|
are:
|
|
\begin{itemize}
|
|
\item p for pointer.
|
|
\item i for integer.
|
|
\item f for float.
|
|
\item d for double.
|
|
\item l for long.
|
|
\item s for struct.
|
|
\end{itemize}
|
|
Combinations of letters are also possible.
|
|
\end{itemize}
|
|
|
|
Some more general data structure implementations are used in SICS: There is
|
|
a general linked list package from a dutch men documented in lld.h. There is
|
|
a dynamical array in dynar.* and a parameter array in obpar.*.
|
|
|
|
|
|
|