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.HexUtils;
027
028/**
029 * This class represents an Explicit Addressing Command packet. Packet is 
030 * built using the parameters of the constructor or providing a valid API 
031 * payload.
032 * 
033 * <p>Allows application layer fields (endpoint and cluster ID) to be 
034 * specified for a data transmission. Similar to the Transmit Request, but 
035 * also requires application layer addressing fields to be specified 
036 * (endpoints, cluster ID, profile ID). An Explicit Addressing Request API 
037 * frame causes the module to send data as an RF packet to the specified 
038 * destination, using the specified source and destination endpoints, cluster 
039 * ID, and profile ID.</p>
040 * 
041 * <p>The 64-bit destination address should be set to 
042 * {@code 0x000000000000FFFF} for a broadcast transmission (to all
043 * devices).</p>
044 * 
045 * <p>The coordinator can be addressed by either setting the 64-bit address 
046 * to all {@code 0x00} and the 16-bit address to {@code 0xFFFE}, OR by 
047 * setting the 64-bit address to the coordinator's 64-bit address and the 
048 * 16-bit address to {@code 0x0000}.</p>
049 * 
050 * <p>For all other transmissions, setting the 16-bit address to the correct 
051 * 16-bit address can help improve performance when transmitting to
052 * multiple destinations.</p>
053 * 
054 * <p>If a 16-bit address is not known, this field should be set to 
055 * {@code 0xFFFE} (unknown).</p>.
056 * 
057 * <p>The Transmit Status frame 
058 * ({@link com.digi.xbee.api.packet.APIFrameType#TRANSMIT_REQUEST}) will 
059 * indicate the discovered 16-bit address, if successful (see 
060 * {@link com.digi.xbee.api.packet.common.TransmitStatusPacket}).</p>
061 * 
062 * <p>The broadcast radius can be set from {@code 0} up to {@code NH}. If set 
063 * to {@code 0}, the value of {@code NH} specifies the broadcast radius
064 * (recommended). This parameter is only used for broadcast transmissions.</p>
065 * 
066 * <p>The maximum number of payload bytes can be read with the {@code NP} 
067 * command. Note: if source routing is used, the RF payload will be reduced 
068 * by two bytes per intermediate hop in the source route.</p>
069 * 
070 * <p>Several transmit options can be set using the transmit options bitfield.
071 * </p>
072 * 
073 * @see com.digi.xbee.api.models.XBeeTransmitOptions
074 * @see com.digi.xbee.api.models.XBee16BitAddress#COORDINATOR_ADDRESS
075 * @see com.digi.xbee.api.models.XBee16BitAddress#UNKNOWN_ADDRESS
076 * @see com.digi.xbee.api.models.XBee64BitAddress#BROADCAST_ADDRESS
077 * @see com.digi.xbee.api.models.XBee64BitAddress#COORDINATOR_ADDRESS
078 * @see com.digi.xbee.api.packet.common.ExplicitRxIndicatorPacket
079 * @see com.digi.xbee.api.packet.XBeeAPIPacket
080 */
081public class ExplicitAddressingPacket extends XBeeAPIPacket {
082
083        // Constants
084        private static final int MIN_API_PAYLOAD_LENGTH = 20; // 1 (Frame type) + 1 (frame ID) + 8 (64-bit address) + 2 (16-bit address) + 1 (source endpoint) + 1 (destination endpoint) + 2 (cluster ID) + 2 (profile ID) + 1 (broadcast radius) + 1 (options)
085        
086        // Variables
087        private final XBee64BitAddress destAddress64;
088        
089        private final XBee16BitAddress destAddress16;
090        
091        private final int broadcastRadius;
092        private final int transmitOptions;
093        
094        private final int sourceEndpoint;
095        private final int destEndpoint;
096        private final int clusterID;
097        private final int profileID;
098        private byte[] rfData;
099        
100        private Logger logger;
101        
102        /**
103         * Creates a new {@code ExplicitAddressingPacket} object from the given 
104         * payload.
105         * 
106         * @param payload The API frame payload. It must start with the frame type 
107         *                corresponding to an Explicit Addressing packet 
108         *                ({@code 0x11}).
109         *                The byte array must be in {@code OperatingMode.API} mode.
110         * 
111         * @return Parsed Explicit Addressing packet.
112         * 
113         * @throws IllegalArgumentException if {@code payload[0] != APIFrameType.EXPLICIT_ADDRESSING_COMMAND_FRAME.getValue()} or
114         *                                  if {@code payload.length < }{@value #MIN_API_PAYLOAD_LENGTH} or
115         *                                  if {@code frameID < 0} or
116         *                                  if {@code frameID > 255} or 
117         *                                  if {@code sourceEndpoint < 0} or 
118         *                                  if {@code sourceEndpoint > 255} or 
119         *                                  if {@code destEndpoint < 0} or 
120         *                                  if {@code destEndpoint > 255} or 
121         *                                  if {@code clusterID < 0} or 
122         *                                  if {@code clusterID > 65535} or 
123         *                                  if {@code profileID < 0} or 
124         *                                  if {@code profileID > 65535} or 
125         *                                  if {@code broadcastRadius < 0} or
126         *                                  if {@code broadcastRadius > 255} or
127         *                                  if {@code transmitOptions < 0} or
128         *                                  if {@code transmitOptions > 255}.
129         * @throws NullPointerException if {@code payload == null}.
130         */
131        public static ExplicitAddressingPacket createPacket(byte[] payload) {
132                if (payload == null)
133                        throw new NullPointerException("Explicit Addressing packet payload cannot be null.");
134                
135                // 1 (Frame type) + 1 (frame ID) + 8 (64-bit address) + 2 (16-bit address) + 1 (source endpoint) + 1 (destination endpoint) + 2 (cluster ID) + 2 (profile ID) + 1 (broadcast radius) + 1 (options)
136                if (payload.length < MIN_API_PAYLOAD_LENGTH)
137                        throw new IllegalArgumentException("Incomplete Explicit Addressing packet.");
138                
139                if ((payload[0] & 0xFF) != APIFrameType.EXPLICIT_ADDRESSING_COMMAND_FRAME.getValue())
140                        throw new IllegalArgumentException("Payload is not an Explicit Addressing packet.");
141                
142                // payload[0] is the frame type.
143                int index = 1;
144                
145                // Frame ID byte.
146                int frameID = payload[index] & 0xFF;
147                index = index + 1;
148                
149                // 8 bytes of 64-bit address.
150                XBee64BitAddress destAddress64 = new XBee64BitAddress(Arrays.copyOfRange(payload, index, index + 8));
151                index = index + 8;
152                
153                // 2 bytes of 16-bit address.
154                XBee16BitAddress destAddress16 = new XBee16BitAddress(payload[index] & 0xFF, payload[index + 1] & 0xFF);
155                index = index + 2;
156                
157                // Source endpoint byte.
158                int sourceEndpoint = payload[index] & 0xFF;
159                index = index + 1;
160                
161                // Destination endpoint byte.
162                int destEndpoint = payload[index] & 0xFF;
163                index = index + 1;
164                
165                // 2 bytes of cluster ID.
166                int clusterID = (payload[index] & 0xFF) << 8 | payload[index + 1] & 0xFF;
167                index = index + 2;
168                
169                // 2 bytes of profile ID.
170                int profileID = (payload[index] & 0xFF) << 8 | payload[index + 1] & 0xFF;
171                index = index + 2;
172                
173                // Broadcast radius byte.
174                int broadcastRadius = payload[index] & 0xFF;
175                index = index + 1;
176                
177                // Options byte.
178                int options = payload[index] & 0xFF;
179                index = index + 1;
180                
181                // Get RF data.
182                byte[] rfData = null;
183                if (index < payload.length)
184                        rfData = Arrays.copyOfRange(payload, index, payload.length);
185                
186                return new ExplicitAddressingPacket(frameID, destAddress64, destAddress16, sourceEndpoint, destEndpoint, clusterID, profileID, broadcastRadius, options, rfData);
187        }
188        
189        /**
190         * Class constructor. Instantiates a new {@code ExplicitAddressingPacket} 
191         * object with the given parameters.
192         * 
193         * @param frameID Frame ID.
194         * @param destAddress64 64-bit address of the destination device.
195         * @param destAddress16 16-bit address of the destination device.
196         * @param sourceEndpoint Source endpoint for the transaction.
197         * @param destEndpoint Destination endpoint for the transaction.
198         * @param clusterID Cluster ID used in the transaction.
199         * @param profileID Profile ID used in the transaction.
200         * @param broadcastRadius Maximum number of hops a broadcast transmission 
201         *                        can traverse. Set to 0 to use the network 
202         *                        maximum hops value.
203         * @param transmitOptions Bitfield of supported transmission options.
204         * @param rfData RF Data that is sent to the destination device.
205         * 
206         * @throws IllegalArgumentException if {@code frameID < 0} or
207         *                                  if {@code frameID > 255} or 
208         *                                  if {@code sourceEndpoint < 0} or 
209         *                                  if {@code sourceEndpoint > 255} or 
210         *                                  if {@code destEndpoint < 0} or 
211         *                                  if {@code destEndpoint > 255} or 
212         *                                  if {@code clusterID < 0} or 
213         *                                  if {@code clusterID > 65535} or 
214         *                                  if {@code profileID < 0} or 
215         *                                  if {@code profileID > 65535} or 
216         *                                  if {@code broadcastRadius < 0} or
217         *                                  if {@code broadcastRadius > 255} or
218         *                                  if {@code transmitOptions < 0} or
219         *                                  if {@code transmitOptions > 255}.
220         * @throws NullPointerException if {@code destAddress64 == null} or 
221         *                              if {@code destAddress16 == null}.
222         * 
223         * @see com.digi.xbee.api.models.XBeeTransmitOptions
224         * @see com.digi.xbee.api.models.XBee16BitAddress
225         * @see com.digi.xbee.api.models.XBee64BitAddress
226         */
227        public ExplicitAddressingPacket(int frameID, XBee64BitAddress destAddress64, XBee16BitAddress destAddress16,
228                        int sourceEndpoint, int destEndpoint, int clusterID, int profileID, int broadcastRadius,
229                        int transmitOptions, byte[] rfData) {
230                super(APIFrameType.EXPLICIT_ADDRESSING_COMMAND_FRAME);
231                
232                if (destAddress64 == null)
233                        throw new NullPointerException("64-bit destination address cannot be null.");
234                if (destAddress16 == null)
235                        throw new NullPointerException("16-bit destination address cannot be null.");
236                if (frameID < 0 || frameID > 255)
237                        throw new IllegalArgumentException("Frame ID must be between 0 and 255.");
238                if (sourceEndpoint < 0 || sourceEndpoint > 255)
239                        throw new IllegalArgumentException("Source endpoint must be between 0 and 255.");
240                if (destEndpoint < 0 || destEndpoint > 255)
241                        throw new IllegalArgumentException("Destination endpoint must be between 0 and 255.");
242                if (clusterID < 0 || clusterID > 65535)
243                        throw new IllegalArgumentException("Cluster ID must be between 0 and 65535.");
244                if (profileID < 0 || profileID > 65535)
245                        throw new IllegalArgumentException("Profile ID must be between 0 and 65535.");
246                if (broadcastRadius < 0 || broadcastRadius > 255)
247                        throw new IllegalArgumentException("Broadcast radius must be between 0 and 255.");
248                if (transmitOptions < 0 || transmitOptions > 255)
249                        throw new IllegalArgumentException("Transmit options must be between 0 and 255.");
250                
251                this.frameID = frameID;
252                this.destAddress64 = destAddress64;
253                this.destAddress16 = destAddress16;
254                this.sourceEndpoint = sourceEndpoint;
255                this.destEndpoint = destEndpoint;
256                this.clusterID = clusterID;
257                this.profileID = profileID;
258                this.broadcastRadius = broadcastRadius;
259                this.transmitOptions = transmitOptions;
260                this.rfData = rfData;
261                this.logger = LoggerFactory.getLogger(ExplicitAddressingPacket.class);
262        }
263        
264        /*
265         * (non-Javadoc)
266         * @see com.digi.xbee.api.packet.XBeeAPIPacket#getAPIPacketSpecificData()
267         */
268        @Override
269        public byte[] getAPIPacketSpecificData() {
270                ByteArrayOutputStream data = new ByteArrayOutputStream();
271                try {
272                        data.write(destAddress64.getValue());
273                        data.write(destAddress16.getValue());
274                        data.write(sourceEndpoint);
275                        data.write(destEndpoint);
276                        data.write(clusterID >> 8);
277                        data.write(clusterID);
278                        data.write(profileID >> 8);
279                        data.write(profileID);
280                        data.write(broadcastRadius);
281                        data.write(transmitOptions);
282                        if (rfData != null)
283                                data.write(rfData);
284                } catch (IOException e) {
285                        logger.error(e.getMessage(), e);
286                }
287                return data.toByteArray();
288        }
289        
290        /*
291         * (non-Javadoc)
292         * @see com.digi.xbee.api.packet.XBeeAPIPacket#needsAPIFrameID()
293         */
294        @Override
295        public boolean needsAPIFrameID() {
296                return true;
297        }
298        
299        /*
300         * (non-Javadoc)
301         * @see com.digi.xbee.api.packet.XBeeAPIPacket#isBroadcast()
302         */
303        @Override
304        public boolean isBroadcast() {
305                if (get64BitDestinationAddress().equals(XBee64BitAddress.BROADCAST_ADDRESS) 
306                                || get16BitDestinationAddress().equals(XBee16BitAddress.BROADCAST_ADDRESS))
307                        return true;
308                return false;
309        }
310        
311        /**
312         * Returns the 64-bit destination address.
313         * 
314         * @return The 64-bit destination address.
315         * 
316         * @see com.digi.xbee.api.models.XBee64BitAddress
317         */
318        public XBee64BitAddress get64BitDestinationAddress() {
319                return destAddress64;
320        }
321        
322        /**
323         * Returns the 16-bit destination address.
324         * 
325         * @return The 16-bit destination address.
326         * 
327         * @see com.digi.xbee.api.models.XBee16BitAddress
328         */
329        public XBee16BitAddress get16BitDestinationAddress() {
330                return destAddress16;
331        }
332        
333        /**
334         * Returns the source endpoint of the transmission.
335         * 
336         * @return The source endpoint of the transmission.
337         */
338        public int getSourceEndpoint() {
339                return sourceEndpoint;
340        }
341        
342        /**
343         * Returns the destination endpoint of the transmission.
344         * 
345         * @return The destination endpoint of the transmission.
346         */
347        public int getDestinationEndpoint() {
348                return destEndpoint;
349        }
350        
351        /**
352         * Returns the cluster ID used in the transmission.
353         * 
354         * @return The cluster ID used in the transmission.
355         */
356        public int getClusterID() {
357                return clusterID;
358        }
359        
360        /**
361         * Returns the profile ID used in the transmission.
362         * 
363         * @return The profile ID used in the transmission.
364         */
365        public int getProfileID() {
366                return profileID;
367        }
368        
369        /**
370         * Returns the broadcast radius.
371         * 
372         * @return The broadcast radius.
373         */
374        public int getBroadcastRadius() {
375                return broadcastRadius;
376        }
377        
378        /**
379         * Returns the transmit options bitfield.
380         * 
381         * @return The transmit options bitfield.
382         * 
383         * @see com.digi.xbee.api.models.XBeeTransmitOptions
384         */
385        public int getTransmitOptions() {
386                return transmitOptions;
387        }
388        
389        /**
390         * Sets the RF Data to send.
391         * 
392         * @param rfData RF Data to send.
393         */
394        public void setRFData(byte[] rfData) {
395                this.rfData = rfData;
396        }
397        
398        /**
399         * Returns the RF Data to send.
400         * 
401         * @return RF Data to send.
402         */
403        public byte[] getRFData() {
404                return rfData;
405        }
406        
407        /*
408         * (non-Javadoc)
409         * @see com.digi.xbee.api.packet.XBeeAPIPacket#getAPIPacketParameters()
410         */
411        @Override
412        public LinkedHashMap<String, String> getAPIPacketParameters() {
413                LinkedHashMap<String, String> parameters = new LinkedHashMap<String, String>();
414                parameters.put("Frame ID", HexUtils.prettyHexString(HexUtils.integerToHexString(frameID, 1)) + " (" + frameID + ")");
415                parameters.put("64-bit dest. address", HexUtils.prettyHexString(destAddress64.toString()));
416                parameters.put("16-bit dest. address", HexUtils.prettyHexString(destAddress16.toString()));
417                parameters.put("Source endpoint", HexUtils.prettyHexString(HexUtils.integerToHexString(sourceEndpoint, 1)));
418                parameters.put("Dest. endpoint", HexUtils.prettyHexString(HexUtils.integerToHexString(destEndpoint, 1)));
419                parameters.put("Cluster ID", HexUtils.prettyHexString(HexUtils.integerToHexString(clusterID, 2)));
420                parameters.put("Profile ID", HexUtils.prettyHexString(HexUtils.integerToHexString(profileID, 2)));
421                parameters.put("Broadcast radius", HexUtils.prettyHexString(HexUtils.integerToHexString(broadcastRadius, 1)) + " (" + broadcastRadius + ")");
422                parameters.put("Transmit options", HexUtils.prettyHexString(HexUtils.integerToHexString(transmitOptions, 1)));
423                if (rfData != null)
424                        parameters.put("RF data", HexUtils.prettyHexString(HexUtils.byteArrayToHexString(rfData)));
425                return parameters;
426        }
427}