001/**
002 *
003 */
004package org.openimaj.hardware.serial;
005
006import java.io.IOException;
007import java.io.InputStream;
008import java.io.OutputStream;
009import java.util.ArrayList;
010import java.util.HashSet;
011import java.util.List;
012
013import jssc.SerialPort;
014import jssc.SerialPortException;
015import jssc.SerialPortList;
016import jssc.SerialPortTimeoutException;
017
018/**
019 * Serial device driver. Uses RXTX library underneath. The native parts of the
020 * RXTX library are published to the Maven repository as a JAR and are extracted
021 * and the java.library.path property is flushed and reset.
022 *
023 * @author David Dupplaw (dpd@ecs.soton.ac.uk)
024 *
025 * @created 12 Jul 2011
026 */
027public class SerialDevice implements SerialDataListener
028{
029        /** The RXTX serial port we'll be reading from */
030        private SerialPort serialPort = null;
031
032        /** Listeners for data events coming from the serial port */
033        private List<SerialDataListener> listeners = new ArrayList<SerialDataListener>();
034
035        /** The regular expression used to split incoming data for the listeners */
036        private String regex = "\n";
037
038        /** The input stream for the port */
039        private InputStream inputStream = null;
040
041        /** The output stream for the port */
042        private OutputStream outputStream = null;
043
044        /** The serial reader used to buffer and parse incoming data */
045        private SerialReader serialReader = null;
046
047        /** The parser being used to parse incoming data */
048        private RegExParser regexParser = null;
049
050        /**
051         * Constructor that takes the port name to connect to and the rate at which
052         * to connect. The data rate will be set to 19,200 with 8 data bits, 1 stop
053         * bit and no parity.
054         *
055         * @param portName
056         *            The port name to connect to.
057         * @throws Exception
058         */
059        public SerialDevice(String portName) throws Exception
060        {
061                this(portName, 4800, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
062        }
063
064        /**
065         * Complete constructor that takes all the information required to connect
066         * to a port.
067         *
068         * @param portName
069         *            The port name to connect to.
070         * @param dataRate
071         *            The data rate to read from the port.
072         * @param dataBits
073         *            The number of data bits
074         * @param stopBits
075         *            The number of stop bits
076         * @param parity
077         *            The bit parity
078         * @throws Exception
079         */
080        public SerialDevice(String portName, int dataRate, int dataBits, int stopBits, int parity)
081                        throws Exception
082        {
083                // Set the serial port information
084                serialPort = new SerialPort(portName);
085                serialPort.openPort();
086                serialPort.setParams(dataRate, dataBits, stopBits, parity);
087
088                // Get the input and output streams from and to the serial port.
089                outputStream = new OutputStream() {
090                        @Override
091                        public void write(int b) throws IOException {
092                                try {
093                                        serialPort.writeByte((byte) b);
094                                } catch (final SerialPortException e) {
095                                        throw new IOException(e);
096                                }
097                        }
098
099                        @Override
100                        public void write(byte[] b) throws IOException {
101                                try {
102                                        serialPort.writeBytes(b);
103                                } catch (final SerialPortException e) {
104                                        throw new IOException(e);
105                                }
106                        }
107                };
108                inputStream = new InputStream() {
109                        @Override
110                        public int read() throws IOException {
111                                while (true) {
112                                        try {
113                                                if (!serialPort.isOpened())
114                                                        return -1;
115
116                                                return serialPort.readBytes(1, 100)[0];
117                                        } catch (final SerialPortTimeoutException e) {
118                                                // ignore and try again
119                                        } catch (final SerialPortException e) {
120                                                if (e.getMessage().contains("Port not opened"))
121                                                        return -1;
122                                                throw new IOException(e);
123                                        }
124                                }
125                        }
126                };
127
128                // Set up our data listener
129                regexParser = new RegExParser(regex);
130                serialReader = new SerialReader(inputStream, regexParser);
131                serialReader.addSerialDataListener(this);
132                serialPort.addEventListener(serialReader, SerialPort.MASK_RXCHAR);
133        }
134
135        /**
136         * {@inheritDoc}
137         *
138         * @see java.lang.Object#finalize()
139         */
140        @Override
141        protected void finalize() throws Throwable
142        {
143                if (serialPort.isOpened()) {
144                        serialReader.close();
145                        serialPort.removeEventListener();
146                        serialPort.closePort();
147                }
148                super.finalize();
149        }
150
151        /**
152         * Close the connection to the serial port.
153         *
154         * @throws IOException
155         */
156        public void close() throws IOException
157        {
158                try {
159                        serialReader.close();
160                        serialPort.removeEventListener();
161                        serialPort.closePort();
162                } catch (final SerialPortException e) {
163                        throw new IOException(e);
164                }
165        }
166
167        /**
168         * Add the given {@link SerialDataListener} to the listener list.
169         *
170         * @param sdl
171         *            The {@link SerialDataListener} to add.
172         */
173        public void addSerialDataListener(SerialDataListener sdl)
174        {
175                listeners.add(sdl);
176        }
177
178        /**
179         * Remove the given {@link SerialDataListener} from the listener list
180         *
181         * @param sdl
182         *            The {@link SerialDataListener} to remove.
183         */
184        public void removeSerialDataListener(SerialDataListener sdl)
185        {
186                listeners.remove(sdl);
187        }
188
189        /**
190         * Fires the serial data event when data is received on the port.
191         *
192         * @param data
193         *            The data that was received
194         */
195        protected void fireSerialDataEvent(String data)
196        {
197                for (final SerialDataListener listener : listeners)
198                        listener.dataReceived(data);
199        }
200
201        /**
202         * Returns the regular expression being used to split incoming strings.
203         *
204         * @return the regular expression being used to split incoming strings.
205         */
206        public String getRegex()
207        {
208                return regex;
209        }
210
211        /**
212         * Set the regular expression to use to split incoming strings.
213         *
214         * @param regex
215         *            the regex to split incoming strings
216         */
217        public void setRegex(String regex)
218        {
219                this.regex = regex;
220                this.regexParser.setRegEx(regex);
221        }
222
223        /**
224         * Returns the input stream for this device.
225         *
226         * @return the input stream
227         */
228        public InputStream getInputStream()
229        {
230                return inputStream;
231        }
232
233        /**
234         * Returns the output stream for this device.
235         *
236         * @return the output stream
237         */
238        public OutputStream getOutputStream()
239        {
240                return outputStream;
241        }
242
243        /**
244         * {@inheritDoc}
245         *
246         * @see org.openimaj.hardware.serial.SerialDataListener#dataReceived(java.lang.String)
247         */
248        @Override
249        public void dataReceived(String data)
250        {
251                fireSerialDataEvent(data);
252        }
253
254        /**
255         * @return A HashSet containing the identifier for all serial ports
256         */
257        public static HashSet<String> getSerialPorts()
258        {
259                final HashSet<String> ports = new HashSet<String>();
260                for (final String s : SerialPortList.getPortNames()) {
261                        ports.add(s);
262                }
263                return ports;
264        }
265}