WHT-ISIS-9

WHT ISIS Polarisation Module
VAX Software Manual

A.N. Johnson

Issue 1.1; 23 August 1996




Royal Greenwich Observatory,
Madingley Road,
Cambridge CB3 0HJ

Telephone (01223) 374000
Fax (01223) 374700
Internet anj@ast.cam.ac.uk

1 Introduction

This document is the Software Manual for the VAX ADAM I-task of the new ISIS Polarisation Unit. It contains medium-level descriptions of the various software modules which together implement the ISISP I-task.

1.1 Intended Readership

1.2 Applicability

This document applies to versions V0-2 and V0-3 of the ISISP software.

1.3 Purpose

This document is intended to be used as an introduction to the ISISP software and to the DUA and IMI modules which have been written to be useable in other projects. It does not describe the workings of every subroutine (many are not even mentioned in this text), but covers the overall structure of the software, and includes some additional examples (not proven ones though) of how to use the library routines.

1.4 How to use this document

The remainder of the document consists of three sections which describe the DUA and IMI subsystems and the application-specific code within ISISP. The ISISP section assumes some familiarity with DUA and IMI, thus these sections should be read first.

1.5 Related documents

[1] WHT-ISIS-4 WHT ISIS Polarisaion Module User Requirements Document,
V.E. Austin, Issue 1.1, 28 July 1995.
[2] WHT-ISIS-6 WHT ISIS Polarisation Module Vax Software Design Document,
A.N. Johnson, Issue 1.3, 23 August 1996.
[3] WHT-ISIS-10 WHT ISIS Polarisation Module System Acceptance Test Specification,
Frank Gribbin et al., Issue 0.4, 14 June 1996.
[4] WHT-ISIS-11 WHT ISIS Polarisation Module System Acceptance Test Report,
Frank Gribbin and Stuart Barker, Issue 1.1, 22 June 1996.

2 DUA - DRAMA Under ADAM

2.1 Module Purpose

The DUA module was created to enable VAX ADAM applications to be written so they would need less restructuring if ported to DRAMA at a later date. DUA does not attempt to implement more than those parts of the DITS module which were needed for the ISIS Polarisation I-task. It does not attempt to emulate the DRAMA argument or parameter systems, and only provides the central core of DITS (the DITS calls it emulates were taken from an early version of the DITS documentation and although they are still valid under DITS there are now enhanced interface calls available). DUA may be used in other projects, it is not specific to ISIS Polarisation at all.

2.2 Source Files

DUA consists of three files:

The source must be compiled with the DEC ANSI (DECC) compiler, and the resulting object files linked with the standard ADAM I-task libraries.

2.3 DUA Applications

A DUA application must contain the normal ADAM I-task routines void devinit(int *status) and void taskname(int *status), however the content of these routines is likely to be fairly minimal. The main structure of the application including all the actions it supports is defined in an array of DuaActionMapType structures, each of which describes a different action which the task supports. Within each structure are a pair of pointers to routines within the application which implement the action obey and cancel (kick) operations. The structure includes a string defining the action name (maximum 15 characters plus the closing '\0'), and there is also an integer code which can be used by the application to distinguish between actions if the same OBEY routine is used for more than one action.

The DuaActionMapType array must be registered with DUA within the application's devinit() routine by calling DuaPutActionHandlers(count, actions, status) where count gives the number of entries in the actions array. Unlike in DRAMA, it is only possible to call this routine once in an application (if called more than once the earlier actions will be lost), and the action array must remain in existence for the whole life of the task. A simple example of an action map which implements four actions is:

DuaActionMapType actionMap[] = {
    initObey,  NULL,        0, "INITIALISE",
    countObey, countCancel, 0, "COUNT",
    pingObey,  NULL,        0, "PING",
    exitObey,  NULL,        0, "EXIT",
}

int actionCount = sizeof(actions) / sizeof(actions[0]);
This actionCount definition is useful as it works out the number of actions in the table automatically. In this case only the count action has an associated cancel routine; NULL is used here for the other actions. The user code value is not used in this application.

For a simple application, the task initialisation routine which uses this action map would be:

void devinit(int *status) {
    if (!StatusOkP(status)) return;
    
    DuaInit(status);
    DuaPutActionHandlers(actionCount, actionMap, status);
}
The recommended method of testing the inherited status under DRAMA is to use the StatusOkP(status) macro (the P stands for predicate), which returns TRUE if the status is still good, false if there is an error status present.

When an ADAM action message is received, the ADAM fixed part will call the taskname() routine, which should then pass control immediately on to DUA's DuaDispatchAction(). This routine will fetch the action name, context and reason from the fixed part and then call the relevant application obey or cancel handler. For our simple application, this routine is:

void simple(int *status) {
    if (!StatusOkP(status)) return;
    
    DuaDispatchAction(status);
    return;
}

2.4 Action Handlers

The application must contain the four action routines named in the action array. The simplest of these are for the ping and exit actions:

void pingObey(StatusType *status) {
    if (!StatusOkP(status)) return;
    
    return;
}
void exitObey(StatusType *status) {
    if (!StatusOkP(status)) return;
    
    printf("SIMPLE: Exiting.\n");
    exit(0);
}
Although not necessary under DUA, there is a DRAMA convention that every task should support both ping (which does nothing but always completes successfully) and exit (which under DRAMA is the method by which a task is shut down). As with ADAM, an action completes if returns with an error status, or if it has not requested any kind of reschedule and it returns a good status (STATUS__OK). It should also check the inherited status on entry - note that within DUA and DRAMA the inherited status is a StatusType instead of an int, although the two are synonymous.

The obey handler for the initialisation action, which resets a counter to zero is:

int counter;

void initObey(StatusType *status) {
    if (!StatusOkP(status)) return;
    
    printf("SIMPLE: Initialising...\n");
    counter = 0;
    return;
}

2.5 Task Rescheduling

If an action cannot complete immediately but needs to wait for some external interaction, it can ask to be rescheduled at some time in the future using the procedure call DuaPutRequest(DUA_REQ_WAIT, status) and returning.

When in this waiting state there are two means by which the action can be rescheduled. The first is after a delay, setting the delay time in milliseconds with DuaPutDELAY(time, status) (the name capitalisation is inconsistent because DuaPutDELAY is not directly compatible with the equivalent DRAMA routine - DitsPutDelay() takes a more complicated delay time parameter in the form of a structure).

The other method of rescheduling an action is for another action within this task to send it a signal by passing the action name into a call to DuaSignal(actionName, value, status). The target action must be waiting to be rescheduled for it to be able to receive this signal. It is possible to use both reschedule methods simultaneously, the delay acting as a timeout on the external event.

When an action is rescheduled, the obey routine is called again. The routine can find out if this is a rescheduled action by examining the value returned by the macro DuaGetReason() which is DUA_REA_RESCHED if the reschedule is due to the delay timeout completing, or DUA_REA_ASTINT if the reschedule is due to a signal from another action.

It is possible to request DUA to call a different routine when an action reschedules instead of the original obey routine. This is done by giving the new routine name in a call to DuaPutObeyHandler(routine, status) before the action returns. The new routine will be used for further reschedules of this action, but if the action completes and is re-run the original handler given in the action map structure will be called again.

For our simple example, the count action will instigate a 5 second delay, then increment and print the counter and exit. The behaviour after the delay is coded into a different routine:

void increment(StatusType *status) {
    if (!StatusOkP(status)) return;
    
    counter++;
    printf("SIMPLE: Counter = %d\n", counter);
    return;
}
void countObey(StatusType *status) {
    if (!StatusOkP(status)) return;
    
    /* First set the routine to be called on reschedule */
    DuaPutObeyHandler(increment, status);
    
    /* Then request a reschedule in 5 seconds */
    DuaPutRequest(DUA_REQ_WAIT, status);
    DuaPutDELAY(5000, status);
    
    return;
}
The count action also supports the cancel (kick) operation, which will stop the count from occurring if received during the 5 second delay period when the action is outstanding. The routine to implement this looks as follows:

