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