 1Content-type: application/x-Unknown; charset=UTF8
package nmc.sidekick;
import javax.comm.SerialPort;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.Exception;
import nmc.common.TimestampedData;


class TimeoutException extends Exception {
    TimeoutException() {super();}
    TimeoutException(String s) {super(s);}
}

abstract public class StdInstrumentDriver implements IoResource, Instrument {

    String _name="StdInstrumentDriver";
    
    byte[] _enter;
    byte[] _prompt;
    byte[] _sampleRequest;
    byte[] _sampleRequestEcho;
    byte[] _cfgEol;
    
    
    int _timeout;
    int _maxTries;
    int _maxAttnBytes;
    int _maxSampleBytes;
    int _initMaxTries;
    int _maxSkipBytes;
    int _cfgBufSize;


    /** Device communications port */
    SerialPort _commPort;

    /** Input from device */
    public InputStream _input;    // pulled from _commPort
      
    /** Output to device */  
    public OutputStream _output;  // pulled from _commPort

    /** Unique sensor ID */
    long _sensorID = 9999; // does this ID apply to, say, MicroCat,
                           // or to MicroCat serial# 149236 ?

    public void setSerialPort(SerialPort aPort) throws IOException {
	_commPort = aPort;
        _input    = aPort.getInputStream();  // may throw exception
        _output   = aPort.getOutputStream(); // may throw exception
    }

    /** Read a data sample from device communications interface. */
    public TimestampedData poll() throws Exception {
        int tries = 0;
        TimestampedData retVal;

        while (tries < _maxTries) {
            tries++;
            try {
                byte[] theSample = getSample();
                TimestampedData retval = new TimestampedData(theSample);
                return retval;
            } catch (TimeoutException e) {
                DebugMessage.println("Driver: '"
                                   + _name
                                   + "' got timeout");
            } // other Exceptions fly past into the caller
        }
        
        throw new Exception("Retry limit exceeded (retries=" + tries + ")");
    }

    private byte[] getSample() throws Exception {

        write( _output, _enter );

        skipUntil( _input, _prompt );

        write( _output, _sampleRequest );

        skipUntil( _input, _sampleRequestEcho );

        byte[] sampleBuf = new byte[_maxSampleBytes];
        int bytesCaptured = readUntil( _input, sampleBuf, _prompt );


        // copy the sample into the exactly-right-sized byte array
        byte[] retval = new byte[bytesCaptured];
        for (int i = 0; i < bytesCaptured; i++) {
            retval[i] = sampleBuf[i];
        }
        return retval;
    }

    protected void write ( OutputStream ostream, byte[] src ) 
        throws IOException {

        write( ostream, src, src.length );

    }

    protected void write ( OutputStream ostream, byte[] src, int valid )
        throws IOException {

        ostream.write( src, 0, valid );

    }

    protected void skipUntil( InputStream istream, byte[] terminator )
        throws Exception {
        
        if (terminator == null)
            return;             // no terminator means don't look for this

        ///////////////////////////////////////////////////////////
        // If we find that we are skipping huge amounts of bytes,
        // we can rewrite this -- for now, it's useful to reuse the
        // code in readUntil(...)
        //
        byte[] buf = new byte[ _maxSkipBytes ];
        readUntil( istream, buf, terminator );
    }
        

    public TimestampedData configure(byte[] cfgData) throws Exception {
        DebugMessage.println("We are in the driver \""
                           + _name
                           + "\" with configuration data="
                           + new String(cfgData));

        byte[] tempBuf = new byte[ _cfgBufSize ];
        ByteArrayOutputStream cfgRecord = new ByteArrayOutputStream();
        ByteArrayInputStream cfgSource = new ByteArrayInputStream( cfgData );

        // In the code below, we assume that the instrument is
        // full duplex, and that it is using end-of-line characters
        // that are acceptable.  Instead of copying the configuration
        // requests from the source, we know that the instrument will
        // echo the source request with its notion of eol and then
        // send the result(s) (if any) with its notion of eol, and then
        // send the "prompt characters".  The code's notion of prompt
        // includes the end-of-line character(s) that immediately preceed
        // the prompt (so if the human sees "S>" the code's notion of
        // the prompt is something like "\r\nS>".  Dumb terminals and
        // DOS are going to want to see \r\n, while Unix variants will
        // want to use \n as end-of-line.

        int count;
        while ( cfgSource.available() > 0 ) {
            count = readUntil( cfgSource, tempBuf, _cfgEol );
            DebugMessage.println("forming string from tempBuf contents...");
            String s = new String( tempBuf, 0, count );
            DebugMessage.println("returned (1) from readUntil with tempBuf=" + s);
            write( _output, tempBuf, count);
            DebugMessage.println("wrote tempBuf to _output");
            write( _output, _enter );
            DebugMessage.println("wrote _enter to _output");
            count = readUntil( _input, tempBuf, _prompt );
            String s2 = new String(tempBuf, 0, count);
            DebugMessage.println("readUntil (2) returned with tempBuf=" + s2);
            write( cfgRecord, tempBuf, count + _prompt.length );
        }
        byte[] result = cfgRecord.toByteArray();
        cfgRecord.close();
        cfgSource.close();
        TimestampedData retval = new TimestampedData( result );
        return retval;
    }

