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