001package jmri.jmrix; 002 003 004import java.io.*; 005import java.util.Vector; 006 007import jmri.SystemConnectionMemo; 008import jmri.jmrix.fakeport.FakeInputStream; 009 010/** 011 * Provide an abstract base for *PortController classes. 012 * <p> 013 * The intent is to hide, to the extent possible, all the references to the 014 * actual serial library in use within this class. Subclasses then 015 * rely on methods here to maniplate the content of the 016 * protected currentSerialPort variable/ 017 * 018 * @see jmri.jmrix.SerialPortAdapter 019 * 020 * @author Bob Jacobsen Copyright (C) 2001, 2002, 2023 021 */ 022abstract public class AbstractSerialPortController extends AbstractPortController implements SerialPortAdapter { 023 024 protected AbstractSerialPortController(SystemConnectionMemo connectionMemo) { 025 super(connectionMemo); 026 } 027 028 protected volatile SerialPort currentSerialPort = null; 029 private final ReplaceableInputStream inputStream = new ReplaceableInputStream(); 030 private final ReplaceableOutputStream outputStream = new ReplaceableOutputStream(); 031 032 /** 033 * Standard error handling for jmri.jmrix.purejavacomm port-busy case. 034 * 035 * @param p the exception being handled, if additional information 036 * from it is desired 037 * @param portName name of the port being accessed 038 * @param log where to log a status message 039 * @return Localized message, in case separate presentation to user is 040 * desired 041 */ 042 //@Deprecated(forRemoval=true) // with jmri.jmrix.PureJavaComm 043 public String handlePortBusy(jmri.jmrix.purejavacomm.PortInUseException p, String portName, org.slf4j.Logger log) { 044 log.error("{} port is in use: {}", portName, p.getMessage()); 045 ConnectionStatus.instance().setConnectionState(this.getSystemConnectionMemo(), ConnectionStatus.CONNECTION_DOWN); 046 return Bundle.getMessage("SerialPortInUse", portName); 047 } 048 049 /** 050 * Specific error handling for jmri.jmrix.purejavacomm port-not-found case. 051 * @param p no such port exception. 052 * @param portName port name. 053 * @param log system log. 054 * @return human readable string with error detail. 055 */ 056 //@Deprecated(forRemoval=true) // with jmri.jmrix.PureJavaComm 057 public String handlePortNotFound(jmri.jmrix.purejavacomm.NoSuchPortException p, String portName, org.slf4j.Logger log) { 058 log.error("Serial port {} not found", portName); 059 ConnectionStatus.instance().setConnectionState(this.getSystemConnectionMemo(), ConnectionStatus.CONNECTION_DOWN); 060 return Bundle.getMessage("SerialPortNotFound", portName); 061 } 062 063 /** 064 * Standard error handling for the general port-not-found case. 065 * @param memo the system memo 066 * @param portName port name. 067 * @param log system log, passed so logging comes from bottom level class 068 * @param ex Underlying Exception that caused this failure 069 * @return human readable string with error detail. 070 */ 071 public static String handlePortNotFound(SystemConnectionMemo memo, String portName, org.slf4j.Logger log, Exception ex) { 072 log.error("Serial port {} not found: {}", portName, ex.getMessage()); 073 if (memo != null) { 074 ConnectionStatus.instance().setConnectionState(memo, ConnectionStatus.CONNECTION_DOWN); 075 } 076 return Bundle.getMessage("SerialPortNotFound", portName); 077 } 078 079 /** 080 * {@inheritDoc} 081 */ 082 @Override 083 public void connect() throws java.io.IOException { 084 openPort(mPort, "JMRI app"); 085 } 086 087 /** 088 * Do the formal opening of the port, 089 * set the port for blocking reads without timeout, 090 * set the port to 8 data bits, 1 stop bit, no parity 091 * and purge the port's input stream. 092 * <p> 093 * Does not do the rest of the setup implied in the {@link #openPort} method. 094 * This is usually followed by calls to 095 * {@link #setBaudRate}, {@link #configureLeads} and {@link #setFlowControl}. 096 * 097 * @param portName local system name for the desired port 098 * @param log Logger to use for errors, passed so that errors are logged from low-level class 099 * @return the serial port object for later use 100 */ 101 protected final SerialPort activatePort(String portName, org.slf4j.Logger log) { 102 return activatePort(this.getSystemConnectionMemo(), portName, log, 1, SerialPort.Parity.NONE); 103 } 104 105 /** 106 * Do the formal opening of the port, 107 * set the port for blocking reads without timeout, 108 * set the port to 8 data bits, the indicated number of stop bits, no parity, 109 * and purge the port's input stream. 110 * <p> 111 * Does not do the rest of the setup implied in the {@link #openPort} method. 112 * This is usually followed by calls to 113 * {@link #setBaudRate}, {@link #configureLeads} and {@link #setFlowControl}. 114 * 115 * @param portName local system name for the desired port 116 * @param log Logger to use for errors, passed so that errors are logged from low-level class' 117 * @param stop_bits The number of stop bits, either 1 or 2 118 * @return the serial port object for later use 119 */ 120 protected final SerialPort activatePort(String portName, org.slf4j.Logger log, int stop_bits) { 121 return activatePort(this.getSystemConnectionMemo(), portName, log, stop_bits, SerialPort.Parity.NONE); 122 } 123 124 /** 125 * Do the formal opening of the port, 126 * set the port for blocking reads without timeout, 127 * set the port to 8 data bits, the indicated number of stop bits and parity, 128 * and purge the port's input stream. 129 * <p> 130 * Does not do the rest of the setup implied in the {@link #openPort} method. 131 * This is usually followed by calls to 132 * {@link #setBaudRate}, {@link #configureLeads} and {@link #setFlowControl}. 133 * 134 * @param memo the system memo 135 * @param portName local system name for the desired port 136 * @param log Logger to use for errors, passed so that errors are logged from low-level class' 137 * @param stop_bits The number of stop bits, either 1 or 2 138 * @param parity one of the defined parity contants 139 * @return the serial port object for later use 140 */ 141 public static SerialPort activatePort( 142 SystemConnectionMemo memo, 143 String portName, 144 org.slf4j.Logger log, 145 int stop_bits, 146 SerialPort.Parity parity) { 147 148 return jmri.jmrix.jserialcomm.JSerialPort.activatePort(memo, portName, log, stop_bits, parity); 149 } 150 151 protected final void setComPortTimeouts(SerialPort serialPort, Blocking blocking, int timeout) { 152 serialPort.setComPortTimeouts(blocking.getValue(), timeout, 0); 153 } 154 155 /** 156 * {@inheritDoc} 157 */ 158 @Override 159 public void setPort(String port) { 160 log.debug("Setting port to {}", port); 161 mPort = port; 162 } 163 protected String mPort = null; 164 165 /** 166 * {@inheritDoc} 167 * 168 * Overridden in simulator adapter classes to return ""; 169 */ 170 @Override 171 public String getCurrentPortName() { 172 if (mPort == null) { 173 if (getPortNames() == null) { 174 // this shouldn't happen in normal operation 175 // but in the tests this can happen if the receive thread has been interrupted 176 log.error("Port names returned as null"); 177 return null; 178 } 179 if (getPortNames().size() <= 0) { 180 log.error("No usable ports returned"); 181 return null; 182 } 183 return null; 184 // return (String)getPortNames().elementAt(0); 185 } 186 return mPort; 187 } 188 189 /** 190 * Provide the actual serial port names. 191 * As a public static method, this can be accessed outside the jmri.jmrix 192 * package to get the list of names for e.g. context reports. 193 * 194 * @return the port names in the form they can later be used to open the port 195 */ 196 public static Vector<String> getActualPortNames() { 197 return jmri.jmrix.jserialcomm.JSerialPort.getActualPortNames(); 198 } 199 200 /** 201 * Set the control leads and flow control for jmri.jmrix.purejavacomm. This handles any necessary 202 * ordering. 203 * 204 * @param serialPort Port to be updated 205 * @param flow flow control mode from (@link jmri.jmrix.purejavacomm.SerialPort} 206 * @param rts set RTS active if true 207 * @param dtr set DTR active if true 208 */ 209 //@Deprecated(forRemoval=true) // Removed with jmri.jmrix.PureJavaComm 210 protected void configureLeadsAndFlowControl(jmri.jmrix.purejavacomm.SerialPort serialPort, int flow, boolean rts, boolean dtr) { 211 // (Jan 2018) PJC seems to mix termios and ioctl access, so it's not clear 212 // what's preserved and what's not. Experimentally, it seems necessary 213 // to write the control leads, set flow control, and then write the control 214 // leads again. 215 serialPort.setRTS(rts); 216 serialPort.setDTR(dtr); 217 218 try { 219 if (flow != jmri.jmrix.purejavacomm.SerialPort.FLOWCONTROL_NONE) { 220 serialPort.setFlowControlMode(flow); 221 } 222 } catch (jmri.jmrix.purejavacomm.UnsupportedCommOperationException e) { 223 log.warn("Could not set flow control, ignoring"); 224 } 225 if (flow!=jmri.jmrix.purejavacomm.SerialPort.FLOWCONTROL_RTSCTS_OUT) serialPort.setRTS(rts); // not connected in some serial ports and adapters 226 serialPort.setDTR(dtr); 227 } 228 229 /** 230 * Set the baud rate on the port 231 * 232 * @param serialPort Port to be updated 233 * @param baud baud rate to be set 234 */ 235 protected final void setBaudRate(SerialPort serialPort, int baud) { 236 serialPort.setBaudRate(baud); 237 } 238 239 /** 240 * Set the control leads. 241 * 242 * @param serialPort Port to be updated 243 * @param rts set RTS active if true 244 * @param dtr set DTR active if true 245 */ 246 protected final void configureLeads(SerialPort serialPort, boolean rts, boolean dtr) { 247 if (rts) { 248 serialPort.setRTS(); 249 } else { 250 serialPort.clearRTS(); 251 } 252 if (dtr) { 253 serialPort.setDTR(); 254 } else { 255 serialPort.clearDTR(); 256 } 257 258 } 259 260 /** 261 * Enumerate the possible flow control choices 262 */ 263 public enum FlowControl { 264 NONE, 265 RTSCTS, 266 XONXOFF 267 } 268 269 /** 270 * Enumerate the possible timeout choices 271 */ 272 public enum Blocking { 273 NONBLOCKING(com.fazecast.jSerialComm.SerialPort.TIMEOUT_NONBLOCKING), 274 READ_BLOCKING(com.fazecast.jSerialComm.SerialPort.TIMEOUT_READ_BLOCKING), 275 READ_SEMI_BLOCKING(com.fazecast.jSerialComm.SerialPort.TIMEOUT_READ_SEMI_BLOCKING); 276 277 private final int value; 278 279 Blocking(int value) { 280 this.value = value; 281 } 282 283 public int getValue() { 284 return value; 285 } 286 } 287 288 /** 289 * Configure the flow control settings. Keep this in synch with the 290 * FlowControl enum. 291 * 292 * @param serialPort Port to be updated 293 * @param flow set which kind of flow control to use 294 */ 295 protected final void setFlowControl(SerialPort serialPort, FlowControl flow) { 296 lastFlowControl = flow; 297 serialPort.setFlowControl(flow); 298 } 299 300 private FlowControl lastFlowControl = FlowControl.NONE; 301 /** 302 * get the flow control mode back from the actual port. 303 * @param serialPort Port to be examined 304 * @return flow control setting observed in the port 305 */ 306 protected final FlowControl getFlowControl(SerialPort serialPort) { 307 // do a cross-check, just in case there's an issue 308 int nowFlow = serialPort.getFlowControlSettings(); 309 310 switch (lastFlowControl) { 311 312 case NONE: 313 if (nowFlow != com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_DISABLED) 314 log.error("Expected flow {} but found {}", lastFlowControl, nowFlow); 315 break; 316 case RTSCTS: 317 if (nowFlow != (com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_RTS_ENABLED 318 | com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_CTS_ENABLED)) 319 log.error("Expected flow {} but found {}", lastFlowControl, nowFlow); 320 break; 321 case XONXOFF: 322 if (nowFlow != (com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_XONXOFF_IN_ENABLED 323 | com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_XONXOFF_OUT_ENABLED)) 324 log.error("Expected flow {} but found {}", lastFlowControl, nowFlow); 325 break; 326 default: 327 log.warn("Unexpected FlowControl mode: {}", lastFlowControl); 328 } 329 330 return lastFlowControl; 331 } 332 333 /** 334 * Add a data listener to the specified port 335 * @param serialPort Port to be updated 336 * @param serialPortDataListener the listener to add 337 */ 338 protected final void setDataListener(SerialPort serialPort, SerialPortDataListener serialPortDataListener){ 339 serialPort.addDataListener(serialPortDataListener); 340 } 341 342 /** 343 * Cleanly close the specified port 344 * @param serialPort Port to be closed 345 */ 346 protected final void closeSerialPort(SerialPort serialPort){ 347 serialPort.closePort(); 348 } 349 350 /** 351 * Set the flow control for jmri.jmrix.purejavacomm, while also setting RTS and DTR to active. 352 * 353 * @param serialPort Port to be updated 354 * @param flow flow control mode from (@link jmri.jmrix.purejavacomm.SerialPort} 355 */ 356 //@Deprecated(forRemoval=true) // with jmri.jmrix.PureJavaComm 357 protected final void configureLeadsAndFlowControl(jmri.jmrix.purejavacomm.SerialPort serialPort, int flow) { 358 configureLeadsAndFlowControl(serialPort, flow, true, true); 359 } 360 361 /** 362 * Report the connection status. 363 * Typically used after the connection is complete 364 * @param log The low-level logger to get this reported against the right class 365 * @param portName low-level name of selected port 366 */ 367 protected final void reportPortStatus(org.slf4j.Logger log, String portName) { 368 if (log.isInfoEnabled()) { 369 log.info("{}: Port {} opened at {} baud, sees DTR: {} RTS: {} DSR: {} CTS: {} DCD: {} flow: {}", 370 this.getSystemConnectionMemo().getUserName(), currentSerialPort.getDescriptivePortName(), 371 currentSerialPort.getBaudRate(), currentSerialPort.getDTR(), 372 currentSerialPort.getRTS(), currentSerialPort.getDSR(), currentSerialPort.getCTS(), 373 currentSerialPort.getDCD(), getFlowControl(currentSerialPort)); 374 } 375 if (log.isDebugEnabled()) { 376 String stopBits; 377 switch (currentSerialPort.getNumStopBits()) { 378 case com.fazecast.jSerialComm.SerialPort.TWO_STOP_BITS: 379 stopBits = "2"; 380 break; 381 case com.fazecast.jSerialComm.SerialPort.ONE_STOP_BIT: 382 stopBits = "1"; 383 break; 384 default: 385 stopBits = "unknown"; 386 break; 387 } 388 log.debug(" {} data bits, {} stop bits", 389 currentSerialPort.getNumDataBits(), stopBits); 390 } 391 392 } 393 394 395 // When PureJavaComm is removed, set this to 'final' to find 396 // identical implementations in the subclasses - but note simulators are now overriding 397 @Override 398 public DataInputStream getInputStream() { 399 if (!opened) { 400 log.error("getInputStream called before open, stream not available"); 401 return null; 402 } 403 inputStream.replaceStream(currentSerialPort.getInputStream()); 404 return new DataInputStream(inputStream); 405 } 406 407 // When PureJavaComm is removed, set this to 'final' to find 408 // identical implementations in the subclasses - but note simulators are now overriding 409 @Override 410 public DataOutputStream getOutputStream() { 411 if (!opened) { 412 log.error("getOutputStream called before open, stream not available"); 413 } 414 outputStream.replaceStream(currentSerialPort.getOutputStream()); 415 return new DataOutputStream(outputStream); 416 } 417 418 419 /** 420 * {@inheritDoc} 421 */ 422 @Override 423 public final void configureBaudRate(String rate) { 424 mBaudRate = rate; 425 } 426 427 /** 428 * {@inheritDoc} 429 */ 430 @Override 431 public final void configureBaudRateFromNumber(String indexString) { 432 int baudNum; 433 int index = 0; 434 final String[] rates = validBaudRates(); 435 final int[] numbers = validBaudNumbers(); 436 if ((numbers == null) || (numbers.length == 0)) { // simulators return null TODO for SpotBugs make that into an empty array 437 mBaudRate = null; 438 log.debug("no serial port speed values received (OK for simulator)"); 439 return; 440 } 441 if (numbers.length != rates.length) { 442 mBaudRate = null; 443 log.error("arrays wrong length in currentBaudNumber: {}, {}", numbers.length, rates.length); 444 return; 445 } 446 if (indexString.isEmpty()) { 447 mBaudRate = null; // represents "(none)" 448 log.debug("empty baud rate received"); 449 return; 450 } 451 try { 452 // since 4.16 first try to convert loaded value directly to integer 453 baudNum = Integer.parseInt(indexString); // new storage format, will throw ex on old format 454 log.debug("new profile format port speed value"); 455 } catch (NumberFormatException ex) { 456 // old pre 4.15.8 format is i18n string including thousand separator and whatever suffix like "18,600 bps (J1)" 457 log.warn("old profile format port speed value converted"); 458 // filter only numerical characters from indexString 459 StringBuilder baudNumber = new StringBuilder(); 460 boolean digitSeen = false; 461 for (int n = 0; n < indexString.length(); n++) { 462 if (Character.isDigit(indexString.charAt(n))) { 463 digitSeen = true; 464 baudNumber.append(indexString.charAt(n)); 465 } else if ((indexString.charAt(n) == ' ') && digitSeen) { 466 break; // break on first space char encountered after at least 1 digit was found 467 } 468 } 469 if (baudNumber.toString().equals("")) { // no number found in indexString e.g. "(automatic)" 470 baudNum = 0; 471 } else { 472 try { 473 baudNum = Integer.parseInt(baudNumber.toString()); 474 } catch (NumberFormatException e2) { 475 mBaudRate = null; // represents "(none)" 476 log.error("error in filtering old profile format port speed value"); 477 return; 478 } 479 log.debug("old format baud number: {}", indexString); 480 } 481 } 482 // fetch baud rate description from validBaudRates[] array copy and set 483 for (int i = 0; i < numbers.length; i++) { 484 if (numbers[i] == baudNum) { 485 index = i; 486 log.debug("found new format baud value at index {}", i); 487 break; 488 } 489 } 490 mBaudRate = validBaudRates()[index]; 491 log.debug("mBaudRate set to: {}", mBaudRate); 492 } 493 494 /** 495 * {@inheritDoc} 496 * Invalid indexes are ignored. 497 */ 498 @Override 499 public final void configureBaudRateFromIndex(int index) { 500 if (validBaudRates().length > index && index > -1 ) { 501 mBaudRate = validBaudRates()[index]; 502 log.debug("mBaudRate set by index to: {}", mBaudRate); 503 } else { 504 // expected for simulators extending serialPortAdapter, mBaudRate already null 505 log.debug("no baud rate index {} in array size {}", index, validBaudRates().length); 506 } 507 } 508 509 protected String mBaudRate = null; 510 511 @Override 512 public int defaultBaudIndex() { 513 return -1; 514 } 515 516 /** 517 * {@inheritDoc} 518 */ 519 @Override 520 public String getCurrentBaudRate() { 521 if (mBaudRate == null) { 522 return ""; 523 } 524 return mBaudRate; 525 } 526 527 /** 528 * {@inheritDoc} 529 */ 530 @Override 531 public final String getCurrentBaudNumber() { 532 int[] numbers = validBaudNumbers(); 533 String[] rates = validBaudRates(); 534 if (numbers == null || rates == null || numbers.length != rates.length) { // entries in arrays should correspond 535 return ""; 536 } 537 String baudNumString = ""; 538 // first try to find the configured baud rate value 539 if (mBaudRate != null) { 540 for (int i = 0; i < numbers.length; i++) { 541 if (rates[i].equals(mBaudRate)) { 542 baudNumString = Integer.toString(numbers[i]); 543 break; 544 } 545 } 546 } else if (defaultBaudIndex() > -1) { 547 // use default 548 baudNumString = Integer.toString(numbers[defaultBaudIndex()]); 549 log.debug("using default port speed {}", baudNumString); 550 } 551 log.debug("mBaudRate = {}, matched to string {}", mBaudRate, baudNumString); 552 return baudNumString; 553 } 554 555 @Override 556 public final int getCurrentBaudIndex() { 557 if (mBaudRate != null) { 558 String[] rates = validBaudRates(); 559 // find the configured baud rate value 560 for (int i = 0; i < rates.length; i++) { 561 if (rates[i].equals(mBaudRate)) { 562 return i; 563 } 564 } 565 } 566 return defaultBaudIndex(); // default index or -1 if port speed not supported 567 } 568 569 /** 570 * {@inheritDoc} 571 */ 572 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "PZLA_PREFER_ZERO_LENGTH_ARRAYS", 573 justification = "null signals incorrect implementation of portcontroller") 574 @Override 575 public String[] validBaudRates() { 576 log.error("default validBaudRates implementation should not be used", new Exception()); 577 return null; 578 } 579 580 /** 581 * {@inheritDoc} 582 */ 583 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "PZLA_PREFER_ZERO_LENGTH_ARRAYS", 584 justification = "null signals incorrect implementation of portcontroller") 585 @Override 586 public int[] validBaudNumbers() { 587 log.error("default validBaudNumbers implementation should not be used", new Exception()); 588 return null; 589 } 590 591 /** 592 * Convert a baud rate I18N String to an int number, e.g. "9,600 baud" to 9600. 593 * <p> 594 * Uses the validBaudNumbers() and validBaudRates() methods to do this. 595 * 596 * @param currentBaudRate a rate from validBaudRates() 597 * @return baudrate as integer if available and matching first digits in currentBaudRate, 598 * 0 if baudrate not supported by this adapter, 599 * -1 if no match (configuration system should prevent this) 600 */ 601 public final int currentBaudNumber(String currentBaudRate) { 602 String[] rates = validBaudRates(); 603 int[] numbers = validBaudNumbers(); 604 605 // return if arrays invalid 606 if (numbers == null) { 607 log.error("numbers array null in currentBaudNumber()"); 608 return -1; 609 } 610 if (rates == null) { 611 log.error("rates array null in currentBaudNumber()"); 612 return -1; 613 } 614 if (numbers.length != rates.length) { 615 log.error("arrays are of different length in currentBaudNumber: {} vs {}", numbers.length, rates.length); 616 return -1; 617 } 618 if (numbers.length < 1) { 619 log.warn("baudrate is not supported by adapter"); 620 return 0; 621 } 622 // find the baud rate value 623 for (int i = 0; i < numbers.length; i++) { 624 if (rates[i].equals(currentBaudRate)) { 625 return numbers[i]; 626 } 627 } 628 629 // no match 630 log.error("no match to ({}) in currentBaudNumber", currentBaudRate); 631 return -1; 632 } 633 634 /** 635 * {@inheritDoc} 636 * Each serial port adapter should handle this and it should be abstract. 637 */ 638 @Override 639 protected void closeConnection(){} 640 641 /** 642 * Re-setup the connection. 643 * Called when the physical connection has reconnected and can be linked to 644 * this connection. 645 * Each port adapter should handle this and it should be abstract. 646 */ 647 @Override 648 protected void resetupConnection(){} 649 650 /** 651 * Is the serial port open? 652 * The LocoNet simulator uses this class but doesn't open the port. 653 * @return true if the port is open, false otherwise 654 */ 655 public boolean isPortOpen() { 656 return currentSerialPort != null; 657 } 658 659 /** 660 * Replace the serial port with a fake serial port and close the old 661 * serial port. 662 * Note that you can only replace the port once. This call is used when 663 * you want to close the port and reopen it for some special task, for 664 * example upload firmware. 665 */ 666 public void replacePortWithFakePort() { 667 log.warn("Replacing serial port with fake serial port: {}", currentSerialPort.getDescriptivePortName()); 668 SerialPort oldSerialPort = currentSerialPort; 669 SerialPort serialPort = new jmri.jmrix.fakeport.FakeSerialPort(); 670 inputStream.replaceStream(new FakeInputStream()); 671 outputStream.replaceStream(OutputStream.nullOutputStream()); 672 currentSerialPort = serialPort; 673 oldSerialPort.closePort(); 674 } 675 676 /** 677 * Get a string with the serial port settings. 678 * @return the settings as a string 679 */ 680 public String getPortSettingsString() { 681 StringBuilder sb = new StringBuilder(); 682 sb.append("Baudrate: ").append(currentSerialPort.getBaudRate()).append(", "); 683 sb.append("FlowControl: ").append(currentSerialPort.getFlowControlSettings()).append(", "); 684 sb.append("Num data bits: ").append(currentSerialPort.getNumDataBits()).append(", "); 685 sb.append("Num stop bits: ").append(currentSerialPort.getNumStopBits()).append(", "); 686 sb.append("Parity").append(currentSerialPort.getParity().name()); 687 return sb.toString(); 688 } 689 690 691 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractSerialPortController.class); 692 693}