\chapter{Writing SICS Device Drivers} This chapter deals with writing new hardware drivers for SICS. SICS hardware has a dual identity: Towards upper level code SICS hardware is represented by the logical hardware object. All the low level detail is handled in the hardware driver. The point of this is that upper level code does not need to know which type of hardware device is being accessed. Experience shows that this scheme covers most usage cases for a given hardware device. However, there were always exceptions mostly in order to realize special configurations. Such exceptions can be dealt with through special macros which implement commands which do special configuration tasks. In order to be able to write such scripts it is feasible to organize hardware access into three layers: \begin{itemize} \item A communication layer. This layer allows for sending commands and reading data along a given bus. \item A controller layer. If a device or several share a controller make the controller visible within the system. Allow for sending suitable commands to it. \item The actual SICS driver. \end{itemize} This organisation allows scripts to directly talk to devices through either the controller or the communication layer. If something is missing in the general driver interface it is usually easier to add some code at controller or communications level, rather then change all drivers present in the system. All drivers use a common pattern for error handling. Please read the section on the motor driver where this pattern is explained in more detail. The same pattern is applied in most drivers. Please be aware that the hardware drivers have a significant impact on SICS's overall performance. Most of the time SICS will be sitting there and wait for a counter or a motor to finish. And often the devices controlling such things respond pretty slow. The most often called function is the status check function of the driver. It is advisable to optimize this is good as possible. Suggestions for this include: \begin{itemize} \item Reduce the amount of data to be read from the controller as much as possible. \item Make the status function a state machine: in the first state a status request command is sent and the second state is entered. In teh second state, the function checks if data is available on the communication port and process if this is so, else it returns the appropriate busy code. \end{itemize} This section describes the actual drivers. How these drivers are integrated into SICS is described in the chapter on the site data structure (see \ref{site}). \section{The Motor Driver} A motor driver again is represented by an interface encapsulated in a data structure. Polymorphy is achieved in two ways: \begin{itemize} \item Through the functions you have to define for your motor and to assign to the function pointers in the motor driver data structure. \item For the data structure, polymorphy is achieved through overlay. This means, if you define your own motor driver data structure the first fields up to KillPrivate MUST be the same as defined in the MotorDriver structure defined below. You MUST append your own fields below KillPrivate. \end{itemize} This is the motor driver data structure which has to be implemented: \begin{verbatim} typedef struct __AbstractMoDriv { /* general motor driver interface fields. REQUIRED! */ float fUpper; /* upper limit */ float fLower; /* lower limit */ char *name; int (*GetPosition)(void *self, float *fPos); int (*RunTo)(void *self,float fNewVal); int (*GetStatus)(void *self); void (*GetError)(void *self, int *iCode, char *buffer, int iBufLen); int (*TryAndFixIt)(void *self, int iError,float fNew); int (*Halt)(void *self); int (*GetDriverPar)(void *self, char *name, float *value); int (*SetDriverPar)(void *self,SConnection *pCon, char *name, float newValue); void (*ListDriverPar)(void *self, char *motorName, SConnection *pCon); void (*KillPrivate)(void *self); } MotorDriver; \end{verbatim} In order not to have to repeat trivial things all the time two general things must be stated: \begin{itemize} \item The pointer self is always a pointer to the motor driver data structure. \item Functions return 1 on success or 0 on failure if not stated otherwise. \end{itemize} The elements of this data structure are: \begin{description} \item[fUpper,fLower] These are the motors hardware limits. These values are supposed to be identical to the positions of the limit switches on the real thing. Read them from the motor or initialize them from parameters when initializing the motor driver. \item[GetPosition] reads the position of the motor and puts the result into fPos. This ought to be the position from the motor controller. Software zeros are applied later by code in motor.c. GetPosition returns either OKOK when the request succeeded or HWFault on failure. \item[RunTo] Starts the motor to run towards the new position fNewVal. fNewVal must be a value valid for the controller. Software zero points have already been taken account of by code in motor.c. This function shall NOT wait for the completion of the driving operation. RunTo returns either OKOK when the request succeeded or HWFault on failure. \item[GetStatus] This function is called repeatedly by upper level code to poll for the progress of the driving operation. Possible return values of this function are: \begin{description} \item[HWFault] If there is a fault in the hardware or the status cannot be read. \item[HWPosFault] The motor is still alive but the controller was unable to position the motor. \item[HWBusy] The motor is still driving. \item[HWWarn] There is a warning from the controller. \item[HWIdle] The motor has finished driving and is idle. \end{description} \item[GetError] retrieves information about an error which occurred on the motor. An integer error code is returned in iCode. Up to iBufLen characters of descriptive error information is copied into buffer. This information is printed as error message by upper level code. \item[TryAndFixIt] Given an error code in iError, try to repair the problem as far as this is possible in software. iError should be an error code as returned by GetError in iCode. This function has the following return codes: \begin{description} \item[MOTREDO] Problem fixed, try to redo the last the operation. \item[MOTFAIL] The problem cannot be fixed in software. \end{description} The parameter fNew is the target position of the motor. \item[Halt] stops the motor immediately. \item[GetDriverPar] copies the value of the motor driver parameter name into value, if such a parameter exists. \item[SetDriverPar] sets the motor driver parameter name to newValue. Report errors to pCon. \item[ListDriverPar] write the names and values of all driver parameters to the client connection pCon. \item[KillPrivate] releases any memory possibly allocated for private fields in the motor data structure. \end{description} In order to understand the relationship between GetError and TryAndFixIt it helps to look at the way how errors are handled by upper level code in motor.c: If an error in any function occurs, GetError gets called. An error message is printed. Then TryAndFixIt is called with the error code returned in iCode as a parameter. If TryAndFixIt returns MOTFAIL, the code gives up. If TryAndFixIt returns MOTREDO, the failed operation is retried. At max 3 retries are performed. If the operation does not succeed after three retries, a motor failure is reported. The GetDriverPar, SetDriverPar and ListDriverPar functions implement some support for driver private configuration parameters. Such parameters are meant to be configured from the instrument initialization file. Currently there is no support to include these parameters into the status file. If there are no such parameters have these functions do nothing and return 1. \section{The Counter Driver} A counter driver is a driver for some box which allows to count for a preset time or monitor and manages single counters and monitors. Such a driver is represented by a data structure: \begin{verbatim} typedef struct __COUNTER { /* variables */ char *name; char *type; CounterMode eMode; float fPreset; float fLastCurrent; float fTime; int iNoOfMonitors; long lCounts[MAXCOUNT]; int iPause; int iErrorCode; /* functions */ int (*GetStatus)(struct __COUNTER *self, float *fControl); int (*Start)(struct __COUNTER *self); int (*Pause)(struct __COUNTER *self); int (*Continue)(struct __COUNTER *self); int (*Halt)(struct __COUNTER *self); int (*ReadValues)(struct __COUNTER *self); int (*GetError)(struct __COUNTER *self, int *iCode, char *error, int iErrLen); int (*TryAndFixIt)(struct __COUNTER *self, int iCode); int (*Set)(struct __COUNTER *self,char *name, int iCter, float fVal); int (*Get)(struct __COUNTER *self,char *name, int iCter, float *fVal); int (*Send)(struct __COUNTER *self, char *pText, char *pReply, int iReplyLen); void (*KillPrivate)(struct __COUNTER *self); void *pData; /* counter specific data goes here, ONLY for internal driver use! */ } CounterDriver, *pCounterDriver; \end{verbatim} Polymorphy is achieved through the function pointers. Differences in the data structure for different counter boxes are accounted for through the pData pointer. This is meant to be initialized by the actual counter driver to a private data structure which holds data relevant to this particular counter. All functions take a pointer to this counter driver structure as parameter self. If not stated otherwise functions return 1 on success and 0 on failure. The fields: \begin{description} \item[name] The counter name in SICS \item[type] The driver type. \item[eMode] The counter mode. Possible values eTimer for preset timer and eMonitor for preset monitor operation. This mode will be set by upper level code. \item[fPreset] The preset for either timer or monitor. \item[fLastCurrent] the last known value for the control variable during counting. Gets updated in GetStatus while counting and is used for reporting count status. \item[fTime] The time the last counting operation took. This is a time read from the counter box. This could be different from elapsed time because the count may have paused for instance because the beam was low. \item[iNoOfMonitors] is the number of monitors and counters this counter box supports. \item[lCounts] An array for storing the values of counters and monitors after counting. The PSI EL7373 counter box allows to read values only once after counting finished. This is why the values had to be cached in lCounts. \item[iPause] A flag which becomes true if the counter has been paused. \item[iErrorCode] A private variable holding error codes. \item[GetStatus] This function is called while upper level code polls for the counter to finish. It has to return the status of the counting operation and update the current value of the control variable in fControl. Possible return values are: \begin{description} \item[HWBusy] when counting. \item[HWIdle] when finished counting or idle. \item[HWNoBeam] when counting is halted due to lack of beam. \item[HWPause] if counting is paused. \item[HWFault] if the status cannot be obtained. \end{description} \item[Start] start counting in the count mode and with the preset previously confugured. Do NOT wait for counting to finish! \item[Pause] pause counting. \item[Continue] continue a paused counting operation. \item[Halt] stop counting. \item[ReadValues] read all counters and monitors into lCounts. \item[GetError] retrieves information about an error which occurred on the counter. An integer error code is returned in iCode. Up to iErrLen characters of descriptive error information is copied into error. This information is printed as error message by upper level code. \item[TryAndFixIt] Given an error code in iCode, try to repair the problem as far as this is possible in software. iError should be an error code as returned by GetError in iCode. This function has the following return codes: \begin{description} \item[COREDO] Problem fixed, try to redo the last the operation. \item[COTERM] The problem cannot be fixed in software. \end{description} \item[Set] set parameter name associated with counter iCter to fVal. \item[Get] return in fVal the value of parameter name associated with iCter. These two functions allow to set counter driver parameters. \item[Send] send pText to the counter controller and return iReplyLen characters of response from the counter controller in pReply. This is a bypass to set controller parameters manually. \item[KillPrivate] properly delete counter driver private data pData. Also close any connections to the hardware. \end{description} \section{Environment Controller Driver} This is the driver for all sample environment controllers, be it temperature controllers, magnet controllers etc. An environment controller driver is represented by the following data structure: \begin{verbatim} typedef struct __EVDriver { int (*SetValue)(pEVDriver self, float fNew); int (*GetValue)(pEVDriver self, float *fPos); int (*GetValues)(pEVDriver self, float *fTarget, float *fPos, float *fDelta); int (*Send)(pEVDriver self, char *pCommand, char *pReplyBuffer, int iReplBufLen); int (*GetError)(pEVDriver self, int *iCode, char *pError, int iErrLen); int (*TryFixIt)(pEVDriver self, int iCode); int (*Init)(pEVDriver self); int (*Close)(pEVDriver self); void *pPrivate; void (*KillPrivate)(void *pData); } EVDriver; \end{verbatim} All functions take a pointer to their own data structure as the first argument (self). They return 1 on success or 0 on failure if not stated otherwise. The fields: \begin{description} \item[SetValue] set fNew as the new set value for the device. It should start heating or cooling or whatever then. \item[GetValue] reads the current value from the device into *fPos. \item[GetValues] is used when the readout sensor and the control sensor are very different. This function then reads the current set value, the current position and calculates the difference between these value into fDelta. This function does not need to be defined, it is replaced by a standard one based on GetValue if not present. \item[Send] send a command in pCommand to the controller and returns at max iReplBuflen bytes of result in pReplyBuffer. This is breakout which allows to send arbitray data to the controller. \item[GetError] retrieves information about an error which occurred on the device. An integer error code is returned in iCode. Up to iErrLen characters of descriptive error information is copied into pError. This information is printed as error message by upper level code. \item[TryAndFixIt] Given an error code in iError, try to repair the problem as far as this is possible in software. iError should be an error code as returned by GetError in iCode. This function has the following return codes: \begin{description} \item[DEVREDO] Problem fixed, try to redo the last the operation. \item[DEVFAULT] The problem cannot be fixed in software. \end{description} \item[Init] initializes a connection to a controller and puts the thing into the right mode. \item[Close] closes a connection to a controller. \item[pPrivate] A pointer to a driver specific data structure which can be filled with meaning by instances of drivers. \item[KillPrivate] a function which has to release all memory associated with pPrivate. \end{description} \section{Histogram Memory Drivers} Histogram memories are devices in which neutron events for area detector or time--of--flight detectors are assigned to their correct bins. Then these usually large data sets have to be transferred to SICS for further processing. In SICS, histogram memories are also able to do count control, i.e. count until a preset monitor or time is reached. This gives a slightly complicated driver interface. If this assumption does not hold there are two options: \begin{itemize} \item Pass in a counter as a configuration parameter and chain count control to this counter. \item Make the count control functions dummies and let HMControl do the rest. See hmcontrol.h and .c for details. \end{itemize} Though never used so far the histogram memory driver has support for multiple banks of detectors being controlled by one histogram memory. A histogram memory driver is implemented by filling in the data structure given below: \begin{verbatim} typedef struct __HistDriver { pHMdata data; /* counting operations data */ CounterMode eCount; float fCountPreset; /* status flags */ int iReconfig; int iUpdate; /* interface functions */ int (*Configure)(pHistDriver self, SConnection *pCon, pStringDict pOpt, SicsInterp *pSics); int (*Start)(pHistDriver self, SConnection *pCon); int (*Halt)(pHistDriver self); int (*GetCountStatus)(pHistDriver self, SConnection *pCon); int (*GetError)(pHistDriver self, int *iCode, char *perror, int iErrlen); int (*TryAndFixIt)(pHistDriver self, int iCode); int (*GetData)(pHistDriver self, SConnection *pCon); int (*GetHistogram)(pHistDriver self, SConnection *pCon, int i, int iStart, int iEnd, HistInt *pData); int (*SetHistogram)(pHistDriver self, SConnection *pCon, int i, int iStart, int iEnd, HistInt *pData); long (*GetMonitor)(pHistDriver self, int i, SConnection *pCon); float (*GetTime)(pHistDriver self, SConnection *pCon); int (*Preset)(pHistDriver self, SConnection *pCon, HistInt iVal); int (*Pause)(pHistDriver self, SConnection *pCon); int (*Continue)(pHistDriver self, SConnection *pCon); int (*FreePrivate)(pHistDriver self); void *pPriv; } HistDriver; \end{verbatim} All functions take a pointer to their driver data structure as an argument. If not stated otherwise they return 1 on success, 0 on failure. \begin{description} \item[data] Is a pointer to an HMdata object which does all the dimension handling, buffers histogram memory content, deals with time binnings etc. \item[eCount] A counter mode, as defined above for counters. \item[fCountPreset] A preset for either monitor or time. \item[iReconfig] A flag which will be set by upper level code when a reconfiguration is necessary. \item[iUpdate] a flag which invalidates the buffered histogram. Should be set 1 in each call to GetCountStatus. \item[Configure] configures the histogram memory to the specifications given in the fields of the HMdriver structure. Further driver specific information can be read from the options dictionary passed in. Configuration options are stored in the string dictionary pOpt. This dictionary holds name value pairs which must be interpreted by this routine. Then configure has to configure the histogram memory according to the options passed in. \item[Start] starts a counting operation according to the current settings of the counter mode parameters. \item[Halt] implements an emergency stop of a counting operation. \item[GetCountStatus] serves to monitor the status of the counting operation. Possible return values to this call are: \begin{itemize} \item HWBUSY when still counting. \item HWNoBeam when the monitor is to low. \item HWIDLE or OKOK when nothing is going on. \item HWFault when there is an error on the device. \end{itemize} \item[GetError] will be called whenever an error has been detected on the device. The task is to put an internal error code into the iCode parameter. The string parameter perror will be filled with a text description of the error. But maximum iErrlen characters will be transferred to the error string in order to protect against memory corruption. Therefore iLen must be the maximum field length of error. \item[TryAndFixIt] is the next function called in case of an error on the device. Its second parameter is the internal code obtained in the ICode parameter of the call to GetError. The task of this function is to examine the error code and do whatever is possible in software to fix the problem. TryAndFixIt returns one of the following values: \begin{itemize} \item COREDO when the error could be fixed, but the upper level code will need to rerun the command which failed. \item COTERM when the software is unable to fix the problem and a real mechanic with a hammer is needed (or somebody able to reboot!). \item MOTOK when the error was fixed and nor further action is necessary. \end{itemize} \item[GetData] transfers all the data collected in the HM into the host computers memory buffer. \item[GetHistogram] copies data between iStart and iEnd from histogram bank i into the data space pData. Please make sure that pData is big enough to hold the data. \item[SetHistogram] presets the histogram bank i with the data given in lData. A conversion from different binwidth to long is performed as well. iStart and iStop define the start and end of the stretch of histogram to replace. \item[GetMonitor] returns the counts in the monitor i. GetMonitor returns a negative value on error. The error will have been printed to pCon. \item[GetTime] returns the actual counting time. \item[Preset] initializes the histogram memory to the value given by iVal. \item[Pause] pauses data collection. \item[Continue] continues a paused data collection. \item[FreePrivate] will be called automatically by DeleteHistDriver and has the task to remove the private data installed by implementations of an actual histogram memory driver. \item[pPriv] is a pointer which a actual histogram memory driver may use to hold a driver specific data structure. \end{description} \section{Velocity Selector Driver} This is a driver for velocity selectors as used at SANS machines. A velocity selector is a kind of turbine which selects wavelength through rotation speed. Though it rotates fast it is not a chopper, which are handled in SICS through the general controller driver described below. The velocity selector driver data structure includes: \begin{verbatim} typedef struct __VelSelDriv { void *pPrivate; void (*DeletePrivate)(void *pData); float fTolerance; int (*Halt)(pVelSelDriv self); int (*GetError)(pVelSelDriv self, int *iCode, char *pError, int iErrlen); int (*TryAndFixIt)(pVelSelDriv self, int iCode); int (*GetRotation)(pVelSelDriv self, float *fRot); int (*SetRotation)(pVelSelDriv self, float fRot); int (*GetStatus)(pVelSelDriv self, int *iCall, float *fCur); int (*GetDriverText)(pVelSelDriv self, char *pText, int iTextLen); int (*GetLossCurrent)(pVelSelDriv self, float *fLoss); int (*Init)(pVelSelDriv self, SConnection *pCon); }VelSelDriv; \end{verbatim} All functions take a pointer to their driver data structure as an argument. If not stated otherwise they return 1 on success, 0 on failure. The fields: \begin{description} \item[pPrivate] a pointer to a driver private data structure. \item[DeletePrivate] a function which releases any memory associated with pPrivate. DeletePrivate is called with a pointer to the driver private data pPrivate as argument. \item[fTolerance] This driver assumes it has reached the target speed if the speed difference target speed - read speed is less then this tolerance value for four consecutive times. \item[Halt] stops the velocity selector. \item[GetError] returns an error code in *iCode and iErrlen bytes of textual description of the last error on the velocity selector in pError. \item[TryAndFixIt] tries to fix the error defined through iCode. iCode should be the value as returned in *iCode in GetError. This function returns: \begin{description} \item[VELOREDO] redo last operation, error fixed. \item[VELOFAIL] cannot fix the error from software. \end{description} \item[GetRotation] reads the rotation speed into *fRot. \item[SetRotation] sets a new rotation speed fRot for the selector. Do NOT wait until finished. \item[GetStatus] is used to poll for the status of the last driving operation. It returns: \begin{description} \item[VSACCEL] when the velocity selector is accelerating. \item[VSFAIL] if the status cannot be read. \item[VSOK] when the velocity seelctor has reached its target speed. \end{description} The current rotation speed is returned in *fCur. iCall is a value which indicates in which state the selector is: \begin{description} \item[ROTMOVE] normal running. \item[ROTSTART] starting the velocity selector. The Dornier velocity selector have a certain minimum speed. If they are standing they have to be started first. \end{description} \item[GetDriverText] returns iTextLen bytes of additional status information in pText. This is a list name: value pairs separated by commata. This is meant to hold additional selector readouts such as vacuum states, temperatures etc. \item[GetLossCurrent] initiates a measurement of the loss current of the velocity selector. The result is returned in *fLoss. \item[Init] initiates a connection to a velocity selector. \end{description} It may be possible that this driver is not very general. It was developed for Dornier velocity selectors because these were the only one seen. \section{General Controller Driver} This is driver for a SICS general controller object. SICS sports a general controller object which allows to read a selection of parameters and to set some parameters. Adapters exists which implement the driveable or environment interface for parameters in such a controller. The parameters supported are part of the drivers interface. This scheme is currently used to control choppers in SICS, but it is not restricted to this usage. The driver: \begin{verbatim} typedef struct __CODRI { int (*Init)(pCodri self); int (*Close)(pCodri self); int (*Delete)(pCodri self); int (*SetPar)(pCodri self, char *parname, float fValue); int (*SetPar2)(pCodri self, char *parname, char *value); int (*GetPar)(pCodri self, char *parname, char *pBuffer, int iBufLen); int (*CheckPar)(pCodri self, char *parname); int (*GetError)(pCodri self, int *iCode, char *pError, int iErrLen); int (*TryFixIt)(pCodri self, int iCode); int (*Halt)(pCodri self); char *pParList; void *pPrivate; }Codri; \end{verbatim} All functions take a pointer to their driver data structure as an argument. If not stated otherwise they return 1 on success, 0 on failure. The fields: \begin{description} \item[Init] initializes a connection to the controller and the driver. \item[Close] closes the connection to a controller. \item[Delete] releases all memory associated with this drivers private data structure pPrivate. \item[SetPar] sets the parameter parname to a new value fValue. \item[SetPar2] same as SetPar but with new value as text. \item[GetPar] read the current value of parname into pBuffer. The value is formatted as text. At max iBufLen bytes are copied into pBuffer. \item[CheckPar] checks the progress of setting parname. CheckPar returns: \begin{description} \item[HWFault] If there is a fault in the hardware or the status cannot be read. \item[HWBusy] The parameter is still driving. \item[HWIdle] The parameter has finished changing and is idle. \end{description} \item[GetError] returns an error code in *iCode and iErrlen bytes of textual description of the last error on the velocity selector in pError. \item[TryAndFixIt] tries to fix the error defined through iCode. iCode should be the value as returned in *iCode in GetError. This function returns: \begin{description} \item[CHREDO] redo last operation, error fixed. \item[CHFAIL] cannot fix the error from software. \end{description} \item[Halt] stop any driving parameters. \item[pParList] a comma separated list of parameters supported by this driver. \item[pPrivate] a driver private data structure. \end{description}