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