001/**
002 * Copyright (c) 2014-2015 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;
013
014import java.io.IOException;
015import java.util.ArrayList;
016import java.util.Arrays;
017import java.util.Set;
018import java.util.TreeSet;
019
020import org.slf4j.Logger;
021import org.slf4j.LoggerFactory;
022
023import com.digi.xbee.api.connection.IConnectionInterface;
024import com.digi.xbee.api.connection.DataReader;
025import com.digi.xbee.api.connection.serial.SerialPortParameters;
026import com.digi.xbee.api.exceptions.ATCommandException;
027import com.digi.xbee.api.exceptions.InterfaceNotOpenException;
028import com.digi.xbee.api.exceptions.InvalidOperatingModeException;
029import com.digi.xbee.api.exceptions.OperationNotSupportedException;
030import com.digi.xbee.api.exceptions.TimeoutException;
031import com.digi.xbee.api.exceptions.TransmitException;
032import com.digi.xbee.api.exceptions.XBeeException;
033import com.digi.xbee.api.io.IOLine;
034import com.digi.xbee.api.io.IOMode;
035import com.digi.xbee.api.io.IOSample;
036import com.digi.xbee.api.io.IOValue;
037import com.digi.xbee.api.listeners.IExplicitDataReceiveListener;
038import com.digi.xbee.api.listeners.IIOSampleReceiveListener;
039import com.digi.xbee.api.listeners.IModemStatusReceiveListener;
040import com.digi.xbee.api.listeners.IPacketReceiveListener;
041import com.digi.xbee.api.listeners.IDataReceiveListener;
042import com.digi.xbee.api.models.ATCommand;
043import com.digi.xbee.api.models.ATCommandResponse;
044import com.digi.xbee.api.models.ATCommandStatus;
045import com.digi.xbee.api.models.AssociationIndicationStatus;
046import com.digi.xbee.api.models.HardwareVersion;
047import com.digi.xbee.api.models.PowerLevel;
048import com.digi.xbee.api.models.RemoteATCommandOptions;
049import com.digi.xbee.api.models.XBee16BitAddress;
050import com.digi.xbee.api.models.XBee64BitAddress;
051import com.digi.xbee.api.models.OperatingMode;
052import com.digi.xbee.api.models.XBeeProtocol;
053import com.digi.xbee.api.models.XBeeTransmitStatus;
054import com.digi.xbee.api.packet.XBeeAPIPacket;
055import com.digi.xbee.api.packet.APIFrameType;
056import com.digi.xbee.api.packet.XBeePacket;
057import com.digi.xbee.api.packet.common.ATCommandPacket;
058import com.digi.xbee.api.packet.common.ATCommandQueuePacket;
059import com.digi.xbee.api.packet.common.ATCommandResponsePacket;
060import com.digi.xbee.api.packet.common.IODataSampleRxIndicatorPacket;
061import com.digi.xbee.api.packet.common.RemoteATCommandPacket;
062import com.digi.xbee.api.packet.common.RemoteATCommandResponsePacket;
063import com.digi.xbee.api.packet.common.TransmitStatusPacket;
064import com.digi.xbee.api.packet.raw.RX16IOPacket;
065import com.digi.xbee.api.packet.raw.RX64IOPacket;
066import com.digi.xbee.api.packet.raw.TXStatusPacket;
067import com.digi.xbee.api.utils.ByteUtils;
068import com.digi.xbee.api.utils.HexUtils;
069
070/**
071 * This class provides common functionality for all XBee devices.
072 * 
073 * @see XBeeDevice
074 * @see RemoteXBeeDevice
075 */
076public abstract class AbstractXBeeDevice {
077        
078        // Constants.
079        
080        /**
081         * Default receive timeout used to wait for a response in synchronous 
082         * operations: {@value} ms.
083         * 
084         * @see XBeeDevice#getReceiveTimeout()
085         * @see XBeeDevice#setReceiveTimeout(int)
086         */
087        protected final static int DEFAULT_RECEIVE_TIMETOUT = 2000; // 2.0 seconds of timeout to receive packet and command responses.
088        
089        /**
090         * Timeout to wait before entering in command mode: {@value} ms.
091         * 
092         * <p>It is used to determine the operating mode of the module (this 
093         * library only supports API modes, not transparent mode).</p>
094         * 
095         * <p>This value depends on the {@code GT}, {@code AT} and/or {@code BT} 
096         * parameters.</p>
097         * 
098         * @see XBeeDevice#determineOperatingMode()
099         */
100        protected final static int TIMEOUT_BEFORE_COMMAND_MODE = 1200;
101        
102        /**
103         * Timeout to wait after entering in command mode: {@value} ms.
104         * 
105         * <p>It is used to determine the operating mode of the module (this 
106         * library only supports API modes, not transparent mode).</p>
107         * 
108         * <p>This value depends on the {@code GT}, {@code AT} and/or {@code BT} 
109         * parameters.</p>
110         * 
111         * @see XBeeDevice#determineOperatingMode()
112         */
113        protected final static int TIMEOUT_ENTER_COMMAND_MODE = 1500;
114        
115        // Variables.
116        protected IConnectionInterface connectionInterface;
117        
118        protected DataReader dataReader = null;
119        
120        protected XBeeProtocol xbeeProtocol = XBeeProtocol.UNKNOWN;
121        
122        protected OperatingMode operatingMode = OperatingMode.UNKNOWN;
123        
124        protected XBee16BitAddress xbee16BitAddress = XBee16BitAddress.UNKNOWN_ADDRESS;
125        protected XBee64BitAddress xbee64BitAddress = XBee64BitAddress.UNKNOWN_ADDRESS;
126        
127        protected int currentFrameID = 0xFF;
128        protected int receiveTimeout = DEFAULT_RECEIVE_TIMETOUT;
129        
130        protected AbstractXBeeDevice localXBeeDevice;
131        
132        protected Logger logger;
133        
134        private String nodeID;
135        private String firmwareVersion;
136        
137        private HardwareVersion hardwareVersion;
138        
139        private Object ioLock = new Object();
140        
141        private boolean ioPacketReceived = false;
142        private boolean applyConfigurationChanges = true;
143        
144        private byte[] ioPacketPayload;
145        
146        /**
147         * Class constructor. Instantiates a new {@code XBeeDevice} object in the 
148         * given port name and baud rate.
149         * 
150         * @param port Serial port name where XBee device is attached to.
151         * @param baudRate Serial port baud rate to communicate with the device. 
152         *                 Other connection parameters will be set as default (8 
153         *                 data bits, 1 stop bit, no parity, no flow control).
154         * 
155         * @throws IllegalArgumentException if {@code baudRate < 0}.
156         * @throws NullPointerException if {@code port == null}.
157         * 
158         * @see #AbstractXBeeDevice(IConnectionInterface)
159         * @see #AbstractXBeeDevice(String, SerialPortParameters)
160         * @see #AbstractXBeeDevice(XBeeDevice, XBee64BitAddress)
161         * @see #AbstractXBeeDevice(XBeeDevice, XBee64BitAddress, XBee16BitAddress, String)
162         * @see #AbstractXBeeDevice(String, int, int, int, int, int)
163         */
164        public AbstractXBeeDevice(String port, int baudRate) {
165                this(XBee.createConnectiontionInterface(port, baudRate));
166        }
167        
168        /**
169         * Class constructor. Instantiates a new {@code XBeeDevice} object in the 
170         * given serial port name and settings.
171         * 
172         * @param port Serial port name where XBee device is attached to.
173         * @param baudRate Serial port baud rate to communicate with the device.
174         * @param dataBits Serial port data bits.
175         * @param stopBits Serial port data bits.
176         * @param parity Serial port data bits.
177         * @param flowControl Serial port data bits.
178         * 
179         * @throws IllegalArgumentException if {@code baudRate < 0} or
180         *                                  if {@code dataBits < 0} or
181         *                                  if {@code stopBits < 0} or
182         *                                  if {@code parity < 0} or
183         *                                  if {@code flowControl < 0}.
184         * @throws NullPointerException if {@code port == null}.
185         * 
186         * @see #AbstractXBeeDevice(IConnectionInterface)
187         * @see #AbstractXBeeDevice(String, int)
188         * @see #AbstractXBeeDevice(String, SerialPortParameters)
189         * @see #AbstractXBeeDevice(XBeeDevice, XBee64BitAddress)
190         * @see #AbstractXBeeDevice(XBeeDevice, XBee64BitAddress, XBee16BitAddress, String)
191         */
192        public AbstractXBeeDevice(String port, int baudRate, int dataBits, int stopBits, int parity, int flowControl) {
193                this(port, new SerialPortParameters(baudRate, dataBits, stopBits, parity, flowControl));
194        }
195        
196        /**
197         * Class constructor. Instantiates a new {@code XBeeDevice} object in the 
198         * given serial port name and parameters.
199         * 
200         * @param port Serial port name where XBee device is attached to.
201         * @param serialPortParameters Object containing the serial port parameters.
202         * 
203         * @throws NullPointerException if {@code port == null} or
204         *                              if {@code serialPortParameters == null}.
205         * 
206         * @see #AbstractXBeeDevice(IConnectionInterface)
207         * @see #AbstractXBeeDevice(String, int)
208         * @see #AbstractXBeeDevice(XBeeDevice, XBee64BitAddress)
209         * @see #AbstractXBeeDevice(XBeeDevice, XBee64BitAddress, XBee16BitAddress, String)
210         * @see #AbstractXBeeDevice(String, int, int, int, int, int)
211         * @see com.digi.xbee.api.connection.serial.SerialPortParameters
212         */
213        public AbstractXBeeDevice(String port, SerialPortParameters serialPortParameters) {
214                this(XBee.createConnectiontionInterface(port, serialPortParameters));
215        }
216        
217        /**
218         * Class constructor. Instantiates a new {@code XBeeDevice} object with the 
219         * given connection interface.
220         * 
221         * @param connectionInterface The connection interface with the physical 
222         *                            XBee device.
223         * 
224         * @throws NullPointerException if {@code connectionInterface == null}.
225         * 
226         * @see #AbstractXBeeDevice(String, int)
227         * @see #AbstractXBeeDevice(String, SerialPortParameters)
228         * @see #AbstractXBeeDevice(XBeeDevice, XBee64BitAddress)
229         * @see #AbstractXBeeDevice(XBeeDevice, XBee64BitAddress, XBee16BitAddress, String)
230         * @see #AbstractXBeeDevice(String, int, int, int, int, int)
231         * @see com.digi.xbee.api.connection.IConnectionInterface
232         */
233        public AbstractXBeeDevice(IConnectionInterface connectionInterface) {
234                if (connectionInterface == null)
235                        throw new NullPointerException("ConnectionInterface cannot be null.");
236                
237                this.connectionInterface = connectionInterface;
238                this.logger = LoggerFactory.getLogger(this.getClass());
239                logger.debug(toString() + "Using the connection interface {}.", 
240                                connectionInterface.getClass().getSimpleName());
241        }
242        
243        /**
244         * Class constructor. Instantiates a new {@code RemoteXBeeDevice} object 
245         * with the given local {@code XBeeDevice} which contains the connection 
246         * interface to be used.
247         * 
248         * @param localXBeeDevice The local XBee device that will behave as 
249         *                        connection interface to communicate with this 
250         *                        remote XBee device.
251         * @param addr64 The 64-bit address to identify this XBee device.
252         * 
253         * @throws IllegalArgumentException If {@code localXBeeDevice.isRemote() == true}.
254         * @throws NullPointerException if {@code localXBeeDevice == null} or
255         *                              if {@code addr64 == null}.
256         * 
257         * @see #AbstractXBeeDevice(IConnectionInterface)
258         * @see #AbstractXBeeDevice(String, int)
259         * @see #AbstractXBeeDevice(String, SerialPortParameters)
260         * @see #AbstractXBeeDevice(XBeeDevice, XBee64BitAddress, XBee16BitAddress, String)
261         * @see #AbstractXBeeDevice(String, int, int, int, int, int)
262         * @see com.digi.xbee.api.models.XBee16BitAddress
263         */
264        public AbstractXBeeDevice(XBeeDevice localXBeeDevice, XBee64BitAddress addr64) {
265                this(localXBeeDevice, addr64, null, null);
266        }
267        
268        /**
269         * Class constructor. Instantiates a new {@code RemoteXBeeDevice} object 
270         * with the given local {@code XBeeDevice} which contains the connection 
271         * interface to be used.
272         * 
273         * @param localXBeeDevice The local XBee device that will behave as 
274         *                        connection interface to communicate with this 
275         *                        remote XBee device.
276         * @param addr64 The 64-bit address to identify this XBee device.
277         * @param addr16 The 16-bit address to identify this XBee device. It might 
278         *               be {@code null}.
279         * @param id The node identifier of this XBee device. It might be 
280         *           {@code null}.
281         * 
282         * @throws IllegalArgumentException If {@code localXBeeDevice.isRemote() == true}.
283         * @throws NullPointerException If {@code localXBeeDevice == null} or
284         *                              if {@code addr64 == null}.
285         * 
286         * @see #AbstractXBeeDevice(IConnectionInterface)
287         * @see #AbstractXBeeDevice(String, int)
288         * @see #AbstractXBeeDevice(String, SerialPortParameters)
289         * @see #AbstractXBeeDevice(XBeeDevice, XBee64BitAddress)
290         * @see #AbstractXBeeDevice(String, int, int, int, int, int)
291         * @see com.digi.xbee.api.models.XBee16BitAddress
292         * @see com.digi.xbee.api.models.XBee64BitAddress
293         */
294        public AbstractXBeeDevice(XBeeDevice localXBeeDevice, XBee64BitAddress addr64, 
295                        XBee16BitAddress addr16, String id) {
296                if (localXBeeDevice == null)
297                        throw new NullPointerException("Local XBee device cannot be null.");
298                if (addr64 == null)
299                        throw new NullPointerException("XBee 64-bit address of the device cannot be null.");
300                if (localXBeeDevice.isRemote())
301                        throw new IllegalArgumentException("The given local XBee device is remote.");
302                
303                this.localXBeeDevice = localXBeeDevice;
304                this.connectionInterface = localXBeeDevice.getConnectionInterface();
305                this.xbee64BitAddress = addr64;
306                this.xbee16BitAddress = addr16;
307                if (addr16 == null)
308                        xbee16BitAddress = XBee16BitAddress.UNKNOWN_ADDRESS;
309                this.nodeID = id;
310                this.logger = LoggerFactory.getLogger(this.getClass());
311                logger.debug(toString() + "Using the connection interface {}.", 
312                                connectionInterface.getClass().getSimpleName());
313        }
314        
315        /**
316         * Returns the connection interface associated to this XBee device.
317         * 
318         * @return XBee device's connection interface.
319         * 
320         * @see com.digi.xbee.api.connection.IConnectionInterface
321         */
322        public IConnectionInterface getConnectionInterface() {
323                return connectionInterface;
324        }
325        
326        /**
327         * Returns whether this XBee device is a remote device.
328         * 
329         * @return {@code true} if this XBee device is a remote device, 
330         *         {@code false} otherwise.
331         */
332        abstract public boolean isRemote();
333        
334        /**
335         * Reads some parameters from this device and obtains its protocol.
336         * 
337         * <p>This method refresh the values of:</p>
338         * <ul>
339         * <li>64-bit address only if it is not initialized.</li>
340         * <li>Node Identifier.</li>
341         * <li>Hardware version if it is not initialized.</li>
342         * <li>Firmware version.</li>
343         * <li>XBee device protocol.</li>
344         * <li>16-bit address (not for DigiMesh/DigiPoint modules).</li>
345         * </ul>
346         * 
347         * @throws InterfaceNotOpenException if this device connection is not open.
348         * @throws TimeoutException if there is a timeout reading the parameters.
349         * @throws XBeeException if there is any error trying to read the device information or
350         *                       if there is any other XBee related exception.
351         * 
352         * @see #get16BitAddress()
353         * @see #get64BitAddress()
354         * @see #getHardwareVersion()
355         * @see #getNodeID()
356         * @see #getFirmwareVersion()
357         * @see #getXBeeProtocol()
358         * @see #setNodeID(String)
359         */
360        public void readDeviceInfo() throws TimeoutException, XBeeException {
361                byte[] response = null;
362                // Get the 64-bit address.
363                if (xbee64BitAddress == null || xbee64BitAddress == XBee64BitAddress.UNKNOWN_ADDRESS) {
364                        String addressHigh;
365                        String addressLow;
366                        
367                        response = getParameter("SH");
368                        addressHigh = HexUtils.byteArrayToHexString(response);
369                        
370                        response = getParameter("SL");
371                        addressLow = HexUtils.byteArrayToHexString(response);
372                        
373                        while(addressLow.length() < 8)
374                                addressLow = "0" + addressLow;
375                        
376                        xbee64BitAddress = new XBee64BitAddress(addressHigh + addressLow);
377                }
378                // Get the Node ID.
379                response = getParameter("NI");
380                nodeID = new String(response);
381                
382                // Get the hardware version.
383                if (hardwareVersion == null) {
384                        response = getParameter("HV");
385                        hardwareVersion = HardwareVersion.get(response[0]);
386                }
387                // Get the firmware version.
388                response = getParameter("VR");
389                firmwareVersion = HexUtils.byteArrayToHexString(response);
390                
391                // Original value of the protocol.
392                XBeeProtocol origProtocol = getXBeeProtocol();
393                
394                // Obtain the device protocol.
395                xbeeProtocol = XBeeProtocol.determineProtocol(hardwareVersion, firmwareVersion);
396                
397                if (origProtocol != XBeeProtocol.UNKNOWN
398                                && origProtocol != xbeeProtocol) {
399                        throw new XBeeException("Error reading device information: "
400                                        + "Your module seems to be " + xbeeProtocol 
401                                        + " and NOT " + origProtocol + ". Check if you are using" 
402                                        + " the appropriate device class.");
403                }
404                
405                // Get the 16-bit address. This must be done after obtaining the protocol because 
406                // DigiMesh and Point-to-Multipoint protocols don't have 16-bit addresses.
407                XBeeProtocol protocol = getXBeeProtocol();
408                if (protocol != XBeeProtocol.DIGI_MESH 
409                                && protocol != XBeeProtocol.DIGI_POINT
410                                && protocol != XBeeProtocol.UNKNOWN) {
411                        response = getParameter("MY");
412                        xbee16BitAddress = new XBee16BitAddress(response);
413                }
414        }
415        
416        /**
417         * Returns the 16-bit address of this XBee device.
418         * 
419         * <p>To refresh this value use the {@link #readDeviceInfo()} method.</p>
420         * 
421         * @return The 16-bit address of this XBee device.
422         * 
423         * @see com.digi.xbee.api.models.XBee16BitAddress
424         */
425        public XBee16BitAddress get16BitAddress() {
426                return xbee16BitAddress;
427        }
428        
429        /**
430         * Returns the 64-bit address of this XBee device.
431         * 
432         * <p>If this value is {@code null} or 
433         * {@code XBee64BitAddress.UNKNOWN_ADDRESS}, use the 
434         * {@link #readDeviceInfo()} method to get its value.</p>
435         * 
436         * @return The 64-bit address of this XBee device.
437         * 
438         * @see com.digi.xbee.api.models.XBee64BitAddress
439         */
440        public XBee64BitAddress get64BitAddress() {
441                return xbee64BitAddress;
442        }
443        
444        /**
445         * Returns the Operating mode (AT, API or API escaped) of this XBee device 
446         * for a local device, and the operating mode of the local device used as 
447         * communication interface for a remote device.
448         * 
449         * @return The operating mode of the local XBee device.
450         * 
451         * @see #isRemote()
452         * @see com.digi.xbee.api.models.OperatingMode
453         */
454        protected OperatingMode getOperatingMode() {
455                if (isRemote())
456                        return localXBeeDevice.getOperatingMode();
457                return operatingMode;
458        }
459        
460        /**
461         * Returns the XBee Protocol of this XBee device.
462         * 
463         * <p>To refresh this value use the {@link #readDeviceInfo()} method.</p>
464         * 
465         * @return The XBee device protocol.
466         * 
467         * @see com.digi.xbee.api.models.XBeeProtocol
468         */
469        public XBeeProtocol getXBeeProtocol() {
470                return xbeeProtocol;
471        }
472        
473        /**
474         * Returns the node identifier of this XBee device.
475         * 
476         * <p>To refresh this value use the {@link #readDeviceInfo()} method.</p>
477         * 
478         * @return The node identifier of this device.
479         * 
480         * @see #setNodeID(String)
481         */
482        public String getNodeID() {
483                return nodeID;
484        }
485        
486        /**
487         * Sets the node identifier of this XBee device.
488         * 
489         * @param nodeID The new node id of the device.
490         * 
491         * @throws IllegalArgumentException if {@code nodeID.length > 20}.
492         * @throws InterfaceNotOpenException if this device connection is not open.
493         * @throws NullPointerException if {@code nodeID == null}.
494         * @throws TimeoutException if there is a timeout setting the node ID value.
495         * @throws XBeeException if there is any other XBee related exception.
496         * 
497         * @see #getNodeID()
498         */
499        public void setNodeID(String nodeID) throws TimeoutException, XBeeException {
500                if (nodeID == null)
501                        throw new NullPointerException("Node ID cannot be null.");
502                if (nodeID.length() > 20)
503                        throw new IllegalArgumentException("Node ID length must be less than 21.");
504                
505                setParameter("NI", nodeID.getBytes());
506                
507                this.nodeID = nodeID;
508        }
509        
510        /**
511         * Returns the firmware version (hexadecimal string value) of this XBee 
512         * device.
513         * 
514         * <p>To refresh this value use the {@link #readDeviceInfo()} method.</p>
515         * 
516         * @return The firmware version of the XBee device.
517         */
518        public String getFirmwareVersion() {
519                return firmwareVersion;
520        }
521        
522        /**
523         * Returns the hardware version of this XBee device.
524         * 
525         * <p>If this value is {@code null}, use the {@link #readDeviceInfo()} 
526         * method to get its value.</p>
527         * 
528         * @return The hardware version of the XBee device.
529         * 
530         * @see com.digi.xbee.api.models.HardwareVersion
531         * @see com.digi.xbee.api.models.HardwareVersionEnum
532         */
533        public HardwareVersion getHardwareVersion() {
534                return hardwareVersion;
535        }
536        
537        /**
538         * Updates the current device reference with the data provided for the 
539         * given device.
540         * 
541         * <p><b>This is only for internal use.</b></p>
542         * 
543         * @param device The XBee Device to get the data from.
544         */
545        public void updateDeviceDataFrom(AbstractXBeeDevice device) {
546                // TODO Should the devices have the same protocol??
547                // TODO Should be allow to update a local from a remote or viceversa?? Maybe 
548                // this must be in the Local/Remote device class(es) and not here... 
549                
550                // Only update the Node Identifier if the provided is not null.
551                if (device.getNodeID() != null)
552                        this.nodeID = device.getNodeID();
553                
554                // Only update the 64-bit address if the original is null or unknown.
555                XBee64BitAddress addr64 = device.get64BitAddress();
556                if (addr64 != null && addr64 != XBee64BitAddress.UNKNOWN_ADDRESS
557                                && !addr64.equals(xbee64BitAddress) 
558                                && (xbee64BitAddress == null 
559                                        || xbee64BitAddress.equals(XBee64BitAddress.UNKNOWN_ADDRESS))) {
560                        xbee64BitAddress = addr64;
561                }
562                
563                // TODO Change here the 16-bit address or maybe in ZigBee and 802.15.4?
564                // TODO Should the 16-bit address be always updated? Or following the same rule as the 64-bit address.
565                XBee16BitAddress addr16 = device.get16BitAddress();
566                if (addr16 != null && !addr16.equals(xbee16BitAddress)) {
567                        xbee16BitAddress = addr16;
568                }
569                
570                //this.deviceType = device.deviceType; // This is not yet done.
571                
572                // The operating mode: only API/API2. Do we need this for a remote device?
573                // The protocol of the device should be the same.
574                // The hardware version should be the same.
575                // The firmware version can change...
576        }
577        
578        /**
579         * Adds the provided listener to the list of listeners to be notified
580         * when new packets are received. 
581         * 
582         * <p>If the listener has been already included, this method does nothing.
583         * </p>
584         * 
585         * @param listener Listener to be notified when new packets are received.
586         * 
587         * @throws NullPointerException if {@code listener == null}
588         * 
589         * @see #removePacketListener(IPacketReceiveListener)
590         * @see com.digi.xbee.api.listeners.IPacketReceiveListener
591         */
592        protected void addPacketListener(IPacketReceiveListener listener) {
593                if (listener == null)
594                        throw new NullPointerException("Listener cannot be null.");
595                
596                if (dataReader == null)
597                        return;
598                dataReader.addPacketReceiveListener(listener);
599        }
600        
601        /**
602         * Removes the provided listener from the list of packets listeners. 
603         * 
604         * <p>If the listener was not in the list this method does nothing.</p>
605         * 
606         * @param listener Listener to be removed from the list of listeners.
607         * 
608         * @throws NullPointerException if {@code listener == null}
609         * 
610         * @see #addPacketListener(IPacketReceiveListener)
611         * @see com.digi.xbee.api.listeners.IPacketReceiveListener
612         */
613        protected void removePacketListener(IPacketReceiveListener listener) {
614                if (listener == null)
615                        throw new NullPointerException("Listener cannot be null.");
616                
617                if (dataReader == null)
618                        return;
619                dataReader.removePacketReceiveListener(listener);
620        }
621        
622        /**
623         * Adds the provided listener to the list of listeners to be notified
624         * when new data is received. 
625         * 
626         * <p>If the listener has been already included this method does nothing.
627         * </p>
628         * 
629         * @param listener Listener to be notified when new data is received.
630         * 
631         * @throws NullPointerException if {@code listener == null}
632         * 
633         * @see #removeDataListener(IDataReceiveListener)
634         * @see com.digi.xbee.api.listeners.IDataReceiveListener
635         */
636        protected void addDataListener(IDataReceiveListener listener) {
637                if (listener == null)
638                        throw new NullPointerException("Listener cannot be null.");
639                
640                if (dataReader == null)
641                        return;
642                dataReader.addDataReceiveListener(listener);
643        }
644        
645        /**
646         * Removes the provided listener from the list of data listeners. 
647         * 
648         * <p>If the listener was not in the list this method does nothing.</p>
649         * 
650         * @param listener Listener to be removed from the list of listeners.
651         * 
652         * @throws NullPointerException if {@code listener == null}
653         * 
654         * @see #addDataListener(IDataReceiveListener)
655         * @see com.digi.xbee.api.listeners.IDataReceiveListener
656         */
657        protected void removeDataListener(IDataReceiveListener listener) {
658                if (listener == null)
659                        throw new NullPointerException("Listener cannot be null.");
660                
661                if (dataReader == null)
662                        return;
663                dataReader.removeDataReceiveListener(listener);
664        }
665        
666        /**
667         * Adds the provided listener to the list of listeners to be notified
668         * when new IO samples are received. 
669         * 
670         * <p>If the listener has been already included this method does nothing.
671         * </p>
672         * 
673         * @param listener Listener to be notified when new IO samples are received.
674         * 
675         * @throws NullPointerException if {@code listener == null}
676         * 
677         * @see #removeIOSampleListener(IIOSampleReceiveListener)
678         * @see com.digi.xbee.api.listeners.IIOSampleReceiveListener
679         */
680        protected void addIOSampleListener(IIOSampleReceiveListener listener) {
681                if (listener == null)
682                        throw new NullPointerException("Listener cannot be null.");
683                
684                if (dataReader == null)
685                        return;
686                dataReader.addIOSampleReceiveListener(listener);
687        }
688        
689        /**
690         * Removes the provided listener from the list of IO samples listeners. 
691         * 
692         * <p>If the listener was not in the list this method does nothing.</p>
693         * 
694         * @param listener Listener to be removed from the list of listeners.
695         * 
696         * @throws NullPointerException if {@code listener == null}
697         * 
698         * @see #addIOSampleListener(IIOSampleReceiveListener)
699         * @see com.digi.xbee.api.listeners.IIOSampleReceiveListener
700         */
701        protected void removeIOSampleListener(IIOSampleReceiveListener listener) {
702                if (listener == null)
703                        throw new NullPointerException("Listener cannot be null.");
704                
705                if (dataReader == null)
706                        return;
707                dataReader.removeIOSampleReceiveListener(listener);
708        }
709        
710        /**
711         * Adds the provided listener to the list of listeners to be notified
712         * when new Modem Status events are received.
713         * 
714         * <p>If the listener has been already included this method does nothing.
715         * </p>
716         * 
717         * @param listener Listener to be notified when new Modem Status events are 
718         *                 received.
719         * 
720         * @throws NullPointerException if {@code listener == null}
721         * 
722         * @see #removeModemStatusListener(IModemStatusReceiveListener)
723         * @see com.digi.xbee.api.listeners.IModemStatusReceiveListener
724         */
725        protected void addModemStatusListener(IModemStatusReceiveListener listener) {
726                if (listener == null)
727                        throw new NullPointerException("Listener cannot be null.");
728                
729                if (dataReader == null)
730                        return;
731                dataReader.addModemStatusReceiveListener(listener);
732        }
733        
734        /**
735         * Removes the provided listener from the list of Modem Status listeners.
736         * 
737         * <p>If the listener was not in the list this method does nothing.</p>
738         * 
739         * @param listener Listener to be removed from the list of listeners.
740         * 
741         * @throws NullPointerException if {@code listener == null}
742         * 
743         * @see #addModemStatusListener(IModemStatusReceiveListener)
744         * @see com.digi.xbee.api.listeners.IModemStatusReceiveListener
745         */
746        protected void removeModemStatusListener(IModemStatusReceiveListener listener) {
747                if (listener == null)
748                        throw new NullPointerException("Listener cannot be null.");
749                if (dataReader == null)
750                        return;
751                dataReader.removeModemStatusReceiveListener(listener);
752        }
753        
754        /**
755         * Adds the provided listener to the list of listeners to be notified
756         * when new explicit data packets are received.
757         * 
758         * <p>If the listener has been already included this method does nothing.
759         * </p>
760         * 
761         * @param listener Listener to be notified when new explicit data packets 
762         *                 are received.
763         * 
764         * @throws NullPointerException if {@code listener == null}
765         * 
766         * @see #removeExplicitDataListener(IExplicitDataReceiveListener)
767         * @see com.digi.xbee.api.listeners.IExplicitDataReceiveListener
768         */
769        protected void addExplicitDataListener(IExplicitDataReceiveListener listener) {
770                if (listener == null)
771                        throw new NullPointerException("Listener cannot be null.");
772                
773                if (dataReader == null)
774                        return;
775                dataReader.addExplicitDataReceiveListener(listener);
776        }
777        
778        /**
779         * Removes the provided listener from the list of explicit data receive 
780         * listeners.
781         * 
782         * <p>If the listener was not in the list this method does nothing.</p>
783         * 
784         * @param listener Listener to be removed from the list of listeners.
785         * 
786         * @throws NullPointerException if {@code listener == null}
787         * 
788         * @see #addExplicitDataListener(IExplicitDataReceiveListener)
789         * @see com.digi.xbee.api.listeners.IExplicitDataReceiveListener
790         */
791        protected void removeExplicitDataListener(IExplicitDataReceiveListener listener) {
792                if (listener == null)
793                        throw new NullPointerException("Listener cannot be null.");
794                if (dataReader == null)
795                        return;
796                dataReader.removeExplicitDataReceiveListener(listener);
797        }
798        
799        /**
800         * Sends the given AT command and waits for answer or until the configured 
801         * receive timeout expires.
802         * 
803         * <p>The receive timeout is configured using the {@code setReceiveTimeout}
804         * method and can be consulted with {@code getReceiveTimeout} method.</p>
805         * 
806         * @param command AT command to be sent.
807         * @return An {@code ATCommandResponse} object containing the response of 
808         *         the command or {@code null} if there is no response.
809         *         
810         * @throws InterfaceNotOpenException if this device connection is not open.
811         * @throws InvalidOperatingModeException if the operating mode is different 
812         *                                       than {@link OperatingMode#API} and 
813         *                                       {@link OperatingMode#API_ESCAPE}.
814         * @throws IOException if an I/O error occurs while sending the AT command.
815         * @throws NullPointerException if {@code command == null}.
816         * @throws TimeoutException if the configured time expires while waiting 
817         *                          for the command reply.
818         * 
819         * @see XBeeDevice#getReceiveTimeout()
820         * @see XBeeDevice#setReceiveTimeout(int)
821         * @see com.digi.xbee.api.models.ATCommand
822         * @see com.digi.xbee.api.models.ATCommandResponse
823         */
824        protected ATCommandResponse sendATCommand(ATCommand command) 
825                        throws InvalidOperatingModeException, TimeoutException, IOException {
826                // Check if command is null.
827                if (command == null)
828                        throw new NullPointerException("AT command cannot be null.");
829                // Check connection.
830                if (!connectionInterface.isOpen())
831                        throw new InterfaceNotOpenException();
832                
833                ATCommandResponse response = null;
834                OperatingMode operatingMode = getOperatingMode();
835                switch (operatingMode) {
836                case AT:
837                case UNKNOWN:
838                default:
839                        throw new InvalidOperatingModeException(operatingMode);
840                case API:
841                case API_ESCAPE:
842                        // Create the corresponding AT command packet depending on if the device is local or remote.
843                        XBeePacket packet;
844                        if (isRemote()) {
845                                XBee16BitAddress remote16BitAddress = get16BitAddress();
846                                if (remote16BitAddress == null)
847                                        remote16BitAddress = XBee16BitAddress.UNKNOWN_ADDRESS;
848                                int remoteATCommandOptions = RemoteATCommandOptions.OPTION_NONE;
849                                if (isApplyConfigurationChangesEnabled())
850                                        remoteATCommandOptions |= RemoteATCommandOptions.OPTION_APPLY_CHANGES;
851                                packet = new RemoteATCommandPacket(getNextFrameID(), get64BitAddress(), remote16BitAddress, remoteATCommandOptions, command.getCommand(), command.getParameter());
852                        } else {
853                                if (isApplyConfigurationChangesEnabled())
854                                        packet = new ATCommandPacket(getNextFrameID(), command.getCommand(), command.getParameter());
855                                else
856                                        packet = new ATCommandQueuePacket(getNextFrameID(), command.getCommand(), command.getParameter());
857                        }
858                        if (command.getParameter() == null)
859                                logger.debug(toString() + "Sending AT command '{}'.", command.getCommand());
860                        else
861                                logger.debug(toString() + "Sending AT command '{} {}'.", command.getCommand(), HexUtils.prettyHexString(command.getParameter()));
862                        try {
863                                // Send the packet and build the corresponding response depending on if the device is local or remote.
864                                XBeePacket answerPacket;
865                                if (isRemote())
866                                        answerPacket = localXBeeDevice.sendXBeePacket(packet);
867                                else
868                                        answerPacket = sendXBeePacket(packet);
869                                if (answerPacket instanceof ATCommandResponsePacket)
870                                        response = new ATCommandResponse(command, ((ATCommandResponsePacket)answerPacket).getCommandValue(), ((ATCommandResponsePacket)answerPacket).getStatus());
871                                else if (answerPacket instanceof RemoteATCommandResponsePacket)
872                                        response = new ATCommandResponse(command, ((RemoteATCommandResponsePacket)answerPacket).getCommandValue(), ((RemoteATCommandResponsePacket)answerPacket).getStatus());
873                                
874                                if (response != null && response.getResponse() != null)
875                                        logger.debug(toString() + "AT command response: {}.", HexUtils.prettyHexString(response.getResponse()));
876                                else
877                                        logger.debug(toString() + "AT command response: null.");
878                        } catch (ClassCastException e) {
879                                logger.error("Received an invalid packet type after sending an AT command packet." + e);
880                        }
881                }
882                return response;
883        }
884        
885        /**
886         * Sends the given XBee packet asynchronously.
887         * 
888         * <p>The method will not wait for an answer for the packet.</p>
889         * 
890         * <p>To be notified when the answer is received, use 
891         * {@link #sendXBeePacket(XBeePacket, IPacketReceiveListener)}.</p>
892         * 
893         * @param packet XBee packet to be sent asynchronously.
894         * 
895         * @throws InterfaceNotOpenException if this device connection is not open.
896         * @throws InvalidOperatingModeException if the operating mode is different 
897         *                                       than {@link OperatingMode#API} and 
898         *                                       {@link OperatingMode#API_ESCAPE}.
899         * @throws IOException if an I/O error occurs while sending the XBee packet.
900         * @throws NullPointerException if {@code packet == null}.
901         * 
902         * @see #sendXBeePacket(XBeePacket)
903         * @see #sendXBeePacket(XBeePacket, IPacketReceiveListener)
904         * @see #sendXBeePacketAsync(XBeePacket)
905         * @see com.digi.xbee.api.packet.XBeePacket
906         */
907        protected void sendXBeePacketAsync(XBeePacket packet) 
908                        throws InvalidOperatingModeException, IOException {
909                sendXBeePacket(packet, null);
910        }
911        
912        /**
913         * Sends the given XBee packet asynchronously and registers the given 
914         * packet listener (if not {@code null}) to wait for an answer.
915         * 
916         * <p>The method will not wait for an answer for the packet, but the given 
917         * listener will be notified when the answer arrives.</p>
918         * 
919         * @param packet XBee packet to be sent.
920         * @param packetReceiveListener Listener for the operation, {@code null} 
921         *                              not to be notified when the answer arrives.
922         * 
923         * @throws InterfaceNotOpenException if this device connection is not open.
924         * @throws InvalidOperatingModeException if the operating mode is different 
925         *                                       than {@link OperatingMode#API} and 
926         *                                       {@link OperatingMode#API_ESCAPE}.
927         * @throws IOException if an I/O error occurs while sending the XBee packet.
928         * @throws NullPointerException if {@code packet == null}.
929         * 
930         * @see #sendXBeePacket(XBeePacket)
931         * @see #sendXBeePacket(XBeePacket, IPacketReceiveListener)
932         * @see #sendXBeePacketAsync(XBeePacket)
933         * @see com.digi.xbee.api.listeners.IPacketReceiveListener
934         * @see com.digi.xbee.api.packet.XBeePacket
935         */
936        protected void sendXBeePacket(XBeePacket packet, IPacketReceiveListener packetReceiveListener)
937                        throws InvalidOperatingModeException, IOException {
938                // Check if the packet to send is null.
939                if (packet == null)
940                        throw new NullPointerException("XBee packet cannot be null.");
941                // Check connection.
942                if (!connectionInterface.isOpen())
943                        throw new InterfaceNotOpenException();
944                
945                OperatingMode operatingMode = getOperatingMode();
946                switch (operatingMode) {
947                case AT:
948                case UNKNOWN:
949                default:
950                        throw new InvalidOperatingModeException(operatingMode);
951                case API:
952                case API_ESCAPE:
953                        // Add the required frame ID and subscribe listener if given.
954                        if (packet instanceof XBeeAPIPacket) {
955                                if (((XBeeAPIPacket)packet).needsAPIFrameID()) {
956                                        if (((XBeeAPIPacket)packet).getFrameID() == XBeeAPIPacket.NO_FRAME_ID)
957                                                ((XBeeAPIPacket)packet).setFrameID(getNextFrameID());
958                                        if (packetReceiveListener != null)
959                                                dataReader.addPacketReceiveListener(packetReceiveListener, ((XBeeAPIPacket)packet).getFrameID());
960                                } else if (packetReceiveListener != null)
961                                        dataReader.addPacketReceiveListener(packetReceiveListener);
962                        }
963                        
964                        // Write packet data.
965                        writePacket(packet);
966                        break;
967                }
968        }
969        
970        /**
971         * Sends the given XBee packet synchronously and blocks until response is 
972         * received or receive timeout is reached.
973         * 
974         * <p>The receive timeout is configured using the {@code setReceiveTimeout}
975         * method and can be consulted with {@code getReceiveTimeout} method.</p>
976         * 
977         * <p>Use {@link #sendXBeePacketAsync(XBeePacket)} for non-blocking 
978         * operations.</p>
979         * 
980         * @param packet XBee packet to be sent.
981         * @return An {@code XBeePacket} containing the response of the sent packet 
982         *         or {@code null} if there is no response.
983         * 
984         * @throws InterfaceNotOpenException if this device connection is not open.
985         * @throws InvalidOperatingModeException if the operating mode is different 
986         *                                       than {@link OperatingMode#API} and 
987         *                                       {@link OperatingMode#API_ESCAPE}.
988         * @throws IOException if an I/O error occurs while sending the XBee packet.
989         * @throws NullPointerException if {@code packet == null}.
990         * @throws TimeoutException if the configured time expires while waiting for
991         *                          the packet reply.
992         * 
993         * @see #sendXBeePacket(XBeePacket)
994         * @see #sendXBeePacket(XBeePacket, IPacketReceiveListener)
995         * @see #sendXBeePacketAsync(XBeePacket)
996         * @see XBeeDevice#setReceiveTimeout(int)
997         * @see XBeeDevice#getReceiveTimeout()
998         * @see com.digi.xbee.api.packet.XBeePacket
999         */
1000        protected XBeePacket sendXBeePacket(final XBeePacket packet) 
1001                        throws InvalidOperatingModeException, TimeoutException, IOException {
1002                // Check if the packet to send is null.
1003                if (packet == null)
1004                        throw new NullPointerException("XBee packet cannot be null.");
1005                // Check connection.
1006                if (!connectionInterface.isOpen())
1007                        throw new InterfaceNotOpenException();
1008                
1009                OperatingMode operatingMode = getOperatingMode();
1010                switch (operatingMode) {
1011                case AT:
1012                case UNKNOWN:
1013                default:
1014                        throw new InvalidOperatingModeException(operatingMode);
1015                case API:
1016                case API_ESCAPE:
1017                        // Build response container.
1018                        ArrayList<XBeePacket> responseList = new ArrayList<XBeePacket>();
1019                        
1020                        // If the packet does not need frame ID, send it async. and return null.
1021                        if (packet instanceof XBeeAPIPacket) {
1022                                if (!((XBeeAPIPacket)packet).needsAPIFrameID()) {
1023                                        sendXBeePacketAsync(packet);
1024                                        return null;
1025                                }
1026                        } else {
1027                                sendXBeePacketAsync(packet);
1028                                return null;
1029                        }
1030                        
1031                        // Add the required frame ID to the packet if necessary.
1032                        insertFrameID(packet);
1033                        
1034                        // Generate a packet received listener for the packet to be sent.
1035                        IPacketReceiveListener packetReceiveListener = createPacketReceivedListener(packet, responseList);
1036                        
1037                        // Add the packet listener to the data reader.
1038                        addPacketListener(packetReceiveListener);
1039                        
1040                        // Write the packet data.
1041                        writePacket(packet);
1042                        try {
1043                                // Wait for response or timeout.
1044                                synchronized (responseList) {
1045                                        try {
1046                                                responseList.wait(receiveTimeout);
1047                                        } catch (InterruptedException e) {}
1048                                }
1049                                // After the wait check if we received any response, if not throw timeout exception.
1050                                if (responseList.size() < 1)
1051                                        throw new TimeoutException();
1052                                // Return the received packet.
1053                                return responseList.get(0);
1054                        } finally {
1055                                // Always remove the packet listener from the list.
1056                                removePacketListener(packetReceiveListener);
1057                        }
1058                }
1059        }
1060        
1061        /**
1062         * Insert (if possible) the next frame ID stored in the device to the 
1063         * provided packet.
1064         * 
1065         * @param xbeePacket The packet to add the frame ID.
1066         * 
1067         * @see com.digi.xbee.api.packet.XBeePacket
1068         */
1069        private void insertFrameID(XBeePacket xbeePacket) {
1070                if (xbeePacket instanceof XBeeAPIPacket)
1071                        return;
1072                
1073                if (((XBeeAPIPacket)xbeePacket).needsAPIFrameID() && ((XBeeAPIPacket)xbeePacket).getFrameID() == XBeeAPIPacket.NO_FRAME_ID)
1074                        ((XBeeAPIPacket)xbeePacket).setFrameID(getNextFrameID());
1075        }
1076        
1077        /**
1078         * Returns the packet listener corresponding to the provided sent packet. 
1079         * 
1080         * <p>The listener will filter those packets  matching with the Frame ID of 
1081         * the sent packet storing them in the provided responseList array.</p>
1082         * 
1083         * @param sentPacket The packet sent.
1084         * @param responseList List of packets received that correspond to the 
1085         *                     frame ID of the packet sent.
1086         * 
1087         * @return A packet receive listener that will filter the packets received 
1088         *         corresponding to the sent one.
1089         * 
1090         * @see com.digi.xbee.api.listeners.IPacketReceiveListener
1091         * @see com.digi.xbee.api.packet.XBeePacket
1092         */
1093        private IPacketReceiveListener createPacketReceivedListener(final XBeePacket sentPacket, final ArrayList<XBeePacket> responseList) {
1094                IPacketReceiveListener packetReceiveListener = new IPacketReceiveListener() {
1095                        /*
1096                         * (non-Javadoc)
1097                         * @see com.digi.xbee.api.listeners.IPacketReceiveListener#packetReceived(com.digi.xbee.api.packet.XBeePacket)
1098                         */
1099                        @Override
1100                        public void packetReceived(XBeePacket receivedPacket) {
1101                                // Check if it is the packet we are waiting for.
1102                                if (((XBeeAPIPacket)receivedPacket).checkFrameID((((XBeeAPIPacket)sentPacket).getFrameID()))) {
1103                                        // Security check to avoid class cast exceptions. It has been observed that parallel processes 
1104                                        // using the same connection but with different frame index may collide and cause this exception at some point.
1105                                        if (sentPacket instanceof XBeeAPIPacket
1106                                                        && receivedPacket instanceof XBeeAPIPacket) {
1107                                                XBeeAPIPacket sentAPIPacket = (XBeeAPIPacket)sentPacket;
1108                                                XBeeAPIPacket receivedAPIPacket = (XBeeAPIPacket)receivedPacket;
1109                                                
1110                                                // If the packet sent is an AT command, verify that the received one is an AT command response and 
1111                                                // the command matches in both packets.
1112                                                if (sentAPIPacket.getFrameType() == APIFrameType.AT_COMMAND) {
1113                                                        if (receivedAPIPacket.getFrameType() != APIFrameType.AT_COMMAND_RESPONSE)
1114                                                                return;
1115                                                        if (!((ATCommandPacket)sentAPIPacket).getCommand().equalsIgnoreCase(((ATCommandResponsePacket)receivedPacket).getCommand()))
1116                                                                return;
1117                                                }
1118                                                // If the packet sent is a remote AT command, verify that the received one is a remote AT command response and 
1119                                                // the command matches in both packets.
1120                                                if (sentAPIPacket.getFrameType() == APIFrameType.REMOTE_AT_COMMAND_REQUEST) {
1121                                                        if (receivedAPIPacket.getFrameType() != APIFrameType.REMOTE_AT_COMMAND_RESPONSE)
1122                                                                return;
1123                                                        if (!((RemoteATCommandPacket)sentAPIPacket).getCommand().equalsIgnoreCase(((RemoteATCommandResponsePacket)receivedPacket).getCommand()))
1124                                                                return;
1125                                                }
1126                                        }
1127                                        
1128                                        // Verify that the sent packet is not the received one! This can happen when the echo mode is enabled in the 
1129                                        // serial port.
1130                                        if (!isSamePacket(sentPacket, receivedPacket)) {
1131                                                responseList.add(receivedPacket);
1132                                                synchronized (responseList) {
1133                                                        responseList.notify();
1134                                                }
1135                                        }
1136                                }
1137                        }
1138                };
1139                
1140                return packetReceiveListener;
1141        }
1142        
1143        /**
1144         * Returns whether the sent packet is the same than the received one.
1145         * 
1146         * @param sentPacket The packet sent.
1147         * @param receivedPacket The packet received.
1148         * 
1149         * @return {@code true} if the sent packet is the same than the received 
1150         *         one, {@code false} otherwise.
1151         * 
1152         * @see com.digi.xbee.api.packet.XBeePacket
1153         */
1154        private boolean isSamePacket(XBeePacket sentPacket, XBeePacket receivedPacket) {
1155                // TODO Should not we implement the {@code equals} method in the XBeePacket??
1156                if (HexUtils.byteArrayToHexString(sentPacket.generateByteArray()).equals(HexUtils.byteArrayToHexString(receivedPacket.generateByteArray())))
1157                        return true;
1158                return false;
1159        }
1160        
1161        /**
1162         * Writes the given XBee packet in the connection interface of this device.
1163         * 
1164         * @param packet XBee packet to be written.
1165         * 
1166         * @throws IOException if an I/O error occurs while writing the XBee packet 
1167         *                     in the connection interface.
1168         * 
1169         * @see com.digi.xbee.api.packet.XBeePacket
1170         */
1171        private void writePacket(XBeePacket packet) throws IOException {
1172                logger.debug(toString() + "Sending XBee packet: \n{}", packet.toPrettyString());
1173                // Write bytes with the required escaping mode.
1174                switch (operatingMode) {
1175                case API:
1176                default:
1177                        connectionInterface.writeData(packet.generateByteArray());
1178                        break;
1179                case API_ESCAPE:
1180                        connectionInterface.writeData(packet.generateByteArrayEscaped());
1181                        break;
1182                }
1183        }
1184        
1185        /**
1186         * Returns the next Frame ID of this XBee device.
1187         * 
1188         * @return The next Frame ID.
1189         */
1190        protected int getNextFrameID() {
1191                if (isRemote())
1192                        return localXBeeDevice.getNextFrameID();
1193                if (currentFrameID == 0xff) {
1194                        // Reset counter.
1195                        currentFrameID = 1;
1196                } else
1197                        currentFrameID ++;
1198                return currentFrameID;
1199        }
1200        
1201        /**
1202         * Sends the provided {@code XBeePacket} and determines if the transmission 
1203         * status is success for synchronous transmissions.
1204         * 
1205         * <p>If the status is not success, an {@code TransmitException} is thrown.</p>
1206         * 
1207         * @param packet The {@code XBeePacket} to be sent.
1208         * @param asyncTransmission Determines whether the transmission must be 
1209         *                          asynchronous.
1210         * 
1211         * @throws InterfaceNotOpenException if this device connection is not open.
1212         * @throws NullPointerException if {@code packet == null}.
1213         * @throws TransmitException if {@code packet} is not an instance of 
1214         *                           {@code TransmitStatusPacket} or 
1215         *                           if {@code packet} is not an instance of 
1216         *                           {@code TXStatusPacket} or 
1217         *                           if its transmit status is different than 
1218         *                           {@code XBeeTransmitStatus.SUCCESS}.
1219         * @throws XBeeException if there is any other XBee related error.
1220         * 
1221         * @see com.digi.xbee.api.packet.XBeePacket
1222         */
1223        protected void sendAndCheckXBeePacket(XBeePacket packet, boolean asyncTransmission) throws TransmitException, XBeeException {
1224                XBeePacket receivedPacket = null;
1225                
1226                // Send the XBee packet.
1227                try {
1228                        if (asyncTransmission)
1229                                sendXBeePacketAsync(packet);
1230                        else
1231                                receivedPacket = sendXBeePacket(packet);
1232                } catch (IOException e) {
1233                        throw new XBeeException("Error writing in the communication interface.", e);
1234                }
1235                
1236                // If the transmission is async. we are done.
1237                if (asyncTransmission)
1238                        return;
1239                
1240                // Check if the packet received is a valid transmit status packet.
1241                if (receivedPacket == null)
1242                        throw new TransmitException(null);
1243                if (receivedPacket instanceof TransmitStatusPacket) {
1244                        if (((TransmitStatusPacket)receivedPacket).getTransmitStatus() == null)
1245                                throw new TransmitException(null);
1246                        else if (((TransmitStatusPacket)receivedPacket).getTransmitStatus() != XBeeTransmitStatus.SUCCESS)
1247                                throw new TransmitException(((TransmitStatusPacket)receivedPacket).getTransmitStatus());
1248                } else if (receivedPacket instanceof TXStatusPacket) {
1249                        if (((TXStatusPacket)receivedPacket).getTransmitStatus() == null)
1250                                throw new TransmitException(null);
1251                        else if (((TXStatusPacket)receivedPacket).getTransmitStatus() != XBeeTransmitStatus.SUCCESS)
1252                                throw new TransmitException(((TXStatusPacket)receivedPacket).getTransmitStatus());
1253                } else
1254                        throw new TransmitException(null);
1255        }
1256        
1257        /**
1258         * Sets the configuration of the given IO line of this XBee device.
1259         * 
1260         * @param ioLine The IO line to configure.
1261         * @param ioMode The IO mode to set to the IO line.
1262         * 
1263         * @throws InterfaceNotOpenException if this device connection is not open.
1264         * @throws NullPointerException if {@code ioLine == null} or
1265         *                              if {@code ioMode == null}.
1266         * @throws TimeoutException if there is a timeout sending the set 
1267         *                          configuration command.
1268         * @throws XBeeException if there is any other XBee related exception.
1269         * 
1270         * @see #getIOConfiguration(IOLine)
1271         * @see com.digi.xbee.api.io.IOLine
1272         * @see com.digi.xbee.api.io.IOMode
1273         */
1274        public void setIOConfiguration(IOLine ioLine, IOMode ioMode) throws TimeoutException, XBeeException {
1275                // Check IO line.
1276                if (ioLine == null)
1277                        throw new NullPointerException("IO line cannot be null.");
1278                if (ioMode == null)
1279                        throw new NullPointerException("IO mode cannot be null.");
1280                // Check connection.
1281                if (!connectionInterface.isOpen())
1282                        throw new InterfaceNotOpenException();
1283                
1284                setParameter(ioLine.getConfigurationATCommand(), new byte[]{(byte)ioMode.getID()});
1285        }
1286        
1287        /**
1288         * Returns the configuration mode of the provided IO line of this XBee 
1289         * device.
1290         * 
1291         * @param ioLine The IO line to get its configuration.
1292         * 
1293         * @return The IO mode (configuration) of the provided IO line.
1294         * 
1295         * @throws InterfaceNotOpenException if this device connection is not open.
1296         * @throws NullPointerException if {@code ioLine == null}.
1297         * @throws TimeoutException if there is a timeout sending the get 
1298         *                          configuration command.
1299         * @throws XBeeException if there is any other XBee related exception.
1300         * 
1301         * @see #setIOConfiguration(IOLine, IOMode)
1302         * @see com.digi.xbee.api.io.IOLine
1303         * @see com.digi.xbee.api.io.IOMode
1304         */
1305        public IOMode getIOConfiguration(IOLine ioLine) throws TimeoutException, XBeeException {
1306                // Check IO line.
1307                if (ioLine == null)
1308                        throw new NullPointerException("DIO pin cannot be null.");
1309                // Check connection.
1310                if (!connectionInterface.isOpen())
1311                        throw new InterfaceNotOpenException();
1312                
1313                // Check if the received configuration mode is valid.
1314                int ioModeValue = getParameter(ioLine.getConfigurationATCommand())[0];
1315                IOMode dioMode = IOMode.getIOMode(ioModeValue, ioLine);
1316                if (dioMode == null)
1317                        throw new OperationNotSupportedException("Received configuration mode '" + HexUtils.integerToHexString(ioModeValue, 1) + "' is not valid.");
1318                
1319                // Return the configuration mode.
1320                return dioMode;
1321        }
1322        
1323        /**
1324         * Sets the digital value (high or low) to the provided IO line of this 
1325         * XBee device.
1326         * 
1327         * @param ioLine The IO line to set its value.
1328         * @param ioValue The IOValue to set to the IO line ({@code HIGH} or 
1329         *              {@code LOW}).
1330         * 
1331         * @throws InterfaceNotOpenException if this device connection is not open.
1332         * @throws NullPointerException if {@code ioLine == null} or 
1333         *                              if {@code ioValue == null}.
1334         * @throws TimeoutException if there is a timeout sending the set DIO 
1335         *                          command.
1336         * @throws XBeeException if there is any other XBee related exception.
1337         * 
1338         * @see #getIOConfiguration(IOLine)
1339         * @see #setIOConfiguration(IOLine, IOMode)
1340         * @see com.digi.xbee.api.io.IOLine
1341         * @see com.digi.xbee.api.io.IOValue
1342         * @see com.digi.xbee.api.io.IOMode#DIGITAL_OUT_HIGH
1343         * @see com.digi.xbee.api.io.IOMode#DIGITAL_OUT_LOW
1344         */
1345        public void setDIOValue(IOLine ioLine, IOValue ioValue) throws TimeoutException, XBeeException {
1346                // Check IO line.
1347                if (ioLine == null)
1348                        throw new NullPointerException("IO line cannot be null.");
1349                // Check IO value.
1350                if (ioValue == null)
1351                        throw new NullPointerException("IO value cannot be null.");
1352                // Check connection.
1353                if (!connectionInterface.isOpen())
1354                        throw new InterfaceNotOpenException();
1355                
1356                setParameter(ioLine.getConfigurationATCommand(), new byte[]{(byte)ioValue.getID()});
1357        }
1358        
1359        /**
1360         * Returns the digital value of the provided IO line of this XBee device.
1361         * 
1362         * <p>The provided <b>IO line must be previously configured as digital I/O
1363         * </b>. To do so, use {@code setIOConfiguration} and the following 
1364         * {@code IOMode}:</p>
1365         * 
1366         * <ul>
1367         * <li>{@code IOMode.DIGITAL_IN} to configure as digital input.</li>
1368         * <li>{@code IOMode.DIGITAL_OUT_HIGH} to configure as digital output, high.
1369         * </li>
1370         * <li>{@code IOMode.DIGITAL_OUT_LOW} to configure as digital output, low.
1371         * </li>
1372         * </ul>
1373         * 
1374         * @param ioLine The IO line to get its digital value.
1375         * 
1376         * @return The digital value corresponding to the provided IO line.
1377         * 
1378         * @throws InterfaceNotOpenException if this device connection is not open.
1379         * @throws NullPointerException if {@code ioLine == null}.
1380         * @throws TimeoutException if there is a timeout sending the get IO values 
1381         *                          command.
1382         * @throws XBeeException if there is any other XBee related exception.
1383         * 
1384         * @see #getIOConfiguration(IOLine)
1385         * @see #setIOConfiguration(IOLine, IOMode)
1386         * @see com.digi.xbee.api.io.IOLine
1387         * @see com.digi.xbee.api.io.IOValue
1388         * @see com.digi.xbee.api.io.IOMode#DIGITAL_IN
1389         * @see com.digi.xbee.api.io.IOMode#DIGITAL_OUT_HIGH
1390         * @see com.digi.xbee.api.io.IOMode#DIGITAL_OUT_LOW
1391         */
1392        public IOValue getDIOValue(IOLine ioLine) throws TimeoutException, XBeeException {
1393                // Check IO line.
1394                if (ioLine == null)
1395                        throw new NullPointerException("IO line cannot be null.");
1396                
1397                // Obtain an IO Sample from the XBee device.
1398                IOSample ioSample = readIOSample();
1399                
1400                // Check if the IO sample contains the expected IO line and value.
1401                if (!ioSample.hasDigitalValues() || !ioSample.getDigitalValues().containsKey(ioLine))
1402                        throw new OperationNotSupportedException("Answer does not contain digital data for " + ioLine.getName() + ".");
1403                
1404                // Return the digital value. 
1405                return ioSample.getDigitalValues().get(ioLine);
1406        }
1407        
1408        /**
1409         * Sets the duty cycle (in %) of the provided IO line of this XBee device. 
1410         * 
1411         * <p>The provided <b>IO line must be</b>:</p>
1412         * 
1413         * <ul>
1414         * <li><b>PWM capable</b> ({@link IOLine#hasPWMCapability()}).</li>
1415         * <li>Previously <b>configured as PWM Output</b> (use 
1416         * {@code setIOConfiguration} and {@code IOMode.PWM}).</li>
1417         * </ul>
1418         * 
1419         * @param ioLine The IO line to set its duty cycle value.
1420         * @param dutyCycle The duty cycle of the PWM.
1421         * 
1422         * @throws IllegalArgumentException if {@code ioLine.hasPWMCapability() == false} or 
1423         *                                  if {@code value < 0} or
1424         *                                  if {@code value > 1023}.
1425         * @throws InterfaceNotOpenException if this device connection is not open.
1426         * @throws NullPointerException if {@code ioLine == null}.
1427         * @throws TimeoutException if there is a timeout sending the set PWM duty 
1428         *                          cycle command.
1429         * @throws XBeeException if there is any other XBee related exception.
1430         * 
1431         * @see #getIOConfiguration(IOLine)
1432         * @see #getIOConfiguration(IOLine)
1433         * @see #setIOConfiguration(IOLine, IOMode)
1434         * @see #getPWMDutyCycle(IOLine)
1435         * @see com.digi.xbee.api.io.IOLine
1436         * @see com.digi.xbee.api.io.IOMode#PWM
1437         */
1438        public void setPWMDutyCycle(IOLine ioLine, double dutyCycle) throws TimeoutException, XBeeException {
1439                // Check IO line.
1440                if (ioLine == null)
1441                        throw new NullPointerException("IO line cannot be null.");
1442                // Check if the IO line has PWM capability.
1443                if (!ioLine.hasPWMCapability())
1444                        throw new IllegalArgumentException("Provided IO line does not have PWM capability.");
1445                // Check duty cycle limits.
1446                if (dutyCycle < 0 || dutyCycle > 100)
1447                        throw new IllegalArgumentException("Duty Cycle must be between 0% and 100%.");
1448                // Check connection.
1449                if (!connectionInterface.isOpen())
1450                        throw new InterfaceNotOpenException();
1451                
1452                // Convert the value.
1453                int finaldutyCycle = (int)(dutyCycle * 1023.0/100.0);
1454                
1455                setParameter(ioLine.getPWMDutyCycleATCommand(), ByteUtils.intToByteArray(finaldutyCycle));
1456        }
1457        
1458        /**
1459         * Gets the PWM duty cycle (in %) corresponding to the provided IO line of 
1460         * this XBee device.
1461         * 
1462         * <p>The provided <b>IO line must be</b>:</p>
1463         * 
1464         * <ul>
1465         * <li><b>PWM capable</b> ({@link IOLine#hasPWMCapability()}).</li>
1466         * <li>Previously <b>configured as PWM Output</b> (use 
1467         * {@code setIOConfiguration} and {@code IOMode.PWM}).</li>
1468         * </ul>
1469         * 
1470         * @param ioLine The IO line to get its PWM duty cycle.
1471         * 
1472         * @return The PWM duty cycle value corresponding to the provided IO line 
1473         *         (0% - 100%).
1474         * 
1475         * @throws IllegalArgumentException if {@code ioLine.hasPWMCapability() == false}.
1476         * @throws InterfaceNotOpenException if this device connection is not open.
1477         * @throws NullPointerException if {@code ioLine == null}.
1478         * @throws TimeoutException if there is a timeout sending the get PWM duty 
1479         *                          cycle command.
1480         * @throws XBeeException if there is any other XBee related exception.
1481         * 
1482         * @see #getIOConfiguration(IOLine)
1483         * @see #setIOConfiguration(IOLine, IOMode)
1484         * @see #setPWMDutyCycle(IOLine, double)
1485         * @see com.digi.xbee.api.io.IOLine
1486         * @see com.digi.xbee.api.io.IOMode#PWM
1487         */
1488        public double getPWMDutyCycle(IOLine ioLine) throws TimeoutException, XBeeException {
1489                // Check IO line.
1490                if (ioLine == null)
1491                        throw new NullPointerException("IO line cannot be null.");
1492                // Check if the IO line has PWM capability.
1493                if (!ioLine.hasPWMCapability())
1494                        throw new IllegalArgumentException("Provided IO line does not have PWM capability.");
1495                // Check connection.
1496                if (!connectionInterface.isOpen())
1497                        throw new InterfaceNotOpenException();
1498                
1499                byte[] value = getParameter(ioLine.getPWMDutyCycleATCommand());
1500                
1501                // Return the PWM duty cycle value.
1502                int readValue = ByteUtils.byteArrayToInt(value);
1503                return Math.round((readValue * 100.0/1023.0) * 100.0) / 100.0;
1504        }
1505        
1506        /**
1507         * Returns the analog value of the provided IO line of this XBee device.
1508         * 
1509         * <p>The provided <b>IO line must be previously configured as ADC</b>. To 
1510         * do so, use {@code setIOConfiguration} and {@code IOMode.ADC}.</p>
1511         * 
1512         * @param ioLine The IO line to get its analog value.
1513         * 
1514         * @return The analog value corresponding to the provided IO line.
1515         * 
1516         * @throws InterfaceNotOpenException if this device connection is not open.
1517         * @throws NullPointerException if {@code ioLine == null}.
1518         * @throws TimeoutException if there is a timeout sending the get IO values
1519         *                          command.
1520         * @throws XBeeException if there is any other XBee related exception.
1521         * 
1522         * @see #getIOConfiguration(IOLine)
1523         * @see #setIOConfiguration(IOLine, IOMode)
1524         * @see com.digi.xbee.api.io.IOLine
1525         * @see com.digi.xbee.api.io.IOMode#ADC
1526         */
1527        public int getADCValue(IOLine ioLine) throws TimeoutException, XBeeException {
1528                // Check IO line.
1529                if (ioLine == null)
1530                        throw new NullPointerException("IO line cannot be null.");
1531                
1532                // Obtain an IO Sample from the XBee device.
1533                IOSample ioSample = readIOSample();
1534                
1535                // Check if the IO sample contains the expected IO line and value.
1536                if (!ioSample.hasAnalogValues() || !ioSample.getAnalogValues().containsKey(ioLine))
1537                        throw new OperationNotSupportedException("Answer does not contain analog data for " + ioLine.getName() + ".");
1538                
1539                // Return the analog value.
1540                return ioSample.getAnalogValues().get(ioLine);
1541        }
1542        
1543        /**
1544         * Sets the 64-bit destination extended address of this XBee device.
1545         * 
1546         * <p>{@link XBee64BitAddress#BROADCAST_ADDRESS} is the broadcast address 
1547         * for the PAN. {@link XBee64BitAddress#COORDINATOR_ADDRESS} can be used to 
1548         * address the Pan Coordinator.</p>
1549         * 
1550         * @param xbee64BitAddress 64-bit destination address to be configured.
1551         * 
1552         * @throws InterfaceNotOpenException if this device connection is not open.
1553         * @throws NullPointerException if {@code xbee64BitAddress == null}.
1554         * @throws TimeoutException if there is a timeout sending the set 
1555         *                          destination address command.
1556         * @throws XBeeException if there is any other XBee related exception.
1557         * 
1558         * @see #getDestinationAddress()
1559         * @see com.digi.xbee.api.models.XBee64BitAddress
1560         */
1561        public void setDestinationAddress(XBee64BitAddress xbee64BitAddress) throws TimeoutException, XBeeException {
1562                if (xbee64BitAddress == null)
1563                        throw new NullPointerException("Address cannot be null.");
1564                
1565                // This method needs to apply changes after modifying the destination 
1566                // address, but only if the destination address could be set successfully.
1567                boolean applyChanges = isApplyConfigurationChangesEnabled();
1568                if (applyChanges)
1569                        enableApplyConfigurationChanges(false);
1570                
1571                byte[] address = xbee64BitAddress.getValue();
1572                try {
1573                        setParameter("DH", Arrays.copyOfRange(address, 0, 4));
1574                        setParameter("DL", Arrays.copyOfRange(address, 4, 8));
1575                        applyChanges();
1576                } finally {
1577                        // Always restore the old value of the AC.
1578                        enableApplyConfigurationChanges(applyChanges);
1579                }
1580        }
1581        
1582        /**
1583         * Returns the 64-bit destination extended address of this XBee device.
1584         * 
1585         * <p>{@link XBee64BitAddress#BROADCAST_ADDRESS} is the broadcast address 
1586         * for the PAN. {@link XBee64BitAddress#COORDINATOR_ADDRESS} can be used to 
1587         * address the Pan Coordinator.</p>
1588         * 
1589         * @return 64-bit destination address.
1590         * 
1591         * @throws InterfaceNotOpenException if this device connection is not open.
1592         * @throws TimeoutException if there is a timeout sending the get
1593         *                          destination address command.
1594         * @throws XBeeException if there is any other XBee related exception.
1595         * 
1596         * @see #setDestinationAddress(XBee64BitAddress)
1597         * @see com.digi.xbee.api.models.XBee64BitAddress
1598         */
1599        public XBee64BitAddress getDestinationAddress() throws TimeoutException, XBeeException {
1600                byte[] dh = getParameter("DH");
1601                byte[] dl = getParameter("DL");
1602                byte[] address = new byte[dh.length + dl.length];
1603                
1604                System.arraycopy(dh, 0, address, 0, dh.length);
1605                System.arraycopy(dl, 0, address, dh.length, dl.length);
1606                
1607                return new XBee64BitAddress(address);
1608        }
1609        
1610        /**
1611         * Sets the IO sampling rate to enable periodic sampling in this XBee 
1612         * device.
1613         * 
1614         * <p>A sample rate of {@code 0} ms. disables this feature.</p>
1615         * 
1616         * <p>All enabled digital IO and analog inputs will be sampled and
1617         * transmitted every {@code rate} milliseconds to the configured destination
1618         * address.</p>
1619         * 
1620         * <p>The destination address can be configured using the 
1621         * {@code setDestinationAddress(XBee64BitAddress)} method and retrieved by 
1622         * {@code getDestinationAddress()}.</p>
1623         * 
1624         * @param rate IO sampling rate in milliseconds.
1625         * 
1626         * @throws IllegalArgumentException if {@code rate < 0} or {@code rate >
1627         *                                  0xFFFF}.
1628         * @throws InterfaceNotOpenException if this device connection is not open.
1629         * @throws TimeoutException if there is a timeout sending the set IO
1630         *                          sampling rate command.
1631         * @throws XBeeException if there is any other XBee related exception.
1632         * 
1633         * @see #getDestinationAddress()
1634         * @see #setDestinationAddress(XBee64BitAddress)
1635         * @see #getIOSamplingRate()
1636         */
1637        public void setIOSamplingRate(int rate) throws TimeoutException, XBeeException {
1638                // Check range.
1639                if (rate < 0 || rate > 0xFFFF)
1640                        throw new IllegalArgumentException("Rate must be between 0 and 0xFFFF.");
1641                // Check connection.
1642                if (!connectionInterface.isOpen())
1643                        throw new InterfaceNotOpenException();
1644                
1645                setParameter("IR", ByteUtils.intToByteArray(rate));
1646        }
1647        
1648        /**
1649         * Returns the IO sampling rate of this XBee device.
1650         * 
1651         * <p>A sample rate of {@code 0} means the IO sampling feature is disabled.
1652         * </p>
1653         * 
1654         * <p>Periodic sampling allows this XBee module to take an IO sample and 
1655         * transmit it to a remote device (configured in the destination address) 
1656         * at the configured periodic rate (ms).</p>
1657         * 
1658         * @return IO sampling rate in milliseconds.
1659         * 
1660         * @throws InterfaceNotOpenException if this device connection is not open.
1661         * @throws TimeoutException if there is a timeout sending the get IO
1662         *                          sampling rate command.
1663         * @throws XBeeException if there is any other XBee related exception.
1664         * 
1665         * @see #getDestinationAddress()
1666         * @see #setDestinationAddress(XBee64BitAddress)
1667         * @see #setIOSamplingRate(int)
1668         */
1669        public int getIOSamplingRate() throws TimeoutException, XBeeException {
1670                // Check connection.
1671                if (!connectionInterface.isOpen())
1672                        throw new InterfaceNotOpenException();
1673                
1674                byte[] rate = getParameter("IR");
1675                return ByteUtils.byteArrayToInt(rate);
1676        }
1677        
1678        /**
1679         * Sets the digital IO lines of this XBee device to be monitored and 
1680         * sampled whenever their status changes.
1681         * 
1682         * <p>A {@code null} set of lines disables this feature.</p>
1683         * 
1684         * <p>If a change is detected on an enabled digital IO pin, a digital IO
1685         * sample is immediately transmitted to the configured destination address.
1686         * </p>
1687         * 
1688         * <p>The destination address can be configured using the 
1689         * {@code setDestinationAddress(XBee64BitAddress)} method and retrieved by 
1690         * {@code getDestinationAddress()}.</p>
1691         * 
1692         * @param lines Set of IO lines to be monitored, {@code null} to disable 
1693         *              this feature.
1694         * 
1695         * @throws InterfaceNotOpenException if this device connection is not open.
1696         * @throws TimeoutException if there is a timeout sending the set DIO
1697         *                          change detection command.
1698         * @throws XBeeException if there is any other XBee related exception.
1699         * 
1700         * @see #getDestinationAddress()
1701         * @see #getDIOChangeDetection()
1702         * @see #setDestinationAddress(XBee64BitAddress)
1703         */
1704        public void setDIOChangeDetection(Set<IOLine> lines) throws TimeoutException, XBeeException {
1705                // Check connection.
1706                if (!connectionInterface.isOpen())
1707                        throw new InterfaceNotOpenException();
1708                
1709                byte[] bitfield = new byte[2];
1710                
1711                if (lines != null) {
1712                        for (IOLine line : lines) {
1713                                int i = line.getIndex();
1714                                if (i < 8)
1715                                        bitfield[1] = (byte) (bitfield[1] | (1 << i));
1716                                else
1717                                        bitfield[0] = (byte) (bitfield[0] | (1 << i - 8));
1718                        }
1719                }
1720                
1721                setParameter("IC", bitfield);
1722        }
1723        
1724        /**
1725         * Returns the set of IO lines of this device that are monitored for 
1726         * change detection.
1727         * 
1728         * <p>A {@code null} set means the DIO change detection feature is disabled.
1729         * </p>
1730         * 
1731         * <p>Modules can be configured to transmit to the configured destination 
1732         * address a data sample immediately whenever a monitored digital IO line 
1733         * changes state.</p> 
1734         * 
1735         * @return Set of digital IO lines that are monitored for change detection,
1736         *         {@code null} if there are no monitored lines.
1737         * 
1738         * @throws InterfaceNotOpenException if this device connection is not open.
1739         * @throws TimeoutException if there is a timeout sending the get DIO
1740         *                          change detection command.
1741         * @throws XBeeException if there is any other XBee related exception.
1742         * 
1743         * @see #getDestinationAddress()
1744         * @see #setDestinationAddress(XBee64BitAddress)
1745         * @see #setDIOChangeDetection(Set)
1746         */
1747        public Set<IOLine> getDIOChangeDetection() throws TimeoutException, XBeeException {
1748                // Check connection.
1749                if (!connectionInterface.isOpen())
1750                        throw new InterfaceNotOpenException();
1751                
1752                byte[] bitfield = getParameter("IC");
1753                TreeSet<IOLine> lines = new TreeSet<IOLine>();
1754                int mask = (bitfield[0] << 8) + (bitfield[1] & 0xFF);
1755                
1756                for (int i = 0; i < 16; i++) {
1757                        if (ByteUtils.isBitEnabled(mask, i))
1758                                lines.add(IOLine.getDIO(i));
1759                }
1760                
1761                if (lines.size() > 0)
1762                        return lines;
1763                return null;
1764        }
1765        
1766        /**
1767         * Applies changes to all command registers causing queued command register
1768         * values to be applied.
1769         * 
1770         * <p>This method must be invoked if the 'apply configuration changes' 
1771         * option is disabled and the changes to this XBee device parameters must 
1772         * be applied.</p>
1773         * 
1774         * <p>To know if the 'apply configuration changes' option is enabled, use 
1775         * the {@code isApplyConfigurationChangesEnabled()} method. And to 
1776         * enable/disable this feature, the method 
1777         * {@code enableApplyConfigurationChanges(boolean)}.</p>
1778         * 
1779         * <p>Applying changes does not imply the modifications will persist 
1780         * through subsequent resets. To do so, use the {@code writeChanges()} 
1781         * method.</p>
1782         * 
1783         * @throws InterfaceNotOpenException if this device connection is not open.
1784         * @throws TimeoutException if there is a timeout sending the get Apply
1785         *                          Changes command.
1786         * @throws XBeeException if there is any other XBee related exception.
1787         * 
1788         * @see #enableApplyConfigurationChanges(boolean)
1789         * @see #isApplyConfigurationChangesEnabled()
1790         * @see #setParameter(String, byte[])
1791         * @see #writeChanges()
1792         */
1793        public void applyChanges() throws TimeoutException, XBeeException {
1794                executeParameter("AC");
1795        }
1796        
1797        /**
1798         * Checks if the provided {@code ATCommandResponse} is valid throwing an 
1799         * {@code ATCommandException} in case it is not.
1800         * 
1801         * @param response The {@code ATCommandResponse} to check.
1802         * 
1803         * @throws ATCommandException if {@code response == null} or 
1804         *                            if {@code response.getResponseStatus() != ATCommandStatus.OK}.
1805         * 
1806         * @see com.digi.xbee.api.models.ATCommandResponse
1807         */
1808        protected void checkATCommandResponseIsValid(ATCommandResponse response) throws ATCommandException {
1809                if (response == null || response.getResponseStatus() == null)
1810                        throw new ATCommandException(null);
1811                else if (response.getResponseStatus() != ATCommandStatus.OK)
1812                        throw new ATCommandException(response.getResponseStatus());
1813        }
1814        
1815        /**
1816         * Returns an IO sample from this XBee device containing the value of all
1817         * enabled digital IO and analog input channels.
1818         * 
1819         * @return An IO sample containing the value of all enabled digital IO and
1820         *         analog input channels.
1821         * 
1822         * @throws InterfaceNotOpenException if this device connection is not open.
1823         * @throws TimeoutException if there is a timeout getting the IO sample.
1824         * @throws XBeeException if there is any other XBee related exception.
1825         * 
1826         * @see com.digi.xbee.api.io.IOSample
1827         */
1828        public IOSample readIOSample() throws TimeoutException, XBeeException {
1829                // Check connection.
1830                if (!connectionInterface.isOpen())
1831                        throw new InterfaceNotOpenException();
1832                
1833                // Try to build an IO Sample from the sample payload.
1834                byte[] samplePayload = null;
1835                IOSample ioSample;
1836                
1837                // The response to the IS command in local 802.15.4 devices is empty, 
1838                // so we have to create a packet listener to receive the IO sample.
1839                if (!isRemote() && getXBeeProtocol() == XBeeProtocol.RAW_802_15_4) {
1840                        executeParameter("IS");
1841                        samplePayload = receiveRaw802IOPacket();
1842                        if (samplePayload == null)
1843                                throw new TimeoutException("Timeout waiting for the IO response packet.");
1844                } else
1845                        samplePayload = getParameter("IS");
1846                
1847                try {
1848                        ioSample = new IOSample(samplePayload);
1849                } catch (IllegalArgumentException e) {
1850                        throw new XBeeException("Couldn't create the IO sample.", e);
1851                } catch (NullPointerException e) {
1852                        throw new XBeeException("Couldn't create the IO sample.", e);
1853                }
1854                return ioSample;
1855        }
1856        
1857        /**
1858         * Returns the latest 802.15.4 IO packet and returns its value.
1859         * 
1860         * @return The value of the latest received 802.15.4 IO packet.
1861         */
1862        private byte[] receiveRaw802IOPacket() {
1863                ioPacketReceived = false;
1864                ioPacketPayload = null;
1865                addPacketListener(IOPacketReceiveListener);
1866                synchronized (ioLock) {
1867                        try {
1868                                ioLock.wait(receiveTimeout);
1869                        } catch (InterruptedException e) { }
1870                }
1871                removePacketListener(IOPacketReceiveListener);
1872                if (ioPacketReceived)
1873                        return ioPacketPayload;
1874                return null;
1875        }
1876        
1877        /**
1878         * Custom listener for 802.15.4 IO packets. It will try to receive an 
1879         * 802.15.4 IO sample packet.
1880         * 
1881         * <p>When an IO sample packet is received, it saves its payload and 
1882         * notifies the object that was waiting for the reception.</p>
1883         */
1884        private IPacketReceiveListener IOPacketReceiveListener = new IPacketReceiveListener() {
1885                /*
1886                 * (non-Javadoc)
1887                 * @see com.digi.xbee.api.listeners.IPacketReceiveListener#packetReceived(com.digi.xbee.api.packet.XBeePacket)
1888                 */
1889                @Override
1890                public void packetReceived(XBeePacket receivedPacket) {
1891                        // Discard non API packets.
1892                        if (!(receivedPacket instanceof XBeeAPIPacket))
1893                                return;
1894                        // If we already have received an IO packet, ignore this packet.
1895                        if (ioPacketReceived)
1896                                return;
1897                        
1898                        // Save the packet value (IO sample payload)
1899                        switch (((XBeeAPIPacket)receivedPacket).getFrameType()) {
1900                        case IO_DATA_SAMPLE_RX_INDICATOR:
1901                                ioPacketPayload = ((IODataSampleRxIndicatorPacket)receivedPacket).getRFData();
1902                                break;
1903                        case RX_IO_16:
1904                                ioPacketPayload = ((RX16IOPacket)receivedPacket).getRFData();
1905                                break;
1906                        case RX_IO_64:
1907                                ioPacketPayload = ((RX64IOPacket)receivedPacket).getRFData();
1908                                break;
1909                        default:
1910                                return;
1911                        }
1912                        // Set the IO packet received flag.
1913                        ioPacketReceived = true;
1914                        
1915                        // Continue execution by notifying the lock object.
1916                        synchronized (ioLock) {
1917                                ioLock.notify();
1918                        }
1919                }
1920        };
1921        
1922        /**
1923         * Performs a software reset on this XBee device and blocks until the 
1924         * process is completed.
1925         * 
1926         * @throws TimeoutException if the configured time expires while waiting 
1927         *                          for the command reply.
1928         * @throws XBeeException if there is any other XBee related exception.
1929         */
1930        abstract public void reset() throws TimeoutException, XBeeException;
1931        
1932        /**
1933         * Sets the given parameter with the provided value in this XBee device.
1934         * 
1935         * <p>If the 'apply configuration changes' option is enabled in this device,
1936         * the configured value for the given parameter will be immediately applied, 
1937         * if not the method {@code applyChanges()} must be invoked to apply it.</p>
1938         * 
1939         * <p>Use:</p>
1940         * <ul>
1941         * <li>Method {@code isApplyConfigurationChangesEnabled()} to know 
1942         * if the 'apply configuration changes' option is enabled.</li>
1943         * <li>Method {@code enableApplyConfigurationChanges(boolean)} to enable or
1944         * disable this option.</li>
1945         * </ul>
1946         * 
1947         * <p>To make parameter modifications persist through subsequent resets use 
1948         * the {@code writeChanges()} method.</p>
1949         * 
1950         * @param parameter The name of the parameter to be set.
1951         * @param parameterValue The value of the parameter to set.
1952         * 
1953         * @throws IllegalArgumentException if {@code parameter.length() != 2}.
1954         * @throws NullPointerException if {@code parameter == null} or 
1955         *                              if {@code parameterValue == null}.
1956         * @throws TimeoutException if there is a timeout setting the parameter.
1957         * @throws XBeeException if {@code parameter} is not supported by the module or
1958         *                       if {@code parameterValue} is not supported or
1959         *                       if there is any other XBee related exception.
1960         * 
1961         * @see #applyChanges()
1962         * @see #enableApplyConfigurationChanges(boolean)
1963         * @see #executeParameter(String) 
1964         * @see #getParameter(String)
1965         * @see #isApplyConfigurationChangesEnabled()
1966         * @see #writeChanges()
1967         */
1968        public void setParameter(String parameter, byte[] parameterValue) throws TimeoutException, XBeeException {
1969                if (parameterValue == null)
1970                        throw new NullPointerException("Value of the parameter cannot be null.");
1971                
1972                sendParameter(parameter, parameterValue);
1973        }
1974        
1975        /**
1976         * Gets the value of the given parameter from this XBee device.
1977         * 
1978         * @param parameter The name of the parameter to retrieve its value.
1979         * 
1980         * @return A byte array containing the value of the parameter.
1981         * 
1982         * @throws IllegalArgumentException if {@code parameter.length() != 2}.
1983         * @throws NullPointerException if {@code parameter == null}.
1984         * @throws TimeoutException if there is a timeout getting the parameter value.
1985         * @throws XBeeException if {@code parameter} is not supported by the module or
1986         *                       if there is any other XBee related exception.
1987         * 
1988         * @see #executeParameter(String)
1989         * @see #setParameter(String, byte[])
1990         */
1991        public byte[] getParameter(String parameter) throws TimeoutException, XBeeException {
1992                byte[] parameterValue = sendParameter(parameter, null);
1993                
1994                // Check if the response is null, if so throw an exception (maybe it was a write-only parameter).
1995                if (parameterValue == null)
1996                        throw new OperationNotSupportedException("Couldn't get the '" + parameter + "' value.");
1997                return parameterValue;
1998        }
1999        
2000        /**
2001         * Executes the given command in this XBee device.
2002         * 
2003         * <p>This method is intended to be used for those AT parameters that cannot 
2004         * be read or written, they just execute some action in the XBee module.</p>
2005         * 
2006         * @param parameter The AT command to be executed.
2007         * 
2008         * @throws IllegalArgumentException if {@code parameter.length() != 2}.
2009         * @throws NullPointerException if {@code parameter == null}.
2010         * @throws TimeoutException if there is a timeout executing the parameter.
2011         * @throws XBeeException if {@code parameter} is not supported by the module or
2012         *                       if there is any other XBee related exception.
2013         * 
2014         * @see #getParameter(String)
2015         * @see #setParameter(String, byte[])
2016         */
2017        public void executeParameter(String parameter) throws TimeoutException, XBeeException {
2018                sendParameter(parameter, null);
2019        }
2020        
2021        /**
2022         * Sends the given AT parameter to this XBee device with an optional 
2023         * argument or value and returns the response (likely the value) of that 
2024         * parameter in a byte array format.
2025         * 
2026         * @param parameter The name of the AT command to be executed.
2027         * @param parameterValue The value of the parameter to set (if any).
2028         * 
2029         * @return A byte array containing the value of the parameter.
2030         * 
2031         * @throws IllegalArgumentException if {@code parameter.length() != 2}.
2032         * @throws NullPointerException if {@code parameter == null}.
2033         * @throws TimeoutException if there is a timeout executing the parameter.
2034         * @throws XBeeException if {@code parameter} is not supported by the module or
2035         *                       if {@code parameterValue} is not supported or
2036         *                       if there is any other XBee related exception.
2037         * 
2038         * @see #getParameter(String)
2039         * @see #executeParameter(String)
2040         * @see #setParameter(String, byte[])
2041         */
2042        private byte[] sendParameter(String parameter, byte[] parameterValue) throws TimeoutException, XBeeException {
2043                if (parameter == null)
2044                        throw new NullPointerException("Parameter cannot be null.");
2045                if (parameter.length() != 2)
2046                        throw new IllegalArgumentException("Parameter must contain exactly 2 characters.");
2047                
2048                ATCommand atCommand = new ATCommand(parameter, parameterValue);
2049                
2050                // Create and send the AT Command.
2051                ATCommandResponse response = null;
2052                try {
2053                        response = sendATCommand(atCommand);
2054                } catch (IOException e) {
2055                        throw new XBeeException("Error writing in the communication interface.", e);
2056                }
2057                
2058                // Check if AT Command response is valid.
2059                checkATCommandResponseIsValid(response);
2060                
2061                // Return the response value.
2062                return response.getResponse();
2063        }
2064        
2065        /*
2066         * (non-Javadoc)
2067         * @see java.lang.Object#toString()
2068         */
2069        @Override
2070        public String toString() {
2071                return connectionInterface.toString();
2072        }
2073        
2074        /**
2075         * Enables or disables the 'apply configuration changes' option for this 
2076         * device.
2077         * 
2078         * <p>Enabling this option means that when any parameter of this XBee 
2079         * device is set, it will be also applied.</p>
2080         * 
2081         * <p>If this option is disabled, the method {@code applyChanges()} must be 
2082         * used in order to apply the changes in all the parameters that were 
2083         * previously set.</p>
2084         * 
2085         * @param enabled {@code true} to apply configuration changes when an XBee 
2086         *                parameter is set, {@code false} otherwise.
2087         * 
2088         * @see #applyChanges()
2089         * @see #isApplyConfigurationChangesEnabled()
2090         */
2091        public void enableApplyConfigurationChanges(boolean enabled) {
2092                applyConfigurationChanges = enabled;
2093        }
2094        
2095        /**
2096         * Returns whether the 'apply configuration changes' option is enabled in 
2097         * this device.
2098         * 
2099         * <p>If this option is enabled, when any parameter of this XBee device is 
2100         * set, it will be also applied.</p>
2101         * 
2102         * <p>If this option is disabled, the method {@code applyChanges()} must be 
2103         * used in order to apply the changes in all the parameters that were 
2104         * previously set.</p>
2105         * 
2106         * @return {@code true} if the option is enabled, {@code false} otherwise.
2107         * 
2108         * @see #applyChanges()
2109         * @see #enableApplyConfigurationChanges(boolean)
2110         */
2111        public boolean isApplyConfigurationChangesEnabled() {
2112                return applyConfigurationChanges;
2113        }
2114        
2115        /**
2116         * Configures the 16-bit address (network address) of this XBee device with 
2117         * the provided one.
2118         * 
2119         * @param xbee16BitAddress The new 16-bit address.
2120         * 
2121         * @throws InterfaceNotOpenException if this device connection is not open.
2122         * @throws NullPointerException if {@code xbee16BitAddress == null}.
2123         * @throws TimeoutException if there is a timeout setting the address.
2124         * @throws XBeeException if there is any other XBee related exception.
2125         * 
2126         * @see #get16BitAddress()
2127         * @see com.digi.xbee.api.models.XBee16BitAddress
2128         */
2129        protected void set16BitAddress(XBee16BitAddress xbee16BitAddress) throws TimeoutException, XBeeException {
2130                if (xbee16BitAddress == null)
2131                        throw new NullPointerException("16-bit address canot be null.");
2132                
2133                setParameter("MY", xbee16BitAddress.getValue());
2134                
2135                this.xbee16BitAddress = xbee16BitAddress;
2136        }
2137        
2138        /**
2139         * Returns the operating PAN ID (Personal Area Network Identifier) of 
2140         * this XBee device.
2141         * 
2142         * <p>For modules to communicate they must be configured with the same 
2143         * identifier. Only modules with matching IDs can communicate with each 
2144         * other.This parameter allows multiple networks to co-exist on the same 
2145         * physical channel.</p>
2146         * 
2147         * @return The operating PAN ID of this XBee device.
2148         * 
2149         * @throws InterfaceNotOpenException if this device connection is not open.
2150         * @throws TimeoutException if there is a timeout getting the PAN ID.
2151         * @throws XBeeException if there is any other XBee related exception.
2152         * 
2153         * @see #setPANID(byte[])
2154         */
2155        public byte[] getPANID() throws TimeoutException, XBeeException {
2156                switch (getXBeeProtocol()) {
2157                case ZIGBEE:
2158                        return getParameter("OP");
2159                default:
2160                        return getParameter("ID");
2161                }
2162        }
2163        
2164        /**
2165         * Sets the PAN ID (Personal Area Network Identifier) of this XBee device.
2166         * 
2167         * <p>For modules to communicate they must be configured with the same 
2168         * identifier. Only modules with matching IDs can communicate with each 
2169         * other.This parameter allows multiple networks to co-exist on the same 
2170         * physical channel.</p>
2171         * 
2172         * @param panID The new PAN ID of this XBee device.
2173         * 
2174         * @throws IllegalArgumentException if {@code panID.length == 0} or 
2175         *                                  if {@code panID.length > 8}.
2176         * @throws InterfaceNotOpenException if this device connection is not open.
2177         * @throws NullPointerException if {@code panID == null}.
2178         * @throws TimeoutException if there is a timeout setting the PAN ID.
2179         * @throws XBeeException if there is any other XBee related exception.
2180         * 
2181         * @see #getPANID()
2182         */
2183        public void setPANID(byte[] panID) throws TimeoutException, XBeeException {
2184                if (panID == null)
2185                        throw new NullPointerException("PAN ID cannot be null.");
2186                if (panID.length == 0)
2187                        throw new IllegalArgumentException("Length of the PAN ID cannot be 0.");
2188                if (panID.length > 8)
2189                        throw new IllegalArgumentException("Length of the PAN ID cannot be longer than 8 bytes.");
2190                
2191                setParameter("ID", panID);
2192        }
2193        
2194        /**
2195         * Returns the output power level at which this XBee device transmits 
2196         * conducted power.
2197         * 
2198         * @return The output power level of this XBee device.
2199         * 
2200         * @throws InterfaceNotOpenException if this device connection is not open.
2201         * @throws TimeoutException if there is a timeout getting the power level.
2202         * @throws XBeeException if there is any other XBee related exception.
2203         * 
2204         * @see #setPowerLevel(PowerLevel)
2205         * @see com.digi.xbee.api.models.PowerLevel
2206         */
2207        public PowerLevel getPowerLevel() throws TimeoutException, XBeeException {
2208                byte[] powerLevelValue = getParameter("PL");
2209                return PowerLevel.get(ByteUtils.byteArrayToInt(powerLevelValue));
2210        }
2211        
2212        /**
2213         * Sets the output power level at which this XBee device transmits 
2214         * conducted power.
2215         * 
2216         * @param powerLevel The new output power level to be set in this XBee 
2217         *                   device.
2218         * 
2219         * @throws InterfaceNotOpenException if this device connection is not open.
2220         * @throws NullPointerException if {@code powerLevel == null}.
2221         * @throws TimeoutException if there is a timeout setting the power level.
2222         * @throws XBeeException if there is any other XBee related exception.
2223         * 
2224         * @see #getPowerLevel()
2225         * @see com.digi.xbee.api.models.PowerLevel
2226         */
2227        public void setPowerLevel(PowerLevel powerLevel) throws TimeoutException, XBeeException {
2228                if (powerLevel == null)
2229                        throw new NullPointerException("Power level cannot be null.");
2230                
2231                setParameter("PL", ByteUtils.intToByteArray(powerLevel.getValue()));
2232        }
2233        
2234        /**
2235         * Returns the current association status of this XBee device.
2236         * 
2237         * <p>It indicates occurrences of errors during the last association 
2238         * request.</p>
2239         * 
2240         * @return The association indication status of the XBee device.
2241         * 
2242         * @throws InterfaceNotOpenException if this device connection is not open.
2243         * @throws TimeoutException if there is a timeout getting the association 
2244         *                          indication status.
2245         * @throws XBeeException if there is any other XBee related exception.
2246         * 
2247         * @see #forceDisassociate()
2248         * @see com.digi.xbee.api.models.AssociationIndicationStatus
2249         */
2250        protected AssociationIndicationStatus getAssociationIndicationStatus() throws TimeoutException, XBeeException {
2251                byte[] associationIndicationValue = getParameter("AI");
2252                return AssociationIndicationStatus.get(ByteUtils.byteArrayToInt(associationIndicationValue));
2253        }
2254        
2255        /**
2256         * Forces this XBee device to immediately disassociate from the network and 
2257         * re-attempt to associate.
2258         * 
2259         * <p>Only valid for End Devices.</p>
2260         * 
2261         * @throws InterfaceNotOpenException if this device connection is not open.
2262         * @throws TimeoutException if there is a timeout executing the 
2263         *         disassociation command.
2264         * @throws XBeeException if there is any other XBee related exception.
2265         * 
2266         * @see #getAssociationIndicationStatus()
2267         */
2268        protected void forceDisassociate() throws TimeoutException, XBeeException {
2269                executeParameter("DA");
2270        }
2271        
2272        /**
2273         * Writes configurable parameter values to the non-volatile memory of this 
2274         * XBee device so that parameter modifications persist through subsequent 
2275         * resets.
2276         * 
2277         * <p>Parameters values remain in this device's memory until overwritten by 
2278         * subsequent use of this method.</p>
2279         * 
2280         * <p>If changes are made without writing them to non-volatile memory, the 
2281         * module reverts back to previously saved parameters the next time the 
2282         * module is powered-on.</p>
2283         * 
2284         * <p>Writing the parameter modifications does not mean those values are 
2285         * immediately applied, this depends on the status of the 'apply 
2286         * configuration changes' option. Use method 
2287         * {@code isApplyConfigurationChangesEnabled()} to get its status and 
2288         * {@code enableApplyConfigurationChanges(boolean)} to enable/disable the 
2289         * option. If it is disable method {@code applyChanges()} can be used in 
2290         * order to manually apply the changes.</p>
2291         * 
2292         * @throws InterfaceNotOpenException if this device connection is not open.
2293         * @throws TimeoutException if there is a timeout executing the write 
2294         *                          changes command.
2295         * @throws XBeeException if there is any other XBee related exception.
2296         * 
2297         * @see #applyChanges()
2298         * @see #enableApplyConfigurationChanges(boolean)
2299         * @see #isApplyConfigurationChangesEnabled()
2300         * @see #setParameter(String, byte[])
2301         */
2302        public void writeChanges() throws TimeoutException, XBeeException {
2303                executeParameter("WR");
2304        }
2305}