001/** 002 * 003 */ 004package org.openimaj.hardware.serial; 005 006import gnu.io.CommPort; 007import gnu.io.CommPortIdentifier; 008import gnu.io.PortInUseException; 009import gnu.io.SerialPort; 010 011import java.io.BufferedOutputStream; 012import java.io.File; 013import java.io.FileOutputStream; 014import java.io.IOException; 015import java.io.InputStream; 016import java.io.OutputStream; 017import java.lang.reflect.Field; 018import java.net.URL; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.Enumeration; 024import java.util.HashSet; 025import java.util.List; 026 027import org.bridj.Platform; 028 029/** 030 * Serial device driver. Uses RXTX library underneath. The native parts of 031 * the RXTX library are published to the Maven repository as a JAR and 032 * are extracted and the java.library.path property is flushed and reset. 033 * 034 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 035 * 036 * @created 12 Jul 2011 037 */ 038public class SerialDevice implements SerialDataListener 039{ 040 /** The RXTX serial port we'll be reading from */ 041 private SerialPort serialPort = null; 042 043 /** Listeners for data events coming from the serial port */ 044 private List<SerialDataListener> listeners = new ArrayList<SerialDataListener>(); 045 046 /** The regular expression used to split incoming data for the listeners */ 047 private String regex = "\n"; 048 049 /** The input stream for the port */ 050 private InputStream inputStream = null; 051 052 /** The output stream for the port */ 053 private OutputStream outputStream = null; 054 055 /** The serial reader used to buffer and parse incoming data */ 056 private SerialReader serialReader = null; 057 058 /** The parser being used to parse incoming data */ 059 private RegExParser regexParser = null; 060 061 // Static block that loads the native libraries 062 static 063 { 064 // The package in the JAR where we've stored the native libs 065 String libprefix = "/org/openimaj/driver/serial/native/"; 066 067 // Try to find out if we've got a library to load 068 String libraryResource = null; 069 for( String s : getEmbeddedLibraryResource("rxtxSerial") ) 070 { 071 URL r = SerialDevice.class.getResource( libprefix + s ); 072 if( r != null ) 073 { 074 libraryResource = libprefix + s; 075 break; 076 } 077 } 078 079 // Stop if we can't find the library 080 if( libraryResource == null ) 081 throw new RuntimeException("Unable to load platform library"); 082 083 String directory = null; 084 try 085 { 086 // Extract the library from the JAR and find the directory into which it was put 087 // ... it's this we'll add as the library path 088 File file = extractEmbeddedLibraryResource(libraryResource); 089 directory = file.getAbsoluteFile().getParent(); 090 } 091 catch (IOException e) 092 { 093 throw new RuntimeException("Error unpacking platform library"); 094 } 095 096 // http://blog.cedarsoft.com/2010/11/setting-java-library-path-programmatically/ 097 try 098 { 099 // Flush the paths in the Classloader 100 System.setProperty( "java.library.path", directory ); 101 102 Field fieldSysPath = ClassLoader.class.getDeclaredField( "sys_paths" ); 103 fieldSysPath.setAccessible( true ); 104 fieldSysPath.set( null, null ); 105 } 106 catch( SecurityException e ) 107 { 108 e.printStackTrace(); 109 } 110 catch( IllegalArgumentException e ) 111 { 112 e.printStackTrace(); 113 } 114 catch( NoSuchFieldException e ) 115 { 116 e.printStackTrace(); 117 } 118 catch( IllegalAccessException e ) 119 { 120 e.printStackTrace(); 121 } 122 } 123 124 /** 125 * Returns a singleton list containing the library name for 126 * the given platform. 127 * 128 * @param name The name of the library. 129 * @return The library name in a singleton list. 130 */ 131 static Collection<String> getEmbeddedLibraryResource(String name) 132 { 133 if( Platform.isWindows() ) 134 return Collections.singletonList((Platform.is64Bits() ? "win64/" : "win32/") + name + ".dll"); 135 136 if( Platform.isMacOSX() ) 137 { 138 String generic = "darwin_universal/lib" + name + ".jnilib"; 139 140 if (Platform.isAmd64Arch()) 141 return Arrays.asList("darwin_x64/lib" + name + ".jnilib", generic); 142 else return Collections.singletonList(generic); 143 } 144 145 if( Platform.isLinux() ) 146 return Collections.singletonList( 147 (Platform.is64Bits() ? "linux_x64/lib" : "linux_x86/lib") 148 + name + ".so"); 149 150 throw new RuntimeException("Platform not supported ! " + 151 "(os.name='" + System.getProperty("os.name") + 152 "', os.arch='" + System.getProperty("os.arch") + "')"); 153 } 154 155 /** 156 * Copies the file from the resource into a temporary file. 157 * 158 * @param libraryResource The library resource to copy 159 * @return The file where the resource was copied to. 160 * @throws IOException If the file could not be read or written to. 161 */ 162 static File extractEmbeddedLibraryResource( String libraryResource ) 163 throws IOException 164 { 165 File libdir = File.createTempFile( new File(libraryResource).getName(), null ); 166 libdir.delete(); 167 libdir.mkdir(); 168 libdir.deleteOnExit(); 169 170 File libFile = new File(libdir, new File(libraryResource).getName()); 171 libFile.deleteOnExit(); 172 173 InputStream in = SerialDevice.class.getResourceAsStream(libraryResource); 174 OutputStream out = new BufferedOutputStream(new FileOutputStream(libFile)); 175 176 int len; 177 byte[] b = new byte[8196]; 178 while ((len = in.read(b)) > 0) 179 out.write(b, 0, len); 180 181 out.close(); 182 in.close(); 183 184 System.out.println( "Using library "+libraryResource ); 185 186 return libFile; 187 } 188 189 /** 190 * Constructor that takes the port name to connect to and the rate at which 191 * to connect. The data rate will be set to 19,200 with 8 data bits, 1 stop bit 192 * and no parity. 193 * 194 * @param portName The port name to connect to. 195 * @throws Exception 196 */ 197 public SerialDevice( String portName ) throws Exception 198 { 199 this( portName, 4800, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE ); 200 } 201 202 /** 203 * Complete constructor that takes all the information required to connect to 204 * a port. 205 * 206 * @param portName The port name to connect to. 207 * @param dataRate The data rate to read from the port. 208 * @param dataBits The number of data bits 209 * @param stopBits The number of stop bits 210 * @param parity The bit parity 211 * @throws Exception 212 */ 213 public SerialDevice( String portName, int dataRate, int dataBits, int stopBits, int parity ) 214 throws Exception 215 { 216 // Connect to the given port name with a 2 second timeout. 217 CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier( portName ); 218 CommPort commPort = portIdentifier.open( this.getClass().getName(), 2000 ); 219 220 // Set the serial port information 221 serialPort = (SerialPort) commPort; 222 serialPort.setSerialPortParams( dataRate, dataBits, stopBits, parity ); 223 224 // Get the input and output streams from and to the serial port. 225 outputStream = serialPort.getOutputStream(); 226 inputStream = serialPort.getInputStream(); 227 228 // Set up our data listener 229 regexParser = new RegExParser( regex ); 230 serialReader = new SerialReader( inputStream, regexParser ); 231 serialReader.addSerialDataListener( this ); 232 serialPort.addEventListener( serialReader ); 233 serialPort.notifyOnDataAvailable(true); 234 } 235 236 /** 237 * {@inheritDoc} 238 * @see java.lang.Object#finalize() 239 */ 240 @Override 241 protected void finalize() throws Throwable 242 { 243 serialPort.removeEventListener(); 244 serialPort.close(); 245 super.finalize(); 246 } 247 248 /** 249 * Close the connection to the serial port. 250 */ 251 public void close() 252 { 253 serialPort.removeEventListener(); 254 serialPort.close(); 255 } 256 257 /** 258 * Add the given {@link SerialDataListener} to the listener list. 259 * @param sdl The {@link SerialDataListener} to add. 260 */ 261 public void addSerialDataListener( SerialDataListener sdl ) 262 { 263 listeners.add( sdl ); 264 } 265 266 /** 267 * Remove the given {@link SerialDataListener} from the listener list 268 * @param sdl The {@link SerialDataListener} to remove. 269 */ 270 public void removeSerialDataListener( SerialDataListener sdl ) 271 { 272 listeners.remove( sdl ); 273 } 274 275 /** 276 * Fires the serial data event when data is received on the port. 277 * @param data The data that was received 278 */ 279 protected void fireSerialDataEvent( String data ) 280 { 281 for( SerialDataListener listener: listeners ) 282 listener.dataReceived( data ); 283 } 284 285 /** 286 * Returns the regular expression being used to split incoming strings. 287 * @return the regular expression being used to split incoming strings. 288 */ 289 public String getRegex() 290 { 291 return regex; 292 } 293 294 /** 295 * Set the regular expression to use to split incoming strings. 296 * @param regex the regex to split incoming strings 297 */ 298 public void setRegex( String regex ) 299 { 300 this.regex = regex; 301 this.regexParser.setRegEx( regex ); 302 } 303 304 /** 305 * Returns the input stream for this device. 306 * @return the input stream 307 */ 308 public InputStream getInputStream() 309 { 310 return inputStream; 311 } 312 313 /** 314 * Returns the output stream for this device. 315 * @return the output stream 316 */ 317 public OutputStream getOutputStream() 318 { 319 return outputStream; 320 } 321 322 /** 323 * {@inheritDoc} 324 * @see org.openimaj.hardware.serial.SerialDataListener#dataReceived(java.lang.String) 325 */ 326 @Override 327 public void dataReceived( String data ) 328 { 329 fireSerialDataEvent( data ); 330 } 331 332 /** 333 * @return A HashSet containing the CommPortIdentifier for all serial ports 334 * that are not currently being used. 335 */ 336 public static HashSet<CommPortIdentifier> getAvailableSerialPorts() 337 { 338 HashSet<CommPortIdentifier> h = new HashSet<CommPortIdentifier>(); 339 Enumeration<?> thePorts = CommPortIdentifier.getPortIdentifiers(); 340 while( thePorts.hasMoreElements() ) 341 { 342 CommPortIdentifier com = (CommPortIdentifier)thePorts.nextElement(); 343 switch (com.getPortType()) 344 { 345 case CommPortIdentifier.PORT_SERIAL: 346 try 347 { 348 CommPort thePort = com.open( "CommUtil", 50 ); 349 thePort.close(); 350 h.add( com ); 351 } 352 catch( PortInUseException e ) 353 { 354 System.out.println( "Port, " + com.getName() 355 + ", is in use." ); 356 } 357 catch( Exception e ) 358 { 359 System.err.println( "Failed to open port " + com.getName() ); 360 e.printStackTrace(); 361 } 362 } 363 } 364 return h; 365 } 366}