void countCancel(StatusType *status) {
    if (!StatusOkP(status)) return;
    
    DuaPutRequest(DUA_REQ_CANCEL, status);
    return;
}
If it is desirable to change the cancel (kick) handler routine during certain critical parts of the action the routine DuaPutKickHandler(routine, status) can be used for this.

2.6 Action Information

Various routines and macros are provided to allow an action to discover contextual information. DuaGetReason() has already been described above. DuaGetContext() returns either DUA_CTX_OBEY or DUA_CTX_KICK depending on whether this is an obey or cancel operation. DuaGetSeq() returns the sequence number for this action (i.e. a count of how many times the action has rescheduled, which starts at zero for the initial obey call). DuaGetCode() allows the routine to fetch the user-defined integer code number from the DuaActionMap entry for the current action.

The DuaPutRequest routine which has already been introduced above can take three possible request values: DUA_REQ_WAIT was used above to cause the action to reschedule after some delay or as a result of a DuaSignal to this action; DUA_REQ_REQ_END explicitly signals that the action has completed, and DUA_REQ_CANCEL indicates that the action is cancelled (Note this latter request is necessary with DUA because ADAM requires it, but the DRAMA DITS_REQ_CANCEL does not exist).

2.7 DUA Internals

In addition to the DuaActionMap array which has already been described, DUA maintains two parallel arrays DuaObeyHandlerMap and DuaKickHandlerMap which hold the obey and cancel routine pointers for each action which have been set using DuaPutObeyHandler and DuaPutKickHandler - these arrays allow several actions to be running simultaneously, although there can be at most one instance of any particular action active at any one time.

When DuaDispatchAction is called it requests the action name from ADAM and looks this up in the DuaActionMap array. When it has located the correct entry, it also requests the sequence number, reason code and context code from ADAM and stores these in the data structure DuaCurrentAction. If the sequence number is zero, the DuaObeyHandlerMap and DuaKickHandlerMap array entries for this particular action are set from the DuaActionMap entry. Finally the application's obey or kick handler is called, depending on the context code received.

3 IMI - The ING Mechanism Interface

3.1 Module Purpose

The IMI module was written as part of the ISIS Polarisation I-task as a general-purpose interface between an I-task and the standard EPICS mechanism interface developed for WYFFOS and also used for ISISP control. IMI is actually a collection of three separate interface modules and some additional miscellaneous routines:

3.2 Source Files

IMI consists of seven files:

  1. imi.c
  2. This contains the C source code of the main IMI routines including the Noticeboard and MIMIC interfaces.

  3. imi_ca.c
  4. The Channel Access routines in this file may be used independently of imi.c

  5. imi_cf.c
  6. The Configuration Database routines in this file are also stand-alone.

  7. imi.h
  8. This is the header file for inclusion by applications which use all the facilities of IMI.

  9. imi_ca.h
  10. Applications which only require the Channel Access routines can use this header file.

  11. imi_cf.h
  12. Applications which only require the Configuration Database should use this header.

  13. imi_errs.msg
  14. This is the VAX error message code source file, which must be converted to imi_errs.h before compiling IMI or any application which uses it.

The source must be compiled with the DEC ANSI (DECC) compiler, and the resulting object files linked with the standard ADAM I-task libraries, the EPICS Channel Access library, the MIMIC interface library and the noticeboard interface libraries NBU and NBS_SHR.

3.3 IMI_CA - Channel Access Wrappers

These routines are provided for convenience both within IMI and for applications using it. All these routines have names beginning imiCa and include the ADAM inherited status pointer as their last parameter. Most of the common Channel Access routines are supported; the exceptions are generally for access to arrays or synchronous groups, and were not required for ISISP. Some understanding of the standard Channel Access client library is useful; this section is not intended to explain or document the EPICS code.

3.3.1 Subroutine Reference

In the routine descriptions below, the routine arguments use the following notation:

A argType inputArgName

This is an input argument to the routine. If the argument is a pointer, the object it points to will not be modified in any way.

" argType outputArgName

This is an output argument from the routine. It must actually be a pointer to a location where the return value is to be placed. The contents of this location on entry to the routine are not important.

A" argType bidirectionalArgName

This is a bidirectional argument. It is a pointer to a location which may contain some input to the routine and might be modified to return a value to the caller.


imiCaInitialise(status)

A" StatusType *status

ADAM inherited status

This routine allows the Channel Access library to initialise any internal information when a client program starts running. Calling this routine is not essential as the other routines will cause it to be run if they are called first.


imiCaProcess(wait_time, status)

A float process_time

Time in seconds to spend processing Channel Access events

A" StatusType *status

ADAM inherited status

This routine is the Channel Access equivalent of the ADAM main loop, and is required to allow communication between the calling task and the EPICS IOCs it controls. When called, it allows the EPICS ca_pend_event routine the given time to process network messages and perform any application call-backs.


imiCaSearch(mechname, varname, pchannel, status)

A const char *mechname

Zero-terminated string containing mechanism name

A const char *varname

Zero-terminated string containing control variable name

A" chid *pchannel

Location to hold returned channel identifier

A" StatusType *status

ADAM inherited status

IMI was built for instruments having three-part channel-access names which take the form instrument:mechanism:variable. This routine constructs the full name from three components before passing it to ca_search to make the necessary channel connection to the IOC. The first part - the instrument name including the trailing colon - must be provided by the application in a zero-terminated string named pvPrefix. It will be impossible to link the application code into an executable image without this. The second and third components of the name are provided as input parameters to the routine - note that the control variable name should include a leading colon, as the full name is just the concatenation of these three strings. If the channel names do not match this convention, any components not required should be defined as zero-length strings, not NULL pointers.

If pchannel points to a valid channel identifier on entry, this will be cleared by passing it to ca_clear_channel before connecting to the given channel name.

When imiCaSearch is called for the first time after a task has started it opens its connection to the network. Under VMS the task must have OPER privilege to be able to perform UDP broadcasts, and without this privilege ca_search will return with the error ECA_NOCAST. The routine checks for this particular common error as it is possible to inform the user how to fix it. All errors set status to IMI__CAERR.

Connection requests are queued, and the returned channel identifier is only of limited use before the channel connects. Use imiCaPendIo to wait for the connection to be made.


imiCaGet(type, channel, pvalue, status)

A long type

A DBR_XXXX constant from the EPICS db_access.h header file which defines the data type requested for the return value.

A chid channel

The channel identifier which indicates the source of the data.

" void *pvalue

The buffer to use for the fetched data value. This must be large enough to hold the requested data type.

A" StatusType *status

ADAM inherited status.

The data fetch request is passed to ca_get and any errors reported, setting status to IMI__CAERR. The returned data will not be valid until after the next successful call to imiCaPendIo.


imiCaGetCallback(type, channel, pfunc, userarg, status)

A long type

A DBR_XXXX constant from the EPICS db_access.h header file which defines the data type requested for the return value.

A chid channel

The channel identifier which indicates the source of the data.

A void (*pfunc)(struct event_handler_args)

Pointer to a function to call when the requested data is received from the IOC.

A void *userarg

An application pointer which can be read by the callback routine to determine its context.

A" StatusType *status

ADAM inherited status.

The data fetch request is passed to ca_get_callback and any errors reported, setting status to IMI__CAERR. When the returned data arrives, the function is called with a structure which includes the returned data and the user's pointer. Note that the callback will be run within the context of an ADAM action running imiCaProcess or imiCaPendIo, which may be different from the action which called imiCaGetCallback.


imiCaPut(type, channel, pvalue, status)

A long type

A DBF_XXXX constant from the EPICS db_access.h header file which defines the data type of the given data.

A chid channel

The channel identifier which indicates the destination for the data.

A void *pvalue

The buffer containing the data to be sent.

A" StatusType *status

ADAM inherited status.

The data send request is passed to ca_put and any errors reported, setting status to IMI__CAERR. The data may be queued, and can also be rejected by the IOC if it is not possible to convert the given data into something which can be understood by the receiving record.


imiCaMonitor(type, channel, pevent, pfunc, userarg, status)

A long type

A DBR_XXXX constant from the EPICS db_access.h header file which defines the data type requested for the return value.

A chid channel

The channel identifier which indicates the source of the data.

