001package jmri.jmrix; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import java.io.*; 006import java.util.Set; 007import java.util.Vector; 008import java.util.regex.Pattern; 009import java.util.stream.Collectors; 010import java.util.stream.Stream; 011 012import jmri.SystemConnectionMemo; 013import jmri.util.SystemType; 014 015/** 016 * Provide an abstract base for *PortController classes. 017 * <p> 018 * The intent is to hide, to the extent possible, all the references to the 019 * actual serial library in use within this class. Subclasses then 020 * rely on methods here to maniplate the content of the 021 * protected currentSerialPort variable/ 022 * 023 * @see jmri.jmrix.SerialPortAdapter 024 * 025 * @author Bob Jacobsen Copyright (C) 2001, 2002, 2023 026 */ 027abstract public class AbstractSerialPortController extends AbstractPortController implements SerialPortAdapter { 028 029 protected AbstractSerialPortController(SystemConnectionMemo connectionMemo) { 030 super(connectionMemo); 031 } 032 033 protected SerialPort currentSerialPort = null; 034 035 /** 036 * Standard error handling for jmri.jmrix.purejavacomm port-busy case. 037 * 038 * @param p the exception being handled, if additional information 039 * from it is desired 040 * @param portName name of the port being accessed 041 * @param log where to log a status message 042 * @return Localized message, in case separate presentation to user is 043 * desired 044 */ 045 //@Deprecated(forRemoval=true) // with jmri.jmrix.PureJavaComm 046 public String handlePortBusy(jmri.jmrix.purejavacomm.PortInUseException p, String portName, org.slf4j.Logger log) { 047 log.error("{} port is in use: {}", portName, p.getMessage()); 048 ConnectionStatus.instance().setConnectionState(this.getSystemPrefix(), portName, ConnectionStatus.CONNECTION_DOWN); 049 return Bundle.getMessage("SerialPortInUse", portName); 050 } 051 052 /** 053 * Specific error handling for jmri.jmrix.purejavacomm port-not-found case. 054 * @param p no such port exception. 055 * @param portName port name. 056 * @param log system log. 057 * @return human readable string with error detail. 058 */ 059 //@Deprecated(forRemoval=true) // with jmri.jmrix.PureJavaComm 060 public String handlePortNotFound(jmri.jmrix.purejavacomm.NoSuchPortException p, String portName, org.slf4j.Logger log) { 061 log.error("Serial port {} not found", portName); 062 ConnectionStatus.instance().setConnectionState(this.getSystemPrefix(), portName, ConnectionStatus.CONNECTION_DOWN); 063 return Bundle.getMessage("SerialPortNotFound", portName); 064 } 065 066 /** 067 * Standard error handling for the general port-not-found case. 068 * @param systemPrefix the system prefix 069 * @param portName port name. 070 * @param log system log, passed so logging comes from bottom level class 071 * @param ex Underlying Exception that caused this failure 072 * @return human readable string with error detail. 073 */ 074 public static String handlePortNotFound(String systemPrefix, String portName, org.slf4j.Logger log, Exception ex) { 075 log.error("Serial port {} not found: {}", portName, ex.getMessage()); 076 if (systemPrefix != null) { 077 ConnectionStatus.instance().setConnectionState(systemPrefix, portName, ConnectionStatus.CONNECTION_DOWN); 078 } 079 return Bundle.getMessage("SerialPortNotFound", portName); 080 } 081 082 /** 083 * {@inheritDoc} 084 */ 085 @Override 086 public void connect() throws java.io.IOException { 087 openPort(mPort, "JMRI app"); 088 } 089 090 /** 091 * Do the formal opening of the port, 092 * set the port for blocking reads without timeout, 093 * set the port to 8 data bits, 1 stop bit, no parity 094 * and purge the port's input stream. 095 * <p> 096 * Does not do the rest of the setup implied in the {@link #openPort} method. 097 * This is usually followed by calls to 098 * {@link #setBaudRate}, {@link #configureLeads} and {@link #setFlowControl}. 099 * 100 * @param portName local system name for the desired port 101 * @param log Logger to use for errors, passed so that errors are logged from low-level class 102 * @return the serial port object for later use 103 */ 104 final protected SerialPort activatePort(String portName, org.slf4j.Logger log) { 105 return activatePort(this.getSystemPrefix(), portName, log, 1, Parity.NONE); 106 } 107 108 /** 109 * Do the formal opening of the port, 110 * set the port for blocking reads without timeout, 111 * set the port to 8 data bits, the indicated number of stop bits, no parity, 112 * and purge the port's input stream. 113 * <p> 114 * Does not do the rest of the setup implied in the {@link #openPort} method. 115 * This is usually followed by calls to 116 * {@link #setBaudRate}, {@link #configureLeads} and {@link #setFlowControl}. 117 * 118 * @param portName local system name for the desired port 119 * @param log Logger to use for errors, passed so that errors are logged from low-level class' 120 * @param stop_bits The number of stop bits, either 1 or 2 121 * @return the serial port object for later use 122 */ 123 final protected SerialPort activatePort(String portName, org.slf4j.Logger log, int stop_bits) { 124 return activatePort(this.getSystemPrefix(), portName, log, stop_bits, Parity.NONE); 125 } 126 127 /** 128 * Do the formal opening of the port, 129 * set the port for blocking reads without timeout, 130 * set the port to 8 data bits, the indicated number of stop bits and parity, 131 * and purge the port's input stream. 132 * <p> 133 * Does not do the rest of the setup implied in the {@link #openPort} method. 134 * This is usually followed by calls to 135 * {@link #setBaudRate}, {@link #configureLeads} and {@link #setFlowControl}. 136 * 137 * @param systemPrefix the system prefix 138 * @param portName local system name for the desired port 139 * @param log Logger to use for errors, passed so that errors are logged from low-level class' 140 * @param stop_bits The number of stop bits, either 1 or 2 141 * @param parity one of the defined parity contants 142 * @return the serial port object for later use 143 */ 144 public static SerialPort activatePort(String systemPrefix, String portName, org.slf4j.Logger log, int stop_bits, Parity parity) { 145 com.fazecast.jSerialComm.SerialPort serialPort; 146 147 // convert the 1 or 2 stop_bits argument to the proper jSerialComm code value 148 int stop_bits_code; 149 switch (stop_bits) { 150 case 1: 151 stop_bits_code = com.fazecast.jSerialComm.SerialPort.ONE_STOP_BIT; 152 break; 153 case 2: 154 stop_bits_code = com.fazecast.jSerialComm.SerialPort.TWO_STOP_BITS; 155 break; 156 default: 157 throw new IllegalArgumentException("Incorrect stop_bits argument: "+stop_bits); 158 } 159 160 try { 161 serialPort = com.fazecast.jSerialComm.SerialPort.getCommPort(portName); 162 serialPort.openPort(); 163 serialPort.setComPortTimeouts(com.fazecast.jSerialComm.SerialPort.TIMEOUT_READ_BLOCKING, 0, 0); 164 serialPort.setNumDataBits(8); 165 serialPort.setNumStopBits(stop_bits_code); 166 serialPort.setParity(parity.getValue()); 167 purgeStream(serialPort.getInputStream()); 168 } catch (java.io.IOException | com.fazecast.jSerialComm.SerialPortInvalidPortException ex) { 169 // IOException includes 170 // com.fazecast.jSerialComm.SerialPortIOException 171 handlePortNotFound(systemPrefix, portName, log, ex); 172 return null; 173 } 174 return new SerialPort(serialPort); 175 } 176 177 final protected void setComPortTimeouts(SerialPort serialPort, Blocking blocking, int timeout) { 178 serialPort.serialPort.setComPortTimeouts(blocking.getValue(), timeout, 0); 179 } 180 181 /** 182 * {@inheritDoc} 183 */ 184 @Override 185 public void setPort(String port) { 186 log.debug("Setting port to {}", port); 187 mPort = port; 188 } 189 protected String mPort = null; 190 191 /** 192 * {@inheritDoc} 193 * 194 * Overridden in simulator adapter classes to return ""; 195 */ 196 @Override 197 public String getCurrentPortName() { 198 if (mPort == null) { 199 if (getPortNames() == null) { 200 // this shouldn't happen in normal operation 201 // but in the tests this can happen if the receive thread has been interrupted 202 log.error("Port names returned as null"); 203 return null; 204 } 205 if (getPortNames().size() <= 0) { 206 log.error("No usable ports returned"); 207 return null; 208 } 209 return null; 210 // return (String)getPortNames().elementAt(0); 211 } 212 return mPort; 213 } 214 215 private static String getSymlinkTarget(File symlink) { 216 try { 217 // Path.toRealPath() follows a symlink 218 return symlink.toPath().toRealPath().toFile().getName(); 219 } catch (IOException e) { 220 return null; 221 } 222 } 223 224 /** 225 * Provide the actual serial port names. 226 * As a public static method, this can be accessed outside the jmri.jmrix 227 * package to get the list of names for e.g. context reports. 228 * 229 * @return the port names in the form they can later be used to open the port 230 */ 231// @SuppressWarnings("UseOfObsoleteCollectionType") // historical interface 232 @SuppressFBWarnings("DMI_HARDCODED_ABSOLUTE_FILENAME") // /dev/ is not expected to change on Linux and Mac 233 public static Vector<String> getActualPortNames() { 234 // first, check that the comm package can be opened and ports seen 235 var portNameVector = new Vector<String>(); 236 237 com.fazecast.jSerialComm.SerialPort[] portIDs = com.fazecast.jSerialComm.SerialPort.getCommPorts(); 238 // find the names of suitable ports 239 for (com.fazecast.jSerialComm.SerialPort portID : portIDs) { 240 portNameVector.addElement(portID.getSystemPortName()); 241 } 242 243 // On Linux and Mac, use the system property purejavacomm.portnamepattern 244 // to let the user add additional serial ports 245 String portnamePattern = System.getProperty("purejavacomm.portnamepattern"); 246 if ((portnamePattern != null) && (SystemType.isLinux() || SystemType.isMacOSX())) { 247 Pattern pattern = Pattern.compile(portnamePattern); 248 249 File[] files = new File("/dev").listFiles(); 250 if (files != null) { 251 Set<String> ports = Stream.of(files) 252 .filter(file -> !file.isDirectory() 253 && (pattern.matcher(file.getName()).matches() 254 || portNameVector.contains(getSymlinkTarget(file))) 255 && !portNameVector.contains(file.getName())) 256 .map(File::getName) 257 .collect(Collectors.toSet()); 258 259 portNameVector.addAll(ports); 260 } 261 262 } 263 264 return portNameVector; 265 } 266 267 /** 268 * Set the control leads and flow control for jmri.jmrix.purejavacomm. This handles any necessary 269 * ordering. 270 * 271 * @param serialPort Port to be updated 272 * @param flow flow control mode from (@link jmri.jmrix.purejavacomm.SerialPort} 273 * @param rts set RTS active if true 274 * @param dtr set DTR active if true 275 */ 276 //@Deprecated(forRemoval=true) // Removed with jmri.jmrix.PureJavaComm 277 protected void configureLeadsAndFlowControl(jmri.jmrix.purejavacomm.SerialPort serialPort, int flow, boolean rts, boolean dtr) { 278 // (Jan 2018) PJC seems to mix termios and ioctl access, so it's not clear 279 // what's preserved and what's not. Experimentally, it seems necessary 280 // to write the control leads, set flow control, and then write the control 281 // leads again. 282 serialPort.setRTS(rts); 283 serialPort.setDTR(dtr); 284 285 try { 286 if (flow != jmri.jmrix.purejavacomm.SerialPort.FLOWCONTROL_NONE) { 287 serialPort.setFlowControlMode(flow); 288 } 289 } catch (jmri.jmrix.purejavacomm.UnsupportedCommOperationException e) { 290 log.warn("Could not set flow control, ignoring"); 291 } 292 if (flow!=jmri.jmrix.purejavacomm.SerialPort.FLOWCONTROL_RTSCTS_OUT) serialPort.setRTS(rts); // not connected in some serial ports and adapters 293 serialPort.setDTR(dtr); 294 } 295 296 /** 297 * Set the baud rate on the port 298 * 299 * @param serialPort Port to be updated 300 * @param baud baud rate to be set 301 */ 302 final protected void setBaudRate(SerialPort serialPort, int baud) { 303 serialPort.serialPort.setBaudRate(baud); 304 } 305 306 /** 307 * Set the control leads. 308 * 309 * @param serialPort Port to be updated 310 * @param rts set RTS active if true 311 * @param dtr set DTR active if true 312 */ 313 final protected void configureLeads(SerialPort serialPort, boolean rts, boolean dtr) { 314 if (rts) { 315 serialPort.serialPort.setRTS(); 316 } else { 317 serialPort.serialPort.clearRTS(); 318 } 319 if (dtr) { 320 serialPort.serialPort.setDTR(); 321 } else { 322 serialPort.serialPort.clearDTR(); 323 } 324 325 } 326 327 /** 328 * Configure the port's parity 329 * 330 * @param serialPort Port to be updated 331 * @param parity the desired parity as one of the define static final constants 332 */ 333 final protected void setParity(com.fazecast.jSerialComm.SerialPort serialPort, Parity parity) { 334 serialPort.setParity(parity.getValue()); // constants are defined with values for the specific port class 335 } 336 337 /** 338 * Enumerate the possible flow control choices 339 */ 340 public enum FlowControl { 341 NONE, 342 RTSCTS, 343 XONXOFF 344 } 345 346 /** 347 * Enumerate the possible parity choices 348 */ 349 public enum Parity { 350 NONE(com.fazecast.jSerialComm.SerialPort.NO_PARITY), 351 EVEN(com.fazecast.jSerialComm.SerialPort.EVEN_PARITY), 352 ODD(com.fazecast.jSerialComm.SerialPort.ODD_PARITY); 353 354 private final int value; 355 356 Parity(int value) { 357 this.value = value; 358 } 359 360 public int getValue() { 361 return value; 362 } 363 364 public static Parity getParity(int parity) { 365 for (Parity p : Parity.values()) { 366 if (p.value == parity) { 367 return p; 368 } 369 } 370 throw new IllegalArgumentException("Unknown parity"); 371 } 372 } 373 374 /** 375 * Enumerate the possible timeout choices 376 */ 377 public enum Blocking { 378 NONBLOCKING(com.fazecast.jSerialComm.SerialPort.TIMEOUT_NONBLOCKING), 379 READ_BLOCKING(com.fazecast.jSerialComm.SerialPort.TIMEOUT_READ_BLOCKING), 380 READ_SEMI_BLOCKING(com.fazecast.jSerialComm.SerialPort.TIMEOUT_READ_SEMI_BLOCKING); 381 382 private final int value; 383 384 Blocking(int value) { 385 this.value = value; 386 } 387 388 public int getValue() { 389 return value; 390 } 391 } 392 393 /** 394 * Configure the flow control settings. Keep this in synch with the 395 * FlowControl enum. 396 * 397 * @param serialPort Port to be updated 398 * @param flow set which kind of flow control to use 399 */ 400 final protected void setFlowControl(SerialPort serialPort, FlowControl flow) { 401 lastFlowControl = flow; 402 serialPort.setFlowControl(flow); 403 } 404 405 private FlowControl lastFlowControl = FlowControl.NONE; 406 /** 407 * get the flow control mode back from the actual port. 408 * @param serialPort Port to be examined 409 * @return flow control setting observed in the port 410 */ 411 final protected FlowControl getFlowControl(SerialPort serialPort) { 412 // do a cross-check, just in case there's an issue 413 int nowFlow = serialPort.serialPort.getFlowControlSettings(); 414 415 switch (lastFlowControl) { 416 417 case NONE: 418 if (nowFlow != com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_DISABLED) 419 log.error("Expected flow {} but found {}", lastFlowControl, nowFlow); 420 break; 421 case RTSCTS: 422 if (nowFlow != (com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_RTS_ENABLED 423 | com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_CTS_ENABLED)) 424 log.error("Expected flow {} but found {}", lastFlowControl, nowFlow); 425 break; 426 case XONXOFF: 427 if (nowFlow != (com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_XONXOFF_IN_ENABLED 428 | com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_XONXOFF_OUT_ENABLED)) 429 log.error("Expected flow {} but found {}", lastFlowControl, nowFlow); 430 break; 431 default: 432 log.warn("Unexpected FlowControl mode: {}", lastFlowControl); 433 } 434 435 return lastFlowControl; 436 } 437 438 /** 439 * Add a data listener to the specified port 440 * @param serialPort Port to be updated 441 * @param serialPortDataListener the listener to add 442 */ 443 final protected void setDataListener(SerialPort serialPort, SerialPortDataListener serialPortDataListener){ 444 serialPort.serialPort.addDataListener(new com.fazecast.jSerialComm.SerialPortDataListener() { 445 @Override 446 public int getListeningEvents() { 447 return serialPortDataListener.getListeningEvents(); 448 } 449 450 @Override 451 public void serialEvent(com.fazecast.jSerialComm.SerialPortEvent event) { 452 serialPortDataListener.serialEvent(new SerialPortEvent(event)); 453 } 454 }); 455 } 456 457 /** 458 * Cleanly close the specified port 459 * @param serialPort Port to be closed 460 */ 461 final protected void closeSerialPort(SerialPort serialPort){ 462 serialPort.serialPort.closePort(); 463 } 464 465 /** 466 * Set the flow control for jmri.jmrix.purejavacomm, while also setting RTS and DTR to active. 467 * 468 * @param serialPort Port to be updated 469 * @param flow flow control mode from (@link jmri.jmrix.purejavacomm.SerialPort} 470 */ 471 //@Deprecated(forRemoval=true) // with jmri.jmrix.PureJavaComm 472 final protected void configureLeadsAndFlowControl(jmri.jmrix.purejavacomm.SerialPort serialPort, int flow) { 473 configureLeadsAndFlowControl(serialPort, flow, true, true); 474 } 475 476 /** 477 * Report the connection status. 478 * Typically used after the connection is complete 479 * @param log The low-level logger to get this reported against the right class 480 * @param portName low-level name of selected port 481 */ 482 final protected void reportPortStatus(org.slf4j.Logger log, String portName) { 483 if (log.isInfoEnabled()) { 484 log.info("{}: Port {} opened at {} baud, sees DTR: {} RTS: {} DSR: {} CTS: {} DCD: {} flow: {}", 485 this.getSystemConnectionMemo().getUserName(), currentSerialPort.serialPort.getDescriptivePortName(), 486 currentSerialPort.serialPort.getBaudRate(), currentSerialPort.serialPort.getDTR(), 487 currentSerialPort.serialPort.getRTS(), currentSerialPort.serialPort.getDSR(), currentSerialPort.serialPort.getCTS(), 488 currentSerialPort.serialPort.getDCD(), getFlowControl(currentSerialPort)); 489 } 490 if (log.isDebugEnabled()) { 491 String stopBits; 492 switch (currentSerialPort.serialPort.getNumStopBits()) { 493 case com.fazecast.jSerialComm.SerialPort.TWO_STOP_BITS: 494 stopBits = "2"; 495 break; 496 case com.fazecast.jSerialComm.SerialPort.ONE_STOP_BIT: 497 stopBits = "1"; 498 break; 499 default: 500 stopBits = "unknown"; 501 break; 502 } 503 log.debug(" {} data bits, {} stop bits", 504 currentSerialPort.serialPort.getNumDataBits(), stopBits); 505 } 506 507 } 508 509 510 // When PureJavaComm is removed, set this to 'final' to find 511 // identical implementations in the subclasses - but note simulators are now overriding 512 @Override 513 public DataInputStream getInputStream() { 514 if (!opened) { 515 log.error("getInputStream called before open, stream not available"); 516 return null; 517 } 518 return new DataInputStream(currentSerialPort.serialPort.getInputStream()); 519 } 520 521 // When PureJavaComm is removed, set this to 'final' to find 522 // identical implementations in the subclasses - but note simulators are now overriding 523 @Override 524 public DataOutputStream getOutputStream() { 525 if (!opened) { 526 log.error("getOutputStream called before open, stream not available"); 527 } 528 529 return new DataOutputStream(currentSerialPort.serialPort.getOutputStream()); 530 } 531 532 533 /** 534 * {@inheritDoc} 535 */ 536 @Override 537 final public void configureBaudRate(String rate) { 538 mBaudRate = rate; 539 } 540 541 /** 542 * {@inheritDoc} 543 */ 544 @Override 545 final public void configureBaudRateFromNumber(String indexString) { 546 int baudNum; 547 int index = 0; 548 final String[] rates = validBaudRates(); 549 final int[] numbers = validBaudNumbers(); 550 if ((numbers == null) || (numbers.length == 0)) { // simulators return null TODO for SpotBugs make that into an empty array 551 mBaudRate = null; 552 log.debug("no serial port speed values received (OK for simulator)"); 553 return; 554 } 555 if (numbers.length != rates.length) { 556 mBaudRate = null; 557 log.error("arrays wrong length in currentBaudNumber: {}, {}", numbers.length, rates.length); 558 return; 559 } 560 if (indexString.isEmpty()) { 561 mBaudRate = null; // represents "(none)" 562 log.debug("empty baud rate received"); 563 return; 564 } 565 try { 566 // since 4.16 first try to convert loaded value directly to integer 567 baudNum = Integer.parseInt(indexString); // new storage format, will throw ex on old format 568 log.debug("new profile format port speed value"); 569 } catch (NumberFormatException ex) { 570 // old pre 4.15.8 format is i18n string including thousand separator and whatever suffix like "18,600 bps (J1)" 571 log.warn("old profile format port speed value converted"); 572 // filter only numerical characters from indexString 573 StringBuilder baudNumber = new StringBuilder(); 574 boolean digitSeen = false; 575 for (int n = 0; n < indexString.length(); n++) { 576 if (Character.isDigit(indexString.charAt(n))) { 577 digitSeen = true; 578 baudNumber.append(indexString.charAt(n)); 579 } else if ((indexString.charAt(n) == ' ') && digitSeen) { 580 break; // break on first space char encountered after at least 1 digit was found 581 } 582 } 583 if (baudNumber.toString().equals("")) { // no number found in indexString e.g. "(automatic)" 584 baudNum = 0; 585 } else { 586 try { 587 baudNum = Integer.parseInt(baudNumber.toString()); 588 } catch (NumberFormatException e2) { 589 mBaudRate = null; // represents "(none)" 590 log.error("error in filtering old profile format port speed value"); 591 return; 592 } 593 log.debug("old format baud number: {}", indexString); 594 } 595 } 596 // fetch baud rate description from validBaudRates[] array copy and set 597 for (int i = 0; i < numbers.length; i++) { 598 if (numbers[i] == baudNum) { 599 index = i; 600 log.debug("found new format baud value at index {}", i); 601 break; 602 } 603 } 604 mBaudRate = validBaudRates()[index]; 605 log.debug("mBaudRate set to: {}", mBaudRate); 606 } 607 608 /** 609 * {@inheritDoc} 610 * Invalid indexes are ignored. 611 */ 612 @Override 613 final public void configureBaudRateFromIndex(int index) { 614 if (validBaudRates().length > index && index > -1 ) { 615 mBaudRate = validBaudRates()[index]; 616 log.debug("mBaudRate set by index to: {}", mBaudRate); 617 } else { 618 // expected for simulators extending serialPortAdapter, mBaudRate already null 619 log.debug("no baud rate index {} in array size {}", index, validBaudRates().length); 620 } 621 } 622 623 protected String mBaudRate = null; 624 625 @Override 626 public int defaultBaudIndex() { 627 return -1; 628 } 629 630 /** 631 * {@inheritDoc} 632 */ 633 @Override 634 public String getCurrentBaudRate() { 635 if (mBaudRate == null) { 636 return ""; 637 } 638 return mBaudRate; 639 } 640 641 /** 642 * {@inheritDoc} 643 */ 644 @Override 645 final public String getCurrentBaudNumber() { 646 int[] numbers = validBaudNumbers(); 647 String[] rates = validBaudRates(); 648 if (numbers == null || rates == null || numbers.length != rates.length) { // entries in arrays should correspond 649 return ""; 650 } 651 String baudNumString = ""; 652 // first try to find the configured baud rate value 653 if (mBaudRate != null) { 654 for (int i = 0; i < numbers.length; i++) { 655 if (rates[i].equals(mBaudRate)) { 656 baudNumString = Integer.toString(numbers[i]); 657 break; 658 } 659 } 660 } else if (defaultBaudIndex() > -1) { 661 // use default 662 baudNumString = Integer.toString(numbers[defaultBaudIndex()]); 663 log.debug("using default port speed {}", baudNumString); 664 } 665 log.debug("mBaudRate = {}, matched to string {}", mBaudRate, baudNumString); 666 return baudNumString; 667 } 668 669 @Override 670 final public int getCurrentBaudIndex() { 671 if (mBaudRate != null) { 672 String[] rates = validBaudRates(); 673 // find the configured baud rate value 674 for (int i = 0; i < rates.length; i++) { 675 if (rates[i].equals(mBaudRate)) { 676 return i; 677 } 678 } 679 } 680 return defaultBaudIndex(); // default index or -1 if port speed not supported 681 } 682 683 /** 684 * {@inheritDoc} 685 */ 686 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "PZLA_PREFER_ZERO_LENGTH_ARRAYS", 687 justification = "null signals incorrect implementation of portcontroller") 688 @Override 689 public String[] validBaudRates() { 690 log.error("default validBaudRates implementation should not be used", new Exception()); 691 return null; 692 } 693 694 /** 695 * {@inheritDoc} 696 */ 697 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "PZLA_PREFER_ZERO_LENGTH_ARRAYS", 698 justification = "null signals incorrect implementation of portcontroller") 699 @Override 700 public int[] validBaudNumbers() { 701 log.error("default validBaudNumbers implementation should not be used", new Exception()); 702 return null; 703 } 704 705 /** 706 * Convert a baud rate I18N String to an int number, e.g. "9,600 baud" to 9600. 707 * <p> 708 * Uses the validBaudNumbers() and validBaudRates() methods to do this. 709 * 710 * @param currentBaudRate a rate from validBaudRates() 711 * @return baudrate as integer if available and matching first digits in currentBaudRate, 712 * 0 if baudrate not supported by this adapter, 713 * -1 if no match (configuration system should prevent this) 714 */ 715 final public int currentBaudNumber(String currentBaudRate) { 716 String[] rates = validBaudRates(); 717 int[] numbers = validBaudNumbers(); 718 719 // return if arrays invalid 720 if (numbers == null) { 721 log.error("numbers array null in currentBaudNumber()"); 722 return -1; 723 } 724 if (rates == null) { 725 log.error("rates array null in currentBaudNumber()"); 726 return -1; 727 } 728 if (numbers.length != rates.length) { 729 log.error("arrays are of different length in currentBaudNumber: {} vs {}", numbers.length, rates.length); 730 return -1; 731 } 732 if (numbers.length < 1) { 733 log.warn("baudrate is not supported by adapter"); 734 return 0; 735 } 736 // find the baud rate value 737 for (int i = 0; i < numbers.length; i++) { 738 if (rates[i].equals(currentBaudRate)) { 739 return numbers[i]; 740 } 741 } 742 743 // no match 744 log.error("no match to ({}) in currentBaudNumber", currentBaudRate); 745 return -1; 746 } 747 748 /** 749 * {@inheritDoc} 750 * Each serial port adapter should handle this and it should be abstract. 751 */ 752 @Override 753 protected void closeConnection(){} 754 755 /** 756 * Re-setup the connection. 757 * Called when the physical connection has reconnected and can be linked to 758 * this connection. 759 * Each port adapter should handle this and it should be abstract. 760 */ 761 @Override 762 protected void resetupConnection(){} 763 764 765 public static class SerialPort { 766 767 public static final int LISTENING_EVENT_DATA_AVAILABLE = 768 com.fazecast.jSerialComm.SerialPort.LISTENING_EVENT_DATA_AVAILABLE; 769 770 public static final int ONE_STOP_BIT = com.fazecast.jSerialComm.SerialPort.ONE_STOP_BIT; 771 public static final int NO_PARITY = com.fazecast.jSerialComm.SerialPort.NO_PARITY; 772 773 private final com.fazecast.jSerialComm.SerialPort serialPort; 774 775 private SerialPort(com.fazecast.jSerialComm.SerialPort serialPort) { 776 this.serialPort = serialPort; 777 } 778 779 public void addDataListener(SerialPortDataListener listener) { 780 this.serialPort.addDataListener(new com.fazecast.jSerialComm.SerialPortDataListener() { 781 @Override 782 public int getListeningEvents() { 783 return listener.getListeningEvents(); 784 } 785 786 @Override 787 public void serialEvent(com.fazecast.jSerialComm.SerialPortEvent event) { 788 listener.serialEvent(new SerialPortEvent(event)); 789 } 790 }); 791 } 792 793 public InputStream getInputStream() { 794 return this.serialPort.getInputStream(); 795 } 796 797 public OutputStream getOutputStream() { 798 return this.serialPort.getOutputStream(); 799 } 800 801 public void setRTS() { 802 this.serialPort.setRTS(); 803 } 804 805 public void clearRTS() { 806 this.serialPort.clearRTS(); 807 } 808 809 public void setBaudRate(int baudrate) { 810 this.serialPort.setBaudRate(baudrate); 811 } 812 813 public int getBaudRate() { 814 return this.serialPort.getBaudRate(); 815 } 816 817 public void setNumDataBits(int bits) { 818 this.serialPort.setNumDataBits(bits); 819 } 820 821 public void setNumStopBits(int bits) { 822 this.serialPort.setNumStopBits(bits); 823 } 824 825 public void setParity(Parity parity) { 826 serialPort.setParity(parity.getValue()); // constants are defined with values for the specific port class 827 } 828 829 public void setDTR() { 830 this.serialPort.setDTR(); 831 } 832 833 public void clearDTR() { 834 this.serialPort.clearDTR(); 835 } 836 837 public boolean getDTR() { 838 return this.serialPort.getDTR(); 839 } 840 841 public boolean getRTS() { 842 return this.serialPort.getRTS(); 843 } 844 845 public boolean getDSR() { 846 return this.serialPort.getDSR(); 847 } 848 849 public boolean getCTS() { 850 return this.serialPort.getCTS(); 851 } 852 853 public boolean getDCD() { 854 return this.serialPort.getDCD(); 855 } 856 857 public boolean getRI() { 858 return this.serialPort.getRI(); 859 } 860 861 /** 862 * Configure the flow control settings. Keep this in synch with the 863 * FlowControl enum. 864 * 865 * @param flow set which kind of flow control to use 866 */ 867 public final void setFlowControl(FlowControl flow) { 868 boolean result = true; 869 870 if (null == flow) { 871 log.error("Invalid null FlowControl enum member"); 872 } else switch (flow) { 873 case RTSCTS: 874 result = serialPort.setFlowControl(com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_RTS_ENABLED 875 | com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_CTS_ENABLED ); 876 break; 877 case XONXOFF: 878 result = serialPort.setFlowControl(com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_XONXOFF_IN_ENABLED 879 | com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_XONXOFF_OUT_ENABLED); 880 break; 881 case NONE: 882 result = serialPort.setFlowControl(com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_DISABLED); 883 break; 884 default: 885 log.error("Invalid FlowControl enum member: {}", flow); 886 break; 887 } 888 889 if (!result) log.error("Port did not accept flow control setting {}", flow); 890 } 891 892 public void setBreak() { 893 this.serialPort.setBreak(); 894 } 895 896 public void clearBreak() { 897 this.serialPort.clearBreak(); 898 } 899 900 public void closePort() { 901 this.serialPort.closePort(); 902 } 903 904 public String getDescriptivePortName() { 905 return this.serialPort.getDescriptivePortName(); 906 } 907 908 @Override 909 public String toString() { 910 return this.serialPort.toString(); 911 } 912 913 } 914 915 916 public static interface SerialPortDataListener { 917 918 void serialEvent(SerialPortEvent serialPortEvent); 919 920 public int getListeningEvents(); 921 922 } 923 924 public static class SerialPortEvent { 925 926 private final com.fazecast.jSerialComm.SerialPortEvent event; 927 928 private SerialPortEvent(com.fazecast.jSerialComm.SerialPortEvent event) { 929 this.event = event; 930 } 931 932 public int getEventType() { 933 return event.getEventType(); 934 } 935 } 936 937 938 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractSerialPortController.class); 939 940}