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