Files
sics/doc/programmer/oguide.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.*.