A" evid *pevent

Location to hold an event identifier for this monitor. NULL may be used if the event identifier is not required.

A void (*pfunc)(struct event_handler_args)

Pointer to a function to call when notice of a significant change to this channel is received from the IOC.

A void *userarg

An application pointer which can be read by the callback routine to determine its context.

A" StatusType *status

ADAM inherited status.

If pevent points to a valid event identifier, this event is cleared by passing it to ca_clear_event. The monitor request is forwarded to ca_add_event and any errors reported, setting status to IMI__CAERR. Whenever the value being monitored changes beyond any monitor deadband level, the IOC will send notification of this, and the application's callback routine will be called with the new value. Note that the callback will be run within the context of an ADAM action running imiCaProcess or imiCaPendIo, which may be different from the action which called imiCaMonitor.


imiCaConnection(channel, pfunc, status)

A chid channel

The channel to be monitored.

A void (*pfunc)(struct connection_handler_args)

Pointer to a function to call whenever the connection status of the given channel changes.

A" StatusType *status

ADAM inherited status.

The function pfunc is registered as the connection handler for the given channel by calling ca_change_connection_event and any errors reported, setting status to IMI__CAERR. Whenever this channel disconnects or reconnects, the function will be called with notification of the channel involved and what occurred.


imiCaPendIo(timeout, status, format, ...)

A float timeout

Number of seconds to wait for I/O to complete.

A" StatusType *status

ADAM inherited status.

ca_pend_io is called with the given timeout period. If the outstanding I/O operations are not all completed within the given period an error will occur. In this case, the error is reported to the user and status set to IMI__CAERR.

3.4 IMI_CF - Configuration Database Module

The Configuration Database routines are designed to simplify the task of keeping instrument configuration data in a human-readable disk file and accessing and modifying this data easily under program control. The disk file format used is based on the MS-Windows .INI file, which allows data items to be named and separated into different sections, and comments can also be included for humans to read. In use, the file is read into memory in its entirety, the memory version modified by the application as necessary and the updated database written back to disk. Data are stored as strings, thus the application can record any data type by suitable conversion.

3.4.1 Data Formats
3.4.1.1 On Disk

The disk file is structured with one item per line. Blank lines and lines containing only whitespace characters are ignored and will be discarded. There are four different types of items:

Comments begin with a semi-colon ; and may be followed by any amount of text up to the end of the line. The semi-colon may be indented using any combination of whitespace characters (space or tab), and the indentation will be retained when the file is loaded and saved back to disk.

Section headers have the section name given between square brackets [section name]. The name may contain spaces, although those next to the brackets will be stripped, as will any whitespace before the open bracket and any characters after the close bracket. The application will not be able to match a section name if it includes spaces at either end of the match string. A section ends immediately before the next section header or the end of file.

Within each section are any number of data and/or flag items. Data items take the format item name = string value. The item name may contain spaces, although as with the section name any leading or trailing spaces will be stripped. The first equals sign on the line is taken as the separator between the item name and its string value, thus the string may contain equals characters but the name may not. Although these routines do not mandate any other restrictions on names (other than not starting with an open square bracket or a semi-colon), it is recommended that names be kept to alpha-numerics, spaces and the symbols _$.:(). The string value is less restricted, but should only contain printable characters and may not incorporate an end-of-line. If spaces are required at the leading or trailing end of the string value, the application should enclose the string within suitable quotation characters and must strip these quotes from the value itself.

Flag items are essentially data items without an associated value. The equals sign must be omitted. They can be used to signal a Boolean value by their presence or absence within the section.

The following is an example of a database on disk:

;Example Configuration Database

[section 1]
; This is a comment.
data item = string value
flag item

[section 2]
another flag item
data item = different string value

1.4.1.2 In Memory

When a database is loaded into memory, it exists as a number of linked records, arranged in a comb structure with sections along the spine and data items, flags and comments down the teeth. Multiple databases can be loaded at the same time; the application only needs to keep track of a single cfid for each loaded database, which is used as the basis for all search and modification operations. All records use the same C structure which contains a name string, an enumerated type to indicate what this record stores, a forward link to the next record in the list, and a union to hold either the string value of a data item or comment, or a pointer to the first data item within a section

.

The linked-list memory structure is illustrated in the following diagram:

This does not show the names or values associated with each record, but is intended to illustrate how the memory database relates to the disk file. The above structure would be created when the disk file example given above was read into memory. Note that the two data items were given the same name, but will not be confused as they are in different sections of the database. It is possible to include comments, data items or flags before the first section header and they will be retained, but this approach is not recommended for data as it is not possible to create new items here under program control.

3.4.2 Database Applications
3.4.2.1 Application Structure

An application may use the Configuration Database routines in several different ways, but the following two are recommended:

1. Load the database into memory at the start of the program. Information can be extracted from the database whenever it is needed, and as configuration changes are made the memory database is updated. The database is saved when desired, or as the program exits.

2. Load the database into memory at the start of the program, and extract all of the desired information from the database into other data structures, then delete the database from memory. Whenever the database is to be saved, load the disk version into memory again, update those configuration items which have changed and save the modified database to disk, finally deleting the database again.

The former approach requires the database to remain in memory during the whole period while the program is running, but will be slightly faster as the disk file is only read once, at the beginning. The latter approach only uses memory for the database during loading and saving operations, and does not require the program to record the location of the database in memory outside of these periods; it also permits several programs running simultaneously to keep their information within different sections of the same file (provided the file is locked before the load operation when it is about to be updated and unlocked after being re-written; under VMS this may be impossible due to the version numbering system).

With the second approach it is possible to create a new database in memory from the other data structures and save it to disk without loading the original database file, but if this is done any comments in the file will be lost. Comments can only be created by hand-editing the database file, and cannot be extracted from the database when it is in memory. This technique should be reserved for those situations when there is no disk file available - say when the program is first run, or if the existing disk file is corrupt.

3.4.2.2 Simple Example

A drinks machine needs to store the drink name and price for a number of different kinds of beverage which it can dispense. This information could be stored in a configuration file which is read whenever the machine is turned on and updated when the drinks are changed. The code to read the database into memory would look like this:

cfid database = imiCfRead(CONFIG_FILENAME, status);

if (!StatusOkP(status)) {
    imiCfDelete(database, NULL);
    database = NULL
    return;
}
If the read fails, imiCfRead will return as much of the database as it has read so far. In this case, we regard any faults as fatal, freeing up the memory used by deleting the database and stopping further processing.

With the database in memory we now need to extract information from it about the drinks loaded in each dispenser. This information we will place into an array of structures for use elsewhere in the program.

struct {
    char *drink_name;
    int drink_price;
} dispenser[NUMBER_DISPENSERS];
Each dispenser has its own section within the database. We start by looping through all of the dispensers in turn, getting an identifier for each section as we come to it.

for (index = 0; index < NUMBER_DISPENSERS; index++) {
    char section_name[40];
    cfid section;
    char *price;
    
    sprintf(section_name, "dispenser %d", index);
    section = imiCfSearch(database, section_name);
    if (section == NULL) {
        *status = BAD_DATABASE;
        return;
    }
Still within the loop, we now fetch the name and price information from this section, and store the result in the array:

    dispenser[index].drink_name = imiCfGet(section, "drink");
    price = imiCfGet(section, "price");
    if (dispenser[index].drink_name == NULL || price == NULL) {
        *status = BAD_DATABASE;
        return;
    }
    dispenser[index].drink_price = atoi(price);
}
The price information needs to be stored as an integer but imiCfGet returns a string, thus we have to convert this using atoi, after checking that the database actually contained the information we asked for.

After a change to the price or type of drink available from the machine, the configuration file must be updated. Assuming that the dispenser array now contains the new information, we need to update the database and store this back to disk. We will use the same piece of code to create and populate a new database if there is no database present.

if (database == NULL) {
    database = imiCfCreate(NULL, CONFIG_FILENAME, status);
    if (database == NULL) return;
}
Having successfully created a database if it does not already exist, we loop through each dispenser:

