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}