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