001/**
002 * Copyright (c) 2014-2015 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.models;
013
014import java.util.LinkedList;
015
016import com.digi.xbee.api.RemoteXBeeDevice;
017import com.digi.xbee.api.packet.APIFrameType;
018import com.digi.xbee.api.packet.XBeeAPIPacket;
019import com.digi.xbee.api.packet.XBeePacket;
020import com.digi.xbee.api.packet.common.ExplicitRxIndicatorPacket;
021import com.digi.xbee.api.packet.common.ReceivePacket;
022import com.digi.xbee.api.packet.common.RemoteATCommandResponsePacket;
023import com.digi.xbee.api.packet.raw.RX16IOPacket;
024import com.digi.xbee.api.packet.raw.RX16Packet;
025import com.digi.xbee.api.packet.raw.RX64IOPacket;
026import com.digi.xbee.api.packet.raw.RX64Packet;
027
028/**
029 * This class represents a queue of XBee packets used for sequential packets 
030 * reading within the XBee Java API. 
031 * 
032 * <p>The class provides some methods to get specific packet types from 
033 * different source nodes.</p>
034 * 
035 * @see com.digi.xbee.api.packet.XBeePacket
036 */
037public class XBeePacketsQueue {
038
039        // Constants.
040        /**
041         * Default maximum number of packets to store in the queue 
042         * (value: {@value}).
043         */
044        public static final int DEFAULT_MAX_LENGTH = 50;
045        
046        // Variables.
047        private int maxLength = DEFAULT_MAX_LENGTH;
048        
049        private LinkedList<XBeePacket> packetsList;
050        
051        private Object lock = new Object();
052        
053        /**
054         * Class constructor. Instantiates a new object of type 
055         * {@code XBeePacketsQueue}.
056         */
057        public XBeePacketsQueue() {
058                this(DEFAULT_MAX_LENGTH);
059        }
060        
061        /**
062         * Class constructor. Instantiates a new object of type 
063         * {@code XBeePacketsQueue} with the given maximum length.
064         * 
065         * @param maxLength Maximum length of the queue.
066         * 
067         * @throws IllegalArgumentException if {@code maxLength < 1}.
068         */
069        public XBeePacketsQueue(int maxLength) {
070                if (maxLength < 1)
071                        throw new IllegalArgumentException("Queue length must be greater than 0.");
072                
073                this.maxLength = maxLength;
074                packetsList = new LinkedList<XBeePacket>();
075        }
076        
077        /**
078         * Adds the provided packet to the list of packets. If the queue is full 
079         * the first packet will be discarded to add the given one.
080         * 
081         * @param xbeePacket The XBee packet to be added to the list.
082         * 
083         * @see com.digi.xbee.api.packet.XBeePacket
084         */
085        public void addPacket(XBeePacket xbeePacket) {
086                synchronized (lock) {
087                        if (packetsList.size() == maxLength)
088                                packetsList.removeFirst();
089                        packetsList.add(xbeePacket);
090                }
091        }
092        
093        /**
094         * Clears the list of packets.
095         */
096        public void clearQueue() {
097                synchronized (lock) {
098                        packetsList.clear();
099                }
100        }
101        
102        /**
103         * Returns the first packet from the queue waiting up to the specified 
104         * timeout if  necessary for an XBee packet to become available. 
105         * {@code null }if the queue is empty.
106         * 
107         * @param timeout The time in milliseconds to wait for an XBee packet to 
108         *                become available. 0 to return immediately.
109         * @return The first packet from the queue, {@code null} if it is empty.
110         * 
111         * @see com.digi.xbee.api.packet.XBeePacket
112         */
113        public XBeePacket getFirstPacket(int timeout) {
114                if (timeout > 0) {
115                        XBeePacket xbeePacket = getFirstPacket(0);
116                        // Wait for a timeout or until an XBee packet is read.
117                        Long deadLine = System.currentTimeMillis() + timeout;
118                        while (xbeePacket == null && deadLine > System.currentTimeMillis()) {
119                                sleep(100);
120                                xbeePacket = getFirstPacket(0);
121                        }
122                        return xbeePacket;
123                } 
124                synchronized (lock) {
125                        if (!packetsList.isEmpty())
126                                return packetsList.pop();
127                }
128                return null;
129        }
130        
131        /**
132         * Returns the first packet from the queue whose 64-bit source address 
133         * matches the address of the provided remote XBee device.
134         * 
135         * <p>The methods waits up to the specified timeout if necessary for an 
136         * XBee packet to become available. Null if the queue is empty or there is 
137         * not any XBee packet sent by the provided remote XBee device.</p>
138         * 
139         * @param remoteXBeeDevice The remote XBee device containing the 64-bit 
140         *                         address to look for in the list of packets.
141         * @param timeout The time in milliseconds to wait for an XBee packet from 
142         *                the specified remote XBee device to become available. 
143         *                0 to return immediately.
144         * 
145         * @return The first XBee packet whose 64-bit address matches the address 
146         *         of the provided remote XBee device. {@code null} if no packets 
147         *         from the specified XBee device are found in the queue.
148         * 
149         * @see com.digi.xbee.api.RemoteXBeeDevice
150         * @see com.digi.xbee.api.packet.XBeePacket
151         */
152        public XBeePacket getFirstPacketFrom(RemoteXBeeDevice remoteXBeeDevice, int timeout) {
153                if (timeout > 0) {
154                        XBeePacket xbeePacket = getFirstPacketFrom(remoteXBeeDevice, 0);
155                        // Wait for a timeout or until an XBee packet from remoteXBeeDevice is read.
156                        Long deadLine = System.currentTimeMillis() + timeout;
157                        while (xbeePacket == null && deadLine > System.currentTimeMillis()) {
158                                sleep(100);
159                                xbeePacket = getFirstPacketFrom(remoteXBeeDevice, 0);
160                        }
161                        return xbeePacket;
162                } else {
163                        synchronized (lock) {
164                                for (int i = 0; i < packetsList.size(); i++) {
165                                        XBeePacket xbeePacket = packetsList.get(i);
166                                        if (addressesMatch(xbeePacket, remoteXBeeDevice))
167                                                return packetsList.remove(i);
168                                }
169                        }
170                }
171                return null;
172        }
173        
174        /**
175         * Returns the first data packet from the queue waiting up to the 
176         * specified timeout if necessary for an XBee data packet to become 
177         * available. {@code null} if the queue is empty or there is not any data 
178         * packet inside.
179         * 
180         * @param timeout The time in milliseconds to wait for an XBee data packet 
181         *                to become available. 0 to return immediately.
182         * 
183         * @return The first data packet from the queue, {@code null} if it is 
184         *         empty or no data packets are contained in the queue.
185         * 
186         * @see com.digi.xbee.api.packet.XBeePacket
187         */
188        public XBeePacket getFirstDataPacket(int timeout) {
189                if (timeout > 0) {
190                        XBeePacket xbeePacket = getFirstDataPacket(0);
191                        // Wait for a timeout or until a data XBee packet is read.
192                        Long deadLine = System.currentTimeMillis() + timeout;
193                        while (xbeePacket == null && deadLine > System.currentTimeMillis()) {
194                                sleep(100);
195                                xbeePacket = getFirstDataPacket(0);
196                        }
197                        return xbeePacket;
198                } else {
199                        synchronized (lock) {
200                                for (int i = 0; i < packetsList.size(); i++) {
201                                        XBeePacket xbeePacket = packetsList.get(i);
202                                        if (isDataPacket(xbeePacket))
203                                                return packetsList.remove(i);
204                                }
205                        }
206                }
207                return null;
208        }
209        
210        /**
211         * Returns the first data packet from the queue whose 64-bit source 
212         * address matches the address of the provided remote XBee device.
213         * 
214         * <p>The methods waits up to the specified timeout if necessary for an 
215         * XBee data packet to become available. {@code null} if the queue is 
216         * empty or there is not any XBee data packet sent by the provided remote 
217         * XBee device.</p>
218         * 
219         * @param remoteXBeeDevice The XBee device containing the 64-bit address 
220         *                         to look for in the list of packets.
221         * @param timeout The time in milliseconds to wait for an XBee data packet 
222         *                from the specified remote XBee device to become 
223         *                available. 0 to return immediately.
224         * 
225         * @return The first XBee data packet whose its 64-bit address matches the 
226         *         address of the provided remote XBee device. {@code null} if no 
227         *         data packets from the specified XBee device are found in the 
228         *         queue.
229         * 
230         * @see com.digi.xbee.api.RemoteXBeeDevice
231         * @see com.digi.xbee.api.packet.XBeePacket
232         */
233        public XBeePacket getFirstDataPacketFrom(RemoteXBeeDevice remoteXBeeDevice, int timeout) {
234                if (timeout > 0) {
235                        XBeePacket xbeePacket = getFirstDataPacketFrom(remoteXBeeDevice, 0);
236                        // Wait for a timeout or until an XBee packet from remoteXBeeDevice is read.
237                        Long deadLine = System.currentTimeMillis() + timeout;
238                        while (xbeePacket == null && deadLine > System.currentTimeMillis()) {
239                                sleep(100);
240                                xbeePacket = getFirstDataPacketFrom(remoteXBeeDevice, 0);
241                        }
242                        return xbeePacket;
243                } else {
244                        synchronized (lock) {
245                                for (int i = 0; i < packetsList.size(); i++) {
246                                        XBeePacket xbeePacket = packetsList.get(i);
247                                        if (isDataPacket(xbeePacket) && addressesMatch(xbeePacket, remoteXBeeDevice))
248                                                return packetsList.remove(i);
249                                }
250                        }
251                }
252                return null;
253        }
254        
255        /**
256         * Returns the first explicit data packet from the queue waiting up to the 
257         * specified timeout if necessary for an XBee explicit data packet to 
258         * become available. {@code null} if the queue is empty or there is not 
259         * any explicit data packet inside.
260         * 
261         * @param timeout The time in milliseconds to wait for an XBee explicit 
262         *                data packet to become available. 0 to return immediately.
263         * 
264         * @return The first explicit data packet from the queue, {@code null} if 
265         *         it is empty or no data packets are contained in the queue.
266         * 
267         * @see com.digi.xbee.api.packet.XBeePacket
268         * @see com.digi.xbee.api.packet.common.ExplicitRxIndicatorPacket
269         */
270        public XBeePacket getFirstExplicitDataPacket(int timeout) {
271                if (timeout > 0) {
272                        XBeePacket xbeePacket = getFirstExplicitDataPacket(0);
273                        // Wait for a timeout or until an explicit data XBee packet is read.
274                        Long deadLine = System.currentTimeMillis() + timeout;
275                        while (xbeePacket == null && deadLine > System.currentTimeMillis()) {
276                                sleep(100);
277                                xbeePacket = getFirstExplicitDataPacket(0);
278                        }
279                        return xbeePacket;
280                } else {
281                        synchronized (lock) {
282                                for (int i = 0; i < packetsList.size(); i++) {
283                                        XBeePacket xbeePacket = packetsList.get(i);
284                                        if (isExplicitDataPacket(xbeePacket))
285                                                return packetsList.remove(i);
286                                }
287                        }
288                }
289                return null;
290        }
291        
292        /**
293         * Returns the first explicit data packet from the queue whose 64-bit 
294         * source address matches the address of the provided remote XBee device.
295         * 
296         * <p>The methods waits up to the specified timeout if necessary for an 
297         * XBee explicit data packet to become available. {@code null} if the 
298         * queue is empty or there is not any XBee explicit data packet sent by 
299         * the provided remote XBee device.</p>
300         * 
301         * @param remoteXBeeDevice The XBee device containing the 64-bit address 
302         *                         to look for in the list of packets.
303         * @param timeout The time in milliseconds to wait for an XBee explicit 
304         *                data packet from the specified remote XBee device to 
305         *                become available. 0 to return immediately.
306         * 
307         * @return The first XBee explicit data packet whose its 64-bit address 
308         *         matches the address of the provided remote XBee device. 
309         *         {@code null} if no explicit data packets from the specified 
310         *         XBee device are found in the queue.
311         * 
312         * @see com.digi.xbee.api.RemoteXBeeDevice
313         * @see com.digi.xbee.api.packet.XBeePacket
314         * @see com.digi.xbee.api.packet.common.ExplicitRxIndicatorPacket
315         */
316        public XBeePacket getFirstExplicitDataPacketFrom(RemoteXBeeDevice remoteXBeeDevice, int timeout) {
317                if (timeout > 0) {
318                        XBeePacket xbeePacket = getFirstExplicitDataPacketFrom(remoteXBeeDevice, 0);
319                        // Wait for a timeout or until an XBee explicit data packet from remoteXBeeDevice is read.
320                        Long deadLine = System.currentTimeMillis() + timeout;
321                        while (xbeePacket == null && deadLine > System.currentTimeMillis()) {
322                                sleep(100);
323                                xbeePacket = getFirstExplicitDataPacketFrom(remoteXBeeDevice, 0);
324                        }
325                        return xbeePacket;
326                } else {
327                        synchronized (lock) {
328                                for (int i = 0; i < packetsList.size(); i++) {
329                                        XBeePacket xbeePacket = packetsList.get(i);
330                                        if (isExplicitDataPacket(xbeePacket) && addressesMatch(xbeePacket, remoteXBeeDevice))
331                                                return packetsList.remove(i);
332                                }
333                        }
334                }
335                return null;
336        }
337        
338        /**
339         * Returns whether or not the source address of the provided XBee packet 
340         * matches the address of the given remote XBee device.
341         * 
342         * @param xbeePacket The XBee packet to compare its address with the 
343         *                   remote XBee device.
344         * @param remoteXBeeDevice The remote XBee device to compare its address 
345         *                         with the XBee packet.
346         * 
347         * @return {@code true} if the source address of the provided packet (if 
348         *         it has) matches the address of the remote XBee device.
349         * 
350         * @see com.digi.xbee.api.RemoteXBeeDevice
351         * @see com.digi.xbee.api.packet.XBeePacket
352         */
353        private boolean addressesMatch(XBeePacket xbeePacket, RemoteXBeeDevice remoteXBeeDevice) {
354                if (!(xbeePacket instanceof XBeeAPIPacket))
355                        return false;
356                APIFrameType packetType = ((XBeeAPIPacket)xbeePacket).getFrameType();
357                switch (packetType) {
358                case RECEIVE_PACKET:
359                        if (remoteXBeeDevice.get64BitAddress() != null && ((ReceivePacket)xbeePacket).get64bitSourceAddress().equals(remoteXBeeDevice.get64BitAddress()))
360                                return true;
361                        if (remoteXBeeDevice.get16BitAddress() != null && ((ReceivePacket)xbeePacket).get16bitSourceAddress().equals(remoteXBeeDevice.get16BitAddress()))
362                                return true;
363                        break;
364                case REMOTE_AT_COMMAND_RESPONSE:
365                        if (remoteXBeeDevice.get64BitAddress() != null && ((RemoteATCommandResponsePacket)xbeePacket).get64bitSourceAddress().equals(remoteXBeeDevice.get64BitAddress()))
366                                return true;
367                        if (remoteXBeeDevice.get16BitAddress() != null && ((RemoteATCommandResponsePacket)xbeePacket).get16bitSourceAddress().equals(remoteXBeeDevice.get16BitAddress()))
368                                return true;
369                        break;
370                case RX_16:
371                        if (((RX16Packet)xbeePacket).get16bitSourceAddress().equals(remoteXBeeDevice.get16BitAddress()))
372                                return true;
373                        break;
374                case RX_64:
375                        if (((RX64Packet)xbeePacket).get64bitSourceAddress().equals(remoteXBeeDevice.get64BitAddress()))
376                                return true;
377                        break;
378                case RX_IO_16:
379                        if (((RX16IOPacket)xbeePacket).get16bitSourceAddress().equals(remoteXBeeDevice.get16BitAddress()))
380                                return true;
381                        break;
382                case RX_IO_64:
383                        if (((RX64IOPacket)xbeePacket).get64bitSourceAddress().equals(remoteXBeeDevice.get64BitAddress()))
384                                return true;
385                        break;
386                case EXPLICIT_RX_INDICATOR:
387                        if (((ExplicitRxIndicatorPacket)xbeePacket).get64BitSourceAddress().equals(remoteXBeeDevice.get64BitAddress()))
388                                return true;
389                        break;
390                default:
391                        return false;
392                }
393                return false;
394        }
395        
396        /**
397         * Returns whether or not the given XBee packet is a data packet.
398         * 
399         * @param xbeePacket The XBee packet to check if is data packet.
400         * 
401         * @return {@code true} if the XBee packet is a data packet, {@code false} 
402         *         otherwise.
403         * 
404         * @see com.digi.xbee.api.packet.XBeePacket
405         */
406        private boolean isDataPacket(XBeePacket xbeePacket) {
407                if (!(xbeePacket instanceof XBeeAPIPacket))
408                        return false;
409                APIFrameType packetType = ((XBeeAPIPacket)xbeePacket).getFrameType();
410                switch (packetType) {
411                        case RECEIVE_PACKET:
412                        case RX_16:
413                        case RX_64:
414                                return true;
415                        default:
416                                return false;
417                }
418        }
419        
420        /**
421         * Returns whether or not the given XBee packet is an explicit data packet.
422         * 
423         * @param xbeePacket The XBee packet to check if is an explicit data packet.
424         * 
425         * @return {@code true} if the XBee packet is an explicit data packet, 
426         *         {@code false} otherwise.
427         * 
428         * @see com.digi.xbee.api.packet.XBeePacket
429         * @see com.digi.xbee.api.packet.common.ExplicitRxIndicatorPacket
430         */
431        private boolean isExplicitDataPacket(XBeePacket xbeePacket) {
432                if (!(xbeePacket instanceof XBeeAPIPacket))
433                        return false;
434                APIFrameType packetType = ((XBeeAPIPacket)xbeePacket).getFrameType();
435                return packetType == APIFrameType.EXPLICIT_RX_INDICATOR;
436        }
437        
438        /**
439         * Sleeps the thread for the given number of milliseconds.
440         * 
441         * @param milliseconds The number of milliseconds that the thread should 
442         *        be sleeping.
443         */
444        private void sleep(int milliseconds) {
445                try {
446                        Thread.sleep(milliseconds);
447                } catch (InterruptedException e) { }
448        }
449        
450        /**
451         * Returns the maximum size of the XBee packets queue.
452         * 
453         * @return The maximum size of the XBee packets queue.
454         */
455        public int getMaxSize() {
456                return maxLength;
457        }
458        
459        /**
460         * Returns the current size of the XBee packets queue.
461         * 
462         * @return The current size of the XBee packets queue.
463         */
464        public int getCurrentSize() {
465                synchronized (lock) {
466                        return packetsList.size();
467                }
468        }
469}