EPICS Home

Experimental Physics and Industrial Control System


 
1994  1995  1996  1997  1998  1999  2000  2001  2002  <20032004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  Index 1994  1995  1996  1997  1998  1999  2000  2001  2002  <20032004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020 
<== Date ==> <== Thread ==>

Subject: RE: drvAscii R3.14 version
From: Kevin Tsubota <ktsubota@keck.hawaii.edu>
To: "'Porter, Rodney R.'" <rporter@anl.gov>, tech-talk@aps.anl.gov
Cc: Al Honey <ahoney@keck.hawaii.edu>
Date: Mon, 14 Apr 2003 14:36:17 -1000
Hi Rodney,
Allan Honey is the one who actually worked on it for us here.
There's the original version in our ftp site:
    ftp.keck.hawaii.edu/pub/epics/drivers
 
Attached are updated files with some modifications from
Paul Tyler for Linux and some other updates ontop of it from Allan.
 
Hopefully, Allan will repost the updates to our ftp site.
 
Kevin
 
 
-----Original Message-----
From: Porter, Rodney R. [mailto:rporter@anl.gov]
Sent: Monday, April 14, 2003 5:42 AM
To: tech-talk@aps.anl.gov
Subject: drvAscii R3.14 version

How do I get drvSerial/drvAscii for R3.14? 

 

I ask because of the following response to the Serial Driver for LINUX thread. 

 

Note:  I have implemented the parsing of checksums in drvAscii v. 2.2 with no problems.  They have added a user definable pre-parser that can handle this. 

 

1. Use of drvSerial/ drvASCII (thanks to Paul Tyler and Kevin Tsubota who sent to me the R3.14 version). It works fine. But, due to the very general protocol of drvASCII, it is neither possible to implement the checksum policy of the FP (fieldpoint) protocol, nor the check of the module responses. On the other hand, this approach is very convenient to test individual FP commands on the fly (particularly useful to understand the different types of commands, parameters etc..)

 

 

Rodney Porter

Scientific Associate - Data Acquisition

Intense Pulsed Neutron Source Division

Argonne National Laboratory

Bldg. 360, Rm. C-209

9700 S. Cass Ave.

Argonne, IL  60439

(630) 252 - 7151

(630) 252 - 4163 fax

 

--- Begin Message ---
Subject: drvAscii
From: ahoney@hapuna.keck.hawaii.edu
To: pct@pdnt53.anp.ansto.gov.au
Cc: ahoney@polihua.keck.hawaii.edu, ktsubota@polihua.keck.hawaii.edu
Date: Thu, 20 Mar 2003 13:12:44 -1000
Title: drvAscii

Hopefully this is all compilable.

Let me know if you have problems Paul.

Sometime in the near future we should get this working for our version
of solaris R3.14, Kevin.

AH

 

static char rcsid[] = "$Id: devAscii.c,v 1.1 2003/01/14 00:27:40 ktsubota Exp $"; 
/*
 *  Author: Allan Honey (Keck observatory) with considerable collaboration 
 *         by W.Lupton.
 *
 *         Derived from the Compumotor SX device support (devCmSx.c)
 *         written by Jeff Hill.
 *         
 */

/*
 *  devAscii provides the interface between records, whose DTYP='Ascii SIO',
 *  and drvAscii. The records supported are ai, ao, bi, bo, mbbi, mbbi 
 *  direct, mbbo, mbbo direct, longin, longout, stringin, stringout, and 
 *  waveform. Where the waveform record is used as a long stringin record 
 *  such that strings greater than MAX_STRING_SIZE (40) can be handled. 
 *
 *  The basic design is to pass record specific data to drvAscii, which 
 *  formats a request for drvSerial, then eventually asynchronously calls 
 *  back devAscii with the response.
 *
 *  PARM STRING USAGE
 *  During record init the PARM string (record's INP or OUT field) is parsed
 *  and the information is cached and passed to drvAscii. drvAscii uses the 
 *  information to determine how to handle i/o for the record.
 *
 *  The format of the PARM string is: 
 *    serial port name {[signal number | array size] @}
 *                     {<special record> || 
 *                      [<command prompt><response format>]
 *                     {<readback prompt><readback response format>}}
 *
 *    where:
 *    signal number = Sn @     - not implemented
 *    array number  = An @     - not implemented
 *
 *    special_record 
 *    = [ slope || 
 *        timeout || 
 *        writeCmt %s || 
 *        readCmt %s || 
 *        connect || 
 *        connectSts || 
 *        debug ]
 *
 *    note that the %s for the write and read command terminators are 
 *    necessary!
 *
 *    Note that all records must have a serial port name. As of Jan 97 the
 *    driver was not tested with file streams but in principle only minor
 *    changes should need to be made to make it work. All records except
 *    a stringout record must have a command prompt.
 *
 *  PLEASE refer to the modification history from August 2002 as the
 *         keyword REAL now exists for analog records only.
 * 
 *
 *  SPECIAL RECORDS
 *  The 'special records' control various aspects of how drvAscii functions.
 *  Note that a special record affects the i/o of all records associated
 *  with a serial link. A description of each 'special' record follows.
 *
 *  'slope' is used to manipulate analog i/o values. Slope is needed 
 *  because: the RVAL for an analog input record is an integer and there 
 *  is no way to circumvent conversion from RVAL to VAL (probably would 
 *  not want to anyways) so ESLO, ASLO and ROFF come into play; the RVAL 
 *  from an analog output record is an integer and if one used VAL then 
 *  the ESLO, ASLO and ROFF would be circumvented. Thus all analog output 
 *  values (rval) are divided by slope prior to formatting the output 
 *  string, and all analog input values are multiplied by slope (eg. a 
 *  remote device which returns values with a precision to the second 
 *  decimal place, such as 2.34, may have a slope of 100 such that the 
 *  associated AI rval would be 234 -> 0xea,the record's EGUL and EGUF 
 *  could be set so that the original value is restored or converted 
 *  directly into some other unit). 
 *
 *  The 'slope' record should be associated with an AO record.
 *
 *  PLEASE refer to the modification history from August 2002 as to how
 *         the modifications affect the above discussion of analog 
 *         conversions.
 *
 *  'timeout' allows one to control the maximum time that drvAscii will 
 *  wait for a response to a command/prompt. 'timeout' should be associated 
 *  with a longout record. (future mod is to allow this to be a float so 
 *  fractions of seconds can be specified).
 *
 *  'writeCmt %s' and 'readCmt %s' allows one to change the write and read 
 *  command terminators. The writeCmt is tagged on to the end of the output 
 *  string (prompts), the default is 'crlf'. The readCmt defines the end of 
 *  a response string, default is 'crlf'. The associated records must be 
 *  stringout records. The command terminator strings will be parsed using 
 *  C character and numeric escape codes - be aware that dbpf and caput do
 *  not handle escape codes identically! Also converting from a capfast 
 *  drawing to a database strips the '\' in '\n' so one cannot currently 
 *  specify the usual command terminators as a constant to a capfast record.
 *
 *  'connect' allows one to change the connection state of a serial link.
 *  Upon successful initialization the connection state will be on (1).
 *  When the connection state is off (0) then drvAscii will not perform any
 *  i/o. 
 *
 *  'connectSts' returns the current connection state of a serial link
 *  where 1 means on or ok and 0 means off or disabled.
 *
 *  (The following was added August 2002)
 *  'debug' allows one to control the amount of information generated
 *  from within drvAscii. A debug record must be an integer out record.
 *  There may be as many debug records as there are logical links being
 *  handled by drvAscii (i.e. drvAscii may have connections to multiple
 *  serial ports.
 * 
 *  There are essentially two levels of debug and they only affect the
 *  drvAscii functions. 

 *  If debug is non-zero then all data output from the default tx framing 
 *  routine (putFrame) is displayed on the console port, as is all input 
 *  received by the rx framing routine (getFrame). Where, the non-printing 
 *  characters are displayed as the ascii representation of 2 digit 
 *  hexidecimal characters, enclosed in brackets '[xx]'. 
 *
 *  If debug >= 8 then all the record-specific information is displayed 
 *  each time a drvAscii processes a record (writeInteger, writeReal, 
 *  writeString, readInteger, readReal, and readString functions). The
 *  displayed information is essentially that which is relevant to prompts 
 *  and responses. Thereby, allowing one to determine if the prompt and 
 *  response specifications were parsed as expected, as well as, allowing 
 *  one to work out what drvAscii is attempting to do)  
 *
 *  In addition, to the debug records, a drvAscii global variable 
 *  (drvAsciiDebugLevel) exists for controlling the displaying of 
 *  additional information. The use of this variable allows one to display 
 *  information during iocInit, which can not be done with the 
 *  aforementioned debug record, as well as processing information. 
 *  Said variable affects both the devAscii and drvAscii functions. The 
 *  variable is intended to be used as a bit mask.
 *
 *  Only two bits are used within the devAscii functions.
 *  
 *  If (drvAsciiDebugLevel & 1) then a one line message is displayed
 *  for each record that is handled at iocInit. This simply identifies
 *  the records, being processed, by name, and displayes the parm fields
 *  being parsed. 
 * 
 *  If (drvAsciiDebugLevel & 32) then the name of each read and
 *  write function herein is displayed when a function is invoked.
 *  Thus giving a poorman's trace.
 *
 *  The affects on the drvAscii functions are as follows.
 * 
 *  If (drvAsciiDebugLevel & 2) then the response data and formatting
 *  string used to parse that data is displayed from within 
 *  processOutputResponse().
 *
 *  If (drvAsciiDebugLevel & 8) various parsing info is displayed
 *  from within drvAsciiCreateSioLink() during iocInit. The intent
 *  is to allow one to figure what is wrong with a record.
 *
 *  If (drvAsciiDebugLevel & 16) then all input and output data is
 *  displayed in hex form rather than char form (this affects the
 *  displays from the debug records discussed above).
 *  
 *  If (drvAsciiDebugLevel & 64) the the name of each invoked function,
 *  within drvAscii, is displayed. Again this provides a poorman's 
 *  trace. 
 *
 *
 *      PROMPT AND RESPONSE FORMAT STRINGS (extensions to scanf syntax)
 *
 * KILL SYNTAX
 *  In order to make drvAscii as flexible as possible, with respect to 
 *  remotely connected devices, (at least as far as my imagination could 
 *  fathom) the format strings were extended beyond the usual '%' C spec-
 *  ifications.
 *
 *  drvAscii makes extensive use of sscanf, sprintf, strlen, ...
 *  thus certain values embedded in return strings could cause problems. 
 *  One of the first uses of drvAscii, at Keck, was interfacing to the 
 *  shutter clinometers.  Unfortunately, the underlying device preceeds 
 *  its response with a NULL byte ('\0'). Which of course is going to make 
 *  string processing very cumbersome (the string appears to be a null 
 *  string).
 *
 *  To provide a mechanism for 'ignoring' a leading null byte or, for that
 *  matter, any series of leading bytes, a kill syntax was implemented 
 *  ('%nk' or '%*k'). This essentially means skip the first 'n' characters 
 *  in a response, '%*k' means ignore the entire response. If the kill 
 *  syntax is used it must be the first characters in the response format 
 *  string and only one such specification is permitted. The kill syntax is 
 *  not permitted in a prompt string as it would be meaningless.
 *
 *
 * MULTILINE INPUT SYNTAX
 *  At Keck the infra-red choppers have status request commands which 
 *  generate multi-line responses. To handle this type of response it is 
 *  necessary to circumvent the normal read command terminator mechanism
 *  (ie. the low level framing routine normally waits for a single command 
 *  terminator sequence). This was accomplished by the command terminator 
 *  count syntax '%nt'. This causes the framing to wait for 'n' command 
 *  terminator sequences. 
 *
 *  The command terminator count format string must be either at the 
 *  beginning  of the response format string or immediately following the 
 *  kill string.
 *
 * 
 * BINARY CONVERSIONS
 *  At Keck the secondary actuators are driven by Compumotor motor con-
 *  trollers, to which the interface is via serial i/o. Several of the 
 *  commands to these controllers return statuses as a string of '0' and 
 *  '1' such as '1011001101' or '1110_0110_1111'. It is necessary to con-
 *  vert these strings to integer values before returning them to the Epics 
 *  records, which cannot be done with standard C scanf syntax.
 *
 *  drvAscii augments the standard C scanf syntax with '%nb %[abc...]'. 
 *  This syntax indicates that the incoming string is a sequence of '1' 
 *  and '0' which must be converted to an integer value 
 *  (eg. '101101' -> 0x2d -> 45). The optional '%[abc...]' syntax allows 
 *  one to specify a list of delimiter characters. If the delimiter string 
 *  exists then it must immediately follow the '%nb' string. Also note that 
 *  '%*b' is not valid, as the same can be accomplished with '%*d' or '%*f'.
 *
 * 
 * STRING TO NUMERIC
 *  At Keck we have some devices, namely the Compumotor motor controllers, 
 *  which return statuses as ascii characters (why they did this with some
 *  statuses and binary strings '0' and '1' for other statuses and did not
 *  allow one to select a mode is a mystery). In any event, drvAscii allows
 *  '%nc' or '%ns' to be specified for numeric data. Note that 'n' may not
 *  be larger than 4.  
 *
 *  This syntax will cause the incoming string to be converted to an 
 *  integer, and hence to a float for analog inputs. For instance the 
 *  string "abcd" would be converted to the integer value 1633837924 
 *  which is 0x61626364. Note that the first character of the incoming 
 *  string will be the high byte of the resultant integer value.
 *
 *  Note that this implementation is not complete and actually unacceptable
 *  with converting string<->numeric. The problem is that string functions
 *  are extensively used and, therefore, a 0 is converted to a null byte, 
 *  causing unexpected results. 
 *
 * LONG STRING INPUTS
 *  At Keck we had a need to allow inputting of strings longer than the 
 *  maximum string allowed for a stringin record (40 bytes). To accommodate
 *  this, device support for the waveform record was implemented. This
 *  allows strings upto DRV_SERIAL_BUF_SIZE (currently defined in 
 *  drvSerial.h as 0x400 -> 1024). 
 *
 *  Note that the user is assumed to have connected the waveform record to 
 *  a subroutine record (or equivalent) so as to parse the incoming string 
 *  as desired.
 *
 *  
 * PARM STRING EXAMPLES 
 *    @/tyCo/1 <timeout>
 *    this would be a record, associated with the link on '/tyCo/1' (which 
 *    is the vxworks stream associated with an on board serial port), that
 *    allows control of the i/o timeout for asynchronous i/o.
 *
 *    @/tyCo/1
 *    this would have to be a stringout record as they are the only records 
 *    for which a command/prompt is not necessary.
 *
 *    @/tyCo/1 <CHN1><001,%f>
 *    this would be an AI record for which a remote device would respond
 *    to the prompt 'CHN1' with '001,' followed by a floating point value.
 *    Note that the angle brackets are necessary delimiters however they 
 *    are stripped off and never seen by drvAscii. At this time, Jan 97,
 *    there is no provision for an escape code for angle brackets.
 *
 *    @/tyCo/1 <CHN1><%4k%f>
 *    same as the previous example except the '001,' will be ignored (ie.
 *    pattern matching will not occur). Note that all characters in an 
 *    input stream must be consumed or the stream is rejected. So if a
 *    remote device returns a string of characters before and/or after 
 *    a numeric string then those strings must be specified in the response
 *    format string. If the before or after strings can vary then it is
 *    necessary to input to a stringin or waveform record and process the
 *    stream with a subroutine.
 *
 *    @/tyCo/1 <CHN1><%*k>
 *    this is invalid as an input record cannot ignore all characters.
 * 
 *  A word of warning! Be very careful how one uses '%nc' as '%c' does
 *  not function the same as '%1c' and '%*c', with Vxworks implementation
 *  of sscanf. In actual fact '%1c' and '%*c' function as though they were
 *  '%1s'. I also believe that '%[^...]' is incorrect. 

 *  If one has the following string as input "1RA\r*@\r" then only the 
 *  following response  format string will succeed "%2t1RA%*[*]%s". These 
 *  will not "%2t1RA%*c%s", "%2t1RA%*[^*]%s". The problem is that leading 
 *  whitespace is incorrectly handled by sscanf.
 *
 *   
 * PROCESSING
 *  When an Ascii SIO record processes, the devAscii i/o function passes 
 *  drvAscii a pointer to an asynchronous IO structure and a pointer to an 
 *  output value. drvAscii formats an ascii string, as per the format 
 *  registered on record init as specified in the record's parm string 
 *  (INP or OUT field), setups up a callback routine, queues the string 
 *  plus port specific info, to the appropriate transmit task in drvSerial,
 *  and returns a status value to devAscii.

 *  If a response is expected then the status is set to
 *  S_drvAscii_AsyncCompletion (only occurs for input records) causing 
 *  devAscii to mark the record's PACT=true. If and when the remote device 
 *  responds the response string is parsed, as per the specified response 
 *  string format and, in the case of input records, the devAscii 
 *  asynchronous callback routine is called. 
 *
 *  If the write/read cycle fails then the asynchronous callback routine is
 *  passed an error status such that devAscii sets the records alarm state.
 *
 *  The async callback routine sets the records rval (val for stringin) 
 *  then causes the record to be processed again, which ultimately will 
 *  clear the PACT.
 * 
 *
 *
 *  Modification history:
 *
 *  Aug 2002 by A. Honey
 *    1. Significant modifications, within drvAscii.c, in regards to 
 *     the handling of synchronization semaphores so as to alleviate 
 *     potential loss of read/write synchronization. Although the 
 *     synchronization problem was infrequent it sometimes required a 
 *     processor reboot in order to correct the problem. Hopefully, 
 *     synchronization will now be auto-magically re-established.
 *
 *    2. Analog records may now have 'REAL' specified in their parm fields,
 *     after the link specification and before the first prompt format
 *     field. 
 *
 *     If an analog input record has the REAL attribute then the value 
 *     returned form the remote device is written into the record's VAL
 *     field and RVAL/ESLO conversions are bypassed. Note that also 
 *     bypasses the 'slope' record behavior. Similarly, analog output
 *     records will have their VAL values output to the remote device
 *     rather than their RVAL values. In general this eliminates the
 *     conversions to/fro real and integer which caused loss of
 *     precision, as well as, reducing the considerable complexity in using
 *     drvAscii for analog records.
 *
 *    3. The format-string delimiters no longer have to be '<' and '>'
 *     for all records. Now the first character follwing the link
 *     specification is assumed to be the field delimiter, with the pairs
 *     '<>', '()', '{}', and '[]' assumed, when the left hand delimiter
 *     is encountered. These are now valid (on a record-by-record basis):
 *                  @/tyco/0 <status?> <%s>
 *                  @/tyco/0 (status?) (%s)
 *                  @/tyco/0 !status?! !%s!
 *                  @/tyco/0 *status?* *%s*
 *                  @/tyco/0 Xstatus?X X%sX
 *
 *    4. User-specified framing routines can be specified.
 *     With this release one can create their own input and output
 *     framing routines and have them override the default getFrame()
 *     and putFrame() routines. This allows one to do more sophisticated
 *     packet framing (e.g. a simple checksum could be added/checked).
 *
 *     To specify special framing routines one must download the library,
 *     they created, containing those routine then register the 
 *     functions with drvAsciiSetTxFunc() and drvAsciiSetRxFunc(), all
 *     prior to iocInit. Note that a different set of framing routines 
 *     can be registered for each serial link. Also note that there are
 *     special requirements imposed on the framing routines. An example
 *     exists within drvAscii.c
 *     
 *    5. More extensive control of debugging information via link-specific
 *     debug records and via a drvAscii global variable drvAsciiDebugLevel.
 *
 *    6. Format specifications may now have embedded hex or octal bytes
 *     Said bytes must be of the form '\xnn' or '\ooo'. Those bytes are
 *     translated when the format specs are parsed during record init.
 *     For instance a prompt format such as <\x58\x59\x5a?> will result 
 *     in the string 'XYZ?' being output to the remote device.
 *     
 *    7. All numeric escape codes, that exist in output or input data
 *     streams, will be automatically translated. For instance a
 *     stringIn record with this prompt and response format '<%s?><%s>' 
 *     can be manipulated with "caput stringIn '\x58'" to result
 *     in a prompt of 'X?' being transmitted. This should simplify record
 *     manipulation for those apps which talk to multi-dropped devices
 *     whose addresses are leading non-printing ASCII bytes. The numeric
 *     escape codes need not result in a printable ASCII characters, 
 *     that is, "caput stringIn '\x81'" is valid. However, Beware that 
 *     drvAscii uses sprintf and sscanf so embedding a null byte will 
 *     cause unexpected behavior.
 *
 */
 
/*
 * ANSI C
 */
#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<ctype.h>
#include	<limits.h>


/*
 * EPICS
 */
/*
 * EPICS
 */
#include <epicsInterrupt.h>
#include <epicsRingBytes.h>
#include <epicsMutex.h>
#include <epicsEvent.h>
#include <epicsThread.h>
#include <epicsTime.h>
#include <epicsAssert.h>
#include <epicsPrint.h>
#include <epicsEvent.h>

#include	<alarm.h>
#include	<cvtTable.h>
#include	<dbDefs.h>
#include	<dbAccess.h>
#include	<dbFldTypes.h>
#include        <recSup.h>
#include        <recGbl.h>
#include	<devSup.h>
#include	<dbScan.h>
#include	<link.h>
#include	<aiRecord.h>
#include	<aoRecord.h>
#include	<biRecord.h>
#include	<boRecord.h>
#include	<mbbiRecord.h>
#include        <mbbiDirectRecord.h>
#include        <mbboRecord.h>
#include        <mbboDirectRecord.h>
#include        <stringinRecord.h>
#include        <stringoutRecord.h>
#include        <longinRecord.h>
#include        <longoutRecord.h>
#include	<waveformRecord.h>

/*
 * Ascii driver
 */
#include "drvSerial.h"
#include "drvAscii.h" 

#ifdef vxWorks
#include <vxWorks.h>
#include <semLib.h>
#include <ioLib.h>
#include <devLib.h> /* S_dev_noMemory */
#else

/* S_dev_noMemory changed to be S_db_noMemory from dbAccessDefs.h */

/*
 * extracted from: /home/vw/m5.3.1.ppc/target/h/semLib.h
 */
/* binary semaphore initial state */
typedef enum            /* SEM_B_STATE */
    {
    SEM_EMPTY,                  /* 0: semaphore not available */
    SEM_FULL                    /* 1: semaphore available */
    } SEM_B_STATE;

/*
 * extracted from: /home/vw/m5.3.1.ppc/target/h/ioLib.h
 */
/* ioctl function codes */

#define FIONREAD        1               /* get num chars available to read */
#define FIOFLUSH        2               /* flush any chars in buffers */
#define FIOOPTIONS      3               /* set options (FIOSETOPTIONS) */
#define FIOBAUDRATE     4               /* set serial baud rate */
#define FIODISKFORMAT   5               /* format disk */
#define FIODISKINIT     6               /* initialize disk directory */
#define FIOSEEK         7               /* set current file char position */
#define FIOWHERE        8               /* get current file char position */
#define FIODIRENTRY     9               /* return a directory entry (obsolete)*/
#define FIORENAME       10              /* rename a directory entry */
#define FIOREADYCHANGE  11              /* return TRUE if there has been a
                                           media change on the device */
#define FIONWRITE       12              /* get num chars still to be written */
#define FIODISKCHANGE   13              /* set a media change on the device */
#define FIOCANCEL       14              /* cancel read or write on the device */
#define FIOSQUEEZE      15              /* squeeze out empty holes in rt-11
                                         * file system */
/*
 * extracted from: /home/vw/m5.3.1.ppc/target/h/ioLib.h
 */

#if     !defined(NULL)
#define NULL            0
#endif

#if     !defined(EOF) || (EOF!=(-1))
#define EOF             (-1)
#endif

#if     !defined(FALSE) || (FALSE!=0)
#define FALSE           0
#endif

#if     !defined(TRUE) || (TRUE!=1)
#define TRUE            1
#endif


#define NONE            (-1)    /* for times when NULL won't do */
#define EOS             '\0'    /* C string terminator */

/* timeout defines */

#define NO_WAIT         0
#define WAIT_FOREVER    (-1)

/* return status values */

#define OK              0
#define ERROR           (-1)

#endif /* vxWorks */


#define S_devAscii_Ok            0
#define S_devAscii_dontConvert   2
#define S_devAscii_badRec        3
 
#define devInitPassBeforeDevInitRec 0
#define devInitPassAfterDevInitRec 1

/* 
 *  The following structure is created for each record.
 *  The resulting structure is pointed to by the record's dpvt field. This 
 *  is essentially the handle used by the driver (drvAscii.c) for processing 
 *  the request.
 */ 
typedef struct ascii_dev_priv { 
  void        *pRec;  /* record pointer */
  long        sigNum_arraySize; /* NOT YET IMPLEMENTED 970106 */
  IOSCANPVT   spvt;   /* support interrupt from this parameter    */
  drvAsyncIO  aio;    /* async io structure. included in this structure is:*/
                      /* id - (drvSioLinkId) drvSerial structure for tx/rx */
		      /* pCB - the device callback routine for async       */
                      /* completion pIoDoneArg - (arg for pIoDoneCB) which */
                      /* is a pointerto the record (*pAppDrvPrivate) -     */
                      /* drvAscii callback function used for proccessing   */
                      /* responses to output commands                      */
}devAsciiPriv; 
 
LOCAL drvAsyncUpdateCallBack devAsciiUpdate;

/*
 *      DEVICE ENTRY TABLES
 */
LOCAL long devAsciiInit();

typedef struct {
  long		number;
  DEVSUPFUN	report;
  DEVSUPFUN	init;
  DEVSUPFUN	init_record;
  DEVSUPFUN	get_ioint_info;
  DEVSUPFUN	read_write;
} INTEGERDSET;

typedef struct {
  long		number;
  DEVSUPFUN	report;
  DEVSUPFUN	init;
  DEVSUPFUN	init_record;
  DEVSUPFUN	get_ioint_info;
  DEVSUPFUN	read_write;
  DEVSUPFUN	special_linconv;
} FLOATDSET;

/*
 *  Device entry table for AI records
 */
LOCAL long aiInitRec();
LOCAL long aiGetIoIntInfo();
LOCAL long aiRead();
LOCAL long aiLinearConvert();
LOCAL devIoDoneCallBack aiReadAsyncCompletion;
LOCAL devRealIoDoneCallBack aiReadRealAsyncCompletion;

FLOATDSET devAiAscii = {
  6,
  NULL,
  devAsciiInit,
  aiInitRec,
  aiGetIoIntInfo,
  aiRead,
  aiLinearConvert
};

/*
 *  Device entry table for AO records
 */
LOCAL long aoInitRec();
LOCAL long aoWrite();
LOCAL long aoLinearConvert();
FLOATDSET devAoAscii = {
  6,
  NULL,
  devAsciiInit,
  aoInitRec,
  NULL,
  aoWrite,
  aoLinearConvert
};

/*
 *  Device entry table for BI records
 */

LOCAL long biInitRec();
LOCAL long biGetIoIntInfo();
LOCAL long biRead();
LOCAL devIoDoneCallBack biReadAsyncCompletion;
INTEGERDSET devBiAscii = { 
  5,
  NULL,
  devAsciiInit,
  biInitRec,
  biGetIoIntInfo,
  biRead
};

/*
 *  Device entry table for BO records 
 */

LOCAL long boInitRec();
LOCAL long boWrite();
INTEGERDSET devBoAscii = { 
  5,
  NULL,
  devAsciiInit,
  boInitRec,
  NULL,
  boWrite
};

/*
 *  Device entry table for MBBI records
 */

LOCAL long mbbiInitRec();
LOCAL long mbbiGetIoIntInfo();
LOCAL long mbbiRead();
LOCAL devIoDoneCallBack mbbiReadAsyncCompletion;
INTEGERDSET devMbbiAscii = {
  5,
  NULL,
  devAsciiInit,
  mbbiInitRec,
  mbbiGetIoIntInfo,
  mbbiRead
};

/*
 *  Device entry table for MBBO records
 */

LOCAL long mbboInitRec();
LOCAL long mbboWrite();
INTEGERDSET  devMbboAscii = { 
  5,
  NULL,
  devAsciiInit,
  mbboInitRec,
  NULL,
  mbboWrite
};

/*
 *  Device entry table for MBBI direct records
 */
LOCAL long mbbiDirectInitRec();
LOCAL long mbbiDirectGetIoIntInfo();
LOCAL long mbbiDirectRead();
LOCAL devIoDoneCallBack mbbiDirectReadAsyncCompletion;
INTEGERDSET devMbbiDirectAscii = {
  5,
  NULL,
  devAsciiInit,
  mbbiDirectInitRec,
  mbbiDirectGetIoIntInfo,
  mbbiDirectRead
};

/*
 *  Device entry table for MBBO direct records
 */

LOCAL long mbboDirectInitRec();
LOCAL long mbboDirectWrite();
INTEGERDSET  devMbboDirectAscii = {
  5,
  NULL,
  devAsciiInit,
  mbboDirectInitRec,
  NULL,
  mbboDirectWrite
};

/*
 *  Device entry table for stringin records
 */
LOCAL long siInitRec();
LOCAL long siGetIoIntInfo();
LOCAL long siRead();
LOCAL devIoDoneCallBack siReadAsyncCompletion;
INTEGERDSET  devSiAscii = {
  5,
  NULL,
  devAsciiInit,
  siInitRec,
  siGetIoIntInfo,
  siRead
};

/*
 *  Device entry table for stringout records
 */
LOCAL long soInitRec();
LOCAL long soWrite();
INTEGERDSET  devSoAscii = {
  5,
  NULL,
  devAsciiInit,
  soInitRec,
  NULL,
  soWrite
};

/*
 *  Device entry table for longin records
 */
LOCAL long liInitRec();
LOCAL long liGetIoIntInfo();
LOCAL long liRead();
LOCAL devIoDoneCallBack liReadAsyncCompletion;
INTEGERDSET devLiAscii = {
  5,
  NULL,
  devAsciiInit,
  liInitRec,
  liGetIoIntInfo,
  liRead
};

/*
 *  Device entry table for longout records
 */
LOCAL long loInitRec();
LOCAL long loWrite();
INTEGERDSET devLoAscii = {
  5,
  NULL,
  devAsciiInit,
  loInitRec,
  NULL,
  loWrite
};


/*
 *  Device entry table for waveform (long stringin) records
 */
LOCAL long wfInitRec();
LOCAL long wfGetIoIntInfo();
LOCAL long wfRead();
LOCAL devIoDoneCallBack wfReadAsyncCompletion;
INTEGERDSET  devWfAscii = {
  5,
  NULL,
  devAsciiInit,
  wfInitRec,
  wfGetIoIntInfo,
  wfRead
};

extern int drvAsciiDebugLevel;


/*
 * ---------------------------------------------------------------------------
 *  copyString - copys one string to another upto a maximum length
 *     or a specified character. This is used by parseAsciiAddress.
 */
LOCAL void copyString ( char *pStr,
			char eos,
			char *pDest,
			long pDestLength )
{
  long index = 0;

  while ( index < pDestLength 
	  && 
	  pStr[index] != eos ) {

    pDest[index] = pStr[index];
    index++;
  }
  
  pDest[index] = '\0';
}


/*
 * ---------------------------------------------------------------------------
 *  getChannel - returns the 'channel' which is the value you for 'S' in
 *   the usual '#Cn Sm @', albeit '#Cn' is not relevant. Note that 'Am' 
 *   can be in place of 'Sm' but is not yet supported (as of 970117) and
 *   is intended for arrays of values.
 */
LOCAL long getChannel( char *bfr ) 
{
  char *pStart;
  long number = 0;

  pStart = bfr;

  while ( pStart 
	  && 
	  (*pStart != 'S' && *pStart != 'A') ) 
    pStart--; 

  if ( !pStart ) { 

	    number = -1;
	
  } else if ( *(pStart-1) != ' ' ) {

    number = -1;

  } else {

    pStart++;
    sscanf( bfr, "%ld", &number );
  }

  return number;
}


/*
 * ---------------------------------------------------------------------------
 * parseAsciiAddress() - parses a record's parm field into:
 *     a 'filename' (port designation passed to drvAscii
 *     a command/prompt string or special command name
 *     a command response format string
 *     a readback prompt string (output value's initial value request ) 
 *     a readback response format string
 *
 *     Note that only the 'filename' must exist for all record types.
 *     The type of record determines whether or not the command/prompt 
 *     and command response format strings are required. The readback
 *     prompt and response format strings are always optional.
 * 
 *     The resultant strings are copied into the appropriate char array
 *     within 'drvCmndArg *pCmndArgs'
 *
 */
