    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 java.io.IOException;
    015
    016import org.slf4j.Logger;
    017import org.slf4j.LoggerFactory;
    018
    019import com.digi.xbee.api.connection.IConnectionInterface;
    020import com.digi.xbee.api.exceptions.ConnectionException;
    021import com.digi.xbee.api.exceptions.InvalidConfigurationException;
    022
    023/**
    024 * Abstract class that provides common functionality to work with serial ports.
    025 */
    026public abstract class AbstractSerialPort implements IConnectionInterface {
    027
    028        // Constants.
    029        /**
    030         * Default receive timeout: {@value} seconds.
    031         *
    032         * <p>When the specified number of milliseconds have elapsed, read will
    033         * return immediately.</p>
    034         */
    035        public static final int DEFAULT_PORT_TIMEOUT = 10;
    036
    037        /**
    038         * Default number of data bits: {@value}.
    039         */
    040        public static final int DEFAULT_DATA_BITS = 8;
    041
    042        /**
    043         * Default number of stop bits: {@value}.
    044         */
    045        public static final int DEFAULT_STOP_BITS = 1;
    046
    047        /**
    048         * Default parity: {@value} (None).
    049         */
    050        public static final int DEFAULT_PARITY = 0;
    051
    052        /**
    053         * Default flow control: {@value} (None).
    054         */
    055        public static final int DEFAULT_FLOW_CONTROL = 0;
    056
    057        protected static final int FLOW_CONTROL_HW = 3;
    058
    059        protected static final String PORT_ALIAS = "Serial Port";
    060
    061        // Variables.
    062        protected String port;
    063
    064        protected int baudRate;
    065        protected int receiveTimeout;
    066
    067        protected SerialPortParameters parameters;
    068
    069        protected boolean connectionOpen = false;
    070
    071        private Logger logger;
    072
    073        /**
    074         * Class constructor. Instantiates a new {@code AbstractSerialPort} object
    075         * with the given parameters.
    076         *
    077         * @param port COM port name to use.
    078         * @param parameters Serial port connection parameters.
    079         *
    080         * @throws NullPointerException if {@code port == null} or
    081         *                              if {@code parameters == null}.
    082         *
    083         * @see #AbstractSerialPort(String, int)
    084         * @see #AbstractSerialPort(String, int, int)
    085         * @see #AbstractSerialPort(String, SerialPortParameters, int)
    086         * @see SerialPortParameters
    087         */
    088        protected AbstractSerialPort(String port, SerialPortParameters parameters) {
    089                this(port, parameters, DEFAULT_PORT_TIMEOUT);
    090        }
    091
    092        /**
    093         * Class constructor. Instantiates a new {@code AbstractSerialPort} object
    094         * with the given parameters.
    095         *
    096         * @param port COM port name to use.
    097         * @param baudRate Serial connection baud rate, the rest of parameters will
    098         *                 be set by default.
    099         *
    100         * @throws NullPointerException if {@code port == null}.
    101         *
    102         * @see #DEFAULT_DATA_BITS
    103         * @see #DEFAULT_FLOW_CONTROL
    104         * @see #DEFAULT_PARITY
    105         * @see #DEFAULT_STOP_BITS
    106         * @see #DEFAULT_PORT_TIMEOUT
    107         * @see #AbstractSerialPort(String, int, int)
    108         * @see #AbstractSerialPort(String, SerialPortParameters)
    109         * @see #AbstractSerialPort(String, SerialPortParameters, int)
    110         */
    111        protected AbstractSerialPort(String port, int baudRate) {
    112                this(port, new SerialPortParameters(baudRate, DEFAULT_DATA_BITS, DEFAULT_STOP_BITS, DEFAULT_PARITY, DEFAULT_FLOW_CONTROL), DEFAULT_PORT_TIMEOUT);
    113        }
    114
    115        /**
    116         * Class constructor. Instantiates a new {@code AbstractSerialPort} object
    117         * with the given parameters.
    118         *
    119         * @param port COM port name to use.
    120         * @param baudRate Serial port baud rate, the rest of parameters will be
    121         *        set by default.
    122         * @param receiveTimeout Receive timeout in milliseconds.
    123         *
    124         * @throws IllegalArgumentException if {@code receiveTimeout < 0}.
    125         * @throws NullPointerException if {@code port == null}.
    126         *
    127         * @see DEFAULT_DATA_BITS
    128         * @see DEFAULT_FLOW_CONTROL
    129         * @see DEFAULT_PARITY
    130         * @see DEFAULT_STOP_BITS
    131         * @see #AbstractSerialPort(String, int)
    132         * @see #AbstractSerialPort(String, SerialPortParameters)
    133         * @see #AbstractSerialPort(String, SerialPortParameters, int)
    134         */
    135        protected AbstractSerialPort(String port, int baudRate, int receiveTimeout) {
    136                this(port, new SerialPortParameters(baudRate, DEFAULT_DATA_BITS, DEFAULT_STOP_BITS, DEFAULT_PARITY, DEFAULT_FLOW_CONTROL), receiveTimeout);
    137        }
    138
    139        /**
    140         * Class constructor. Instantiates a new {@code AbstractSerialPort} object
    141         * with the given parameters.
    142         *
    143         * @param port COM port name to use.
    144         * @param parameters Serial port connection parameters.
    145         * @param receiveTimeout Serial connection receive timeout in milliseconds.
    146         *
    147         * @throws IllegalArgumentException if {@code receiveTimeout < 0}.
    148         * @throws NullPointerException if {@code port == null} or
    149         *                              if {@code parameters == null}.
    150         *
    151         * @see #AbstractSerialPort(String, int)
    152         * @see #AbstractSerialPort(String, int, int)
    153         * @see #AbstractSerialPort(String, SerialPortParameters)
    154         * @see SerialPortParameters
    155         */
    156        protected AbstractSerialPort(String port, SerialPortParameters parameters, int receiveTimeout) {
    157                if (port == null)
    158                        throw new NullPointerException("Serial port cannot be null");
    159
    160                if (parameters == null)
    161                        throw new NullPointerException("SerialPortParameters cannot be null");
    162
    163                if (receiveTimeout < 0)
    164                        throw new IllegalArgumentException("Receive timeout cannot be less than 0");
    165
    166                this.port = port;
    167                this.baudRate = parameters.baudrate;
    168                this.receiveTimeout = receiveTimeout;
    169                this.parameters = parameters;
    170                this.logger = LoggerFactory.getLogger(AbstractSerialPort.class);
    171        }
    172
    173        /*
    174         * (non-Javadoc)
    175         * @see com.digi.xbee.api.connection.IConnectionInterface#isOpen()
    176         */
    177        @Override
    178        public boolean isOpen() {
    179                return connectionOpen;
    180        }
    181
    182        /**
    183         * Returns the name of the serial port.
    184         *
    185         * @return Port name.
    186         */
    187        public String getPort() {
    188                return port;
    189        }
    190
    191        /**
    192         * Sets the state of the DTR.
    193         *
    194         * @param state {@code true} to set the line status high, {@code false} to
    195         *              set it low.
    196         *
    197         * @see #isCD()
    198         * @see #isCTS()
    199         * @see #isDSR()
    200         * @see #setRTS(boolean)
    201         */
    202        public abstract void setDTR(boolean state);
    203
    204        /**
    205         * Sets the state of the RTS line.
    206         *
    207         * @param state {@code true} to set the line status high, {@code false} to
    208         *              set it low.
    209         *
    210         * @see #isCD()
    211         * @see #isCTS()
    212         * @see #isDSR()
    213         * @see #setDTR(boolean)
    214         */
    215        public abstract void setRTS(boolean state);
    216
    217        /**
    218         * Returns the state of the CTS line.
    219         *
    220         * @return {@code true} if the line is high, {@code false} otherwise.
    221         *
    222         * @see #isCD()
    223         * @see #isDSR()
    224         * @see #setDTR(boolean)
    225         * @see #setRTS(boolean)
    226         */
    227        public abstract boolean isCTS();
    228
    229        /**
    230         * Returns the state of the DSR line.
    231         *
    232         * @return {@code true} if the line is high, {@code false} otherwise.
    233         *
    234         * @see #isCD()
    235         * @see #isCTS()
    236         * @see #setDTR(boolean)
    237         * @see #setRTS(boolean)
    238         */
    239        public abstract boolean isDSR();
    240
    241        /**
    242         * Returns the state of the CD line.
    243         *
    244         * @return {@code true} if the line is high, {@code false} otherwise.
    245         *
    246         * @see #isCTS()
    247         * @see #isDSR()
    248         * @see #setDTR(boolean)
    249         * @see #setRTS(boolean)
    250         */
    251        public abstract boolean isCD();
    252
    253        /**
    254         * Returns whether or not the port's flow control is configured in
    255         * hardware mode.
    256         *
    257         * @return {@code true} if the flow control is hardware, {@code false}
    258         *         otherwise.
    259         *
    260         * @see #getPortParameters()
    261         * @see #setPortParameters(SerialPortParameters)
    262         * @see #setPortParameters(int, int, int, int, int)
    263         */
    264        public boolean isHardwareFlowControl() {
    265                return parameters.flowControl == FLOW_CONTROL_HW;
    266        }
    267
    268        /**
    269         * Sets the new parameters of the serial port.
    270         *
    271         * @param baudRate The new value of baud rate.
    272         * @param dataBits The new value of data bits.
    273         * @param stopBits The new value of stop bits.
    274         * @param parity The new value of parity.
    275         * @param flowControl The new value of flow control.
    276         *
    277         * @throws ConnectionException if any error occurs when setting the serial
    278         *                             port parameters
    279         * @throws IllegalArgumentException if {@code baudRate < 0} or
    280         *                                  if {@code dataBits < 0} or
    281         *                                  if {@code stopBits < 0} or
    282         *                                  if {@code parity < 0} or
    283         *                                  if {@code flowControl < 0}.
    284         * @throws InvalidConfigurationException if the configuration is invalid.
    285         *
    286         * @see #getPortParameters()
    287         * @see #setPortParameters(SerialPortParameters)
    288         */
    289        public void setPortParameters(int baudRate, int dataBits, int stopBits, int parity, int flowControl) throws InvalidConfigurationException, ConnectionException {
    290                SerialPortParameters parameters = new SerialPortParameters(baudRate, dataBits, stopBits, parity, flowControl);
    291                setPortParameters(parameters);
    292        }
    293
    294        /**
    295         * Sets the new parameters of the serial port as
    296         * {@code SerialPortParameters}.
    297         *
    298         * @param parameters The new serial port parameters.
    299         *
    300         * @throws ConnectionException if any error occurs when setting the serial
    301         *                             port parameters.
    302         * @throws InvalidConfigurationException if the configuration is invalid.
    303         * @throws NullPointerException if {@code parameters == null}.
    304         *
    305         * @see #getPortParameters()
    306         * @see #setPortParameters(int, int, int, int, int)
    307         * @see SerialPortParameters
    308         */
    309        public void setPortParameters(SerialPortParameters parameters) throws InvalidConfigurationException, ConnectionException {
    310                if (parameters == null)
    311                        throw new NullPointerException("Serial port parameters cannot be null.");
    312
    313                baudRate = parameters.baudrate;
    314                this.parameters = parameters;
    315                if (isOpen()) {
    316                        close();
    317                        open();
    318                }
    319        }
    320
    321        /**
    322         * Enables or disables the break line.
    323         *
    324         * @param enabled {@code true} to enable the Break line, {@code false} to
    325         *                disable it.
    326         *
    327         * @see #sendBreak(int)
    328         */
    329        public abstract void setBreak(boolean enabled);
    330
    331        /**
    332         * Sends a break signal to the serial port with the given duration
    333         * (in milliseconds).
    334         *
    335         * @param duration Duration of the break signal in milliseconds.
    336         *
    337         * @see #setBreak(boolean)
    338         */
    339        public abstract void sendBreak(int duration);
    340
    341        /**
    342         * Sets the read timeout of the serial port (in milliseconds).
    343         *
    344         * @param timeout The new read timeout of the serial port in milliseconds.
    345         *
    346         * @see #getReadTimeout()
    347         */
    348        public abstract void setReadTimeout(int timeout);
    349
    350        /**
    351         * Returns the read timeout of the serial port (in milliseconds).
    352         *
    353         * @return The read timeout of the serial port in milliseconds.
    354         *
    355         * @see #setReadTimeout(int)
    356         */
    357        public abstract int getReadTimeout();
    358
    359        /**
    360         * Purges the serial port removing all the data from the input stream.
    361         *
    362         * @see #flush()
    363         */
    364        public void purge() {
    365                if (getInputStream() != null) {
    366                        try {
    367                                byte[] availableBytes = new byte[getInputStream().available()];
    368                                if (getInputStream().available() > 0)
    369                                        getInputStream().read(availableBytes, 0, getInputStream().available());
    370                        } catch (IOException e) {
    371                                logger.error(e.getMessage(), e);
    372                        }
    373                }
    374        }
    375
    376        /**
    377         * Flushes the available data of the output stream.
    378         *
    379         * @see #purge()
    380         */
    381        public void flush() {
    382                if (getOutputStream() != null) {
    383                        try {
    384                                getOutputStream().flush();
    385                        } catch (IOException e) {
    386                                logger.error(e.getMessage(), e);
    387                        }
    388                }
    389        }
    390
    391        /*
    392         * (non-Javadoc)
    393         * @see com.digi.xbee.api.connection.IConnectionInterface#writeData(byte[])
    394         */
    395        @Override
    396        public void writeData(byte[] data) throws IOException {
    397                if (data == null)
    398                        throw new NullPointerException("Data to be sent cannot be null.");
    399
    400                if (getOutputStream() != null) {
    401                        // Writing data in ports without any device connected and configured with
    402                        // hardware flow-control causes the majority of serial libraries to hang.
    403
    404                        // Before writing any data, check if the port is configured with hardware
    405                        // flow-control and, if so, try to write the data up to 3 times verifying
    406                        // that the CTS line is high (there is a device connected to the other side
    407                        // ready to receive data).
    408                        if (isHardwareFlowControl()) {
    409                                int tries = 0;
    410                                while (tries < 3 && !isCTS()) {
    411                                        try {
    412                                                Thread.sleep(100);
    413                                        } catch (InterruptedException e) { }
    414                                        tries += 1;
    415                                }
    416                                if (isCTS()) {
    417                                        getOutputStream().write(data);
    418                                        getOutputStream().flush();
    419                                }
    420                        } else {
    421                                getOutputStream().write(data);
    422                                getOutputStream().flush();
    423                        }
    424                }
    425        }
    426
    427        /*
    428         * (non-Javadoc)
    429         * @see com.digi.xbee.api.connection.IConnectionInterface#writeData(byte[], int, int)
    430         */
    431        @Override
    432        public void writeData(byte[] data, int offset, int length) throws IOException {
    433                if (data == null)
    434                        throw new NullPointerException("Data to be sent cannot be null.");
    435                if (offset < 0)
    436                        throw new IllegalArgumentException("Offset cannot be less than 0.");
    437                if (length < 1)
    438                        throw new IllegalArgumentException("Length cannot be less than 0.");
    439                if (offset >= data.length)
    440                        throw new IllegalArgumentException("Offset must be less than the data length.");
    441                if (offset + length > data.length)
    442                        throw new IllegalArgumentException("Offset + length cannot be great than the data length.");
    443
    444                if (getOutputStream() != null) {
    445                        // Writing data in ports without any device connected and configured with
    446                        // hardware flow-control causes the majority of serial libraries to hang.
    447
    448                        // Before writing any data, check if the port is configured with hardware
    449                        // flow-control and, if so, try to write the data up to 3 times verifying
    450                        // that the CTS line is high (there is a device connected to the other side
    451                        // ready to receive data).
    452                        if (isHardwareFlowControl()) {
    453                                int tries = 0;
    454                                while (tries < 3 && !isCTS()) {
    455                                        try {
    456                                                Thread.sleep(100);
    457                                        } catch (InterruptedException e) { }
    458                                        tries += 1;
    459                                }
    460                                if (isCTS()) {
    461                                        getOutputStream().write(data, offset, length);
    462                                        getOutputStream().flush();
    463                                }
    464                        } else {
    465                                getOutputStream().write(data, offset, length);
    466                                getOutputStream().flush();
    467                        }
    468                }
    469        }
    470
    471        /*
    472         * (non-Javadoc)
    473         * @see com.digi.xbee.api.connection.IConnectionInterface#readData(byte[])
    474         */
    475        @Override
    476        public int readData(byte[] data) throws IOException {
    477                if (data == null)
    478                        throw new NullPointerException("Buffer cannot be null.");
    479
    480                int readBytes = 0;
    481                if (getInputStream() != null)
    482                        readBytes = getInputStream().read(data);
    483                return readBytes;
    484        }
    485
    486        /*
    487         * (non-Javadoc)
    488         * @see com.digi.xbee.api.connection.IConnectionInterface#readData(byte[], int, int)
    489         */
    490        @Override
    491        public int readData(byte[] data, int offset, int length) throws IOException {
    492                if (data == null)
    493                        throw new NullPointerException("Buffer cannot be null.");
    494                if (offset < 0)
    495                        throw new IllegalArgumentException("Offset cannot be less than 0.");
    496                if (length < 1)
    497                        throw new IllegalArgumentException("Length cannot be less than 0.");
    498                if (offset >= data.length)
    499                        throw new IllegalArgumentException("Offset must be less than the buffer length.");
    500                if (offset + length > data.length)
    501                        throw new IllegalArgumentException("Offset + length cannot be great than the buffer length.");
    502
    503                int readBytes = 0;
    504                if (getInputStream() != null)
    505                        readBytes = getInputStream().read(data, offset, length);
    506                return readBytes;
    507        }
    508
    509        /**
    510         * Returns the XBee serial port parameters.
    511         *
    512         * @return The XBee serial port parameters.
    513         *
    514         * @see #setPortParameters(SerialPortParameters)
    515         * @see #setPortParameters(int, int, int, int, int)
    516         * @see SerialPortParameters
    517         */
    518        public SerialPortParameters getPortParameters() {
    519                if (parameters != null)
    520                        return parameters;
    521                return new SerialPortParameters(baudRate, DEFAULT_DATA_BITS,
    522                                DEFAULT_STOP_BITS, DEFAULT_PARITY, DEFAULT_FLOW_CONTROL);
    523        }
    524
    525        /**
    526         * Returns the serial port receive timeout (in milliseconds).
    527         *
    528         * @return The serial port receive timeout in milliseconds.
    529         *
    530         * @see #getReadTimeout()
    531         * @see #setReadTimeout(int)
    532         */
    533        public int getReceiveTimeout() {
    534                return receiveTimeout;
    535        }
    536
    537        /*
    538         * (non-Javadoc)
    539         * @see java.lang.Object#toString()
    540         */
    541        @Override
    542        public String toString() {
    543                if (parameters != null) {
    544                        String parity = "N";
    545                        String flowControl = "N";
    546                        if (parameters.parity == 1)
    547                                parity = "O";
    548                        else if (parameters.parity == 2)
    549                                parity = "E";
    550                        else if (parameters.parity == 3)
    551                                parity = "M";
    552                        else if (parameters.parity == 4)
    553                                parity = "S";
    554                        if (parameters.flowControl == 1
    555                                        || parameters.flowControl == 2
    556                                        || parameters.flowControl == 3)
    557                                flowControl = "H";
    558                        else if (parameters.flowControl == 4
    559                                        || parameters.flowControl == 8
    560                                        || parameters.flowControl == 12)
    561                                flowControl = "S";
    562                        return "[" + port + " - " + baudRate + "/" + parameters.dataBits +
    563                                        "/"  + parity + "/" + parameters.stopBits + "/" + flowControl + "] ";
    564                } else
    565                        return "[" + port + " - " + baudRate + "/8/N/1/N] ";
    566        }
    567}