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}