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