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}