001/**
002 * Copyright (c) 2014 Digi International Inc.,
003 * All rights not expressly granted are reserved.
004 *
005 * This Source Code Form is subject to the terms of the Mozilla Public
006 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
007 * You can obtain one at http://mozilla.org/MPL/2.0/.
008 *
009 * Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
010 * =======================================================================
011 */
012package com.digi.xbee.api.connection.serial;
013
014import gnu.io.CommPortIdentifier;
015import gnu.io.CommPortOwnershipListener;
016import gnu.io.NoSuchPortException;
017import gnu.io.PortInUseException;
018import gnu.io.RXTXPort;
019import gnu.io.SerialPortEvent;
020import gnu.io.SerialPortEventListener;
021import gnu.io.UnsupportedCommOperationException;
022
023import java.io.IOException;
024import java.io.InputStream;
025import java.io.OutputStream;
026import java.util.ArrayList;
027import java.util.Enumeration;
028import java.util.TooManyListenersException;
029
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import com.digi.xbee.api.exceptions.ConnectionException;
034import com.digi.xbee.api.exceptions.InterfaceInUseException;
035import com.digi.xbee.api.exceptions.InvalidConfigurationException;
036import com.digi.xbee.api.exceptions.InvalidInterfaceException;
037import com.digi.xbee.api.exceptions.PermissionDeniedException;
038
039/**
040 * This class represents a serial port using the RxTx library to communicate
041 * with it.
042 */
043public class SerialPortRxTx extends AbstractSerialPort implements SerialPortEventListener, CommPortOwnershipListener {
044        
045        // Variables.
046        private final Object lock = new Object();
047        
048        private RXTXPort serialPort;
049        
050        private InputStream inputStream;
051        
052        private OutputStream outputStream;
053        
054        private Thread breakThread;
055        
056        private boolean breakEnabled = false;
057        
058        private CommPortIdentifier portIdentifier = null;
059        
060        private Logger logger;
061        
062        /**
063         * Class constructor. Instances a new {@code SerialPortRxTx} object using
064         * the given parameters.
065         * 
066         * @param port Serial port name to use.
067         * @param parameters Serial port parameters.
068         * 
069         * @throws NullPointerException if {@code port == null} or
070         *                              if {@code parameters == null}.
071         * 
072         * @see #SerialPortRxTx(String, int)
073         * @see #SerialPortRxTx(String, int, int)
074         * @see #SerialPortRxTx(String, SerialPortParameters, int)
075         * @see SerialPortParameters
076         */
077        public SerialPortRxTx(String port, SerialPortParameters parameters) {
078                this(port, parameters, DEFAULT_PORT_TIMEOUT);
079        }
080        
081        /**
082         * Class constructor. Instances a new {@code SerialPortRxTx} object using
083         * the given parameters.
084         * 
085         * @param port Serial port name to use.
086         * @param parameters Serial port parameters.
087         * @param receiveTimeout Serial port receive timeout in milliseconds.
088         * 
089         * @throws IllegalArgumentException if {@code receiveTimeout < 0}.
090         * @throws NullPointerException if {@code port == null} or
091         *                              if {@code parameters == null}.
092         * 
093         * @see #SerialPortRxTx(String, int)
094         * @see #SerialPortRxTx(String, int, int)
095         * @see #SerialPortRxTx(String, SerialPortParameters)
096         * @see SerialPortParameters
097         */
098        public SerialPortRxTx(String port, SerialPortParameters parameters, int receiveTimeout) {
099                super(port, parameters, receiveTimeout);
100                this.logger = LoggerFactory.getLogger(SerialPortRxTx.class);
101        }
102        
103        /**
104         * Class constructor. Instances a new {@code SerialPortRxTx} object using
105         * the given parameters.
106         * 
107         * @param port Serial port name to use.
108         * @param baudRate Serial port baud rate, the rest of parameters will be 
109         *                 set by default.
110         * 
111         * @throws NullPointerException if {@code port == null}.
112         * 
113         * @see #DEFAULT_DATA_BITS
114         * @see #DEFAULT_FLOW_CONTROL
115         * @see #DEFAULT_PARITY
116         * @see #DEFAULT_STOP_BITS
117         * @see #DEFAULT_PORT_TIMEOUT
118         * @see #SerialPortRxTx(String, int, int)
119         * @see #SerialPortRxTx(String, SerialPortParameters)
120         * @see #SerialPortRxTx(String, SerialPortParameters, int)
121         * @see SerialPortParameters
122         */
123        public SerialPortRxTx(String port, int baudRate) {
124                this(port, baudRate, DEFAULT_PORT_TIMEOUT);
125        }
126        
127        /**
128         * Class constructor. Instances a new {@code SerialPortRxTx} object using
129         * the given parameters.
130         * 
131         * @param port Serial port name to use.
132         * @param baudRate Serial port baud rate, the rest of parameters will be 
133         *                 set by default.
134         * @param receiveTimeout Serial port receive timeout in milliseconds.
135         * 
136         * @throws IllegalArgumentException if {@code receiveTimeout < 0}.
137         * @throws NullPointerException if {@code port == null}.
138         * 
139         * @see #DEFAULT_DATA_BITS
140         * @see #DEFAULT_FLOW_CONTROL
141         * @see #DEFAULT_PARITY
142         * @see #DEFAULT_STOP_BITS
143         * @see #SerialPortRxTx(String, int)
144         * @see #SerialPortRxTx(String, SerialPortParameters)
145         * @see #SerialPortRxTx(String, SerialPortParameters, int)
146         * @see SerialPortParameters
147         */
148        public SerialPortRxTx(String port, int baudRate, int receiveTimeout) {
149                super(port, baudRate, receiveTimeout);
150                this.logger = LoggerFactory.getLogger(SerialPortRxTx.class);
151        }
152        
153        /*
154         * (non-Javadoc)
155         * @see com.digi.xbee.api.connection.IConnectionInterface#open()
156         */
157        @Override
158        public void open() throws InterfaceInUseException, InvalidInterfaceException, InvalidConfigurationException, PermissionDeniedException {
159                // Check that the given serial port exists.
160                try {
161                        portIdentifier = CommPortIdentifier.getPortIdentifier(port);
162                } catch (NoSuchPortException e) {
163                        throw new InvalidInterfaceException("No such port: " + port, e);
164                }
165                try {
166                        // Get the serial port.
167                        serialPort = (RXTXPort)portIdentifier.open(PORT_ALIAS + " " + port, receiveTimeout);
168                        // Set port as connected.
169                        connectionOpen = true;
170                        // Configure the port.
171                        if (parameters == null)
172                                parameters = new SerialPortParameters(baudRate, DEFAULT_DATA_BITS, DEFAULT_STOP_BITS, DEFAULT_PARITY, DEFAULT_FLOW_CONTROL);
173                        serialPort.setSerialPortParams(baudRate, parameters.dataBits, parameters.stopBits, parameters.parity);
174                        serialPort.setFlowControlMode(parameters.flowControl);
175                        
176                        serialPort.enableReceiveTimeout(receiveTimeout);
177                        
178                        // Set the port ownership.
179                        portIdentifier.addPortOwnershipListener(this);
180                        
181                        // Initialize input and output streams before setting the listener.
182                        inputStream = serialPort.getInputStream();
183                        outputStream = serialPort.getOutputStream();
184                        // Activate data received event.
185                        serialPort.notifyOnDataAvailable(true);
186                        // Register serial port event listener to be notified when data is available.
187                        serialPort.addEventListener(this);
188                } catch (PortInUseException e) {
189                        throw new InterfaceInUseException("Port " + port + " is already in use by other application(s)", e);
190                } catch (UnsupportedCommOperationException e) {
191                        throw new InvalidConfigurationException(e.getMessage(), e);
192                } catch (TooManyListenersException e) {
193                        throw new InvalidConfigurationException(e.getMessage(), e);
194                }
195        }
196        
197        /*
198         * (non-Javadoc)
199         * @see com.digi.xbee.api.connection.IConnectionInterface#close()
200         */
201        @Override
202        public void close() {
203                try {
204                        if (inputStream != null) {
205                                inputStream.close();
206                                inputStream = null;
207                        }
208                        if (outputStream != null) {
209                                outputStream.close();
210                                outputStream = null;
211                        }
212                } catch (IOException e) {
213                        logger.error(e.getMessage(), e);
214                }
215                synchronized (lock) {
216                        if (serialPort != null) {
217                                try {
218                                        serialPort.notifyOnDataAvailable(false);
219                                        serialPort.removeEventListener();
220                                        portIdentifier.removePortOwnershipListener(this);
221                                        serialPort.close();
222                                        serialPort = null;
223                                        connectionOpen = false;
224                                } catch (Exception e) { }
225                        }
226                }
227        }
228        
229        /*
230         * (non-Javadoc)
231         * @see gnu.io.SerialPortEventListener#serialEvent(gnu.io.SerialPortEvent)
232         */
233        @Override
234        public void serialEvent(SerialPortEvent event) {
235                // Listen only to data available event.
236                switch (event.getEventType()) {
237                case SerialPortEvent.DATA_AVAILABLE:
238                        // Check if serial device has been disconnected or not.
239                        try {
240                                getInputStream().available();
241                        } catch (Exception e) {
242                                // Serial device has been disconnected.
243                                close();
244                                synchronized (this) {
245                                        this.notify();
246                                }
247                                break;
248                        }
249                        // Notify data is available by waking up the read thread.
250                        try {
251                                if (getInputStream().available() > 0) {
252                                        synchronized (this) {
253                                                this.notify();
254                                        }
255                                }
256                        } catch (Exception e) {
257                                logger.error(e.getMessage(), e);
258                        }
259                        break;
260                }
261        }
262        
263        /*
264         * (non-Javadoc)
265         * @see java.lang.Object#toString()
266         */
267        @Override
268        public String toString() {
269                return super.toString();
270        }
271        
272        /*
273         * (non-Javadoc)
274         * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#setBreak(boolean)
275         */
276        @Override
277        public void setBreak(boolean enabled) {
278                breakEnabled = enabled;
279                if(breakEnabled){
280                        if (breakThread == null) {
281                                breakThread = new Thread() {
282                                        public void run() {
283                                                while (breakEnabled && serialPort != null)
284                                                        serialPort.sendBreak(100);
285                                        };
286                                };
287                                breakThread.start();
288                        }
289                } else {
290                        if (breakThread != null)
291                                breakThread.interrupt();
292                        breakThread = null;
293                        serialPort.sendBreak(0);
294                }
295        }
296        
297        /*
298         * (non-Javadoc)
299         * @see com.digi.xbee.api.connection.IConnectionInterface#getInputStream()
300         */
301        @Override
302        public InputStream getInputStream() {
303                return inputStream;
304        }
305        
306        /*
307         * (non-Javadoc)
308         * @see com.digi.xbee.api.connection.IConnectionInterface#getOutputStream()
309         */
310        @Override
311        public OutputStream getOutputStream() {
312                return outputStream;
313        }
314        
315        /*
316         * (non-Javadoc)
317         * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#setReadTimeout(int)
318         */
319        @Override
320        public void setReadTimeout(int timeout) {
321                serialPort.disableReceiveTimeout();
322                serialPort.enableReceiveTimeout(timeout);
323        }
324        
325        /*
326         * (non-Javadoc)
327         * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#getReadTimeout()
328         */
329        @Override
330        public int getReadTimeout() {
331                return serialPort.getReceiveTimeout();
332        }
333        
334        /*
335         * (non-Javadoc)
336         * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#setDTR(boolean)
337         */
338        @Override
339        public void setDTR(boolean state) {
340                serialPort.setDTR(state);
341        }
342        
343        /*
344         * (non-Javadoc)
345         * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#setRTS(boolean)
346         */
347        @Override
348        public void setRTS(boolean state) {
349                serialPort.setRTS(state);
350        }
351        
352        
353        /*
354         * (non-Javadoc)
355         * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#setPortParameters(int, int, int, int, int)
356         */
357        @Override
358        public void setPortParameters(int baudRate, int dataBits, int stopBits,
359                        int parity, int flowControl) throws InvalidConfigurationException, ConnectionException {
360                parameters = new SerialPortParameters(baudRate, dataBits, stopBits, parity, flowControl);
361                
362                if (serialPort != null) {
363                        try {
364                                serialPort.setSerialPortParams(baudRate, dataBits, stopBits, parity);
365                                serialPort.setFlowControlMode(flowControl);
366                        } catch (UnsupportedCommOperationException e) {
367                                throw new InvalidConfigurationException(e.getMessage(), e);
368                        }
369                }
370        }
371        
372        /*
373         * (non-Javadoc)
374         * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#sendBreak(int)
375         */
376        @Override
377        public void sendBreak(int duration) {
378                if (serialPort != null)
379                        serialPort.sendBreak(duration);
380        }
381        
382        /*
383         * (non-Javadoc)
384         * @see gnu.io.CommPortOwnershipListener#ownershipChange(int)
385         */
386        @Override
387        public void ownershipChange(int nType) {
388                switch (nType) {
389                case CommPortOwnershipListener.PORT_OWNERSHIP_REQUESTED:
390                        onSerialOwnershipRequested(null);
391                        break;
392                }
393        }
394        
395        /**
396         * Releases the port on any ownership request in the same application 
397         * instance.
398         * 
399         * @param data The port requester.
400         */
401        private void onSerialOwnershipRequested(Object data) {
402                try {
403                        throw new Exception();
404                } catch (Exception e) {
405                        StackTraceElement[] elems = e.getStackTrace();
406                        String requester = elems[elems.length - 4].getClassName();
407                        synchronized (this) {
408                                this.notify();
409                        }
410                        close();
411                        String myPackage = this.getClass().getPackage().getName();
412                        if (requester.startsWith(myPackage))
413                                requester = "another AT connection";
414                        logger.warn("Connection for port {} canceled due to ownership request from {}.", port, requester);
415                }
416        }
417        
418        /**
419         * Retrieves the list of available serial ports in the system.
420         * 
421         * @return List of available serial ports.
422         * 
423         * @see #listSerialPortsInfo()
424         */
425        public static String[] listSerialPorts() {
426                ArrayList<String> serialPorts = new ArrayList<String>();
427                
428                @SuppressWarnings("unchecked")
429                Enumeration<CommPortIdentifier> comPorts = CommPortIdentifier.getPortIdentifiers();
430                if (comPorts == null)
431                        return serialPorts.toArray(new String[serialPorts.size()]);
432                
433                while (comPorts.hasMoreElements()) {
434                        CommPortIdentifier identifier = (CommPortIdentifier)comPorts.nextElement();
435                        if (identifier == null)
436                                continue;
437                        String strName = identifier.getName();
438                        serialPorts.add(strName);
439                }
440                return serialPorts.toArray(new String[serialPorts.size()]);
441        }
442        
443        /**
444         * Retrieves the list of available serial ports with their information.
445         * 
446         * @return List of available serial ports with their information.
447         * 
448         * @see #listSerialPorts()
449         * @see SerialPortInfo
450         */
451        public static ArrayList<SerialPortInfo> listSerialPortsInfo() {
452                ArrayList<SerialPortInfo> ports = new ArrayList<SerialPortInfo>();
453                
454                @SuppressWarnings("unchecked")
455                Enumeration<CommPortIdentifier> comPorts = CommPortIdentifier.getPortIdentifiers();
456                if (comPorts == null)
457                        return ports;
458                
459                while (comPorts.hasMoreElements()) {
460                        CommPortIdentifier identifier = (CommPortIdentifier)comPorts.nextElement();
461                        if (identifier == null)
462                                continue;
463                        ports.add(new SerialPortInfo(identifier.getName()));
464                }
465                return ports;
466        }
467        
468        /*
469         * (non-Javadoc)
470         * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#isCTS()
471         */
472        @Override
473        public boolean isCTS() {
474                return serialPort.isCTS();
475        }
476        
477        /*
478         * (non-Javadoc)
479         * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#isDSR()
480         */
481        @Override
482        public boolean isDSR() {
483                return serialPort.isDSR();
484        }
485        
486        /*
487         * (non-Javadoc)
488         * @see com.digi.xbee.api.connection.serial.AbstractSerialPort#isCD()
489         */
490        @Override
491        public boolean isCD() {
492                return serialPort.isCD();
493        }
494}