001/**
002 *
003 */
004package org.openimaj.hardware.serial;
005
006import gnu.trove.list.TByteList;
007import gnu.trove.list.array.TByteArrayList;
008
009import java.io.Closeable;
010import java.io.IOException;
011import java.io.InputStream;
012import java.util.ArrayList;
013import java.util.List;
014
015import jssc.SerialPortEvent;
016import jssc.SerialPortEventListener;
017
018/**
019 * An event listener that receives data from the serial port, buffers the data,
020 * parses the data then calls the listeners for every sentence parsed.
021 *
022 * @author David Dupplaw (dpd@ecs.soton.ac.uk)
023 *
024 * @created 12 Jul 2011
025 */
026public class SerialReader implements SerialPortEventListener, Closeable
027{
028        /** The input stream from the serial device */
029        private InputStream inputStream = null;
030
031        /** The parser being used for incoming data */
032        private SerialDataParser parser = null;
033
034        /** We use trove to buffer the incoming data */
035        private TByteList buffer = new TByteArrayList();
036
037        /** The maximum size of a buffer before parsing data */
038        private int maxSize = 256;
039
040        /** Listeners */
041        private List<SerialDataListener> listeners = new ArrayList<SerialDataListener>();
042
043        private boolean closed = false;
044
045        /**
046         * Default constructor
047         *
048         * @param in
049         * @param parser
050         */
051        public SerialReader(InputStream in, SerialDataParser parser)
052        {
053                this.inputStream = in;
054                this.parser = parser;
055        }
056
057        /**
058         * {@inheritDoc}
059         */
060        @Override
061        public void serialEvent(SerialPortEvent event)
062        {
063                if (closed)
064                        return;
065
066                if (event != null && !event.isRXCHAR())
067                        return;
068
069                try
070                {
071                        // Reads all the data from the serial port event (upto a maximum
072                        // size)
073                        int data = 0;
074                        while (buffer.size() < maxSize && (data = inputStream.read()) > -1)
075                                buffer.add((byte) data);
076
077                        // Parse the data
078                        final String dataString = new String(buffer.toArray(), 0, buffer.size());
079                        final String[] strings = parser.parse(dataString);
080                        final String leftOvers = parser.getLeftOverString();
081
082                        // If we've got to the end of the stream, we'll simply fire the
083                        // events
084                        // for the strings that are parsed and the left overs.
085                        if (data == -1)
086                        {
087                                if (strings.length > 0)
088                                        fireDataReceived(strings);
089                                if (leftOvers.length() > 0)
090                                        fireDataReceived(new String[] { leftOvers });
091                                buffer.clear();
092                        }
093                        else
094                        {
095                                // Keep the left-over parts of the string in the buffer
096                                if (leftOvers != null)
097                                        buffer = buffer.subList(
098                                                        buffer.size() - leftOvers.length(),
099                                                        buffer.size());
100                                else
101                                        buffer.clear();
102
103                                // Let everyone know we have data!
104                                fireDataReceived(strings);
105                        }
106                } catch (final IOException e)
107                {
108                        // FIXME: RuntimeException? Seems a bit harsh.
109                        throw new RuntimeException(e);
110                }
111        }
112
113        /**
114         * Add a serial data listener that will be informed of individual tokens
115         * that are parsed from the parser.
116         *
117         * @param listener
118         *            The listener
119         */
120        public void addSerialDataListener(SerialDataListener listener)
121        {
122                listeners.add(listener);
123        }
124
125        /**
126         * Remove the given listener from this reader.
127         *
128         * @param listener
129         *            The listener
130         */
131        public void removeSerialDataListener(SerialDataListener listener)
132        {
133                listeners.remove(listener);
134        }
135
136        /**
137         * Fire multiple events: one for each parsed string.
138         *
139         * @param strings
140         *            The strings parsed from the parser.
141         */
142        protected void fireDataReceived(String[] strings)
143        {
144                for (final String s : strings)
145                        for (final SerialDataListener listener : listeners)
146                                listener.dataReceived(s);
147        }
148
149        /**
150         * Set the size of the buffer to use. The buffer size must be larger than
151         * any expected data item that you are wanting to parse. If your sentences
152         * can be up to 128 bytes, then the buffer should be at least 128 bytes. It
153         * can be larger.
154         *
155         * @param maxSize
156         *            The size of the buffer to use.
157         */
158        public void setMaxBufferSize(int maxSize)
159        {
160                this.maxSize = maxSize;
161        }
162
163        @Override
164        public void close() throws IOException {
165                this.closed = true;
166        }
167}