for (index = 0; index < NUMBER_DISPENSERS; index++) {
    char section_name[40];
    cfid section;
    char price[20];
    
    sprintf(section_name, "dispenser %d", index);
    section = imiCfCreate(database, section_name, status);
If the routine imiCfCreate is called with a database identifier as its first argument as above, it checks if the given name already exists as a section, and if so it behaves exactly like imiCfSearch and returns the relevant section identifier. If the section name does not exist, a new section is created with that name and the new identifier returned. We finish the loop by creating the leaf data items in case they don't already exist, and putting the new values into the database

    (void) imiCfCreate(section, "drink", status);
    imiCfPut(section, "drink", dispenser[index].drink_name, status);
    
    (void) imiCfCreate(section, "price", status);
    sprintf(price, "%d", dispenser[index].drink_price);
    imiCfPut(section, "price", price, status);
}
The final job is to write the updated database to the disk file. We only do this if all of the above steps succeeded - any failures could mean that the database in memory is not complete or up to date:

if (!StatusOkP(status)) return;
imiCfWrite(database, CONFIG_FILENAME, status);
In a later version of the software it is necessary to add another data item to the database and the dispenser array, this being a flag to allow individual dispensers to be disabled. A dispenser is marked as unusable if the flag item disabled is found in the given section; in memory this is held as an enumerated type in a new member of the dispenser structure, now is declared as

struct {
    char *drink_name;
    int drink_price;
    enum {enabled, disabled} status;
} dispenser[NUMBER_DISPENSERS];
When reading the database from disk, we add the following code within the loop:

cfid disabled;

disabled = imiCfGet(section, "disabled");
if (disabled == NULL) {
    dispenser[index].status = disabled;
} else {
    dispenser[index].status = enabled;
}
To write the status into the database, we add the code

if (dispenser[index].status == disabled) {
    (void) imiCfCreate(section, "disabled", status);
} else {
    imiCfPrune(section, "disabled");
}
The call to imiCfPrune causes any item matching the given name to be deleted from the database; the item does not have to exist however. When a data item is pruned, all storage allocated for the item and any contents will be released back to the system. In the above example, the drink_name members of the dispenser array structures hold pointers to the strings stored within the database, rather than copying the string into an external buffer. This is legal, provided the database remains in memory for the whole duration of the program and is not modified elsewhere in the program.

3.4.3 Subroutine Reference

In the routine descriptions below, the routine arguments use the following notation:

. returnType

If the routine returns a function value, the return type is given here.

A argType inputArgName

This is an input argument to the routine. If the argument is a pointer, the object it points to will not be modified in any way.

" argType outputArgName

This is an output argument from the routine. It must actually be a pointer to a location where the return value is to be placed. The contents of this location on entry to the routine are not important.

A" argType bidirectionalArgName

This is a bidirectional argument. It is a pointer to a location which may contain some input to the routine and might be modified to return a value to the caller.


imiCfCreate(parentId, itemName, status)

. cfid

Identifier of the created or found item

A cfid parentId

Section or database identifier, or NULL to create a new database

A const char *itemName

Item or section name to create or find

A" StatusType *status

ADAM inherited status

Creates a new database, section or data item, depending on the value passed in parentId, and returns an identifier for the item. If the itemName string matches an item which already exists within parentId, no creation is performed and the identifier of the existing item is returned instead. The itemName string may not start with a semi-colon or open square bracket, and it is recommended that the other characters within the name be kept to alpha-numerics, space, and the symbols _$.:(). The name should not contain leading or trailing spaces, although these are acceptable within it.

In all cases, the new item is empty; for a data item, this means it is effectively a flag item, but it will be converted into a data item when it is given a value using imiCfPut.

It is not possible to create a data item within the top level of a database (i.e. before the first section header), although these can be manually inserted by editing the database file.

If memory for the item cannot be allocated, status will be set to the value of errno and NULL returned as the identifier.


imiCfRead(filename, status)

. cfid

Identifier of the database in memory

A char *filename

Disk file to be read

A" StatusType *status

ADAM inherited status

Creates a new database and reads the contents of the disk database given by filename into this database. The database identifier is returned to the caller. The disk database is not checked for duplicate names; these will be read in and remain in the database, but cannot be accessed or modified using the normal imiCf routines. The maximum length of a single input line from the file is 255 characters, including the zero string terminator and allowance may also need to be made for CR/LF at the end of the line.

If the given filename cannot be opened, NULL is returned with status set to IMI__NOFILE. If memory runs out or an I/O error occurs while reading the file, a database will be returned if at all possible containing as much data as could be read, with the status set to the system error value from errno. The value NULL is returned if the memory for the top-level database item could not be allocated.


imiCfWrite(databaseId, filename, status)

A cfid databaseId

Identifier of the database in memory

A const char *filename

Disk file to be read

A" StatusType *status

ADAM inherited status

Saves a complete database from memory into the disk file given by filename.

If the given filename cannot be opened for writing, status is set to IMI__OPENERR. If an I/O error occurs while writing the file, status will be set to the system error value from errno.


imiCfSearch(databaseId, match)

. cfid

Identifier of the matching section

A cfid database

Identifier of the database to be searched

A const char *match

Name of the section to find

The section names in the given databaseId are compared with the match string to find one which is identical (wildcards are not supported), and the identifier of the first matching section is returned. If no matching section can be found, the value of NULL is returned instead.


imiCfGet(sectionId, itemName)

. char *

Value string of item

A cfid sectionId

Section to search for item

A const char *itemName

Item name to find

Searches through the database section given by sectionId for a data or flag item which has the name itemName. When found, a pointer to the value string field of this item is returned. If no items within this section match the value NULL is returned. Flag items do not have a value associated with them, but they are guaranteed to return a non-NULL value which points to a zero-terminated character string; currently this string has zero length.

The calling application may store the returned character pointer rather than copying the data from it, but the buffer it points to may be freed if the item is modified using imiCfPut. Changing the database value by writing to the buffer directly using this pointer is strongly discouraged.


imiCfPut(sectionId, itemName, value, status)

A cfid sectionId

Section to search for item

A const char *itemName

Item name to find

A const char *value

String value to put into item

A" StatusType *status

ADAM inherited status

Searches for a data item within the section given by sectionId with the name itemName. If this item exists, the value associated with it is set to the string in value. This routine is also used to convert a flag item into a data item (by giving a non-NULL value string) or vice-versa (by passing NULL for value). A copy is made of the character string, which can be any length (however note the line length limitation in imiCfRead) but should not include control characters.

If the itemName cannot be matched within the given section, the routine returns with status set to IMI__NOCFITEM. If the system is unable to allocate memory for the new item, status is set from errno.


imiCfPrune(sectionId, match)

A cfid sectionId

Section to search for item

A const char *match

Item name to delete

Deletes a single data item from the section sectionId whose item name is identical to the match string. Can also be used to remove all items from the section by giving match a NULL value; this will not delete the section header, but all data items and comments will be removed from within it. No error occurs if there are no items within the section with the given name.


imiCfDelete(databaseId, match)

A cfid databaseId

Database to search for section

A const char *match

Section name to delete

Deletes a whole section from the database databaseId whose section name is identical to the match string. Can also be used to delete the whole database by giving match as NULL. When a section is deleted, all of its data items are freed as well. No error occurs if there are no sections with the given section name.

3.5 IMI - ING Mechanism Interface

The ING Mechanism Interface module is intended to be a standard library which provides services to interface an ADAM (and eventually a DRAMA) task to an EPICS system which has been produced to the command interface standard originally developed for WYFFOS and later adopted for the ISIS Polarisation module. The details of this command interface are described in the document WHT-ISIS-XX but are summarised below for ease of understanding of the IMI software. The routines provided by the module are designed to allow flexibility of design of the underlying application while still providing facilities to reduce the amount of programming for most applications to a minimum.

The IMI routines use Guy Rixon's TALK module from the RAT subsystem to output messages, and also call NBUC for interfacing to the ADAM noticeboard and the MIMIC for graphical status.

3.5.1 EPICS Command Interface

