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