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.packet.common;
013
014import java.io.ByteArrayOutputStream;
015import java.io.IOException;
016import java.util.Arrays;
017import java.util.LinkedHashMap;
018
019import org.slf4j.Logger;
020import org.slf4j.LoggerFactory;
021
022import com.digi.xbee.api.models.XBee16BitAddress;
023import com.digi.xbee.api.models.XBee64BitAddress;
024import com.digi.xbee.api.packet.APIFrameType;
025import com.digi.xbee.api.packet.XBeeAPIPacket;
026import com.digi.xbee.api.utils.ByteUtils;
027import com.digi.xbee.api.utils.HexUtils;
028
029/**
030 * This class represents an Explicit RX Indicator packet. Packet is 
031 * built using the parameters of the constructor or providing a valid API 
032 * payload.
033 * 
034 * <p>When the modem receives an RF packet it is sent out the UART using this 
035 * message type (when AO=1).</p>
036 * 
037 * <p>This packet is received when external devices send explicit addressing 
038 * packets to this module.</p>
039 * 
040 * <p>Among received data, some options can also be received indicating 
041 * transmission parameters.</p> 
042 * 
043 * @see com.digi.xbee.api.models.XBeeReceiveOptions
044 * @see com.digi.xbee.api.packet.common.ExplicitAddressingPacket
045 * @see com.digi.xbee.api.packet.XBeeAPIPacket
046 */
047public class ExplicitRxIndicatorPacket extends XBeeAPIPacket {
048
049        // Constants
050        private static final int MIN_API_PAYLOAD_LENGTH = 18; // 1 (Frame type)  + 8 (64-bit address) + 2 (16-bit address) + 1 (source endpoint) + 1 (destination endpoint) + 2 (cluster ID) + 2 (profile ID) + 1 (receive options)
051        
052        public static final int DATA_ENDPOINT = 0xE8;
053        public static final int DATA_CLUSTER = 0x0011;
054        public static final int DIGI_PROFILE = 0xC105;
055        
056        // Variables
057        private final XBee64BitAddress sourceAddress64;
058        
059        private final XBee16BitAddress sourceAddress16;
060        
061        private final int sourceEndpoint;
062        private final int destEndpoint;
063        private final int clusterID;
064        private final int profileID;
065        private final int receiveOptions;
066        
067        private byte[] rfData;
068        
069        private Logger logger;
070        
071        /**
072         * Creates a new {@code ExplicitRxIndicatorPacket} object from the given 
073         * payload.
074         * 
075         * @param payload The API frame payload. It must start with the frame type 
076         *                corresponding to an Explicit RX Indicator packet 
077         *                ({@code 0x91}).
078         *                The byte array must be in {@code OperatingMode.API} mode.
079         * 
080         * @return Parsed Explicit RX Indicator packet.
081         * 
082         * @throws IllegalArgumentException if {@code payload[0] != APIFrameType.EXPLICIT_RX_INDICATOR.getValue()} or
083         *                                  if {@code payload.length < }{@value #MIN_API_PAYLOAD_LENGTH} or
084         *                                  if {@code sourceEndpoint < 0} or 
085         *                                  if {@code sourceEndpoint > 255} or 
086         *                                  if {@code destEndpoint < 0} or 
087         *                                  if {@code destEndpoint > 255} or 
088         *                                  if {@code clusterID < 0} or 
089         *                                  if {@code clusterID > 65535} or
090         *                                  if {@code profileID < 0} or 
091         *                                  if {@code profileID > 65535} or
092         *                                  if {@code transmitOptions < 0} or
093         *                                  if {@code transmitOptions > 255}.
094         * @throws NullPointerException if {@code payload == null}.
095         */
096        public static ExplicitRxIndicatorPacket createPacket(byte[] payload) {
097                if (payload == null)
098                        throw new NullPointerException("Explicit Rx Indicator packet payload cannot be null.");
099                
100                // 1 (Frame type) + 8 (64-bit address) + 2 (16-bit address) + 1 (source endpoint) + 1 (destination endpoint) + 2 (cluster ID) + 2 (profile ID) + 1 (receive options)
101                if (payload.length < MIN_API_PAYLOAD_LENGTH)
102                        throw new IllegalArgumentException("Incomplete Explicit Rx Indicator packet.");
103                
104                if ((payload[0] & 0xFF) != APIFrameType.EXPLICIT_RX_INDICATOR.getValue())
105                        throw new IllegalArgumentException("Payload is not an Explicit Rx Indicator packet.");
106                
107                // payload[0] is the frame type.
108                int index = 1;
109                
110                // 8 bytes of 64-bit address.
111                XBee64BitAddress destAddress64 = new XBee64BitAddress(Arrays.copyOfRange(payload, index, index + 8));
112                index = index + 8;
113                
114                // 2 bytes of 16-bit address.
115                XBee16BitAddress destAddress16 = new XBee16BitAddress(payload[index] & 0xFF, payload[index + 1] & 0xFF);
116                index = index + 2;
117                
118                // Source endpoint byte.
119                int sourceEndpoint = payload[index] & 0xFF;
120                index = index + 1;
121                
122                // Destination endpoint byte.
123                int destEndpoint = payload[index] & 0xFF;
124                index = index + 1;
125                
126                // 2 bytes of cluster ID.
127                int clusterID = (payload[index] & 0xFF) << 8 | payload[index + 1] & 0xFF;
128                index = index + 2;
129                
130                // 2 bytes of profile ID.
131                int profileID = (payload[index] & 0xFF) << 8 | payload[index + 1] & 0xFF;
132                index = index + 2;
133                
134                // Receive options byte.
135                int receiveOptions = payload[index] & 0xFF;
136                index = index + 1;
137                
138                // Get RF data.
139                byte[] rfData = null;
140                if (index < payload.length)
141                        rfData = Arrays.copyOfRange(payload, index, payload.length);
142                
143                return new ExplicitRxIndicatorPacket(destAddress64, destAddress16, sourceEndpoint, destEndpoint, clusterID, profileID, receiveOptions, rfData);
144        }
145        
146        /**
147         * Class constructor. Instantiates a new {@code ExplicitRxIndicatorPacket} 
148         * object with the given parameters.
149         * 
150         * @param sourceAddress64 64-bit address of the sender device.
151         * @param sourceAddress16 16-bit address of the sender device.
152         * @param sourceEndpoint Endpoint of the source that initiated the 
153         *                       transmission.
154         * @param destEndpoint Endpoint of the destination the message was 
155         *                     addressed to.
156         * @param clusterID Cluster ID the packet was addressed to.
157         * @param profileID Profile ID the packet was addressed to.
158         * @param receiveOptions BitField of receive options.
159         * @param rfData Received RF data.
160         * 
161         * @throws IllegalArgumentException if {@code sourceEndpoint < 0} or 
162         *                                  if {@code sourceEndpoint > 255} or 
163         *                                  if {@code destEndpoint < 0} or 
164         *                                  if {@code destEndpoint > 255} or 
165         *                                  if {@code clusterID < 0} or 
166         *                                  if {@code clusterID > 65535} or
167         *                                  if {@code profileID < 0} or 
168         *                                  if {@code profileID > 65535} or
169         *                                  if {@code receiveOptions < 0} or
170         *                                  if {@code receiveOptions > 255}.
171         * @throws NullPointerException if {@code sourceAddress64 == null} or 
172         *                              if {@code sourceAddress16 == null}.
173         * 
174         * @see com.digi.xbee.api.models.XBeeReceiveOptions
175         * @see com.digi.xbee.api.models.XBee16BitAddress
176         * @see com.digi.xbee.api.models.XBee64BitAddress
177         */
178        public ExplicitRxIndicatorPacket(XBee64BitAddress sourceAddress64, XBee16BitAddress sourceAddress16, 
179                        int sourceEndpoint, int destEndpoint, int clusterID, int profileID,
180                        int receiveOptions, byte[] rfData){
181                super(APIFrameType.EXPLICIT_RX_INDICATOR);
182                
183                if (sourceAddress64 == null)
184                        throw new NullPointerException("64-bit source address cannot be null.");
185                if (sourceAddress16 == null)
186                        throw new NullPointerException("16-bit source address cannot be null.");
187                if (sourceEndpoint < 0 || sourceEndpoint > 255)
188                        throw new IllegalArgumentException("Source endpoint must be between 0 and 255.");
189                if (destEndpoint < 0 || destEndpoint > 255)
190                        throw new IllegalArgumentException("Destination endpoint must be between 0 and 255.");
191                if (clusterID < 0 || clusterID > 65535)
192                        throw new IllegalArgumentException("Cluster ID must be between 0 and 65535.");
193                if (profileID < 0 || profileID > 65535)
194                        throw new IllegalArgumentException("Profile ID must be between 0 and 65535.");
195                if (receiveOptions < 0 || receiveOptions > 255)
196                        throw new IllegalArgumentException("Receive options must be between 0 and 255.");
197                
198                this.sourceAddress64 = sourceAddress64;
199                this.sourceAddress16 = sourceAddress16;
200                this.sourceEndpoint = sourceEndpoint;
201                this.destEndpoint = destEndpoint;
202                this.clusterID = clusterID;
203                this.profileID = profileID;
204                this.receiveOptions = receiveOptions;
205                this.rfData = rfData;
206                this.logger = LoggerFactory.getLogger(ExplicitRxIndicatorPacket.class);
207        }
208        
209        /*
210         * (non-Javadoc)
211         * @see com.digi.xbee.api.packet.XBeeAPIPacket#getAPIPacketSpecificData()
212         */
213        @Override
214        public byte[] getAPIPacketSpecificData() {
215                ByteArrayOutputStream data = new ByteArrayOutputStream();
216                try {
217                        data.write(sourceAddress64.getValue());
218                        data.write(sourceAddress16.getValue());
219                        data.write(sourceEndpoint);
220                        data.write(destEndpoint);
221                        data.write(clusterID >> 8);
222                        data.write(clusterID);
223                        data.write(profileID >> 8);
224                        data.write(profileID);
225                        data.write(receiveOptions);
226                        if (rfData != null)
227                                data.write(rfData);
228                } catch (IOException e) {
229                        logger.error(e.getMessage(), e);
230                }
231                return data.toByteArray();
232        }
233        
234        /*
235         * (non-Javadoc)
236         * @see com.digi.xbee.api.packet.XBeeAPIPacket#needsAPIFrameID()
237         */
238        @Override
239        public boolean needsAPIFrameID() {
240                return false;
241        }
242        
243        /*
244         * (non-Javadoc)
245         * @see com.digi.xbee.api.packet.XBeeAPIPacket#isBroadcast()
246         */
247        @Override
248        public boolean isBroadcast() {
249                if (ByteUtils.isBitEnabled(getReceiveOptions(), 1))
250                        return true;
251                return false;
252        }
253        
254        /**
255         * Returns the 64 bit sender/source address.
256         * 
257         * @return The 64 bit sender/source address.
258         * 
259         * @see com.digi.xbee.api.models.XBee64BitAddress
260         */
261        public XBee64BitAddress get64BitSourceAddress() {
262                return sourceAddress64;
263        }
264        
265        /**
266         * Returns the 16 bit sender/source address.
267         * 
268         * @return The 16 bit sender/source address.
269         * 
270         * @see com.digi.xbee.api.models.XBee16BitAddress
271         */
272        public XBee16BitAddress get16BitSourceAddress() {
273                return sourceAddress16;
274        }
275        
276        /**
277         * Returns the source endpoint of the transmission.
278         * 
279         * @return The source endpoint of the transmission.
280         */
281        public int getSourceEndpoint() {
282                return sourceEndpoint;
283        }
284        
285        /**
286         * Returns the destination endpoint of the transmission.
287         * 
288         * @return The destination endpoint of the transmission.
289         */
290        public int getDestinationEndpoint() {
291                return destEndpoint;
292        }
293        
294        /**
295         * Returns the cluster ID used in the transmission.
296         * 
297         * @return The cluster ID used in the transmission.
298         */
299        public int getClusterID() {
300                return clusterID;
301        }
302        
303        /**
304         * Returns the profile ID used in the transmission.
305         * 
306         * @return The profile ID used in the transmission.
307         */
308        public int getProfileID() {
309                return profileID;
310        }
311        
312        /**
313         * Returns the receive options bitfield.
314         * 
315         * @return The receive options bitfield.
316         * 
317         * @see com.digi.xbee.api.models.XBeeReceiveOptions
318         */
319        public int getReceiveOptions() {
320                return receiveOptions;
321        }
322        
323        /**
324         * Sets the received RF data.
325         * 
326         * @param rfData Received RF data.
327         */
328        public void setRFData(byte[] rfData) {
329                this.rfData = rfData;
330        }
331        
332        /**
333         * Returns the received RF data.
334         * 
335         * @return Received RF data.
336         */
337        public byte[] getRFData() {
338                return rfData;
339        }
340        
341        /*
342         * (non-Javadoc)
343         * @see com.digi.xbee.api.packet.XBeeAPIPacket#getAPIPacketParameters()
344         */
345        @Override
346        public LinkedHashMap<String, String> getAPIPacketParameters() {
347                LinkedHashMap<String, String> parameters = new LinkedHashMap<String, String>();
348                parameters.put("64-bit source address", HexUtils.prettyHexString(sourceAddress64.toString()));
349                parameters.put("16-bit source address", HexUtils.prettyHexString(sourceAddress16.toString()));
350                parameters.put("Source endpoint", HexUtils.prettyHexString(HexUtils.integerToHexString(sourceEndpoint, 1)));
351                parameters.put("Dest. endpoint", HexUtils.prettyHexString(HexUtils.integerToHexString(destEndpoint, 1)));
352                parameters.put("Cluster ID", HexUtils.prettyHexString(HexUtils.integerToHexString(clusterID, 2)));
353                parameters.put("Profile ID", HexUtils.prettyHexString(HexUtils.integerToHexString(profileID, 2)));
354                parameters.put("Receive options", HexUtils.prettyHexString(HexUtils.integerToHexString(receiveOptions, 1)));
355                if (rfData != null)
356                        parameters.put("RF data", HexUtils.prettyHexString(HexUtils.byteArrayToHexString(rfData)));
357                return parameters;
358        }
359}