The command interface is the specification of the communications data and protocol which are used as inputs to the EPICS IOC to cause mechanisms to move and allow the current status of the mechanisms and their commands to be reported back to the higher level software. The components of this interface and their behaviour must be defined explicitly for each application, within limits which are given as part of the command interface specification. The IMI software should allow any interface within this specification to be controlled without having to write a significant amount of Channel-Access client software.

The command interface requires the instrument to be divided up into a number of individually controllable mechanisms. These can interact with each other as necessary, but the details of such interaction are outside of the remit of the command interface and may require software interlocks to be created within the EPICS application. A mechanism may fall into one of three different types:

Each of these provides a superset of the capabilities of the previous mechanism type, and these are described in order below.

All instruments are required to use Channel Access names which are divided into three parts:

These parts are separated by a colon (:), for example isis:slwidth:current. EPICS enforces a maximum length of 31 characters for the complete name, so some abbreviation of the mechanism names will probably be necessary. The case of the name may be important. Those records which are used within an application but are not part of the external interface do not have to follow this convention, but must all start with the instrument name part to distinguish them from similar records in other applications which may be running simultaneously.

3.5.1.1 Position Mechanisms

The Position mechanism is the simplest interface type, and is used for those mechanisms which just provide a single piece of positional information to the higher level application. No control is possible of these mechanisms, and they never need to signal warnings or errors. Examples of this kind of mechanism are not particularly common as there is usually a requirement to notify the user if a sensor value goes outside of its normal range, or if an instrument door is opened say, but this interface type is useful in conjunction with other mechanisms to provide additional position data, or for mechanisms whose controll is derived automatically from another command interface. The ISISP waveplate detent clamps are position mechanisms.

A position mechanism interface provides a single EPICS record giving the current position or state being reported, with the name instrument:mechanism:current. This may be updated at any time. The record type may be any non-array type, and will be monitored by IMI as a string. Where only a small number of discrete positions or states are possible, a Multi-Bit Binary type is recommended as this provides an enumerated type and avoids the use of "magic numbers" in the interface definition.

3.5.1.2 Status Mechanisms

The status mechanism adds the ability to report errors to the position functionality. These may be indications of values going out of range (for example equipment over-heating), or indications of equipment failure. No control is possible of these mechanisms however.

The status mechanism interface provides three EPICS records to the controlling application, giving the current position of the mechanism (instrument:mechanism:current) as well as a mechanism status value (instrument:mechanism:mechstat) and a string describing the mechanism status (instrument:mechanism:errstr). When the EPICS software wishes to signal an alarm condition it puts an error number into the mechstat record and fills in the error string errstr with a suitable description. The latter is intended to be reported to the user, so it should be suitably detailed and clear in its meaning for this purpose. The error code number is a 32-bit integer and is passed to the application as such, thus it may incorporate additional information about the error if desired, although the use of negative values is not recommended as these may conflict with IMI usage. The value zero (OK) is used to indicate no error, when the contents of errstr are ignored.

3.5.1.3 Control Mechanisms

The control mechanism extends the status functions and allowing full control of the mechanism. This incorporates requests to initialise the device, move to a selectable position and stop a movement in progress. The controlling application can monitor the progress of a command, and is informed separately about errors in the command itself and in the mechanism. Timeouts are provided to permit error recovery in the event of failure of the EPICS system.

In addition to the records described for the status mechanism above, the control interface provides six more records for control and feedback of the command process. Four different commands are supported, MOVE, DATUM, STOP and UPDATE, and these are selected by writing the relevant command as a string to the instrument:mechanism:comm record. To use the MOVE command the desired position must be first written into the instrument:mechanism:demand record. This demand record will usually be of the same type as the instrument:mechanism:current record which reports the current position or state of the mechanism, such that when a MOVE command has successfully completed the current position should be equal to the demanded position.

In order to support the STOP command, the processing involved in actually moving a mechanism is distinguished from that of checking the validity of the command and its parameters. Feedback on the validity of a command is provided in the instrument:mechanism:commstat command status record and its related instrument:mechanism:commstr command error string. When the IOC detects that the comm field has been set, it sets commstat to ACTIVE and checks that the command is valid, including the contents of the demand record if relevant. If the command can be accepted, the commstat record is then set to DONE and the action commences. If however the command cannot be accepted, commstat is set to an error condition (for example REJECTIIO if an interlock prevents the command from running) and a short description of the error is placed in the commstr record (possibly "Rejected - Command is interlocked" in the above case, although the text in this example should also include the source of the interlock).

Another record instrument:mechanism:clstat is also used to indicate the status of the movement command. When a command starts operating the value in this record is changed from zero to one (ACTIVE) to indicate the client is active. If possible the EPICS application should update the current record with the position of the mechanism while it is moving. When the command eventually finishes, clstat changes back from a one to a zero (DONE) to indicate this, although it does not necessarily mean that it was successful.

Failure of a command is signaled using the mechstat and errstr records described above, which should be set before clstat is changed back to zero (DONE). Mechanism processing may set warning values in the mechstat and errstr records during the course of a command, and these will all be reported to the user as they occur, however the command can still succeed if mechstat is zero when clstat changes to DONE - this is useful to permit reporting of mechanisms being slow to move, do to low air pressure for example, but still managing to arrive at their correct destination.

Sometimes a mechanism may stick before it reaches the demand position, even though the EPICS control system is continually trying to move it as requested. Each control mechanism also has associated with it a instrument:mechanism:timeout record, which contains a timeout interval in seconds. This is the maximum length of time which any command to this mechanism should take to run, and is used within both the EPICS and ADAM applications to stop any outstanding action which takes longer than this period. The ADAM program should actually add some seconds onto the value it obtains in order to allow the EPICS timeout to stop the command first if possible.

In addition to the MOVE command already described, the DATUM command can be supported by mechanisms which are incrementally encoded to re-zero the encoder. For mechanisms which are slow in operation, the user may wish to be able to abort a command while it is in progress, thus the STOP command is available. Finally the UPDATE command is supported for those mechanisms which need to perform some processing to be able to refresh their current position.

3.5.2 Noticeboard and MIMIC interfaces

Usually an ADAM applications task which uses IMI is mostly just a glorified protocol converter, taking ADAM action messages and converting these into commands to the mechanisms under control, and passing status and positional information back via the noticeboard and a MIMIC display. IMI can perform most of the activities necessary to do this without having to write much applications code. To make use of this, the noticeboard structure should be reflect the mechanism command interface structure as follows.

3.5.2.1 Position Noticeboards

The single position record from the command interface maps onto a single noticeboard item, with the name INSTRUMENT.MECHANISM.CURRENT. Note that the strings used for INSTRUMENT and MECHANISM do not have to be the same as those used for the EPICS record names. ADAM requires that all noticeboard names be in upper case. The type of the noticeboard item must match that of the current record of the command interface, as unless the application performs some type conversion the value will be set as a string. When this value has been set in the noticeboard item, a command which may be different for each mechanism is sent to the MIMIC to force the new value to be displayed.

3.5.2.2 Status and Command Noticeboards

For the more complex interface types, another noticeboard item is added which is intended to be used for the MIMIC display status of the mechanism and hence its displayed colour, and is named INSTRUMENT.MECHANISM.MIM_STATUS. This is set to mim_s_ERROR if a non-zero value is received for mechstat, to mim_s_IN_USE whenever a zero (OK) mechstat is received, and to mim_s_OFF if the EPICS system becomes disconnected. The mechanism's MIMIC update command is also sent whenever this status value changes.

3.5.3 IMI Mechanisms

An application using IMI to control an EPICS system contains a number of data structures which describe each mechanism in turn. As well as providing necessary information about the mechanism, the data structures include pointers to various functions within the application which IMI calls to report information about the mechanism back to the application. This approach enables the application to contain the code necessary to control the instrument while IMI takes care of the work necessary to interface to Channel-Access.

3.5.3.1 Unknown Mechanism structure

Because of their significant difference in complexity, the three mechanism types use different data structures to provide the necessary information to IMI. All three structures are initially identical to an additional mechanism structure called an unknownMech_s structure, which is used within IMI wherever the particular mechanism type is unknown or undefined. The generic type mechanism_t (actually a void* pointer) is available for application routines to point to a mechanism structure before they work out what type it is. This must be cast to a pointer to one of the mechanism structures before it can be used however.

