import java.io.*;
import java.util.*;
import javax.comm.*;

/**
 * This class implements the lowest-level communication
 * between the desktop and one or more Lego Mindstorms, using
 * the Java SerialComm API (available from the JDC).
 */
public class IRProtocol implements SerialPortEventListener {

	private SerialPort port;
	private OutputStream out;
	private InputStream in;
	private Vector listeners;
	private MessageBuffer mbuf;
	private boolean debugOn = false;
	
	/**
	 * Create a new IRProtocol instance. This will try to acquire the
	 * serial port "/dev/term/a", and configure it for 2400,8,O,1.
	 */
	public IRProtocol() {
	
		listeners = new Vector();
		mbuf = new MessageBuffer();
		
		CommPortIdentifier portID = null;
		
		try { portID = CommPortIdentifier.getPortIdentifier("/dev/term/a"); }
		catch (Exception e) { e.printStackTrace(); }
		
		if (portID.getPortType() == CommPortIdentifier.PORT_SERIAL) {
			try {
				port = (SerialPort) portID.open("RCXRemote", 2000);
				out = port.getOutputStream();
				in = port.getInputStream();
				port.setSerialPortParams(2400, SerialPort.DATABITS_8, 
				SerialPort.STOPBITS_1, SerialPort.PARITY_ODD);
				port.addEventListener(this);
				port.notifyOnDataAvailable(true);
			} catch(Exception e) { e.printStackTrace(); }
			
			System.out.println("using port " + portID.getName()
				+ ". Please make sure the IR transmitter is "
				+ "connected to that port!\n");
			System.out.println("parity is set to " 
				+ (port.getParity() == SerialPort.PARITY_ODD ? "ODD" : "NONE"));
			System.out.println("receive buffer size is " + port.getInputBufferSize());
			return ;
		}
                System.out.println("Could not find a serial port! Make sure "
			+ "you have a 'javax.comm.properties' file in the "
			+ "same directory as the 'comm.jar' file, and that "
			+ "it lists the correct serial port driver.");
		throw new RuntimeException();
	}

	/*
	 * Enable or disable debugging messages. Passing 'true'
	 * as the argument will enable debugging, 'false' will
	 * turn it off.
	 */
	public void enableDebugging(boolean d) {
		debugOn = d;
	}

	private void debug(String txt) {
		if (debugOn) {
			System.out.print(txt);
		}
	}
	
	private void debugln(String txt) {
		if (debugOn) {
			System.out.println(txt);
		}
	}

	private void debugln() {
		debugln("");
	}
	
	/** add a MessageListener for asynchronous receiving */
	public void addMessageListener(MessageListener l) {
		mbuf.addMessageListener(l);
	}
	
	/** remove a MessageListener for asynchronous receiving */
	public void removeMessageListener(MessageListener l) {
		mbuf.removeMessageListener(l);
	}
	
	/** SerialPortEventListener method */
	public void serialEvent(SerialPortEvent e) {
		int type = e.getEventType();
		
		if (type == SerialPortEvent.DATA_AVAILABLE) {
			int available = 0;
                        try {
				available = in.available();
				debugln("IRProtocol.serialEvent(): available = " + available);

				byte[] chunk = new byte[available];
				in.read(chunk);
				mbuf.append(chunk);
			} catch(Exception x) { x.printStackTrace(); }
		}
	}
	
	private String toHex(byte x) {
		return ((x & 0xff) < 16 ? "0" : "") + Integer.toHexString(x & 0xff).toUpperCase();
	}
	
	/** Send a raw message, i.e. do not use the Lego IR protocol */
	public void sendRaw(byte[] data) throws IOException {
		debug("IRProtcol.sendRaw(): ");
		for (int i = 0; i < data.length; i++) {
			debug(toHex(data[i]) + " ");
		}
		debugln();
		
		out.write(data);
		out.flush();
	}
	
	/** Receive a raw message, i.e. assume the Lego IR protocol is not used */
	public void receiveRaw(byte[] data) throws IOException {
		in.read(data);
		
		debug("IRProtcol.receiveRaw(): ");
		for (int i = 0; i < data.length; i++) {
			debug(toHex(data[i]) + " ");
		}
		debugln();
	}
	
	/**
	 * Send a message using the Lego IR protocol. All messages are
	 * prefaced with a three byte header (0x55, 0xff, 0x00). Each
	 * data byte is followed by its two's complement. The message
	 * is followed by a simple checksum, and the two's complement
	 * of that checksum. The checksum is computed by doing an 8-bit
	 * accumulation of all the data bytes. A little more formally,
	 * messages look like this:
	 * 

* 0x55 0xff 0x00 D1 ~D1 D2 ~D2 ... Dn ~Dn CS ~CS */ public void send(byte[] data) throws IOException { byte[] buf = new byte[2 * data.length + 5]; byte sum = 0; int i; buf[0] = (byte)0x55; buf[1] = (byte)0xff; buf[2] = (byte)0x00; for (i = 0; i < data.length; i++) { buf[2 * i + 3] = data[i]; buf[2 * i + 4] = (byte)~data[i]; sum += data[i]; } buf[buf.length - 2] = sum; buf[buf.length - 1] = (byte)~sum; debug("IRProtcol.send(): "); for (i = 0; i < buf.length; i++) { debug(toHex(buf[i]) + " "); } debugln(); out.write(buf); out.flush(); } /** * Receive a message assuming the Lego IR protocol is used. All * messages are prefaced with a three byte header (0x55, 0xff, 0x00). * Each data byte is followed by its two's complement. The message * is followed by a simple checksum, and the two's complement * of that checksum. The checksum is computed by doing an 8-bit * accumulation of all the data bytes. A little more formally, * messages look like this: *

* 0x55 0xff 0x00 D1 ~D1 D2 ~D2 ... Dn ~Dn CS ~CS */ public void receive(byte[] data) throws IOException { byte[] buf = new byte[2 * data.length + 5]; byte sum = 0; int i; in.read(buf); debug("IRProtcol.receive(): "); for (i = 0; i < buf.length; i++) { debug(toHex(buf[i]) + " "); } debugln(); if (!(buf[0] == (byte)0x55 && buf[1] == (byte)0xff && buf[2] == (byte)0x00)) { System.out.println("IRProtocol.receive(): invalid header: " + toHex(buf[0]) + " " + toHex(buf[1]) + " " + toHex(buf[2]) + " "); throw new RuntimeException(); } for (i = 0; i < data.length; i++) { data[i] = buf[2 * i + 3]; if (buf[2 * i + 4] != ~data[i]) { System.out.println("IRProtocol.receive(): wrong two's complement for data[" + i + "]"); throw new RuntimeException(); } sum += data[i]; } if (buf[buf.length - 2] != sum) { System.out.println("IRProtocol.receive(): wrong checksum"); throw new RuntimeException(); } if (buf[buf.length - 1] != ~sum) { System.out.println("IRProtocol.receive(): wrong two's complement for checksum"); throw new RuntimeException(); } } /** * Call this method when you're done using the IR protocol. * This will release the serial port. */ public void close() { port.close(); } }