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