The unknown mechanism structure is defined within the imi.h file to be:

struct unknownMech_s {
    const char *name;           /* Mechanism name for humans */
    const char *pvName;         /* Mechanism name for CA */
    const char *nbName;         /* Mechanism name for NBU */
    const enum mechType_e type; /* What descriptor this is */
    int imiStatus;              /* What's happening here */
    int moncount;               /* Count of CA monitors received */
    void *applPrivate;          /* Available for application's use */
    initMech_r init;            /* Initialise routine */
};

Any mechanism may be known by different names to Channel Access, the ADAM Noticeboard and for any messages output to the user, and these are all set by the various name strings at the top. The enumerated value type indicates whether a particular mechanism is a control, status or position mechanism and hence how the fields of this structure beyond init should be interpreted. The enumerated values available are defined as

enum mechType_e {
    unknownMech,
    controlMech,
    statusMech,
    positionMech
};
The imiStatus field is used within IMI to keep track of the current progress of a command, and these progress indications can be displayed for diagnostic purposes by setting the talk_r_FILTER urgency level to talk_ce_DEBUG. The moncount field is also output in these diagnostic messages, and is incremented every time an IMI callback routine is run by Channel Access for this particular mechanism, providing a useful serial number for the action callback routines. The applPrivate field allows the application callback routines to maintain additional contextual information on a per-mechanism basis. Finally init is a pointer to a function (usually within IMI) which initialises this particular mechanism - see section 1.5.5 below for a description of the IMI initialisation process which gives more details of this function.

3.5.3.2 Position Mechanism structure

Beyond the init field, the mechanism structure types differ. The Position mechanism only contains two more fields which need to be set by the applications developer:

posnResp_r posnCallback;    /* Position update callback */
char *mimCmd;               /* Mimic update command string */
The position update callback is a pointer to an applications routine which will be called whenever a position update is received from the EPICS system. For many applications however, this callback only needs to copy the value to the related noticeboard item and send an update command to the MIMIC display. A routine posnPosn is provided within IMI to perform these actions for position mechanisms, thus the application may not need any code within it to support some position mechanisms. The position callback routines for all mechanism types must match the following function prototype:

void mechPosn(mechanism_t mech, char *current);
There are additional fields beyond these in the struct positionMech_s definition, but these should not be initialised or modified by the application, being used by IMI at run-time to hold identifiers for Channel Access and the noticeboard.

3.5.3.3 Status Mechanism structure

The status mechanism structure adds an extra callback field to the above, called whenever a new value for mechstat and errstr is received and whenever the EPICS IOC disconnects. Beyond init therefore the definition of struct statusMech_s looks like this:

posnResp_r posnCallback;    /* Position update callback */
statResp_r statCallback;    /* Mechanism status update */

char *mimCmd;               /* Mimic update command string */
The mechanism status callback routine must match the following function prototype:

void mechStat(mechanism_t mech, long mechstat, char *errstr);

The value of errstr will be NULL in the following circumstances:

The callback routine will probably be run again very soon with an updated value from the IOC's instrument:mechanism:mechstat record.

Both of the IMI_S_ symbols are negative numbers, thus the selection of values for mechstat which are less than zero are to be strongly discouraged.

3.5.3.4 Command Mechanism structure

The command mechanism structure has several more callbacks and routine pointers, so the definition of a struct controlMech_s continues after the init field as follows:

    moveMech_r moveCmd;         /* Move to position */
    datumMech_r datumCmd;       /* Goto encoder zero-point */
    stopMech_r stopCmd;         /* Abort movement */
    updateMech_r updateCmd;     /* Request current position */

    commResp_r commCallback;    /* Command processing complete */
    posnResp_r posnCallback;    /* Position update */
    actvResp_r actvCallback;    /* Command active or complete */
    statResp_r statCallback;    /* Mechanism status update */

    char *mimCmd;               /* Mimic update command string */

The four function pointers at the beginning give the routines to be called to send the relevant command to the mechanism, if this particular command is supported on this mechanism. The pointers must be initialised by the application to point to the IMI routines ctrlMove, ctrlDatum, ctrlDatum and ctrlUpdate respectively, or to some other application routines which call these. If a particular mechanism does not support one or more of the commands, the relevant function pointer should be initialised to NULL instead. Use of these functions is described in section 1.5.4 below.

The additional callback routines are used to inform the application of the progress of a command as reported by the EPICS system. The commCallback routine is used to tell the application whether a command has been accepted or not when a new value is received from the commstat record. If the command was rejected for some reason (i.e. commstat is not DONE) the command error string commstr is also supplied to the callback, although for an accepted command this value will be NULL. The application's callback routine must match the following function prototype:

void commComm(mechanism_t mech, char *commstat, char *commstr);

Because the behaviour of this routine will vary between different applications, IMI does not provide a standard version of it - each application must define its own, and there may be different routines for each mechanism or mechanism type. Note that this callback routine will be run within the ADAM context of the Channel Access polling action rather than the action which initiated the command, thus when a command is rejected it will probably be necessary to signal to the originating action to get it to finish with a suitable error.

The actvCallback routine signals the receipt of a new value from the clstat record, indicating that a mechanism has just started or completed a movement command:

void commActv(mechanism_t mech, int active);

The command did not necessarily come from this task, thus the active parameter is a bitmask of two flags which can thus take four possible states. The table below lists meanings of the various symbolic values for the parameter.




The receipt of either IMI_C_MINE_ACTIVE or IMI_C_OTHERACTIVE is a good point to set the MIMIC display of the mechanism to the state mim_s_MOVING. When IMI_C_MINE_DONE is received the callback will probably need to signal to the originating action to get it to finish. The success or failure of the command can be found by examining cmdMech->mechstat.value which holds the last reported value from the mechstat record. The ISISP I-task sets this field to -1 when the command starts so it can tell whether an error has occurred at its later completion.

3.5.4 Sending Commands

The imi.h include file defines four C macros to make calling the command routines easy to do. These effectively have the function prototypes.

void imiMove(struct commandMech_s *mech, char *demand, int *status);
void imiDatum(struct commandMech_s *mech, int *status);
void imiStop(struct commandMech_s *mech, int *status);
void imiUpdate(struct commandMech_s *mech, int *status);

although because these are macros the mech parameter is evaluated more than once for each instance. In the case of the imiMove routine the additional demand string parameter is required, the contents of which should be convertible by Channel Access to match the type and range of the relevant instrument:mechanism:demand record. The other command types do not require a parameter.

As part of the processing which occurs when sending a command, the timeout record is read from the IOC and the value obtained is available to the application after the macro completes, in the long integer cmdMech->timeout.value. As a result of having to obtain this value from the IOC, the command routines call the Channel Access ca_pend_io which will block the application until the result is received from the IOC or the 5 second timeout occurs.

3.5.5 Initialisation

When an IMI application starts up, its various Channel Access connections must be made and the ADAM noticeboard items found. The networked nature of the Channel Access database connections would make this startup a relatively long process if mechanisms were initialised serially, thus IMI has been designed to initialise mechanisms in parallel, only waiting for network activity to complete when all mechanisms have done as much work as they can without feedback from the IOC.

The initialisation code for a mechanism is given by a function pointed to by mech->init. A standard routine is provided for each of the mechanism types and this should normally be sufficient for all applications. The applications code must call imiInit at some point before any other IMI mechanism routine - inside an ADAM INITIALISE action is a suitable place as imiInit can be run more than once to reset connections. The main argument to imiInit is a NULL-terminated array of mechanism_t pointers to all of the mechanisms controlled by the application, and this is used to call each mech->init routine in turn a number of times. The routine must provide the following function prototype:

int mechInit(mechanism_t mech, short pass, StatusType *status);

The three IMI-provided standard routines for the three different types of mechanism are defined thus, and named posnInit, statInit and ctrlInit. The short pass argument is a counter which starts at zero and is incremented between subsequent sets of calls to the mech->init routines. After all of the routines have been called once, Channel Access' ca_pend_io is called to process all outstanding network activity generated, before the routines are called again in the next pass. The return value from the function is a true/false value which indicates whether this particular mechanism needs another initialisation pass or not (FALSE means initialisation is complete). This looping continues until all mechanisms have returned FALSE, up to a maximum of 12 passes.

