PenStreamingConnection.java

00001 package edu.stanford.hci.r3.pen.streaming;
00002 
00003 import java.io.IOException;
00004 import java.io.InputStream;
00005 import java.util.ArrayList;
00006 import java.util.Enumeration;
00007 import java.util.List;
00008 import java.util.TooManyListenersException;
00009 
00010 import javax.comm.CommPortIdentifier;
00011 import javax.comm.PortInUseException;
00012 import javax.comm.SerialPort;
00013 import javax.comm.SerialPortEvent;
00014 import javax.comm.SerialPortEventListener;
00015 import javax.comm.UnsupportedCommOperationException;
00016 
00017 import edu.stanford.hci.r3.pen.PenSample;
00018 import edu.stanford.hci.r3.pen.streaming.listeners.PenListener;
00019 import edu.stanford.hci.r3.util.DebugUtils;
00020 import edu.stanford.hci.r3.util.communications.COMPort;
00021 
00040 public class PenStreamingConnection implements SerialPortEventListener {
00041 
00042         private static enum StreamingField {
00043                 FORCE, HEADER, X, X_FRACTION, Y, Y_FRACTION
00044         }
00045 
00046         private static final boolean DEBUG = false;
00047 
00048         public static final COMPort DEFAULT_PORT = COMPort.COM5;
00049 
00050         // PenUP Identifier
00051         private static final byte ID_PEN_UP = 0x01;
00052 
00053         // SimpleCoord Identifier
00054         private static final byte ID_SIMPLE_COORD = 0x00;
00055 
00059         private static PenStreamingConnection instance = null;
00060 
00061         // length of the PenUP Packet
00062         private static final byte LENGTH_PEN_UP = 0x00;
00063 
00064         // length of the Simple Coordinate Packet
00065         private static final byte LENGTH_SIMPLE_COORD = 0x0B;
00066 
00067         private static CommPortIdentifier portID;
00068 
00072         public static PenStreamingConnection getInstance() {
00073                 return getInstance(null);
00074         }
00075 
00080         @SuppressWarnings("unchecked")
00081         public static PenStreamingConnection getInstance(COMPort port) {
00082                 if (instance != null) {
00083                         return instance;
00084                 }
00085 
00086                 // set up a connection to the COM port read from it, and display to console
00087                 // boolean portFound = false;
00088                 if (port == null) {
00089                         port = DEFAULT_PORT;
00090                 }
00091 
00092                 StringBuilder msg = new StringBuilder();
00093                 msg.append("PenStreamingConnection: Looking for " + port + ". Found {");
00094 
00095                 final Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();
00096                 while (portList.hasMoreElements()) {
00097                         portID = (CommPortIdentifier) portList.nextElement();
00098                         if (portID.getPortType() == CommPortIdentifier.PORT_SERIAL) {
00099                                 String nameOfDiscoveredPort = portID.getName();
00100                                 if (nameOfDiscoveredPort.equals(port.toString())) {
00101                                         msg.append(" " + nameOfDiscoveredPort + " }");
00102                                         instance = new PenStreamingConnection();
00103                                         DebugUtils.println(msg.toString());
00104                                         System.out.flush();
00105                                         return instance;
00106                                 } else {
00107                                         msg.append(" " + nameOfDiscoveredPort);
00108                                 }
00109                         }
00110                 }
00111                 msg.append(" }\n");
00112                 msg.append("Port " + port + " not found.");
00113                 DebugUtils.println(msg.toString());
00114                 System.out.flush();
00115                 System.err.println("Is JavaCOMM not installed?");
00116                 System.err.println("Is your Bluetooth Dongle unplugged?");
00117                 System.err.println("Are connecting to the correct COM port, named ANOTO STREAMING?");
00118                 return null;
00119         }
00120 
00121         private byte bCurrent;
00122 
00123         private byte bLast;
00124 
00125         private byte bLastLast;
00126 
00127         private int force = 0;
00128 
00129         private InputStream inputStream;
00130 
00131         private PenSample lastSample;
00132 
00133         // list of listeners; add a PenListener to this list to listen to pen events
00134         private List<PenListener> listeners = new ArrayList<PenListener>();
00135 
00136         private StreamingField nextUp = StreamingField.HEADER;
00137 
00138         private int numBytesCoord;
00139 
00140         private boolean penIsUp = true;
00141 
00142         private SerialPort serialPort;
00143 
00144         private long timestamp;
00145 
00146         private int x = 0;
00147 
00148         private int xFraction = 0;
00149 
00150         private int y = 0;
00151 
00152         private int yFraction = 0;
00153 
00157         private PenStreamingConnection() {
00158                 try {
00159                         serialPort = (SerialPort) portID.open("StreamingPen", 2000);
00160                 } catch (PortInUseException e) {
00161                         e.printStackTrace();
00162                 }
00163 
00164                 try {
00165                         // TODO: Possible Null Pointer Exception Bug
00166                         inputStream = serialPort.getInputStream();
00167                 } catch (IOException e) {
00168                         e.printStackTrace();
00169                 }
00170 
00171                 try {
00172                         serialPort.addEventListener(this);
00173                 } catch (TooManyListenersException e) {
00174                         e.printStackTrace();
00175                 }
00176 
00177                 serialPort.notifyOnDataAvailable(true);
00178 
00179                 try {
00180                         serialPort.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
00181                                         SerialPort.PARITY_NONE);
00182                 } catch (UnsupportedCommOperationException e) {
00183                         e.printStackTrace();
00184                 }
00185         }
00186 
00193         public void addPenListener(PenListener pl) {
00194                 if (DEBUG) {
00195                         System.out.println("Adding a listener...");
00196                 }
00197                 listeners.add(pl);
00198         }
00199 
00203         public void exit() {
00204                 // is it sufficient to close the Input Stream?
00205                 try {
00206                         DebugUtils.println("Closing the connection to the Streaming Pen.");
00207                         inputStream.close();
00208                         serialPort.close();
00209                         instance = null;
00210                 } catch (IOException e) {
00211                         e.printStackTrace();
00212                 }
00213 
00214         }
00215 
00221         private void handleByte(byte streamedByte) {
00222 
00223                 // we got a new byte, so we push the others back
00224                 bLastLast = bLast;
00225                 bLast = bCurrent;
00226                 bCurrent = streamedByte;
00227 
00228                 // System.out.println(nextUp);
00229 
00230                 // looking for the header portion of the data
00231                 if (nextUp == StreamingField.HEADER) {
00232                         if (bCurrent == LENGTH_SIMPLE_COORD && bLast == 0x00 && bLastLast == ID_SIMPLE_COORD) {
00233                                 // System.out.print("SAMPLE: ");
00234                                 // we are now in the sample mode
00235                                 // we should read the next 0x0B bytes as coordinates and force
00236                                 nextUp = StreamingField.X;
00237                                 numBytesCoord = 0;
00238                         } else if (bCurrent == LENGTH_PEN_UP && bLast == 0x00 && bLastLast == ID_PEN_UP) {
00239                                 // System.out.println("PEN UP");
00240 
00241                                 double lastX = 0;
00242                                 double lastY = 0;
00243                                 if (lastSample != null) {
00244                                         lastX = lastSample.x;
00245                                         lastY = lastSample.y;
00246                                 }
00247 
00248                                 penIsUp = true;
00249                                 for (PenListener pl : listeners) {
00250                                         // on October 27, 2006, I changed behavior so that the pen up sample
00251                                         // now passes X & Y info
00252                                         // before, it passed x=0, y=0
00253                                         final PenSample penSample = new PenSample(lastX, lastY, 0, timestamp, true);
00254                                         // on June 12, 2006, I changed the behavior so that a .sample event is NOT
00255                                         // generated on pen up. It simply passes the pen up event with the timestamp
00256                                         // along...
00257                                         // thus, .sample is NEVER called with isPenUp() == true
00258                                         // pl.sample(penSample);
00259                                         pl.penUp(penSample);
00260                                 }
00261                         }
00262                 } else if (nextUp == StreamingField.X) { // 4 bytes long, X Coordinate
00263                         numBytesCoord++;
00264                         x = x << 8; // shift left by one byte
00265                         x = x | (bCurrent & 0xFF); // attach the byte
00266 
00267                         if (numBytesCoord == 4) {
00268                                 // after four loops, the x value is set to what we want
00269                                 nextUp = StreamingField.Y;
00270                                 numBytesCoord = 0;
00271                         }
00272                 } else if (nextUp == StreamingField.Y) { // 4 bytes long, Y Coordinate
00273                         numBytesCoord++;
00274                         y = y << 8;
00275                         y = y | (bCurrent & 0xFF);
00276 
00277                         if (numBytesCoord == 4) {
00278                                 // after four loops, the Y value is set to what we want
00279                                 nextUp = StreamingField.X_FRACTION;
00280                                 numBytesCoord = 0;
00281                         }
00282                 } else if (nextUp == StreamingField.X_FRACTION) {
00283                         // save the value
00284                         xFraction = (bCurrent >> 5) & 0x7; // last three bits
00285                         nextUp = StreamingField.Y_FRACTION;
00286                 } else if (nextUp == StreamingField.Y_FRACTION) {
00287                         // save the value
00288                         yFraction = (bCurrent >> 5) & 0x7; // last three bits
00289                         nextUp = StreamingField.FORCE;
00290                 } else if (nextUp == StreamingField.FORCE) {
00291                         // save the value
00292                         // mask it to make it unsigned
00293                         // force = 128 - (((int) bCurrent) & 0xFF);
00294                         force = 126 - (bCurrent & 0xFF) * 2;
00295                         if (force < 0) {
00296                                 force = 0;
00297                         }
00298 
00299                         // IMPLEMENTATION NOTE:
00300                         // type 'float' is NOT long enough to hold the orignial X/Y and their
00301                         // fraction part simutaneously, since the original X/Y is too big
00302                         // so we have to append the fraction part after conversion.
00303 
00304                         timestamp = System.currentTimeMillis();
00305 
00306                         // done with the whole streaming sample, so output it!
00307                         if (DEBUG) {
00308                                 System.out.println("(" + (x + xFraction * 0.125) + ", " + (y + yFraction * 0.125) + ")"
00309                                                 + " f: " + force + " t: " + timestamp);
00310                                 System.out.flush();
00311                         }
00312 
00313                         final PenSample penSample = new PenSample(x + (xFraction * 0.125), y + (yFraction * 0.125),
00314                                         force, timestamp, false);
00315 
00316                         if (penIsUp) {
00317                                 penIsUp = false;
00318                                 for (PenListener pl : listeners) {
00319                                         // Nov 12, 2006, I changed the behavior of .penDown to NOT send a .sample
00320                                         // event... because It seems rather redundant.
00321                                         // so now, neither penUp nor penDown sends an extra sample event
00322                                         // penDown contains a true sample
00323                                         // penUp just contains the values of the most recent sample
00324                                         // It is designed this way to facilitate calibration.
00325                                         pl.penDown(penSample);
00326                                 }
00327                         } else {
00328                                 // pen is already down, so we just generate .sample events...
00329 
00330                                 for (PenListener pl : listeners) {
00331                                         // June 12, 2006
00332                                         // (ronyeh) I changed the behavior of pen listeners a bit here...
00333                                         // now, we only pass ONE pen sample to all listeners
00334                                         // if there are multiple listeners, then they must make their own copies if
00335                                         // they're gonna keep them around
00336                                         // (OR beware that others may have your samples too)
00337                                         pl.sample(penSample);
00338                                 }
00339                         }
00340 
00341                         // keep it around so that we can pass this information to the pen up event!
00342                         lastSample = penSample;
00343 
00344                         // reset our values
00345                         x = 0;
00346                         y = 0;
00347                         xFraction = 0;
00348                         yFraction = 0;
00349                         force = 0;
00350 
00351                         // look for the header of the next sample
00352                         nextUp = StreamingField.HEADER;
00353                 }
00354         }
00355 
00361         public void serialEvent(SerialPortEvent event) {
00362                 switch (event.getEventType()) {
00363 
00364                 case SerialPortEvent.BI:
00365                         // fall through
00366                 case SerialPortEvent.OE:
00367                         // fall through
00368                 case SerialPortEvent.FE:
00369                         // fall through
00370                 case SerialPortEvent.PE:
00371                         // fall through
00372                 case SerialPortEvent.CD:
00373                         // fall through
00374                 case SerialPortEvent.CTS:
00375                         // fall through
00376                 case SerialPortEvent.DSR:
00377                         // fall through
00378                 case SerialPortEvent.RI:
00379                         // fall through
00380                 case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
00381                         break;
00382 
00383                 case SerialPortEvent.DATA_AVAILABLE: // there is data to process!
00384                         byte[] readBuffer = new byte[20];
00385                         try {
00386                                 while (inputStream.available() > 0) {
00387                                         int numBytes = inputStream.read(readBuffer);
00388 
00389                                         // process the byte of data
00390                                         for (int i = 0; i < numBytes; i++) {
00391                                                 handleByte(readBuffer[i]);
00392                                         }
00393                                 }
00394                         } catch (IOException e) {
00395                         }
00396 
00397                         break;
00398                 }
00399         }
00400 }

Generated on Sat Apr 14 18:21:37 2007 for R3 Paper Toolkit by  doxygen 1.4.7