001/** 002 * Copyright (c) 2014 Digi International Inc., 003 * All rights not expressly granted are reserved. 004 * 005 * This Source Code Form is subject to the terms of the Mozilla Public 006 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 007 * You can obtain one at http://mozilla.org/MPL/2.0/. 008 * 009 * Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343 010 * ======================================================================= 011 */ 012package com.digi.xbee.api.connection.serial; 013 014import java.io.IOException; 015 016import org.slf4j.Logger; 017import org.slf4j.LoggerFactory; 018 019import com.digi.xbee.api.connection.IConnectionInterface; 020import com.digi.xbee.api.exceptions.ConnectionException; 021import com.digi.xbee.api.exceptions.InvalidConfigurationException; 022 023/** 024 * Abstract class that provides common functionality to work with serial ports. 025 */ 026public abstract class AbstractSerialPort implements IConnectionInterface { 027 028 // Constants. 029 /** 030 * Default receive timeout: {@value} seconds. 031 * 032 * <p>When the specified number of milliseconds have elapsed, read will 033 * return immediately.</p> 034 */ 035 public static final int DEFAULT_PORT_TIMEOUT = 10; 036 037 /** 038 * Default number of data bits: {@value}. 039 */ 040 public static final int DEFAULT_DATA_BITS = 8; 041 042 /** 043 * Default number of stop bits: {@value}. 044 */ 045 public static final int DEFAULT_STOP_BITS = 1; 046 047 /** 048 * Default parity: {@value} (None). 049 */ 050 public static final int DEFAULT_PARITY = 0; 051 052 /** 053 * Default flow control: {@value} (None). 054 */ 055 public static final int DEFAULT_FLOW_CONTROL = 0; 056 057 protected static final int FLOW_CONTROL_HW = 3; 058 059 protected static final String PORT_ALIAS = "Serial Port"; 060 061 // Variables. 062 protected String port; 063 064 protected int baudRate; 065 protected int receiveTimeout; 066 067 protected SerialPortParameters parameters; 068 069 protected boolean connectionOpen = false; 070 071 private Logger logger; 072 073 /** 074 * Class constructor. Instantiates a new {@code AbstractSerialPort} object 075 * with the given parameters. 076 * 077 * @param port COM port name to use. 078 * @param parameters Serial port connection parameters. 079 * 080 * @throws NullPointerException if {@code port == null} or 081 * if {@code parameters == null}. 082 * 083 * @see #AbstractSerialPort(String, int) 084 * @see #AbstractSerialPort(String, int, int) 085 * @see #AbstractSerialPort(String, SerialPortParameters, int) 086 * @see SerialPortParameters 087 */ 088 protected AbstractSerialPort(String port, SerialPortParameters parameters) { 089 this(port, parameters, DEFAULT_PORT_TIMEOUT); 090 } 091 092 /** 093 * Class constructor. Instantiates a new {@code AbstractSerialPort} object 094 * with the given parameters. 095 * 096 * @param port COM port name to use. 097 * @param baudRate Serial connection baud rate, the rest of parameters will 098 * be set by default. 099 * 100 * @throws NullPointerException if {@code port == null}. 101 * 102 * @see #DEFAULT_DATA_BITS 103 * @see #DEFAULT_FLOW_CONTROL 104 * @see #DEFAULT_PARITY 105 * @see #DEFAULT_STOP_BITS 106 * @see #DEFAULT_PORT_TIMEOUT 107 * @see #AbstractSerialPort(String, int, int) 108 * @see #AbstractSerialPort(String, SerialPortParameters) 109 * @see #AbstractSerialPort(String, SerialPortParameters, int) 110 */ 111 protected AbstractSerialPort(String port, int baudRate) { 112 this(port, new SerialPortParameters(baudRate, DEFAULT_DATA_BITS, DEFAULT_STOP_BITS, DEFAULT_PARITY, DEFAULT_FLOW_CONTROL), DEFAULT_PORT_TIMEOUT); 113 } 114 115 /** 116 * Class constructor. Instantiates a new {@code AbstractSerialPort} object 117 * with the given parameters. 118 * 119 * @param port COM port name to use. 120 * @param baudRate Serial port baud rate, the rest of parameters will be 121 * set by default. 122 * @param receiveTimeout Receive timeout in milliseconds. 123 * 124 * @throws IllegalArgumentException if {@code receiveTimeout < 0}. 125 * @throws NullPointerException if {@code port == null}. 126 * 127 * @see DEFAULT_DATA_BITS 128 * @see DEFAULT_FLOW_CONTROL 129 * @see DEFAULT_PARITY 130 * @see DEFAULT_STOP_BITS 131 * @see #AbstractSerialPort(String, int) 132 * @see #AbstractSerialPort(String, SerialPortParameters) 133 * @see #AbstractSerialPort(String, SerialPortParameters, int) 134 */ 135 protected AbstractSerialPort(String port, int baudRate, int receiveTimeout) { 136 this(port, new SerialPortParameters(baudRate, DEFAULT_DATA_BITS, DEFAULT_STOP_BITS, DEFAULT_PARITY, DEFAULT_FLOW_CONTROL), receiveTimeout); 137 } 138 139 /** 140 * Class constructor. Instantiates a new {@code AbstractSerialPort} object 141 * with the given parameters. 142 * 143 * @param port COM port name to use. 144 * @param parameters Serial port connection parameters. 145 * @param receiveTimeout Serial connection receive timeout in milliseconds. 146 * 147 * @throws IllegalArgumentException if {@code receiveTimeout < 0}. 148 * @throws NullPointerException if {@code port == null} or 149 * if {@code parameters == null}. 150 * 151 * @see #AbstractSerialPort(String, int) 152 * @see #AbstractSerialPort(String, int, int) 153 * @see #AbstractSerialPort(String, SerialPortParameters) 154 * @see SerialPortParameters 155 */ 156 protected AbstractSerialPort(String port, SerialPortParameters parameters, int receiveTimeout) { 157 if (port == null) 158 throw new NullPointerException("Serial port cannot be null"); 159 160 if (parameters == null) 161 throw new NullPointerException("SerialPortParameters cannot be null"); 162 163 if (receiveTimeout < 0) 164 throw new IllegalArgumentException("Receive timeout cannot be less than 0"); 165 166 this.port = port; 167 this.baudRate = parameters.baudrate; 168 this.receiveTimeout = receiveTimeout; 169 this.parameters = parameters; 170 this.logger = LoggerFactory.getLogger(AbstractSerialPort.class); 171 } 172 173 /* 174 * (non-Javadoc) 175 * @see com.digi.xbee.api.connection.IConnectionInterface#isOpen() 176 */ 177 @Override 178 public boolean isOpen() { 179 return connectionOpen; 180 } 181 182 /** 183 * Returns the name of the serial port. 184 * 185 * @return Port name. 186 */ 187 public String getPort() { 188 return port; 189 } 190 191 /** 192 * Sets the state of the DTR. 193 * 194 * @param state {@code true} to set the line status high, {@code false} to 195 * set it low. 196 * 197 * @see #isCD() 198 * @see #isCTS() 199 * @see #isDSR() 200 * @see #setRTS(boolean) 201 */ 202 public abstract void setDTR(boolean state); 203 204 /** 205 * Sets the state of the RTS line. 206 * 207 * @param state {@code true} to set the line status high, {@code false} to 208 * set it low. 209 * 210 * @see #isCD() 211 * @see #isCTS() 212 * @see #isDSR() 213 * @see #setDTR(boolean) 214 */ 215 public abstract void setRTS(boolean state); 216 217 /** 218 * Returns the state of the CTS line. 219 * 220 * @return {@code true} if the line is high, {@code false} otherwise. 221 * 222 * @see #isCD() 223 * @see #isDSR() 224 * @see #setDTR(boolean) 225 * @see #setRTS(boolean) 226 */ 227 public abstract boolean isCTS(); 228 229 /** 230 * Returns the state of the DSR line. 231 * 232 * @return {@code true} if the line is high, {@code false} otherwise. 233 * 234 * @see #isCD() 235 * @see #isCTS() 236 * @see #setDTR(boolean) 237 * @see #setRTS(boolean) 238 */ 239 public abstract boolean isDSR(); 240 241 /** 242 * Returns the state of the CD line. 243 * 244 * @return {@code true} if the line is high, {@code false} otherwise. 245 * 246 * @see #isCTS() 247 * @see #isDSR() 248 * @see #setDTR(boolean) 249 * @see #setRTS(boolean) 250 */ 251 public abstract boolean isCD(); 252 253 /** 254 * Returns whether or not the port's flow control is configured in 255 * hardware mode. 256 * 257 * @return {@code true} if the flow control is hardware, {@code false} 258 * otherwise. 259 * 260 * @see #getPortParameters() 261 * @see #setPortParameters(SerialPortParameters) 262 * @see #setPortParameters(int, int, int, int, int) 263 */ 264 public boolean isHardwareFlowControl() { 265 return parameters.flowControl == FLOW_CONTROL_HW; 266 } 267 268 /** 269 * Sets the new parameters of the serial port. 270 * 271 * @param baudRate The new value of baud rate. 272 * @param dataBits The new value of data bits. 273 * @param stopBits The new value of stop bits. 274 * @param parity The new value of parity. 275 * @param flowControl The new value of flow control. 276 * 277 * @throws ConnectionException if any error occurs when setting the serial 278 * port parameters 279 * @throws IllegalArgumentException if {@code baudRate < 0} or 280 * if {@code dataBits < 0} or 281 * if {@code stopBits < 0} or 282 * if {@code parity < 0} or 283 * if {@code flowControl < 0}. 284 * @throws InvalidConfigurationException if the configuration is invalid. 285 * 286 * @see #getPortParameters() 287 * @see #setPortParameters(SerialPortParameters) 288 */ 289 public void setPortParameters(int baudRate, int dataBits, int stopBits, int parity, int flowControl) throws InvalidConfigurationException, ConnectionException { 290 SerialPortParameters parameters = new SerialPortParameters(baudRate, dataBits, stopBits, parity, flowControl); 291 setPortParameters(parameters); 292 } 293 294 /** 295 * Sets the new parameters of the serial port as 296 * {@code SerialPortParameters}. 297 * 298 * @param parameters The new serial port parameters. 299 * 300 * @throws ConnectionException if any error occurs when setting the serial 301 * port parameters. 302 * @throws InvalidConfigurationException if the configuration is invalid. 303 * @throws NullPointerException if {@code parameters == null}. 304 * 305 * @see #getPortParameters() 306 * @see #setPortParameters(int, int, int, int, int) 307 * @see SerialPortParameters 308 */ 309 public void setPortParameters(SerialPortParameters parameters) throws InvalidConfigurationException, ConnectionException { 310 if (parameters == null) 311 throw new NullPointerException("Serial port parameters cannot be null."); 312 313 baudRate = parameters.baudrate; 314 this.parameters = parameters; 315 if (isOpen()) { 316 close(); 317 open(); 318 } 319 } 320 321 /** 322 * Enables or disables the break line. 323 * 324 * @param enabled {@code true} to enable the Break line, {@code false} to 325 * disable it. 326 * 327 * @see #sendBreak(int) 328 */ 329 public abstract void setBreak(boolean enabled); 330 331 /** 332 * Sends a break signal to the serial port with the given duration 333 * (in milliseconds). 334 * 335 * @param duration Duration of the break signal in milliseconds. 336 * 337 * @see #setBreak(boolean) 338 */ 339 public abstract void sendBreak(int duration); 340 341 /** 342 * Sets the read timeout of the serial port (in milliseconds). 343 * 344 * @param timeout The new read timeout of the serial port in milliseconds. 345 * 346 * @see #getReadTimeout() 347 */ 348 public abstract void setReadTimeout(int timeout); 349 350 /** 351 * Returns the read timeout of the serial port (in milliseconds). 352 * 353 * @return The read timeout of the serial port in milliseconds. 354 * 355 * @see #setReadTimeout(int) 356 */ 357 public abstract int getReadTimeout(); 358 359 /** 360 * Purges the serial port removing all the data from the input stream. 361 * 362 * @see #flush() 363 */ 364 public void purge() { 365 if (getInputStream() != null) { 366 try { 367 byte[] availableBytes = new byte[getInputStream().available()]; 368 if (getInputStream().available() > 0) 369 getInputStream().read(availableBytes, 0, getInputStream().available()); 370 } catch (IOException e) { 371 logger.error(e.getMessage(), e); 372 } 373 } 374 } 375 376 /** 377 * Flushes the available data of the output stream. 378 * 379 * @see #purge() 380 */ 381 public void flush() { 382 if (getOutputStream() != null) { 383 try { 384 getOutputStream().flush(); 385 } catch (IOException e) { 386 logger.error(e.getMessage(), e); 387 } 388 } 389 } 390 391 /* 392 * (non-Javadoc) 393 * @see com.digi.xbee.api.connection.IConnectionInterface#writeData(byte[]) 394 */ 395 @Override 396 public void writeData(byte[] data) throws IOException { 397 if (data == null) 398 throw new NullPointerException("Data to be sent cannot be null."); 399 400 if (getOutputStream() != null) { 401 // Writing data in ports without any device connected and configured with 402 // hardware flow-control causes the majority of serial libraries to hang. 403 404 // Before writing any data, check if the port is configured with hardware 405 // flow-control and, if so, try to write the data up to 3 times verifying 406 // that the CTS line is high (there is a device connected to the other side 407 // ready to receive data). 408 if (isHardwareFlowControl()) { 409 int tries = 0; 410 while (tries < 3 && !isCTS()) { 411 try { 412 Thread.sleep(100); 413 } catch (InterruptedException e) { } 414 tries += 1; 415 } 416 if (isCTS()) { 417 getOutputStream().write(data); 418 getOutputStream().flush(); 419 } 420 } else { 421 getOutputStream().write(data); 422 getOutputStream().flush(); 423 } 424 } 425 } 426 427 /* 428 * (non-Javadoc) 429 * @see com.digi.xbee.api.connection.IConnectionInterface#writeData(byte[], int, int) 430 */ 431 @Override 432 public void writeData(byte[] data, int offset, int length) throws IOException { 433 if (data == null) 434 throw new NullPointerException("Data to be sent cannot be null."); 435 if (offset < 0) 436 throw new IllegalArgumentException("Offset cannot be less than 0."); 437 if (length < 1) 438 throw new IllegalArgumentException("Length cannot be less than 0."); 439 if (offset >= data.length) 440 throw new IllegalArgumentException("Offset must be less than the data length."); 441 if (offset + length > data.length) 442 throw new IllegalArgumentException("Offset + length cannot be great than the data length."); 443 444 if (getOutputStream() != null) { 445 // Writing data in ports without any device connected and configured with 446 // hardware flow-control causes the majority of serial libraries to hang. 447 448 // Before writing any data, check if the port is configured with hardware 449 // flow-control and, if so, try to write the data up to 3 times verifying 450 // that the CTS line is high (there is a device connected to the other side 451 // ready to receive data). 452 if (isHardwareFlowControl()) { 453 int tries = 0; 454 while (tries < 3 && !isCTS()) { 455 try { 456 Thread.sleep(100); 457 } catch (InterruptedException e) { } 458 tries += 1; 459 } 460 if (isCTS()) { 461 getOutputStream().write(data, offset, length); 462 getOutputStream().flush(); 463 } 464 } else { 465 getOutputStream().write(data, offset, length); 466 getOutputStream().flush(); 467 } 468 } 469 } 470 471 /* 472 * (non-Javadoc) 473 * @see com.digi.xbee.api.connection.IConnectionInterface#readData(byte[]) 474 */ 475 @Override 476 public int readData(byte[] data) throws IOException { 477 if (data == null) 478 throw new NullPointerException("Buffer cannot be null."); 479 480 int readBytes = 0; 481 if (getInputStream() != null) 482 readBytes = getInputStream().read(data); 483 return readBytes; 484 } 485 486 /* 487 * (non-Javadoc) 488 * @see com.digi.xbee.api.connection.IConnectionInterface#readData(byte[], int, int) 489 */ 490 @Override 491 public int readData(byte[] data, int offset, int length) throws IOException { 492 if (data == null) 493 throw new NullPointerException("Buffer cannot be null."); 494 if (offset < 0) 495 throw new IllegalArgumentException("Offset cannot be less than 0."); 496 if (length < 1) 497 throw new IllegalArgumentException("Length cannot be less than 0."); 498 if (offset >= data.length) 499 throw new IllegalArgumentException("Offset must be less than the buffer length."); 500 if (offset + length > data.length) 501 throw new IllegalArgumentException("Offset + length cannot be great than the buffer length."); 502 503 int readBytes = 0; 504 if (getInputStream() != null) 505 readBytes = getInputStream().read(data, offset, length); 506 return readBytes; 507 } 508 509 /** 510 * Returns the XBee serial port parameters. 511 * 512 * @return The XBee serial port parameters. 513 * 514 * @see #setPortParameters(SerialPortParameters) 515 * @see #setPortParameters(int, int, int, int, int) 516 * @see SerialPortParameters 517 */ 518 public SerialPortParameters getPortParameters() { 519 if (parameters != null) 520 return parameters; 521 return new SerialPortParameters(baudRate, DEFAULT_DATA_BITS, 522 DEFAULT_STOP_BITS, DEFAULT_PARITY, DEFAULT_FLOW_CONTROL); 523 } 524 525 /** 526 * Returns the serial port receive timeout (in milliseconds). 527 * 528 * @return The serial port receive timeout in milliseconds. 529 * 530 * @see #getReadTimeout() 531 * @see #setReadTimeout(int) 532 */ 533 public int getReceiveTimeout() { 534 return receiveTimeout; 535 } 536 537 /* 538 * (non-Javadoc) 539 * @see java.lang.Object#toString() 540 */ 541 @Override 542 public String toString() { 543 if (parameters != null) { 544 String parity = "N"; 545 String flowControl = "N"; 546 if (parameters.parity == 1) 547 parity = "O"; 548 else if (parameters.parity == 2) 549 parity = "E"; 550 else if (parameters.parity == 3) 551 parity = "M"; 552 else if (parameters.parity == 4) 553 parity = "S"; 554 if (parameters.flowControl == 1 555 || parameters.flowControl == 2 556 || parameters.flowControl == 3) 557 flowControl = "H"; 558 else if (parameters.flowControl == 4 559 || parameters.flowControl == 8 560 || parameters.flowControl == 12) 561 flowControl = "S"; 562 return "[" + port + " - " + baudRate + "/" + parameters.dataBits + 563 "/" + parity + "/" + parameters.stopBits + "/" + flowControl + "] "; 564 } else 565 return "[" + port + " - " + baudRate + "/8/N/1/N] "; 566 } 567}