The structure of an initialisation routine is therefore probably a single switch statement with a case for each pass which requires processing, generating a return value of TRUE, and a default case for later passes which generate a return value of FALSE. The standard routines use the first pass to do ADAM noticeboard lookups and Channel Access searches; on the second pass the monitors and connection handlers are registered, and information about the range of values acceptable to any mechanism:demand records is requested; the third pass just sets a flag to indicate the mechanism has been successfully initialised, thus normally only three passes will be required.

Note that Channel Access monitors may not connect up until the next time the application's POLL action calls ca_pend_event, thus the current state of the instrument will not be reflected into the noticeboard items until sometime after the INITIALISE action or equivalent has completed.

3.5.6 Other IMI routines

There are three other routines provided within imi.c which are useful in IMI applications or have significant usage within it.

3.5.6.1 Mechanism Status

void imiStatus(mechanism_t mech, int newStatus, const char *descr);
This routine is used to record state transitions for an individual mechanism, some of which are purely diagnostic but some of which are necessary to the functioning of the software. The int newStatus value takes one of two different types; either an IMI_S_ state constant, which indicates diagnostic information about the status of the mechanism, or an IMI_B_ bit-mask or its negation, which records persistent state information about interface activity. In both cases the value of mech->imiStatus is altered, and if the display of debug level messages is enabled in the talker library, a text message is output describing the state transition and incorporating the descr string argument.

The valid IMI_S_ and IMI_B_ symbols and their meanings when passed to imiStatus are:





The IMI_B_ and -IMI_B_ values respectively set and clear a bit in the mech->imiStatus field, without altering any other bits and are therefore able to maintain state information for the interface - the bit value can be found using mech->imiStatus & IMI_B_INITIALISED for example. The IMI_S_ values are stored in the lower bits of mech->imiStatus and can be obtained using mech->imiStatus & IMI_B_MASK.

3.5.6.2 ADAM Noticeboard

A routine is provided by and used within IMI to combine several strings into a complete noticeboard item name, search the noticeboard for this name and return the result of this search.

nbid imiNbSearch(const char *mechname, const char *varname, 
                 StatusType *status);
This routine creates a noticeboard name string by concatenating three strings: the application's noticeboard name given in its global variable char *nbPrefix, and the two string arguments mechname and varname (separators are not added, thus the nbPrefix string should end in a `.' and the varname string should begin with one). This name is passed to the noticeboard lookup routine nbuc_fi_NBL and the resulting item locator (called an nbid within IMI) is returned.

Two macros are also defined to read and write noticeboard values. These map to the relevant routines in the nbuc library, and are just provided for symmetry with the Channel Access routines. These macros are equivalent to the function prototypes:

void imiNbPut(nbid item, const char *value, StatusType *status);
char *imiNbGet(nbid item, StatusType *status);
The string buffer containing the item value returned by imiNbGet must be passed to free() once it is no longer required by the application.

3.5.6.3 MIMIC Commands

Sending an update command to the MIMIC display usually requires an application to create a Pascal string descriptor to hold the message. IMI provides a simple C routine which does all the hard work, including connecting up to the MIMIC in the first place.

void imiSendMimic(const char *mimic_command, StatusType *status);

4 ISISP - The Polarisation I-task

This section describes the ISISP I-task, which uses the facilities of the DUA and IMI modules to implement the application to control the EPICS-based ISIS Polarisation unit. The remainder of this document contains a brief summary of the source file structure, followed by details of the various ADAM actions which are accepted and the routines which implement these, then the additional IMI mechanisms not otherwise described.

Reference should also be made to the document WHT-ISIS-6 which gives an overview of the software interface to the I-task. Neither of these documents are intended to be descriptions of the commands for use by visiting astronomers.

4.1 Source Files

The application-specific parts of the ISISP I-task are contained within 6 source and header files:

  1. isisp.c
  2. Initialisation routine and the main ADAM task entry point, which passes control to the DUA action dispatcher. See the description of the DUA module and the code example presented in section for an understanding of the content of this file.

  3. isisp_data.c
  4. Definitions of the mechanism structures and the main action table. This module does not contain any executable code.

  5. isisp_actions.c
  6. The application-specific code, comprising some global variables, the action handler routines and mechanism callbacks.

  7. isisp_data.h
  8. Declarations of the names and types of the definitions in isisp_data.c

  9. isisp_actions.h
  10. Declarations of the names and types of the routines, structures and global variables defined in isisp_actions.c

  11. isisp_errs.msg
  12. Application error code definitions, which must be converted from the error message format into isisp_errs.h before compiling ISISP.

The software is written in ANSI C, and thus must be compiled using the DECC compiler. The resulting object code needs to be linked with the ADAM I-task libraries, EPICS Channel Access, the MIMIC interface and the noticeboard libraries NBU and NBS_SHR.

4.2 ADAM Actions and the Action Routines

The actions and their associated routines are described in the order in which they occur in the isisp_actions.c source file. Actions fall into one of three types: System actions concerned with the operation of the I-task itself (PING, POLL, EXIT, INITIALISE, MESSDISP and TIMEDISP); Configuration of the software which affects its behaviour (AUTO_FOCUS, CHANGE_WP, SAVE_CONFIG and READ_CONFIG); and Command actions which cause IMI to send a command to the EPICS IOC and cause the movement of one or more mechanisms (PHWSLIDE, PQWSLIDE, PHWANGLE, PQWANGLE, PHWSPIN and PQWSPIN).


4.2.1 PING

This action is an adopted convention from DRAMA, and is defined to do nothing. It provides a means to determine whether the I-task is currently loaded or not.

4.2.1.1 ADAM Parameters

None.

4.2.1.2 Action Routines

void pingObey(StatusType *status);

4.2.1.3 Returned Status values

Always returns with good status.


4.2.2 EXIT

This action is also included by following a DRAMA convention. It causes the task to exit immediately. Under ADAM this does not appear to tidy up the task completely, thus its use is discouraged, but it has not been removed.

4.2.2.1 ADAM Parameters

None.

4.2.2.2 Action Routines

void exitObey(StatusType *status);

4.2.2.3 Returned Status values

Never returns.


4.2.3 POLL

This action starts Channel-Access polling, which must be run frequently to maintain communications with the EPICS IOC. This is implemented by having the action continually reschedule itself after it has spent a period of time processing Channel Access messages. The status of the POLL action is recorded by the program so other actions which rely on Channel Access can be aborted in the event that the POLL action is not running.

4.2.3.1 ADAM Parameters

4.2.3.2 Action Routines

As the action continually reschedules itself, a cancel routine is provided as well as the normal action obey routine. The cancel routine passes an action cancellation request to DUA.

void pollObey(StatusType *status);
void pollCancel(StatusType *status);

4.2.3.3 Returned Status values


4.2.4 INITIALISE

Performs a repeatable initialisation, incorporating initialising the IMI mechanism interfaces which causes the Channel Access connections to be re-made, relinking to the ISISP Noticeboard, and setting various flags including a noticeboard item to indicate that connection to the IOC has succeeded. The initialisation action must complete without error before the Configuration or Command actions will run.

A DB_PREFIX parameter can be given to this action if it is desired to control an IOC which is running the ISISP EPICS database under a different instrument name to isisp (NB. Due to a bug in V0-2 of the software, do not use instrument names longer than 4 characters. This will be fixed in V0-3, when the limit will be 14 characters).

4.2.4.1 ADAM Parameters

4.2.4.2 Action Routines

void initialiseObey(StatusType *status);

4.2.4.3 Returned Status values



4.2.5 MESSDISP

This action controls the output of textual messages by using its MESS_DISP parameter to set the TALK urgency level using talk_r_FILTER. The default level for this is 1, which corresponds to talk_ce_HELP. Level 2 is for debugging, which will cause additional status messages to be output by IMI. Level 0 can also be used to skip some informational messages, although all errors will still be reported.

4.2.5.1 ADAM Parameters

