    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.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];
    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}