import java.io.*;
import gnu.io.*;


/**
 * Interface to 5DT Glove. The glove must be connected to a serial port or serial to USB adapter.
 * @author Michael Lones 19/04/2010
 */
public class GloveRecorder {

	RXTXPort port = null;				// connection to the glove
	InputStream in;						// data received from the glove
	RecordThread recordThread;
	int currTime;
	
	/**
	 * Connects to glove and copies data to file specified as first argument over the duration
	 * specified by the second argument.
	 * @param args filename duration_in_seconds
	 */
	public static void main(String[] args) {
		GloveRecorder recorder = new GloveRecorder();
		if(recorder.connect())
			try {
				recorder.record(new File(args[0]), Integer.parseInt(args[1]));
			} catch (IOException e) {
				e.printStackTrace();
			}
	}
	
	/**
	 * Creates a connection to the glove. It is assumed that the glove is connected to
	 * COM5 under Windows or /dev/tty.usbserial on a Mac.
	 */
	public boolean connect() {
		// get port name
		String portName;
		String osname = System.getProperty("os.name");
    	if(osname.startsWith("Mac"))
    		portName = "/dev/tty.usbserial";
		else if(osname.startsWith("Windows"))
	        portName = "COM5";
	    else {
	    	System.err.println("Glove connection failed: no default port name for operating system");
	    	return false;
	    }
		
    	// connect to serial port
		try {
		  port = new RXTXPort(portName);
		}
		catch(PortInUseException piuE) {
			System.err.println("Glove connection failed: serial port "+portName+" in use or does not exist");
			return false;
		}
		
		// set up communication protocol
		try {
			port.setSerialPortParams(115200, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
		}
		catch (UnsupportedCommOperationException ucoE) {
			System.err.println(ucoE);
			System.err.println("Glove connection failed: could not configure serial port");
			System.err.println("Please ensure that serial port driver is up to date");
			return false;
		}
		
		// get input stream for serial port
		in = port.getInputStream();
		return true;
	}
	
	/**
	 * Thread for copying data from the serial port to a specified file
	 */
	class RecordThread extends Thread {
		PrintWriter writer;
		BufferedInputStream buffer;
		boolean stop;
		long ltime;
		
		public RecordThread(File destination, int time) throws IOException {
			writer = new PrintWriter(new FileWriter(destination));
			buffer = new BufferedInputStream(in);
			ltime = time*1000;
			stop = false;
		}
		
		public void run() {
			long startTime = System.currentTimeMillis();
			int data, value, s;
			boolean valid, fullByte;
			int[] packet = new int[19];
            try {
            	while(!stop && System.currentTimeMillis() < startTime+ltime) {
            		valid = false;
				    data = buffer.read();
				    if(data==0x3c) { // check for start character
				    	valid = true;
				    	packet[0] = buffer.read(); // read glove type
				    	packet[1] = buffer.read(); // read glove version
					    fullByte = true;
					    for(s=2; s<18; s++) { // read sensor values
					    	if(fullByte) { // do this when reading a full byte followed by a nibble
					    		value = buffer.read(); // read first 8 bits of sensor value
					    		value = value<<4; // shift along to make room for the other 4 bits
					    		data = buffer.read(); // read next 8 bits from stream
					    		value = value | data>>4; // copy 4 bits from this to complete the 12 bit sensor value
					    		packet[s] = value;
					    	}
					    	else { // and this when reading a nibble followed by a byte
					    		value = data<<8; // shift the 4 bits already loaded during reading of previous sensor value
					    		value = value & 0xFFF; // discard the 4 bits we don't want
					    		data = buffer.read(); // read the next 8 bits
					    		value = value | data; // and put them in front of the existing 4 bits
					    		packet[s] = value;
					    	}
					    	fullByte = !fullByte;
					    }
					    packet[18] = buffer.read(); // read checksum
					    if(buffer.read()==0x3e) // check for footer
					    	valid = valid && true;
				    }
				    
				    if(valid) { // save packet to disk
				    	for(int p: packet) {
					    	writer.write(""+p+'\t');
					    }
					    writer.println();
					    writer.flush();
				    }
			    }
			    writer.flush();
			    writer.close();
            }
            // note that PrintWriter does not throw exceptions
            // use writer.checkError() to check for write errors
            catch (IOException e) {
                e.printStackTrace();
            }  
		}
	}
	
	/**
	 * Copies data from the glove to the specified file for the specified period.
	 */
	public void record(File destination, int time) throws IOException {
		currTime = time;
		(recordThread = new RecordThread(destination, time)).start();
	}

	/**
	 * Spawns a recording thread and starts copying glove data to a file.
	 */
	public void startRecording(File destination) throws IOException {
		(recordThread = new RecordThread(destination, Integer.MAX_VALUE)).start();
	}

	/**
	 * Stops the recording thread.
	 */
	public void stopRecording() {
		if(recordThread!=null) {
			recordThread.stop = true;
			while(recordThread.isAlive()) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {}
			}
		}
	}
	
}