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