001/** 002 * Copyright 2017, Digi International Inc. 003 * 004 * This Source Code Form is subject to the terms of the Mozilla Public 005 * License, v. 2.0. If a copy of the MPL was not distributed with this 006 * file, you can obtain one at http://mozilla.org/MPL/2.0/. 007 * 008 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 009 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 010 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 011 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 012 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 013 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 014 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 015 */ 016package com.digi.xbee.api.connection.serial; 017 018import gnu.io.CommPortIdentifier; 019import gnu.io.CommPortOwnershipListener; 020import gnu.io.NoSuchPortException; 021import gnu.io.PortInUseException; 022import gnu.io.RXTXPort; 023import gnu.io.SerialPortEvent; 024import gnu.io.SerialPortEventListener; 025import gnu.io.UnsupportedCommOperationException; 026 027import java.io.IOException; 028import java.io.InputStream; 029import java.io.OutputStream; 030import java.util.ArrayList; 031import java.util.Enumeration; 032import java.util.TooManyListenersException; 033 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036 037import com.digi.xbee.api.exceptions.ConnectionException; 038import com.digi.xbee.api.exceptions.InterfaceInUseException; 039import com.digi.xbee.api.exceptions.InvalidConfigurationException; 040import com.digi.xbee.api.exceptions.InvalidInterfaceException; 041import com.digi.xbee.api.exceptions.PermissionDeniedException; 042 043/** 044 * This class represents a serial port using the RxTx library to communicate 045 * with it. 046 */ 047public class SerialPortRxTx extends AbstractSerialPort implements SerialPortEventListener, CommPortOwnershipListener { 048 049 // Variables. 050 private final Object lock = new Object(); 051 052 private RXTXPort serialPort; 053 054 private InputStream inputStream; 055 056 private OutputStream outputStream; 057 058 private Thread breakThread; 059 060 private boolean breakEnabled = false; 061 062 private CommPortIdentifier portIdentifier = null; 063 064 private Logger logger; 065 066 /** 067 * Class constructor. Instances a new {@code SerialPortRxTx} object using 068 * the given parameters. 069 * 070 * @param port Serial port name to use. 071 * @param parameters Serial port parameters. 072 * 073 * @throws NullPointerException if {@code port == null} or 074 * if {@code parameters == null}. 075 * 076 * @see #SerialPortRxTx(String, int) 077 * @see #SerialPortRxTx(String, int, int) 078 * @see #SerialPortRxTx(String, SerialPortParameters, int) 079 * @see SerialPortParameters 080 */ 081 public SerialPortRxTx(String port, SerialPortParameters parameters) { 082 this(port, parameters, DEFAULT_PORT_TIMEOUT); 083 } 084 085 /** 086 * Class constructor. Instances a new {@code SerialPortRxTx} object using 087 * the given parameters. 088 * 089 * @param port Serial port name to use. 090 * @param parameters Serial port parameters. 091 * @param receiveTimeout Serial port receive timeout in milliseconds. 092 * 093 * @throws IllegalArgumentException if {@code receiveTimeout < 0}. 094 * @throws NullPointerException if {@code port == null} or 095 * if {@code parameters == null}. 096 * 097 * @see #SerialPortRxTx(String, int) 098 * @see #SerialPortRxTx(String, int, int) 099 * @see #SerialPortRxTx(String, SerialPortParameters) 100 * @see SerialPortParameters 101 */ 102 public SerialPortRxTx(String port, SerialPortParameters parameters, int receiveTimeout) { 103 super(port, parameters, receiveTimeout); 104 this.logger = LoggerFactory.getLogger(SerialPortRxTx.class); 105 } 106 107 /** 108 * Class constructor. Instances a new {@code SerialPortRxTx} object using 109 * the given parameters. 110 * 111 * @param port Serial port name to use. 112 * @param baudRate Serial port baud rate, the rest of parameters will be 113 * set by default. 114 * 115 * @throws NullPointerException if {@code port == null}. 116 * 117 * @see #DEFAULT_DATA_BITS 118 * @see #DEFAULT_FLOW_CONTROL 119 * @see #DEFAULT_PARITY 120 * @see #DEFAULT_STOP_BITS 121 * @see #DEFAULT_PORT_TIMEOUT 122 * @see #SerialPortRxTx(String, int, int) 123 * @see #SerialPortRxTx(String, SerialPortParameters) 124 * @see #SerialPortRxTx(String, SerialPortParameters, int) 125 * @see SerialPortParameters 126 */ 127 public SerialPortRxTx(String port, int baudRate) { 128 this(port, baudRate, DEFAULT_PORT_TIMEOUT); 129 } 130 131 /** 132 * Class constructor. Instances a new {@code SerialPortRxTx} object using 133 * the given parameters. 134 * 135 * @param port Serial port name to use. 136 * @param baudRate Serial port baud rate, the rest of parameters will be 137 * set by default. 138 * @param receiveTimeout Serial port receive timeout in milliseconds. 139 * 140 * @throws IllegalArgumentException if {@code receiveTimeout < 0}. 141 * @throws NullPointerException if {@code port == null}. 142 * 143 * @see #DEFAULT_DATA_BITS 144 * @see #DEFAULT_FLOW_CONTROL 145 * @see #DEFAULT_PARITY 146 * @see #DEFAULT_STOP_BITS 147 * @see #SerialPortRxTx(String, int) 148 * @see #SerialPortRxTx(String, SerialPortParameters) 149 * @see #SerialPortRxTx(String, SerialPortParameters, int) 150 * @see SerialPortParameters 151 */ 152 public SerialPortRxTx(String port, int baudRate, int receiveTimeout) { 153 super(port, baudRate, receiveTimeout); 154 this.logger = LoggerFactory.getLogger(SerialPortRxTx.class); 155 } 156 157 /* 158 * (non-Javadoc) 159 * @see com.digi.xbee.api.connection.IConnectionInterface#open() 160 */ 161 @Override 162 public void open() throws InterfaceInUseException, InvalidInterfaceException, InvalidConfigurationException, PermissionDeniedException { 163 // Check that the given serial port exists. 164 try { 165 portIdentifier = CommPortIdentifier.getPortIdentifier(port); 166 } catch (NoSuchPortException e) { 167 throw new InvalidInterfaceException("No such port: " + port, e); 168 } 169 try { 170 // Get the serial port. 171 serialPort = (RXTXPort)portIdentifier.open(PORT_ALIAS + " " + port, receiveTimeout); 172 // Set port as connected. 173 connectionOpen = true; 174 // Configure the port. 175 if (parameters == null) 176 parameters = new SerialPortParameters(baudRate, DEFAULT_DATA_BITS, DEFAULT_STOP_BITS, DEFAULT_PARITY, DEFAULT_FLOW_CONTROL); 177 serialPort.setSerialPortParams(baudRate, parameters.dataBits, parameters.stopBits, parameters.parity); 178 serialPort.setFlowControlMode(parameters.flowControl); 179 180 serialPort.enableReceiveTimeout(receiveTimeout); 181 182 // Set the port ownership. 183 portIdentifier.addPortOwnershipListener(this); 184 185 // Initialize input and output streams before setting the listener. 186 inputStream = serialPort.getInputStream(); 187 outputStream = serialPort.getOutputStream(); 188 // Activate data received event. 189 serialPort.notifyOnDataAvailable(true); 190 // Register serial port event listener to be notified when data is available. 191 serialPort.addEventListener(this); 192 } catch (PortInUseException e) { 193 throw new InterfaceInUseException("Port " + port + " is already in use by other application(s)", e); 194 } catch (UnsupportedCommOperationException e) { 195 throw new InvalidConfigurationException(e.getMessage(), e); 196 } catch (TooManyListenersException e) { 197 throw new InvalidConfigurationException(e.getMessage(), e); 198 } 199 } 200 201 /* 202 * (non-Javadoc) 203 * @see com.digi.xbee.api.connection.IConnectionInterface#close() 204 */ 205 @Override 206 public void close() { 207 try { 208 if (inputStream != null) { 209 inputStream.close(); 210 inputStream = null; 211 } 212 if (outputStream != null) { 213 outputStream.close(); 214 outputStream = null; 215 } 216 } catch (IOException e) { 217 logger.error(e.getMessage(), e); 218 } 219 synchronized (lock) { 220 if (serialPort != null) { 221 try { 222 serialPort.notifyOnDataAvailable(false); 223 serialPort.removeEventListener(); 224 portIdentifier.removePortOwnershipListener(this); 225 serialPort.close(); 226 serialPort = null; 227 connectionOpen = false; 228 } catch (Exception e) { } 229 } 230 } 231 } 232 233 /* 234 * (non-Javadoc) 235 * @see gnu.io.SerialPortEventListener#serialEvent(gnu.io.SerialPortEvent) 236 */ 237 @Override 238 public void serialEvent(SerialPortEvent event) { 239 // Listen only to data available event. 240 switch (event.getEventType()) { 241 case SerialPortEvent.DATA_AVAILABLE: 242 // Check if serial device has been disconnected or not. 243 try { 244 getInputStream().available(); 245 } catch (Exception e) { 246 // Serial device has been disconnected. 247 close(); 248 synchronized (this) { 249 this.notify(); 250 } 251 break; 252 } 253 // Notify data is available by waking up the read thread. 254 try { 255 if (getInputStream().available() > 0) { 256 synchronized (this) { 257 this.notify(); 258 } 259 } 260 } catch (Exception e) { 261 logger.error(e.getMessage(), e); 262 } 263 break; 264 } 265 } 266 267 /* 268 * (non-Javadoc) 269 * @see java.lang.Object#toString() 270 */ 271 @Override 272 public String toString() { 273 return super.toString(); 274 } 275 276 /* 277 * (non-Javadoc) 278 * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#setBreak(boolean) 279 */ 280 @Override 281 public void setBreak(boolean enabled) { 282 breakEnabled = enabled; 283 if(breakEnabled){ 284 if (breakThread == null) { 285 breakThread = new Thread() { 286 public void run() { 287 while (breakEnabled && serialPort != null) 288 serialPort.sendBreak(100); 289 }; 290 }; 291 breakThread.start(); 292 } 293 } else { 294 if (breakThread != null) 295 breakThread.interrupt(); 296 breakThread = null; 297 serialPort.sendBreak(0); 298 } 299 } 300 301 /* 302 * (non-Javadoc) 303 * @see com.digi.xbee.api.connection.IConnectionInterface#getInputStream() 304 */ 305 @Override 306 public InputStream getInputStream() { 307 return inputStream; 308 } 309 310 /* 311 * (non-Javadoc) 312 * @see com.digi.xbee.api.connection.IConnectionInterface#getOutputStream() 313 */ 314 @Override 315 public OutputStream getOutputStream() { 316 return outputStream; 317 } 318 319 /* 320 * (non-Javadoc) 321 * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#setReadTimeout(int) 322 */ 323 @Override 324 public void setReadTimeout(int timeout) { 325 serialPort.disableReceiveTimeout(); 326 serialPort.enableReceiveTimeout(timeout); 327 } 328 329 /* 330 * (non-Javadoc) 331 * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#getReadTimeout() 332 */ 333 @Override 334 public int getReadTimeout() { 335 return serialPort.getReceiveTimeout(); 336 } 337 338 /* 339 * (non-Javadoc) 340 * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#setDTR(boolean) 341 */ 342 @Override 343 public void setDTR(boolean state) { 344 serialPort.setDTR(state); 345 } 346 347 /* 348 * (non-Javadoc) 349 * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#setRTS(boolean) 350 */ 351 @Override 352 public void setRTS(boolean state) { 353 serialPort.setRTS(state); 354 } 355 356 357 /* 358 * (non-Javadoc) 359 * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#setPortParameters(int, int, int, int, int) 360 */ 361 @Override 362 public void setPortParameters(int baudRate, int dataBits, int stopBits, 363 int parity, int flowControl) throws InvalidConfigurationException, ConnectionException { 364 parameters = new SerialPortParameters(baudRate, dataBits, stopBits, parity, flowControl); 365 366 if (serialPort != null) { 367 try { 368 serialPort.setSerialPortParams(baudRate, dataBits, stopBits, parity); 369 serialPort.setFlowControlMode(flowControl); 370 } catch (UnsupportedCommOperationException e) { 371 throw new InvalidConfigurationException(e.getMessage(), e); 372 } 373 } 374 } 375 376 /* 377 * (non-Javadoc) 378 * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#sendBreak(int) 379 */ 380 @Override 381 public void sendBreak(int duration) { 382 if (serialPort != null) 383 serialPort.sendBreak(duration); 384 } 385 386 /* 387 * (non-Javadoc) 388 * @see gnu.io.CommPortOwnershipListener#ownershipChange(int) 389 */ 390 @Override 391 public void ownershipChange(int nType) { 392 switch (nType) { 393 case CommPortOwnershipListener.PORT_OWNERSHIP_REQUESTED: 394 onSerialOwnershipRequested(null); 395 break; 396 } 397 } 398 399 /** 400 * Releases the port on any ownership request in the same application 401 * instance. 402 * 403 * @param data The port requester. 404 */ 405 private void onSerialOwnershipRequested(Object data) { 406 try { 407 throw new Exception(); 408 } catch (Exception e) { 409 StackTraceElement[] elems = e.getStackTrace(); 410 String requester = elems[elems.length - 4].getClassName(); 411 synchronized (this) { 412 this.notify(); 413 } 414 close(); 415 String myPackage = this.getClass().getPackage().getName(); 416 if (requester.startsWith(myPackage)) 417 requester = "another AT connection"; 418 logger.warn("Connection for port {} canceled due to ownership request from {}.", port, requester); 419 } 420 } 421 422 /** 423 * Retrieves the list of available serial ports in the system. 424 * 425 * @return List of available serial ports. 426 * 427 * @see #listSerialPortsInfo() 428 */ 429 public static String[] listSerialPorts() { 430 ArrayList<String> serialPorts = new ArrayList<String>(); 431 432 @SuppressWarnings("unchecked") 433 Enumeration<CommPortIdentifier> comPorts = CommPortIdentifier.getPortIdentifiers(); 434 if (comPorts == null) 435 return serialPorts.toArray(new String[serialPorts.size()]); 436 437 while (comPorts.hasMoreElements()) { 438 CommPortIdentifier identifier = (CommPortIdentifier)comPorts.nextElement(); 439 if (identifier == null) 440 continue; 441 String strName = identifier.getName(); 442 serialPorts.add(strName); 443 } 444 return serialPorts.toArray(new String[serialPorts.size()]); 445 } 446 447 /** 448 * Retrieves the list of available serial ports with their information. 449 * 450 * @return List of available serial ports with their information. 451 * 452 * @see #listSerialPorts() 453 * @see SerialPortInfo 454 */ 455 public static ArrayList<SerialPortInfo> listSerialPortsInfo() { 456 ArrayList<SerialPortInfo> ports = new ArrayList<SerialPortInfo>(); 457 458 @SuppressWarnings("unchecked") 459 Enumeration<CommPortIdentifier> comPorts = CommPortIdentifier.getPortIdentifiers(); 460 if (comPorts == null) 461 return ports; 462 463 while (comPorts.hasMoreElements()) { 464 CommPortIdentifier identifier = (CommPortIdentifier)comPorts.nextElement(); 465 if (identifier == null) 466 continue; 467 ports.add(new SerialPortInfo(identifier.getName())); 468 } 469 return ports; 470 } 471 472 /* 473 * (non-Javadoc) 474 * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#isCTS() 475 */ 476 @Override 477 public boolean isCTS() { 478 return serialPort.isCTS(); 479 } 480 481 /* 482 * (non-Javadoc) 483 * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#isDSR() 484 */ 485 @Override 486 public boolean isDSR() { 487 return serialPort.isDSR(); 488 } 489 490 /* 491 * (non-Javadoc) 492 * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#isCD() 493 */ 494 @Override 495 public boolean isCD() { 496 return serialPort.isCD(); 497 } 498}