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