/**
         * Discovers and reports the first remote XBee device that matches the
         * supplied identifier.
         *
         * <p>This method blocks until the device is discovered or the configured
         * timeout in the device (NT) expires.</p>
         *
         * @param id The identifier of the device to be discovered.
         *
         * @return The discovered remote XBee device with the given identifier,
         *         {@code null} if the timeout expires and the device was not found.
         *
         * @throws InterfaceNotOpenException if the device is not open.
         * @throws XBeeException if there is an error sending the discovery command.
         *
         * @see #discoverDevices(List)
         */
        public RemoteXBeeDevice DiscoverDevice(string id)        /*throws XBeeException */
        {
            // Check if the connection is open.
            if (!xbeeDevice.IsOpen)
            {
                throw new InterfaceNotOpenException();
            }

            logger.DebugFormat("{0}ND for {1} device.", xbeeDevice.ToString(), id);

            running     = true;
            discovering = true;

            performNodeDiscovery(null, id);

            XBeeNetwork      network = xbeeDevice.GetNetwork();
            RemoteXBeeDevice rDevice = null;

            if (deviceList != null && deviceList.Count > 0)
            {
                rDevice = deviceList[0];
                if (rDevice != null)
                {
                    rDevice = network.addRemoteDevice(rDevice);
                }
            }

            return(rDevice);
        }
        /**
         * Notifies the given discovery listeners that a device was discovered.
         *
         * @param listeners The discovery listeners to be notified.
         * @param device The remote device discovered.
         */
        private void notifyDeviceDiscovered(IList <IDiscoveryListener> listeners, RemoteXBeeDevice device)
        {
            if (listeners == null)
            {
                lock (deviceList)
                {
                    deviceList.Add(device);
                }
                return;
            }

            XBeeNetwork network = xbeeDevice.GetNetwork();

            RemoteXBeeDevice addedDev = network.addRemoteDevice(device);

            if (addedDev != null)
            {
                foreach (IDiscoveryListener listener in listeners)
                {
                    listener.DeviceDiscovered(addedDev);
                }
            }
            else
            {
                String error = "Error adding device '" + device + "' to the network.";
                notifyDiscoveryError(listeners, error);
            }
        }
        /**
         * Adds the given list of remote devices to the network.
         *
         * <p>Notice that this operation does not join the remote XBee devices to
         * the network; it just tells the network that it contains those devices.
         * However, the devices have only been added to the device list, and may
         * not be physically in the same network.</p>
         *
         * <p>The way of adding a device to the network is based on the 64-bit
         * address. If it is not configured:</p>
         *
         * <ul>
         * <li>For 802.15.4 and ZigBee devices, the 16-bit address will be used instead.</li>
         * <li>For the rest will return {@code false} as the result of the addition.</li>
         * </ul>
         *
         * @param list The list of remote devices to be added to the network.
         *
         * @return A list with the successfully added devices to the network.
         *
         * @throws ArgumentNullException if {@code list == null}.
         *
         * @see #addRemoteDevice(RemoteXBeeDevice)
         * @see RemoteXBeeDevice
         */
        public List <RemoteXBeeDevice> addRemoteDevices(IList <RemoteXBeeDevice> list)
        {
            if (list == null)
            {
                throw new ArgumentNullException("The list of remote devices cannot be null.");
            }

            List <RemoteXBeeDevice> addedList = new List <RemoteXBeeDevice>(list.Count);

            if (list.Count == 0)
            {
                return(addedList);
            }

            logger.DebugFormat("{0}Adding '{1}' devices to network.", localDevice.ToString(), list.Count);

            for (int i = 0; i < list.Count; i++)
            {
                RemoteXBeeDevice d = addRemoteDevice(list[i]);
                if (d != null)
                {
                    addedList.Add(d);
                }
            }

            return(addedList);
        }
            public async void PacketReceived(XBeePacket receivedPacket)
            {
                if (!_node.discovering)
                {
                    return;
                }
                RemoteXBeeDevice rdevice = null;

                byte[] commandValue = _node.GetRemoteDeviceData((XBeeAPIPacket)receivedPacket);

                rdevice = await _node.ParseDiscoveryAPIData(commandValue, _node.xbeeDevice);

                // If a device with a specific id is being search and it is
                // already found, return it.
                if (_id != null)
                {
                    if (rdevice != null && _id.Equals(rdevice.NodeID))
                    {
                        lock (_node.deviceList)
                        {
                            _node.deviceList.Add(rdevice);
                        }
                        // If the local device is 802.15.4 wait until the 'end' command is received.
                        if (_node.xbeeDevice.XBeeProtocol != XBeeProtocol.RAW_802_15_4)
                        {
                            _node.discovering = false;
                        }
                    }
                }
                else if (rdevice != null)
                {
                    _node.notifyDeviceDiscovered(_listeners, rdevice);
                }
            }
        /**
         * Retrieves the 16-bit address of the given remote device.
         *
         * @param device The remote device to get the 16-bit address.
         *
         * @return The 16-bit address of the device, {@code null} if it does not
         *         contain a valid one.
         */
        private XBee16BitAddress Get16BitAddress(RemoteXBeeDevice device)
        {
            if (device == null)
            {
                return(null);
            }

            XBee16BitAddress address = null;

            switch (device.XBeeProtocol)
            {
            case XBeeProtocol.RAW_802_15_4:
                address = ((RemoteRaw802Device)device).Get16BitAddress();
                break;

            case XBeeProtocol.ZIGBEE:
                address = ((RemoteZigBeeDevice)device).Get16BitAddress();
                break;

            default:
                // TODO should we allow this operation for general remote devices?
                address = device.Get16BitAddress();
                break;
            }

            return(address);
        }
        /**
         * Removes the given remote XBee device from the network.
         *
         * <p>Notice that this operation does not remove the remote XBee device
         * from the actual XBee network; it just tells the network object that it
         * will no longer contain that device. However, next time a discovery is
         * performed, it could be added again automatically.</p>
         *
         * <p>This method will check for a device that matches the 64-bit address
         * of the provided one, if found, that device will be removed from the
         * corresponding list. In case the 64-bit address is not defined, it will
         * use the 16-bit address for DigiMesh and ZigBee devices.</p>
         *
         * @param remoteDevice The remote device to be removed from the network.
         *
         * @throws ArgumentNullException if {@code RemoteDevice == null}.
         *
         * @see #addRemoteDevice(RemoteXBeeDevice)
         * @see #clearDeviceList()
         * @see RemoteXBeeDevice
         */
        public void removeRemoteDevice(RemoteXBeeDevice remoteDevice)
        {
            if (remoteDevice == null)
            {
                throw new ArgumentNullException("Remote device cannot be null.");
            }

            RemoteXBeeDevice devInNetwork = null;

            // Look in the 64-bit map.
            XBee64BitAddress addr64 = remoteDevice.Get64BitAddress();

            if (addr64 != null && !addr64.Equals(XBee64BitAddress.UNKNOWN_ADDRESS))
            {
                if (remotesBy64BitAddr.TryRemove(addr64, out devInNetwork))
                {
                    return;
                }
            }

            // If not found, look in the 16-bit map.
            XBee16BitAddress addr16 = Get16BitAddress(remoteDevice);

            if (addr16 != null && !addr16.Equals(XBee16BitAddress.UNKNOWN_ADDRESS))
            {
                // The preference order is:
                //    1.- Look in the 64-bit map
                //    2.- Then in the 16-bit map.
                // This should be maintained in the 'getDeviceBy16BitAddress' method.

                // Look for the 16-bit address in the 64-bit map.
                ICollection <RemoteXBeeDevice> devices = remotesBy64BitAddr.Values;
                foreach (RemoteXBeeDevice d in devices)
                {
                    XBee16BitAddress a = Get16BitAddress(d);
                    if (a != null && a.Equals(addr16))
                    {
                        RemoteXBeeDevice r;
                        remotesBy64BitAddr.TryRemove(d.Get64BitAddress(), out r);
                        return;
                    }
                }

                // If not found, look for the 16-bit address in the 16-bit map.
                // Remove the device.
                if (remotesBy16BitAddr.TryRemove(addr16, out devInNetwork))
                {
                    return;
                }
            }

            // If the device does not contain a valid address log an error.
            if ((addr64 == null || addr64.Equals(XBee64BitAddress.UNKNOWN_ADDRESS)) &&
                (addr16 == null || addr16.Equals(XBee16BitAddress.UNKNOWN_ADDRESS)))
            {
                logger.ErrorFormat("{0}Remote device '{1}' cannot be removed: 64-bit and 16-bit addresses must be specified.",
                                   localDevice.ToString(), remoteDevice.ToString());
            }
        }
        /**
         * Returns the remote device already contained in the network whose 16-bit
         * address matches the given one.
         *
         * <p>Note that this method <b>does not perform a discovery</b>, only
         * returns the device that has been previously discovered.</p>
         *
         * @param address The 16-bit address of the device to be retrieved.
         *
         * @return The remote device in the network or {@code null} if it is not
         *         found.
         *
         * @throws ArgumentException if {@code address.equals(XBee16BitAddress.UNKNOWN_ADDRESS)}.
         * @throws ArgumentNullException if {@code address == null}.
         * @throws OperationNotSupportedException if the protocol of the local XBee device is DigiMesh or Point-to-Multipoint.
         */
        public RemoteXBeeDevice GetDevice(XBee16BitAddress address)        /*throws OperationNotSupportedException */
        {
            if (localDevice.XBeeProtocol == XBeeProtocol.DIGI_MESH)
            {
                throw new OperationNotSupportedException("DigiMesh protocol does not support 16-bit addressing.");
            }
            if (localDevice.XBeeProtocol == XBeeProtocol.DIGI_POINT)
            {
                throw new OperationNotSupportedException("Point-to-Multipoint protocol does not support 16-bit addressing.");
            }
            if (address == null)
            {
                throw new ArgumentNullException("16-bit address cannot be null.");
            }
            if (address.Equals(XBee16BitAddress.UNKNOWN_ADDRESS))
            {
                throw new ArgumentNullException("16-bit address cannot be unknown.");
            }

            logger.DebugFormat("{0}Getting device '{1}' from network.", localDevice.ToString(), address);

            // The preference order is:
            //    1.- Look in the 64-bit map
            //    2.- Then in the 16-bit map.
            // This should be maintained in the 'addRemoteDevice' method.

            RemoteXBeeDevice devInNetwork = null;

            // Look in the 64-bit map.
            ICollection <RemoteXBeeDevice> devices = remotesBy64BitAddr.Values;

            foreach (RemoteXBeeDevice d in devices)
            {
                XBee16BitAddress a = Get16BitAddress(d);
                if (a != null && a.Equals(address))
                {
                    devInNetwork = d;
                    break;
                }
            }

            // Look in the 16-bit map.
            if (devInNetwork == null)
            {
                devInNetwork = remotesBy16BitAddr[address];
            }

            return(devInNetwork);
        }
        /**
         * Discovers and reports all remote XBee devices that match the supplied
         * identifiers.
         *
         * <p>This method blocks until the configured timeout in the device (NT)
         * expires.</p>
         *
         * @param ids List which contains the identifiers of the devices to be
         *            discovered.
         *
         * @return A list of the discovered remote XBee devices with the given
         *         identifiers.
         *
         * @throws InterfaceNotOpenException if the device is not open.
         * @throws XBeeException if there is an error discovering the devices.
         *
         * @see #discoverDevice(String)
         */
        public List <RemoteXBeeDevice> discoverDevices(IList <string> ids)      /*throws XBeeException */
        {
            // Check if the connection is open.
            if (!xbeeDevice.IsOpen)
            {
                throw new InterfaceNotOpenException();
            }

            logger.DebugFormat("{0}ND for all {1} devices.", xbeeDevice.ToString(), ids.ToString());

            running     = true;
            discovering = true;

            performNodeDiscovery(null, null);

            List <RemoteXBeeDevice> foundDevices = new List <RemoteXBeeDevice>(0);

            if (deviceList == null)
            {
                return(foundDevices);
            }

            XBeeNetwork network = xbeeDevice.GetNetwork();

            foreach (RemoteXBeeDevice d in deviceList)
            {
                string nID = d.NodeID;
                if (nID == null)
                {
                    continue;
                }
                foreach (string id in ids)
                {
                    if (nID.Equals(id))
                    {
                        RemoteXBeeDevice rDevice = network.addRemoteDevice(d);
                        if (rDevice != null && !foundDevices.Contains(rDevice))
                        {
                            foundDevices.Add(rDevice);
                        }
                    }
                }
            }

            return(foundDevices);
        }
        /**
         * Adds the given remote device to the network.
         *
         * <p>Notice that this operation does not join the remote XBee device to the
         * network; it just tells the network that it contains that device. However,
         * the device has only been added to the device list, and may not be
         * physically in the same network.</p>
         *
         * <p>The way of adding a device to the network is based on the 64-bit
         * address. If it is not configured:</p>
         *
         * <ul>
         * <li>For 802.15.4 and ZigBee devices, it will use the 16-bit address.</li>
         * <li>For the rest will return {@code false} as the result of the addition.</li>
         * </ul>
         *
         * @param remoteDevice The remote device to be added to the network.
         *
         * @return The remote XBee Device instance in the network, {@code null} if
         *         the device could not be successfully added.
         *
         * @throws ArgumentNullException if {@code RemoteDevice == null}.
         *
         * @see #addRemoteDevices(List)
         * @see #removeRemoteDevice(RemoteXBeeDevice)
         * @see RemoteXBeeDevice
         */
        public RemoteXBeeDevice addRemoteDevice(RemoteXBeeDevice remoteDevice)
        {
            if (remoteDevice == null)
                throw new ArgumentNullException("Remote device cannot be null.");

            logger.DebugFormat("{0}Adding device '{1}' to network.", localDevice.ToString(), remoteDevice.ToString());

            RemoteXBeeDevice devInNetwork = null;
            XBee64BitAddress addr64 = remoteDevice.Get64BitAddress();
            XBee16BitAddress addr16 = Get16BitAddress(remoteDevice);

            // Check if the device has 64-bit address.
            if (addr64 != null && !addr64.Equals(XBee64BitAddress.UNKNOWN_ADDRESS))
            {
                // The device has 64-bit address, so look in the 64-bit map.
                if (remotesBy64BitAddr.TryGetValue(addr64, out devInNetwork))
                {
                    // The device exists in the 64-bit map, so update the reference and return it.
                    logger.DebugFormat("{0}Existing device '{1}' in network.", localDevice.ToString(), devInNetwork.ToString());
                    devInNetwork.UpdateDeviceDataFrom(remoteDevice);
                    return devInNetwork;
                }
                else
                {
                    // The device does not exist in the 64-bit map, so check its 16-bit address.
                    if (addr16 != null && !addr16.Equals(XBee16BitAddress.UNKNOWN_ADDRESS))
                    {
                        // The device has 16-bit address, so look in the 16-bit map.
                        if (remotesBy16BitAddr.TryGetValue(addr16, out devInNetwork))
                        {
                            // The device exists in the 16-bit map, so remove it and add it to the 64-bit map.
                            logger.DebugFormat("{0}Existing device '{1}' in network.", localDevice.ToString(), devInNetwork.ToString());
                            remotesBy16BitAddr.TryRemove(addr16, out devInNetwork);
                            devInNetwork.UpdateDeviceDataFrom(remoteDevice);
                            remotesBy64BitAddr.AddOrUpdate(addr64, devInNetwork, (k, v) => devInNetwork);
                            return devInNetwork;
                        }
                        else
                        {
                            // The device does not exist in the 16-bit map, so add it to the 64-bit map.
                            remotesBy64BitAddr.AddOrUpdate(addr64, remoteDevice, (k, v) => remoteDevice);
                            return remoteDevice;
                        }
                    }
                    else
                    {
                        // The device has not 16-bit address, so add it to the 64-bit map.
                        remotesBy64BitAddr.AddOrUpdate(addr64, remoteDevice, (k, v) => remoteDevice);
                        return remoteDevice;
                    }
                }
            }

            // If the device has not 64-bit address, check if it has 16-bit address.
            if (addr16 != null && !addr16.Equals(XBee16BitAddress.UNKNOWN_ADDRESS))
            {
                // The device has 16-bit address, so look in the 64-bit map.
                ICollection<RemoteXBeeDevice> devices = remotesBy64BitAddr.Values;
                foreach (RemoteXBeeDevice d in devices)
                {
                    XBee16BitAddress a = Get16BitAddress(d);
                    if (a != null && a.Equals(addr16))
                    {
                        devInNetwork = d;
                        break;
                    }
                }
                // Check if the device exists in the 64-bit map.
                if (devInNetwork != null)
                {
                    // The device exists in the 64-bit map, so update the reference and return it.
                    logger.DebugFormat("{0}Existing device '{1}' in network.", localDevice.ToString(), devInNetwork.ToString());
                    devInNetwork.UpdateDeviceDataFrom(remoteDevice);
                    return devInNetwork;
                }
                else
                {
                    // The device does not exist in the 64-bit map, so look in the 16-bit map.
                    devInNetwork = remotesBy16BitAddr[addr16];
                    if (devInNetwork != null)
                    {
                        // The device exists in the 16-bit map, so update the reference and return it.
                        logger.DebugFormat("{0}Existing device '{1}' in network.", localDevice.ToString(), devInNetwork.ToString());
                        devInNetwork.UpdateDeviceDataFrom(remoteDevice);
                        return devInNetwork;
                    }
                    else
                    {
                        // The device does not exist in the 16-bit map, so add it.
                        remotesBy16BitAddr.AddOrUpdate(addr16, remoteDevice, (k, v) => remoteDevice);
                        return remoteDevice;
                    }
                }
            }

            // If the device does not contain a valid address, return null.
            logger.ErrorFormat("{0}Remote device '{1}' cannot be added: 64-bit and 16-bit addresses must be specified.",
                    localDevice.ToString(), remoteDevice.ToString());
            return null;
        }
        /**
         * Reads a new data packet received by this XBee device during the provided
         * timeout.
         *
         * <p>This method blocks until new data is received or the given timeout
         * expires.</p>
         *
         * <p>If the provided remote XBee device is {@code null} the method returns
         * the first data packet read from any remote device.
         * <br>
         * If it the remote device is not {@code null} the method returns the first
         * data package read from the provided device.
         * </p>
         *
         * @param remoteXBeeDevice The remote device to get a data packet from.
         *                         {@code null} to read a data packet sent by any
         *                         remote XBee device.
         * @param timeout The time to wait for a data packet in milliseconds.
         *
         * @return An {@code XBeeMessage} received by this device, containing the
         *         data and the source address of the remote node that sent the
         *         data. {@code null} if this device did not receive new data
         *         during {@code timeout} milliseconds, or if any error occurs while
         *         trying to get the source of the message.
         *
         * @throws InterfaceNotOpenException if this device connection is not open.
         *
         * @see RemoteXBeeDevice
         * @see com.digi.xbee.api.models.XBeeMessage
         */
        private XBeeMessage ReadDataPacket(RemoteXBeeDevice remoteXBeeDevice, int timeout)
        {
            // Check connection.
            if (!connectionInterface.SerialPort.IsOpen)
                throw new InterfaceNotOpenException();

            XBeePacketsQueue xbeePacketsQueue = dataReader.XBeePacketsQueue;
            XBeePacket xbeePacket = null;

            if (remoteXBeeDevice != null)
                xbeePacket = xbeePacketsQueue.getFirstDataPacketFrom(remoteXBeeDevice, timeout);
            else
                xbeePacket = xbeePacketsQueue.getFirstDataPacket(timeout);

            if (xbeePacket == null)
                return null;

            // Obtain the remote device from the packet.
            RemoteXBeeDevice remoteDevice = null;
            try
            {
                remoteDevice = dataReader.GetRemoteXBeeDeviceFromPacket((XBeeAPIPacket)xbeePacket);
                // If the provided device is not null, add it to the network, so the
                // device provided is the one that will remain in the network.
                if (remoteXBeeDevice != null)
                    remoteDevice = GetNetwork().addRemoteDevice(remoteXBeeDevice);

                // The packet always contains information of the source so the
                // remote device should never be null.
                if (remoteDevice == null)
                    return null;

            }
            catch (XBeeException e)
            {
                logger.Error(e.Message, e);
                return null;
            }

            // Obtain the data from the packet.
            byte[] data = null;

            switch (((XBeeAPIPacket)xbeePacket).FrameType)
            {
                case APIFrameType.RECEIVE_PACKET:
                    ReceivePacket receivePacket = (ReceivePacket)xbeePacket;
                    data = receivePacket.RFData;
                    break;
                case APIFrameType.RX_16:
                    RX16Packet rx16Packet = (RX16Packet)xbeePacket;
                    data = rx16Packet.RFData;
                    break;
                case APIFrameType.RX_64:
                    RX64Packet rx64Packet = (RX64Packet)xbeePacket;
                    data = rx64Packet.RFData;
                    break;
                default:
                    return null;
            }

            // Create and return the XBee message.
            return new XBeeMessage(remoteDevice, data, ((XBeeAPIPacket)xbeePacket).IsBroadcast);
        }
        /**
         * Retrieves the 16-bit address of the given remote device.
         *
         * @param device The remote device to get the 16-bit address.
         *
         * @return The 16-bit address of the device, {@code null} if it does not
         *         contain a valid one.
         */
        private XBee16BitAddress Get16BitAddress(RemoteXBeeDevice device)
        {
            if (device == null)
                return null;

            XBee16BitAddress address = null;

            switch (device.XBeeProtocol)
            {
                case XBeeProtocol.RAW_802_15_4:
                    address = ((RemoteRaw802Device)device).Get16BitAddress();
                    break;
                case XBeeProtocol.ZIGBEE:
                    address = ((RemoteZigBeeDevice)device).Get16BitAddress();
                    break;
                default:
                    // TODO should we allow this operation for general remote devices?
                    address = device.Get16BitAddress();
                    break;
            }

            return address;
        }
        /**
         * Reads new data received from the given remote XBee device during the
         * configured received timeout.
         *
         * <p>This method blocks until new data from the provided remote XBee
         * device is received or the configured receive timeout expires.</p>
         *
         * <p>The received timeout is configured using the {@code setReceiveTimeout}
         * method and can be consulted with {@code getReceiveTimeout} method.</p>
         *
         * <p>For non-blocking operations, register a {@code IDataReceiveListener}
         * using the method {@link #addDataListener(IDataReceiveListener)}.</p>
         *
         * @param remoteXBeeDevice The remote device to read data from.
         *
         * @return An {@code XBeeMessage} object containing the data and the source
         *         address of the remote node that sent the data. {@code null} if
         *         this device did not receive new data from the provided remote
         *         XBee device during the configured receive timeout.
         *
         * @throws InterfaceNotOpenException if this device connection is not open.
         * @throws ArgumentNullException if {@code remoteXBeeDevice == null}.
         *
         * @see #readDataFrom(RemoteXBeeDevice, int)
         * @see #getReceiveTimeout()
         * @see #setReceiveTimeout(int)
         * @see #readData()
         * @see #readData(int)
         * @see RemoteXBeeDevice
         * @see com.digi.xbee.api.models.XBeeMessage
         */
        public XBeeMessage readDataFrom(RemoteXBeeDevice remoteXBeeDevice)
        {
            if (remoteXBeeDevice == null)
                throw new ArgumentNullException("Remote XBee device cannot be null.");

            return ReadDataPacket(remoteXBeeDevice, TIMEOUT_READ_PACKET);
        }
        /**
         * Adds the given remote device to the network.
         *
         * <p>Notice that this operation does not join the remote XBee device to the
         * network; it just tells the network that it contains that device. However,
         * the device has only been added to the device list, and may not be
         * physically in the same network.</p>
         *
         * <p>The way of adding a device to the network is based on the 64-bit
         * address. If it is not configured:</p>
         *
         * <ul>
         * <li>For 802.15.4 and ZigBee devices, it will use the 16-bit address.</li>
         * <li>For the rest will return {@code false} as the result of the addition.</li>
         * </ul>
         *
         * @param remoteDevice The remote device to be added to the network.
         *
         * @return The remote XBee Device instance in the network, {@code null} if
         *         the device could not be successfully added.
         *
         * @throws ArgumentNullException if {@code RemoteDevice == null}.
         *
         * @see #addRemoteDevices(List)
         * @see #removeRemoteDevice(RemoteXBeeDevice)
         * @see RemoteXBeeDevice
         */
        public RemoteXBeeDevice addRemoteDevice(RemoteXBeeDevice remoteDevice)
        {
            if (remoteDevice == null)
            {
                throw new ArgumentNullException("Remote device cannot be null.");
            }

            logger.DebugFormat("{0}Adding device '{1}' to network.", localDevice.ToString(), remoteDevice.ToString());

            RemoteXBeeDevice devInNetwork = null;
            XBee64BitAddress addr64       = remoteDevice.Get64BitAddress();
            XBee16BitAddress addr16       = Get16BitAddress(remoteDevice);

            // Check if the device has 64-bit address.
            if (addr64 != null && !addr64.Equals(XBee64BitAddress.UNKNOWN_ADDRESS))
            {
                // The device has 64-bit address, so look in the 64-bit map.
                if (remotesBy64BitAddr.TryGetValue(addr64, out devInNetwork))
                {
                    // The device exists in the 64-bit map, so update the reference and return it.
                    logger.DebugFormat("{0}Existing device '{1}' in network.", localDevice.ToString(), devInNetwork.ToString());
                    devInNetwork.UpdateDeviceDataFrom(remoteDevice);
                    return(devInNetwork);
                }
                else
                {
                    // The device does not exist in the 64-bit map, so check its 16-bit address.
                    if (addr16 != null && !addr16.Equals(XBee16BitAddress.UNKNOWN_ADDRESS))
                    {
                        // The device has 16-bit address, so look in the 16-bit map.
                        if (remotesBy16BitAddr.TryGetValue(addr16, out devInNetwork))
                        {
                            // The device exists in the 16-bit map, so remove it and add it to the 64-bit map.
                            logger.DebugFormat("{0}Existing device '{1}' in network.", localDevice.ToString(), devInNetwork.ToString());
                            remotesBy16BitAddr.TryRemove(addr16, out devInNetwork);
                            devInNetwork.UpdateDeviceDataFrom(remoteDevice);
                            remotesBy64BitAddr.AddOrUpdate(addr64, devInNetwork, (k, v) => devInNetwork);
                            return(devInNetwork);
                        }
                        else
                        {
                            // The device does not exist in the 16-bit map, so add it to the 64-bit map.
                            remotesBy64BitAddr.AddOrUpdate(addr64, remoteDevice, (k, v) => remoteDevice);
                            return(remoteDevice);
                        }
                    }
                    else
                    {
                        // The device has not 16-bit address, so add it to the 64-bit map.
                        remotesBy64BitAddr.AddOrUpdate(addr64, remoteDevice, (k, v) => remoteDevice);
                        return(remoteDevice);
                    }
                }
            }

            // If the device has not 64-bit address, check if it has 16-bit address.
            if (addr16 != null && !addr16.Equals(XBee16BitAddress.UNKNOWN_ADDRESS))
            {
                // The device has 16-bit address, so look in the 64-bit map.
                ICollection <RemoteXBeeDevice> devices = remotesBy64BitAddr.Values;
                foreach (RemoteXBeeDevice d in devices)
                {
                    XBee16BitAddress a = Get16BitAddress(d);
                    if (a != null && a.Equals(addr16))
                    {
                        devInNetwork = d;
                        break;
                    }
                }
                // Check if the device exists in the 64-bit map.
                if (devInNetwork != null)
                {
                    // The device exists in the 64-bit map, so update the reference and return it.
                    logger.DebugFormat("{0}Existing device '{1}' in network.", localDevice.ToString(), devInNetwork.ToString());
                    devInNetwork.UpdateDeviceDataFrom(remoteDevice);
                    return(devInNetwork);
                }
                else
                {
                    // The device does not exist in the 64-bit map, so look in the 16-bit map.
                    devInNetwork = remotesBy16BitAddr[addr16];
                    if (devInNetwork != null)
                    {
                        // The device exists in the 16-bit map, so update the reference and return it.
                        logger.DebugFormat("{0}Existing device '{1}' in network.", localDevice.ToString(), devInNetwork.ToString());
                        devInNetwork.UpdateDeviceDataFrom(remoteDevice);
                        return(devInNetwork);
                    }
                    else
                    {
                        // The device does not exist in the 16-bit map, so add it.
                        remotesBy16BitAddr.AddOrUpdate(addr16, remoteDevice, (k, v) => remoteDevice);
                        return(remoteDevice);
                    }
                }
            }

            // If the device does not contain a valid address, return null.
            logger.ErrorFormat("{0}Remote device '{1}' cannot be added: 64-bit and 16-bit addresses must be specified.",
                               localDevice.ToString(), remoteDevice.ToString());
            return(null);
        }
        /*throws TimeoutException, XBeeException */
        /**
         * Sends the provided data to the given XBee device choosing the optimal
         * send method depending on the protocol of the local XBee device.
         *
         * <p>This method blocks till a success or error response arrives or the
         * configured receive timeout expires.</p>
         *
         * <p>The received timeout is configured using the {@code setReceiveTimeout}
         * method and can be consulted with {@code getReceiveTimeout} method.</p>
         *
         * <p>For non-blocking operations use the method
         * {@link #sendDataAsync(RemoteXBeeDevice, byte[])}.</p>
         *
         * @param xbeeDevice The XBee device of the network that will receive the
         *                   data.
         * @param data Byte array containing the data to be sent.
         *
         * @throws InterfaceNotOpenException if this device connection is not open.
         * @throws ArgumentNullException if {@code xbeeDevice == null} or
         *                              if {@code data == null}.
         * @throws TimeoutException if there is a timeout sending the data.
         * @throws XBeeException if there is any other XBee related exception.
         *
         * @see #getReceiveTimeout()
         * @see #setReceiveTimeout(int)
         * @see #sendDataAsync(RemoteXBeeDevice, byte[])
         */
        public void SendData(RemoteXBeeDevice xbeeDevice, byte[] data)
        {
            if (xbeeDevice == null)
                throw new ArgumentNullException("Remote XBee device cannot be null");

            switch (XBeeProtocol)
            {
                case XBeeProtocol.ZIGBEE:
                case XBeeProtocol.DIGI_POINT:
                    if (xbeeDevice.Get64BitAddress() != null && xbeeDevice.Get16BitAddress() != null)
                        SendData(xbeeDevice.Get64BitAddress(), xbeeDevice.Get16BitAddress(), data);
                    else
                        SendData(xbeeDevice.Get64BitAddress(), data);
                    break;
                case XBeeProtocol.RAW_802_15_4:
                    if (this is Raw802Device)
                    {
                        if (xbeeDevice.Get64BitAddress() != null)
                            ((Raw802Device)this).SendData(xbeeDevice.Get64BitAddress(), data);
                        else
                            ((Raw802Device)this).SendData(xbeeDevice.Get16BitAddress(), data);
                    }
                    else
                        SendData(xbeeDevice.Get64BitAddress(), data);
                    break;
                case XBeeProtocol.DIGI_MESH:
                default:
                    SendData(xbeeDevice.Get64BitAddress(), data);
                    break;
            }
        }
        /**
         * Reads new data received from the given remote XBee device during the
         * provided timeout.
         *
         * <p>This method blocks until new data from the provided remote XBee
         * device is received or the given timeout expires.</p>
         *
         * <p>For non-blocking operations, register a {@code IDataReceiveListener}
         * using the method {@link #addDataListener(IDataReceiveListener)}.</p>
         *
         * @param remoteXBeeDevice The remote device to read data from.
         * @param timeout The time to wait for new data in milliseconds.
         *
         * @return An {@code XBeeMessage} object containing the data and the source
         *         address of the remote node that sent the data. {@code null} if
         *         this device did not receive new data from the provided remote
         *         XBee device during {@code timeout} milliseconds.
         *
         * @throws ArgumentException if {@code timeout < 0}.
         * @throws InterfaceNotOpenException if this device connection is not open.
         * @throws ArgumentNullException if {@code remoteXBeeDevice == null}.
         *
         * @see #readDataFrom(RemoteXBeeDevice)
         * @see #getReceiveTimeout()
         * @see #setReceiveTimeout(int)
         * @see #readData()
         * @see #readData(int)
         * @see RemoteXBeeDevice
         * @see com.digi.xbee.api.models.XBeeMessage
         */
        public XBeeMessage readDataFrom(RemoteXBeeDevice remoteXBeeDevice, int timeout)
        {
            if (remoteXBeeDevice == null)
                throw new ArgumentNullException("Remote XBee device cannot be null.");
            if (timeout < 0)
                throw new ArgumentException("Read timeout must be 0 or greater.");

            return ReadDataPacket(remoteXBeeDevice, timeout);
        }
        /**
         * Parses the given node discovery API data to create and return a remote
         * XBee Device.
         *
         * @param data Byte array with the data to parse.
         * @param localDevice The local device that received the remote XBee data.
         *
         * @return Discovered XBee device.
         */
        private async Task <RemoteXBeeDevice> ParseDiscoveryAPIData(byte[] data, XBeeDevice localDevice)
        {
            if (data == null)
            {
                return(null);
            }

            RemoteXBeeDevice device = null;
            XBee16BitAddress addr16 = null;
            XBee64BitAddress addr64 = null;
            String           id     = null;
            // TODO role of the device: coordinator, router, end device or unknown.
            //XBeeDeviceType role = XBeeDeviceType.UNKNOWN;
            int signalStrength = 0;

            byte[] profileID      = null;
            byte[] manufacturerID = null;

            using (var inputStream = new MemoryStream(data))
            {
                // Read 16 bit address.
                addr16 = new XBee16BitAddress(await ByteUtils.ReadBytes(2, inputStream));
                // Read 64 bit address.
                addr64 = new XBee64BitAddress(await ByteUtils.ReadBytes(8, inputStream));


                switch (localDevice.XBeeProtocol)
                {
                case XBeeProtocol.ZIGBEE:
                case XBeeProtocol.DIGI_MESH:
                case XBeeProtocol.ZNET:
                case XBeeProtocol.DIGI_POINT:
                case XBeeProtocol.XLR:
                // TODO [XLR_DM] The next version of the XLR will add DigiMesh support.
                // For the moment only point-to-multipoint is supported in this kind of devices.
                case XBeeProtocol.XLR_DM:
                    // Read node identifier.
                    id = ByteUtils.ReadString(inputStream);
                    // Read parent address.
                    XBee16BitAddress parentAddress = new XBee16BitAddress(await ByteUtils.ReadBytes(2, inputStream));
                    // TODO Read device type.
                    //role = XBeeDeviceType.get(inputStream.read());
                    // Consume status byte, it is not used yet.
                    await ByteUtils.ReadBytes(1, inputStream);

                    // Read profile ID.
                    profileID = await ByteUtils.ReadBytes(2, inputStream);

                    // Read manufacturer ID.
                    manufacturerID = await ByteUtils.ReadBytes(2, inputStream);

                    logger.DebugFormat("{0}Discovered {1} device: 16-bit[{2}], 64-bit[{3}], id[{4}], parent[{5}], profile[{6}], manufacturer[{7}].",
                                       xbeeDevice.ToString(), localDevice.XBeeProtocol.GetDescription(), addr16,
                                       addr64, id, parentAddress, HexUtils.ByteArrayToHexString(profileID),
                                       HexUtils.ByteArrayToHexString(manufacturerID));

                    break;

                case XBeeProtocol.RAW_802_15_4:
                    // Read strength signal byte.
                    signalStrength = inputStream.ReadByte();
                    // Read node identifier.
                    id = ByteUtils.ReadString(inputStream);

                    logger.DebugFormat("{0}Discovered {1} device: 16-bit[{2}], 64-bit[{3}], id[{4}], rssi[{5}].",
                                       xbeeDevice.ToString(), localDevice.XBeeProtocol.GetDescription(), addr16, addr64, id, signalStrength);

                    break;

                case XBeeProtocol.UNKNOWN:
                default:
                    logger.DebugFormat("{0}Discovered {1} device: 16-bit[{2}], 64-bit[{3}].",
                                       xbeeDevice.ToString(), localDevice.XBeeProtocol.GetDescription(), addr16, addr64);
                    break;
                }

                // Create device and fill with parameters.
                switch (localDevice.XBeeProtocol)
                {
                case XBeeProtocol.ZIGBEE:
                    device = new RemoteZigBeeDevice(localDevice, addr64, addr16, id /*, role*/);
                    // TODO profileID and manufacturerID
                    break;

                case XBeeProtocol.DIGI_MESH:
                    device = new RemoteDigiMeshDevice(localDevice, addr64, id /*, role*/);
                    // TODO profileID and manufacturerID
                    break;

                case XBeeProtocol.DIGI_POINT:
                    device = new RemoteDigiPointDevice(localDevice, addr64, id /*, role*/);
                    // TODO profileID and manufacturerID
                    break;

                case XBeeProtocol.RAW_802_15_4:
                    device = new RemoteRaw802Device(localDevice, addr64, addr16, id /*, role*/);
                    // TODO signalStrength
                    break;

                default:
                    device = new RemoteXBeeDevice(localDevice, addr64, addr16, id /*, role*/);
                    break;
                }
            }

            return(device);
        }
 /*throws XBeeException */
 /**
  * Sends the provided data to the provided XBee device asynchronously.
  *
  * <p>Asynchronous transmissions do not wait for answer from the remote
  * device or for transmit status packet.</p>
  *
  * @param xbeeDevice The XBee device of the network that will receive the
  *                   data.
  * @param data Byte array containing the data to be sent.
  *
  * @throws InterfaceNotOpenException if this device connection is not open.
  * @throws ArgumentNullException if {@code xbeeDevice == null} or
  *                              if {@code data == null}.
  * @throws XBeeException if there is any XBee related exception.
  *
  * @see #sendData(RemoteXBeeDevice, byte[])
  * @see RemoteXBeeDevice
  */
 public void SendDataAsync(RemoteXBeeDevice xbeeDevice, byte[] data)
 {
     if (xbeeDevice == null)
         throw new ArgumentNullException("Remote XBee device cannot be null");
     SendDataAsync(xbeeDevice.Get64BitAddress(), data);
 }
        /**
         * Removes the given remote XBee device from the network.
         *
         * <p>Notice that this operation does not remove the remote XBee device
         * from the actual XBee network; it just tells the network object that it
         * will no longer contain that device. However, next time a discovery is
         * performed, it could be added again automatically.</p>
         *
         * <p>This method will check for a device that matches the 64-bit address
         * of the provided one, if found, that device will be removed from the
         * corresponding list. In case the 64-bit address is not defined, it will
         * use the 16-bit address for DigiMesh and ZigBee devices.</p>
         *
         * @param remoteDevice The remote device to be removed from the network.
         *
         * @throws ArgumentNullException if {@code RemoteDevice == null}.
         *
         * @see #addRemoteDevice(RemoteXBeeDevice)
         * @see #clearDeviceList()
         * @see RemoteXBeeDevice
         */
        public void removeRemoteDevice(RemoteXBeeDevice remoteDevice)
        {
            if (remoteDevice == null)
                throw new ArgumentNullException("Remote device cannot be null.");

            RemoteXBeeDevice devInNetwork = null;

            // Look in the 64-bit map.
            XBee64BitAddress addr64 = remoteDevice.Get64BitAddress();
            if (addr64 != null && !addr64.Equals(XBee64BitAddress.UNKNOWN_ADDRESS))
            {
                if (remotesBy64BitAddr.TryRemove(addr64, out devInNetwork))
                    return;
            }

            // If not found, look in the 16-bit map.
            XBee16BitAddress addr16 = Get16BitAddress(remoteDevice);
            if (addr16 != null && !addr16.Equals(XBee16BitAddress.UNKNOWN_ADDRESS))
            {

                // The preference order is:
                //    1.- Look in the 64-bit map
                //    2.- Then in the 16-bit map.
                // This should be maintained in the 'getDeviceBy16BitAddress' method.

                // Look for the 16-bit address in the 64-bit map.
                ICollection<RemoteXBeeDevice> devices = remotesBy64BitAddr.Values;
                foreach (RemoteXBeeDevice d in devices)
                {
                    XBee16BitAddress a = Get16BitAddress(d);
                    if (a != null && a.Equals(addr16))
                    {
                        RemoteXBeeDevice r;
                        remotesBy64BitAddr.TryRemove(d.Get64BitAddress(), out r);
                        return;
                    }
                }

                // If not found, look for the 16-bit address in the 16-bit map.
                // Remove the device.
                if (remotesBy16BitAddr.TryRemove(addr16, out devInNetwork))
                    return;
            }

            // If the device does not contain a valid address log an error.
            if ((addr64 == null || addr64.Equals(XBee64BitAddress.UNKNOWN_ADDRESS))
                    && (addr16 == null || addr16.Equals(XBee16BitAddress.UNKNOWN_ADDRESS)))
                logger.ErrorFormat("{0}Remote device '{1}' cannot be removed: 64-bit and 16-bit addresses must be specified.",
                        localDevice.ToString(), remoteDevice.ToString());
        }
		/**
		 * Notifies the given discovery listeners that a device was discovered.
		 * 
		 * @param listeners The discovery listeners to be notified.
		 * @param device The remote device discovered.
		 */
		private void notifyDeviceDiscovered(IList<IDiscoveryListener> listeners, RemoteXBeeDevice device)
		{
			if (listeners == null)
			{
				lock (deviceList)
				{
					deviceList.Add(device);
				}
				return;
			}

			XBeeNetwork network = xbeeDevice.GetNetwork();

			RemoteXBeeDevice addedDev = network.addRemoteDevice(device);
			if (addedDev != null)
			{
				foreach (IDiscoveryListener listener in listeners)
					listener.DeviceDiscovered(addedDev);
			}
			else
			{
				String error = "Error adding device '" + device + "' to the network.";
				notifyDiscoveryError(listeners, error);
			}
		}
		/**
		 * Parses the given node discovery API data to create and return a remote 
		 * XBee Device.
		 * 
		 * @param data Byte array with the data to parse.
		 * @param localDevice The local device that received the remote XBee data.
		 * 
		 * @return Discovered XBee device.
		 */
		private async Task<RemoteXBeeDevice> ParseDiscoveryAPIData(byte[] data, XBeeDevice localDevice)
		{
			if (data == null)
				return null;

			RemoteXBeeDevice device = null;
			XBee16BitAddress addr16 = null;
			XBee64BitAddress addr64 = null;
			String id = null;
			// TODO role of the device: coordinator, router, end device or unknown.
			//XBeeDeviceType role = XBeeDeviceType.UNKNOWN;
			int signalStrength = 0;
			byte[] profileID = null;
			byte[] manufacturerID = null;

			using (var inputStream = new MemoryStream(data))
			{
				// Read 16 bit address.
				addr16 = new XBee16BitAddress(await ByteUtils.ReadBytes(2, inputStream));
				// Read 64 bit address.
				addr64 = new XBee64BitAddress(await ByteUtils.ReadBytes(8, inputStream));


				switch (localDevice.XBeeProtocol)
				{
					case XBeeProtocol.ZIGBEE:
					case XBeeProtocol.DIGI_MESH:
					case XBeeProtocol.ZNET:
					case XBeeProtocol.DIGI_POINT:
					case XBeeProtocol.XLR:
					// TODO [XLR_DM] The next version of the XLR will add DigiMesh support.
					// For the moment only point-to-multipoint is supported in this kind of devices.
					case XBeeProtocol.XLR_DM:
						// Read node identifier.
						id = ByteUtils.ReadString(inputStream);
						// Read parent address.
						XBee16BitAddress parentAddress = new XBee16BitAddress(await ByteUtils.ReadBytes(2, inputStream));
						// TODO Read device type.
						//role = XBeeDeviceType.get(inputStream.read());
						// Consume status byte, it is not used yet.
						await ByteUtils.ReadBytes(1, inputStream);
						// Read profile ID.
						profileID = await ByteUtils.ReadBytes(2, inputStream);
						// Read manufacturer ID.
						manufacturerID = await ByteUtils.ReadBytes(2, inputStream);

						logger.DebugFormat("{0}Discovered {1} device: 16-bit[{2}], 64-bit[{3}], id[{4}], parent[{5}], profile[{6}], manufacturer[{7}].",
								xbeeDevice.ToString(), localDevice.XBeeProtocol.GetDescription(), addr16,
								addr64, id, parentAddress, HexUtils.ByteArrayToHexString(profileID),
								HexUtils.ByteArrayToHexString(manufacturerID));

						break;
					case XBeeProtocol.RAW_802_15_4:
						// Read strength signal byte.
						signalStrength = inputStream.ReadByte();
						// Read node identifier.
						id = ByteUtils.ReadString(inputStream);

						logger.DebugFormat("{0}Discovered {1} device: 16-bit[{2}], 64-bit[{3}], id[{4}], rssi[{5}].",
								xbeeDevice.ToString(), localDevice.XBeeProtocol.GetDescription(), addr16, addr64, id, signalStrength);

						break;
					case XBeeProtocol.UNKNOWN:
					default:
						logger.DebugFormat("{0}Discovered {1} device: 16-bit[{2}], 64-bit[{3}].",
								xbeeDevice.ToString(), localDevice.XBeeProtocol.GetDescription(), addr16, addr64);
						break;
				}

				// Create device and fill with parameters.
				switch (localDevice.XBeeProtocol)
				{
					case XBeeProtocol.ZIGBEE:
						device = new RemoteZigBeeDevice(localDevice, addr64, addr16, id/*, role*/);
						// TODO profileID and manufacturerID
						break;
					case XBeeProtocol.DIGI_MESH:
						device = new RemoteDigiMeshDevice(localDevice, addr64, id/*, role*/);
						// TODO profileID and manufacturerID
						break;
					case XBeeProtocol.DIGI_POINT:
						device = new RemoteDigiPointDevice(localDevice, addr64, id/*, role*/);
						// TODO profileID and manufacturerID
						break;
					case XBeeProtocol.RAW_802_15_4:
						device = new RemoteRaw802Device(localDevice, addr64, addr16, id/*, role*/);
						// TODO signalStrength
						break;
					default:
						device = new RemoteXBeeDevice(localDevice, addr64, addr16, id/*, role*/);
						break;
				}
			}

			return device;
		}