001package jmri.jmrix.can.adapters.gridconnect;
002
003import java.io.DataInputStream;
004import java.io.FilterInputStream;
005import java.io.IOException;
006import java.io.InputStream;
007import java.util.concurrent.BlockingQueue;
008import java.util.concurrent.LinkedBlockingQueue;
009
010import jmri.jmrix.ConnectionStatus;
011import jmri.jmrix.can.TrafficController;
012
013/**
014 * Implements SerialPortAdapter for the GridConnect protocol.
015 *
016 * @author Bob Jacobsen Copyright (C) 2001, 2002
017 * @author Andrew Crosland Copyright (C) 2008
018 * @author Balazs Racz Copyright (C) 2017
019 */
020public class GcSerialDriverAdapter extends GcPortController {
021
022    protected FlowControl flowControl = FlowControl.NONE; // disabled to start
023
024    /**
025     * Creates a new CAN GridConnect Network Driver Adapter.
026     */
027    public GcSerialDriverAdapter() {
028        super(new jmri.jmrix.can.CanSystemConnectionMemo());
029        option1Name = "Protocol"; // NOI18N
030        options.put(option1Name, new Option(Bundle.getMessage("ConnectionProtocol"),
031                jmri.jmrix.can.ConfigurationManager.getSystemOptions()));
032        this.manufacturerName = jmri.jmrix.merg.MergConnectionTypeList.MERG;
033        allowConnectionRecovery = true;
034    }
035
036    /**
037     * Creates a new CAN GridConnect Network Driver Adapter.
038     * <p>
039     * Allows for default systemPrefix other than "M".
040     * @param prefix System Prefix.
041     */
042    public GcSerialDriverAdapter(String prefix) {
043        super(new jmri.jmrix.can.CanSystemConnectionMemo(prefix));
044        option1Name = "Protocol"; // NOI18N
045        options.put(option1Name, new Option(Bundle.getMessage("ConnectionProtocol"),
046                jmri.jmrix.can.ConfigurationManager.getSystemOptions()));
047        this.manufacturerName = jmri.jmrix.merg.MergConnectionTypeList.MERG;
048        allowConnectionRecovery = true;
049    }
050
051    /**
052     * Creates a new CAN GridConnect Network Driver Adapter.
053     * <p>
054     * Allows for default systemPrefix other than "M".
055     * @param prefix System Prefix.
056     * @param flow flow control, true for RTS/CTS
057     */
058    public GcSerialDriverAdapter(String prefix, FlowControl flow) {
059        super(new jmri.jmrix.can.CanSystemConnectionMemo(prefix));
060        option1Name = "Protocol"; // NOI18N
061        options.put(option1Name, new Option(Bundle.getMessage("ConnectionProtocol"),
062                jmri.jmrix.can.ConfigurationManager.getSystemOptions()));
063        this.manufacturerName = jmri.jmrix.merg.MergConnectionTypeList.MERG;
064        allowConnectionRecovery = true;
065        flowControl = flow;
066    }
067
068    /**
069     * {@inheritDoc}
070     */
071    @Override
072    public String openPort(String portName, String appName) {
073
074        // get and open the primary port
075        currentSerialPort = activatePort(portName, log);
076        if (currentSerialPort == null) {
077            log.error("failed to connect CAN to {}", portName);
078            return Bundle.getMessage("SerialPortNotFound", portName);
079        }
080        log.info("Connecting CAN to {} {}", portName, currentSerialPort);
081
082        // try to set it for communication via SerialDriver
083        // find the baud rate value, configure comm options
084        int baud = currentBaudNumber(mBaudRate);
085        setBaudRate(currentSerialPort, baud);
086        configureLeads(currentSerialPort, true, true);
087        localSetFlowControl();
088
089        // get and save stream
090        serialStream = currentSerialPort.getInputStream();
091        // this is referenced in several other methods,
092        // so can't easily be removed.
093
094        // report status
095        reportPortStatus(log, portName);
096
097        opened = true;
098
099        return null; // indicates OK return
100    }
101
102    /**
103     * Local set up the flow contro, here to allow override
104     */
105    protected void localSetFlowControl() {
106        setFlowControl(currentSerialPort, flowControl);
107    }
108
109    /**
110     * Set up all of the other objects to operate with a CAN RS adapter
111     * connected to this port.
112     * {@inheritDoc}
113     */
114    @Override
115    public void configure() {
116        // Register the CAN traffic controller being used for this connection
117        //GcTrafficController.instance();
118        TrafficController tc = makeGcTrafficController();
119        this.getSystemConnectionMemo().setTrafficController(tc);
120
121        // Now connect to the traffic controller
122        log.debug("Connecting port");
123        tc.connectPort(this);
124
125        this.getSystemConnectionMemo().setProtocol(getOptionState(option1Name));
126
127        // do central protocol-specific configuration
128        //jmri.jmrix.can.ConfigurationManager.configure(getOptionState(option1Name));
129        this.getSystemConnectionMemo().configureManagers();
130    }
131
132    /**
133     * {@inheritDoc}
134     * Reconnects to Traffic Controller.
135     * Updates connection status.
136     */
137    @Override
138    protected void resetupConnection() {
139        if (!getSystemConnectionMemo().getTrafficController().status()) {
140            getSystemConnectionMemo().getTrafficController().connectPort(this);
141            ConnectionStatus.instance().setConnectionState(getSystemConnectionMemo(),
142                ((getSystemConnectionMemo().getTrafficController().status() && status()) ? ConnectionStatus.CONNECTION_UP : ConnectionStatus.CONNECTION_DOWN));
143        }
144    }
145
146    /**
147     * {@inheritDoc}
148     *
149     * Closes serial streams.
150     */
151    @Override
152    protected void closeConnection(){
153        log.info("Closing connection {}.",getCurrentPortName());
154        try {
155            if (serialStream!=null) {
156                serialStream.close();
157            }
158            serialStream = null;
159            if (bufferedStream!=null) {
160                bufferedStream.close();
161            }
162            bufferedStream = null;
163        }
164        catch ( IOException e ) {
165            log.error("unable to close {}",this.currentSerialPort);
166        }
167        if (currentSerialPort!=null) {
168            currentSerialPort.closePort();
169        }
170        currentSerialPort = null;
171    }
172
173    /**
174     * Make a new GC Traffic Controller.
175     * @return new GridConnect Traffic Controller.
176     */
177    protected GcTrafficController makeGcTrafficController() {
178        return new GcTrafficController();
179    }
180
181    /**
182     * Helper class wrapping the input serial port's InputStream.
183     * <p>
184     * It starts a helper thread at high priority that reads the input serial
185     * port as fast as it can, buffering all incoming data in memory in a queue.
186     * <p>
187     * The queue is unbounded and readers will get the data from the queue.
188     * <p>
189     * This class is thread-safe.
190     */
191    private static class AsyncBufferInputStream extends FilterInputStream {
192
193        // written by close() on another thread
194        private volatile boolean active;
195
196        /**
197         * Create new AsyncBufferInputStream.
198         * @param inputStream Input Stream.
199         * @param portName Port Name.
200         */
201        AsyncBufferInputStream(InputStream inputStream, String portName) {
202            super(inputStream);
203            this.portName = portName;
204            active = true;
205            Thread rt = jmri.util.ThreadingUtil.newThread(this::readThreadBody);
206            rt.setName("GcSerialPort InputBufferThread " + portName);
207            rt.setDaemon(true);
208            rt.setPriority(Thread.MAX_PRIORITY);
209            rt.start();
210        }
211
212        /**
213         * Helper function that tries to perform a read from the underlying port
214         * with a given maximum length.
215         *
216         * @param maxLen how many bytes to request from the port. Setting this to 1
217         *            will apparently block the thread if there are zero bytes
218         *            available.
219         * @return a block of data read, or nullptr if fatal IO errors make
220         *         further use of this port impossible.
221         */
222        private BufferEntry tryRead(int maxLen) {
223            BufferEntry tail = new BufferEntry();
224            try {
225                // read what's available, up to maxLen, but always at least 1
226                int len = Math.max (1, Math.min(maxLen, in.available()));
227                tail.data = new byte[len];
228                tail.len = in.read(tail.data, 0, len);
229                errorCount = 0;
230            } catch (IOException e) {
231                tail.e = e;
232                if (++errorCount > MAX_IO_ERRORS_TO_ABORT) {
233                    log.error("Closing read thread due to too many IO errors", e);
234                    return null;
235                } else {
236                    log.debug("Error reading serial port {}", portName, e);
237                }
238            }
239            return tail;
240        }
241
242        /**
243         * Implementation of the buffering thread.
244         */
245        private void readThreadBody() {
246            BufferEntry tail;
247            while (active) {
248                // Try to read one byte to block the thread.
249                tail = tryRead(1);
250                if (tail == null) {
251                    return;
252                }
253                // End of stream. jSerialComm signals a removed USB serial port
254                // (eg. a CANUSB4 unplugged on Windows) by read() returning -1
255                // rather than by throwing. Retrying would spin this maximum
256                // priority thread on a whole core forever, and the reader
257                // would never find out. Hand the end of stream on to the
258                // reader, so the traffic controller sees the connection fail
259                // and starts recovery, and stop this thread.
260                if (tail.len < 0) {
261                    readAhead.add(tail);
262                    return;
263                }
264                if (tail.len > 0 || tail.e != null) {
265                    readAhead.add(tail);
266                } else {
267                    continue;
268                }
269                // Read as many bytes as we have in large increments. Reading 128 bytes is a good
270                // compromise between throughput (4 gridconnect packets per kernel IO) but not
271                // wasting a lot of memory if less data actually shows up.
272                do {
273                    tail = tryRead(128);
274                    if (tail == null) {
275                        return;
276                    }
277                    if (tail.len < 0) {
278                        readAhead.add(tail); // end of stream, see above
279                        return;
280                    }
281                    if (tail.len > 0 || tail.e != null) {
282                        readAhead.add(tail);
283                    } else {
284                        break;
285                    }
286                } while (true);
287            }
288        }
289
290        /**
291         * We queue objects of this class between the read thread and the actual
292         * read() methods.
293         */
294        private static class BufferEntry {
295
296            // data payload
297            byte[] data;
298            // how many bytes of data are filled in
299            int len = 0;
300            // an exception was caught reading the input stream
301            IOException e = null;
302        }
303
304        /**
305         * {@inheritDoc}
306         */
307        @Override
308        public int read() throws IOException {
309            throw new UnsupportedOperationException();
310        }
311
312        /**
313         * {@inheritDoc}
314         */
315        @Override
316        public int read(byte[] bytes) throws IOException {
317            throw new UnsupportedOperationException();
318        }
319
320        /**
321         * {@inheritDoc}
322         */
323        @Override
324        public synchronized int read(byte[] bytes, int skip, int len) throws IOException {
325            if (skip != 0) {
326                throw new UnsupportedOperationException();
327            }
328            if (head != null && head.len < 0) {
329                return -1; // end of stream already reached, the read thread has stopped
330            }
331            if (head == null || headOfs >= head.len) {
332                try {
333                    head = readAhead.take();
334                } catch (InterruptedException e) {
335                    // Interrupted while waiting, eg. connection closing.
336                    // Must not fall through, as head is either null or the
337                    // previous, already returned, entry, which would be replayed.
338                    Thread.currentThread().interrupt();
339                    throw new java.io.InterruptedIOException("Interrupted waiting for data from " + portName);
340                }
341                if (head.e != null) {
342                    throw head.e;
343                }
344                headOfs = 0;
345                if (head.len < 0) {
346                    return -1;
347                }
348            }
349            int cp = head.len - headOfs;
350            if (cp > len) {
351                cp = len;
352            }
353            System.arraycopy(head.data, headOfs, bytes, 0, cp);
354            headOfs += cp;
355            return cp;
356        }
357
358        private final String portName;
359        // After this many consecutive read attempts resulting in an exception we will terminate
360        // the read thread and return the last exception to the reader.
361        private static final int MAX_IO_ERRORS_TO_ABORT = 10;
362        // Queue holding the buffered data.
363        private final BlockingQueue<BufferEntry> readAhead = new LinkedBlockingQueue<>();
364        // The last entry we got from the queue if there are still bytes we need to return from it.
365        BufferEntry head = null;
366        // Offset of next live byte in head.
367        int headOfs = 0;
368        // How many of the last consecutive read attempts have resulted in an exception.
369        int errorCount = 0;
370
371        @Override
372        public void close() throws IOException {
373            active = false;
374            super.close();
375        }
376    }
377
378    /**
379     * Base class methods for the PortController interface.
380     * {@inheritDoc}
381     */
382    @Override
383    public DataInputStream getInputStream() {
384        if (!opened) {
385            log.error("getInputStream called before load(), stream not available");
386            return null;
387        }
388        synchronized (this) {
389            if (bufferedStream == null) {
390                bufferedStream = new AsyncBufferInputStream(serialStream, currentSerialPort.toString());
391            }
392            return new DataInputStream(bufferedStream);
393        }
394    }
395
396    /**
397     * {@inheritDoc}
398     *
399     * @return array of localized valid baud rates
400     */
401    @Override
402    public String[] validBaudRates() {
403        return new String[]{Bundle.getMessage("Baud57600"),
404                Bundle.getMessage("Baud115200"), Bundle.getMessage("Baud230400"),
405                Bundle.getMessage("Baud250000"), Bundle.getMessage("Baud333333"),
406                Bundle.getMessage("Baud460800")};
407    }
408
409    /**
410     * Get an array of valid baud rates.
411     *
412     * @return valid baud rates
413     */
414    @Override
415    public int[] validBaudNumbers() {
416        return new int[]{57600, 115200, 230400, 250000, 333333, 460800};
417    }
418
419    /**
420     * {@inheritDoc}
421     */
422    @Override
423    public int defaultBaudIndex() {
424        return 0;
425    }
426
427    // private control members
428    InputStream serialStream = null;
429    // Stream wrapper that buffers the input bytes.
430    private InputStream bufferedStream = null;
431
432    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GcSerialDriverAdapter.class);
433
434}