4.2.5.2 Action Routines

void messdispObey(StatusType *status);

4.2.5.3 Returned Status values


4.2.6 TIMEDISP

The command actions which control the individual ISISP mechanisms can report the elapsed time taken to perform a command on its completion. This action turns the time reporting facility on or off.

4.2.6.1 ADAM Parameters

4.2.6.2 Action Routines

void timedispObey(StatusType *status);

4.2.6.3 Returned Status values


4.2.7 AUTO_FOCUS

The ISISP subsystem is capable of adjusting the focus positions of the telescope and/or the CAGB slit-view TV camera whenever a waveplate is moved into or out of the light beam. This action controls which focus settings to adjust automatically. When the I-task starts up, the auto-focus mode will be set to NONE (the mode is not retained in the configuration file).

Executing the AUTO_FOCUS action will not cause either focus position to change. It is assumed that the focus is correct for the waveplate positions when auto-focus is turned on. The software retains the knowledge of where the waveplate slides were last commanded so that it does not repeat a focus change if a waveplate is commanded to the same position two or more times in a row. The focus position will not be adjusted if the waveplate slides are moved from the ISISP Engineering MIMIC display, but (provided the focus has not been manually adjusted to compensate during the Engineering control) this will be corrected if the slides are subsequently moved using the I-task.

The magnitude of the focus adjustments for each waveplate at each focus are maintained in the configuration file, but must be edited by hand - the I-task does not provide any facilities to alter these values as they will only change very rarely, if ever.

4.2.7.1 ADAM Parameters

4.2.7.2 Action Routines

void autoFocusObey(StatusType *status);
4.2.7.3 Returned Status values


4.2.8 CHANGE_WP

For circular polarisation, the waveplates are physically swapped over in their mountings, thus the physical mechanisms involved when controlling a particular waveplate must be changed. The software must therefore be informed whenever the waveplate configuration changes, which is done using this action. The TOP_WAVEPLATE parameter indicates whether the half-wave or quarter-wave plate is installed in the mounting position nearest the sky. The other plate is assumed to be in the lower mounting. The waveplate configuration is saved in the ISISP configuration file, thus this action only need be used when the plates are actually swapped over.

4.2.8.1 ADAM Parameters

4.2.8.2 Action Routines
void changeWpObey(StatusType *status);
4.2.8.3 Returned Status values



4.2.9 READ_CONFIG

Configuration data is maintained between runs in an IMI configuration database file, which is read into memory by the READ_CONFIG action. This gets values for the waveplate mounting configuration, and the four focus corrections for each waveplate at both foci. The memory copy of the database is deleted after the relevant information has been extracted from it.

4.2.9.1 ADAM Parameters
None.
4.2.9.2 Action Routines
void readConfigObey(StatusType *status);
4.2.9.3 Returned Status values



4.2.10 SAVE_CONFIG

Configuration data is written out to the database file from the current I-task configuration. If the database file already exists it is read in and the relevant values modified, in order to retain any hand-edited comments. A new database file will be created if none exists.

4.2.10.1 ADAM Parameters
None.
4.2.10.2 Action Routines
void saveConfigObey(StatusType *status);
4.2.10.3 Returned Status values


4.2.11 PHWSLIDE and PQWSLIDE

These actions cause a command to be sent to one of the waveplate slide mechanisms PTSLIDE or PBSLIDE, according to the current waveplate configuration setting. Only the MOVE sub-action is supported, for which the demand position is taken from the SLIDE_STATE parameter.

Although the MIMIC displays information about the physical mechanism (top or bottom slide), the positions must also be reflected into noticeboard items describing the logical mechanism (half or quarter-wave slide) for the observation headers, thus the standard ctrlPosnUnxp callback routine is not used directly.

4.2.11.1 ADAM Parameters

4.2.11.2 Action Routines
void slideObey(StatusType *status);
void actionCancel(StatusType *status);
4.2.11.3 Callback Routines
void slideComm(mechanism_t inMech, char *commstat, char *commstr);
void slideActive(mechanism_t inMech, int active);
void slidePosn(mechanism_t inMech, char *value);
void slideResched(StatusType *status);
4.2.11.4 Returned Status values




4.2.12 PHWANGLE and PQWANGLE

The sub-action is sent as a command to the waveplate angle mechanism PTANGLE or PBANGLE according to the current waveplate configuration setting. The sub-actions MOVE, DATUM and STOP are supported. For the MOVE subaction the demand position is required, given as a real number of degrees as the ANGLE parameter. This must be converted into an integer number of deci-degrees before passing to the IOC. The EPICS software defines a move to angle zero as being equivalent to a DATUM command, resetting the zero position of the incremental encoder to the detented position at which the home switch registers. A STOP command can be used to abort a movement while it is in progress; the MOVE action will have to be cancelled before this can be done, and in fact setting a waveplate angle is probably too fast for this to be useful.

A special position callback routine is required to convert back from the deci-degree units into a real number of degrees for status reporting to the user. Although the MIMIC displays information about the physical mechanism (top or bottom angle), the angle must also be reflected into noticeboard items describing the logical mechanism (half or quarter-wave angle) for the observation headers.

4.2.12.1 ADAM Parameters

4.2.12.2 Action Routines
void angleObey(StatusType *status);
void actionCancel(StatusType *status);
4.2.12.3 Callback Routines
void angleComm(mechanism_t inMech, char *commstat, char *commstr);
void angleActive(mechanism_t inMech, int active);
void anglePosn(mechanism_t inMech, char *value);
void angleResched(StatusType *status);
4.2.12.4 Returned Status values




4.2.13 PHWSPIN and PQWSPIN

A MOVE command is sent to the waveplate spin mechanism PTSPIN or PBSPIN according to the current waveplate configuration setting. The sub-actions MOVE and STOP are supported. For the MOVE subaction the demand position is required, given as a real rate in Hz as the ROTATION_RATE parameter. This is converted into an integer number of deci-Hz before passing to the IOC. The STOP sub-action causes a MOVE to a rotation rate of zero rather than sending the IOC a STOP command (which is not supported by the IOC software).

A special position callback routine is required to convert back from the deci-Hz units into Hz for status reporting to the user. Although the MIMIC displays information about the physical mechanism (top or bottom rotation rate), the rate must also be reflected into noticeboard items describing the logical mechanism (half or quarter-wave rotation rate) for the observation headers.

4.2.13.1 ADAM Parameters

4.2.13.2 Action Routines
void spinObey(StatusType *status);
void actionCancel(StatusType *status);
4.2.13.3 Callback Routines
void spinComm(mechanism_t inMech, char *commstat, char *commstr);
void spinActive(mechanism_t inMech, int active);
void spinPosn(mechanism_t inMech, char *value);
void spinResched(StatusType *status);
4.2.13.4 Returned Status values




4.3 Other Mechanisms

Only the six command mechanisms described above have actions explicitly associated with them. In addition to these, ISISP has six other mechanisms which report positional and/or status information back to the I-task. Because none of these mechanisms require user-unit conversion their behaviour can be implemented completely using the standard IMI callback routines for position and mechanism status reporting, thus there are no routines defined to support them within the ISISP source code.

4.3.1 PTCLAMP and PBCLAMP

The clamp mechanisms provide additional feedback to the user about the accuracy of the waveplate angles, in that when a clamp is ON the waveplate angle is one of sixteen accurate and repeatable values at multiples of 22.5 degrees. Control of the clamps is managed by the EPICS software alone, depending on the demanded waveplate angle. The clamp positions are shown on the MIMIC display, but do not appear in the observation headers

.
4.3.2 PTCONN and PBCONN

These status mechanisms are present as confirmation that the instrument is correctly connected up. Their position and status are given on the MIMIC display, and any change of state will be reported to the observer in the ICL command window.

4.3.3 VMETHERM and VMEFAN

In the event of one of the fans in the VME chassis failing or the chassis over-heating, one or both of these status mechanisms will report the fault to the user both in the ICL command window and on the MIMIC display.

Appendix A. Document history

Issue 0.1 23/8/96 First draft issued for comments

Issue 1.1 1997-06-10 First formal issue. No change w.r.t issue 0.1.