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                synchronized (discoveryListeners) {
223                        nodeDiscovery.startDiscoveryProcess(discoveryListeners);
224                }
225        }
226        
227        /**
228         * Stops the discovery process if it is running.
229         * 
230         * <p>Note that DigiMesh/DigiPoint devices are blocked until the discovery
231         * time configured (NT parameter) has elapsed, so if you try to get/set
232         * any parameter during the discovery process you will receive a timeout 
233         * exception.</p>
234         * 
235         * @see #isDiscoveryRunning()
236         * @see #removeDiscoveryListener(IDiscoveryListener)
237         * @see #startDiscoveryProcess()
238         */
239        public void stopDiscoveryProcess() {
240                nodeDiscovery.stopDiscoveryProcess();
241        }
242        
243        /**
244         * Retrieves whether the discovery process is running or not.
245         * 
246         * @return {@code true} if the discovery process is running, {@code false} 
247         *         otherwise.
248         * 
249         * @see #startDiscoveryProcess()
250         * @see #stopDiscoveryProcess()
251         */
252        public boolean isDiscoveryRunning() {
253                return nodeDiscovery.isRunning();
254        }
255        
256        /**
257         * Configures the discovery timeout ({@code NT} parameter) with the given 
258         * value.
259         * 
260         * <p>Note that in some protocols, the discovery process may take longer
261         * than the value set in this method due to the network propagation time.
262         * </p>
263         * 
264         * @param timeout New discovery timeout in milliseconds.
265         * 
266         * @throws TimeoutException if there is a timeout setting the discovery
267         *                          timeout.
268         * @throws XBeeException if there is any other XBee related exception.
269         * 
270         * @see #setDiscoveryOptions(Set)
271         */
272        public void setDiscoveryTimeout(long timeout) throws TimeoutException, XBeeException {
273                if (timeout <= 0)
274                        throw new IllegalArgumentException("Timeout must be bigger than 0.");
275                
276                localDevice.setParameter("NT", ByteUtils.longToByteArray(timeout / 100));
277        }
278        
279        /**
280         * Configures the discovery options ({@code NO} parameter) with the given 
281         * value.
282         * 
283         * @param options New discovery options.
284         * 
285         * @throws TimeoutException if there is a timeout setting the discovery
286         *                          options.
287         * @throws XBeeException if there is any other XBee related exception.
288         * 
289         * @see #setDiscoveryTimeout(long)
290         * @see DiscoveryOptions
291         */
292        public void setDiscoveryOptions(Set<DiscoveryOptions> options) throws TimeoutException, XBeeException {
293                if (options == null)
294                        throw new NullPointerException("Options cannot be null.");
295                
296                int value = DiscoveryOptions.calculateDiscoveryValue(localDevice.getXBeeProtocol(), options);
297                localDevice.setParameter("NO", ByteUtils.intToByteArray(value));
298        }
299        
300        /**
301         * Returns all remote devices already contained in the network.
302         * 
303         * <p>Note that this method <b>does not perform a discovery</b>, only 
304         * returns the devices that have been previously discovered.</p>
305         * 
306         * @return A list with all XBee devices in the network.
307         * 
308         * @see #getDevices(String)
309         * @see RemoteXBeeDevice
310         */
311        public List<RemoteXBeeDevice> getDevices() {
312                List<RemoteXBeeDevice> nodes = new ArrayList<RemoteXBeeDevice>();
313                nodes.addAll(remotesBy64BitAddr.values());
314                nodes.addAll(remotesBy16BitAddr.values());
315                return nodes;
316        }
317        
318        /**
319         * Returns all remote devices that match the supplied identifier.
320         * 
321         * <p>Note that this method <b>does not perform a discovery</b>, only 
322         * returns the devices that have been previously discovered.</p>
323         * 
324         * @param id The identifier of the devices to be retrieved.
325         * 
326         * @return A list of the remote XBee devices contained in the network with 
327         *         the given identifier.
328         * 
329         * @throws IllegalArgumentException if {@code id.length() == 0}.
330         * @throws NullPointerException if {@code id == null}.
331         * 
332         * @see #getDevice(String)
333         * @see RemoteXBeeDevice
334         */
335        public List<RemoteXBeeDevice> getDevices(String id) {
336                if (id == null)
337                        throw new NullPointerException("Device identifier cannot be null.");
338                if (id.length() == 0)
339                        throw new IllegalArgumentException("Device identifier cannot be an empty string.");
340                
341                List<RemoteXBeeDevice> devices = new ArrayList<RemoteXBeeDevice>();
342                
343                // Look in the 64-bit map.
344                for (RemoteXBeeDevice remote : remotesBy64BitAddr.values()) {
345                        if (remote.getNodeID().equals(id))
346                                devices.add(remote);
347                }
348                // Look in the 16-bit map.
349                for (RemoteXBeeDevice remote : remotesBy16BitAddr.values()) {
350                        if (remote.getNodeID().equals(id))
351                                devices.add(remote);
352                }
353                // Return the list.
354                return devices;
355        }
356        
357        /**
358         * Returns the first remote device that matches the supplied identifier.
359         * 
360         * <p>Note that this method <b>does not perform a discovery</b>, only 
361         * returns the device that has been previously discovered.</p>
362         * 
363         * @param id The identifier of the device to be retrieved.
364         * 
365         * @return The remote XBee device contained in the network with the given 
366         *         identifier, {@code null} if the network does not contain any 
367         *         device with that Node ID.
368         * 
369         * @throws IllegalArgumentException if {@code id.length() == 0}.
370         * @throws NullPointerException if {@code id == null}.
371         * 
372         * @see #discoverDevice(String)
373         * @see #getDevices(String)
374         * @see RemoteXBeeDevice
375         */
376        public RemoteXBeeDevice getDevice(String id) {
377                if (id == null)
378                        throw new NullPointerException("Device identifier cannot be null.");
379                if (id.length() == 0)
380                        throw new IllegalArgumentException("Device identifier cannot be an empty string.");
381                
382                // Look in the 64-bit map.
383                for (RemoteXBeeDevice remote : remotesBy64BitAddr.values()) {
384                        if (remote.getNodeID().equals(id))
385                                return remote;
386                }
387                // Look in the 16-bit map.
388                for (RemoteXBeeDevice remote : remotesBy16BitAddr.values()) {
389                        if (remote.getNodeID().equals(id))
390                                return remote;
391                }
392                // The given ID is not in the network.
393                return null;
394        }
395        
396        /**
397         * Returns the remote device already contained in the network whose 64-bit 
398         * address matches the given one.
399         * 
400         * <p>Note that this method <b>does not perform a discovery</b>, only 
401         * returns the device that has been previously discovered.</p>
402         * 
403         * @param address The 64-bit address of the device to be retrieved.
404         * 
405         * @return The remote device in the network or {@code null} if it is not 
406         *         found.
407         * 
408         * @throws IllegalArgumentException if {@code address.equals(XBee64BitAddress.UNKNOWN_ADDRESS)}.
409         * @throws NullPointerException if {@code address == null}.
410         */
411        public RemoteXBeeDevice getDevice(XBee64BitAddress address) {
412                if (address == null)
413                        throw new NullPointerException("64-bit address cannot be null.");
414                if (address.equals(XBee64BitAddress.UNKNOWN_ADDRESS))
415                        throw new NullPointerException("64-bit address cannot be unknown.");
416                
417                logger.debug("{}Getting device '{}' from network.", localDevice.toString(), address);
418                
419                return remotesBy64BitAddr.get(address);
420        }
421        
422        /**
423         * Returns the remote device already contained in the network whose 16-bit 
424         * address matches the given one.
425         * 
426         * <p>Note that this method <b>does not perform a discovery</b>, only 
427         * returns the device that has been previously discovered.</p>
428         * 
429         * @param address The 16-bit address of the device to be retrieved.
430         * 
431         * @return The remote device in the network or {@code null} if it is not 
432         *         found.
433         * 
434         * @throws IllegalArgumentException if {@code address.equals(XBee16BitAddress.UNKNOWN_ADDRESS)}.
435         * @throws NullPointerException if {@code address == null}.
436         * @throws OperationNotSupportedException if the protocol of the local XBee device is DigiMesh or Point-to-Multipoint.
437         */
438        public RemoteXBeeDevice getDevice(XBee16BitAddress address) throws OperationNotSupportedException {
439                if (localDevice.getXBeeProtocol() == XBeeProtocol.DIGI_MESH)
440                        throw new OperationNotSupportedException("DigiMesh protocol does not support 16-bit addressing.");
441                if (localDevice.getXBeeProtocol() == XBeeProtocol.DIGI_POINT)
442                        throw new OperationNotSupportedException("Point-to-Multipoint protocol does not support 16-bit addressing.");
443                if (address == null)
444                        throw new NullPointerException("16-bit address cannot be null.");
445                if (address.equals(XBee16BitAddress.UNKNOWN_ADDRESS))
446                        throw new NullPointerException("16-bit address cannot be unknown.");
447                
448                logger.debug("{}Getting device '{}' from network.", localDevice.toString(), address);
449                
450                // The preference order is: 
451                //    1.- Look in the 64-bit map 
452                //    2.- Then in the 16-bit map.
453                // This should be maintained in the 'addRemoteDevice' method.
454                
455                RemoteXBeeDevice devInNetwork = null;
456                
457                // Look in the 64-bit map.
458                Collection<RemoteXBeeDevice> devices = remotesBy64BitAddr.values();
459                for (RemoteXBeeDevice d: devices) {
460                        XBee16BitAddress a = get16BitAddress(d);
461                        if (a != null && a.equals(address)) {
462                                devInNetwork = d;
463                                break;
464                        }
465                }
466                
467                // Look in the 16-bit map.
468                if (devInNetwork == null)
469                        devInNetwork = remotesBy16BitAddr.get(address);
470                
471                return devInNetwork;
472        }
473        
474        /**
475         * Adds the given remote device to the network. 
476         * 
477         * <p>Notice that this operation does not join the remote XBee device to the
478         * network; it just tells the network that it contains that device. However, 
479         * the device has only been added to the device list, and may not be 
480         * physically in the same network.</p>
481         * 
482         * <p>The way of adding a device to the network is based on the 64-bit 
483         * address. If it is not configured:</p>
484         * 
485         * <ul>
486         * <li>For 802.15.4 and ZigBee devices, it will use the 16-bit address.</li>
487         * <li>For the rest will return {@code false} as the result of the addition.</li>
488         * </ul>
489         * 
490         * @param remoteDevice The remote device to be added to the network.
491         * 
492         * @return The remote XBee Device instance in the network, {@code null} if
493         *         the device could not be successfully added.
494         * 
495         * @throws NullPointerException if {@code RemoteDevice == null}.
496         * 
497         * @see #addRemoteDevices(List)
498         * @see #removeRemoteDevice(RemoteXBeeDevice)
499         * @see RemoteXBeeDevice
500         */
501        public RemoteXBeeDevice addRemoteDevice(RemoteXBeeDevice remoteDevice) {
502                if (remoteDevice == null)
503                        throw new NullPointerException("Remote device cannot be null.");
504                
505                logger.debug("{}Adding device '{}' to network.", localDevice.toString(), remoteDevice.toString());
506                
507                RemoteXBeeDevice devInNetwork = null;
508                XBee64BitAddress addr64 = remoteDevice.get64BitAddress();
509                XBee16BitAddress addr16 = get16BitAddress(remoteDevice);
510                
511                // Check if the device has 64-bit address.
512                if (addr64 != null && !addr64.equals(XBee64BitAddress.UNKNOWN_ADDRESS)) {
513                        // The device has 64-bit address, so look in the 64-bit map.
514                        devInNetwork = remotesBy64BitAddr.get(addr64);
515                        if (devInNetwork != null) {
516                                // The device exists in the 64-bit map, so update the reference and return it.
517                                logger.debug("{}Existing device '{}' in network.", localDevice.toString(), devInNetwork.toString());
518                                devInNetwork.updateDeviceDataFrom(remoteDevice);
519                                return devInNetwork;
520                        } else {
521                                // The device does not exist in the 64-bit map, so check its 16-bit address.
522                                if (addr16 != null && !addr16.equals(XBee16BitAddress.UNKNOWN_ADDRESS)) {
523                                        // The device has 16-bit address, so look in the 16-bit map.
524                                        devInNetwork = remotesBy16BitAddr.get(addr16);
525                                        if (devInNetwork != null) {
526                                                // The device exists in the 16-bit map, so remove it and add it to the 64-bit map.
527                                                logger.debug("{}Existing device '{}' in network.", localDevice.toString(), devInNetwork.toString());
528                                                devInNetwork = remotesBy16BitAddr.remove(addr16);
529                                                devInNetwork.updateDeviceDataFrom(remoteDevice);
530                                                remotesBy64BitAddr.put(addr64, devInNetwork);
531                                                return devInNetwork;
532                                        } else {
533                                                // The device does not exist in the 16-bit map, so add it to the 64-bit map.
534                                                remotesBy64BitAddr.put(addr64, remoteDevice);
535                                                return remoteDevice;
536                                        }
537                                } else {
538                                        // The device has not 16-bit address, so add it to the 64-bit map.
539                                        remotesBy64BitAddr.put(addr64, remoteDevice);
540                                        return remoteDevice;
541                                }
542                        }
543                }
544                
545                // If the device has not 64-bit address, check if it has 16-bit address.
546                if (addr16 != null && !addr16.equals(XBee16BitAddress.UNKNOWN_ADDRESS)) {
547                        // The device has 16-bit address, so look in the 64-bit map.
548                        Collection<RemoteXBeeDevice> devices = remotesBy64BitAddr.values();
549                        for (RemoteXBeeDevice d : devices) {
550                                XBee16BitAddress a = get16BitAddress(d);
551                                if (a != null && a.equals(addr16)) {
552                                        devInNetwork = d;
553                                        break;
554                                }
555                        }
556                        // Check if the device exists in the 64-bit map.
557                        if (devInNetwork != null) {
558                                // The device exists in the 64-bit map, so update the reference and return it.
559                                logger.debug("{}Existing device '{}' in network.", localDevice.toString(), devInNetwork.toString());
560                                devInNetwork.updateDeviceDataFrom(remoteDevice);
561                                return devInNetwork;
562                        } else {
563                                // The device does not exist in the 64-bit map, so look in the 16-bit map.
564                                devInNetwork = remotesBy16BitAddr.get(addr16);
565                                if (devInNetwork != null) {
566                                        // The device exists in the 16-bit map, so update the reference and return it.
567                                        logger.debug("{}Existing device '{}' in network.", localDevice.toString(), devInNetwork.toString());
568                                        devInNetwork.updateDeviceDataFrom(remoteDevice);
569                                        return devInNetwork;
570                                } else {
571                                        // The device does not exist in the 16-bit map, so add it.
572                                        remotesBy16BitAddr.put(addr16, remoteDevice);
573                                        return remoteDevice;
574                                }
575                        }
576                }
577                
578                // If the device does not contain a valid address, return null.
579                logger.error("{}Remote device '{}' cannot be added: 64-bit and 16-bit addresses must be specified.", 
580                                localDevice.toString(), remoteDevice.toString());
581                return null;
582        }
583        
584        /**
585         * Adds the given list of remote devices to the network.
586         * 
587         * <p>Notice that this operation does not join the remote XBee devices to 
588         * the network; it just tells the network that it contains those devices. 
589         * However, the devices have only been added to the device list, and may 
590         * not be physically in the same network.</p>
591         * 
592         * <p>The way of adding a device to the network is based on the 64-bit 
593         * address. If it is not configured:</p>
594         * 
595         * <ul>
596         * <li>For 802.15.4 and ZigBee devices, the 16-bit address will be used instead.</li>
597         * <li>For the rest will return {@code false} as the result of the addition.</li>
598         * </ul>
599         * 
600         * @param list The list of remote devices to be added to the network.
601         * 
602         * @return A list with the successfully added devices to the network.
603         * 
604         * @throws NullPointerException if {@code list == null}.
605         * 
606         * @see #addRemoteDevice(RemoteXBeeDevice)
607         * @see RemoteXBeeDevice
608         */
609        public List<RemoteXBeeDevice> addRemoteDevices(List<RemoteXBeeDevice> list) {
610                if (list == null)
611                        throw new NullPointerException("The list of remote devices cannot be null.");
612                
613                List<RemoteXBeeDevice> addedList = new ArrayList<RemoteXBeeDevice>(list.size());
614                
615                if (list.size() == 0)
616                        return addedList;
617                
618                logger.debug("{}Adding '{}' devices to network.", localDevice.toString(), list.size());
619                
620                for (int i = 0; i < list.size(); i++) {
621                        RemoteXBeeDevice d = addRemoteDevice(list.get(i));
622                        if (d != null)
623                                addedList.add(d);
624                }
625                
626                return addedList;
627        }
628        
629        /**
630         * Removes the given remote XBee device from the network.
631         * 
632         * <p>Notice that this operation does not remove the remote XBee device 
633         * from the actual XBee network; it just tells the network object that it 
634         * will no longer contain that device. However, next time a discovery is 
635         * performed, it could be added again automatically.</p>
636         * 
637         * <p>This method will check for a device that matches the 64-bit address 
638         * of the provided one, if found, that device will be removed from the 
639         * corresponding list. In case the 64-bit address is not defined, it will 
640         * use the 16-bit address for DigiMesh and ZigBee devices.</p>
641         * 
642         * @param remoteDevice The remote device to be removed from the network.
643         * 
644         * @throws NullPointerException if {@code RemoteDevice == null}.
645         * 
646         * @see #addRemoteDevice(RemoteXBeeDevice)
647         * @see #clearDeviceList()
648         * @see RemoteXBeeDevice
649         */
650        public void removeRemoteDevice(RemoteXBeeDevice remoteDevice) {
651                if (remoteDevice == null)
652                        throw new NullPointerException("Remote device cannot be null.");
653                
654                RemoteXBeeDevice devInNetwork = null;
655                
656                // Look in the 64-bit map.
657                XBee64BitAddress addr64 = remoteDevice.get64BitAddress();
658                if (addr64 != null && !addr64.equals(XBee64BitAddress.UNKNOWN_ADDRESS)) {
659                        devInNetwork = remotesBy64BitAddr.get(addr64);
660                        
661                        // Remove the device.
662                        if (devInNetwork != null) {
663                                remotesBy64BitAddr.remove(addr64);
664                                return;
665                        }
666                }
667                
668                // If not found, look in the 16-bit map.
669                XBee16BitAddress addr16 = get16BitAddress(remoteDevice);
670                if (addr16 != null && !addr16.equals(XBee16BitAddress.UNKNOWN_ADDRESS)) {
671                        
672                        // The preference order is: 
673                        //    1.- Look in the 64-bit map 
674                        //    2.- Then in the 16-bit map.
675                        // This should be maintained in the 'getDeviceBy16BitAddress' method.
676                        
677                        // Look for the 16-bit address in the 64-bit map.
678                        Collection<RemoteXBeeDevice> devices = remotesBy64BitAddr.values();
679                        for (RemoteXBeeDevice d: devices) {
680                                XBee16BitAddress a = get16BitAddress(d);
681                                if (a != null && a.equals(addr16)) {
682                                        remotesBy64BitAddr.remove(d.get64BitAddress());
683                                        return;
684                                }
685                        }
686                        
687                        // If not found, look for the 16-bit address in the 16-bit map. 
688                        devInNetwork = remotesBy16BitAddr.get(addr16);
689                        
690                        // Remove the device.
691                        if (devInNetwork != null) {
692                                remotesBy16BitAddr.remove(addr16);
693                                return;
694                        }
695                }
696                
697                // If the device does not contain a valid address log an error.
698                if ((addr64 == null || addr64.equals(XBee64BitAddress.UNKNOWN_ADDRESS)) 
699                                && (addr16 == null || addr16.equals(XBee16BitAddress.UNKNOWN_ADDRESS)))
700                        logger.error("{}Remote device '{}' cannot be removed: 64-bit and 16-bit addresses must be specified.", 
701                                        localDevice.toString(), remoteDevice.toString());
702        }
703        
704        /**
705         * Removes all the devices from this network. 
706         * 
707         * <p>The network will be empty after this call returns.</p>
708         * 
709         * <p>Notice that this does not imply removing the XBee devices from the 
710         * actual XBee network; it just tells the object that the list should be 
711         * empty now. Next time a discovery is performed, the list could be filled 
712         * with the remote XBee devices found.</p>
713         * 
714         * @see #removeRemoteDevice(RemoteXBeeDevice)
715         */
716        public void clearDeviceList() {
717                logger.debug("{}Clearing the network.", localDevice.toString());
718                remotesBy64BitAddr.clear();
719                remotesBy64BitAddr.clear();
720        }
721        
722        /**
723         * Returns the number of devices already discovered in this network.
724         * 
725         * @return The number of devices already discovered in this network.
726         */
727        public int getNumberOfDevices() {
728                return remotesBy64BitAddr.size() + remotesBy16BitAddr.size();
729        }
730        
731        /**
732         * Retrieves the 16-bit address of the given remote device.
733         * 
734         * @param device The remote device to get the 16-bit address.
735         * 
736         * @return The 16-bit address of the device, {@code null} if it does not
737         *         contain a valid one.
738         */
739        private XBee16BitAddress get16BitAddress(RemoteXBeeDevice device) {
740                if (device == null)
741                        return null;
742                
743                XBee16BitAddress address = null;
744                
745                switch (device.getXBeeProtocol()) {
746                case RAW_802_15_4:
747                        address = ((RemoteRaw802Device)device).get16BitAddress();
748                        break;
749                case ZIGBEE:
750                        address = ((RemoteZigBeeDevice)device).get16BitAddress();
751                        break;
752                default:
753                        // TODO should we allow this operation for general remote devices?
754                        address = device.get16BitAddress();
755                        break;
756                }
757                
758                return address;
759        }
760        
761        /*
762         * (non-Javadoc)
763         * @see java.lang.Object#toString()
764         */
765        @Override
766        public String toString(){
767                return getClass().getName() + " [" + localDevice.toString() + "] @" + 
768                                Integer.toHexString(hashCode());
769        }
770}