    private void checkReadUntilArgs(InputStream input,
                                    byte[] output,
                                    byte[] terminator)
                                                  throws Exception {

        if (input == null) {
            throw new NullPointerException("readUntil called with "
                                           + "null InputStream");
        }

        if (output == null) {
            throw new NullPointerException("readUntil called with "
                                           + "null output buffer");
        }

        if (terminator == null) {
            throw new NullPointerException("readUntil called with "
                                           + "null terminator");
        }
    }
    
    public boolean foundTerminator(byte[] buffer, 
                                   int totalBytesRead,
                                   byte[] terminator) {

        int termSz = terminator.length;  // cache value for readability
        if (totalBytesRead < termSz)
            return false;       // we can't match if we have too few items


        int offset = totalBytesRead-termSz; // starting point is termSz back
        int matches = 0;
        for (int i = 0; i < termSz; i++) {
            if (buffer[i+offset] != terminator[i]) {
                DebugMessage.println("mismatch at i="
                                   + i
                                   + ", buffer="
                                   + buffer[i]
                                   + "terminator[i]="
                                   + terminator[i]);
                return false;
            }
        }
        return true;
    }

    public int readUntil(InputStream instream, 
                         byte[] outbuf,
                         byte[] terminator )
        throws Exception {

        DebugMessage.println("entring readUntil(terminator: "
                           + (new String(terminator))
                           + " )" );


        try {


            // throw exception if any bad args
            checkReadUntilArgs( instream, outbuf, terminator );

            // Total number of bytes read
            int bytesRead = 0;

            long t0 = System.currentTimeMillis();

            byte lastbyte = terminator[terminator.length - 1];

            // Read until we receive terminator
            while (true) {


                ///////////////////////////////////////////////////////////
                // Read one byte at a time - this might be VERY
                // inefficient, depending on whether input is buffered!
                // 

                if (instream.available() > 0) {

                    int c = instream.read();
                    DebugMessage.println("read got: " + (char)c + "=" + c);
                    outbuf[bytesRead++] = (byte)c;
    
                    
                    if (c == lastbyte) {
                        if (foundTerminator(outbuf, bytesRead, terminator)) {
                            DebugMessage.println("found terminator: "
                                               + terminator);
                            return bytesRead - terminator.length;
                        } else {
                            DebugMessage.println("matched last char but "
                                               + "mismatched terminator: "
                                               +terminator);
                        }

                    }

                    // if end of buffer, we missed last chance to
                    // recognize the terminator

                    if (bytesRead >= outbuf.length) {
                        throw new Exception("Output buf in instrument driver "
                                            + "exceeded (buffer max length is " 
                                            + outbuf.length
                                            + ")" );
                    }

                }

                long elapsed = System.currentTimeMillis() - t0;

                if (elapsed > _timeout) {
                    throw new TimeoutException("Timed out");
                }
            }
        }
        finally {
            DebugMessage.println("leaving readUntil");
        }
    }

    /** Read and discard all characters in serial input. */
    public void flushInput() throws Exception {
	_input.skip(_input.available());
    }

    public Object getResourceType(IoResourceType aType) {
	if (aType == IoResourceType.DPA_BOARD)
	    return null;
	else if (aType == IoResourceType.INSTRUMENT)
	    return this;
	else
	    return null; // probably should throw exception
    }

}


