    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.util.ArrayList;
    015import java.util.Collection;
    016import java.util.List;
    017import java.util.Map;
    018import java.util.Set;
    019import java.util.concurrent.ConcurrentHashMap;
    020
    021import org.slf4j.Logger;
    022import org.slf4j.LoggerFactory;
    023
    024import com.digi.xbee.api.exceptions.InterfaceNotOpenException;
    025import com.digi.xbee.api.exceptions.OperationNotSupportedException;
    026import com.digi.xbee.api.exceptions.TimeoutException;
    027import com.digi.xbee.api.exceptions.XBeeException;
    028import com.digi.xbee.api.listeners.IDiscoveryListener;
    029import com.digi.xbee.api.models.DiscoveryOptions;
    030import com.digi.xbee.api.models.XBee16BitAddress;
    031import com.digi.xbee.api.models.XBee64BitAddress;
    032import com.digi.xbee.api.models.XBeeProtocol;
    033import com.digi.xbee.api.utils.ByteUtils;
    034
    035/**
    036 * This class represents an XBee Network.
    037 *
    038 * <p>The network allows the discovery of remote devices in the same network
    039 * as the local one and stores them.</p>
    040 */
    041public class XBeeNetwork {
    042
    043        // Variables.
    044
    045        private XBeeDevice localDevice;
    046
    047        private Map<XBee64BitAddress, RemoteXBeeDevice> remotesBy64BitAddr;
    048        private Map<XBee16BitAddress, RemoteXBeeDevice> remotesBy16BitAddr;
    049
    050        private List<IDiscoveryListener> discoveryListeners = new ArrayList<IDiscoveryListener>();
    051
    052        private NodeDiscovery nodeDiscovery;
    053
    054        protected Logger logger;
    055
    056        /**
    057         * Instantiates a new {@code XBeeNetwork} object.
    058         *
    059         * @param device Local XBee device to get the network from.
    060         *
    061         * @throws NullPointerException if {@code device == null}.
    062         *
    063         * @see XBeeDevice
    064         */
    065        XBeeNetwork(XBeeDevice device) {
    066                if (device == null)
    067                        throw new NullPointerException("Local XBee device cannot be null.");
    068
    069                localDevice = device;
    070                remotesBy64BitAddr = new ConcurrentHashMap<XBee64BitAddress, RemoteXBeeDevice>();
    071                remotesBy16BitAddr = new ConcurrentHashMap<XBee16BitAddress, RemoteXBeeDevice>();
    072                nodeDiscovery = new NodeDiscovery(localDevice);
    073
    074                logger = LoggerFactory.getLogger(this.getClass());
    075        }
    076
    077        /**
    078         * Discovers and reports the first remote XBee device that matches the
    079         * supplied identifier.
    080         *
    081         * <p>This method blocks until the device is discovered or the configured
    082         * timeout expires. To configure the discovery timeout, use the method
    083         * {@link #setDiscoveryTimeout(long)}.</p>
    084         *
    085         * <p>To configure the discovery options, use the
    086         * {@link #setDiscoveryOptions(Set)} method.</p>
    087         *
    088         * @param id The identifier of the device to be discovered.
    089         *
    090         * @return The discovered remote XBee device with the given identifier,
    091         *         {@code null} if the timeout expires and the device was not found.
    092         *
    093         * @throws IllegalArgumentException if {@code id.length() == 0}.
    094         * @throws InterfaceNotOpenException if the device is not open.
    095         * @throws NullPointerException if {@code id == null}.
    096         * @throws XBeeException if there is an error discovering the device.
    097         *
    098         * @see #discoverDevices(List)
    099         * @see #getDevice(String)
    100         * @see RemoteXBeeDevice
    101         */
    102        public RemoteXBeeDevice discoverDevice(String id) throws XBeeException {
    103                if (id == null)
    104                        throw new NullPointerException("Device identifier cannot be null.");
    105                if (id.length() == 0)
    106                        throw new IllegalArgumentException("Device identifier cannot be an empty string.");
    107
    108                logger.debug("{}Discovering '{}' device.", localDevice.toString(), id);
    109
    110                return nodeDiscovery.discoverDevice(id);
    111        }
    112
    113        /**
    114         * Discovers and reports all remote XBee devices that match the supplied
    115         * identifiers.
    116         *
    117         * <p>This method blocks until the configured timeout expires. To configure
    118         * the discovery timeout, use the method {@link #setDiscoveryTimeout(long)}.
    119         * </p>
    120         *
    121         * <p>To configure the discovery options, use the
    122         * {@link #setDiscoveryOptions(Set)} method.</p>
    123         *
    124         * @param ids List which contains the identifiers of the devices to be
    125         *            discovered.
    126         *
    127         * @return A list of the discovered remote XBee devices with the given
    128         *         identifiers.
    129         *
    130         * @throws IllegalArgumentException if {@code ids.size() == 0}.
    131         * @throws InterfaceNotOpenException if the device is not open.
    132         * @throws NullPointerException if {@code ids == null}.
    133         * @throws XBeeException if there is an error discovering the devices.
    134         *
    135         * @see #discoverDevice(String)
    136         * @see RemoteXBeeDevice
    137         */
    138        public List<RemoteXBeeDevice> discoverDevices(List<String> ids) throws XBeeException {
    139                if (ids == null)
    140                        throw new NullPointerException("List of device identifiers cannot be null.");
    141                if (ids.size() == 0)
    142                        throw new IllegalArgumentException("List of device identifiers cannot be empty.");
    143
    144                logger.debug("{}Discovering all '{}' devices.", localDevice.toString(), ids.toString());
    145
    146                return nodeDiscovery.discoverDevices(ids);
    147        }
    148
    149        /**
    150         * Adds the given discovery listener to the list of listeners to be notified
    151         * when the discovery process is running.
    152         *
    153         * <p>If the listener has already been included, this method does nothing.
    154         * </p>
    155         *
    156         * @param listener Listener to be notified when the discovery process is
    157         *                 running.
    158         *
    159         * @throws NullPointerException if {@code listener == null}.
    160         *
    161         * @see com.digi.xbee.api.listeners.IDiscoveryListener
    162         * @see #removeDiscoveryListener(IDiscoveryListener)
    163         */
    164        public void addDiscoveryListener(IDiscoveryListener listener) {
    165                if (listener == null)
    166                        throw new NullPointerException("Listener cannot be null.");
    167
    168                synchronized (discoveryListeners) {
    169                        if (!discoveryListeners.contains(listener))
    170                                discoveryListeners.add(listener);
    171                }
    172        }
    173
    174        /**
    175         * Removes the given discovery listener from the list of discovery
    176         * listeners.
    177         *
    178         * <p>If the listener is not included in the list, this method does nothing.
    179         * </p>
    180         *
    181         * @param listener Discovery listener to remove.
    182         *
    183         * @throws NullPointerException if {@code listener == null}.
    184         *
    185         * @see com.digi.xbee.api.listeners.IDiscoveryListener
    186         * @see #addDiscoveryListener(IDiscoveryListener)
    187         */
    188        public void removeDiscoveryListener(IDiscoveryListener listener) {
    189                if (listener == null)
    190                        throw new NullPointerException("Listener cannot be null.");
    191
    192                synchronized (discoveryListeners) {
    193                        if (discoveryListeners.contains(listener))
    194                                discoveryListeners.remove(listener);
    195                }
    196        }
    197
    198        /**
    199         * Starts the discovery process with the configured timeout and options.
    200         *
    201         * <p>To be notified every time an XBee device is discovered, add a
    202         * {@code IDiscoveryListener} using the
    203         * {@link #addDiscoveryListener(IDiscoveryListener)} method before starting
    204         * the discovery process.</p>
    205         *
    206         * <p>To configure the discovery timeout, use the
    207         * {@link #setDiscoveryTimeout(long)} method.</p>
    208         *
    209         * <p>To configure the discovery options, use the
    210         * {@link #setDiscoveryOptions(Set)} method.</p>
    211         *
    212         * @throws IllegalStateException if the discovery process is already running.
    213         * @throws InterfaceNotOpenException if the device is not open.
    214         *
    215         * @see #addDiscoveryListener(IDiscoveryListener)
    216         * @see #stopDiscoveryProcess()
    217         */
    218        public void startDiscoveryProcess() {
    219                if (isDiscoveryRunning())
    220                        throw new IllegalStateException("The discovery process is already running.");
    221
    222                nodeDiscovery.startDiscoveryProcess(discoveryListeners);
    223        }
    224
    225        /**
    226         * Stops the discovery process if it is running.
    227         *
    228         * <p>Note that DigiMesh/DigiPoint devices are blocked until the discovery
    229         * time configured (NT parameter) has elapsed, so if you try to get/set
    230         * any parameter during the discovery process you will receive a timeout
    231         * exception.</p>
    232         *
    233         * @see #isDiscoveryRunning()
    234         * @see #removeDiscoveryListener(IDiscoveryListener)
    235         * @see #startDiscoveryProcess()
    236         */
    237        public void stopDiscoveryProcess() {
    238                nodeDiscovery.stopDiscoveryProcess();
    239        }
    240
    241        /**
    242         * Retrieves whether the discovery process is running or not.
    243         *
    244         * @return {@code true} if the discovery process is running, {@code false}
    245         *         otherwise.
    246         *
    247         * @see #startDiscoveryProcess()
    248         * @see #stopDiscoveryProcess()
    249         */
    250        public boolean isDiscoveryRunning() {
    251                return nodeDiscovery.isRunning();
    252        }
    253
    254        /**
    255         * Configures the discovery timeout ({@code NT} parameter) with the given
    256         * value.
    257         *
    258         * <p>Note that in some protocols, the discovery process may take longer
    259         * than the value set in this method due to the network propagation time.
    260         * </p>
    261         *
    262         * @param timeout New discovery timeout in milliseconds.
    263         *
    264         * @throws TimeoutException if there is a timeout setting the discovery
    265         *                          timeout.
    266         * @throws XBeeException if there is any other XBee related exception.
    267         *
    268         * @see #setDiscoveryOptions(Set)
    269         */
    270        public void setDiscoveryTimeout(long timeout) throws TimeoutException, XBeeException {
    271                if (timeout <= 0)
    272                        throw new IllegalArgumentException("Timeout must be bigger than 0.");
    273
    274                localDevice.setParameter("NT", ByteUtils.longToByteArray(timeout / 100));
    275        }
    276
    277        /**
    278         * Configures the discovery options ({@code NO} parameter) with the given
    279         * value.
    280         *
    281         * @param options New discovery options.
    282         *
    283         * @throws TimeoutException if there is a timeout setting the discovery
    284         *                          options.
    285         * @throws XBeeException if there is any other XBee related exception.
    286         *
    287         * @see #setDiscoveryTimeout(long)
    288         */
    289        public void setDiscoveryOptions(Set<DiscoveryOptions> options) throws TimeoutException, XBeeException {
    290                if (options == null)
    291                        throw new NullPointerException("Options cannot be null.");
    292
    293                int value = DiscoveryOptions.calculateDiscoveryValue(localDevice.getXBeeProtocol(), options);
    294                localDevice.setParameter("NO", ByteUtils.intToByteArray(value));
    295        }
    296
    297        /**
    298         * Returns all remote devices already contained in the network.
    299         *
    300         * <p>Note that this method <b>does not perform a discovery</b>, only
    301         * returns the devices that have been previously discovered.</p>
    302         *
    303         * @return A list with all XBee devices in the network.
    304         *
    305         * @see #getDevices(String)
    306         * @see RemoteXBeeDevice
    307         */
    308        public List<RemoteXBeeDevice> getDevices() {
    309                List<RemoteXBeeDevice> nodes = new ArrayList<RemoteXBeeDevice>();
    310                nodes.addAll(remotesBy64BitAddr.values());
    311                nodes.addAll(remotesBy16BitAddr.values());
    312                return nodes;
    313        }
    314
    315        /**
    316         * Returns all remote devices that match the supplied identifier.
    317         *
    318         * <p>Note that this method <b>does not perform a discovery</b>, only
    319         * returns the devices that have been previously discovered.</p>
    320         *
    321         * @param id The identifier of the devices to be retrieved.
    322         *
    323         * @return A list of the remote XBee devices contained in the network with
    324         *         the given identifier.
    325         *
    326         * @throws IllegalArgumentException if {@code id.length() == 0}.
    327         * @throws NullPointerException if {@code id == null}.
    328         *
    329         * @see #getDevice(String)
    330         * @see RemoteXBeeDevice
    331         */
    332        public List<RemoteXBeeDevice> getDevices(String id) {
    333                if (id == null)
    334                        throw new NullPointerException("Device identifier cannot be null.");
    335                if (id.length() == 0)
    336                        throw new IllegalArgumentException("Device identifier cannot be an empty string.");
    337
    338                List<RemoteXBeeDevice> devices = new ArrayList<RemoteXBeeDevice>();
    339
    340                // Look in the 64-bit map.
    341                for (RemoteXBeeDevice remote : remotesBy64BitAddr.values()) {
    342                        if (remote.getNodeID().equals(id))
    343                                devices.add(remote);
    344                }
    345                // Look in the 16-bit map.
    346                for (RemoteXBeeDevice remote : remotesBy16BitAddr.values()) {
    347                        if (remote.getNodeID().equals(id))
    348                                devices.add(remote);
    349                }
    350                // Return the list.
    351                return devices;
    352        }
    353
    354        /**
    355         * Returns the first remote device that matches the supplied identifier.
    356         *
    357         * <p>Note that this method <b>does not perform a discovery</b>, only
    358         * returns the device that has been previously discovered.</p>
    359         *
    360         * @param id The identifier of the device to be retrieved.
    361         *
    362         * @return The remote XBee device contained in the network with the given
    363         *         identifier, {@code null} if the network does not contain any
    364         *         device with that Node ID.
    365         *
    366         * @throws IllegalArgumentException if {@code id.length() == 0}.
    367         * @throws NullPointerException if {@code id == null}.
    368         *
    369         * @see #discoverDevice(String)
    370         * @see #getDevices(String)
    371         * @see RemoteXBeeDevice
    372         */
    373        public RemoteXBeeDevice getDevice(String id) {
    374                if (id == null)
    375                        throw new NullPointerException("Device identifier cannot be null.");
    376                if (id.length() == 0)
    377                        throw new IllegalArgumentException("Device identifier cannot be an empty string.");
    378
    379                // Look in the 64-bit map.
    380                for (RemoteXBeeDevice remote : remotesBy64BitAddr.values()) {
    381                        if (remote.getNodeID().equals(id))
    382                                return remote;
    383                }
    384                // Look in the 16-bit map.
    385                for (RemoteXBeeDevice remote : remotesBy16BitAddr.values()) {
    386                        if (remote.getNodeID().equals(id))
    387                                return remote;
    388                }
    389                // The given ID is not in the network.
    390                return null;
    391        }
    392
    393        /**
    394         * Returns the remote device already contained in the network whose 64-bit
    395         * address matches the given one.
    396         *
    397         * <p>Note that this method <b>does not perform a discovery</b>, only
    398         * returns the device that has been previously discovered.</p>
    399         *
    400         * @param address The 64-bit address of the device to be retrieved.
    401         *
    402         * @return The remote device in the network or {@code null} if it is not
    403         *         found.
    404         *
    405         * @throws IllegalArgumentException if {@code address.equals(XBee64BitAddress.UNKNOWN_ADDRESS)}.
    406         * @throws NullPointerException if {@code address == null}.
    407         */
    408        public RemoteXBeeDevice getDevice(XBee64BitAddress address) {
    409                if (address == null)
    410                        throw new NullPointerException("64-bit address cannot be null.");
    411                if (address.equals(XBee64BitAddress.UNKNOWN_ADDRESS))
    412                        throw new NullPointerException("64-bit address cannot be unknown.");
    413
    414                logger.debug("{}Getting device '{}' from network.", localDevice.toString(), address);
    415
    416                return remotesBy64BitAddr.get(address);
    417        }
    418
    419        /**
    420         * Returns the remote device already contained in the network whose 16-bit
    421         * address matches the given one.
    422         *
    423         * <p>Note that this method <b>does not perform a discovery</b>, only
    424         * returns the device that has been previously discovered.</p>
    425         *
    426         * @param address The 16-bit address of the device to be retrieved.
    427         *
    428         * @return The remote device in the network or {@code null} if it is not
    429         *         found.
    430         *
    431         * @throws IllegalArgumentException if {@code address.equals(XBee16BitAddress.UNKNOWN_ADDRESS)}.
    432         * @throws NullPointerException if {@code address == null}.
    433         * @throws OperationNotSupportedException if the protocol of the local XBee device is DigiMesh.
    434         */
    435        public RemoteXBeeDevice getDevice(XBee16BitAddress address) throws OperationNotSupportedException {
    436                if (localDevice.getXBeeProtocol() == XBeeProtocol.DIGI_MESH)
    437                        throw new OperationNotSupportedException("DigiMesh protocol does not support 16-bit addressing.");
    438                if (localDevice.getXBeeProtocol() == XBeeProtocol.DIGI_POINT)
    439                        throw new OperationNotSupportedException("Point-to-Multipoint protocol does not support 16-bit addressing.");
    440                if (address == null)
    441                        throw new NullPointerException("16-bit address cannot be null.");
    442                if (address.equals(XBee16BitAddress.UNKNOWN_ADDRESS))
    443                        throw new NullPointerException("16-bit address cannot be unknown.");
    444
    445                logger.debug("{}Getting device '{}' from network.", localDevice.toString(), address);
    446
    447                // The preference order is:
    448                //    1.- Look in the 64-bit map
    449                //    2.- Then in the 16-bit map.
    450                // This should be maintained in the 'addRemoteDevice' method.
    451
    452                RemoteXBeeDevice devInNetwork = null;
    453
    454                // Look in the 64-bit map.
    455                Collection<RemoteXBeeDevice> devices = remotesBy64BitAddr.values();
    456                for (RemoteXBeeDevice d: devices) {
    457                        XBee16BitAddress a = get16BitAddress(d);
    458                        if (a != null && a.equals(address)) {
    459                                devInNetwork = d;
    460                                break;
    461                        }
    462                }
    463
    464                // Look in the 16-bit map.
    465                if (devInNetwork == null)
    466                        devInNetwork = remotesBy16BitAddr.get(address);
    467
    468                return devInNetwork;
    469        }
    470
    471        /**
    472         * Adds a remote device to the network.
    473         *
    474         * <p>This method will look for the 64-bit address. If it is not configured:
    475         * </p>
    476         *
    477         * <ul>
    478         * <li>For 802.15.4 devices, it will look for the 16-bit address.</li>
    479         * <li>For the rest will return {@code false} as the result of the addition.
    480         * </li>
    481         * </ul>
    482         *
    483         * @param remoteDevice The remote device to be added to the network.
    484         *
    485         * @return The remote XBee Device instance in the network, {@code null} if
    486         *         the device could not be successfully added.
    487         *
    488         * @throws NullPointerException if {@code RemoteDevice == null}.
    489         *
    490         * @see #addRemoteDevices(List)
    491         */
    492        public RemoteXBeeDevice addRemoteDevice(RemoteXBeeDevice remoteDevice) {
    493                if (remoteDevice == null)
    494                        throw new NullPointerException("Remote device cannot be null.");
    495
    496                logger.debug("{}Adding device '{}' to network.", localDevice.toString(), remoteDevice.toString());
    497
    498                RemoteXBeeDevice devInNetwork = null;
    499                XBee64BitAddress addr64 = remoteDevice.get64BitAddress();
    500                XBee16BitAddress addr16 = get16BitAddress(remoteDevice);
    501
    502                // Check if the device has 64-bit address.
    503                if (addr64 != null && !addr64.equals(XBee64BitAddress.UNKNOWN_ADDRESS)) {
    504                        // The device has 64-bit address, so look in the 64-bit map.
    505                        devInNetwork = remotesBy64BitAddr.get(addr64);
    506                        if (devInNetwork != null) {
    507                                // The device exists in the 64-bit map, so update the reference and return it.
    508                                logger.debug("{}Existing device '{}' in network.", localDevice.toString(), devInNetwork.toString());
    509                                devInNetwork.updateDeviceDataFrom(remoteDevice);
    510                                return devInNetwork;
    511                        } else {
    512                                // The device does not exist in the 64-bit map, so check its 16-bit address.
    513                                if (addr16 != null && !addr16.equals(XBee16BitAddress.UNKNOWN_ADDRESS)) {
    514                                        // The device has 16-bit address, so look in the 16-bit map.
    515                                        devInNetwork = remotesBy16BitAddr.get(addr16);
    516                                        if (devInNetwork != null) {
    517                                                // The device exists in the 16-bit map, so remove it and add it to the 64-bit map.
    518                                                logger.debug("{}Existing device '{}' in network.", localDevice.toString(), devInNetwork.toString());
    519                                                devInNetwork = remotesBy16BitAddr.remove(addr16);
    520                                                devInNetwork.updateDeviceDataFrom(remoteDevice);
    521                                                remotesBy64BitAddr.put(addr64, devInNetwork);
    522                                                return devInNetwork;
    523                                        } else {
    524                                                // The device does not exist in the 16-bit map, so add it to the 64-bit map.
    525                                                remotesBy64BitAddr.put(addr64, remoteDevice);
    526                                                return remoteDevice;
    527                                        }
    528                                } else {
    529                                        // The device has not 16-bit address, so add it to the 64-bit map.
    530                                        remotesBy64BitAddr.put(addr64, remoteDevice);
    531                                        return remoteDevice;
    532                                }
    533                        }
    534                }
    535
    536                // If the device has not 64-bit address, check if it has 16-bit address.
    537                if (addr16 != null && !addr16.equals(XBee16BitAddress.UNKNOWN_ADDRESS)) {
    538                        // The device has 16-bit address, so look in the 64-bit map.
    539                        Collection<RemoteXBeeDevice> devices = remotesBy64BitAddr.values();
    540                        for (RemoteXBeeDevice d : devices) {
    541                                XBee16BitAddress a = get16BitAddress(d);
    542                                if (a != null && a.equals(addr16)) {
    543                                        devInNetwork = d;
    544                                        break;
    545                                }
    546                        }
    547                        // Check if the device exists in the 64-bit map.
    548                        if (devInNetwork != null) {
    549                                // The device exists in the 64-bit map, so update the reference and return it.
    550                                logger.debug("{}Existing device '{}' in network.", localDevice.toString(), devInNetwork.toString());
    551                                devInNetwork.updateDeviceDataFrom(remoteDevice);
    552                                return devInNetwork;
    553                        } else {
    554                                // The device does not exist in the 64-bit map, so look in the 16-bit map.
    555                                devInNetwork = remotesBy16BitAddr.get(addr16);
    556                                if (devInNetwork != null) {
    557                                        // The device exists in the 16-bit map, so update the reference and return it.
    558                                        logger.debug("{}Existing device '{}' in network.", localDevice.toString(), devInNetwork.toString());
    559                                        devInNetwork.updateDeviceDataFrom(remoteDevice);
    560                                        return devInNetwork;
    561                                } else {
    562                                        // The device does not exist in the 16-bit map, so add it.
    563                                        remotesBy16BitAddr.put(addr16, remoteDevice);
    564                                        return remoteDevice;
    565                                }
    566                        }
    567                }
    568
    569                // If the device does not contain a valid address, return null.
    570                logger.error("{}Remote device '{}' cannot be added: 64-bit and 16-bit addresses must be specified.",
    571                                localDevice.toString(), remoteDevice.toString());
    572                return null;
    573        }
    574
    575        /**
    576         * Adds the given list of remote devices to the network.
    577         *
    578         * <p>The way of adding a device to the network is based on the 64-bit
    579         * address. If it is not configured:</p>
    580         *
    581         * <ul>
    582         * <li>For 802.15.4 devices, the 16-bit address will be used instead.</li>
    583         * <li>For the rest will return {@code false} as the result of the addition.
    584         * </li>
    585         * </ul>
    586         *
    587         * @param list The list of remote devices to be added to the network.
    588         *
    589         * @return A list with the successfully added devices to the network.
    590         *
    591         * @throws NullPointerException if {@code list == null}.
    592         *
    593         * @see #addRemoteDevice(RemoteXBeeDevice)
    594         */
    595        public List<RemoteXBeeDevice> addRemoteDevices(List<RemoteXBeeDevice> list) {
    596                if (list == null)
    597                        throw new NullPointerException("The list of remote devices cannot be null.");
    598
    599                List<RemoteXBeeDevice> addedList = new ArrayList<RemoteXBeeDevice>(list.size());
    600
    601                if (list.size() == 0)
    602                        return addedList;
    603
    604                logger.debug("{}Adding '{}' devices to network.", localDevice.toString(), list.size());
    605
    606                for (int i = 0; i < list.size(); i++) {
    607                        RemoteXBeeDevice d = addRemoteDevice(list.get(i));
    608                        if (d != null)
    609                                addedList.add(d);
    610                }
    611
    612                return addedList;
    613        }
    614
    615        /**
    616         * Removes the given remote XBee device from the network.
    617         *
    618         * <p>This method will check for a device that matches the 64-bit address
    619         * of the provided one, if found, that device will be removed from the
    620         * corresponding list. In case the 64-bit address is null, it will look for
    621         * the device in the 16-bit addresses list.</p>
    622         *
    623         * @param remoteDevice The remote device to be removed from the network.
    624         *
    625         * @throws NullPointerException if {@code RemoteDevice == null}.
    626         */
    627        public void removeRemoteDevice(RemoteXBeeDevice remoteDevice) {
    628                if (remoteDevice == null)
    629                        throw new NullPointerException("Remote device cannot be null.");
    630
    631                RemoteXBeeDevice devInNetwork = null;
    632
    633                // Look in the 64-bit map.
    634                XBee64BitAddress addr64 = remoteDevice.get64BitAddress();
    635                if (addr64 != null && !addr64.equals(XBee64BitAddress.UNKNOWN_ADDRESS)) {
    636                        devInNetwork = remotesBy64BitAddr.get(addr64);
    637
    638                        // Remove the device.
    639                        if (devInNetwork != null) {
    640                                remotesBy64BitAddr.remove(addr64);
    641                                return;
    642                        }
    643                }
    644
    645                // If not found, look in the 16-bit map.
    646                XBee16BitAddress addr16 = get16BitAddress(remoteDevice);
    647                if (addr16 != null && !addr16.equals(XBee16BitAddress.UNKNOWN_ADDRESS)) {
    648
    649                        // The preference order is:
    650                        //    1.- Look in the 64-bit map
    651                        //    2.- Then in the 16-bit map.
    652                        // This should be maintained in the 'getDeviceBy16BitAddress' method.
    653
    654                        // Look for the 16-bit address in the 64-bit map.
    655                        Collection<RemoteXBeeDevice> devices = remotesBy64BitAddr.values();
    656                        for (RemoteXBeeDevice d: devices) {
    657                                XBee16BitAddress a = get16BitAddress(d);
    658                                if (a != null && a.equals(addr16)) {
    659                                        remotesBy64BitAddr.remove(d.get64BitAddress());
    660                                        return;
    661                                }
    662                        }
    663
    664                        // If not found, look for the 16-bit address in the 16-bit map.
    665                        devInNetwork = remotesBy16BitAddr.get(addr16);
    666
    667                        // Remove the device.
    668                        if (devInNetwork != null) {
    669                                remotesBy16BitAddr.remove(addr16);
    670                                return;
    671                        }
    672                }
    673
    674                // If the device does not contain a valid address log an error.
    675                if ((addr64 == null || addr64.equals(XBee64BitAddress.UNKNOWN_ADDRESS))
    676                                && (addr16 == null || addr16.equals(XBee16BitAddress.UNKNOWN_ADDRESS)))
    677                        logger.error("{}Remote device '{}' cannot be removed: 64-bit and 16-bit addresses must be specified.",
    678                                        localDevice.toString(), remoteDevice.toString());
    679        }
    680
    681        /**
    682         * Removes all the devices from this network.
    683         *
    684         * <p>The network will be empty after this call returns.</p>
    685         */
    686        public void clearDeviceList() {
    687                logger.debug("{}Clearing the network.", localDevice.toString());
    688                remotesBy64BitAddr.clear();
    689                remotesBy64BitAddr.clear();
    690        }
    691
    692        /**
    693         * Returns the number of devices already discovered in this network.
    694         *
    695         * @return The number of devices already discovered in this network.
    696         */
    697        public int getNumberOfDevices() {
    698                return remotesBy64BitAddr.size() + remotesBy16BitAddr.size();
    699        }
    700
    701        /**
    702         * Retrieves the 16-bit address of the given remote device.
    703         *
    704         * @param device The remote device to get the 16-bit address.
    705         *
    706         * @return The 16-bit address of the device, {@code null} if it does not
    707         *         contain a valid one.
    708         */
    709        private XBee16BitAddress get16BitAddress(RemoteXBeeDevice device) {
    710                if (device == null)
    711                        return null;
    712
    713                XBee16BitAddress address = null;
    714
    715                switch (device.getXBeeProtocol()) {
    716                case RAW_802_15_4:
    717                        address = ((RemoteRaw802Device)device).get16BitAddress();
    718                        break;
    719                case ZIGBEE:
    720                        address = ((RemoteZigBeeDevice)device).get16BitAddress();
    721                        break;
    722                default:
    723                        // TODO should we allow this operation for general remote devices?
    724                        address = device.get16BitAddress();
    725                        break;
    726                }
    727
    728                return address;
    729        }
    730
    731        /*
    732         * (non-Javadoc)
    733         * @see java.lang.Object#toString()
    734         */
    735        @Override
    736        public String toString(){
    737                return getClass().getName() + " [" + localDevice.toString() + "] @" +
    738                                Integer.toHexString(hashCode());
    739        }
    740}