LOCAL long parseAsciiAddress ( const char *dtype,
			       char       *pAddr,
			       char       *pFileName,
			       unsigned    maxFileName,
			       drvCmndArg *pCmndArgs,
			       long       *sigNum_arraySize)
{
  int  status;
  int  done = 0;
  int  offset = 0;

  char format[32];
  char tempStr[256];
  char *pStart = NULL,
       *pEnd = NULL,
       *ptr,
       cEnd, cStart;
    
  if ( drvAsciiDebugLevel & 1 )
    printf("parseAsciiAddress(%s)\n", pAddr );

  /* 
   *  Ensures that all strings are null terminated. This is protection
   *  against an undefined string.
   */
  pCmndArgs->cmndPrompt[0] =
  pCmndArgs->cmndFormat[0] =
  pCmndArgs->rbvPrompt[0] =
  pCmndArgs->rbvFormat[0] = '\0';
  
  /*
   *  Parse the 'file' name (ie. the serial link identification string).
   *  If it doesn't exist then abort.
   */
  assert( maxFileName>=1 );
  assert( pFileName );
 
  sprintf( format, "%%%ds",maxFileName-1 );
  status = sscanf( pAddr, format, pFileName );

  if ( status < 1 ) {


    return S_drvAscii_badParam;

  } else {
    /*
     *  The link designation must begin with '@' and end at
     *  the first whitespace character.
     */
    pStart = pAddr;
    
    while ( !isspace( *pStart ) && (*pStart != '\0')) pStart++;

    if ( *pStart == '\0' )
    /* There is nothing else to do. */
      return S_devAscii_Ok;

    /* 
     *  Determine if there are any special parsing specifications.
     */    
    do {

      sscanf( pStart, "%s", tempStr );

      ptr = tempStr;

      while ( *ptr != '\0' ) { *ptr = (char) toupper( *ptr ); ptr++; };
      
      if ( strncmp( tempStr, "REAL", 3 ) == 0 ) {

	/*  Output values are to be taken from val not rval or input 
	 *  values are to be written to val and not rval. This is to
	 *  bypass the default analog record processing rval->val. This 
	 *  also eliminates the use of 'slope' processing.
	 */
	offset = 5;
	pCmndArgs->passThru = 1;

      } else done = 1;

      if ( !done )
	pStart += offset;

    } while ( !done );

    /*
     *  The first character after the special parsing designations
     *  is the field delimiter. The special case is the backward
     *  compatiblity issue that '<' and '>' are a matched set, and
     *  these pairs are assumed: '{}', '[]', '()'.
     */
    while ( isspace( *pStart ) && (*pStart != '\0') ) pStart++;
    cStart = *pStart;
    
    if ( cStart == '<' )
      cEnd = '>';

    else if ( cStart == '{' )
      cEnd = '}';

    else if ( cStart == '[' )
      cEnd = ']';

    else if ( cStart == '(' )
      cEnd = ')';

    else
      cEnd = cStart;
 
    if ( cStart == '"' ) {

      errPrintf( S_drvAscii_badParam, __FILE__, __LINE__,
		 "'\"' double quote delimiters are not allowed!\n");
      return S_drvAscii_badParam;
    }


    /* Locate the first command/prompt string delimiter. */
    pStart = strchr( pAddr, cStart );

    /* Attempt to locate the signal number/array size delimiter. */
    pEnd = strchr( pAddr, '@' );     
 
    if ( pStart == NULL ) {

	return S_drvAscii_badParam;
    
    } else if ( pEnd && !(pEnd > pStart) ) { 
      /* 
       *  The @ is not embedded in a command/prompt so assume it delimits 
       *  a signal number or array size.
       *
       *  NOTE THIS ALL FAILS IF @ IS PART OF THE SERIAL PORT NAME!!!
       */
      *sigNum_arraySize = getChannel( pEnd );

      if ( *sigNum_arraySize < 0 ) {

	*sigNum_arraySize = 0;

	return S_drvAscii_badParam;
      }
    }
 
    /*
     *  Locate the command/prompt string. If it doesn't exist and the 
     *  record is not a string record then abort (ie. all records but 
     *  string records require a command/prompt string).
     */
    if ( pStart ) {
      ptr = pStart;
      ptr++;
      pEnd = strchr( ptr, cEnd );
      
      if ( !pEnd ) {

	return S_drvAscii_badParam;
      }

      copyString( ++pStart, cEnd,
		  pCmndArgs->cmndPrompt,
		  min( (int)((int)pEnd - (int)pStart),
		       sizeof( pCmndArgs->cmndPrompt )-1 ) ); 
    }

    /*  Find any command response format string. */
    if ( ++pEnd == '\0' )
      return S_drvAscii_Ok;

    pStart = strchr( pEnd, cStart );


    if ( pStart ) {

      ptr = pStart;
      ptr++;
      pEnd = strchr (ptr, cEnd );

      if ( !pEnd ) {

	return S_drvAscii_badParam;
      }

      copyString( ++pStart, cEnd,
		  pCmndArgs->cmndFormat,
		  min( (int)((int)pEnd - (int)pStart),
		       sizeof( pCmndArgs->cmndFormat )-1 ) );
    }
    
    /* 
     *  Find any readback prompt string. This would typically exist for
     *  an output for which an initial value can be obtained.
     */
    if ( ++pEnd == '\0' ) 
      return S_drvAscii_Ok;

    pStart = strchr( pEnd, cStart );
      
    if (pStart) {

      ptr = pEnd;
      ptr++;
      pEnd = strchr( ptr, cEnd );

      if ( !pEnd ) {

	return S_drvAscii_badParam;
      }

      copyString( ++pStart, cEnd,
		  pCmndArgs->rbvPrompt,
		  min( (int)((int)pEnd - (int)pStart),
		       sizeof( pCmndArgs->rbvPrompt )-1 ) );
    }
   
    /* 
     *  Find any readback response format string. This would typically
     *  exist for formatting a response for a request for the initial
     *  value for an output record.
     */
    if ( ++pEnd == '\0' ) 
      return S_drvAscii_Ok;

    pStart = strchr( pEnd, cStart );
   
    if (pStart) {

      ptr = pEnd;
      ptr++;
      pEnd = strchr( ptr, cEnd );

      if (!pEnd) {

	return S_drvAscii_badParam;
      }

      copyString( ++pStart, cEnd,
		  pCmndArgs->rbvFormat,
		  min( (int)((int)pEnd - (int)pStart),
		       sizeof( pCmndArgs->rbvFormat )-1 ) );
    }
  }
  
  return S_devAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 * devAsciiInitPrivate() - this function validates a record's parm field
 *     and establishes the drvAscii info required for processing the record.
 *     This includes the callback info required for asynchronous completion. 
 */
LOCAL long devAsciiInitPrivate( const char   *dtype, 
				struct link  *pLink, 
				void         *pRec, 
				devAsciiPriv **ppPriv,
				long         *sigNum_arraySize )
{	
  int	       status;
  char	       fileName[sizeof(pLink->value)];
  drvCmndArg   *pCmndArgs;
  devAsciiPriv *pPriv = NULL;

  /*  If the record does not have INST_IO defined then abort. */
  if ( pLink->type != INST_IO ) {

    status = S_db_badField;

    recGblRecordError ( status, pRec, 
		        ": Address type must be type \"instrument\"" );
    return status;
  }
 
  if ( drvAsciiDebugLevel & 1 )
    printf("devAsciiInitPrivate(%s,%s),",dtype, ((aiRecord *)pRec)->name );

  /* 
   *  Allocate the structure which will ultimately be pointed to by the 
   *  record's dpvt field. 
   */
  pPriv = calloc( 1, sizeof( *pPriv ) );

  if ( pPriv == NULL ) {
    /* 
     *  This error may be catastrophic as pPriv being NULL is not 
     *  appropriately captured.
     */
    *ppPriv = NULL;
    status = S_db_noMemory;

    recGblRecordError ( status, pRec, 
			": no room for device private" );
    return status;
  }

  /*  Init the scan private info. */
  scanIoInit( &pPriv->spvt );

  /*  Ensure that the record's dpvt pointer is set. */
  *ppPriv = pPriv;

  /*  Allocate space for the strings embedded in the parm field. */
  pCmndArgs = calloc( 1,sizeof( drvCmndArg ) );

  if ( !pCmndArgs ) {

    status = S_db_noMemory;

    recGblRecordError ( status, pRec, 
			": no room for command args" );
    return status;
  }

  /*  Parse the parm field. */
  status = parseAsciiAddress( dtype,
			      pLink->value.instio.string,
			      fileName,
			      sizeof(fileName),
			      pCmndArgs,
			      sigNum_arraySize );
  if ( status ) {

    /*  The record's parm field is invalid! */
    free( pCmndArgs );

    recGblRecordError (status, pRec, 
		       ": Syntax error in PARM string");

    return status;
  }

  /*
   *  Setup the drvAscii info and ensure a comms link is established with
   *  drvSerial.
   */
  status = drvAsciiCreateSioLink( dtype,  
				  fileName, 
				  pCmndArgs, 
				  &pPriv->aio.id, 
				  devAsciiUpdate, 
				  pPriv   );
  if ( status ) {

    /* The link was not successfully established! */  
    recGblRecordError ( status, pRec, 
			": failed to create serial link" );
    return status;
  } 

  return S_devAscii_Ok;
} 


/*
 * ---------------------------------------------------------------------------
 * devAsciiUpdate() 
 */
LOCAL void devAsciiUpdate(void *pArg, 
			  long status, 
			  long value)
{
  devAsciiPriv 	*pPriv = (devAsciiPriv *)pArg;
  
  scanIoRequest( pPriv->spvt );
}

/*
 * ---------------------------------------------------------------------------
 * devAsciiInit() 
 */
LOCAL long devAsciiInit(unsigned pass)
{
  long	status;
  
  switch ( pass ) {

  case devInitPassBeforeDevInitRec: 
    status = S_devAscii_Ok;
    break;
    
  case devInitPassAfterDevInitRec: 
    /*
     *  Initiate scanning on all links. 
     */
    status = drvAsciiInitiateAll();
    break;
    
  default: /*  Not expected. */
    status = S_devAscii_Ok;
    break;
  }
  
  return status;
}


/*
 * ---------------------------------------------------------------------------
 * reportRecCheck() - determines if the record's private info is valid 
 *     or not. If validity cannot be ascertained and the record's undefined 
 *     fields is not set then a record error message is generated. In 
 *     addiion the record's alarm severity is set to UDF, it's udf field 
 *     is set and it's pact is set. This effectively inhibits the record 
 *     from processing in the future.       
 */     
LOCAL long reportBadRec(
		       struct dbCommon *pRec,
		       devAsciiPriv	*pPriv
		       )
{
  /* 
   *  If the device private structure was never created then set
   *  the record's fault states.
   */
  if ( pPriv == NULL ) {

    /*
     *  Generate and error message and set the record's alarm state.
     *  This is only done if udf is not true to ensure a flood of 
     *  messages does not occur.
     */
    if ( pRec->udf == FALSE ) {

      recGblRecordError( S_db_badField, (void *)pRec,
			 "devAscii: the record failed init so it has been disabled" );
    }


    recGblSetSevr( pRec, UDF_ALARM, INVALID_ALARM );
 
    /*
     *  Ensure that the record is disabled.
     */
    pRec->pact = TRUE;
    pRec->udf  = TRUE;

    return S_devAscii_badRec; 
  }

  return S_devAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 * reportWriteFail() - determines if the read failed because the record
 *     is faulty. 
 *
 *     If the record is faulty and the record's undefined field is not
 *     set then a record error message is generated. In addition the 
 *     record's alarm severity is set to UDF, it's udf field is set and 
 *     it's pact is set. This effectively inhibits the record from pro-
 *     cessing in the future. 
 *
 *     If the write failed for any other reason then a write error message 
 *     is generated and the record's alarm severity is set to a WRITE alarm.
 */     
LOCAL long reportWriteFail(
			   struct dbCommon *pRec,
			   long status
			   )
{
  if ( (status == S_drvAscii_badParam) ||  
       (status == S_devAscii_badRec)      ) {
    /*
     *  There is something wrong with the record so mark
     *  it so it will never attempt a write in the future.
     */
    if ( pRec->udf == FALSE ) {
      /*
       *  Generate an error message and set the record's alarm state.
       */
      recGblRecordError( S_db_badField, (void *)pRec,
			 "devAscii: Invalid record; the record has been disabled" );

    }

    recGblSetSevr( pRec, UDF_ALARM, INVALID_ALARM );

    pRec->pact = TRUE;
    pRec->udf  = TRUE;

    return S_db_badField;

  } else {
    /* 
     *  The write failed so set the record into alarm.
     */
    recGblRecordError( S_drvAscii_dataErr , (void *)pRec,
		       "devAscii: write failed" );

    recGblSetSevr( pRec, WRITE_ALARM, MAJOR_ALARM );
  }

  return status;
}


/*
 * ---------------------------------------------------------------------------
 * reportReadFail() - determines if the read failed because the record
 *     is faulty. 
 *
 *     If the record is faulty and the record's undefined field is not
 *     set then a record error message is generated. In addition the 
 *     record's alarm severity is set to UDF, it's udf field is set and 
 *     it's pact is set. This effectively inhibits the record from pro-
 *     cessing in the future. 
 *
 *     If the write failed for any other reason then a write error message 
 *     is generated and the record's alarm severity is set to a READ alarm.
 */     
LOCAL long reportReadFail(
			   struct dbCommon *pRec,
			   long status
			   )
{
  if ( (status == S_drvAscii_badParam) ||  
       (status == S_devAscii_badRec)      ) {
    /*
     *  There is something wrong with the record so mark
     *  it so it will never attempt a write in the future.
     */
    if ( pRec->udf == FALSE ) {
      /*
       *  Generate an error message and set the record's alarm state.
       */
      recGblRecordError( S_db_badField, (void *)pRec,
			 "devAscii: Invalid record; the record has been disabled" );

      recGblSetSevr( pRec, UDF_ALARM, INVALID_ALARM );
    }

    pRec->pact = TRUE;
    pRec->udf  = TRUE;

    return S_db_badField;

  } else {
    /* 
     *  The read failed so set the record into alarm.
     */
    recGblRecordError( S_drvAscii_dataErr , (void *)pRec,
		       "devAscii: read failed" );

    recGblSetSevr( pRec, READ_ALARM, MAJOR_ALARM );
  }

  return status;
}


/* 
 * ---------------------------------------------------------------------------
 * devAsciiWriteAsyncCompletion() -  Asynchronous write completion routine.
 */
LOCAL void devAsciiWriteAsyncCompletion( void *pArg, long status, long value )
{
  struct dbCommon       *pRec = (struct dbCommon *)pArg;
  struct rset           *prset = (struct rset *)(pRec->rset);
 
  if ( drvAsciiDebugLevel & 32 ) printf("devAsciiWriteAsyncCompletion\n");

  dbScanLock( pRec );

  if ( status == S_drvAscii_Ok ) {
    /* 
     *  The write completed succesfully.
     */
    pRec->udf = FALSE;

  } else {
    /*
     *  The read failed so report the failure.
     */
    reportWriteFail( pRec, status );

  }

  if ( !pRec->udf )
    /* 
     *  Cause the record to process iff the record is not faulty as
     *  this will clear the pact, which is not desired for a faulty
     *  record.
     */
    (*prset->process)( pRec );

  dbScanUnlock( pRec );
}


/*
 * ---------------------------------------------------------------------------
 * aiInitRec() - initializes an AI record. This setups up all the drvAscii
 *     info, drvSerial info, and asynchronous completion callback info,
 *     all of which is attached to the record's device private field.
 */     
LOCAL long aiInitRec (struct aiRecord *pAi) 
{
  long           status;
  devAsciiPriv   *pPriv = NULL;

  /*  Initialize the record and driver. */
  status = devAsciiInitPrivate( "REAL_IN", 
				&pAi->inp, pAi, &pPriv, 
				&(pPriv->sigNum_arraySize) );

  if ( status != S_drvAscii_Ok && status != S_devAscii_Ok ) {

    return status;

  }

  /*
   *  The prompt must exist but it cannot have a data type specification.
   *
   *  The response format can be missing, in which case '%f' is assumed.
   *
   *  The response format may be string ('%nc' or '%ns') but 'n' must not
   *  be greater than 4. With a string specification the input ascii stream
   *  will be converted to an integer value (eg. "abcd" -> 0x61626364 ->
   *  1633837924). 
   *
   *  Also arrays of values are not currently supported (Jan. 1997).
   */
  
  if ( (strlen(pPriv->aio.id.pCmndArg->cmndPrompt) == 0) 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killCnt 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killAll 
       || 
       ((pPriv->aio.id.pCmndArg->cmndInfo.dataType == RBF_CHAR) 
	&&
        (pPriv->aio.id.pCmndArg->cmndInfo.dataCnt > 4) ) 
       ||
       (pPriv->aio.id.pCmndArg->cmndInfo.dataType == RBF_STRING) 
       ||
       (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType != RBF_UNDEFINED) 
       ||
       (status != S_drvAscii_Ok) ) {
    /*
     *  Something is wrong with the record so mark it for alarms.
     */
    status = S_db_badField;

    recGblRecordError( status, (void *)pAi,
		       "devAscii: (Ai init_record) Illegal format spec");

    pAi->dpvt = (void *) NULL;

    pAi->udf  = TRUE;

  } else {

    if ( pPriv->aio.id.pCmndArg->cmndInfo.dataType == RBF_CHAR )
      pPriv->aio.id.pCmndArg->cmndInfo.dataType = RBF_STRING;

    pAi->udf  = FALSE;

    pAi->dpvt = (void *) pPriv;

    /*  Set linear conversion slope. */
    pAi->eslo = pAi->eguf - pAi->egul;

    /* 
     *  Setup the async callback routine, the arg to which is a pointer  
     *  back to the record.
     */
    if (pPriv->aio.id.pCmndArg->passThru)
      pPriv->aio.pIoDoneCB = aiReadRealAsyncCompletion;
    else
      pPriv->aio.pIoDoneCB = aiReadAsyncCompletion;

    pPriv->aio.pIoDoneArg  = pAi;
  }

  return status;
}

/*
 * ---------------------------------------------------------------------------
 * aiRead() - analog input routine
 */
LOCAL long aiRead (struct aiRecord * pAi)
{
  devAsciiPriv	*pPriv = (devAsciiPriv *) pAi->dpvt;

  long		status;
  long       	rval;
  
  double        fval;

  if ( drvAsciiDebugLevel & 32 ) printf("aiRead\n");

  /* 
   *  If a read is already in progress, or the record is locked out, then 
   *  exit. 
   */
  if ( pAi->pact == TRUE ) {

    if ( pAi->udf )
      return S_devAscii_badRec; 

    else {

      if ( pPriv->aio.id.pCmndArg->passThru )
	return S_devAscii_dontConvert;
      else
	return S_devAscii_Ok; 
    }
  }

  /*
   *  Determine if the record is valid so that processing can proceed.
   */
  status = reportBadRec( (struct dbCommon *)pAi, 
			 (devAsciiPriv	*)  pAi->dpvt );

  if ( status != S_devAscii_Ok ) return status;

  /* 
   *  Perform the read and if completion is asynchronous set the pact.
   */
  if ( pPriv->aio.id.pCmndArg->passThru )
    status = drvAsciiRealIo( &pPriv->aio, &fval );
  else
    status = drvAsciiIntIo( &pPriv->aio, &rval );

  if ( status == S_drvAscii_AsyncCompletion ) {
    /*
     *  The record will be asynchronously called back when the 
     *  read completes so mark it as such.
     */
    pAi->pact = TRUE; 

    return S_devAscii_Ok;

  } else if ( status == S_drvAscii_Ok) {
    /* 
     *  The read completed non-asynchronously so update the record's
     *  value field.
     */
    if ( pPriv->aio.id.pCmndArg->passThru ) {

      pAi->val = fval;

      status = S_devAscii_dontConvert;

    } else {

      pAi->rval = rval;

      status = S_devAscii_Ok;
    }

    pAi->udf  = FALSE;

    return status;

  } else {

    return reportReadFail( (struct dbCommon *)pAi, status );

  }
}

/* 
 * ---------------------------------------------------------------------------
 *  aiReadAsyncCompletion() - Asynchronous read completion routine for AI
 */
LOCAL void aiReadAsyncCompletion(void *pArg, long status, long value)
{
  struct aiRecord      *pAi = (struct aiRecord *)pArg;
  struct dbCommon      *pRec = (struct dbCommon *)pArg;
  struct rset          *prset = (struct rset *)(pRec->rset);

  if ( drvAsciiDebugLevel & 32 ) printf("aiReadAsyncCompletion\n");

  dbScanLock( pRec );

  if ( status == S_drvAscii_Ok ) {
    /* 
     *  The read completed succesfully so copy the input data.
     */
    pAi->rval = value;

    pAi->udf  = FALSE;

  } else {

    /*
     *  The read failed so report the failure.
     */
    reportReadFail( pRec, status );

  }

  if ( !pRec->udf )
    /* 
     *  Cause the record to process iff the record is not faulty as
     *  this will clear the pact, which is not desired for a faulty
     *  record.
     */
    (*prset->process)( pRec );

  dbScanUnlock( pRec );
}

/* 
 * ---------------------------------------------------------------------------
 *  aiReadRelaAsyncCompletion() - Asynchronous read completion routine for AI
 */
LOCAL void aiReadRealAsyncCompletion(void *pArg, long status, double value)
{
  struct aiRecord      *pAi = (struct aiRecord *)pArg;
  struct dbCommon      *pRec = (struct dbCommon *)pArg;
  struct rset          *prset = (struct rset *)(pRec->rset);

  if ( drvAsciiDebugLevel & 32 ) printf("aiReadRealAsyncCompletion\n");

  dbScanLock( pRec );

  if ( status == S_drvAscii_Ok ) {
    /* 
     *  The read completed succesfully so copy the input data.
     */
    pAi->val = value;

    pAi->udf  = FALSE;

  } else {

    /*
     *  The read failed so report the failure.
     */
    reportReadFail( pRec, status );

  }

  if ( !pRec->udf )
    /* 
     *  Cause the record to process iff the record is not faulty as
     *  this will clear the pact, which is not desired for a faulty
     *  record.
     */
    (*prset->process)( pRec );

  dbScanUnlock( pRec );
}

/*
 * ---------------------------------------------------------------------------
 * aiLinearConvert() - provides for eslo calc when eguf or egul
 *     are modified.
 */
LOCAL long aiLinearConvert (struct aiRecord *pAi, int after)
{
  if( !after ) {

    return( S_devAscii_Ok );
  }
  
  /*  Set linear conversion slope. */
  pAi->eslo = pAi->eguf - pAi->egul;
  
  return( S_devAscii_Ok );
}

/*
 * ---------------------------------------------------------------------------
 * aiGetIoIntInfo() - Used for obtaining the scan private info
 */
LOCAL long aiGetIoIntInfo(int             cmd,
			  struct aiRecord *pAi,
			  IOSCANPVT       *ppvt)
{
  devAsciiPriv *pPriv = (devAsciiPriv *) pAi->dpvt;
  
  if ( pPriv ) 
    *ppvt = pPriv->spvt;
  
  else 
    *ppvt = NULL;

  return S_devAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 * aoInitRec() - initializes AO records. This setups up all the drvAscii
 *     and drvSerial info, all of which is attached to the record's device 
 *     private field.
 */
LOCAL long aoInitRec (struct aoRecord *pAo) 
{
  devAsciiPriv	*pPriv = NULL;
  long           status;
  long		 rval;
  
  /*  Initialize the record and driver. */
  status = devAsciiInitPrivate( "REAL_OUT", 
				&pAo->out, pAo, &pPriv,
				&(pPriv->sigNum_arraySize) );

  if ( status != S_drvAscii_Ok && status != S_devAscii_Ok ) {

    return status;

  }

  /*
   *  A prompt must exist. However a data type specification need not 
   *  exist, in which case '%f' is assumed.
   *
   *  Responses must not cause assignment (ie. all reponse format strings 
   *  must be of the form '%*' ). 
   *
   *  Output formats of '%nc' and '%ns' are valid but 'n' must not be 
   *  greater than 4. That is, a 4 byte integer will be converted to an 
   *  output string of 4 ascii chars (eg. 1633837924 -> 0x61626364 -> 
   *  "abcd"). Note that the resultant ascii chars may not be printable 
   *  ascii chars (eg 23200612 -> 0x01620364 -> ^Ab^Cd, where ^A is 
   *  control A -> 0x01).
   *
   *  Also arrays of values are not currently supported (Jan. 1997).
   */
  if ( (strlen(pPriv->aio.id.pCmndArg->cmndPrompt) == 0)  
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killCnt 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killAll 
       ||
      ((pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType == RBF_CHAR) 
       &&
        (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataCnt > 4) ) 
       ||
       (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType == RBF_STRING)
       ||
       (pPriv->aio.id.pCmndArg->cmndInfo.dataType != RBF_UNDEFINED) 
       ||
       (status != S_drvAscii_Ok) ) {
    /*
     *  Something is wrong with the record so mark it for alarms.
     */
    status = S_db_badField;

    recGblRecordError( status, (void *)pAo,
		       "devAscii: (Ao init_record) Illegal format spec");

    pAo->dpvt = (void *) NULL;

    pAo->udf  = TRUE;

  } else {

    if ( pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType == RBF_CHAR )
      pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType = RBF_STRING;

    pAo->udf  = FALSE;

    pAo->dpvt = pPriv;

    /* 
     *  Setup the async callback routine, the arg to which is a pointer  
     *  back to the record.
     */
    pPriv->aio.pIoDoneCB  = devAsciiWriteAsyncCompletion;
    pPriv->aio.pIoDoneArg = pAo;
    
    /* 
     *  Set linear conversion slope.
     */
    pAo->eslo = pAo->eguf - pAo->egul;
    
    if ( status == OK
	 && 
	 strlen( pPriv->aio.id.pCmndArg->rbvPrompt ) > 0 )

      /*  If possible get an initial value for the record. */
      if ( drvAsciiReadOutput( &pPriv->aio.id, &rval ) != OK 
	   || 
	   pAo->pini ) {

	status = S_devAscii_dontConvert;

      } else {

	/*  Set the initial value. */
	pAo->rval = (long) rval;

      } else status = S_devAscii_dontConvert;
  }

  return status; 
}

/*
 * ---------------------------------------------------------------------------
 * aoWrite() - analog output routine
 */
LOCAL long aoWrite (struct aoRecord * pAo)
{
  devAsciiPriv 	*pPriv = (devAsciiPriv *)pAo->dpvt;

  long		status;

  double        fval;

  if ( drvAsciiDebugLevel & 32 ) printf("aoWrite\n");

  /* 
   *  If a write is already in progress, or the record is locked-out,
   *  then exit.
   */
  if ( pAo->pact == TRUE ) {

    if ( pAo->udf )
      return S_devAscii_badRec; 

    else {

      if ( pPriv->aio.id.pCmndArg->passThru )
	return S_devAscii_dontConvert;
      else
	return S_devAscii_Ok; 
    }
  }
  
  /*
   *  Determine if the record is valid so that processing can proceed.
   */
  status = reportBadRec( (struct dbCommon *)pAo, 
			 (devAsciiPriv	*)  pAo->dpvt );

  if ( status != S_devAscii_Ok ) return status;
  
  /* 
   *  Perform the write. 
   */
  if ( pPriv->aio.id.pCmndArg->passThru ) {

    fval = pAo->oval;

    status = drvAsciiRealIo( &pPriv->aio, &fval );

  } else {

    fval = pAo->rval;

    status = drvAsciiRealIo( &pPriv->aio, &fval );
  }

  if ( status == S_drvAscii_AsyncCompletion ) {
    /*
     *  The record will be asynchronously called back when the 
     *  write completes so mark it as such.
     */
    pAo->pact = TRUE;
    
    status = S_devAscii_Ok;

  } else if ( status != S_drvAscii_Ok ) {
    /*
     *  The write failed so set the record's alarm states.
     */
    return reportWriteFail( (struct dbCommon *)pAo, status );
  }

  pAo->udf = FALSE;

  return S_devAscii_Ok;
}

/*
 * ---------------------------------------------------------------------------
 * aoLinearConvert() - provides for eslo calc when eguf or egul are modified.
 */
LOCAL long aoLinearConvert (struct aoRecord *pAo, int after)
{
  if( !after ) return( S_devAscii_Ok );

  /*  Set linear conversion slope. */
  pAo->eslo = pAo->eguf - pAo->egul;

  return( S_devAscii_Ok );
}


/*
 * ---------------------------------------------------------------------------
 * biInitRec() - initializes BI records. This setups up all the drvAscii
 *     info, drvSerial info, and asynchronous completion callback info, 
 *     all of which is attached to the record's device private field.
 */
LOCAL long biInitRec(struct biRecord *pBi) 
{
  long         status;
  devAsciiPriv *pPriv= NULL;
  
  /*  Initialize the record and driver. */
  status = devAsciiInitPrivate( "INTEGER_IN", 
				&pBi->inp, pBi, &pPriv, 
				&(pPriv->sigNum_arraySize) );
  
  if ( status != S_drvAscii_Ok && status != S_devAscii_Ok ) {

    return status;

  }

  /*
   *  The prompt must exist but it cannot have a data type specification.
   *
   *  The response format can be missing, in which case '%d' is assumed.
   *
   *  The response format may be char ('%nc') but 'n' must not
   *  be greater than 4. With a string specification the input ascii stream
   *  will be converted to an integer value (eg. "abcd" -> 0x61626364 ->
   *  1633837924). 
   *
   *  Also arrays of values are not currently supported (Jan. 1997).
   */
  
  if ( (strlen(pPriv->aio.id.pCmndArg->cmndPrompt) == 0) 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killCnt 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killAll 
       || 
       ((pPriv->aio.id.pCmndArg->cmndInfo.dataType == RBF_CHAR) 
	&&
        (pPriv->aio.id.pCmndArg->cmndInfo.dataCnt > 4) ) 
       ||
       (pPriv->aio.id.pCmndArg->cmndInfo.dataType == RBF_STRING) 
       ||
       (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType != RBF_UNDEFINED) 
       ||
       (status != S_drvAscii_Ok) ) {
    /*
     *  Something is wrong with the record so mark it for alarms.
     */
    status = S_db_badField;

    recGblRecordError( status, (void *)pBi,
		       "devAscii: (Bi init_record) Illegal format spec");

    pBi->dpvt = (void *) NULL;

    pBi->udf = TRUE;

  } else {

    if ( pPriv->aio.id.pCmndArg->cmndInfo.dataType == RBF_CHAR )
      pPriv->aio.id.pCmndArg->cmndInfo.dataType = RBF_STRING;

    pBi->udf = FALSE;

    pBi->dpvt = (void *) pPriv;
    
    /* 
     *  Setup the async callback routine, the arg to which is a pointer  
     *  back to the record.
     */
    pPriv->aio.pIoDoneCB  = biReadAsyncCompletion;
    pPriv->aio.pIoDoneArg = pBi;
    
    pBi->mask = 1;
  }

  return status;
}

/*
 * ---------------------------------------------------------------------------
 * Used for obtaining the scan private info
 */
LOCAL long biGetIoIntInfo( int             cmd,
			   struct biRecord *pBi,
			   IOSCANPVT	   *ppvt)
{
  devAsciiPriv *pPriv = (devAsciiPriv *) pBi->dpvt;
  
  if ( pPriv ) 
    *ppvt = pPriv->spvt;
  
  else 
    *ppvt = NULL;

  return S_devAscii_Ok;
}

/*
 * ---------------------------------------------------------------------------
 * biRead() - BI input routine
 */
LOCAL long biRead (struct biRecord * pBi)
{
  devAsciiPriv 	*pPriv = (devAsciiPriv *)pBi->dpvt;
  long		val;
  long		status;
  
  if ( drvAsciiDebugLevel & 32 ) printf("biRead\n");

  /* 
   *  If a read is already in progress, or the record is locked out, then 
   *  exit. 
   */
  if ( pBi->pact ) {

    if ( pBi->udf )
      return S_devAscii_badRec; 

    else
      return S_devAscii_Ok; 
  }

  /*
   *  Determine if the record is valid so that processing can proceed.
   */
  status = reportBadRec( (struct dbCommon *)pBi, 
			 (devAsciiPriv	*)  pBi->dpvt );

  if ( status != S_devAscii_Ok ) return status;

  /* 
   *  Perform the read and if completion is asynchronous set the pact.
   */
  status = drvAsciiIntIo( &pPriv->aio, &val );

  if ( status == S_drvAscii_AsyncCompletion ) {
    /*
     *  The record will be asynchronously called back when the 
     *  read completes so mark it as such.
     */
    pBi->pact = TRUE;

    return S_devAscii_Ok;

  } else if ( status == S_drvAscii_Ok) {
    /* 
     *  The read completed non-asynchronously so update the record's
     *  value field.
     */
    pBi->rval = val;

    pBi->udf  = FALSE;

    return S_devAscii_Ok;

  } else {

    return reportReadFail( (struct dbCommon *)pBi, status );
  }
}

/* 
 * ---------------------------------------------------------------------------
 *  biReadAsyncCompletion() - Asynchronous read completion routine for BI
 */
LOCAL void biReadAsyncCompletion(void *pArg, long status, long value)
{
  struct biRecord      *pBi = (struct biRecord *)pArg;
  struct dbCommon      *pRec = (struct dbCommon *)pArg;
  struct rset          *prset = (struct rset *)(pRec->rset);
  
  if ( drvAsciiDebugLevel & 32 ) printf("biReadAsyncCompletion\n");

  dbScanLock( pRec );

  if ( status == S_drvAscii_Ok ) {
    /* 
     *  The read completed succesfully so copy the input data.
     */
    pBi->rval = value;

    pBi->udf  = FALSE;

  } else {

    /*
     *  The read failed so report the failure.
     */
    reportReadFail( pRec, status );

  }

  if ( !pRec->udf )
    /* 
     *  Cause the record to process iff the record is not faulty as
     *  this will clear the pact, which is not desired for a faulty
     *  record.
     */
    (*prset->process)( pRec );

  dbScanUnlock( pRec );
}


/*
 * ---------------------------------------------------------------------------
 * boInitRec() - initializes BO records. This setups up all the drvAscii 
 *     and drvSerial info, all of which is attached to the record's  
 *     device private field.
 */
LOCAL long boInitRec(struct boRecord *pBo) 
{
  long          status;
  devAsciiPriv	*pPriv = NULL;
  long		rval;
   
  /*  Initialize the record and driver. */
  status = devAsciiInitPrivate( "INTEGER_OUT", 
				&pBo->out, pBo, &pPriv,
				&(pPriv->sigNum_arraySize) );
   
  if ( status != S_drvAscii_Ok && status != S_devAscii_Ok ) {

    return status;

  }

  /*
   *  A prompt must exist. However a data type specification need 
   *  not exist,in which case '%f' is assumed.
   *
   *  Responses must not cause assignment (ie. all reponse format 
   *  strings must be of the form '%*' ). 
   *
   *  Output formats of '%nc' and '%ns' are valid but 'n' must not
   *  be greater than 4. That is, a 4 byte integer will be converted 
   *  to an output string of 4 ascii chars (eg. 1633837924 -> 0x61626364
   *  -> "abcd"). Note that the resultant ascii chars may not be 
   *  printable ascii chars (eg 23200612 -> 0x01620364 -> ^Ab^Cd, 
   *  where ^A is control A -> 0x01).
   *
   *  Also arrays of values are not currently supported (Jan. 1997).
   */
  if ( (strlen(pPriv->aio.id.pCmndArg->cmndPrompt) == 0)  
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killCnt 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killAll 
       || 
      ((pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType == RBF_CHAR) 
       &&
        (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataCnt > 4) ) 
       ||
       (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType == RBF_STRING)
       ||
       (pPriv->aio.id.pCmndArg->cmndInfo.dataType != RBF_UNDEFINED) 
       ||
       (status != S_drvAscii_Ok) ) {
    /*
     *  Something is wrong with the record so mark it for alarms.
     */
    status = S_db_badField;

    recGblRecordError( status, (void *)pBo,
		       "devAscii: (Bo init_record) Illegal format spec");

    pBo->dpvt = (void *) NULL;

    pBo->udf  = TRUE;

  } else {

    if ( pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType == RBF_CHAR )
      pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType = RBF_STRING;

    pBo->udf  = FALSE;

    pBo->dpvt = pPriv;
    
    /* 
     *  Setup the async callback routine, the arg to which is a pointer  
     *  back to the record.
     */
    pPriv->aio.pIoDoneCB  = devAsciiWriteAsyncCompletion;
    pPriv->aio.pIoDoneArg = pBo;
    
    pBo->mask = 1;
    
    if ( status == OK 
	 && 
	 strlen( pPriv->aio.id.pCmndArg->rbvPrompt ) > 0 ) {

      /*  If possible get an initial value for the record. */
      status = drvAsciiReadOutput( &pPriv->aio.id, &rval );
    
      if ( status == OK ) {

	/*  Set the initial value. */
	pBo->rval = (long) rval;
	
	status = S_devAscii_Ok;
      }
    }
  }

  return status;
}

/*
 * ---------------------------------------------------------------------------
 * boWrite() - BO output routine
 */
LOCAL long boWrite (struct boRecord * pBo)
{
  devAsciiPriv	*pPriv = (devAsciiPriv *)pBo->dpvt;
  int		status;
  long		rval;
  
  if ( drvAsciiDebugLevel & 32 ) printf("boWrite\n");

  /* 
   *  If a write is already in progress, or the record is locked-out,
   *  then exit. 
   */
  if ( pBo->pact ) {
  
    if ( pBo->udf )
      return S_devAscii_badRec; 

    else
      return S_devAscii_Ok; 
  }
  
  /*
   *  Determine if the record is valid so that processing can proceed.
   */
  status = reportBadRec( (struct dbCommon *)pBo, 
			 (devAsciiPriv	*)  pBo->dpvt );

  if ( status != S_devAscii_Ok ) return status;
  
  rval = pBo->rval & pBo->mask;

  /* 
   *  Perform the write. 
   */
  status = drvAsciiIntIo( &pPriv->aio, &rval );

  if ( status == S_drvAscii_AsyncCompletion ) {
    /*
     *  The record will be asynchronously called back when the 
     *  write completes so mark it as such.
     */
    pBo->pact = TRUE;
    
    status = S_devAscii_Ok;

  } else if ( status != S_drvAscii_Ok ) {
    /*
     *  The write failed so set the record's alarm states.
     */
    return reportWriteFail( (struct dbCommon *)pBo, status );
  }

  pBo->udf = FALSE;

  return S_devAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 * mbbiInitRec() - initializes MBBI records. This setups up all the drvAscii 
 *     info, drvSerial info, and asynchronous completion callback info, 
 *     all of which is attached to the record's device private field.
 */
LOCAL long mbbiInitRec(struct mbbiRecord *pMbbi)
{
  devAsciiPriv *pPriv = NULL;
  long         status;
  
  /*  Initialize the record and driver. */
  status = devAsciiInitPrivate( "INTEGER_IN", 
				&pMbbi->inp, pMbbi, &pPriv,
				&(pPriv->sigNum_arraySize) );
  
  if ( status != S_drvAscii_Ok && status != S_devAscii_Ok ) {

    return status;

  }

  /*
   *  The prompt must exist but it cannot have a data type specification.
   *
   *  The response format can be missing, in which case '%f' is assumed.
   *
   *  The response format may be string ('%nc' or '%ns') but 'n' must not
   *  be greater than 4. With a string specification the input ascii stream
   *  will be converted to an integer value (eg. "abcd" -> 0x61626364 ->
   *  1633837924). 
   *
   *  Also arrays of values are not currently supported (Jan. 1997).
   */
  
  if ( (strlen(pPriv->aio.id.pCmndArg->cmndPrompt) == 0) 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killCnt 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killAll 
       || 
       ((pPriv->aio.id.pCmndArg->cmndInfo.dataType == RBF_CHAR) 
	&&
        (pPriv->aio.id.pCmndArg->cmndInfo.dataCnt > 4) ) 
       ||
       (pPriv->aio.id.pCmndArg->cmndInfo.dataType == RBF_STRING) 
	||
       (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType != RBF_UNDEFINED) 
       ||
       (status != S_drvAscii_Ok) ) {
    /*
     *  Something is wrong with the record so mark it for alarms.
     */
    status = S_db_badField;

    recGblRecordError( status, (void *)pMbbi,
		       "devAscii: (Mbbi init_record) Illegal format spec");

    pMbbi->dpvt = (void *) NULL;

    pMbbi->udf  = TRUE;

  } else {

    if ( pPriv->aio.id.pCmndArg->cmndInfo.dataType == RBF_CHAR )
      pPriv->aio.id.pCmndArg->cmndInfo.dataType = RBF_STRING;

    pMbbi->udf  = FALSE;

    pMbbi->dpvt = pPriv;
    
    /* 
     *  Setup the async callback routine, the arg to which is a pointer back 
     *  to the record.
     */
    pPriv->aio.pIoDoneCB  = mbbiReadAsyncCompletion;
    pPriv->aio.pIoDoneArg = pMbbi;
    
    pMbbi->shft = 0;
  }

  return status;
}

/*
 * ---------------------------------------------------------------------------
 * mbbiGetIoIntInfo() - Used for obtaining the scan private info
 */
LOCAL long mbbiGetIoIntInfo( int                 cmd,
			     struct mbbiRecord   *pMbbi,
			     IOSCANPVT           *ppvt)
{
  devAsciiPriv	*pPriv = (devAsciiPriv *) pMbbi->dpvt;
  
  if ( pPriv ) 
    *ppvt = pPriv->spvt;
  
  else 
    *ppvt = NULL;

  return S_devAscii_Ok;
}

/*
 * ---------------------------------------------------------------------------
 * mbbiRead() - MBBI input routine
 */
LOCAL long mbbiRead (struct mbbiRecord * pMbbi)
{
  devAsciiPriv	*pPriv = (devAsciiPriv *) pMbbi->dpvt;
  long   	 val;
  long		 status;
  
  if ( drvAsciiDebugLevel & 32 ) printf("mbbiRead\n");

  /* 
   *  If a read is already in progress, or the record is locked out, then 
   *  exit. 
   */
  if ( pMbbi->pact ) { 

    if ( pMbbi->udf )
      return S_devAscii_badRec; 

    else
      return S_devAscii_Ok; 
  }

  /*
   *  Determine if the record is valid so that processing can proceed.
   */
  status = reportBadRec( (struct dbCommon *)pMbbi, 
			 (devAsciiPriv	*)  pMbbi->dpvt );

  if ( status != S_devAscii_Ok ) return status;

  /* 
   *  Perform the read and if completion is asynchronous set the pact.
   */
  status = drvAsciiIntIo(&pPriv->aio, &val);

  if (status == S_drvAscii_AsyncCompletion) {
    /*
     *  The record will be asynchronously called back when the 
     *  read completes so mark it as such.
     */
    pMbbi->pact = TRUE;

    return S_devAscii_Ok;

  } else if ( status == S_drvAscii_Ok) {
    /* 
     *  The read completed non-asynchronously so update the record's
     *  value field.
     */
    pMbbi->rval = val;

    pMbbi->udf  = FALSE;

    return S_devAscii_Ok;

  } else {

    return reportReadFail( (struct dbCommon *)pMbbi, status );

  }
}

/* 
 * ---------------------------------------------------------------------------
 * mbbiReadAsyncCompletion() -  Asynchronous read completion routine for BI
 */
LOCAL void mbbiReadAsyncCompletion(void *pArg, long status, long value)
{
  struct mbbiRecord *pMbbi= (struct mbbiRecord *)pArg;
  struct dbCommon   *pRec = (struct dbCommon *)pArg;
  struct rset       *prset = (struct rset *)(pRec->rset);
  
  if ( drvAsciiDebugLevel & 32 ) printf("mbbiReadAsyncCompletion\n");

  dbScanLock( pRec );

  if ( status == S_drvAscii_Ok ) {
    /* 
     *  The read completed succesfully so copy the input data.
     */
    pMbbi->rval = value;

    pMbbi->udf  = FALSE;

  } else {
    /*
     *  The read failed so report the failure.
     */
    reportReadFail( pRec, status );

  }

  if ( !pRec->udf )
    /* 
     *  Cause the record to process iff the record is not faulty as
     *  this will clear the pact, which is not desired for a faulty
     *  record.
     */
    (*prset->process)( pRec );

  dbScanUnlock( pRec );
}


/*
 * ---------------------------------------------------------------------------
 * mbboInitRec() - initializes MBBO records. This setups up all the drvAscii 
 *     and drvSerial info, all of which is attached to the record's device 
 *     private field.
 */
LOCAL long mbboInitRec(struct mbboRecord *pMbbo)
{
  long		 status;
  devAsciiPriv	*pPriv = NULL;
  long 		 rval;
  
  /*  Initialize the record and driver. */
  status = devAsciiInitPrivate( "INTEGER_OUT", 
				&pMbbo->out, pMbbo, &pPriv,
				&(pPriv->sigNum_arraySize) );
  
  if ( status != S_drvAscii_Ok && status != S_devAscii_Ok ) {

    return status;

  }

  /*
   *  A prompt must exist. However a data type specification need 
   *  not exist, in which case '%f' is assumed.
   *
   *  Responses must not cause assignment (ie. all reponse format   
   *  stringsmust be of the form '%*' ). 
   *
   *  Output formats of '%nc' and '%ns' are valid but 'n' must not 
   *  be greater than 4. That is, a 4 byte integer will be converted 
   *  to an output string of 4 ascii chars (eg. 1633837924 -> 
   *  0x61626364 -> "abcd"). Note that the resultant ascii chars may
   *  not be printable ascii chars (eg 23200612 -> 0x01620364 -> 
   *  ^Ab^Cd, where ^A is control A -> 0x01).
   *
   *  Also arrays of values are not currently supported (Jan. 1997).
   */
  if ( (strlen(pPriv->aio.id.pCmndArg->cmndPrompt) == 0)  
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killCnt 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killAll 
       || 
       ((pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType == RBF_CHAR) 
	&&
        (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataCnt > 4) ) 
       ||
       (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType == RBF_STRING) 
	||
       (pPriv->aio.id.pCmndArg->cmndInfo.dataType != RBF_UNDEFINED) 
       ||
       (status != S_drvAscii_Ok) ) {
    /*
     *  Something is wrong with the record so mark it for alarms.
     */
    status = S_db_badField;

    recGblRecordError( status, (void *)pMbbo,
		       "devAscii: (Mbbo init_record) Illegal format spec");

    pMbbo->dpvt = (void *) NULL;

    pMbbo->udf = TRUE;

  } else {

    if ( pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType == RBF_CHAR )
      pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType = RBF_STRING;

    pMbbo->udf = FALSE;

    pMbbo->dpvt = (void *) pPriv;
    
    /* 
     *  Setup the async callback routine, the arg to which is a pointer  
     *  back to the record.
     */
    pPriv->aio.pIoDoneCB  = devAsciiWriteAsyncCompletion;
    pPriv->aio.pIoDoneArg = pMbbo;
    
    pMbbo->shft = 0;
    
    if ( status == OK 
	 && 
	 strlen( pPriv->aio.id.pCmndArg->rbvPrompt ) > 0 ) {

      /*  If possible get an initial value for the record. */
      status = drvAsciiReadOutput( &pPriv->aio.id, &rval );
      
      if ( status == OK ) {

	/*  Set the initial value. */
	pMbbo->rval = (long) rval;
	
	status = S_devAscii_Ok;
      }
    }
  }

  return status;
}

/*
 * ---------------------------------------------------------------------------
 * mbboWrite() - MBBO output routine
 */
LOCAL long mbboWrite (struct mbboRecord *pMbbo)
{
  devAsciiPriv	*pPriv = (devAsciiPriv *) pMbbo->dpvt;
  int		status;
  long		rval;
  
  if ( drvAsciiDebugLevel & 32 ) printf("mbboWrite\n");

  /* 
   * if a write is already in progress, or the record is locked-out,
   * then exit 
   */
  if ( pMbbo->pact ) {
 
    if ( pMbbo->udf )
      return S_devAscii_badRec; 

    else
      return S_devAscii_Ok; 
  }
  
  /*
   *  Determine if the record is valid so that processing can proceed.
   */
  status = reportBadRec( (struct dbCommon *)pMbbo, 
			 (devAsciiPriv	*)  pMbbo->dpvt );

  if ( status != S_devAscii_Ok ) return status;
  
  /* 
   *  The use of NOBT and MASK not currently implemented.
   */
  rval = pMbbo->rval;

  /* 
   *  Perform the write. 
   */
  status = drvAsciiIntIo( &pPriv->aio, &rval );

  if ( status == S_drvAscii_AsyncCompletion ) {
    /*
     *  The record will be asynchronously called back when the 
     *  write completes so mark it as such.
     */
    pMbbo->pact = TRUE;
    
    status = S_devAscii_Ok;

  } else if ( status != S_drvAscii_Ok ) {
    /*
     *  The write failed so set the record's alarm states.
     */
    return reportWriteFail( (struct dbCommon *)pMbbo, status );
    
  }

  pMbbo->udf = FALSE;

  return S_devAscii_Ok;
}
 

/*
 * ---------------------------------------------------------------------------
 * siInitRec() - initializes stringin records. This setups up all the 
 *     drvAscii info, drvSerial info, and asynchronous completion callback 
 *     info, all of which is attached to the record's device private field.
 */
LOCAL long siInitRec(struct stringinRecord *pSi)
{
  devAsciiPriv  *pPriv = NULL;
  long          status = S_devAscii_Ok;
  
  /*  Initialize the record and driver. */
  status = devAsciiInitPrivate( "STRING_IN",
				&pSi->inp, pSi, &pPriv,	
				&(pPriv->sigNum_arraySize) );

  if ( status != S_drvAscii_Ok && status != S_devAscii_Ok ) {

    return status;

  }

  /*
   *  The prompt must exist. if %s exists in the prompt then the contents
   *  of VAL will be used as part or all of the prompt.
   */
  if ( pPriv->aio.id.pCmndArg->cmndPromptInfo.killCnt 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killAll 
       || 
       (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType != RBF_UNDEFINED 
	&&
	pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType != RBF_STRING ) 
       ||
       (pPriv->aio.id.pCmndArg->cmndInfo.dataType != RBF_STRING 
	&&
	pPriv->aio.id.pCmndArg->cmndInfo.dataType != RBF_UNDEFINED) 
       ||
       (status != S_drvAscii_Ok) ) {
    /*
     *  Something is wrong with the record so mark it for alarms.
     */
    status = S_db_badField;

    recGblRecordError( status, (void *)pSi,
		       "devAscii: (stringIn init_record) Illegal format spec");

    pSi->udf  = TRUE;

    pSi->dpvt = (void *) NULL;

  } else {

    pSi->udf  = FALSE;

    pSi->dpvt = pPriv;
    
    /* 
     *  Setup the async callback routine, the arg to which is a pointer back 
     *  to the record.
     */
    pPriv->aio.pIoDoneCB  = siReadAsyncCompletion;
    pPriv->aio.pIoDoneArg = pSi;

    pPriv->aio.id.respStr[0] = '\0';
    
    pSi->val[0] = '\0';
  }

  return status;
}

/*
 * ---------------------------------------------------------------------------
 * siGetIoIntInfo() - Used for obtaining the scan private info
 */
LOCAL long siGetIoIntInfo(   int                     cmd,
			     struct stringinRecord   *pSi,
			     IOSCANPVT               *ppvt)
{
  devAsciiPriv	*pPriv = (devAsciiPriv *) pSi->dpvt;
  
  if ( pPriv ) 
    *ppvt = pPriv->spvt;

  else 
    *ppvt = NULL;

  return S_devAscii_Ok;
}

/*
 * ---------------------------------------------------------------------------
 * siRead() - stringin input routine
 */
LOCAL long siRead (struct stringinRecord * pSi)
{
  devAsciiPriv	*pPriv = (devAsciiPriv *) pSi->dpvt;
  drvAsyncIO    *pDrvAsyncIO = &pPriv->aio;

  long		 status;
  long           nelm;

  if ( drvAsciiDebugLevel & 32 ) printf("siRead\n");

  /* 
   *  If a read is already in progress, or the record is locked out, then 
   *  exit. 
   */
  if ( pSi->pact ) { 

    if ( pSi->udf )
      return S_devAscii_badRec;
 
    else
      return S_devAscii_Ok; 
  }

  /*
   *  Determine if the record is valid so that processing can proceed.
   */
  status = reportBadRec( (struct dbCommon *)pSi, 
			 (devAsciiPriv	*)  pSi->dpvt );

  if ( status != S_devAscii_Ok ) return status;

  /* 
   *  Perform the read and if completion is asynchronous set the pact.
   */
  if ( pDrvAsyncIO->id.pCmndArg->cmndPromptInfo.dataType == RBF_STRING ) 
    /* 
     *  We do not check whether or not the val field is null. If it is null
     *  then only the current write command terminator will be output. Note
     *  that this could be anything by setting the readCMT record.
     */
    status = drvAsciiStringIo(&pPriv->aio, pSi->val );

  else
    status = drvAsciiStringIo( &pPriv->aio, "" );

  if ( status == S_drvAscii_AsyncCompletion ) {
    /*
     *  The record will be asynchronously called back when the 
     *  read completes so mark it as such.
     */
    pSi->pact = TRUE;

    return S_devAscii_Ok;

  } else if ( status == S_drvAscii_Ok) {
    /* 
     *  The read completed non-asynchronously so update the record's
     *  value field.
     */
    nelm = min( pDrvAsyncIO->id.respStrCnt, sizeof( pSi->val ) );

    strncpy( pSi->val, pDrvAsyncIO->id.respStr, nelm );      
    pSi->val[nelm] = '\0';

    pSi->udf = FALSE;

    return S_devAscii_Ok;

  } else {

    return reportReadFail( (struct dbCommon *)pSi, status );

  }
}

/* 
 * ---------------------------------------------------------------------------
 * siReadAsyncCompletion() -  Asynchronous read completion routine for 
 *   stringin records
 */
LOCAL void siReadAsyncCompletion( void *pArg, long status, long value )
{
  struct stringinRecord *pSi = (struct stringinRecord *)pArg;
  struct dbCommon       *pRec = (struct dbCommon *)pArg;
  struct rset           *prset = (struct rset *)(pRec->rset);
  devAsciiPriv          *pDevAsciiPriv = (devAsciiPriv *)(pSi->dpvt);
  drvAsyncIO            *pDrvAsyncIO = &pDevAsciiPriv->aio;

  long                   nelm;

  if ( drvAsciiDebugLevel & 32 ) printf("siReadAsyncCompletion\n");

  dbScanLock( pRec );

  if ( status == S_drvAscii_Ok ) {
    /* 
     *  The read completed succesfully so copy the input data.
     */
    nelm = min( pDrvAsyncIO->id.respStrCnt, sizeof( pSi->val ) );

    strncpy( pSi->val, pDrvAsyncIO->id.respStr, nelm ); 
    pSi->val[nelm] = '\0';

    pSi->udf = FALSE;
    
  } else {
    /*
     *  The read failed so report the failure.
     */
    reportReadFail( pRec, status );

  }

  if ( !pRec->udf )
    /* 
     *  Cause the record to process iff the record is not faulty as
     *  this will clear the pact, which is not desired for a faulty
     *  record.
     */
    (*prset->process)( pRec );

  dbScanUnlock( pRec );
}



/*
 * ---------------------------------------------------------------------------
 * soInitRec() - initializes stringout records. This setups up
 *     all the drvAscii and drvSerial info, all of which is attached 
 *     to the record's device private field.
 */
LOCAL long soInitRec(struct stringoutRecord *pSo)
{
  long		status;
  devAsciiPriv	*pPriv = NULL;
  
  /*  Initialize the record and driver. */
  status = devAsciiInitPrivate( "STRING_OUT", 
				&pSo->out, pSo, &pPriv,
				&(pPriv->sigNum_arraySize) );
    
  if ( status != S_drvAscii_Ok && status != S_devAscii_Ok ) {

    return status;

  }

  /*
   *  A prompt need not exist. However if a prompt does exist and a data type 
   *  is specified then the data type must be string (ie. '%ns' is the only
   *  valid option). If no data type is specified then '%s' is assumed.
   *
   *  Responses must not cause assignment (ie. all reponse format strings  
   *  must be of the form '%*' ). 
   *
   *  Also arrays of values are not currently supported (Jan. 1997).
   */
  if ( (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType != RBF_STRING 
	&&
        pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType != RBF_UNDEFINED) 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killCnt 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killAll 
       || 
       (pPriv->aio.id.pCmndArg->cmndInfo.dataType != RBF_UNDEFINED) 
       ||
        (status != S_drvAscii_Ok) ) {
    /*
     *  Something is wrong with the record so mark it for alarms.
     */
    status = S_db_badField;

    recGblRecordError(status, (void *)pSo,
		      "devAscii: (stringOut init_record) Illegal format spec");

    pSo->dpvt = (void *) NULL;

    pSo->udf = TRUE;

  } else {

    pSo->udf = FALSE;

    pSo->dpvt = (void *) pPriv;

    /* 
     *  Setup the async callback routine, the arg to which is a pointer  
     *  back to the record.
     */
    pPriv->aio.pIoDoneCB  = devAsciiWriteAsyncCompletion;
    pPriv->aio.pIoDoneArg = pSo;
    
  }

  return status;
}

/*
 * ---------------------------------------------------------------------------
 * soWrite() - string output routine
 */
LOCAL long soWrite (struct stringoutRecord *pSo)
{
  devAsciiPriv	*pPriv = (devAsciiPriv *) pSo->dpvt;
  int		 status;

  if ( drvAsciiDebugLevel & 32 ) printf("soWrite\n");

  /* 
   *  If a write is already in progress, or the record is locked-out,
   *  then exit.
   */
  if ( pSo->pact ) { 

    if ( pSo->udf )
      return S_devAscii_badRec; 

    else
      return S_devAscii_Ok; 
  }
  
  /*
   *  Determine if the record is valid so that processing can proceed.
   */
  status = reportBadRec( (struct dbCommon *)pSo, 
			 (devAsciiPriv	*)  pSo->dpvt );

  if ( status != S_devAscii_Ok ) return status;
  
  /* 
   *  Perform the write.
   */
  /*
  if ( (strlen(pPriv->aio.id.pCmndArg->cmndPrompt) > 0) 
       &&
       (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType == RBF_UNDEFINED ) ) 

    status = drvAsciiStringIo( &pPriv->aio, "" );

  else 
  */
  if (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType == RBF_STRING)
    status = drvAsciiStringIo( &pPriv->aio, pSo->val );
  else
    status = drvAsciiStringIo( &pPriv->aio, "" );

  if ( status == S_drvAscii_AsyncCompletion ) {
    /*
     *  The record will be asynchronously called back when the 
     *  write completes so mark it as such.
     */
    pSo->pact = TRUE;
    
    status = S_devAscii_Ok;

  } else if ( status != S_drvAscii_Ok ) {
    /*
     *  The write failed so set the record's alarm states.
     */
    return reportWriteFail( (struct dbCommon *)pSo, status );
  }

  pSo->udf = FALSE;

  return S_devAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 * liInitRec()- initializes longin records. This setups up
 *     all the drvAscii info, drvSerial info, and asynchronous completion
 *     callback info, all of which is attached to the record's device
 *     private field.
 */
LOCAL long liInitRec(struct longinRecord *pLi) 
{
  long         status;
  devAsciiPriv *pPriv = NULL;

  /*  Initialize the record and driver. */
  status = devAsciiInitPrivate( "INTEGER_IN", 
				&pLi->inp, pLi, &pPriv,
				&(pPriv->sigNum_arraySize) );
  
  if ( status != S_drvAscii_Ok && status != S_devAscii_Ok ) {

    return status;

  }

  /*
   *  The prompt must exist but it cannot have a data type specification.
   *
   *  The response format can be missing, in which case '%f' is assumed.
   *
   *  The response format may be string ('%nc' or '%ns') but 'n' must not
   *  be greater than 4. With a string specification the input ascii stream
   *  will be converted to an integer value (eg. "abcd" -> 0x61626364 ->
   *  1633837924). 
   *
   *  Also arrays of values are not currently supported (Jan. 1997).
   */
  
  if ( (strlen(pPriv->aio.id.pCmndArg->cmndPrompt) == 0) 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killCnt 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killAll 
       || 
       ((pPriv->aio.id.pCmndArg->cmndInfo.dataType == RBF_CHAR) 
	&&
        (pPriv->aio.id.pCmndArg->cmndInfo.dataCnt > 4) ) 
       ||
       (pPriv->aio.id.pCmndArg->cmndInfo.dataType == RBF_STRING) 
	||
       (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType != RBF_UNDEFINED) 
       ||
       (status != S_drvAscii_Ok) ) {
    /*
     *  Something is wrong with the record so mark it for alarms.
     */
    status = S_db_badField;

    recGblRecordError( status, (void *)pLi,
		       "devAscii: (longIn init_record) Illegal format spec");

    pLi->dpvt = (void *) NULL;

    pLi->udf  = TRUE;

  } else {

    if ( pPriv->aio.id.pCmndArg->cmndInfo.dataType == RBF_CHAR )
      pPriv->aio.id.pCmndArg->cmndInfo.dataType = RBF_STRING;

    pLi->udf  = FALSE;

    pLi->dpvt = pPriv;
  
    /* 
     *  Setup the async callback routine, the arg to which is a pointer back 
     *  to the record
     */
    pPriv->aio.pIoDoneCB  = liReadAsyncCompletion;
    pPriv->aio.pIoDoneArg = pLi;
  }

  return status;
}

/* 
 * ---------------------------------------------------------------------------
 * liGetIoIntInfo() -  Asynchronous read completion routine for AI
 */
LOCAL long liGetIoIntInfo( int                 cmd,
			   struct longinRecord *pLi,
			   IOSCANPVT	       *ppvt)
{
  devAsciiPriv *pPriv = (devAsciiPriv *) pLi->dpvt;
  
  if ( pPriv ) 
    *ppvt = pPriv->spvt;
  
  else 
    *ppvt = NULL;

  return S_devAscii_Ok;
}

/*
 * ---------------------------------------------------------------------------
 * liRead() - longin input routine
 */
LOCAL long liRead (struct longinRecord * pLi)
{
  devAsciiPriv 	*pPriv = (devAsciiPriv *)pLi->dpvt;
  long		val;
  long		status;
  
  if ( drvAsciiDebugLevel & 32 ) printf("liRead\n");

  /* 
   *  If a read is already in progress, or the record is locked out, then 
   *  exit. 
   */
  if ( pLi->pact == TRUE ) {

    if ( pLi->udf )
      return S_devAscii_badRec;
 
    else
      return S_devAscii_Ok; 
  }

  /*
   *  Determine if the record is valid so that processing can proceed.
   */
  status = reportBadRec( (struct dbCommon *)pLi, 
			 (devAsciiPriv	*)  pLi->dpvt );

  if ( status != S_devAscii_Ok ) return status;

  /* 
   *  Perform the read and if completion is asynchronous then
   *  set the pact 
   */
  status = drvAsciiIntIo( &pPriv->aio, &val );

  if ( status == S_drvAscii_AsyncCompletion ) {
    /*
     *  The record will be asynchronously called back when the 
     *  read completes so mark it as such.
     */
    pLi->pact = TRUE;

    return S_devAscii_Ok;

  } else if ( status == S_drvAscii_Ok) {
    /* 
     *  The read completed non-asynchronously so update the record's
     *  value field.
     */
    pLi->val = val;

    pLi->udf = FALSE;

    return S_devAscii_Ok;

  } else {

    return reportReadFail( (struct dbCommon *)pLi, status );

  }
}

/* 
 * ---------------------------------------------------------------------------
 * liReadAsyncCompletion() -  Asynchronous read completion routine for longin
 */
LOCAL void liReadAsyncCompletion(void *pArg, long status, long value)
{
  struct longinRecord   *pLi = (struct longinRecord *)pArg;
  struct dbCommon       *pRec = (struct dbCommon *)pArg;
  struct rset           *prset = (struct rset *)(pRec->rset);
  
  if ( drvAsciiDebugLevel & 32 ) printf("liReadAsyncCompletion\n");

  dbScanLock( pRec );

  if ( status == S_drvAscii_Ok ) {
    /* 
     *  The read completed succesfully so update the record's value field.
     */
    pLi->val = value;

    pLi->udf = FALSE;

  } else {
    /*
     *  The read failed so report the failure.
     */
    reportReadFail( pRec, status );

  }

  if ( !pRec->udf )
    /* 
     *  Cause the record to process iff the record is not faulty as
     *  this will clear the pact, which is not desired for a faulty
     *  record.
     */
    (*prset->process)( pRec );

  dbScanUnlock( pRec );
}


/*
 * ---------------------------------------------------------------------------
 * loInitRec()- initializes longout records. This setups up all the drvAscii 
 *     and drvSerial info, all of which is attached to the record's device 
 *     private field.
 */
LOCAL long loInitRec(struct longoutRecord *pLo) 
{
  long          status;
  devAsciiPriv	*pPriv = NULL;
  long		rval;
  
  /*  Initialize the record and driver. */
  status = devAsciiInitPrivate( "INTEGER_OUT", 
				&pLo->out, pLo, &pPriv,
				&(pPriv->sigNum_arraySize) );
   
  if ( status != S_drvAscii_Ok && status != S_devAscii_Ok ) {

    return status;

  }

  /*
   *  A prompt must exist. However a data type specification need 
   *  not exist, in which case '%f' is assumed.
   *
   *  Responses must not cause assignment (ie. all reponse format 
   *  strings must be of the form '%*' ). 
   *
   *  Output formats of '%nc' and '%ns' are valid but 'n' must not
   *  be greater than 4. That is, a 4 byte integer will be converted 
   *  to an output string of 4 ascii chars (eg. 1633837924 -> 0x61626364 
   *  -> "abcd"). Note that the resultant ascii chars may not be 
   *  printable ascii chars (eg 23200612 -> 0x01620364 -> ^Ab^Cd, 
   *  where ^A is control A -> 0x01).
   *
   *  Also arrays of values are not currently supported (Jan. 1997).
   */
  if ( (strlen(pPriv->aio.id.pCmndArg->cmndPrompt) == 0)  
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killCnt 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killAll 
       || 
      ((pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType == RBF_CHAR) 
       &&
        (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataCnt > 4) ) 
       ||
       (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType == RBF_STRING) 
       ||
       (pPriv->aio.id.pCmndArg->cmndInfo.dataType != RBF_UNDEFINED) 
       ||
       (status != S_drvAscii_Ok) ) {
    /*
     *  Something is wrong with the record so mark it for alarms.
     */
    status = S_db_badField;

    recGblRecordError( status, (void *)pLo,
		       "devAscii: (longOut init_record) Illegal format spec");

    pLo->dpvt = (void *) NULL;

    pLo->udf  = TRUE;

  } else {

    if ( pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType == RBF_CHAR )
      pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType = RBF_STRING;

    pLo->udf  = FALSE;

    pLo->dpvt = pPriv;
    
    /* 
     *  Setup the async callback routine, the arg to which is a pointer  
     *  back to the record.
     */
    pPriv->aio.pIoDoneCB  = devAsciiWriteAsyncCompletion;
    pPriv->aio.pIoDoneArg = pLo;
    
    /*  Even on error ensure the record's fields are initialized. */
    if ( status == OK 
	 && 
	 strlen( pPriv->aio.id.pCmndArg->rbvPrompt ) > 0 ) {
      /*  If possible get an initial value for the record. */
      status = drvAsciiReadOutput( &pPriv->aio.id, &rval );

      if ( status == OK ) {
	/*  Set the initial value. */
	pLo->val = rval;

	status = S_devAscii_Ok;
      }
    }
  }

  return status;
}

/*
 * ---------------------------------------------------------------------------
 * loWrite() - longout output routine
 */
LOCAL long loWrite (struct longoutRecord * pLo)
{
  devAsciiPriv	*pPriv = (devAsciiPriv *)pLo->dpvt;
  int		 status;
  long		 rval;
  
  if ( drvAsciiDebugLevel & 32 ) printf("loWrite\n");

  /* 
   *  If a write is already in progress, or the record is locked-out,
   *  then exit. 
   */
  if ( pLo->pact ) {

    if ( pLo->udf )
      return S_devAscii_badRec;
 
    else
      return S_devAscii_Ok; 
  }
  
  /*
   *  Determine if the record is valid so that processing can proceed.
   */
  status = reportBadRec( (struct dbCommon *)pLo, 
			 (devAsciiPriv	*)  pLo->dpvt );

  if ( status != S_devAscii_Ok ) return status;

  rval = pLo->val;

  /* 
   *  Perform the write. 
   */
  status = drvAsciiIntIo( &pPriv->aio, &rval );

  if ( status == S_drvAscii_AsyncCompletion ) {
    /*
     *  The record will be asynchronously called back when the 
     *  write completes so mark it as such.
     */
    pLo->pact = TRUE;
    
    status = S_devAscii_Ok;

  } else if ( status != S_drvAscii_Ok ) {
    /*
     *  The write failed so set the record's alarm states.
     */
    return reportWriteFail( (struct dbCommon *)pLo, status );
  }

  pLo->udf = FALSE;

  return S_devAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 * mbbiDirectInitRec() - initializes MBBI direct records. This setups up
 *     all the drvAscii info, drvSerial info, and asynchronous completion
 *     callback info, all of which is attached to the record's device
 *     private field.
 */
LOCAL long mbbiDirectInitRec(struct mbbiDirectRecord *pMbbi)
{
  devAsciiPriv *pPriv = NULL;
  long         status;
  
  /*  Initialize the record and driver. */
  status = devAsciiInitPrivate( "INTEGER_IN", 
				&pMbbi->inp, pMbbi, &pPriv,
				&(pPriv->sigNum_arraySize) );
  
  if ( status != S_drvAscii_Ok && status != S_devAscii_Ok ) {

    return status;

  }

  /*
   *  The prompt must exist but it cannot have a data type specification.
   *
   *  The response format can be missing, in which case '%f' is assumed.
   *
   *  The response format may be string ('%nc' or '%ns') but 'n' must not
   *  be greater than 4. With a string specification the input ascii stream
   *  will be converted to an integer value (eg. "abcd" -> 0x61626364 ->
   *  1633837924). 
   *
   *  Also arrays of values are not currently supported (Jan. 1997).
   */
  
  if ( (strlen(pPriv->aio.id.pCmndArg->cmndPrompt) == 0) 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killCnt 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killAll 
       || 
       ((pPriv->aio.id.pCmndArg->cmndInfo.dataType == RBF_CHAR) 
	&&
        (pPriv->aio.id.pCmndArg->cmndInfo.dataCnt > 4) ) 
       ||
       (pPriv->aio.id.pCmndArg->cmndInfo.dataType == RBF_STRING) 
	||
       (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType != RBF_UNDEFINED) 
       ||
       (status != S_drvAscii_Ok) ) {
    /*
     *  Something is wrong with the record so mark it for alarms and
     *  disable its processing.
     */
    status = S_db_badField;

    if ( pMbbi->udf == FALSE ) {

      recGblRecordError( status, (void *)pMbbi,
			 "devAscii: (Mbbi direct init_record) Illegal format spec");

      recGblSetSevr( (struct dbCommon *)pMbbi, UDF_ALARM, INVALID_ALARM );

    }

    pMbbi->dpvt = (void *) NULL;

    pMbbi->udf = TRUE;

  } else {

    if ( pPriv->aio.id.pCmndArg->cmndInfo.dataType == RBF_CHAR )
      pPriv->aio.id.pCmndArg->cmndInfo.dataType = RBF_STRING;

    pMbbi->udf = FALSE;

    pMbbi->dpvt = pPriv;
    
    /* 
     *  Setup the async callback routine, the arg to which is a pointer back 
     *  to the record.
     */
    pPriv->aio.pIoDoneCB  = mbbiDirectReadAsyncCompletion;
    pPriv->aio.pIoDoneArg = pMbbi;
    
    pMbbi->shft = 0;
  }

  return status;
}

/*
 * ---------------------------------------------------------------------------
 * mbbiDirectGetIoIntInfo() - Used for obtaining the scan private info
 */
LOCAL long mbbiDirectGetIoIntInfo( int                     cmd,
				   struct mbbiDirectRecord *pMbbi,
				   IOSCANPVT               *ppvt)
{
  devAsciiPriv	*pPriv = (devAsciiPriv *) pMbbi->dpvt;
  
  if ( pPriv ) 
    *ppvt = pPriv->spvt;

  else 
    *ppvt = NULL;

  return S_devAscii_Ok;
}

/*
 * ---------------------------------------------------------------------------
 * mbbiDirectRead() - MBBI direct input routine
 */
LOCAL long mbbiDirectRead (struct mbbiDirectRecord * pMbbi)
{
  devAsciiPriv	*pPriv = (devAsciiPriv *) pMbbi->dpvt;
  long   	val;
  long		status;
  
  if ( drvAsciiDebugLevel & 32 ) printf("mbbiDirectRead\n");

  /* 
   *  If a read is already in progress, or the record is locked out, then 
   *  exit.
   */
  if ( pMbbi->pact ) {

    if ( pMbbi->udf ) 
      return S_devAscii_badRec; 

    else 
      return S_devAscii_Ok; 
  }

  /*
   *  Determine if the record is valid so that processing can proceed.
   */
  status = reportBadRec( (struct dbCommon *)pMbbi, 
			 (devAsciiPriv	*)  pMbbi->dpvt );

  if ( status != S_devAscii_Ok ) return status;

  /* 
   *  Perform the read and if completion is asynchronous then
   *  set the pact 
   */
  status = drvAsciiIntIo( &pPriv->aio, &val );

  if ( status == S_drvAscii_AsyncCompletion ) {
    /*
     *  The record will be asynchronously called back when the 
     *  read completes so mark it as such.
     */
    pMbbi->pact = TRUE;

    return S_devAscii_Ok;

  } else if ( status == S_drvAscii_Ok )  {
    /* 
     *  The read completed non-asynchronously so update the record's
     *  value field.
     */
    pMbbi->rval = val;

    pMbbi->udf  = FALSE;

    return S_devAscii_Ok;

  } else if (status) {

    return reportReadFail( (struct dbCommon *)pMbbi, status );

  }
  
    return S_devAscii_Ok;
}

/* 
 * ---------------------------------------------------------------------------
 * mbbiDirectReadAsyncCompletion() -  Asynchronous read completion routine 
 *   for MBBI direct
 */
LOCAL void mbbiDirectReadAsyncCompletion(void *pArg, long status, long value)
{
  struct mbbiDirectRecord *pMbbi= (struct mbbiDirectRecord *)pArg;
  struct dbCommon         *pRec = (struct dbCommon *)pArg;
  struct rset             *prset = (struct rset *)(pRec->rset);
  
  if ( drvAsciiDebugLevel & 32 ) printf("mbbiDirectReadAsyncCompletion\n");

  dbScanLock( pRec );

  if ( status == S_drvAscii_Ok ) {
    /* 
     *  The read completed succesfully so update the record's value field.
     */
    pMbbi->rval = value;

    pMbbi->udf  = FALSE;

  } else {
    /*
     *  The read failed so report the failure.
     */
    reportReadFail( pRec, status );

  }

  if ( !pRec->udf )
    /* 
     *  Cause the record to process iff the record is not faulty as
     *  this will clear the pact, which is not desired for a faulty
     *  record.
     */
    (*prset->process)( pRec );

  dbScanUnlock( pRec );
}


/*
 * ---------------------------------------------------------------------------
 * mbboDirectInitRec() - initializes MBBO direct records. This setups up
 *     all the drvAscii and drvSerial info, all of which is attached 
 *     to the record's device private field.
 */
LOCAL long mbboDirectInitRec(struct mbboDirectRecord *pMbbo)
{
  long		status;
  devAsciiPriv	*pPriv = NULL;
  long 		rval;
  
  /*  Initialize the record and driver. */
  status = devAsciiInitPrivate( "INTEGER_OUT",
				&pMbbo->out, pMbbo, &pPriv,
				&(pPriv->sigNum_arraySize) );
  
  if ( status != S_drvAscii_Ok && status != S_devAscii_Ok ) {

    return status;

  }

  /*
   *  A prompt must exist. However a data type specification need 
   *  not exist, in which case '%f' is assumed.
   *
   *  Responses must not cause assignment (ie. all reponse format 
   *  strings must be of the form '%*' ). 
   *
   *  Output formats of '%nc' and '%ns' are valid but 'n' must not 
   *  be greater than 4. That is, a 4 byte integer will be converted 
   *  to an output string of 4 ascii chars (eg. 1633837924 -> 0x61626364 
   *  -> "abcd"). Note that the resultant ascii chars may not be 
   *  printable ascii chars (eg 23200612 -> 0x01620364 -> ^Ab^Cd, 
   *  where ^A is control A -> 0x01).
   *
   *  Also arrays of values are not currently supported (Jan. 1997).
   */
  if ( (strlen(pPriv->aio.id.pCmndArg->cmndPrompt) == 0)  
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killCnt 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killAll 
       || 
       ((pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType == RBF_CHAR) 
	&&
        (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataCnt > 4) ) 
       ||
       (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType == RBF_STRING) 
	||
       (pPriv->aio.id.pCmndArg->cmndInfo.dataType != RBF_UNDEFINED) 
       ||
       (status != S_drvAscii_Ok) ) {
    /*
     *  Something is wrong with the record so mark it for alarms.
     */
    status = S_db_badField;

    if ( pMbbo->udf == FALSE ) {

      recGblRecordError( status, (void *)pMbbo,
			 "devAscii: (Mbbo direct init_record) Illegal format spec");

      recGblSetSevr( (struct dbCommon *)pMbbo, UDF_ALARM, INVALID_ALARM );
    }

    pMbbo->dpvt = (void *) NULL;

    pMbbo->udf  = TRUE;

  } else {

    if ( pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType == RBF_CHAR )
      pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType = RBF_STRING;

    pMbbo->udf  = FALSE;

    pMbbo->dpvt = (void *) pPriv;
    
    /* 
     *  Setup the async callback routine, the arg to which is a pointer  
     *  back to the record.
     */
    pPriv->aio.pIoDoneCB  = devAsciiWriteAsyncCompletion;
    pPriv->aio.pIoDoneArg = pMbbo;
    
    pMbbo->shft = 0;
    
    if ( status == OK 
	 && 
	 strlen( pPriv->aio.id.pCmndArg->rbvPrompt ) > 0 ) {
      /*  If possible get an initial value for the record. */
      status = drvAsciiReadOutput( &pPriv->aio.id, &rval );
    
      if ( status == OK ) {
	/*  Set the initial value. */
	pMbbo->rval = rval;

	status = S_devAscii_Ok;
      }
    }
  }

  return status;
}

/*
 * ---------------------------------------------------------------------------
 * mbboDirectWrite() - MBBO direct output routine
 */
LOCAL long mbboDirectWrite (struct mbboDirectRecord *pMbbo)
{
  devAsciiPriv	*pPriv = (devAsciiPriv *) pMbbo->dpvt;
  int		status;
  long		rval;
  
  if ( drvAsciiDebugLevel & 32 ) printf("mbboDirectWrite\n");

  /* 
   *  If a write is already in progress, or the record is locked-out,
   *  then exit.
   */
  if ( pMbbo->pact ) {

    if ( pMbbo->udf ) 
      return S_devAscii_badRec; 
    
    else 
      return S_devAscii_Ok; 
  }
  
  /*
   *  Determine if the record is valid so that processing can proceed.
   */
  status = reportBadRec( (struct dbCommon *)pMbbo, 
			 (devAsciiPriv	*)pMbbo->dpvt );

  if ( status != S_devAscii_Ok ) return status;

  /* 
   *  Use of NOBT and MASK not currently implemented.
   */
  rval = pMbbo->rval;

  /* 
   *  Perform the write.
   */
  status = drvAsciiIntIo( &pPriv->aio, &rval );

  if ( status == S_drvAscii_AsyncCompletion ) {
    /*
     *  The record will be asynchronously called back when the 
     *  write completes so mark it as such.
     */
    pMbbo->pact = TRUE;
    
    status = S_devAscii_Ok;

  } else if ( status != S_drvAscii_Ok ) {
    /*
     *  The write failed so set the record's alarm states.
     */
    return reportWriteFail( (struct dbCommon *)pMbbo, status );

  }

  pMbbo->udf = FALSE;

  return S_devAscii_Ok;
}
 

/*
 * ---------------------------------------------------------------------------
 * wfInitRec() - initializes waveform records. The waveform record is used
 *     as a long stringin record, that is form inputting strings that are
 *     greater than default stringin record size (40 bytes).
 *
 *     This setups up all the drvAscii info, drvSerial info, and asynchronous
 *     completion callback info, all of which is attached to the record's 
 *     device private field.
 */
LOCAL long wfInitRec(struct waveformRecord *pWf)
{
  devAsciiPriv  *pPriv = NULL;
  long          status = S_devAscii_Ok;
  
  if ( (pWf->ftvl != DBF_CHAR) && (pWf->ftvl != DBF_STRING) ) {

    status = S_db_badField;

    /*  
     *  The record is invalid so generate an error and an alarm then 
     *  ensure the record is disabled so it does not process in the
     *  future.
     */
    recGblRecordError( status, (void *)pWf,
		       "devAscii: (waveform init_record) Illegal format spec");

    recGblSetSevr( (struct dbCommon *)pWf, UDF_ALARM, INVALID_ALARM );

    pWf->pact = TRUE;
    pWf->udf  = TRUE;

    return status;
  }

  /*  Initialize the record and driver. */
  status = devAsciiInitPrivate( "STRING_IN",
				&pWf->inp, pWf, &pPriv,	
				&(pPriv->sigNum_arraySize) );

  if ( status != S_drvAscii_Ok && status != S_devAscii_Ok ) {

    return status;

  }

  /*
   *  The prompt must exist but it cannot have a data type specification.
   *
   */
  if ( (strlen(pPriv->aio.id.pCmndArg->cmndPrompt) == 0) 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killCnt 
       ||
       pPriv->aio.id.pCmndArg->cmndPromptInfo.killAll 
       || 
       (pPriv->aio.id.pCmndArg->cmndPromptInfo.dataType != RBF_UNDEFINED) 
       ||
       pPriv->aio.id.pCmndArg->cmndInfo.dataType == RBF_CHAR
       ||
       (pPriv->aio.id.pCmndArg->cmndInfo.dataType != RBF_STRING
	&&
        pPriv->aio.id.pCmndArg->cmndInfo.dataType != RBF_UNDEFINED) 
       ||
       (status != S_drvAscii_Ok) ) { 
    /*
     *  Something is wrong with the record so mark it for alarms
     *  and disable it's processing.
     */
    status = S_db_badField;

    if ( pWf->udf == FALSE ) {

      recGblRecordError( status, (void *)pWf,
			 "devAscii: (waveform init_record) Illegal format spec");

      recGblSetSevr( (struct dbCommon *)pWf, UDF_ALARM, INVALID_ALARM );
    }

    pWf->dpvt = (void *) NULL;
    pWf->udf  = TRUE;

  } else {

    pWf->udf  = FALSE;

    pWf->dpvt = pPriv;
    
    /* 
     *  Setup the async callback routine, the arg to which is a pointer back 
     *  to the record
     */
    pPriv->aio.pIoDoneCB  = wfReadAsyncCompletion;
    pPriv->aio.pIoDoneArg = pWf;

    pPriv->aio.id.respStr[0] = '\0';
    
    ((char *)pWf->bptr)[0] = '\0';
  }

  return status;
}

/*
 * ---------------------------------------------------------------------------
 * wfGetIoIntInfo() - Used for obtaining the scan private info
 */
LOCAL long wfGetIoIntInfo(   int                     cmd,
			     struct waveformRecord   *pWf,
			     IOSCANPVT               *ppvt)
{
  devAsciiPriv	*pPriv = (devAsciiPriv *) pWf->dpvt;
  
  if ( pPriv ) 
    *ppvt = pPriv->spvt;
  
  else 
    *ppvt = NULL;

  return S_devAscii_Ok;
}

/*
 * ---------------------------------------------------------------------------
 * wfRead() - waveform input routine
 */
LOCAL long wfRead (struct waveformRecord * pWf)
{
  devAsciiPriv	*pPriv = (devAsciiPriv *) pWf->dpvt;
  drvAsyncIO    *pDrvAsyncIO = &pPriv->aio;
  long		 status;
  
  if ( drvAsciiDebugLevel & 32 ) printf("wfRead\n");

  /* 
   *  If a read is already in progress, or the record is locked out, then 
   *  exit. 
   */
  if ( pWf->pact ) {

    if ( pWf->udf )
      return S_devAscii_badRec; 

    else
      return S_devAscii_Ok; 
  }

  /*
   *  Determine if the record is valid so that processing can proceed.
   */
  status = reportBadRec( (struct dbCommon *)pWf, 
			 (devAsciiPriv	*)  pWf->dpvt );

  if ( status != S_devAscii_Ok ) return status;

  /* 
   *  Perform the read and if completion is asynchronous set the pact.
   */
  if ( pDrvAsyncIO->id.pCmndArg->cmndPromptInfo.dataType == RBF_STRING ) 
    /* 
     *  We do not check whether or not the val field is null. If it is null
     *  then only the current write command terminator will be output. Note
     *  that this could be anything by setting the readCMT record.
     */
    status = drvAsciiStringIo(&pPriv->aio, pWf->val );

  else
  status = drvAsciiStringIo( &pPriv->aio, "" );

  if ( status == S_drvAscii_AsyncCompletion ) {
    /*
     *  The record will be asynchronously called back when the 
     *  read completes so mark it as such.
     */
    pWf->pact = TRUE;

    return S_devAscii_Ok;
      
  } else if ( status == S_drvAscii_Ok )  {
    /* 
     *  The read completed non-asynchronously so copy the input into
     *  the record.
     */
    pWf->nord = pWf->nelm < pDrvAsyncIO->id.respStrCnt
      ? pWf->nelm : pDrvAsyncIO->id.respStrCnt;

    strncpy( pWf->bptr, pDrvAsyncIO->id.respStr, pWf->nord );

    pWf->udf = FALSE;
    
    return S_devAscii_Ok;
    
  } else {

    return reportReadFail( (struct dbCommon *)pWf, status );
    
  }
}

/* 
 * ---------------------------------------------------------------------------
 * wfReadAsyncCompletion() -  Asynchronous read completion routine for 
 *   wave form records
 */
LOCAL void wfReadAsyncCompletion( void *pArg, long status, long value )
{
  struct waveformRecord *pWf = (struct waveformRecord *)pArg;
  struct dbCommon       *pRec = (struct dbCommon *)pArg;
  struct rset           *prset = (struct rset *)(pRec->rset);
  devAsciiPriv          *pDevAsciiPriv = (devAsciiPriv *)(pWf->dpvt);
  drvAsyncIO            *pDrvAsyncIO = &pDevAsciiPriv->aio;
 
  if ( drvAsciiDebugLevel & 32 ) printf("wfReadAsyncCompletion\n");

  dbScanLock( pRec );

  if ( status == S_drvAscii_Ok ) {
    /* 
     *  The read completed succesfully so copy the input into the record.
     */
    pWf->nord = pWf->nelm < pDrvAsyncIO->id.respStrCnt 
      ? pWf->nelm : pDrvAsyncIO->id.respStrCnt;

    strncpy( pWf->bptr, pDrvAsyncIO->id.respStr, pWf->nord );
 
    pWf->udf = FALSE;

  } else {
    /*
     *  The read failed so report the failure.
     */
    reportReadFail( (struct dbCommon *)pWf, status );

  }

  if ( !pRec->udf )
    /* 
     *  Cause the record to process iff the record is not faulty as
     *  this will clear the pact, which is not desired for a faulty
     *  record.
     */
    (*prset->process)( pRec );

  dbScanUnlock( pRec );
}



/*+*********************************************************************
  $Log: devAscii.c,v $
  Revision 1.1  2003/01/14 00:27:40  ktsubota
  Initial insertion

  Revision 1.6  2002/09/05 17:55:29  ahoney
  Analog output behavior was modified to use OVAL rather than VAL. This
  was done to preserve the filtering functionality provided by OROC.

  Revision 1.5  2002/09/03 20:53:11  ahoney
        1. Significant modifications, within drvAscii.c, in regards to
         the handling of synchronization semaphores so as to alleviate
         potential loss of read/write synchronization. Although the
         synchronization problem was infrequent it sometimes required a
         processor reboot in order to correct the problem. Hopefully,
         synchronization will now be auto-magically re-established.

        2. Analog records may now have 'REAL' specified in their parm fields,
         after the link specification and before the first prompt format
         field.

         If an analog input record has the REAL attribute then the value
         returned form the remote device is written into the record's VAL
         field and RVAL/ESLO conversions are bypassed. Note that also
         bypasses the 'slope' record behavior. Similarly, analog output
         records will have their VAL values output to the remote device
         rather than their RVAL values. In general this eliminates the
         conversions to/fro real and integer which caused loss of
         precision, as well as, reducing the considerable complexity in using
         drvAscii for analog records.

        3. The format-string delimiters no longer have to be '<' and '>'
         for all records. Now the first character follwing the link
         specification is assumed to be the field delimiter, with the pairs
         '<>', '()', '{}', and '[]' assumed, when the left hand delimiter
         is encountered. These are now valid (on a record-by-record basis):
                      @/tyco/0 <status?> <%s>
                      @/tyco/0 (status?) (%s)
                      @/tyco/0 !status?! !%s!
                      @/tyco/0  status?   %s
                      @/tyco/0 Xstatus?X X%sX

        4. User-specified framing routines can be specified.
         With this release one can create their own input and output
         framing routines and have them override the default getFrame()
         and putFrame() routines. This allows one to do more sophisticated
         packet framing (e.g. a simple checksum could be added/checked).

         To specify special framing routines one must download the library,
         they created, containing those routine then register the
         functions with drvAsciiSetTxFunc() and drvAsciiSetRxFunc(), all
         prior to iocInit. Note that a different set of framing routines
         can be registered for each serial link. Also note that there are
         special requirements imposed on the framing routines. An example
         exists within drvAscii.c

        5. More extensive control of debugging information via link-specific
         debug records and via a drvAscii global variable drvAsciiDebugLevel.

        6. Format specifications may now have embedded hex or octal bytes
         Said bytes must be of the form '\xnn' or '\ooo'. Those bytes are
         translated when the format specs are parsed during record init.
         For instance a prompt format such as <\x58\x59\x5a?> will result
         in the string 'XYZ?' being output to the remote device.

        7. All numeric escape codes, that exist in output or input data
         streams, will be automatically translated. For instance a
         stringIn record with this prompt and response format '<%s?><%s>'
         can be manipulated with "caput stringIn '\x58'" to result
         in a prompt of 'X?' being transmitted. This should simplify record
         manipulation for those apps which talk to multi-dropped devices
         whose addresses are leading non-printing ASCII bytes. The numeric
         escape codes need not result in a printable ASCII characters,
         that is, "caput stringIn '\x81'" is valid. However, Beware that
         drvAscii uses sprintf and sscanf so embedding a null byte will
         cause unexpected behavior.

  Revision 1.4  2000/05/06 02:05:51  ktsubota
  Incorporated changes by A.Honey

  Revision 1.2  1999/07/16 04:20:09  ahoney
  Corrected a bug in mbboInitRec.
  Updated a few statuses.

  Revision 1.1  1998/12/03 23:56:10  ktsubota
  Initial insertion

  Revision 1.13  1998/04/03 23:13:16  ahoney
  Removed the previous mod for string out records, as it changes previous
  behaviour.

  Revision 1.12  1998/03/18 01:39:20  ahoney
  Updated the processing for stringIn records to allows for dynamic
  prompts. This is accomplished by using the VAL field for output when
  prompting and input when a response arrives.

  Revision 1.11  1997/02/08 00:49:21  ahoney
  Removed setting of PACT=1 on failure within mbbireadasynccompletion

  As this would permanently disable the relevant record(s).

 * Revision 1.10  1997/01/23  02:14:03  ahoney
 * Within string input routines I changes strlen( pSi->val ) to
 * sizeof( pSi->val ). These were potential bugs.
 *
 * Revision 1.9  1997/01/21  02:37:55  ahoney
 * Mods to better handle conversion from integer to strings. This is
 * still unacceptable as drvAscii cannot handle null bytes as they
 * appear to be end-of-string terminators.
 *
 * Revision 1.8  1997/01/20  20:50:56  ahoney
 * Extensive mods to accomodate:
 *   -added mbbi and mbbo direct records;
 *   -added waveform records (long stringin);
 *   -added conversion of binary streams '0' and '1', with or without
 *    delimiters
 *   -added conversion from string to numeric 'abcd'->0x61626364->1633837924
 *   -added support for muliple line input strings
 *
 * Revision 1.7  1996/12/18  23:27:30  ahoney
 * Mods to support ao,bi,bo,mbbi,mbbo,longin,longout,stringin, and stringout
 * records. These were necessitated for use with IFSM and chopper.
 *
 * Revision 1.6  1996/09/13  21:04:59  ahoney
 * removed '#define LOCAL' which used during debugging.
 *
 * Revision 1.5  1996/09/13  20:49:50  ahoney
 * Mods to accomodate the flat lamp device.
 *
 * Note this driver was completed only so far as was necessary for the
 * data acquisition systems. The drive will still need a few mods for
 * the IFSM.
 *
 * Revision 1.4  1996/09/11  21:51:48  ahoney
 * Mods to incorporate longin and longout records as needed for the
 * dome flat lamps. Also modified the handling of '%nk' in data formats
 * so that 'n' characters can be 'killed' at the beginning of a data
 * stream - this allows stripping leading NULLs,...
 *
 * Revision 1.3  1996/08/14  18:37:55  ahoney
 * Modified all async completion routines so that PACT is set false on
 * errors. This was done to correct the problem when the serial link is
 * down on startup.
 *
 * Revision 1.2  1996/07/13  00:38:03  ahoney
 * Removed some debugging printf's.
 *
 * Revision 1.1  1996/07/12  23:26:22  ahoney
 * drvAscii is a new directory for support for serial comms to remote
 * devices via ascii strings
 *
 *
***********************************************************************/
 


static char rcsid[] = "$Id: drvAscii.c,v 1.2 2003/02/08 01:10:54 ktsubota Exp $";
 
/* 
 * *
 *  Author: Allan Honey (for the KECK Observatory)
 *  Date: 5-30-96 
 *
 *  drvAscii was based on an earlier driver for the Compumotor SX motor
 *  controllers, which was written by Jeff Hill for the Keck observatory.
 *
 *  NOTES
 *  The callback mechanism and interface to drvSerial was derived from
 *  drvCmSx which was written by Jeff Hill for the Keck observatory.
 *
 *  The driver support requires the facilities provided in drvSerial.c. 
 *
 *  The basic design is to format an output string, from the data supplied 
 *  by device support, and pass a resultant request-for-output to the 
 *  drvSerial transmit task. Included in the request is the routine which 
 *  is to be used for handling the i/o. For instance, if the output string 
 *  was a query for data then a response is expected so the transmit task 
 *  must be blocked until the response is received. The drvSerial receive 
 *  task, which is passed a parsing routine (getFrame) on startup, unblocks 
 *  the transmitter when the response is received.
 * 
 *  The interface to drvAscii is via seven functions.
 *
 *  On driver init the function drvAsciiInit() must be called one time.
 *  This function is the drvInitFunc_t which is in the drvAsciiSio structure
 *  specified in drvSup.ascii. drvAsciiInit initializes: the global linkList,
 *  which is a list of all serial links configured in drvAscii; the 
 *  globalMutex which is the semaphore for the linkList. 
 *
 *  Note that the function drvAsciiReport is also in the drvAsciiSio 
 *  structure.
 *
 *  During device init the function drvAsciiInitiateAll() is called one 
 *  time from devAscii. This function currently does nothing.
 *
 *  During record init the function drvAsciiCreateSioLink() is called once
 *  from devAscii for each record. Suffice it to say that this function
 *  ensures that a link is established via drvSerial, and caches information
 *  needed to complete a transaction with the remote device for the record
 *  in question, including the asynchronous callback info associated with
 *  devAscii.
 *
 *  During record init, an additional function, namely drvAsciiReadOutput(), 
 *  is called from devAscii for each output record. This function provides 
 *  for obtaining initial values for outputs.
 *
 *  During normal record processing, devAscii interfaces to drvAscii with 
 *  the functions drvAsciiIntIo(), drvAsciiRealIo(), and drvAsciiStringIo().
 *  Of course this is in addition to the asynchronous completion routines
 *  which are part of devAscii.
 *
 */
/*
 *  CHANGES - in general see the cvs history at the bottom.
 *
 *  April 2000 - added a writer counter for queued messages. This was
 *               necessary as prior to this it was possible to get
 *               out of synchronization with reading and writing in
 *               such a way that syncrhonization could not be re-established.
 *        
 *               Now, when synchronization is lost, the received messages
 *               are not flushed until the writer count becomes 1. This
 *               means that all queued messages will fail before the 
 *               problem is resolved. Note that if messages are queued
 *               faster than the write/read cycle to the remote device,
 *               when an error occurs (no response to a prompt for example),
 *               then it is still possible that synchronization will never
 *               be re-established.
 *
 *             - added callbacks to the epics output records for those
 *               output records which expect to receive a response. Prior
 *               to this, output record failures would not cause the
 *               associated record to be set into an alarm state. Now
 *               responses are checked and a record's alarm state is
 *               appropriately set on response failures.
 *
 *               Note that this mod may have eliminated the need for
 *               the synchronization mod described above.
 *
 */
/*
 *  ANSI C
 */
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#ifdef __linux
#include <stdint.h>
#endif
#ifdef __alpha
#define uint8_t	unsigned char
#endif
#include <stdarg.h>

/*
 * EPICS
 */
#include <epicsInterrupt.h>
#include <epicsRingBytes.h>
#include <epicsMutex.h>
#include <epicsEvent.h>
#include <epicsThread.h>
#include <epicsTime.h>
#include <epicsAssert.h>
#include <epicsPrint.h>
#include <epicsEvent.h>

#include <errMdef.h>
#include <ellLib.h>
#include <drvSup.h>
#include <dbDefs.h>
#include <dbScan.h>
#include "drvSerial.h"
#include "drvAscii.h" 

#ifdef vxWorks
#include <vxWorks.h>
#include <semLib.h>
#include <ioLib.h>
#include <devLib.h> /* S_dev_noMemory */
#else
/*
 * extracted from: /home/vw/m5.3.1.ppc/target/h/semLib.h
 */
/* binary semaphore initial state */
typedef enum            /* SEM_B_STATE */
    {
    SEM_EMPTY,                  /* 0: semaphore not available */
    SEM_FULL                    /* 1: semaphore available */
    } SEM_B_STATE;

/*
 * extracted from: /home/vw/m5.3.1.ppc/target/h/ioLib.h
 */
/* ioctl function codes */

#define FIONREAD        1               /* get num chars available to read */
#define FIOFLUSH        2               /* flush any chars in buffers */
#define FIOOPTIONS      3               /* set options (FIOSETOPTIONS) */
#define FIOBAUDRATE     4               /* set serial baud rate */
#define FIODISKFORMAT   5               /* format disk */
#define FIODISKINIT     6               /* initialize disk directory */
#define FIOSEEK         7               /* set current file char position */
#define FIOWHERE        8               /* get current file char position */
#define FIODIRENTRY     9               /* return a directory entry (obsolete)*/
#define FIORENAME       10              /* rename a directory entry */
#define FIOREADYCHANGE  11              /* return TRUE if there has been a
                                           media change on the device */
#define FIONWRITE       12              /* get num chars still to be written */
#define FIODISKCHANGE   13              /* set a media change on the device */
#define FIOCANCEL       14              /* cancel read or write on the device */
#define FIOSQUEEZE      15              /* squeeze out empty holes in rt-11
                                         * file system */
/*
 * extracted from: /home/vw/m5.3.1.ppc/target/h/ioLib.h
 */

#if     !defined(NULL)
#define NULL            0
#endif

#if     !defined(EOF) || (EOF!=(-1))
#define EOF             (-1)
#endif

#if     !defined(FALSE) || (FALSE!=0)
#define FALSE           0
#endif

#if     !defined(TRUE) || (TRUE!=1)
#define TRUE            1
#endif


#define NONE            (-1)    /* for times when NULL won't do */
#define EOS             '\0'    /* C string terminator */

/* timeout defines */

#define NO_WAIT         0
#define WAIT_FOREVER    (3600)

/* return status values */

#define OK              0
#define ERROR           (-1)

/*
 * extracted from: /home/vw/m5.3.1.ppc/target/h/types/vwTypesOld.h
 * included by vxWorks.h
 */
/* modes - must match O_RDONLY/O_WRONLY/O_RDWR in ioLib.h! */

#define READ            0
#define WRITE           1
#define UPDATE          2

#endif /* vxWorks */



/*
 *  driver support entry table
 */
long  drvAsciiReport(int level);
long  drvAsciiInit(void);

struct
{
        long      number;
        DRVSUPFUN report;
        DRVSUPFUN init;

}  drvAsciiSio =
   {
     2L,
     drvAsciiReport,
     drvAsciiInit
   };

STATIC rbvFunc     getInteger;
STATIC rbvFunc     getReal;
STATIC rbvFunc     getSlope;
STATIC rbvFunc     getTimeout;
STATIC rbvFunc     getDebugFlag;
STATIC rbvFunc     getConnState;
STATIC rbvFunc     noOutputReadFunc;

STATIC cmdFunc     readConnState;
STATIC cmdFunc     setConnState;
STATIC cmdFunc     setSlope;
STATIC cmdFunc     setTimeout;
STATIC cmdFunc     setDebugFlag;

STATIC cmdFunc     writeInteger;
STATIC cmdFunc     readInteger;
STATIC cmdFunc     writeReal;
STATIC cmdFunc     readReal;

STATIC cmdFunc     setWriteCmt;
STATIC cmdFunc     setReadCmt;
STATIC cmdFunc     writeString;
STATIC cmdFunc     readString;

STATIC drvAsciiPrivateCallBack responseNotifier;

/* 
 *  The cmdTable consists of the possible 'command strings' for which
 *  i/o functions exist. During init, the function drvAsciiCreateSioLink
 *  is called. If the command string or data type string do not match
 *  one of the entries in this table then the associated record is invalid
 *  (ie. its parm field is incorrect). If the command string or data type
 *  string match one of the entries in this table then a pointer is retained
 *  to the relevant entry such that succeeding i/o is performed via the
 *  function(s) in that entry.
 *
 *  In order to add additional functionality to drvAscii it is necessary
 *  to add an entry to this table, both of the functions and an
 *  associated string. This may be appropriate when adding one or two
 *  commands for a remote device, where it is not worth writing a new device
 *  support layer on top of drvAscii.
 *
 */
STATIC cmdTableEntry cmdTable[] = {
  {"INTEGER_OUT",  writeInteger,   (rbvFunc *) getInteger},
  {"INTEGER_IN",   readInteger,    (rbvFunc *) noOutputReadFunc},
  {"REAL_OUT",     writeReal,      (rbvFunc *) getReal},
  {"REAL_IN",      readReal,       (rbvFunc *) noOutputReadFunc},
  {"STRING_OUT",   writeString,    (rbvFunc *) noOutputReadFunc},
  {"STRING_IN",    readString,     (rbvFunc *) noOutputReadFunc},

  /* 
   *  the following are special records, that is, records which affect
   *  the serial link and/or how drvAscii comms over that link
   */
  {"connect",      setConnState,   (rbvFunc *) noOutputReadFunc},
  {"connectSts",   readConnState,  (rbvFunc *) getConnState},
  {"slope",        setSlope,       (rbvFunc *) getSlope},
  {"readCmt",      setReadCmt,     (rbvFunc *) noOutputReadFunc},
  {"timeout",      setTimeout,     (rbvFunc *) getTimeout},
  {"writeCmt",     setWriteCmt,    (rbvFunc *) noOutputReadFunc},
  {"debug",        setDebugFlag,   (rbvFunc *) getDebugFlag},

  {NULL,           NULL,           NULL}
};


/*
 *  forward references 
 */
STATIC drvSerialParseInput getFrame;

STATIC drvSerialSendCB putFrame;
STATIC drvSerialSendCB putFrameAndBlock;

STATIC int  drvAsciiQueryStringReq( drvCmndPrivate    *pPriv, 
				    const 	char  *pMsg, 
				    drvSerialResponse *pRes );
STATIC int  drvAsciiQuery( drvCmndPrivate    *pPriv, 
			   drvSerialRequest  *pReq, 
			   drvSerialResponse *pRes );
STATIC int  drvAsciiSendIgnoreResp( drvAsyncIO       *pIO, 
				    drvSerialRequest *pReq );
STATIC int  drvAsciiSend( drvAppPrivate *pDas, drvSerialRequest *pReq );
STATIC void drvAsciiLoadReq( drvSerialRequest *pReq, const char *pMsg );
STATIC int  putFrameAndRemoveResp( FILE *fp, drvSerialRequest *pReq );
STATIC int  drvAsciiDebug( char *pFormat, ... );
STATIC long drvAsciiOpen( const char *pFileName,drvAppPrivate **ppDas );
STATIC int  drvAsciiSendCallBackWithResp( drvAsyncIO *pIO, 
					  drvSerialRequest *pReq,
					  int noUnlock );
STATIC int  putFrameAndCallBack( FILE *fp, drvSerialRequest *pReq );

/*
 *  global variables controllable from the shell
 */
int      drvAsciiDebugLevel = 0 ; /* Was 64 - I use 0x7fffffef */

/*
 *  local variables that are used for all links
 */
typedef struct link_info {
  ELLNODE	node;

  char		linkName[0x100];

  int           (*txFunc)();
  int           (*rxFunc)();
} linkInfo;
 
STATIC ELLLIST	linkList;

STATIC ELLLIST  protocolList;

STATIC epicsMutexId globalMutex = NULL;


/*
 * ---------------------------------------------------------------------------
 *  drvInitiateAll() - called once during device init. 
 *  Currently does nothing.
 *
 *  Public as it is used by device support
 */
long drvAsciiInitiateAll( void )
{
  /* !!! need to pass app specific init here */
  return S_drvAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 *  drvAsciiIntIo() - this function is the interface between  
 *  devAscii and drvAscii for integer data.
 *
 *  Public as it is used by device support
 */
long drvAsciiIntIo( drvAsyncIO *pIO,	/* async io structure  */
		    long       *pValue) /* raw read/write data */
{
  drvCmndPrivate *pCmndPriv = &pIO->id;

  if ( drvAsciiDebugLevel & 64 ) printf("drvAsciiIntIo\n");

  /*
   *  if the remote device is not connected then return an error 
   *  except for records which are relevant to the connection 
   *  (ie. allow connection status and reconnect commands)
   */
  assert( pCmndPriv->pAppPriv->pLink );

  if ( !pCmndPriv->pAppPriv->pLink->linkId ) return S_drvAscii_linkErr;


  if (   pCmndPriv->pAppPriv->state.noComm
	 && (*pCmndPriv->pCmndTableEntry->pCmdFunc != readConnState)
	 && (*pCmndPriv->pCmndTableEntry->pCmdFunc != setConnState) 
	 )
    return (S_drvAscii_noComm);

  /* 
   *  call the relevant function in the cmdTable, which was setup 
   *  during init, so as to perform the serial i/o
   */
  return (*pCmndPriv->pCmndTableEntry->pCmdFunc)
    ( pCmndPriv->pAppPriv, (void *)pValue, pIO, pCmndPriv->pCmndArg );
} 

/*
 * ---------------------------------------------------------------------------
 *  drvAsciiRealIo() - this function is the interface between 
 *  devAscii and drvAscii for real/floating-point data.
 *
 *  Public as it is used by device support
 */
long drvAsciiRealIo( drvAsyncIO *pIO,	/* async io structure  */
		     double     *pValue)/* raw read/write data */
{
  drvCmndPrivate *pCmndPriv = &pIO->id;

  if ( drvAsciiDebugLevel & 64 ) printf("drvAsciiRealIo\n");

  /*
   *  if the remote device is not connected then return an error 
   *  except for records which are  relevant to the connection 
   *  (ie. allow connection status and reconnect commands)
   */
  assert( pCmndPriv->pAppPriv->pLink );
  if ( !pCmndPriv->pAppPriv->pLink->linkId ) return S_drvAscii_linkErr;

  if (   pCmndPriv->pAppPriv->state.noComm
	 && (*pCmndPriv->pCmndTableEntry->pCmdFunc != readConnState)
	 && (*pCmndPriv->pCmndTableEntry->pCmdFunc != setConnState) 
	 )
    return (S_drvAscii_noComm);

  /* 
   *  call the relevant function in the cmdTable, which was setup 
   *  during init, so as to perform the serial i/o
   */
  return (*pCmndPriv->pCmndTableEntry->pCmdFunc)
    ( pCmndPriv->pAppPriv, (void *)pValue, pIO, pCmndPriv->pCmndArg );
} 

/*
 * ---------------------------------------------------------------------------
 *  drvAsciiStringIo() - this function is the interface between 
 *  devAscii and drvAscii for string data.
 *
 *  Public as it is used by device support
 */
long drvAsciiStringIo( drvAsyncIO *pIO,	     /* async io structure */
		       char       *pDataStr) /* raw read/write data */
{
  drvCmndPrivate *pCmndPriv = &pIO->id;

  if ( drvAsciiDebugLevel & 64 ) printf("drvAsciiStringIo(%s)\n",pDataStr);

  /*
   *  if the remote device is not connected then return an error 
   *  except for records which are  relevant to the connection 
   *  (i.e allow connection status and reconnect commands)
   */
  assert( pCmndPriv->pAppPriv->pLink );

  if ( !pCmndPriv->pAppPriv->pLink->linkId ) return S_drvAscii_linkErr;

  if (   pCmndPriv->pAppPriv->state.noComm
	 && (*pCmndPriv->pCmndTableEntry->pCmdFunc != readConnState)
	 && (*pCmndPriv->pCmndTableEntry->pCmdFunc != setConnState) 
	 )
    return ( S_drvAscii_noComm );

  /* 
   *  call the relevant function in the cmdTable, which was setup 
   *  during init, so as to perform the serial i/o
   */
  return (*pCmndPriv->pCmndTableEntry->pCmdFunc)
    ( pCmndPriv->pAppPriv, (void *)pDataStr, pIO, pCmndPriv->pCmndArg );
} 

/*
 * ---------------------------------------------------------------------------
 *  drvAsciiReadOutput() - this function is called during record init
 *  for those  output records for which initial values can be obtained.
 *  This is the function called from devAscii.
 *
 *  Public as it is used by device support
 */
long drvAsciiReadOutput( 
			drvCmndPrivate *id,	 /* link id */
			long           *pValue)  /* raw read/write data */
{
  if ( drvAsciiDebugLevel & 64 ) printf("drvAsciiReadOutput\n");

  if ( id->pAppPriv->state.noComm )
    return( S_drvAscii_noComm );

  assert( id->pAppPriv->pLink );

  if ( !id->pAppPriv->pLink->linkId ) return S_drvAscii_linkErr;
	
  return (*id->pCmndTableEntry->pOutReadFunc )
                       ( id, pValue, NULL, id->pCmndArg );
}


/*
 * ---------------------------------------------------------------------------
 *  strnccpy - internal function for translating escape sequences.
 *              
 *
 */
STATIC long strnccpy( char *pOut, char *pIn, int oStrSize )
{
  unsigned int val;
  int          oIdx = 0;

  while ( *pIn != '\0') {
    /*
     *  process any escape codes
     */
    if ( *pIn == '\\' ) {

      pIn++;

      if ( *pIn == 'b' )
	pOut[oIdx] = '\b';
      else if ( *pIn == 'f' )
	pOut[oIdx] = '\f';
      else if ( *pIn == 'n' )
	pOut[oIdx] = '\n';
      else if ( *pIn == 'r' )
	pOut[oIdx] = '\r';
      else if ( *pIn == 't' )
	pOut[oIdx] = '\t';
      else if ( *pIn == 'v' )
	pOut[oIdx] = '\v';
      else if ( *pIn == '\\' )
	pOut[oIdx] = '\\';
      else if ( (int)*pIn == 0x27 )
	pOut[oIdx] = '\'';
      else if ( (int)*pIn == 0x22 )
	pOut[oIdx] = '\"';
      else if ( *pIn == 'x' ) {
	/* a hex escape sequence so convert to a byte value */
	if ( isdigit( *(++pIn) ) ) {

	  sscanf( pIn, "%2x", &val );
	  pOut[oIdx] = (char)val;
	  pIn++; 

	} else {

	  /* 
	   *  The input is invalid! 
	   *  The input is of the form "\xc" where 'c' is not a hex digit.
	   */
	  return S_drvAscii_badParam;
	}

      } else if ( isdigit(*pIn) ) {

	/* an octal escape sequence so convert to a byte value */
	sscanf( pIn, "%3o", &val );
	pOut[oIdx] = (char)val;

	if (  isdigit( *(++pIn) ) ) pIn++; 
	if ( !isdigit( *(++pIn) ) ) pIn--;

      } else {
	/* 
	 * not an escape code so include the 
	 * two characters in the data
	 */
	pOut[oIdx++] = '\\';

	if ( (oIdx >= oStrSize) && (oStrSize > 0) )
	  return S_drvAscii_badParam;

	pOut[oIdx] = *pIn;
      }

    } else {

      pOut[oIdx] = *pIn;
    }

    pIn++;

    if ( (++oIdx >= oStrSize) && (oStrSize > 0) )
      return S_drvAscii_badParam;
  }

  pOut[oIdx] = '\0';

  return S_drvAscii_Ok;
}

/*
 * ---------------------------------------------------------------------------
 *  processFormat - private routine for parsing the command prompt, 
 *  command response, read back prompt, and read back response strings.
 */
STATIC long processFormat( char *format, 
			   drvResponseInfo *formatInfo
			   )
{
  int          count1 = 0;

  unsigned int suppress = FALSE, 
               dataTypeFound = FALSE;

  char         *spec, *spec1, *delimEnd;

  /* 
   *  The format string (either the prompt or response) is parsed to  
   *  ensure that it is valid.
   *
   *  If more than one '%' exists then one must be for '%nK' or  '%nB %['  
   *  or '%nT', all others must be of the form '%*,' except if '%nB' does 
   *  not exist, in which case one of '%s'. '%f', '%f',... is valid. That 
   *  is a data type specification is only valid for one value in the 
   *  format string but any number of '%*' is valid. Note that '%*b' is  
   *  not permitted.
   *
   *  The definition of the non-standard C specifications are:
   *   '%nK' is the number of characters to 'kill' at the beginning
   *        of a data stream, '%*K' is valid and means ignore response 
   *
   *   '%nB' indicates that the incoming character stream is a series 
   *        of '0' and '1' from which an integer is to be assembled
   *        the '%[...]', following '%nb', allows for delimiters in a 
   *        bit stream.
   *
   *   '%nT' indicates the string data spans 'n' records (ie. 'n' read 
   *         command terminators will occur before all characters are 
   *         received).
   *
   *  If the an octal escape sequence "\ooo" exists then the sequence
   *  will be translated into the character whose octal equivelent is the 
   *  nnn value. This is also true for hex scape sequences "\xhh".
   *
   *  If the 'n' following '%' is anything other than 1, for data types
   *  other than 'k' or 'b' or 's', then the parameter is invalid, as 
   *  arrays are not currently implemented (as of 970107).
   */

  /*
   *  Set the default data type.
   */
  formatInfo->dataType = RBF_UNDEFINED;

  /*
   *  This translates the escape sequences. It is a local function.
   */
  strnccpy( format, format, 0 );

  formatInfo->dataFormat = format;
  spec = formatInfo->dataFormat;

  while ( (spec1 = strchr( spec, '%' )) ) {

    spec = spec1; 
    spec++;
    count1 = 0;

    delimEnd = strchr( spec, ']' );

    if ( *spec == '*' ) { 

      spec++;
      suppress = TRUE;

    } else 
      suppress = FALSE;

    while ( isdigit( *spec ) ) {

      count1 = count1 * 10 + (int)(*spec - '0');
      spec++;
    }

    if ( suppress )
      count1 = 0;

    else 
      count1 = count1 > 0 ? count1 : 1;
    

    /* 
     *  ignore %l as all integer and real data is scanned as 
     *  long or double 
     */
    if ( *spec == 'l' || *spec == 'L' ) spec++;

    if ( *spec == 'k' || *spec == 'K' ) {
      /* 
       *  some or all of the input stream should be killed (ignored).
       *  this spec can only be at the beginning of the format string
       */
      if ( spec1 != format ) return S_drvAscii_badParam;
      
      formatInfo->kill = TRUE;
	
      if ( suppress ) 
	formatInfo->killAll = TRUE; /* entire input will be ignored */
      
      else 
	formatInfo->killCnt = count1;
      
      formatInfo->dataFormat = ++spec;
      format = formatInfo->dataFormat;
    }

    /* 
     *  data stream can span multiple record boundaries (line terminators)
     *  but the designation must be either at the beginning of the format
     *  string or immediately following '%nk' or '%*k'. Also '%*t' is not
     *  valid.
     */
    else if ( *spec == 't' || *spec == 'T' ) {

      if ( suppress || 
	   dataTypeFound || 
	   formatInfo->recordCnt ||
	   (spec1 != formatInfo->dataFormat) ) 
	return S_drvAscii_badParam;

      formatInfo->recordCnt = count1;

      /*  skip over the %t */
      formatInfo->dataFormat = ++spec;
      format = formatInfo->dataFormat;
    }

    else if ( *spec == 'b' || *spec == 'B' ) {
      /* 
       *  data is expected as a bit stream (ie. all '0' and '1') 
       *  potentially with delimiters. Binary streams cannot be 
       *  suppressed (ie. '%*b' is not allowed as one can accomplish 
       *  the same with %*d or %*f) 
       */
      if ( suppress || dataTypeFound ) return S_drvAscii_badParam;

      formatInfo->dataType = RBF_BINARY;
      dataTypeFound = TRUE;

      formatInfo->dataCnt = count1;
      
      if ( formatInfo->dataFormat != spec1 ) {

        formatInfo->preAmbleCnt = spec1-format;
	formatInfo->preAmble = formatInfo->dataFormat;
      }

      /*  skip over the %nb */
      formatInfo->dataFormat = ++spec;
    }

    else if ( *spec == '[' ) {
      /* 
       *  delimiters exist for a binary stream but the designation must
       *  immediately follow the '%nb' the syntax is '%[c...]' where 'c'
       *  is a delimiter character of which there can be many
       */

      /*  if ']' is missing then abort */
      if ( !delimEnd )
	return S_drvAscii_badParam;

      /* 
       *  if the spec is '%*[' then assume it is not a bit stream delimiter
       *  spec. Otherwise, if it does not immediately follow a '%nb' bit
       *  stream spec, assume it is not a bit stream delimiter. That is,
       *  it can only be a bit stream delimiter if it immediately follows
       *  '%nb' and does not have an assignment suppression char '*'
       */
      if ( spec == formatInfo->dataFormat && !suppress ) {

	if ( formatInfo->dataType != RBF_BINARY )
	  return S_drvAscii_badParam; 

	formatInfo->delimiters = ++spec;
	formatInfo->dataFormat = ++delimEnd;
	spec = delimEnd;
      }
      
      /*  must be for a set capture */
      else 
	spec = delimEnd;       

    }

    else if ( *spec == 's' || *spec == 'S' ||
	      *spec == 'c' || *spec == 'C'    ) {

      if ( !suppress && dataTypeFound ) return S_drvAscii_badParam;

      if ( !suppress ) {
	
	if ( *spec == 'c' || *spec == 'C' )
	  formatInfo->dataType = RBF_CHAR;
	else
	  formatInfo->dataType = RBF_STRING;
      
	formatInfo->dataCnt = count1;
      
	dataTypeFound = TRUE;
	
	if ( formatInfo->dataFormat != spec1 ) {
	  
	  formatInfo->preAmbleCnt = spec1 - formatInfo->dataFormat;
	  formatInfo->preAmble = formatInfo->dataFormat;
	}

	/*  skip over the %nc or %ns */
	formatInfo->dataFormat = ++spec;
      }
    }
    else {
      if ( !suppress && dataTypeFound ) return S_drvAscii_badParam;

      /*  all integer data is scanned into a long */
      if ( *spec == 'd' || *spec == 'D' ||
	   *spec == 'i' || *spec == 'I' ||
	   *spec == 'u' || *spec == 'U' ||
	   *spec == 'o' || *spec == 'O' ||
	   *spec == 'x' || *spec == 'X'    ) {

	if ( !suppress ) {

	  formatInfo->dataCnt = count1;
	  formatInfo->dataType = RBF_INT;

	  dataTypeFound = TRUE;
	}
      }

      /*  all real data is scanned into a double */
      else if ( *spec == 'f' || *spec == 'F' ||
		*spec == 'e' || *spec == 'E'    ) {
		
	if ( !suppress ) {

	  formatInfo->dataCnt = count1;
	  formatInfo->dataType = RBF_REAL;

	  dataTypeFound = TRUE;
	}
      }
      
      else
	/*  other specs are not valid */
	return S_drvAscii_badParam;
    }
  }

  return S_drvAscii_Ok;
}

/*
 * ---------------------------------------------------------------------------
 *  drvAsciiDebug() - function to generate printfs when 
 *  drvAsciiDebugLevel is set.
 */
STATIC int drvAsciiDebug( char *pFormat, ... )
{
  va_list args;

  if ( drvAsciiDebugLevel == 0 ) 
    return 0;
  

  va_start( args, pFormat );
  vprintf( pFormat, args );
  va_end( args );

  return 0;
}


/*
 * ---------------------------------------------------------------------------
 *  printString - private routine for printing ascii strings such that 
 *   non-printable characters are printed as hex values enclosed in 
 *   brackets '[%x]'.
 */
STATIC void printString( long count, char *str )
{
  char *ptr,*end;

  if ( count > 0 )
    end = str+count;

  else 
    end = str;

  ptr = str;

  while ( (ptr < end) || (count > 0 ? count : *ptr != '\0') ) {

    if ( (int)*ptr < 32 || (int)*ptr > 126 || (drvAsciiDebugLevel & 16) )
      printf( "[%02x]", (int)*ptr );

    else
      printf( "%c", *ptr );

    ptr++;
    
    if (count > 0) count--;
  }
}


/*
 * ---------------------------------------------------------------------------
 *  printCmndArgs - used for displaying the command args at debug level.
 *  This routine is public so that one can debug from the shell.
 */
void printCmndArgs( drvCmndArg *pCmndArgs )
{
  if ( !pCmndArgs ) {

    printf(" pCmndArgs=0x%x is NULL\n",(int)pCmndArgs );
    return;
  }

  printf( "\nCMND PROMPT INFO\n" );
  printf( "kill=%d, killAll=%d, killCnt=%ld\n",
	  pCmndArgs->cmndPromptInfo.kill,
	  pCmndArgs->cmndPromptInfo.killAll,
	  pCmndArgs->cmndPromptInfo.killCnt );
  printf( "dataType=%ld, preAcnt=%ld, dataCnt=%ld, recordCnt=%ld, arrayCnt= %ld\n",
	  pCmndArgs->cmndPromptInfo.dataType,
	  pCmndArgs->cmndPromptInfo.preAmbleCnt,
	  pCmndArgs->cmndPromptInfo.dataCnt,
	  pCmndArgs->cmndPromptInfo.recordCnt,
	  pCmndArgs->cmndPromptInfo.arrayCnt  );
/* ktt: causes seg fault 
  printf( "delimiters=0x%x, preAmble=0x%x(%s), dataFormat=0x%x(%s)\n",
	  (int)pCmndArgs->cmndPromptInfo.delimiters,
	  (int)pCmndArgs->cmndPromptInfo.preAmble,
	  pCmndArgs->cmndPromptInfo.preAmble,
	  (int)pCmndArgs->cmndPromptInfo.dataFormat,
	  pCmndArgs->cmndPromptInfo.dataFormat );
*/
  printf( "\nCMND INFO\n" );
  printf( "kill=%d, killAll=%d, killCnt=%ld\n",
	  pCmndArgs->cmndInfo.kill,
	  pCmndArgs->cmndInfo.killAll,
	  pCmndArgs->cmndInfo.killCnt );
  printf( "dataType=%ld, preAcnt=%ld, dataCnt=%ld, recordCnt=%ld, arrayCnt= %ld\n",
	  pCmndArgs->cmndInfo.dataType,
	  pCmndArgs->cmndInfo.preAmbleCnt,
	  pCmndArgs->cmndInfo.dataCnt,
	  pCmndArgs->cmndInfo.recordCnt,
	  pCmndArgs->cmndInfo.arrayCnt  );
/* ktt: causes seg fault 
  printf( "delimiters=0x%x, preAmble=0x%x(%s), dataFormat=0x%x(%s)\n",
	  (int)pCmndArgs->cmndInfo.delimiters,
	  (int)pCmndArgs->cmndInfo.preAmble,
	  pCmndArgs->cmndInfo.preAmble,
	  (int)pCmndArgs->cmndInfo.dataFormat,
	  pCmndArgs->cmndInfo.dataFormat );
*/
  printf( "\nRBV INFO\n" );
  printf( "kill=%d, killAll=%d, killCnt=%ld\n",
	  pCmndArgs->rbvInfo.kill,
	  pCmndArgs->rbvInfo.killAll,
	  pCmndArgs->rbvInfo.killCnt );
  printf( "dataType=%ld, dataCnt=%ld, recordCnt=%ld, arrayCnt= %ld\n",
	  pCmndArgs->rbvInfo.dataType,
	  pCmndArgs->rbvInfo.dataCnt,
	  pCmndArgs->rbvInfo.recordCnt,
	  pCmndArgs->rbvInfo.arrayCnt  );
/* ktt: causes seg fault 
  printf( "delimiters=0x%x, dataFormat=0x%x(%s)\n",
	  (int)pCmndArgs->rbvInfo.delimiters,
	  (int)pCmndArgs->rbvInfo.dataFormat,
	  pCmndArgs->rbvInfo.dataFormat );
*/
  printf( "\ncmndPrompt=" );
  printString( -1, pCmndArgs->cmndPrompt );
  printf( "\n" );

  printf( "cmndFormat=" );
  printString( -1, pCmndArgs->cmndFormat );
  printf( "\n" );

  printf( "rbvPrompt=" );
  printString( -1, pCmndArgs->rbvPrompt );
  printf( "\n" );

  printf( "rbvFormat=" );
  printString( -1, pCmndArgs->rbvFormat );
  printf( "\n" );

}
 

/*
 * ---------------------------------------------------------------------------
 *  printCmndPrivate - used for displaying the command private info  
 *  for a given record. 
 *
 *  This routine is public so that one can debug from the shell.
 */
void printCmndPrivate( drvCmndPrivate *ptr )
{
  printf("drvCmndPrivate:\n");

  if ( !ptr ) {

    printf( "ptr=0x%x is NULL\n", (int)ptr );
    return;
  }

  printf( "node=0x%x, pAppPriv=0x%x, pCmndTableEntry=0x%x, pCmndArg=0x%x\n",
	  (int)&ptr->node, (int)ptr->pAppPriv,
	  (int)ptr->pCmndTableEntry,(int)ptr->pCmndArg );
  printf( "pCb=0x%x, pArg=0x%x, rbvFlag=%d\n",
	 (int)ptr->pUpdateCB, (int)ptr->pUpdateArg, (int)ptr->rbvFlag );


  printf( "respStrCnt=%ld\n", ptr->respStrCnt );
  printf( "respStr=");
  printString( ptr->respStrCnt, ptr->respStr );
  printf( "\n\n" );

  printCmndArgs( ptr->pCmndArg );
}
  

/*
 * ---------------------------------------------------------------------------
 *  printAsyncIo - used for displaying the command private info for a 
 *  given record. 
 *
 *  This routine is public so that one can debug from the shell.
 */
void printAsyncIo( drvAsyncIO  *ptr )
{
  printf("\ndrvAscynIO:\n");

  if ( !ptr ) { 

    printf("ptr=0x%x is NULL\n", (int)ptr );
    return;
  }
  
  printf("p(id)=0x%x, pCB=0x%x, pArg=0x%x, pAppDrvPrivate=0x%x\n",
	 (int)&ptr->id, (int)ptr->pIoDoneCB, 
	 (int)ptr->pIoDoneArg, (int)ptr->pAppDrvPrivate );

  printCmndPrivate( &ptr->id );

  return;  
}


/*
 * ---------------------------------------------------------------------------
 *  drvAsciiReportOneDevice() - function to printf the app priv info 
 *  for a device report.
 */
STATIC void drvAsciiReportOneDevice( drvAppPrivate *pAppPriv, 
				     unsigned      level)
{
  char *pChar;

  assert(pAppPriv);

  if ( level > 0 ) {
    /* call an app specific report function (if it exists). */
    ;
  }

  if ( level > 1 ) {

    printf ( "\t\tBad queries=%lu\n",
	     pAppPriv->queryFailures );
    printf ( "\t\tData errors=%lu\n",
	     pAppPriv->dataErrors );
    printf ( "\t\tAnalog I/O slope=%f\n",
	     pAppPriv->slope );
    printf ( "\t\tResponse timeout=%lu\n",
	     pAppPriv->responseTMO);
    
    /*  display the current command terminators */
    printf( "\t\tWrite terminator=" );

    pChar = pAppPriv->writeCmt;

    while ( pChar && (*pChar != '\0') ) {

      if ( (int)(*pChar) < 32 || (int)(*pChar) > 126 ) 
	printf( "[%2d]", (int)(*pChar) );

      else 
	printf( "%c",*pChar );

      pChar++;
    }

    printf( "\n" );
    printf( "\t\tRead terminator=" );
    pChar = pAppPriv->readCmt;

    while ( pChar && (*pChar != '\0') ) {

      if ( (int)(*pChar) < 32 || (int)(*pChar) > 126 ) 
	printf( "[%2d]",(int)(*pChar) );

      else 
	printf( "%c",*pChar );

      pChar++;
    }

    printf( "\n" );
  }

  /*
  if ( level > 2 ) 
    semShow( pAppPriv->mutexSem, level );
    */
  return;
}


/*
 * ---------------------------------------------------------------------------
 *  drvAsciiReport() - function called for device report.
 *
 *  This routine is public so that it can be used from the shell.
 */
long drvAsciiReport(int level)
{
  drvSioLinkPrivate	*pLink;
  drvAppPrivate	        *pAppPriv;

  /*
   *  run the list of all links with a remote device on them 
   *  (mutex protects list)
   */
  epicsMutexMustLock( globalMutex );
  pLink = (drvSioLinkPrivate *) ellFirst( &linkList );

  while ( pLink ) {

    printf( "\tdrvAscii: port=%s\n", pLink->fileName );
    
    /*
     *  run the list of all devices on this link 
     *  (mutex protects list). Currently only a single remote
     *  device is permitted.
     */
    epicsMutexMustLock( pLink->mutex );

    /*  currently only one remote device is allowed on a link */
    pAppPriv = (drvAppPrivate *) ellFirst( &pLink->remoteDevList );

    drvAsciiReportOneDevice( pAppPriv, level );

    epicsMutexUnlock( pLink->mutex );
    
    if ( level > 2 ) {

      printf( "\nsendBlockSem:" );
      epicsEventShow( pLink->sendBlockSem, level );
      printf( "\nmutex:" );
      epicsMutexShow( pLink->mutex, level );
    }
     
    pLink = (drvSioLinkPrivate *) ellNext( &pLink->node );
  }

  epicsMutexUnlock( globalMutex );
  
  return S_drvAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 *  drvAsciiInit() - driver init function which is called one time 
 *  during ioc init.
 */
long drvAsciiInit( void )
{
  if ( globalMutex == NULL ) {
 
    ellInit( &linkList );

    ellInit( &protocolList );

    globalMutex = epicsMutexCreate( );

    if ( !globalMutex ) 
        epicsPrintf("%s line %d: drvAsciiInit failed no memory (%d)\n",
                    __FILE__, __LINE__, S_drvAscii_noMemory );
      return S_drvAscii_noMemory;
  }

  epicsPrintf("%s line %d: drvAsciiInit was successful (%d)\n",
              __FILE__, __LINE__, S_drvAscii_Ok);
  return S_drvAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 *  drvAsciiCreateSioLink() - this is the function called from 
 *  devAscii, during record init, so as to establish a link, and 
 *  create and initialize the structures which are ultimately linked 
 *  into the record (dvpt) such that succeeding i/o calls can be handled.
 *
 *  It is herein that the links into cmdTable are established.
 *
 */
long drvAsciiCreateSioLink(
		      const char     *pDataType,  /* i/o data type */
		      const char     *pFileName,  /* serial port device name*/
		      drvCmndArg     *pCmndArgs,
                                             /* for future ref of this link */
		      drvCmndPrivate *pId,	
                                             /* called on parameter change  */
		      drvAsyncUpdateCallBack *pCB,
                                             /* parameter to above call back*/
		      const void     *pArg)	  
{
  drvAppPrivate  *pAppPriv = NULL;
  long	         status = S_drvAscii_Ok;
  cmdTableEntry  *pTableEntry = NULL;
  char           *pCmndName = pCmndArgs->cmndPrompt;

  if ( drvAsciiDebugLevel & 128 ) printf("drvAsciiCreateSioLink\n");
  /*  
   *  ensure that the command args structure is valid 
   */
  if ( !pCmndArgs->cmndPrompt ||
       !pCmndArgs->cmndFormat ||
       !pCmndArgs->rbvPrompt  ||
       !pCmndArgs->rbvFormat)
    return S_drvAscii_badParam;

  /*
   *  Search for the command in the table
   */
  pTableEntry = cmdTable;
  while ( pTableEntry->pCmndName != NULL ) {

    if ( strncmp( pTableEntry->pCmndName, 
		  pCmndName,
		  strlen(pTableEntry->pCmndName) ) == 0) {
      break;
    }

    pTableEntry++;
  }
  
  /*
   *  If the command is not in the table then use the data type string 
   *  as the table lookup key. In this case the assumption is that the 
   *  command is a prompt for which a value of the associated type will 
   *  be returned.
   */
  if ( pTableEntry->pCmndName == NULL ) {

    pTableEntry = cmdTable;

    while ( pTableEntry->pCmndName ) {

      if ( strcmp( pTableEntry->pCmndName, pDataType ) == 0 ) 
	break;

      pTableEntry++;
    }
  }
  
  /*
   *  Neither the command name nor the 'record' type string is valid 
   *  so abort
   */ 
  if ( pTableEntry->pCmndName == NULL ) {

    drvAsciiDebug( "(%s %s): Prompt string invalid!\n", 
		   pCmndName, pDataType );
    return S_drvAscii_badParam;
  }

  /* 
   *  Parse the command arg strings. Syntactical faults are captured 
   *  but faults relevant to the record type are left for the record 
   *  init routine, which invoked this routine, within device support.
   */
  if ( strlen( pCmndArgs->cmndPrompt ) )
       status = processFormat( pCmndArgs->cmndPrompt,
			       &pCmndArgs->cmndPromptInfo );

  if ( status ) { 

    drvAsciiDebug( "(%s %s): Bad Cmd prompt!\n",pCmndName, pDataType ); 

    if ( drvAsciiDebugLevel & 0x8 ) 
      printCmndArgs( pCmndArgs );

    return status;
  }

  if ( strlen( pCmndArgs->cmndFormat ) ) {


    status = processFormat( pCmndArgs->cmndFormat,
			    &pCmndArgs->cmndInfo );

  }

  if ( status ) { 

    drvAsciiDebug( "(%s %s): Bad Cmd response format!\n",
		   pCmndName, pDataType); 

    if ( drvAsciiDebugLevel & 0x8 ) 
      printCmndArgs( pCmndArgs );

    return status;
  }

  if ( strlen( pCmndArgs->rbvPrompt ) )
       status = processFormat( pCmndArgs->rbvPrompt, 
			       &pCmndArgs->rbvPromptInfo );

  if ( status ) { 

    drvAsciiDebug( "(%s %s): Bad Rbv prompt!\n",pCmndName, pDataType); 

    if ( drvAsciiDebugLevel & 0x8 ) 
      printCmndArgs( pCmndArgs );

    return status;
  }

  if ( strlen( pCmndArgs->rbvFormat ) )
       status = processFormat( pCmndArgs->rbvFormat, 
			       &pCmndArgs->rbvInfo );
  if ( status ) { 

    drvAsciiDebug( "(%s %s): Bad Rbv response format!\n",pCmndName, pDataType); 

    if ( drvAsciiDebugLevel & 0x8 ) 
      printCmndArgs( pCmndArgs );

    return status;
  }

  if ( drvAsciiDebugLevel & 0x8 ) 
    printCmndArgs( pCmndArgs );

  /*
   *  Find an existing application private structure (or create a
   *  new one). This ultimately results in a read and write task for
   *  the serial i/o.
   */
  status = drvAsciiOpen( pFileName, &pAppPriv );

  /*
   *  If the open fails then the serial 'linkId' will be null.
   *  This is needed so as to ensure subsequent i/o is rejected.
   */
  pId->pAppPriv = pAppPriv;

  pId->pCmndTableEntry = pTableEntry;
  pId->pUpdateCB  = pCB;
  pId->pUpdateArg = pArg;
  pId->pCmndArg   = pCmndArgs;
  pId->respStr[0] = '\0';
  pId->respStrCnt = 0;

  return status;
}


/*
 * ---------------------------------------------------------------------------
 *  drvAsciiOpenLink() - function which is called to establish a new
 *  serial link. This is the interface to drvSerial.
 */
STATIC long drvAsciiOpenLink( const char        *pFileName, 
			      drvAppPrivate     **ppAppPriv,
			      drvSioLinkPrivate **ppLinkPriv)
{
  drvAppPrivate	        *pAppPriv = NULL;

  drvSioLinkPrivate	*pLink;

  linkInfo              *protocolInfo;

  long			status,
                        length;


  *ppAppPriv = NULL;

  /*
   *  allocate and init link private structure 
   */
  pLink = (drvSioLinkPrivate *) calloc( 1, sizeof( *pLink ) );

  if ( !pLink ) {

    return S_drvAscii_noMemory;
  }

  *ppLinkPriv = pLink;
  pLink->linkId = NULL;

  /* 
   *  initialize the link's remote device list. Currently only one is 
   *  allowed. 
   */
  ellInit( &pLink->remoteDevList );

  /*  
   *  create the link's mutual exclusion semaphore 
   */
  pLink->mutex = epicsMutexCreate( );

  if ( !pLink->mutex ) 
    return S_drvAscii_noMemory;

  /*
   *  allocate application private structure 
   */
  pAppPriv = (drvAppPrivate *) calloc( 1, sizeof( *pAppPriv ) );

  if ( !pAppPriv ) 
    return S_drvAscii_noMemory;

  *ppAppPriv = pAppPriv;
  pAppPriv->pLink = pLink;

  pAppPriv->state.noComm = 1;
  pAppPriv->reset = 0;
  pAppPriv->slope = 1.0;
  pAppPriv->responseTMO = 3;
  pAppPriv->dataErrors = 0;
  pAppPriv->queryFailures = 0;
  pAppPriv->debugFlag = 0; /* ### I use -1 ### */
  pAppPriv->writeCmt[0] = '\r';
  pAppPriv->writeCmt[1] = '\n';
  pAppPriv->writeCmt[2] = '\0';
  pAppPriv->readCmt[0] = '\r';
  pAppPriv->readCmt[1] = '\n';
  pAppPriv->readCmt[2] = '\0';
  
  /* 
   *  Check for any special processing receiver/transmitter functions.
   *  These would be for a non-default protocol. The functions, if they
   *  exist, would be defined before iocInit with drvAsciiSetTxFunc()
   *  and drvAsciiSetRxFunc().
   */
  epicsMutexMustLock( globalMutex );

  protocolInfo = (linkInfo *) ellFirst( &protocolList );

  while ( protocolInfo ) {

    if ( !strcmp( pFileName, protocolInfo->linkName ) ) break;

    protocolInfo = (linkInfo *) ellNext( &protocolInfo->node );
  }

  if ( protocolInfo ) {

    pAppPriv->txFunc = protocolInfo->txFunc;    
    pAppPriv->rxFunc = protocolInfo->rxFunc;    
  }

  epicsMutexUnlock( globalMutex );
 
  /*
   *  Allocate the application private structure's mutual exclusion 
   *  semaphore .
   */
  /*
  pAppPriv->mutexSem = epicsEventCreate( SEM_FULL);
  */
  /*
   * semMCreate( SEM_Q_PRIORITY | SEM_INVERSION_SAFE | SEM_DELETE_SAFE );
   */
  /*
  if ( pAppPriv->mutexSem == NULL ) 
    return S_drvAscii_noMemory;
    */
  /*
   *  Keep a list of all devices of this type on this link.
   */
  ellAdd( &pLink->remoteDevList, &pAppPriv->node );

  /*
   *  Create the transmitter semaphore (prevent writes when a 
   *  synchronous write-read cycle is in progress).
   */
  pLink->sendBlockSem = epicsEventCreate( SEM_EMPTY );

  if ( pLink->sendBlockSem == NULL ) 
    return S_drvAscii_noMemory;
  
  /*
   *  create the writer/reader synchronization semaphore
   */
  pLink->syncLock = epicsEventCreate( SEM_FULL );

  if ( pLink->syncLock == NULL ) 
    return S_drvAscii_noMemory;

  /*  
   *  Store the link name (name used in fopen).
   */
  length =  min( strlen( pFileName ),sizeof( pLink->fileName )-1 );
  strncpy( pLink->fileName, pFileName, length );
	  
  pLink->fileName[ min( strlen( pFileName ),
			sizeof( pLink->fileName )-1 )]='\0';

  /*
   *  Open the link. 
   */
  status = drvSerialCreateLink( pFileName,
				getFrame,
				pLink,
				&pLink->linkId );
  if ( status ) 
    return status;
   
  epicsMutexMustLock( globalMutex );
  ellAdd( &linkList, &pLink->node );
  epicsMutexUnlock( globalMutex );
    
  /*  
   *  Everything is OK so mark the comms state as ok.
   */
  pAppPriv->state.noComm = 0;

  return S_drvAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 *  drvAsciiOpen() - function which determines if a new serial link
 *  needs be established or connects to an existing link .
 */
STATIC long drvAsciiOpen( const char    *pFileName,
			  drvAppPrivate **ppAppPriv)
{
  drvAppPrivate	        *pAppPriv = NULL;
  drvSioLinkPrivate	*pLink = NULL;
  long 			status;

  *ppAppPriv = NULL;

  /*
   *  Check to see if anything is on this link.
   */
  status = drvSerialAttachLink( pFileName,
				getFrame,
				(void **)&pLink );

  if ( status == S_drvSerial_OK ) {
    /* 
     *  S link exists for this pFileName so simply return the 
     *  relevant handle.
     */
    assert( pLink );
    epicsMutexMustLock( pLink->mutex );

    /*  
     *  Currently only one remote device is allowed on a link.
     */
    pAppPriv = (drvAppPrivate *) ellFirst( &pLink->remoteDevList );

    epicsMutexUnlock( pLink->mutex );
 
    if ( pAppPriv ) {
      *ppAppPriv = pAppPriv;
      return S_drvAscii_Ok;

    } else {

      *ppAppPriv = NULL;
      return S_drvAscii_linkErr;
    }

  } else if ( status == S_drvSerial_noneAttached ) {
    /*
     *  No serial link exists for pFileName so create a new one.
     */
    status = drvAsciiOpenLink( pFileName, ppAppPriv, &pLink );

    if ( status ) 
      return status;

  } else {
    /*  
     *  A fault is exists in serial driver so abort.
     */
    return status;
  }
  	  
  return status;
}
 

/*
 * ---------------------------------------------------------------------------
 *  getConnState() - returns a link's connection state: 0 = not connected.
 *  currently a link can only be marked as not connected if a fault occurs
 *  during init, or from a database record (ie. the user can force the 
 *  condition).
 */
STATIC long getConnState( drvCmndPrivate *pCmndPriv, 
			  void           *pValue, 
			  drvAsyncIO     *pIO, 
			  drvCmndArg     *pFuncParam )
{
  *(long *)pValue = !( pCmndPriv->pAppPriv->state.noComm );

  return S_drvAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 *  getSlope()- returns a link's slope value. 
 *
 *  The slope is a value by which all float output values are divided 
 *  by and all float input values are multiplied by. The default value 
 *  is 1.0.
 *
 *  Slope is needed because: the RVAL for an analog input record is an  
 *  integer nd there is no way to circumvent conversion from RVAL to VAL 
 *  so ESLO,   ASLO and ROFF come into play; the RVAL from an analog output 
 *  record is an  integer and if one used VAL then the ESLO, ASLO and ROFF 
 *  would be circumvented.
 *
 *  This needs to be upgraded so it is record specific.
 */
STATIC long getSlope ( drvCmndPrivate *pCmndPriv, 
		       void           *pValue, 
		       drvAsyncIO     *pIO, 
		       drvCmndArg     *pFuncParam )
{
  *(long *)pValue = pCmndPriv->pAppPriv->slope;

  return S_drvAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 *  getTimeout() - returns a link's current maximum time allowed for a
 *  response to a data prompt (default is 10 seconds). The originating 
 *  record can be a float or integer
 *
 */
STATIC long getTimeout ( drvCmndPrivate *pCmndPriv, 
			 void           *pValue, 
			 drvAsyncIO     *pIO, 
			 drvCmndArg     *pFuncParam )
{
  *(long *)pValue = pCmndPriv->pAppPriv->responseTMO;

  return S_drvAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 *  getDebugFlag() - returns a link's debug state
 *                   record should be a longin
 */
STATIC long getDebugFlag ( drvCmndPrivate *pCmndPriv, 
			   void          *pValue, 
			   drvAsyncIO    *pIO, 
			   drvCmndArg    *pFuncParam )
{
  *(long *)pValue = pCmndPriv->pAppPriv->debugFlag;

  return S_drvAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 *  extractRealData() - the data stream is expected to be of the form '%f'
 * 
 */
STATIC long extractRealData( char            *pBuf,
			     char            *format,
			     drvResponseInfo *formatInfo,
			     char            *cmt,
			     double          *pValue )
{
  long   count = 0;

  char   bfr[DRV_SERIAL_BUF_SIZE];

  float  fValue;

  char   *ptr, *sign;

  int    zeroIt = 1;

  if ( drvAsciiDebugLevel & 64 ) printf("extractRealData from '%s' format '%s'\n",pBuf,format);

  /*
   *  Force any spaces that are between the numeric sign and the digits
   *  to be 0 so that sscanf yields a numeric value. Note this is needed
   *  for some devices such as Sony gauges. 
   *
   *  This should probably be a selectable on a record by record basis
   *  - a future mod.
   */

  sign = strchr( pBuf, '-' );

  if (!sign)
    sign = strchr( pBuf, '+' );

  if ( sign ) {

    ptr = sign;
    ptr++;

    while ( !isdigit( *ptr ) ) {

      if ( *ptr != ' ' ) {

	zeroIt = 0;
	break;
      }

      if ( *ptr == '\0' )
	break;
      
      ptr++;
    }

    if ( zeroIt ) {

      ptr = ++sign;

      while ( *ptr == ' ' ) {

	*ptr = '0';
	ptr++;
      }
    }
  }

  if ( formatInfo->dataType == RBF_REAL ) {
    /* 
     *  A data type spec of real exists so the data stream is expected 
     *  to be of the form '%f'. A '%n' is appended to the data format  
     *  specification so the total number of characters consumed can be 
     *  tallied.
     */
    sprintf( bfr,"%s%s%s", formatInfo->dataFormat, cmt, "%n" );

    sscanf( pBuf, bfr, &fValue, &count );

    *pValue = fValue;

  } else {
    /*  
     *  A data format specification does not exist so assume '%f' 
     */ 
    if ( format ) 
      sprintf( bfr, "%s%s%s%s", format, "%f", cmt, "%n" );
    
    else
      sprintf( bfr, "%s%s%s", "%f", cmt, "%n" );

    sscanf( pBuf, bfr, &fValue, &count );

    *pValue = fValue;
  }

  if ( drvAsciiDebugLevel & 128 ) printf("extractRealData got value %lg with count %d\n",*pValue,count);

  return count;
}


/*
 * ---------------------------------------------------------------------------
 *  extractIntData() - the data stream is expected to be of the form '%d'
 * 
 */
STATIC long extractIntData( char            *pBuf,
			    char            *format,
			    drvResponseInfo *formatInfo,
			    char            *cmt,
			    long            *pValue )
{
  long   count = 0,
         iValue;

  char   bfr[DRV_SERIAL_BUF_SIZE];

  char   *ptr, *sign;

  int    zeroIt = 1;

  if ( drvAsciiDebugLevel & 64 ) printf("extractIntData\n");

  /*
   *  Force any spaces that are between the numeric sign and the digits
   *  to be 0 so that sscanf yields a numeric value. Note this is needed
   *  for some devices such as Sony gauges. 
   *
   *  This should probably be a selectable option - a future
   *  mod.
   */
  sign = strchr( pBuf, '-' );

  if (!sign)
    sign = strchr( pBuf, '+' );

  if ( sign ) {

    ptr = sign;
    ptr++;

    while ( !isdigit( *ptr ) ) {

      if ( *ptr != ' ' ) {

	zeroIt = 0;
	break;
      }

      if ( *ptr == '\0' )
	break;
      
      ptr++;
    }

    if ( zeroIt ) {

      ptr = ++sign;

      while ( *ptr == ' ' ) {

	*ptr = '0';
	ptr++;
      }
    }
  }

  if ( formatInfo->dataType == RBF_INT ) {
    /* 
     *  A data type spec of integer exists so the data stream is
     *  expected to be of the form '%d'. A '%n' is appended to the 
     *  data format specification so the total number of characters 
     *  consumed can be tallied.
     */
      sprintf( bfr,"%s%s%s", formatInfo->dataFormat, cmt, "%n" );

      sscanf( pBuf, bfr, pValue, &count );
  }
  else {
    /*  
     *  a data format specification does not exist so assume '%d' 
     */ 
    if ( format ) 
      sprintf( bfr, "%s%s%s%s", format, "%d", cmt, "%n" );

    else
      sprintf( bfr, "%s%s%s", "%d", cmt, "%n" );

    sscanf( pBuf, bfr, &iValue, &count );

    *pValue = iValue;
  }

  return count;
}


/*
 * ---------------------------------------------------------------------------
 *  extractBinaryData()- the data stream is expected to be a string of 
 *  '0' and '1'.
 * 
 */
STATIC long extractBinaryData( char            *pBuf,
			       char            *format,
			       drvResponseInfo *formatInfo,
			       char            *cmt,
			       long            *pValue )
{
  long     count = 0,
           idx = 0,
           iValue = 0;

  unsigned abort = FALSE;

  char     *pDelim,
           bfr[DRV_SERIAL_BUF_SIZE];

  if ( drvAsciiDebugLevel & 64 ) printf("extractBinaryData\n");

  /*  
   *  if characters preceed the bit stream then process them first.
   */
  if ( formatInfo->preAmble ) {

    strncpy( bfr, formatInfo->preAmble, formatInfo->preAmbleCnt );
    bfr[formatInfo->preAmbleCnt] = '\0';

    idx = strlen( bfr );
    bfr[idx] = '%';
    bfr[++idx] = 'n';
    bfr[++idx] = '\0';
    sscanf( pBuf, bfr, &count );
  }

  /*  
   *  vxworks implementation of '%*c' skips whitespace so there is 
   *  no way for the user to specify that whitespace (ie. record 
   *  terminators) should be skipped. Thus we always strip whitespace 
   *  before converting to integer. Brutal but necessary. 
   */
  while ( isspace( pBuf[count] ) ) count++;
   
  /*  
   *  Process the bit stream, skipping over any delimiter characters. 
   */
  for ( ;; ) 

    if ( (pBuf[count] != '0') && (pBuf[count] != '1') ) {

      abort = TRUE;
      
      if ( formatInfo->delimiters ) {

	pDelim = formatInfo->delimiters;

	while ( pDelim && (*pDelim != '\0') && (*pDelim != ']') ) {

	  if (*pDelim == pBuf[count]) {
	    abort = FALSE; 
	    break;
	  }

	  pDelim++;
	}
      }

      if ( abort ) break; 

      else count++;

    } else {

      iValue = (iValue << 1) | (pBuf[count++] - '0');
    }

  /*  
   *  If the stream contains characters after the bit stream then 
   *  process them.
   */
  if ( count ) {

    sprintf( bfr, "%s%s%s", formatInfo->dataFormat, cmt, "%n");

    idx = 0;
    sscanf( &pBuf[count], bfr, &idx );
    
    count += idx;
    
    *pValue = iValue;
  }
 
  return count;
}
 

/*
 * ---------------------------------------------------------------------------
 *  extractStringData()- the data stream is expected to be a string 
 *  which will be converted to integer 
 *  (eg. "abcd" -> 0x61626364 -> 1633837924).
 * 
 */
STATIC long extractStringData( char            *pBuf,
			       char            *format,
			       drvResponseInfo *formatInfo,
			       char            *cmt,
			       long            *pValue )
{
  char     bfr[DRV_SERIAL_BUF_SIZE];

  long     count = 0,
           idx = 0,
           iValue = 0;


  if ( drvAsciiDebugLevel & 64 ) printf("extractStringData\n");

  /*
   *  Ensure that the characters preceding the "%nc" all match.
   */
  if ( formatInfo->preAmbleCnt > 0 ) {

    strncpy( bfr, formatInfo->preAmble, formatInfo->preAmbleCnt );

    bfr[formatInfo->preAmbleCnt] = '%';
    bfr[formatInfo->preAmbleCnt+1] = 'n';

    sscanf( pBuf, bfr, &count );

    /*
     *  The leading characters are not as expected so quit.
     */
    if ( count == 0 ) return 0;
  }

  /*
   *  Extract the characters as binary values.
   *  Note that '\0' will not work correctly.
   */
  for ( idx=0; idx<formatInfo->dataCnt; idx++) {

    iValue = (iValue << 8) | (int) pBuf[count+idx];
  }

  *pValue = iValue;

  count += idx;

  /*
   *  Ensure the trailing characters match correctly as well.
   */
  if ( formatInfo->dataFormat[0] != '\0' ) 
    sprintf( bfr, "%s%s%s", formatInfo->dataFormat, cmt, "%n" );
  else
    sprintf( bfr, "%s%s", cmt, "%n" );

  idx = 0;
  sscanf( &pBuf[count], bfr, &idx );
  count += idx;

  return count;
}


/*
 * ---------------------------------------------------------------------------
 *  processRealData() - a data stream of type real is expected.
 * 
 */
STATIC long processRealData( char            *pBuf,
			     char            *format,
			     drvResponseInfo *formatInfo,
			     char            *cmt,
			     double          *pValue )
{
  long   count = 0,
         iValue = 0;

  double fValue = 0.0;

  if ( drvAsciiDebugLevel & 64 ) printf("processRealData\n");

  if ( formatInfo->killAll ) return S_drvAscii_badParam;

  /* 
   *  If a command prompt exists then format the output string and
   *  send it.
   */
  if ( formatInfo->dataType == RBF_INT ) {
    /* 
     *  The data stream is expected to have integer format so process 
     *  the stream and convert the value to real.
     */
    count = extractIntData( pBuf, format, formatInfo, cmt, &iValue ); 

    *pValue = iValue;
  }

  else if ( formatInfo->dataType == RBF_REAL ) {
    /* 
     *  The data stream is expected to have real format so process 
     *  as such and convert to integer.
     */
    count = extractRealData( pBuf, format, formatInfo, cmt, &fValue ); 

    *pValue = fValue; 
  }

  else if ( formatInfo->dataType == RBF_BINARY ) {
    /* 
     *  The data stream is expected to be a bit stream (a series of 
     *  '0' and '1') so process the stream and convert the value to 
     *  integer.
     */
    count = extractBinaryData( pBuf, format, formatInfo, cmt, &iValue ); 

    *pValue = iValue;
  }

  else if ( formatInfo->dataType == RBF_STRING ) {
    /* 
     *  The data stream is expected to be a sequence of ascii chars  
     *  which must be converted to an integer  
     *  (eg.  "abcd" -> 0x61626364 -> 1633837924) and returned as a real
     */
    count = extractStringData( pBuf, format, formatInfo, cmt, &iValue );

    *pValue = iValue;
  }

  else { 
    /*  
     *  No specification exists for the data stream so assume real.
     */
    count = extractRealData( pBuf, format, formatInfo, cmt, &fValue ); 

    *pValue = fValue;
  }

  return count;
}


/*
 * ---------------------------------------------------------------------------
 *  processIntegerData() -  a data stream of type integer is expected.
 * 
 */
STATIC long processIntegerData( char            *pBuf,
				char            *format,
				drvResponseInfo *formatInfo,
				char            *cmt,
				long            *pValue )
{
  long   count = 0,
         iValue = 0;

  double fValue = 0.0;

  if ( drvAsciiDebugLevel & 64 ) printf("processIntegerData\n");

  if ( formatInfo->killAll ) return S_drvAscii_badParam;

  /* 
   *  If a command prompt exists then format the output string and
   *  send it.
   */
  if ( formatInfo->dataType == RBF_INT ) {
    /* 
     *  The data stream is expected to have integer format so process 
     *  as such.
     */
     count = extractIntData( pBuf, format, formatInfo, cmt, &iValue ); 

    *pValue = iValue;
  }

  else if ( formatInfo->dataType == RBF_REAL ) {
    /* 
     *  The data stream is expected to have real format.
     */
    count = extractRealData( pBuf, format, formatInfo, cmt, &fValue ); 

    *pValue = (long) fValue;
  }

  else if ( formatInfo->dataType == RBF_BINARY ) {
    /* 
     *  The data stream is expected to be a bit stream (a series
     *  of '0' and '1') so process the stream and convert the value 
     *  to real.
     */
    count = extractBinaryData( pBuf, format, formatInfo, cmt, &iValue ); 

    *pValue = iValue;
  }

  else if ( formatInfo->dataType == RBF_STRING ) {
    /* 
     *  The data stream is expected to be a sequence of ascii chars  
     *  which must be converted to an integer  
     *  (eg.  "abcd" -> 0x61626364 ->1633837924).
     */
    count = extractStringData( pBuf, format, formatInfo, cmt, &iValue );

    *pValue = iValue;
  }

  else { 
    /*  
     *  No specification exists for the data stream so assume integer.
     */
    count = extractIntData( pBuf, format, formatInfo, cmt, &iValue ); 

    *pValue = iValue;
  }

  return count;
}


/*
 * ---------------------------------------------------------------------------
 *  getInteger() - this function is intended to be used to obtain  
 *  initial values, from a remote device, for items/signals/quantities 
 *  which are integer output things.
 *
 *  This function would normally be called during init to obtain intial 
 *  values integer output records (BO, MBBO, and LO ).
 *
 */
STATIC long getInteger( drvCmndPrivate *pCmndPriv,
			void           *pValue, 
			drvAsyncIO     *pIO, 
			drvCmndArg     *pFuncParam )
{
  drvAppPrivate    *pAppPriv = pCmndPriv->pAppPriv;

  drvSerialResponse resp;

  long              status, value, count;
  char              msg[256], *pBuf;

  /* 
   *  If a prompt exists then format the output strin.
   */
  if ( strlen( pFuncParam->rbvPrompt ) > 0 ) {
    
    sprintf( msg, "%s%s", pFuncParam->rbvPrompt, pAppPriv->writeCmt );
    
    /* 
     *  Send the output and wait for the response 
     */
    pCmndPriv->rbvFlag = TRUE;

    status = drvAsciiQueryStringReq( pCmndPriv, msg, &resp );

    pCmndPriv->rbvFlag = FALSE;

    if ( status ) {

      pAppPriv->queryFailures++;
      return S_drvAscii_queryFail;
    }

    pBuf = (char *)resp.buf;


    if ( strlen( pFuncParam->rbvFormat ) > 0 ) {
      /* 
       *  Skip over (kill) any leading characters if so designated 
       *  in the readback format string.
       */
      pBuf = pBuf + pFuncParam->rbvInfo.killCnt;

      /* 
       *  Get the data value as per the specified forma.
       */
      count  = processIntegerData( pBuf, 
				   pFuncParam->rbvFormat, 
				   &pFuncParam->rbvInfo, 
				   pAppPriv->readCmt,
				   &value );

      /* 
       *  If all the data was not cosumed (ie. the stream did not 
       *  match the specification) then return an error.
       */
      if (count + pFuncParam->rbvInfo.killCnt + 1 != resp.bufCount) 
	status = -1;

      else 
	*((long *)pValue) = value;

    } else
      /* 
       *  Get the data value as per the default format.
       */
      status = sscanf( pBuf, "%ld", (long *) pValue );

    if ( status != 1 ) {

      pAppPriv->dataErrors++;
      return S_drvAscii_dataErr;
    }    
  }

  /*  
   *  If there was no readback capability then simply return.
   */
  return S_drvAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 *  getReal() - this function is intended to be used to obtain 
 *  initial values, from a remote device, for items/signals/quantities
 *   which are float output things.
 *
 *  This function would normally be called during init to obtain intial 
 *  values for real output records (AO).
 *
 */
STATIC long getReal( drvCmndPrivate *pCmndPriv,
		     void          *pValue, 
		     drvAsyncIO    *pIO, 
		     drvCmndArg    *pFuncParam )
{
  drvAppPrivate    *pAppPriv = pCmndPriv->pAppPriv;

  drvSerialResponse resp;

  long              status,
                    count;

  double            fValue;

  char              msg[256], *pBuf;

  /*  
   *  If a prompt exists then format the output string.
   */
  if ( strlen( pFuncParam->rbvPrompt ) > 0 ) {
    
    sprintf( msg, "%s%s", pFuncParam->rbvPrompt, pAppPriv->writeCmt );

    /*  
     *  Send the output and wait for the response.
     */
    pCmndPriv->rbvFlag = TRUE;

    status = drvAsciiQueryStringReq( pCmndPriv, msg, &resp );

    pCmndPriv->rbvFlag = FALSE;

    if ( status ) {

      pAppPriv->queryFailures++;
      return S_drvAscii_queryFail;
    }

    pBuf = (char *)resp.buf;

    if ( strlen( pFuncParam->rbvFormat ) > 0 ) { 
      /* 
       *  Skip over (kill) any leading characters, if designated  
       *  as such in the readback format string.
       */
      pBuf = pBuf + pFuncParam->rbvInfo.killCnt;

     /*  
      *  Get the data value as per the specified format.
      */
      count  = processRealData( pBuf, 
				   pFuncParam->rbvFormat, 
				   &pFuncParam->rbvInfo, 
				   pAppPriv->readCmt,
				   &fValue );

      /* 
       *  If all the data was not cosumed (ie. the stream did not 
       *  match the specification) then return an error.
       */
      if ( count + pFuncParam->rbvInfo.killCnt +1 != resp.bufCount) 
	status = -1;

      else if ( !((*pIO).id.pCmndArg->passThru) )
	*((double *)pValue) = fValue;
      else
	*((long *)pValue) = fValue;

    } else
      /*  
       *  Get the data value as per the default format.
       */
      status = sscanf( pBuf, "%lf", &fValue );

    if ( status != 1 ) {

      pAppPriv->dataErrors++;
      return S_drvAscii_dataErr;
    } 
   
    if ( !((*pIO).id.pCmndArg->passThru) )
      *(double *)pValue = fValue;
    else 
      *(long *)pValue = fValue * pAppPriv->slope;
  }

  /*  
   *  If there was no readback capability then simply return.
   */
  return S_drvAscii_Ok;
}


/* 
 * ---------------------------------------------------------------------------
 *  processOutputResponse() - process responses from output commands
 *               This is essentially a pattern match to determine if 
 *              a command was rejected.
 */
STATIC void processOutputResponse( drvAsyncIO *pIO )
{
  drvCmndPrivate    *pCmndPriv = &pIO->id;
  drvAppPrivate     *pAppPriv = pCmndPriv->pAppPriv; 
  /*
  drvSerialResponse  resp;
  */
  char              *pBuf;
  long               status, count, length;
  long               dataErr = 0, idx = 0;

  char               format[2 * MAX_STRING_SIZE + 2];
  char              *pFrmt, *ptr, *ptr2;

  if ( drvAsciiDebugLevel & 64 ) printf("processOutputResponse\n");

  pAppPriv->resp.buf[0] = '\0';

  status = drvSerialNextResponse( pAppPriv->pLink->linkId, 
				  &pAppPriv->resp );

  pBuf = (char *)(pAppPriv->resp.buf);
  
  /*  
   *  Is there a response string? Should not be here if not! 
   */
  if ( strlen( pCmndPriv->pCmndArg->cmndFormat ) > 0 ) {

    /* 
     *  Should any of the response be ignored? Only the first
     *  'n' chars can be ignored, that is, %nk is only valid
     *  at the beginning of the format string.
     */
    length = strlen( pBuf );

    pBuf = pBuf + pCmndPriv->pCmndArg->cmndInfo.killCnt;

    /*  
     *  Do a simple baseline check that everything matches as is.
     */
    sprintf( format,"%s%s%s",
	     pCmndPriv->pCmndArg->cmndInfo.dataFormat,
	     pAppPriv->readCmt, "%n" );

    sscanf( pBuf, format, &count );

    if ( (count + pCmndPriv->pCmndArg->cmndInfo.killCnt) == length ) {

      /*
       *  The formatting was all successfully matched so we cause
       *  the device callback (ultimately a record is updated) to be 
       *  processed.
       */
      if (pIO->pIoDoneCB)  
	(*(devIoDoneCallBack *)pIO->pIoDoneCB)( (void *)pIO->pIoDoneArg, 
						 S_drvAscii_Ok, 0 ); 
      
      return;
    }

    /* 
     *  No assignments are made for the format string so
     *  all data specs must be of the form %*t. The total
     *  number of chars comsumed in the sscanf is expected
     *  to be the same as the length of data read. If this
     *  is not so then a data error is assumed!
     *
     *  Of course, if "%*s" is part of the response format
     *  then special action must be taken to pattern match
     *  anything which follows the "%*s" so it is accounted for 
     *  (sigh). That is, there may be a preamble and a post-
     *  amble and the postamble is not delineated during
     *  format processing at record init.
     *  
     */ 
    pFrmt = pCmndPriv->pCmndArg->cmndInfo.dataFormat;

    while ( *pFrmt != '%' ) {
      
      if ( *pFrmt != *pBuf ) {

	dataErr = 1;
	break;
      }

      pFrmt++;
      pBuf++;

      idx++;
    }

    if ( !dataErr ) {

      /*
       *  The format string has a '%' something so we need to explicitly
       *  handle '%s'.
       */
      pFrmt+=2;  /* skip over '*' */

      if ( ((*pFrmt == 's') || (*pFrmt == 'S')) 
	   && (*(pFrmt+1) != '\0') ) {
	/* 
	 *  This is grungy as we must pattern match anything which
	 *  follows the "%*s".
	 */
	ptr = strstr( pBuf, ++pFrmt );

	if ( ptr == NULL ) {
 
	  dataErr = 1;

	} else {

	  while ( ptr != NULL ) {

	    ptr2 = ptr;

	    ptr = strstr( ++ptr, pFrmt );

	  }

          sprintf( format, "%s%s%s", pFrmt, pAppPriv->readCmt, "%n" );
	  sscanf( ptr2, format, &count );

	  if ( count < strlen(pFrmt) + strlen(pAppPriv->readCmt) )
	    dataErr = 1;

	}

      } else {
      
	/*
	 *  The format include '%' but not '%s' so try to match
	 *  the specified format with the response.
	 */
	sprintf( format,"%s%s%s", 
		 pCmndPriv->pCmndArg->cmndInfo.dataFormat, 
		 pAppPriv->readCmt, "%n" );

	if ( drvAsciiDebugLevel & 2 ) {

	  printf("response->");
	  printString(-1,(char *)(pAppPriv->resp.buf));
	  printf("<-\n");

	  printf("format->");
	  printString(-1,format);
	  printf("<-\n");	  
	}

	sscanf( (char *)(pAppPriv->resp.buf), format, &count );
	
	if ( (count + pCmndPriv->pCmndArg->cmndInfo.killCnt) != length ) 
	  dataErr = 1;
      }
    }
     
    if ( dataErr ) {

      /*
       *  The response did not match the specified format so set an
       *  error and callback the device support level, which ultimately
       *  updates a record with an error status.
       */
      status = S_drvAscii_dataErr;
      
      errPrintf( status, __FILE__, __LINE__,
		 " : {%s <%s><%s>} response data error <%s>\n",
		 (int)pAppPriv->pLink->fileName,
		 (int)pCmndPriv->pCmndArg->cmndPrompt,
		 (int)pCmndPriv->pCmndArg->cmndFormat, 
		 (int)(pAppPriv->resp.buf) );

      if (pIO->pIoDoneCB)  
	(*pIO->pIoDoneCB)( (void *)pIO->pIoDoneArg, status, 0 ); 

      return;
    }

  } else {
    /*  
     *  The format string does not exist so the record is faulty. 
     *  In actual fact it should not be possible to get here as the 
     *  format string was checked in sendOutput()  
     */
    errPrintf( S_drvAscii_dataErr, __FILE__, __LINE__,
	       " : {%s <%s><NULL>} No response format string.\n",
	       (int)pAppPriv->pLink->fileName, 
	       (int)pCmndPriv->pCmndArg->cmndPrompt );


    if (pIO->pIoDoneCB) 
      (*(devIoDoneCallBack *)pIO->pIoDoneCB)( (void *)pIO->pIoDoneArg, 
					      S_drvAscii_queryFail, 0 ); 
    
    return;
  }

  /*
   *  The response was successfully matched with the expect format
   *  so callback the device support routine to update the record.
   */
  if (pIO->pIoDoneCB) 
	(*(devIoDoneCallBack *)pIO->pIoDoneCB)( (void *)pIO->pIoDoneArg, 
						 S_drvAscii_Ok, 0 ); 
}
  

/*
 * ---------------------------------------------------------------------------
 *  sendOutput() - determines the mechanism for sending an output 
 *  command. The decision is based on whether or not a response is 
 *  expected or to be ignored.
 */
STATIC long sendOutput( drvAppPrivate     *pAppPriv,
			drvSerialRequest  *pReq, 
			drvAsyncIO        *pIO, 
			drvCmndArg        *pFuncParam )
{
  long status;

  if ( drvAsciiDebugLevel & 64 ) printf("sendOutput\n");

  /*  
   *  Is there a response string? 
   */
  if ( strlen( pFuncParam->cmndFormat ) > 0 ) {
    
    /*  
     *  Is the response to be ignored? 
     */
    if ( pFuncParam->cmndInfo.killAll ) {
      /*  
       *  Ignore the response.
       */
      status =  drvAsciiSendIgnoreResp( pIO, pReq );

      if ( status != S_drvSerial_OK ) 
	return status;

      else 
	return S_drvAscii_Ok;

    } else {
      /* 
       *  Do not ignore the response (essentially an error check 
       *  on the response) 
       */
      pIO->pAppDrvPrivate = processOutputResponse;

      status = drvAsciiSendCallBackWithResp( pIO, pReq, 0 );

      if ( status ) 
	return status;

      else 
	return S_drvAscii_AsyncCompletion;
    }

  } else
    /*  
     *  No response is expected so simply send the command.
     */ 
    return drvAsciiSend( pAppPriv, pReq );
}


/*
 * ---------------------------------------------------------------------------
 *  writeInteger() - used for outputting strings in which integer data is
 *  to be embedded, typically BO, MBBO, and LO (longout) records.
 */
STATIC long writeInteger( drvAppPrivate *pAppPriv, 
			  void          *pValue, 
			  drvAsyncIO    *pIO, 
			  drvCmndArg    *pFuncParam )
{
  /*
  drvSerialRequest req; 
  */
  char             format[256],
                   preAmble[128];
  double           fValue;
  long             iValue, iVal,
                   idx, posn, length;
  
  if ( drvAsciiDebugLevel & 64 ) printf("writeInteger\n");

  if (pAppPriv->debugFlag & 0x8) printCmndArgs( pFuncParam );

  pAppPriv->req.buf[0] = '\0';

  /* 
   *  If a command prompt exists then format the output string and
   *  send it.
   */
  if ( strlen( pFuncParam->cmndPrompt ) > 0 ) {

    if ( pFuncParam->cmndPromptInfo.dataType == RBF_UNDEFINED ) 
      sprintf( (char *)(pAppPriv->req.buf), "%s%ld%s", 
	       pFuncParam->cmndPrompt, *(long *)pValue, pAppPriv->writeCmt );

    else if ( pFuncParam->cmndPromptInfo.dataType == RBF_REAL ) {

      fValue = *(long *)pValue;
      sprintf( format, pFuncParam->cmndPrompt, fValue ); 
      sprintf( (char *)(pAppPriv->req.buf), "%s%s", 
	       format, pAppPriv->writeCmt );

    } else if ( pFuncParam->cmndPromptInfo.dataType == RBF_INT ) {

      sprintf( format, pFuncParam->cmndPrompt, *(long *)pValue ); 
      sprintf( (char *)(pAppPriv->req.buf), "%s%s", 
	       format, pAppPriv->writeCmt );

    } else if ( pFuncParam->cmndPromptInfo.dataType == RBF_BINARY ) {

      iValue = *(long *)pValue;
      
      if ( pFuncParam->cmndPromptInfo.preAmble ) {

	length = min( sizeof( preAmble ), 
		      pFuncParam->cmndPromptInfo.preAmbleCnt );

	strncpy( preAmble, pFuncParam->cmndPromptInfo.preAmble, length );
	
	preAmble[length] = '\0';

      } else preAmble[0] = '\0';

      posn = 0;

      /*
       *  Need to distinguish between "%b" and %1b" as in both
       *  case the 'dataCnt' is set to 1!
       */
      if ( pFuncParam->cmndPromptInfo.
	   preAmble[pFuncParam->cmndPromptInfo.preAmbleCnt] == '%' 
	   && 
	   ( pFuncParam->cmndPromptInfo.
	     preAmble[pFuncParam->cmndPromptInfo.preAmbleCnt+1] == 'b'
	     ||
	     pFuncParam->cmndPromptInfo.
	     preAmble[pFuncParam->cmndPromptInfo.preAmbleCnt+1] == 'B'
	     )
	   ) 
	idx = sizeof( long ) * 8;
      else
	idx = pFuncParam->cmndPromptInfo.dataCnt - 1;

      for ( ; idx >= 0; idx-- ) 
	sprintf( &format[posn++], "%c", 
		 (char)(0x30 + (1 & (iValue >>idx))) );
	
      format[posn] = '\0';

      sprintf( (char *)(pAppPriv->req.buf), "%s%s%s%s", preAmble, format, 
	       pFuncParam->cmndPromptInfo.dataFormat, pAppPriv->writeCmt );

    } else if ( pFuncParam->cmndPromptInfo.dataType == RBF_STRING ) {

      iValue = *(long *)pValue;

      if ( pFuncParam->cmndPromptInfo.preAmble ) {

	length = min( sizeof( preAmble ), 
		      pFuncParam->cmndPromptInfo.preAmbleCnt );

	strncpy( preAmble, pFuncParam->cmndPromptInfo.preAmble, length );
	
	preAmble[length] = '\0';

      } else preAmble[0] = '\0';

      posn = 0;

      for ( idx = pFuncParam->cmndPromptInfo.dataCnt - 1; idx >= 0; idx-- ) {
	/*  
	 *  Must suppress output of null bytes! This ain't pretty and is  
	 *  actually a bug. To correct this is a major change as bufCount 
	 *  would need to be done prior to each of the send calls.        
	 *  Maybe outputting as a string should be disallowed or only     
	 *  allow single character conversions such that a null byte will 
	 *  cause no output. 
	 */
        iVal = iValue >> (8 * idx);

        if ( iVal > 0)
	  sprintf( &format[posn++], "%c", (char)iVal );
      }

      format[posn] = '\0';

      sprintf( (char *)(pAppPriv->req.buf), "%s%s%s%s", preAmble, format, 
	       pFuncParam->cmndPromptInfo.dataFormat, pAppPriv->writeCmt );
    }

    return sendOutput( pAppPriv, &(pAppPriv->req), pIO, pFuncParam );
  }
  
  /* 
   *  There is no prompt string so the record is faulty. 
   *  Should not be here as this should have been trapped at init time.
   */
  return S_drvAscii_badParam;
}


/*
 * ---------------------------------------------------------------------------
 *  writeReal() - used for sending floating point values, typically
 *  AI records.
 */
STATIC long writeReal( drvAppPrivate *pAppPriv, 
		       void          *pValue, 
		       drvAsyncIO    *pIO, 
		       drvCmndArg    *pFuncParam )
{
  /*
  drvSerialRequest req; 
  */
  char             format[256],
                   preAmble[128];

  double           fValue;

  long             iValue,
                   idx, posn, length;
    

  if ( drvAsciiDebugLevel & 64 ) printf("writeReal(%f)\n",
					*((double*)pValue));

  if (pAppPriv->debugFlag & 0x8) printCmndArgs( pFuncParam );

  pAppPriv->req.buf[0] = '\0';

  /* 
   *  The value is always divided by slope (default=1.0) so that the
   *  values can be adjusted to the precision required by a remote
   *  device. This is needed because the rval from an analog output
   *  record is an integer - if one used VAL then the ESLO, ASLO and
   *  ROFF would be circumvented.
   */
  if ( !((*pIO).id.pCmndArg->passThru) )
    fValue = (double)(*(double *)pValue / pAppPriv->slope);
  else
    fValue = *(double *)pValue;

  /* 
   *  If a command prompt exists then format the output string and
   *  send it.
   */
  if ( strlen( pFuncParam->cmndPrompt ) > 0 ) {

    if ( pFuncParam->cmndPromptInfo.dataType == RBF_UNDEFINED ) 
      sprintf( (char *)(pAppPriv->req.buf), "%s%f%s", 
	       pFuncParam->cmndPrompt, fValue, pAppPriv->writeCmt );

    else if ( pFuncParam->cmndPromptInfo.dataType == RBF_REAL ) {

      sprintf( format, pFuncParam->cmndPrompt, fValue ); 
      sprintf( (char *)(pAppPriv->req.buf), "%s%s", 
	       format, pAppPriv->writeCmt );

    } else if ( pFuncParam->cmndPromptInfo.dataType == RBF_INT ) {

      iValue = fValue;
      sprintf( format, pFuncParam->cmndPrompt, iValue ); 
      sprintf( (char *)(pAppPriv->req.buf), "%s%s", 
	       format, pAppPriv->writeCmt );

    } else if ( pFuncParam->cmndPromptInfo.dataType == RBF_BINARY ) {

      iValue = fValue;
      
      preAmble[0] = '\0';

      if ( pFuncParam->cmndPromptInfo.preAmble ) {

	length = min( sizeof( preAmble ), 
		      pFuncParam->cmndPromptInfo.preAmbleCnt );

	strncpy( preAmble, pFuncParam->cmndPromptInfo.preAmble, length );

	preAmble[length] = '\0';

      } else preAmble[0] = '\0';

      posn = 0;
      
      /*
       *  Need to distinguish between "%b" and %1b" as in both
       *  case the 'dataCnt' is set to 1!
       */
      if ( pFuncParam->cmndPromptInfo.
	   preAmble[pFuncParam->cmndPromptInfo.preAmbleCnt] == '%' 
	   && 
	   ( pFuncParam->cmndPromptInfo.
	     preAmble[pFuncParam->cmndPromptInfo.preAmbleCnt+1] == 'b'
	     ||
	     pFuncParam->cmndPromptInfo.
	     preAmble[pFuncParam->cmndPromptInfo.preAmbleCnt+1] == 'B'
	     )
	   ) 
	idx = sizeof( long ) * 8;
      else
	idx = pFuncParam->cmndPromptInfo.dataCnt - 1;

      for ( ; idx >= 0; idx-- ) 
	sprintf( &format[posn++], "%c", 
		 (char)(0x30 + (1 & (iValue >>idx))) );
	
      format[posn] = '\0';

      sprintf( (char *)(pAppPriv->req.buf), "%s%s%s%s", preAmble, format, 
	       pFuncParam->cmndPromptInfo.dataFormat, pAppPriv->writeCmt );
    } 

    return sendOutput( pAppPriv, &(pAppPriv->req), pIO, pFuncParam );
  }

  /* 
   *  There is no prompt string so the record is faulty. 
   *  Should not be here as this should have been trapped at init time.
   */
  return S_drvAscii_badParam;

}

/*
 * ---------------------------------------------------------------------------
 *  writeString() - used for sending strings 
 */
STATIC long writeString( drvAppPrivate *pAppPriv, 
			 void          *pDataStr, 
			 drvAsyncIO    *pIO, 
			 drvCmndArg    *pFuncParam )
{
  /*
   drvSerialRequest req; 
   */

  char              buffer[256];
  char              data[256];


  if ( drvAsciiDebugLevel & 64 ) printf("writeString\n");

  if (pAppPriv->debugFlag & 0x8) printCmndArgs( pFuncParam );

  pAppPriv->req.buf[0] = '\0';

  /* 
   *  If a command prompt exists then format the output string and
   *  send it.
   */
  if ( strlen( pFuncParam->cmndPrompt ) > 0 ) {

    if ( pFuncParam->cmndPromptInfo.dataType != RBF_UNDEFINED ) {

      if ( strlen( (char *)pDataStr ) > 0 ) {

	sprintf( buffer, "%s%s", 
		 pFuncParam->cmndPrompt, pAppPriv->writeCmt );

	sprintf( data, buffer, (char *)pDataStr );

      } else {

	sprintf( data, "%s%s", 
		 pFuncParam->cmndPrompt, pAppPriv->writeCmt );

      }
    } else {

      if ( strlen( (char *)pDataStr ) > 0 )
	sprintf( data, "%s%s%s",
		 pFuncParam->cmndPrompt, 
		 (char *)pDataStr, pAppPriv->writeCmt );
      else
	sprintf( data, "%s%s", pFuncParam->cmndPrompt, pAppPriv->writeCmt );

    }
  } else {

    if ( strlen( (char *)pDataStr ) > 0 ) 
      sprintf( data, "%s%s", (char *)pDataStr, pAppPriv->writeCmt );
    else
      sprintf( data, "%s", pAppPriv->writeCmt );

  }
 
  /*
   *  Translate all '\' escape sequences.
   */
  strnccpy( (char *)(pAppPriv->req.buf), data, 0 );

  pIO->pAppDrvPrivate = NULL;

  return sendOutput( pAppPriv, &(pAppPriv->req), pIO, pFuncParam );
}


/*
 * ---------------------------------------------------------------------------
 * processIntegerResponse()
 * async function which is the call back for integer data prompts
 */
STATIC void processIntegerResponse( drvAsyncIO *pIO )
{
  drvCmndPrivate   *pCmndPriv = &pIO->id;
  drvAppPrivate    *pAppPriv = pCmndPriv->pAppPriv; 
  drvResponseInfo  *formatInfo;
  /*
  drvSerialResponse resp; 
  */

  char              *pBuf, *format;
  long              status, sstatus = 1;
  long              count, value = 0;
 
  if ( drvAsciiDebugLevel & 64 ) printf("processIntegerResponse\n");

  pAppPriv->resp.buf[0] = '\0';

  /*  
   *  Fetch the response string.
   */
  status = drvSerialNextResponse( pAppPriv->pLink->linkId, 
				  &(pAppPriv->resp) );


  if ( status != S_drvSerial_OK ) {
    /*  
     *  Serial i/o fault so abort.
     */
    (*(devIoDoneCallBack *)pIO->pIoDoneCB)( (void *)pIO->pIoDoneArg, status, 0 ); 

  } else {

    pBuf = (char *)(pAppPriv->resp.buf);

    if ( pCmndPriv->rbvFlag ) {

      format = pCmndPriv->pCmndArg->rbvFormat;
      formatInfo = &pCmndPriv->pCmndArg->rbvInfo;

    } else {

      format = pCmndPriv->pCmndArg->cmndFormat;
      formatInfo = &pCmndPriv->pCmndArg->cmndInfo;
    }

    if ( strlen( format ) > 0 ) {
      /*
       *  If the format string begins with %k or %'n'K then skip/kill 
       *  the specified number of bytes. Note that the special case '%*k' 
       *  (kill all) is trapped before here such that this processing  
       *  never occurs.
       *
       */
      pBuf = pBuf + formatInfo->killCnt;

      count = processIntegerData( pBuf, format, formatInfo, 
				  pAppPriv->readCmt, &value );

      if ( (count + formatInfo->killCnt + 1)
	   != pAppPriv->resp.bufCount ) sstatus = -1;

    } else {
      /* 
       *  Obtain the expected integer value using the default 
       *  format string 
       */
      sstatus = sscanf( pBuf, "%ld", &value );
    }

    if ( sstatus != 1 ) {
      /*  
       *  Response string was not as expected so ensure an error 
       *  is returned
       */
      status = S_drvAscii_dataErr;
      value = 0;
    }
 
    /*  this is the asynchronous callback to device support */
    (*(devIoDoneCallBack *)pIO->pIoDoneCB)( (void *)pIO->pIoDoneArg, status, value );
  }
}


/*
 * ---------------------------------------------------------------------------
 *  readInteger() - function which prompts for an integer data value
 */
STATIC long readInteger( drvAppPrivate *pAppPriv, 
			 void          *pValue, 
			 drvAsyncIO    *pIO, 
			 drvCmndArg    *pFuncParam )
{
  drvCmndPrivate        *pCmnd = &pIO->id;
  /*
  drvSerialRequest req;
  */
  long             status;

  if ( drvAsciiDebugLevel & 64 ) printf("readInteger\n");

  if (pAppPriv->debugFlag & 0x8) printCmndArgs( pFuncParam );

  pAppPriv->req.buf[0] = '\0';

  /* 
   *  If a command prompt exists then format the output string and
   *  send it - error otherwise, which should have been trapped 
   *  during init
   */
  if ( strlen( pFuncParam->cmndPrompt ) > 0 ) {

    sprintf( (char *)(pAppPriv->req.buf), "%s%s", 
	     pFuncParam->cmndPrompt, pAppPriv->writeCmt );

    /* 
     *  this is a prompt for integer data so setup the appropriate callback
     *  for the response string
     */
    pIO->pAppDrvPrivate = processIntegerResponse;

    pCmnd->rbvFlag = FALSE;

    status = drvAsciiSendCallBackWithResp( pIO, &(pAppPriv->req), 0 );

    if ( status ) return status;
    /* 
     *  indicate to device support that async completion will occur
     *  (ie. the records PACT will get set)
     */
    else return S_drvAscii_AsyncCompletion;

  } else {

    /*  should not get here as this is trapped during init */
    return S_drvAscii_badParam;
  }
}


/*
 * ---------------------------------------------------------------------------
 *  processRealResponse()
 *  async function which is the call back for real value data prompts
 */
STATIC void processRealResponse( drvAsyncIO *pIO )
{
  drvCmndPrivate   *pCmndPriv = &pIO->id;
  drvAppPrivate    *pAppPriv = pCmndPriv->pAppPriv; 
  drvResponseInfo  *formatInfo;
  /*
  drvSerialResponse resp;
  */
  char              *pBuf, *format;
  long              status, sstatus = 1;
  long              count;
  double            fValue = 0.0;
 
  if ( drvAsciiDebugLevel & 64 ) printf("processRealResponse\n");

  pAppPriv->resp.buf[0] = '\0';

  /*
   *  Attempt to queue the message in the serial write task.
   */
  /*  fetch the response string */
  status = drvSerialNextResponse( pAppPriv->pLink->linkId, 
				  &(pAppPriv->resp) );

  if ( status != S_drvSerial_OK ) {
    /*  serial i/o fault so abort */

    (*(devIoDoneCallBack *)pIO->pIoDoneCB)
      ( (void *)pIO->pIoDoneArg, status, 0 ); 

  } else {

    pBuf = (char *)(pAppPriv->resp.buf);

    if ( pCmndPriv->rbvFlag ) {

      format = pCmndPriv->pCmndArg->rbvFormat;
      formatInfo = &pCmndPriv->pCmndArg->rbvInfo;

    } else {

      format = pCmndPriv->pCmndArg->cmndFormat;
      formatInfo = &pCmndPriv->pCmndArg->cmndInfo;
    }

    if ( strlen( format ) > 0 ) {
      /*
       *  if the format string begins with %k or %'n'K then skip/kill 
       *  the specified number of bytes. Note that the special case '%*k' 
       *  (kill all) is trapped before here such that this processing  
       *  never occurs.
       *
       */
      pBuf = pBuf + formatInfo->killCnt;

      /* 
       *  obtain the expected float value using the specified format string 
       */
      count = processRealData( pBuf, format, formatInfo, 
			       pAppPriv->readCmt, &fValue );

      if ( count + formatInfo->killCnt + 1 
	   != pAppPriv->resp.bufCount ) sstatus = -1;

    } else {

      /*  obtain the expected float value using the default format string */
      sstatus = sscanf( pBuf, "%lf", &fValue );
    }

    if ( sstatus != 1 ) {

      /*  
       *  response string was not as expected so ensure an 
       *  error is returned
       */
      status = S_drvAscii_dataErr;
      fValue = 0.0;

    } else {

      status = S_drvAscii_Ok;

      /* 
       *  The value is multiplied by slope (default=1.0) so that the
       *  values can be adjusted from the precision required by a remote
       *  device. This is needed because the rval for an analog input
       *  record is an integer.
       */
      if ( !((*pIO).id.pCmndArg->passThru) )
      {
	fprintf(stderr,"\t\tmultiplying %lg by %lg\n",(double)fValue, (double)pAppPriv->slope);
	fValue *= pAppPriv->slope;
      }
    }

    /*  this is the asynchronous callback to device support */
    if ( (*pIO).id.pCmndArg->passThru )
    {
      (*(devRealIoDoneCallBack *)pIO->pIoDoneCB)( (void *)pIO->pIoDoneArg, 
						  status, fValue );
    }
    else
    {
      (*(devIoDoneCallBack *)pIO->pIoDoneCB)( (void *)pIO->pIoDoneArg, 
					      status, (long) fValue );
    }
  }
}


/*
 * ---------------------------------------------------------------------------
 *  readReal() - function which prompts for float data
 */
STATIC long readReal( drvAppPrivate *pAppPriv, 
		      void          *pValue, 
		      drvAsyncIO    *pIO, 
		      drvCmndArg    *pFuncParam )
{
  drvCmndPrivate        *pCmnd = &pIO->id;
  /*
  drvSerialRequest req;
  */
  long status;
 
  /* 
   *  If a command prompt exists then format the output string and
   *  send it - error otherwise.
   */
  if ( drvAsciiDebugLevel & 64 ) printf("readReal\n");
 
  if (pAppPriv->debugFlag & 0x8) printCmndArgs( pFuncParam );

  pAppPriv->req.buf[0] = '\0';

  /* 
   *  If a command prompt exists then format the output string and
   *  send it - error otherwise.
   */
  if ( strlen( pFuncParam->cmndPrompt ) > 0 ) {

    sprintf( (char *)(pAppPriv->req.buf), "%s%s", 
	     pFuncParam->cmndPrompt, pAppPriv->writeCmt );

    /* 
     *  this is a prompt for float data so setup the appropriate 
     *  callback for the response string
     */
    pIO->pAppDrvPrivate = processRealResponse;

    pCmnd->rbvFlag = FALSE;

    status = drvAsciiSendCallBackWithResp( pIO, &(pAppPriv->req), 0 );
    if ( status ) return status;
    /* 
     *  indicate to device support that async completion will occur
     *  (ie. the records PACT will get set)
     */
    else return S_drvAscii_AsyncCompletion;

  } else {
    /*  should not get here as this is trapped during init */
    return S_drvAscii_badParam;
  }
}


/*
 * ---------------------------------------------------------------------------
 *  processStringResponse()
 *  async function which is the call back for stringin data prompts
 */
STATIC void processStringResponse( drvAsyncIO *pIO )
{
  drvCmndPrivate    *pCmndPriv = &pIO->id;
  drvAppPrivate     *pAppPriv = pCmndPriv->pAppPriv;
  /*
  drvSerialResponse resp; 
  */
  drvResponseInfo  *rFormatInfo;

  long              status;
  long              count = 0;
  
  char              bfr[DRV_SERIAL_BUF_SIZE + 10];
  char              format[DRV_SERIAL_BUF_SIZE + 10];
  char             *pBuf;
  char             *rFormat;

  if ( drvAsciiDebugLevel & 64 ) printf("processStringResponse\n");

  pAppPriv->resp.buf[0] = '\0';

  /*  fetch the response string */
  status = drvSerialNextResponse(pAppPriv->pLink->linkId, 
				 &(pAppPriv->resp) );

  if ( status != S_drvSerial_OK ) {

    /*  serial i/o fault so abort */
    (*(devIoDoneCallBack *)pIO->pIoDoneCB)
      ( (void *)pIO->pIoDoneArg, status, 0 ); 

  }

  if ( pCmndPriv->rbvFlag ) {

    rFormat = pCmndPriv->pCmndArg->rbvFormat;
    rFormatInfo = &pCmndPriv->pCmndArg->rbvInfo;
    
  } else {
    
    rFormat = pCmndPriv->pCmndArg->cmndFormat;
    rFormatInfo = &pCmndPriv->pCmndArg->cmndInfo;
  }
  
  /*
   *  Translate all '\' escape sequences.
   */
  strnccpy( bfr,  (char *)(pAppPriv->resp.buf), pAppPriv->resp.bufCount );
  
  pBuf = bfr;
  
  /*  
   *  Is there a response string? Should not be here if not! 
   */
  if ( strlen( rFormat ) > 0 ) {
    
    /* 
     *  Should any of the response be ignored? Only the first
     *  'n' chars can be ignored, that is, %nk is only valid
     *  at the beginning of the format string.
     */    
    if ( rFormatInfo->killCnt > 0 ) {
      
      pBuf = pBuf + rFormatInfo->killCnt;
      
      while ( *rFormat != 'k' && *rFormat != 'K' && *rFormat != '\0') 
	rFormat++;
    }
    
    if ( rFormatInfo->recordCnt > 1) {
      
      /*
       *  It is beyond the scope of this driver to attempt to
       *  pattern match multi-line responses (one must attach
       *  a subroutine record to a waveform record to accomplish
       *  this). As a result the string is simply copied and
       *  the asynchronous callback is invoked.
       */
      strncpy( pCmndPriv->respStr, pBuf, sizeof( pCmndPriv->respStr ) );
      pCmndPriv->respStrCnt = strlen( pCmndPriv->respStr ); 
      
      /*  This is the asynchronous callback to device support. */
      (*(devIoDoneCallBack *)pIO->pIoDoneCB)
	( (void *)pIO->pIoDoneArg, status, 0 );
      
      return;
    }
    
    /*
     *  Not all formats can be successfully pattern matched
     *  for instance "%s%*s", "%s%c", "%s!", "%s1" are not parasable  
     *  but "%s %*s" is parasable. We have to assume that the "%s" 
     *  will always be terminated by whitespace. Anything else 
     *  is outside the scope of this driver (drvRegExp?).
     */
    if ( strcmp( rFormat, "%s" ) == 0 ) {
      
      /*
       * Simplest case with no preamble or postamble.
       * This is the backwards compatible code.
       */
      pCmndPriv->respStrCnt = min( sizeof( pCmndPriv->respStr ), 
				   pAppPriv->resp.bufCount );
      
      strncpy( pCmndPriv->respStr, pBuf, pCmndPriv->respStrCnt );
      
      count = (pAppPriv->resp.bufCount-1) - rFormatInfo->killCnt;
      
      pCmndPriv->respStrCnt = count;
      
    } else {
      
      sprintf( format, "%s%s%s", rFormat, pAppPriv->readCmt, "%n" );
      
      sscanf( pBuf, format, pCmndPriv->respStr, &count );
      
      pCmndPriv->respStrCnt = strlen( pCmndPriv->respStr ); 
    }
  } else {
    
    /*  
     *  The format string does not exist so we default to "%s".
     */
    
    sprintf( format, "%s%s%s", "%s",  pAppPriv->readCmt, "%n" );
    sscanf( pBuf, format, pCmndPriv->respStr, &count );
    
    pCmndPriv->respStrCnt = strlen( pCmndPriv->respStr );   
    
  }
  
  if ( count + rFormatInfo->killCnt != pAppPriv->resp.bufCount-1 )
    status = S_drvAscii_dataErr;
    
  /*  This is the asynchronous callback to device support. */
  (*(devIoDoneCallBack *)pIO->pIoDoneCB)
    ( (void *)pIO->pIoDoneArg, status, 0 );

}


/*
 * ---------------------------------------------------------------------------
 *  readString() - function which prompts for string data
 */
STATIC long readString( drvAppPrivate *pAppPriv, 
			void          *pDataStr, 
			drvAsyncIO    *pIO, 
			drvCmndArg    *pFuncParam )
{
  drvCmndPrivate        *pCmnd = &pIO->id;
  /*
  drvSerialRequest req; 
  */
  long             status;
  char             format[DRV_SERIAL_BUF_SIZE];
  char             data[DRV_SERIAL_BUF_SIZE];

  if ( drvAsciiDebugLevel & 64 ) printf("readString\n");

  if (pAppPriv->debugFlag & 0x8) printCmndArgs( pFuncParam );

  pAppPriv->req.buf[0] = '\0';

  /* 
   *  If a command prompt exists then format the output string and
   *  send it - error otherwise
   */
  if ( strlen( pFuncParam->cmndPrompt ) == 0 ) {

    if ( strlen( (char *)pDataStr ) > 0 ) 
      sprintf( data, "%s%s", (char *)pDataStr, pAppPriv->writeCmt );
    else
      sprintf( data, "%s", pAppPriv->writeCmt );

  } else {

    if ( pFuncParam->cmndPromptInfo.dataType == RBF_UNDEFINED )

	sprintf( data, "%s%s", pFuncParam->cmndPrompt, pAppPriv->writeCmt );

    else {

      if ( strlen( (char *)pDataStr ) > 0 ) {

	sprintf( format, "%s%s", 
		 pFuncParam->cmndPrompt, pAppPriv->writeCmt );

	sprintf( data, format, (char *)pDataStr );

      } else {
	/*
	 *  This is an error as a prompt exists with some dataType, 
	 *  indicating that pDataStr must be sprintf'd into the 
	 *  prompt yet pDataStr is empty.
	 */
	return S_drvAscii_badParam;
      }
    }
  }

  /*
   *  Translate all '\' escape sequences.
   */
  strnccpy( (char *)(pAppPriv->req.buf), data, 0 );

  pIO->pAppDrvPrivate = processStringResponse; 
 
  pCmnd->rbvFlag = FALSE;
 
  status = drvAsciiSendCallBackWithResp( pIO, &(pAppPriv->req), 0 );

  if ( status ) 
    return status;

  else 
    return S_drvAscii_AsyncCompletion;

  return S_drvAscii_badParam;

}


/*
 * ---------------------------------------------------------------------------
 *  noOutputReadFunc() - calling this function indicates that a wrong 
 *  operation (read or write) was performed for a record.
 */
STATIC long noOutputReadFunc( drvCmndPrivate *pCmndPriv,
			      void           *pValue, 
			      drvAsyncIO     *pIO, 
			      drvCmndArg     *pFuncParam )
{
  return S_drvAscii_writeOnlyParameter;
}



/*
 * ---------------------------------------------------------------------------
 *  setConnState() - allows the link state to be changed from not 
 *  connected (0) to connected (1) and visa versa
 *
 */
STATIC long setConnState( drvAppPrivate *pAppPriv, 
			  void          *pValue, 
			  drvAsyncIO    *pIO, 
			  drvCmndArg    *pFuncParam )
{
  pAppPriv->state.noComm = !(*(long *)pValue);

  if ( *(long *)pValue ) {

    errPrintf( 0, __FILE__, __LINE__, 
	       " : Reinitializing port %s\n", 
	       pAppPriv->pLink->fileName );

    assert( pAppPriv->pLink );

  }

  return S_drvAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 * readConnState() - returns a link's current connection state
 *  currently a link can only be marked as not connected if a fault occurs
 *  during init, or from a database record (ie. the user can force the 
 *  condition
 */
STATIC long readConnState( drvAppPrivate *pAppPriv, 
			   void          *pValue, 
			   drvAsyncIO    *pIO, 
			   drvCmndArg    *pFuncParam)
{
  getConnState( &(pIO->id), pValue, NULL,  pFuncParam );
  
  return S_drvAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 *  setSlope() - sets the slope for the link.
 *  slope is a value by which all float output values are divided by 
 *  and all float input values are multiplied by. The default value 
 *  is 1.0.
 *
 *  Slope is needed because: the RVAL for an analog input record is an 
 *  integer and there is no way to circumvent conversion from RVAL to VAL 
 *  so ESLO, ASLO and ROFF come into play; the RVAL from an analog output
 *  record is an integer and if one used VAL then the ESLO, ASLO and ROFF 
 *  would be circumvented.
 *
 */
STATIC long setSlope( drvAppPrivate *pAppPriv, 
		      void          *pValue, 
		      drvAsyncIO    *pIO, 
		      drvCmndArg    *pFuncParam)
{
 
  pAppPriv->slope = *(long *)pValue; 

  return S_drvAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 * setTimeout() - allows configuration of the maximum time allowed for a
 * response to a data prompt (default is 10 seconds). The originating record
 * can be a float or integer
 *
 */
STATIC long setTimeout( drvAppPrivate *pAppPriv, 
			void          *pValue, 
			drvAsyncIO    *pIO, 
			drvCmndArg    *pFuncParam)
{
  if ( *(long *)pValue <= 0 )
    return S_drvAscii_badParam;

  errPrintf( -1, __FILE__, __LINE__,
	     " : setting response timeout to %ld seconds\n", *(long *)pValue );

  pAppPriv->responseTMO = *(long *)pValue; 
  
  return S_drvAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 *  setWriteCmt() - sets the command terminator for output strings. 
 *
 *  This is a string output function so the orginating record must be
 *  a stringOutput record (no check is made).
 *
 */
STATIC long setWriteCmt( drvAppPrivate *pAppPriv, 
			 void          *pDataStr, 
			 drvAsyncIO    *pIO, 
			 drvCmndArg    *pFuncParam)
{

  if ( !(char *)pDataStr ||  ( strlen((char *)pDataStr) <= 0 ) ) 
    return S_drvAscii_badParam;

  strnccpy( pAppPriv->writeCmt, (char *)pDataStr, MAX_STRING_SIZE );

  pAppPriv->writeCmt[sizeof( pAppPriv->writeCmt ) - 1] = '\0';

  return S_drvAscii_Ok;
}

/*
 * ---------------------------------------------------------------------------
 *  setReadCmt() - sets the command terminator for input strings. 
 *
 *  This is a string output function so the orginating record must be
 *  a stringOutput record (no check is made).
 */
STATIC long setReadCmt( drvAppPrivate *pAppPriv, 
			void          *pDataStr, 
			drvAsyncIO    *pIO, 
			drvCmndArg    *pFuncParam)
{
 
  if ( strlen( (char *)pDataStr ) > sizeof( pAppPriv->readCmt ) - 1 )
    return S_drvAscii_badParam;

  strnccpy( pAppPriv->readCmt, (char *)pDataStr, MAX_STRING_SIZE );

  pAppPriv->readCmt[sizeof( pAppPriv->readCmt ) - 1] = '\0';

  return S_drvAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 *  setDebugFlag() - enables debug for a link.
 *                   should be from a longout record
 */
STATIC long setDebugFlag( drvAppPrivate *pAppPriv, 
			  void          *pValue, 
			  drvAsyncIO    *pIO, 
			  drvCmndArg    *pFuncParam)
{
 
  pAppPriv->debugFlag = *(long *)pValue; 

  return S_drvAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 *  drvAsciiLoadReq()
 */
STATIC void drvAsciiLoadReq( drvSerialRequest *pReq, const char *pMsg )
{
  unsigned char 	*pBuf = pReq->buf;
  
  while ( *pMsg ) {

    *pBuf++ = (unsigned char) *pMsg++;
  }

  *pBuf = *pMsg; /* add nill term */
  pReq->bufCount = pBuf - pReq->buf;
  assert ( pReq->bufCount < sizeof( pReq->buf ) );
}


/*
 * ---------------------------------------------------------------------------
 *  drvAsciiQueryStringReq() 
 *
 *  This function which prompts for and waits for a response. 
 *
 *  THIS IS NOT ASYNCHRONOUS PROCESSING. 
 *
 *  Currently this is only called from get...() functions which 
 *  prompt for initial values at startup.
 */
STATIC int drvAsciiQueryStringReq( drvCmndPrivate    *pCmndPriv,
				   const char        *pMsg,
				   drvSerialResponse *pRes)
{
  drvSerialRequest	req;

  if ( drvAsciiDebugLevel & 64 ) printf("drvAsciiQueryStringReq\n");

  /* setup the request buffer */
  drvAsciiLoadReq( &req, pMsg );

  /* send the request and wait for the response */
  return drvAsciiQuery( pCmndPriv, &req, pRes );
}


/*
 *  responseNotifier()
 *
 *  This function serves as a callback which simply notifies, the 
 *  initiator of a message transaction, that the response has
 *  been received. This is done via a message private semaphore
 *  upon which the initiator is supposedly waiting.
 *
 *  This function executes within the context of the serial write task.
 */
STATIC void responseNotifier( drvAsyncIO *pIO )
{

  if ( pIO->pIoDoneArg != NULL ) {

    epicsEventSignal( (epicsEventId)(pIO->pIoDoneArg) );
    
  }

  return;
}


/*
 * ---------------------------------------------------------------------------
 * drvAsciiQuery()
 *
 *  Attempts to send a message to a remote device, wait for and
 *  fetch a response, then return the response to the caller for 
 *  processing.
 *
 *  This function executes within the context of the calling task
 *  and effectively blocks that task until a response is received
 *  from the remote device, albeit there is a timeout.
 */
STATIC int drvAsciiQuery( drvCmndPrivate    *pCmndPriv,
			  drvSerialRequest  *pReq, 
			  drvSerialResponse *pRes)
{
  drvAppPrivate  *pAppPriv = pCmndPriv->pAppPriv;
  drvAsyncIO     *pIO;
  epicsEventId          responseSem;

  long		  status;

  if ( drvAsciiDebugLevel & 64 ) printf("drvAsciiQuery\n");

  /*
   *  Create a semaphore private to this message transaction. If this
   *  fails then an error is expressed and the message is aborted.
   *  All other errors require that an immediate exit not occur so
   *  that the semaphore can be deleted prior to exiting.
   */
  responseSem = epicsEventCreate( SEM_EMPTY);

  if ( responseSem == NULL ) {

    errPrintf( S_drvAscii_noMemory, __FILE__, __LINE__,
	       " : could not allocate semaphore. Message aborted!\n" );
    
    return S_drvAscii_noMemory;
  }

  /*
   *  Create and Fill in the callback information. In this case the 
   *  notifier will simply give the semaphore. Note that the notifier 
   *  executes within the context of the serial write task.
   *
   *  Note that this will be done only once per records for which an
   *  initial value can be optained during record init. Hence we do
   *  not attempt to reclaim the memory.
   */ 
  pCmndPriv->pRbvArg = (void *) calloc( 1, sizeof( drvAsyncIO ) );
  pIO = (drvAsyncIO *) (pCmndPriv->pRbvArg);

  pIO->pIoDoneArg = (void *)responseSem;
  pIO->pAppDrvPrivate = responseNotifier;

  /*
   *  Attempt to send the message.
   */
  status = drvAsciiSendCallBackWithResp( pIO, pReq, 1 );
  
  if ( status == OK ) { 
    /*
     *  Wait for the response to be available. The notification callback
     *  will give this semaphore. If the give does not occur in a timely
     *  manner then it will be set to null in the callback structure, so
     *  that the notifier will not croak, before the semaphore is deleted.
     */
    status = epicsEventWaitWithTimeout( responseSem,
                                        (double) pAppPriv->responseTMO );


    if (status == OK ) {
      /*
       *  Fetch the response.
       */      
      status = drvSerialNextResponse( pAppPriv->pLink->linkId, pRes );    
      
    }
    
    pIO->pIoDoneArg = NULL;

    epicsEventDestroy( responseSem );

    if ( status != S_drvSerial_OK ) {

      errPrintf( status, __FILE__, __LINE__,
		 " : [%s] response failed", 
		 pAppPriv->pLink->fileName );
      
    }
  }

  /*
   *  Give the semphore which will let the next message be transmitted.
   *  That is, let the next write-read cycle proceed. Note that this
   *  semaphore was taken in the put routine so as to block out other
   *  writers until such time as the response is fetched. Thus the serial
   *  write task is essentially blocked until the semaphore is given.
   */
  epicsEventSignal( pAppPriv->pLink->syncLock );
 
  return S_drvAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 * drvAsciiSend() 
 *
 *  This function simply queues a 'datagram' to the serial write task.
 */
STATIC int drvAsciiSend( drvAppPrivate *pAppPriv, drvSerialRequest *pReq )
{
  long			status;

  if ( drvAsciiDebugLevel & 64 ) printf("drvAsciiSend\n");

  /*
   *  Set up the function to be use by the serial write task
   *  to transmit the message.
   */ 
  pReq->pCB = putFrame;
  pReq->pAppPrivate = pAppPriv;
  pReq->bufCount = strlen( (char *)pReq->buf );

  /*
   *  Attempt to queue the message in the serial write task.
   */
  status = drvSerialSendRequest( pAppPriv->pLink->linkId, dspLowest, pReq );

  if ( status != S_drvSerial_OK ) {

    errPrintf( status, __FILE__, __LINE__,
	       " : [%s] send failed!\n",
	       pAppPriv->pLink->fileName );

    return status;
  }
   
  return S_drvAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 *  drvAsciiSendIgnoreResp() 
 *
 *  This function executes within the context of the caller.
 *
 *  A transmission function is setup which will wait for a
 *  reply from the remote device. However, that response is
 *  simply discarded. Hence, once this function queues the message
 *  to the serial write task, the caller can continue (it is not blocked).
 *  Blocking for the response will occur within the context of the
 *  serial write task.
 */
STATIC int drvAsciiSendIgnoreResp( drvAsyncIO *pIO, 
				   drvSerialRequest *pReq )
{
  drvCmndPrivate        *pCmnd = &pIO->id;
  drvAppPrivate	        *pAppPriv = pCmnd->pAppPriv; 
  
  long			status;

  if ( drvAsciiDebugLevel & 64 ) printf("drvAsciiSendIgnoreResp\n");

  /*
   *  Setup the function to be used by the drvSerial write task 
   */
  pReq->pCB = putFrameAndRemoveResp;
  pReq->pAppPrivate = pIO;
  pReq->bufCount = strlen( (char *)pReq->buf );

  /* 
   *  Attempt to queue the message in the serial write task.
   */
  status = drvSerialSendRequest (pAppPriv->pLink->linkId, dspLowest, pReq );

  if ( status != S_drvSerial_OK ) {

    errPrintf( status, __FILE__, __LINE__,
	       " : [%s] send failed!\n",
	       pAppPriv->pLink->fileName );

    return status;
  }
 
  return S_drvAscii_Ok;
}


/*
 * ---------------------------------------------------------------------------
 *  drvAsciiSendCallBackWithResp() 
 *  This function executes within the context of the caller.
 *
 *  A transmission function is setup which will wait for a
 *  reply from the remote device. When the response is
 *  received a callback function, which must have been setup
 *  outside of this function, will be called so as to process
 *  said response. 
 *
 *  Note that this provides for an asynchronous callback such
 *  that the caller need not be blocked. However, if the caller
 *  specifies noUnlock then it is expected that the caller is
 *  blocking (using a private wakeup mechanism) and will free
 *  the link (allow succeeding write-read cycles) by giving
 *  the syncLock semaphore which would have been taken
 *  within putFrameAndBlock. Such is the case for drvAsciiQuery().
 *
 *  If noUnlock is not specified then the syncLock semaphore
 *  will be taken prior to transmission of the command and given
 *  after the callback function processes the response. 
 *
 *  In either case the blocking for the response will occur within
 *  the context of the serial write task. However, in the noUnlock
 *  case, the caller will also be blocked.
 *
 */
STATIC int drvAsciiSendCallBackWithResp( drvAsyncIO *pIO, 
					 drvSerialRequest *pReq,
					 int noUnlock )
{
  drvCmndPrivate        *pCmnd = &pIO->id;
  drvAppPrivate	        *pAppPriv = pCmnd->pAppPriv; 
  long			status=OK;

  if ( drvAsciiDebugLevel & 64 ) printf("drvAsciiSendCallBackWithResp\n");

  /* 
   *  setup the send function to be used by the drvSerial write task 
   */
  if ( noUnlock )
    pReq->pCB = putFrameAndBlock;

  else
  {
    pReq->pCB = putFrameAndCallBack;
  }

  pReq->pAppPrivate = pIO;

  pReq->bufCount = strlen( (char *)pReq->buf );

  /* 
   *  Queue the message in the drvSerial write task. 
   */
  status = drvSerialSendRequest( pAppPriv->pLink->linkId, dspLowest, pReq );

  if ( status != S_drvSerial_OK ) {

    errPrintf( status, __FILE__, __LINE__,
	       " : [%s] send failed!\n",
	       pAppPriv->pLink->fileName );

    return status;
  }

  if ( drvAsciiDebugLevel & 64 ) printf("OK - done drvAsciiSendCallBackWithResp\n");
  return S_drvAscii_Ok;
}


/* 
 * ---------------------------------------------------------------------------
 *  purge() 
 */
STATIC int purge( drvAppPrivate *pAppPriv )
{
  int status=OK;

  drvSerialResponse	 response;

  status =  epicsEventWaitWithTimeout( pAppPriv->pLink->sendBlockSem, 0 );

  if ( status == OK ) {

    errPrintf( status, __FILE__, __LINE__,
	       " : [%s] lost sem - recovering\n", 
	       (int)pAppPriv->pLink->fileName );
  

    /*
     *  At this point the reponse queue should be empty.
     *  If it is not empty then write-read synchronization
     *  has been lost. Thus any response must be purged.
     */
    do {

      status = drvSerialNextResponse( pAppPriv->pLink->linkId, &response );
      
      if ( status == S_drvSerial_OK ) {
	
	if ( response.bufCount > 0 ) {

	  errPrintf( status, __FILE__, __LINE__,
		     " : [%s] lost msg sync - discarding response\n", 
		     (int)pAppPriv->pLink->fileName );
	}
      }
	
    } while ( status == S_drvSerial_OK ); 

    status = OK;
  }
    
  return OK;  
}
 

/* 
 * ---------------------------------------------------------------------------
 *  putFrameAndCallBack() 
 *  This runs within the context of the serial write task. It causes
 *  the write task to wait for a response via a semaphore given by
 *  the corresponding read function (getFrame) which runs within
 *  the context of the serial read task. The effect is that another
 *  write will not proceed until the read completes hence a 
 *  synchronized write-read cycle. The actual fetching and processing
 *  of the response is done within the callback routine which will
 *  run within the context of the serial write task, that is, the
 *  callback is invoked herein.
 *
 *  The upshot is that blocking for a response occurs within the 
 *  context of the serial write task, rather than within the context
 *  of task which initiated the message.
 */
STATIC int putFrameAndCallBack( FILE             *fp, 
				drvSerialRequest *pReq )
{
  drvAsyncIO	        *pIO = (drvAsyncIO *)pReq->pAppPrivate;
  drvCmndPrivate        *pCmnd = &pIO->id;
  drvAppPrivate	        *pAppPriv = pCmnd->pAppPriv;
  int			 status;
  int			 semStatus;

  if ( drvAsciiDebugLevel & 64 ) printf("putFrameAndCallBack - yes\n");

  status = purge( pAppPriv );

  if ( status != OK && status != S_drvAscii_Ok ) {

    errPrintf( status, __FILE__, __LINE__,
	       " : [%s] send failed!\n",
	       pAppPriv->pLink->fileName );

    return status;
  }

  /*
   *  Attempt to send a query.
   */
  pReq->pAppPrivate = pAppPriv;
  
  pCmnd->pAppPriv->recordCnt = 
    pCmnd->rbvFlag ? pCmnd->pCmndArg->rbvInfo.recordCnt:
    pCmnd->pCmndArg->cmndInfo.recordCnt;
  
  status = putFrame( fp, pReq );
    
  if ( status == OK ) {  
    /*
     *  and block for the response but don't wait forever
     */
    semStatus = epicsEventWaitWithTimeout( pAppPriv->pLink->sendBlockSem, 
					   (double)pAppPriv->responseTMO );

    if ( semStatus != OK ) {

      errPrintf( semStatus, __FILE__, __LINE__,
		 " : [%s] receive failed (%x)\n",
		 (int)pAppPriv->pLink->fileName, semStatus );

      if (pIO->pIoDoneCB) {
	(*(devIoDoneCallBack *)pIO->pIoDoneCB)( (void *)pIO->pIoDoneArg, 
						S_drvAscii_queryFail, 0 ); 
      }

      /* 
       *  Cannot return EOF or all outstanding requests will be  
       *  dropped on floor as drvSerial will close and reopen its 
       *  link. The act of terminating all outstanding requests will 
       *  cause scanning of all asynchronous completion records to be 
       *  blocked as their pact's will be true.
       */
      status = 0;
      
    } else {
      /*
       *  call the call back
       */
      (*pIO->pAppDrvPrivate)( pIO );
      
      status = 0;
    }

  } else {

    errPrintf( status, __FILE__, __LINE__,
	       " : [%s] send failed for <%s>\n", 
	       (int)pAppPriv->pLink->fileName, 
	       (int)(&(pReq->buf[0])) );
    
    status = 0;
  }

  return status;
}


/* 
 * ---------------------------------------------------------------------------
 *  putFrameAndBlock() 
 * 
 *  This runs within the context of the serial write task. The
 *  serial write task is essentially blocked until such time as
 *  the message initiating task gives the locking semaphore,
 *  syncLock, as discussed below.
 *
 *  This is similar to putFrameAndCallBack() except that the 
 *  write-read cycle remains locked (semaphore is not given). It 
 *  is the responsibilty of the task which initiated the message 
 *  to unlock the cycle by giving the syncLock semaphore.
 *
 *  The simplest means of accomplishing this is for the initiator
 *  to setup a callback to provide some form of response ready
 *  notification back to the initiator, which will then give the 
 *  semaphore. Within drvAsciiQuery() the handshake with the 
 *  notification function is done with a message private semaphore 
 *  and, in this case, the initiator is effectively blocking for the  
 *  response (ie. a synchronous write-read). Note that the initiator 
 *  need not block if a sufficient callback is setup. 
 *
 *  Only the initial value read functions (getReal and getInteger) 
 *  currently use drvAsciiQuery, thus the blocking occurs during 
 *  record init and not otherwise (ie. the epics scan tasks never  
 *  block because of this behaviour).
 *  
 */
STATIC int putFrameAndBlock( FILE *fp, drvSerialRequest *pReq )
{
  drvAsyncIO	        *pIO = (drvAsyncIO *)pReq->pAppPrivate;
  drvCmndPrivate        *pCmnd = &pIO->id;
  drvAppPrivate	        *pAppPriv = pCmnd->pAppPriv;
  int			 status;
  int			 semStatus;

  if ( drvAsciiDebugLevel & 64 ) printf("putFrameAndBlock\n");

  status = purge( pAppPriv );

  if ( (status != OK) && (status != S_drvAscii_Ok) ) {

    errPrintf( status, __FILE__, __LINE__,
	       " : [%s] send failed!\n",
	       pAppPriv->pLink->fileName );

    return status;
  }

  /*
   *  Attempt to send a query.
   */
  pReq->pAppPrivate = pAppPriv;

  pCmnd->pAppPriv->recordCnt = 
    pCmnd->rbvFlag ? pCmnd->pCmndArg->rbvInfo.recordCnt
    : pCmnd->pCmndArg->cmndInfo.recordCnt;
    
  status = putFrame( fp, pReq );
    
  if ( status == OK ) {  
    /*
     *  and block for the response.
     */
    semStatus = epicsEventWaitWithTimeout( pAppPriv->pLink->sendBlockSem, 
					   (double)pAppPriv->responseTMO );
      
    if ( semStatus != OK ) {
	
      errPrintf( semStatus, __FILE__, __LINE__, 
		 " : [%s] receive failed!\n", 
		 (int)pAppPriv->pLink->fileName );
	
      status = S_drvAscii_cmdTmo;
	
    } else {
      /*
       *  Call the callback which will fetch and process the response.
       */
      (*pIO->pAppDrvPrivate)( pIO );
      
       /*
       *  The write task (this is within its context) must wait until
       *  the originating task grabs the response. That task will 
       *  synchronize with this via a semaphore, so we block here.
       */
      semStatus = epicsEventWaitWithTimeout( pAppPriv->pLink->syncLock, 
					     (double)pAppPriv->responseTMO );
      
      if ( semStatus != OK ) {

	errPrintf( status, __FILE__, __LINE__,
		   " : [%s] semTake failed for Sync semaphore!\n",
		   (int)pAppPriv->pLink->fileName );
	
	/* 
	 *  We assume the next time this type of message is sent that
	 *  the originating task will correct the semaphore.
	 */
	status = S_drvAscii_semTmo;

      } else status = OK;
    }
  } else {
      
    errPrintf( status, __FILE__, __LINE__,
	       " : [%s] send failed\n", 
	       (int)pAppPriv->pLink->fileName );
    
    status = S_drvAscii_noComm;
  }
  
  return status;
}


/* 
 * ---------------------------------------------------------------------------
 *  putFrameAndRemoveResp()
 *
 *  This runs within the context of the serial write task. It causes
 *  the write task to wait for a response via a semaphore given by
 *  the corresponding read function (getFrame) which runs within
 *  the context of the serial read task. The effect is that another
 *  write will not proceed until the read completes hence a 
 *  synchronized write-read cycle. This function also fetches and 
 *  forgets/ignores the response. 
 *
 *  The upshot is that blocking and disposing of a response occurs 
 *  within the context of the serial write task, rather than within 
 *  the context of task -most likely an epics scan task- which 
 *  initiated the message.
 *
 */
STATIC int putFrameAndRemoveResp( FILE *fp, drvSerialRequest *pReq )
{
  drvAsyncIO	        *pIO = (drvAsyncIO *)pReq->pAppPrivate;
  drvCmndPrivate        *pCmnd = &pIO->id;
  drvAppPrivate	        *pAppPriv = pCmnd->pAppPriv;
  drvSerialResponse	 response;
  int			 status;
  int			 semStatus;

  if ( drvAsciiDebugLevel & 64 ) printf("putFrameAndRemoveResp\n");

  status = purge( pAppPriv );

  if ( status != OK && status != S_drvAscii_Ok ) {

    errPrintf( status, __FILE__, __LINE__,
	       " : [%s] send failed!\n",
	       pAppPriv->pLink->fileName );

    return status;
  }

  /*
   *  Attempt to send a query.
   */
  pReq->pAppPrivate = pAppPriv;

  pCmnd->pAppPriv->recordCnt = 
    pCmnd->rbvFlag ? pCmnd->pCmndArg->rbvInfo.recordCnt
    : pCmnd->pCmndArg->cmndInfo.recordCnt;
    
  status = putFrame( fp, pReq );
    
  if ( status == OK ) {  
    /*
     *  and block for its response
     */
    semStatus = epicsEventWaitWithTimeout( pAppPriv->pLink->sendBlockSem, 
					   (double)pAppPriv->responseTMO );

    if ( semStatus != OK ) {

      errPrintf( semStatus, __FILE__, __LINE__,
		 " : [%s] epicsEventWait failed for send semaphore\n", 
		 (int)pAppPriv->pLink->fileName );
	
      status = S_drvAscii_cmdTmo;

    } else {
      /*
       *  Discard the response.
       */
      status = drvSerialNextResponse( pAppPriv->pLink->linkId, &response );

      if ( status != S_drvSerial_OK ) {

	errPrintf( status, __FILE__, __LINE__,
		   " : [%s] receive failed!\n",
		   (int)pAppPriv->pLink->fileName );

	status = S_drvAscii_noComm;

      } else { 

	status = OK;
      }
    }

  } else {

    errPrintf( status, __FILE__, __LINE__,
	       " : [%s] send failed\n", 
	       (int)pAppPriv->pLink->fileName );
      
    status = S_drvAscii_noComm;      
  }
  
  return status;
}


/* 
 * ---------------------------------------------------------------------------
 *  putFrame()
 *
 *  This function executes within the context of the serial 
 *  write task.
 *
 *  This function outputs a message to a remote device one
 *  character at a time.
 *
 *  This function should not be called directly as it would bypass
 *  the queued messages, within the serial write task, as well as
 *  bypass all synchronizing semaphores (ie. messages and response
 *  could become interleaved).
 */
STATIC int putFrame( FILE *fp, drvSerialRequest *pReq )
{
  uint8_t       *pC;
  int		status = S_drvAscii_Ok;
  drvAppPrivate *pAppPriv = (drvAppPrivate *)(pReq->pAppPrivate);

  if ( pAppPriv->txFunc ) {
    
    status = pAppPriv->txFunc( fp,
			       pAppPriv->pLink->fileName,
			       pReq->bufCount, 
			       (char *)(pReq->buf) );

  } else {

    /*
     *  add the message body
     */
    for ( pC = pReq->buf; pC < &pReq->buf[pReq->bufCount]; pC++ ) {
      
      status = putc( *pC, fp );
      
      if ( status == EOF ) {
	
	errPrintf( status, __FILE__, __LINE__,
		   " : [%s] cannot output character!\n",
		   (int)__FILE__, (int)__LINE__,
		   (int)pAppPriv->pLink->fileName );
	
	return EOF;
      }
    }

    /* 
     *  force the data out on the serial link 
     */
    fflush( fp );

    status = OK;
  }

  if ( pAppPriv->debugFlag ) {
    /*
     *  Do not use epicsPrintf as deadlock over a semaphore 
     *  could occur.
     */
    printf( "drvAscii(%d) => ", pReq->bufCount );
    printString( pReq->bufCount, (char *)(pReq->buf) );
    printf( "\n" );
  }

  return status;
}


/* 
 * ---------------------------------------------------------------------------
 * getFrame()
 * 
 *  This function executes within the context of the serial 
 *  read task.
 *
 *  This function inputs a message from a remote device one
 *  character at a time.
 *
 *  This function provides a notification that a complete message
 *  has been received by giving a link private semaphore, sendBlockSem, 
 *  upon which it is assumed that a reader is blocked. Currently, in
 *  all cases within this driver, it is effectively the serial
 *  write task that would be blocked.
 *
 *  This function should not be called directly as it would bypass
 *  the queued messages, within the serial write task, as well as
 *  bypass all synchronizing semaphores (ie. messages and response
 *  could become interleaved).
 *  
 *  Note that a link can have only one command terminator. This means 
 *  that all remote devices attached to a single serial port must have 
 *  the same command terminator. This is because there is only one rx 
 *  framing routine associated with the link.
 */
STATIC int getFrame( FILE *fp, drvSerialResponse *pResp, void *pAppPriv )
{
  drvSioLinkPrivate  	*pLink = (drvSioLinkPrivate *) pAppPriv;
  drvAppPrivate         *devInfo = NULL;
  uint8_t         	*pC;
  int             	chr;
  int			status;
  int                   cmt = 1;
  int                   cmtIndex = 0;
  int                   firstGet = TRUE,
                        done = FALSE;

  /*
   * find the remote device info in order to access the command 
   * terminator. The first time through there is a timing issue 
   * so a delaying while loop is required.
   */
  devInfo = (drvAppPrivate *) ellFirst( &pLink->remoteDevList );

  while ( !devInfo ) {

    epicsThreadSleep( (double) 1.0 );
    devInfo = (drvAppPrivate *) ellFirst( &pLink->remoteDevList );
  }

  pResp->bufCount = 0;

  /*
   *  If a link-specific user-defined input framing routine exists
   *  then call it. Note that the routine must have been specified
   *  via drvAsciiSetRxFunc() before iocInit.
   */
  if ( devInfo->rxFunc ) {

    pResp->bufCount = devInfo->rxFunc( fp, 
				       devInfo->pLink->fileName,
				       NELEMENTS(pResp->buf), 
				       (char*)(pResp->buf) );

  } else {

    /* 
     * Parse out token until we see the command terminator
     */
    pC = pResp->buf;
    
    while ( pC < &pResp->buf[NELEMENTS(pResp->buf)] && cmt > 0 ) {
      
      chr = getc( fp );
      
      if ( chr == EOF ) 
      {
	return EOF;
      }
      
      if ( firstGet ) {
	
	cmt = devInfo->recordCnt;
	
	if ( (cmt <= 0) && (strlen( devInfo->readCmt ) > 0) ) cmt = 1;
	
	firstGet = FALSE;
      }
      
      /*  
       *  strip null characters as these cause succeeding 
       *  scanf calls to fail
       */
      if ( (char)chr == '\0' ) continue;
      
      /*
       *  If a command terminator exists then continue getting  
       *  characters until the entire terminator is received, 
       *  otherwise terminate on  the first white space char. 
       *  If the data is expected to span  multiple terminators 
       *  (ie. multiple record response) then wait  for the number 
       *  of lines given by 'recordCnt'.
       */
      if ( cmt > 0 ) {
	
	if ( (char)chr == devInfo->readCmt[cmtIndex] )
	{
	  cmtIndex++;
	}
	
	else
	{
	  cmtIndex = 0;
	}
	
	if ( devInfo->readCmt[cmtIndex] == '\0' ) {
	  
	  cmt--;
	  cmtIndex = 0;
	}
	
	if ( cmt == 0 ) done = TRUE;
	
      } else if ( isspace( chr ) ) { 
	
	break;  
      }  
     
      *(pC++) = (uint8_t) chr;
      *pC = '\0';
      pResp->bufCount++;
      
      if ( done )
	break;
    }
  
    /*
     *  message buffer over flow (ignore this frame)
     */
    if ( pC >= &pResp->buf[NELEMENTS(pResp->buf)] ) {
      errPrintf( S_drvAscii_dataErr, __FILE__, __LINE__,
		 " : [%s] buffer over flow!\n", 
		 (int)devInfo->pLink->fileName );
      
    }
   
    pResp->bufCount++; 
    
  }

  devInfo->recordCnt = 0;

  if (devInfo->debugFlag) {
    /*
     *  Do not use epicsPrintf as deadlock over a semaphore 
     *  could occur.
     */
    printf( "drvAscii(%d) <= ", pResp->bufCount);
    printString( (pResp->bufCount)-1, (char *)(pResp->buf) );
    printf( "\n");
  }

     
  /*
   *  wake up send task 
   *  (don't allow further commands until we get response from query)
   */
  epicsEventSignal( pLink->sendBlockSem );
  
  return pResp->bufCount;
}

/* 
 * ---------------------------------------------------------------------------
 *  drvAsciiSetTxFunc()
 * 
 *  This function is intended to be called prior to iocInit (from a startup 
 *  script?) to allow one to register a transmission protocol function, which
 *  would replace the default processing within putFrame().
 *
 */
int drvAsciiSetTxFunc( char *linkName, void *txFunc )
{
  linkInfo *funcInfo;

  /*
  status = semTake( globalMutex, WAIT_FOREVER );
  assert( status == OK );
  */
  drvAsciiInit();

  funcInfo = (linkInfo *) ellFirst( &protocolList );

  while ( funcInfo ) {

    if ( !strcmp( linkName, funcInfo->linkName ) ) break;

    funcInfo = (linkInfo *) ellNext( &funcInfo->node );
  }

  if ( !funcInfo ) { 

    funcInfo = (linkInfo *) calloc( 1, sizeof( linkInfo ) );

    assert( funcInfo );

    strncpy( funcInfo->linkName, linkName, sizeof( funcInfo->linkName) );

    ellAdd( &protocolList, &funcInfo->node );

  } 

  funcInfo->txFunc = (int (*)())txFunc;
  /*
  status = semGive( globalMutex );
  assert( status == OK );
  */
  return OK;
}

/* 
 * ---------------------------------------------------------------------------
 * drvAsciiSetRxFunc()
 * 
 *  This function is intended to be called prior to iocInit (from a startup 
 *  script?) to allow one to register a receive protocol function, which
 *  would replace the default processing within gettFrame().
 * *
 */
int drvAsciiSetRxFunc( char *linkName, void *rxFunc )
{
  linkInfo *funcInfo;

  /*
  status = semTake( globalMutex, WAIT_FOREVER );
  assert( status == OK );
  */
  drvAsciiInit();

  funcInfo = (linkInfo *) ellFirst( &protocolList );

  while ( funcInfo ) {

    if ( !strcmp( linkName, funcInfo->linkName ) ) break;

    funcInfo = (linkInfo *) ellNext( &funcInfo->node );
  }

  if ( !funcInfo ) { 

    funcInfo = (linkInfo *) calloc( 1, sizeof( linkInfo ) );

    assert( funcInfo );

    strncpy( funcInfo->linkName, linkName, sizeof( funcInfo->linkName) );

    ellAdd( &protocolList, &funcInfo->node );

  } 

  funcInfo->rxFunc = (int (*)())rxFunc;
  /*
  status = semGive( globalMutex );
  assert( status == OK );
  */
  return OK;
}

/* 
 * ---------------------------------------------------------------------------
 *  putFrameCsum()
 *
 *  This function is intended as an example of a simple user 
 *  implemented protocol. Such a function should not be added
 *  to this file but, instead, it should be compiled and downloaded
 *  separately. 
 *
 *  This function, as would a similar user created function, is 
 *  associated with the link via drvAsciiSetTxFunc() contained 
 *  herein. Said association must be performed prior to iocInit.
 *
 *  Note that this function is invoked from within putFrame(),
 *  hence, this function must perform the basic functionality
 *  of putFrame().
 *
 *  This function executes within the context of the serial 
 *  write task.
 *
 *  This function outputs a message to a remote device one
 *  character at a time then a 2-byte simple checksum is calculated
 *  and appended to the data stream (in ascii).
 *
 */
int putFrameCsum( FILE *fp, char *fName, int length, char *bfr )
{
  int		 status = S_drvAscii_Ok;
  int            idx;

  long           bcc = 0;

  unsigned int   csumVal;

  {
    /*
     *  add the message body
     */
    for ( idx = 0; idx < length; idx++ ) {
      
      status = putc( bfr[idx], fp );
      
      if ( status == EOF ) {
	
	errPrintf( status, __FILE__, __LINE__,
		   " : [%s] cannot output character!\n",
		   (int)__FILE__, (int)__LINE__,
		   fName );
	
	return EOF;
      }

      bcc += bfr[idx];
    }

    bcc = -bcc;
    bcc = bcc & ((1<<(8))-1);

    csumVal = (bcc >> 4) & 0x000f;

    if ( csumVal > 9 )
      putc( (char)(csumVal + 55), fp );
    else
      putc( (char)(csumVal + 48), fp );

    csumVal = (bcc & 0x000f);

    if ( csumVal > 9 )
      putc( (char)(csumVal + 55), fp );
    else
      putc( (char)(csumVal + 48 ), fp );
    /* 
     *  force the data out on the serial link 
     */
    fflush( fp );

    status = OK;
  }

  return status;
}

/* 
 * ---------------------------------------------------------------------------
 * getFrameCsum()
 * 
 *  This function is intended as an example of a simple user 
 *  implemented protocol. Such a function should not be added
 *  to this file but, instead, it should be compiled and downloaded
 *  separately. 
 *
 *  This function, as would a similar user created function, is 
 *  associated with the link via drvAsciiSetRxFunc() contained 
 *  herein. Said association must be performed prior to iocInit.
 *
 *  Note that this function is invoked from within getFrame(),
 *  hence, this function must perform the basic functionality
 *  of getFrame().
 *
 *  This function executes within the context of the serial 
 *  read task.
 *
 *  This function inputs a message from a remote device one
 *  character at a time and assumed that a 2-byte simple checksum
 *  in ascii is appended to the data.
 *
 */
int getFrameCsum( FILE *fp, char *fName, int maxLen, char *bfr )
{
  int             	chr;
  int                   cmtIdx  = 0;
  int                   csumIdx = 0;
  int                   idx     = 0;

  long                  csum    = 0;
  /* 
   * Parse out token until we see the command terminator
   */  
  while ( idx < maxLen ) {
    
    chr = getc( fp );
    
    if ( chr == EOF ) 
      return EOF;
       
    /*  
     *  strip null characters as these cause succeeding 
     *  scanf calls to fail
     */
    if ( (char)chr == '\0' ) continue;
    
    bfr[idx++] = chr;
    bfr[idx] = '\0';


    if ( cmtIdx < 2 ) {

      if ( (uint8_t)chr == 0xD ) 
	cmtIdx++;

      else if ( (cmtIdx == 1) && ((uint8_t)chr == 0xA) )
	cmtIdx++;
      else
	cmtIdx = 0;

    } else {

      if ( cmtIdx == 2 ) 
	if ( ++csumIdx == 2 ) break;
    }
  }
  
  if ( csumIdx != 2 ) {
    /*
     *  message buffer over flow (ignore this frame)
     */
    errPrintf( S_drvAscii_dataErr, __FILE__, __LINE__,
	       " : [%s] buffer over flow!\n", 
	       (int)fName );
    
    return idx;
  }
   
  /*
   *  Calculate and compare the checksum value.
   */  
  if ( bfr[idx-2] > 96 )
    csum = bfr[idx-2] - 87;
  else if ( bfr[idx-2] > 64 )
    csum = bfr[idx-2] - 55;
  else if ( bfr[idx-2] > 47 )
    csum = bfr[idx-2] - 48;
  else 
    csum =  bfr[idx-2];

  csum <<= 4;

  if ( bfr[idx-1] > 96 )
    csum |= (bfr[idx-1] - 87);
  else if ( bfr[idx-1] > 64 )
    csum |= (bfr[idx-1] - 55);
  else if ( bfr[idx-1] > 47 )
    csum |= (bfr[idx-1] - 48);
  else 
    csum |= bfr[idx-1];

  {
    long bcc= 0;
 
    for (csumIdx = 0; csumIdx < idx-2; csumIdx++) {
      bcc += bfr[csumIdx];
    }

    bcc = -bcc;
    bcc = bcc & ((1<<(8))-1);

    if ( bcc != csum ) {

      errPrintf( S_drvAscii_dataErr, __FILE__, __LINE__,
		 " : [%s] Checksum error <%s>!\n", 
		 fName, bfr );
      
      bfr[0]  = '\0';
      idx = 0;

    } else {

      bfr[idx - 2] = '\0';
      idx -= 2;
    }
  }
       
  return idx;
}


/*+*********************************************************************
  $Log: drvAscii.c,v $
  Revision 1.2  2003/02/08 01:10:54  ktsubota
  Temp changes made so things would not crash on me

  Revision 1.1  2003/01/14 00:27:41  ktsubota
  Initial insertion

  Revision 1.9  2002/09/03 20:53:12  ahoney
        1. Significant modifications, within drvAscii.c, in regards to
         the handling of synchronization semaphores so as to alleviate
         potential loss of read/write synchronization. Although the
         synchronization problem was infrequent it sometimes required a
         processor reboot in order to correct the problem. Hopefully,
         synchronization will now be auto-magically re-established.

        2. Analog records may now have 'REAL' specified in their parm fields,
         after the link specification and before the first prompt format
         field.

         If an analog input record has the REAL attribute then the value
         returned form the remote device is written into the record's VAL
         field and RVAL/ESLO conversions are bypassed. Note that also
         bypasses the 'slope' record behavior. Similarly, analog output
         records will have their VAL values output to the remote device
         rather than their RVAL values. In general this eliminates the
         conversions to/fro real and integer which caused loss of
         precision, as well as, reducing the considerable complexity in using
         drvAscii for analog records.

        3. The format-string delimiters no longer have to be '<' and '>'
         for all records. Now the first character follwing the link
         specification is assumed to be the field delimiter, with the pairs
         '<>', '()', '{}', and '[]' assumed, when the left hand delimiter
         is encountered. These are now valid (on a record-by-record basis):
                      @/tyco/0 <status?> <%s>
                      @/tyco/0 (status?) (%s)
                      @/tyco/0 !status?! !%s!
                      @/tyco/0  status?   %s
                      @/tyco/0 Xstatus?X X%sX

        4. User-specified framing routines can be specified.
         With this release one can create their own input and output
         framing routines and have them override the default getFrame()
         and putFrame() routines. This allows one to do more sophisticated
         packet framing (e.g. a simple checksum could be added/checked).

         To specify special framing routines one must download the library,
         they created, containing those routine then register the
         functions with drvAsciiSetTxFunc() and drvAsciiSetRxFunc(), all
         prior to iocInit. Note that a different set of framing routines
         can be registered for each serial link. Also note that there are
         special requirements imposed on the framing routines. An example
         exists within drvAscii.c

        5. More extensive control of debugging information via link-specific
         debug records and via a drvAscii global variable drvAsciiDebugLevel.

        6. Format specifications may now have embedded hex or octal bytes
         Said bytes must be of the form '\xnn' or '\ooo'. Those bytes are
         translated when the format specs are parsed during record init.
         For instance a prompt format such as <\x58\x59\x5a?> will result
         in the string 'XYZ?' being output to the remote device.

        7. All numeric escape codes, that exist in output or input data
         streams, will be automatically translated. For instance a
         stringIn record with this prompt and response format '<%s?><%s>'
         can be manipulated with "caput stringIn '\x58'" to result
         in a prompt of 'X?' being transmitted. This should simplify record
         manipulation for those apps which talk to multi-dropped devices
         whose addresses are leading non-printing ASCII bytes. The numeric
         escape codes need not result in a printable ASCII characters,
         that is, "caput stringIn '\x81'" is valid. However, Beware that
         drvAscii uses sprintf and sscanf so embedding a null byte will
         cause unexpected behavior.

  Revision 1.7  2000/05/12 20:20:29  ktsubota
  bufCount needs to account for null

  Revision 1.6  2000/05/12 02:03:32  ktsubota
  A.Honey: Increment bufCount in getFrame

  Revision 1.5  2000/05/06 02:05:52  ktsubota
  Incorporated changes by A.Honey

  Revision 1.1  1998/12/03 23:56:11  ktsubota
  Initial insertion

  Revision 1.24  1998/03/18 01:39:22  ahoney
  Updated the processing for stringIn records to allows for dynamic
  prompts. This is accomplished by using the VAL field for output when
  prompting and input when a response arrives.

  Revision 1.22  1998/03/02 21:36:00  ahoney
  Modified extractIntData() and extractRealData() so that spaces bewteen
  the numberic sign and the first digit are changed to 0. This is so
  sscanf functions correctly.

  The need was the idiosyncracy of the Sony Gauges on the secondary
  The gauges provide independent secondary position feedback.

  Revision 1.21  1997/05/13 02:27:51  ahoney
  Removed commented out epicsPrintf statements that were replace with
  logMsg calls.

 * Revision 1.18  1997/05/06  01:00:34  ahoney
 * Mod to eliminate errorPrintf from with the framing routines, as there is
 * the possibilty that a link down condition could cause a serial link task
 * to be deleted when the semaphorer within errorPrintf is taken. Note that
 * errorPrintf was replaced with logMsg().
 *
 * Revision 1.17  1997/03/07  01:36:20  ahoney
 * Added the ability to enable 'debug' on a link-by-link basis. The
 * param field keyword is <debug> and should be associated with a
 * longout record.
 *
 * Revision 1.16  1997/02/19  01:37:20  ahoney
 * Reenabled NULL stripping within getFrame()
 *
 * Revision 1.14  1997/02/13  18:25:14  ahoney
 * Modified getFrame() so that <null> characters are ignored.
 *
 * Revision 1.13  1997/02/10  23:23:48  ahoney
 * Corrected a bug in putFrameAndCallback() - the device level callback on
 * error is now conditional as it was invalid to do so if no async completion
 * routine existed, as was true for output records.
 *
 * Revision 1.12  1997/02/08  00:51:50  ahoney
 * Removed returning EOF on failure within getFrame as this would cause
 * drvSerial to discard all queued commands and delete and restart its
 * tx and rx tasks.
 *
 * This will need to be changed in the future so that a database record
 * can be used to specify the behavior, as in some instances the user may
 * want to old behavior to occur.
 *
 * Revision 1.11  1997/01/23  02:18:10  ahoney
 * Added drvAsciiIoctl() to provide a mechanism to set ioctl options on
 * an underlying serial device driver.
 *
 * Also altered (corrected) extractBinaryData so that skipping characters
 * is possible, although one must use '%*[...]' rather than '%*c' as the
 * latter is not correctly handled by Vxworks implementation of sscanf.
 *
 * Revision 1.10  1997/01/21  02:37:59  ahoney
 * Mods to better handle conversion from integer to strings. This is
 * still unacceptable as drvAscii cannot handle null bytes as they
 * appear to be end-of-string terminators.
 *
 * Revision 1.9  1997/01/20  20:51:01  ahoney
 * Extensive mods to accomodate:
 *   -added mbbi and mbbo direct records;
 *   -added waveform records (long stringin);
 *   -added conversion of binary streams '0' and '1', with or without
 *    delimiters
 *   -added conversion from string to numeric 'abcd'->0x61626364->1633837924
 *   -added support for muliple line input strings
 *
 * Revision 1.8  1996/12/18  23:27:33  ahoney
 * Mods to support ao,bi,bo,mbbi,mbbo,longin,longout,stringin, and stringout
 * records. These were necessitated for use with IFSM and chopper.
 *
 * Revision 1.7  1996/09/13  21:38:49  ahoney
 * Added the port name to all error message epicsPrints.
 *
 * Revision 1.5  1996/09/13  20:49:53  ahoney
 * Mods to accomodate the flat lamp device.
 *
 * Note this driver was completed only so far as was necessary for the
 * data acquisition systems. The drive will still need a few mods for
 * the IFSM.
 *
 * Revision 1.4  1996/09/12  01:09:40  ahoney
 * Corrected an epicsPrint statement in putFrameAndCallBack().
 *
 * Revision 1.3  1996/09/11  21:51:52  ahoney
 * Mods to incorporate longin and longout records as needed for the
 * dome flat lamps. Also modified the handling of '%nk' in data formats
 * so that 'n' characters can be 'killed' at the beginning of a data
 * stream - this allows stripping leading NULLs,...
 *
 * Revision 1.2  1996/07/13  00:38:05  ahoney
 * Removed some debugging printf's.
 *
 * Revision 1.1  1996/07/12  23:26:24  ahoney
 * drvAscii is a new directory for support for serial comms to remote
 * devices via ascii strings
 *

 *
***********************************************************************/
 

/*
static char rcsid[] = "$Id: drvAscii.h,v 1.1 2003/01/14 00:27:42 ktsubota Exp $";
*/
/*
 *
 *
 * Author: Allan Honey (for the KECK Observatory)
 * Date: 7-02-96
 *
 */

#define LOCAL STATIC
#define STATIC static  

#ifndef DRV_SERIAL_BUF_SIZE
#define DRV_SERIAL_BUF_SIZE 0x100
#endif

/*
 * created by drvCreateSioLink()
 */
typedef const void	*drvSioLinkId;

/* 
 * ========================
 * called by device support
 * ========================
 */

#define MAX_STRING_SIZE 40

#define RBF_UNDEFINED 0
#define RBF_STRING 1
#define RBF_INT    2
#define RBF_REAL   3
#define RBF_BINARY 4
#define RBF_CHAR   5

typedef struct drv_response_info {
  unsigned int        kill,
                      killAll;

  long                dataType;
                    
  long                killCnt,
                      dataCnt,
                      recordCnt,
                      arrayCnt,
                      preAmbleCnt;

  char                *delimiters,
                      *dataFormat,
                      *preAmble;

} drvResponseInfo;

typedef struct drv_cmnd_arg {
  long                funcArg;

  long                passThru;    /* don't use slope and put result 
				    * in val not rval. This is only
				    * relevant to analog records    */

  drvResponseInfo     cmndPromptInfo;
  drvResponseInfo     cmndInfo;

  drvResponseInfo     rbvPromptInfo;
  drvResponseInfo     rbvInfo;

  char 		      cmndPrompt[MAX_STRING_SIZE]; 
  char                cmndFormat[MAX_STRING_SIZE];
  char                rbvPrompt[MAX_STRING_SIZE];
  char                rbvFormat[MAX_STRING_SIZE];
} drvCmndArg;


/*
 * link private structure
 */
typedef struct {
  ELLNODE	  node;
  ELLLIST 	  remoteDevList;
  epicsMutexId	  mutex;
  epicsEventId	  syncLock;
  epicsEventId	  sendBlockSem;
  char		  fileName[0x100];
  int		  taskId;
  drvSerialLinkId linkId;
}drvSioLinkPrivate;

typedef struct drv_sio_link_state {
  unsigned	noComm:1;
} drvSioLinkState;

typedef struct drv_app_private {
  ELLNODE		node;
  drvSioLinkPrivate	*pLink;
  epicsMutexId		mutexSem;
  drvSioLinkState       state;
  unsigned		reset;
  long                  queryFailures;
  long                  dataErrors;
  long                  responseTMO;
  float                 slope;        /* used for converting float to int */
				      /* and int to float */

  long                  recordCnt;

  long                  debugFlag;

  char                  writeCmt[MAX_STRING_SIZE+1]; /* output string command terminator */
  char                  readCmt[MAX_STRING_SIZE+1];  /* input string command terminator */
  
  drvSerialRequest      req;
  drvSerialResponse     resp;

  int                  (*txFunc)();
  int                  (*rxFunc)();

  void                  *appPrivate;
}drvAppPrivate;

typedef void drvAsyncUpdateCallBack(void *pArg, long status, long value);
typedef void drvAsyncRealUpdateCallBack(void *pArg, long status, double value);

/*
 * parameter private structure
 */
typedef struct {
  ELLNODE		     node;
  drvAppPrivate	            *pAppPriv;
  struct cmd_table_entry    *pCmndTableEntry;

  drvCmndArg                *pCmndArg;

  drvAsyncUpdateCallBack    *pUpdateCB;	  /* called on parameter change */
  const void 		    *pUpdateArg;  /* parameter to above call back */	
  long                       respStrCnt;
  char                       respStr[DRV_SERIAL_BUF_SIZE];

  long                       rbvFlag;
  const void 		    *pRbvArg;	/* parameter to above call back */     
}drvCmndPrivate;

typedef void devIoDoneCallBack(void *pArg, long status, long value);
typedef void devRealIoDoneCallBack(void *pArg, long status, double value);

typedef struct drv_async_io{
  drvCmndPrivate       	 id;

  void			(*pIoDoneCB)();	/* May be devRealIoDoneCallBack or 
					 * devIoDoneCallBack */
  const void		*pIoDoneArg;
  /*
   * for app private use below this comment
   */
  void			(*pAppDrvPrivate)(struct drv_async_io *pIO);
}drvAsyncIO;
typedef void drvAsciiPrivateCallBack( drvAsyncIO *pio);

typedef long cmdFunc(drvAppPrivate *pAppPriv, void *pData, drvAsyncIO *pIO, drvCmndArg *pFuncParam);

typedef long strCmdFunc(drvAppPrivate *pAppPriv, char *pDataStr, drvAsyncIO *pIO, drvCmndArg *pFuncParam);

typedef long rbvFunc(drvCmndPrivate *pCmndPriv, void *pData, drvAsyncIO *pIO, drvCmndArg *pFuncParam);

typedef struct cmd_table_entry {
  const char	*pCmndName;
  cmdFunc	*pCmdFunc;
  rbvFunc       *pOutReadFunc;
} cmdTableEntry;

long drvAsciiCreateSioLink(
       const char     *pRecType,   /* record type */
       const char     *pFileName,  /* serial port device name */
       drvCmndArg     *pCmndArgs,

       drvCmndPrivate *pId,	  /* for future ref of this link */
       drvAsyncUpdateCallBack *pCB,/* called on parameter change */
       const void     *pArg);      /* parameter to above call back */

/*
 * enable scaning of the device
 * (after all links are created)
 */
long drvAsciiInitiateAll(void);

/*
 * generalized IO to/from a parameter
 * (called by device support)
 */
long drvAsciiIntIo( drvAsyncIO *pIO, long *pValue );
long drvAsciiRealIo( drvAsyncIO *pIO, double *pValue );
long drvAsciiStringIo( drvAsyncIO *pIO, char *pValue );

/*
 * read output parameter during init
 * (some parameters do  not persist in the device and 
 * so S_drvAscii_readOnlyParameter is returned to indicate that
 * we always wish to initialize the parameter from the
 * value stored in the database file
 */
long drvAsciiReadOutput( drvCmndPrivate *id,	/* link id */
			 long *pValue);		/* raw read/write data */

#define M_drvAsciiLib (1002<<16U)
#define drvAsciiError(CODE) (M_drvAsciiLib | (CODE)) 

#define S_drvAscii_Ok 0 /* success */
#define S_drvAscii_badParam drvAsciiError(1) /*Ascii driver: bad parameter*/
#define S_drvAscii_AsyncCompletion drvAsciiError(2) /*Ascii driver: async completion*/
#define S_drvAscii_writeOnlyParameter drvAsciiError(3) /*Ascii driver: write only parameter (cant be read)*/
#define S_drvAscii_noMemory drvAsciiError(4) /*Ascii driver: no memory*/
#define S_drvAscii_uknParam drvAsciiError(5) /*Ascii driver: no paramater by that name*/
#define S_drvAscii_noComm drvAsciiError(6) /*Ascii driver: no communication with device*/
#define S_drvAscii_toLate drvAsciiError(7) /*Ascii driver: func has no effect after initalization*/
#define S_drvAscii_queryFail drvAsciiError(8) /* Ascii driver: a controller query failed */
#define S_drvAscii_dataErr drvAsciiError(9) /* Ascii driver: response string invalid */
#define  S_drvAscii_linkErr drvAsciiError(10) /* Ascii driver: link structure invalid */
#define  S_drvAscii_cmdTmo drvAsciiError(11) /* Ascii driver: command timeout (overloaded?) */
#define  S_drvAscii_semTmo drvAsciiError(12) /* Ascii driver: semTake timeout */


/*===========================================================================
 *
 * PUBLIC DEFINITIONS
 *
 ============================================================================*/



/*+*********************************************************************
  $Log: drvAscii.h,v $
  Revision 1.1  2003/01/14 00:27:42  ktsubota
  Initial insertion

  Revision 1.7  2002/09/03 20:53:13  ahoney
        1. Significant modifications, within drvAscii.c, in regards to
         the handling of synchronization semaphores so as to alleviate
         potential loss of read/write synchronization. Although the
         synchronization problem was infrequent it sometimes required a
         processor reboot in order to correct the problem. Hopefully,
         synchronization will now be auto-magically re-established.

        2. Analog records may now have 'REAL' specified in their parm fields,
         after the link specification and before the first prompt format
         field.

         If an analog input record has the REAL attribute then the value
         returned form the remote device is written into the record's VAL
         field and RVAL/ESLO conversions are bypassed. Note that also
         bypasses the 'slope' record behavior. Similarly, analog output
         records will have their VAL values output to the remote device
         rather than their RVAL values. In general this eliminates the
         conversions to/fro real and integer which caused loss of
         precision, as well as, reducing the considerable complexity in using
         drvAscii for analog records.

        3. The format-string delimiters no longer have to be '<' and '>'
         for all records. Now the first character follwing the link
         specification is assumed to be the field delimiter, with the pairs
         '<>', '()', '{}', and '[]' assumed, when the left hand delimiter
         is encountered. These are now valid (on a record-by-record basis):
                      @/tyco/0 <status?> <%s>
                      @/tyco/0 (status?) (%s)
                      @/tyco/0 !status?! !%s!
                      @/tyco/0  status?   %s
                      @/tyco/0 Xstatus?X X%sX

        4. User-specified framing routines can be specified.
         With this release one can create their own input and output
         framing routines and have them override the default getFrame()
         and putFrame() routines. This allows one to do more sophisticated
         packet framing (e.g. a simple checksum could be added/checked).

         To specify special framing routines one must download the library,
         they created, containing those routine then register the
         functions with drvAsciiSetTxFunc() and drvAsciiSetRxFunc(), all
         prior to iocInit. Note that a different set of framing routines
         can be registered for each serial link. Also note that there are
         special requirements imposed on the framing routines. An example
         exists within drvAscii.c

        5. More extensive control of debugging information via link-specific
         debug records and via a drvAscii global variable drvAsciiDebugLevel.

        6. Format specifications may now have embedded hex or octal bytes
         Said bytes must be of the form '\xnn' or '\ooo'. Those bytes are
         translated when the format specs are parsed during record init.
         For instance a prompt format such as <\x58\x59\x5a?> will result
         in the string 'XYZ?' being output to the remote device.

        7. All numeric escape codes, that exist in output or input data
         streams, will be automatically translated. For instance a
         stringIn record with this prompt and response format '<%s?><%s>'
         can be manipulated with "caput stringIn '\x58'" to result
         in a prompt of 'X?' being transmitted. This should simplify record
         manipulation for those apps which talk to multi-dropped devices
         whose addresses are leading non-printing ASCII bytes. The numeric
         escape codes need not result in a printable ASCII characters,
         that is, "caput stringIn '\x81'" is valid. However, Beware that
         drvAscii uses sprintf and sscanf so embedding a null byte will
         cause unexpected behavior.

  Revision 1.6  2000/05/06 02:05:53  ktsubota
  Incorporated changes by A.Honey

  Revision 1.4  1999/07/22 01:21:09  kics
  corrected a typo in a comment.

 * Revision 1.3  1999/07/22  01:00:02  ahoney
 * Changed the drvAsciiPrivateCallBack typedef to not contain
 * a 'const' parameter.
 *
  Revision 1.1  1998/12/03 23:56:12  ktsubota
  Initial insertion

  Revision 1.8  1997/05/13 02:27:11  ahoney
  Changed pOutReadFunc within cmdTableEntry to be of type cmdFunc in
  order to eliminate compiler warnings.

 * Revision 1.7  1997/03/07  01:36:23  ahoney
 * Added the ability to enable 'debug' on a link-by-link basis. The
 * param field keyword is <debug> and should be associated with a
 * longout record.
 *
 * Revision 1.6  1997/01/23  02:19:50  ahoney
 * Added a #define for LOCAL
 *
 * Revision 1.5  1997/01/20  20:51:05  ahoney
 * Extensive mods to accomodate:
 *   -added mbbi and mbbo direct records;
 *   -added waveform records (long stringin);
 *   -added conversion of binary streams '0' and '1', with or without
 *    delimiters
 *   -added conversion from string to numeric 'abcd'->0x61626364->1633837924
 *   -added support for muliple line input strings
 *
 * Revision 1.4  1996/12/18  23:27:36  ahoney
 * Mods to support ao,bi,bo,mbbi,mbbo,longin,longout,stringin, and stringout
 * records. These were necessitated for use with IFSM and chopper.
 *
 * Revision 1.3  1996/09/13  20:49:54  ahoney
 * Mods to accomodate the flat lamp device.
 *
 * Note this driver was completed only so far as was necessary for the
 * data acquisition systems. The drive will still need a few mods for
 * the IFSM.
 *
 * Revision 1.2  1996/09/11  21:51:54  ahoney
 * Mods to incorporate longin and longout records as needed for the
 * dome flat lamps. Also modified the handling of '%nk' in data formats
 * so that 'n' characters can be 'killed' at the beginning of a data
 * stream - this allows stripping leading NULLs,...
 *
 * Revision 1.1  1996/07/12  23:26:26  ahoney
 * drvAscii is a new directory for support for serial comms to remote
 * devices via ascii strings
 *

 *
***********************************************************************/
 


--- End Message ---
--- Begin Message ---
Subject: drvAscii
From: ahoney@hapuna.keck.hawaii.edu
To: tech-talk@aps.anl.gov
Date: Thu, 20 Mar 2003 10:03:44 -1000
Title: drvAscii

drvAscii version 2.2 now exists in the KECK ftp site.

With release 2.0 and 2.1 there were a few string i/o record issues
which were not backwards compatible. These have been corrected in
release 2.2, along with a few other idiosyncracies.

AH


--- End Message ---

Navigate by Date:
Prev: RE: Channel Access disconnect/reconnect Jeff Hill
Next: RE: NI1014 GPIB controller Mark Rivers
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  <20032004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020 
Navigate by Thread:
Prev: drvAscii R3.14 version Porter, Rodney R.
Next: epicsMutexLockWithTimeout not supported in 3.14.1 Allison, Stephanie
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  <20032004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020