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.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.XBeeAPIPacket;
025import com.digi.xbee.api.packet.APIFrameType;
026import com.digi.xbee.api.utils.HexUtils;
027
028/**
029 * This class represents a Transmit Packet. Packet is built using the parameters 
030 * of the constructor or providing a valid API payload.
031 * 
032 * <p>A Transmit Request API frame causes the module to send data as an RF 
033 * packet to the specified destination.</p>
034 * 
035 * <p>The 64-bit destination address should be set to {@code 0x000000000000FFFF} 
036 * for a broadcast transmission (to all devices).</p>
037 * 
038 * <p>The coordinator can be addressed by either setting the 64-bit address to 
039 * all {@code 0x00} and the 16-bit address to {@code 0xFFFE}, OR by setting the 
040 * 64-bit address to the coordinator's 64-bit address and the 16-bit address to 
041 * {@code 0x0000}.</p>
042 * 
043 * <p>For all other transmissions, setting the 16-bit address to the correct 
044 * 16-bit address can help improve performance when transmitting to multiple 
045 * destinations.</p>
046 * 
047 * <p>If a 16-bit address is not known, this field should be set to 
048 * {@code 0xFFFE} (unknown).</p> 
049 * 
050 * <p>The Transmit Status frame 
051 * ({@link com.digi.xbee.api.packet.APIFrameType#TRANSMIT_REQUEST}) will 
052 * indicate the discovered 16-bit address, if successful (see 
053 * {@link com.digi.xbee.api.packet.common.TransmitStatusPacket}).</p>
054 * 
055 * <p>The broadcast radius can be set from {@code 0} up to {@code NH}. If set 
056 * to {@code 0}, the value of {@code NH} specifies the broadcast radius
057 * (recommended). This parameter is only used for broadcast transmissions.</p>
058 * 
059 * <p>The maximum number of payload bytes can be read with the {@code NP} 
060 * command.</p>
061 * 
062 * <p>Several transmit options can be set using the transmit options bitfield.
063 * </p>
064 * 
065 * @see com.digi.xbee.api.models.XBeeTransmitOptions
066 * @see com.digi.xbee.api.models.XBee16BitAddress#COORDINATOR_ADDRESS
067 * @see com.digi.xbee.api.models.XBee16BitAddress#UNKNOWN_ADDRESS
068 * @see com.digi.xbee.api.models.XBee64BitAddress#BROADCAST_ADDRESS
069 * @see com.digi.xbee.api.models.XBee64BitAddress#COORDINATOR_ADDRESS
070 * @see com.digi.xbee.api.packet.XBeeAPIPacket
071 */
072public class TransmitPacket extends XBeeAPIPacket {
073
074        // Constants.
075        private static final int MIN_API_PAYLOAD_LENGTH = 14; // 1 (Frame type) + 1 (frame ID) + 8 (64-bit address) + 2 (16-bit address) + 1 (broadcast radious) + 1 (options)
076        
077        // Variables.
078        private final XBee64BitAddress destAddress64;
079        
080        private final XBee16BitAddress destAddress16;
081        
082        private final int broadcastRadius;
083        private final int transmitOptions;
084        
085        private byte[] rfData;
086        
087        private Logger logger;
088        
089        /**
090         * Creates a new {@code TransmitPacket} object from the given payload.
091         * 
092         * @param payload The API frame payload. It must start with the frame type 
093         *                corresponding to a Transmit packet ({@code 0x10}).
094         *                The byte array must be in {@code OperatingMode.API} mode.
095         * 
096         * @return Parsed Transmit Request packet.
097         * 
098         * @throws IllegalArgumentException if {@code payload[0] != APIFrameType.TRANSMIT_REQUEST.getValue()} or
099         *                                  if {@code payload.length < }{@value #MIN_API_PAYLOAD_LENGTH} or
100         *                                  if {@code frameID < 0} or
101         *                                  if {@code frameID > 255} or
102         *                                  if {@code broadcastRadius < 0} or
103         *                                  if {@code broadcastRadius > 255} or
104         *                                  if {@code transmitOptions < 0} or
105         *                                  if {@code transmitOptions > 255}.
106         * @throws NullPointerException if {@code payload == null}.
107         */
108        public static TransmitPacket createPacket(byte[] payload) {
109                if (payload == null)
110                        throw new NullPointerException("Transmit packet payload cannot be null.");
111                
112                // 1 (Frame type) + 1 (frame ID) + 8 (64-bit address) + 2 (16-bit address) + 1 (broadcast radious) + 1 (options)
113                if (payload.length < MIN_API_PAYLOAD_LENGTH)
114                        throw new IllegalArgumentException("Incomplete Transmit packet.");
115                
116                if ((payload[0] & 0xFF) != APIFrameType.TRANSMIT_REQUEST.getValue())
117                        throw new IllegalArgumentException("Payload is not a Transmit packet.");
118                
119                // payload[0] is the frame type.
120                int index = 1;
121                
122                // Frame ID byte.
123                int frameID = payload[index] & 0xFF;
124                index = index + 1;
125                
126                // 8 bytes of 64-bit address.
127                XBee64BitAddress destAddress64 = new XBee64BitAddress(Arrays.copyOfRange(payload, index, index + 8));
128                index = index + 8;
129                
130                // 2 bytes of 16-bit address.
131                XBee16BitAddress destAddress16 = new XBee16BitAddress(payload[index] & 0xFF, payload[index + 1] & 0xFF);
132                index = index + 2;
133                
134                // Broadcast radious byte.
135                int broadcastRadius = payload[index] & 0xFF;
136                index = index + 1;
137                
138                // Options byte.
139                int options = payload[index] & 0xFF;
140                index = index + 1;
141                
142                // Get RF data.
143                byte[] rfData = null;
144                if (index < payload.length)
145                        rfData = Arrays.copyOfRange(payload, index, payload.length);
146                
147                return new TransmitPacket(frameID, destAddress64, destAddress16, broadcastRadius, options, rfData);
148        }
149        
150        /**
151         * Class constructor. Instantiates a new {@code TransmitPacket} object
152         * with the given parameters.
153         * 
154         * @param frameID Frame ID.
155         * @param destAddress64 64-bit address of the destination device.
156         * @param destAddress16 16-bit address of the destination device.
157         * @param broadcastRadius maximum number of hops a broadcast transmission 
158         *                        can occur.
159         * @param transmitOptions Bitfield of supported transmission options.
160         * @param rfData RF Data that is sent to the destination device.
161         * 
162         * @throws IllegalArgumentException if {@code frameID < 0} or
163         *                                  if {@code frameID > 255} or
164         *                                  if {@code broadcastRadius < 0} or
165         *                                  if {@code broadcastRadius > 255} or
166         *                                  if {@code transmitOptions < 0} or
167         *                                  if {@code transmitOptions > 255}.
168         * @throws NullPointerException if {@code destAddress64 == null} or
169         *                              if {@code destAddress16 == null}.
170         * 
171         * @see com.digi.xbee.api.models.XBeeTransmitOptions
172         * @see com.digi.xbee.api.models.XBee16BitAddress
173         * @see com.digi.xbee.api.models.XBee64BitAddress
174         */
175        public TransmitPacket(int frameID, XBee64BitAddress destAddress64, XBee16BitAddress destAddress16, 
176                        int broadcastRadius, int transmitOptions, byte[] rfData) {
177                super(APIFrameType.TRANSMIT_REQUEST);
178                
179                if (destAddress64 == null)
180                        throw new NullPointerException("64-bit destination address cannot be null.");
181                if (destAddress16 == null)
182                        throw new NullPointerException("16-bit destination address cannot be null.");
183                if (frameID < 0 || frameID > 255)
184                        throw new IllegalArgumentException("Frame ID must be between 0 and 255.");
185                if (broadcastRadius < 0 || broadcastRadius > 255)
186                        throw new IllegalArgumentException("Broadcast radius must be between 0 and 255.");
187                if (transmitOptions < 0 || transmitOptions > 255)
188                        throw new IllegalArgumentException("Transmit options must be between 0 and 255.");
189                
190                this.frameID = frameID;
191                this.destAddress64 = destAddress64;
192                this.destAddress16 = destAddress16;
193                this.broadcastRadius = broadcastRadius;
194                this.transmitOptions = transmitOptions;
195                this.rfData = rfData;
196                this.logger = LoggerFactory.getLogger(TransmitPacket.class);
197        }
198
199        /*
200         * (non-Javadoc)
201         * @see com.digi.xbee.api.packet.XBeeAPIPacket#getAPIPacketSpecificData()
202         */
203        @Override
204        protected byte[] getAPIPacketSpecificData() {
205                ByteArrayOutputStream data = new ByteArrayOutputStream();
206                try {
207                        data.write(destAddress64.getValue());
208                        data.write(destAddress16.getValue());
209                        data.write(broadcastRadius);
210                        data.write(transmitOptions);
211                        if (rfData != null)
212                                data.write(rfData);
213                } catch (IOException e) {
214                        logger.error(e.getMessage(), e);
215                }
216                return data.toByteArray();
217        }
218
219        /*
220         * (non-Javadoc)
221         * @see com.digi.xbee.api.packet.XBeeAPIPacket#needsAPIFrameID()
222         */
223        @Override
224        public boolean needsAPIFrameID() {
225                return true;
226        }
227        
228        /*
229         * (non-Javadoc)
230         * @see com.digi.xbee.api.packet.XBeeAPIPacket#isBroadcast()
231         */
232        @Override
233        public boolean isBroadcast() {
234                return get64bitDestinationAddress().equals(XBee64BitAddress.BROADCAST_ADDRESS) 
235                                || get16bitDestinationAddress().equals(XBee16BitAddress.BROADCAST_ADDRESS);
236        }
237        
238        /**
239         * Returns the 64-bit destination address.
240         * 
241         * @return The 64-bit destination address.
242         * 
243         * @see com.digi.xbee.api.models.XBee64BitAddress
244         */
245        public XBee64BitAddress get64bitDestinationAddress() {
246                return destAddress64;
247        }
248        
249        /**
250         * Returns the 16-bit destination address.
251         * 
252         * @return The 16-bit destination address.
253         * 
254         * @see com.digi.xbee.api.models.XBee16BitAddress
255         */
256        public XBee16BitAddress get16bitDestinationAddress() {
257                return destAddress16;
258        }
259        
260        /**
261         * Returns the broadcast radius.
262         * 
263         * @return The broadcast radius.
264         */
265        public int getBroadcastRadius() {
266                return broadcastRadius;
267        }
268        
269        /**
270         * Returns the transmit options bitfield.
271         * 
272         * @return The transmit options bitfield.
273         * 
274         * @see com.digi.xbee.api.models.XBeeTransmitOptions
275         */
276        public int getTransmitOptions() {
277                return transmitOptions;
278        }
279        
280        /**
281         * Sets the RF Data to send.
282         * 
283         * @param rfData RF Data to send.
284         */
285        public void setRFData(byte[] rfData) {
286                this.rfData = rfData;
287        }
288        
289        /**
290         * Returns the RF Data to send.
291         * 
292         * @return RF Data to send.
293         */
294        public byte[] getRFData() {
295                return rfData;
296        }
297        
298        /*
299         * (non-Javadoc)
300         * @see com.digi.xbee.api.packet.XBeeAPIPacket#getAPIPacketParameters()
301         */
302        @Override
303        public LinkedHashMap<String, String> getAPIPacketParameters() {
304                LinkedHashMap<String, String> parameters = new LinkedHashMap<String, String>();
305                parameters.put("64-bit dest. address", HexUtils.prettyHexString(destAddress64.toString()));
306                parameters.put("16-bit dest. address", HexUtils.prettyHexString(destAddress16.toString()));
307                parameters.put("Broadcast radius", HexUtils.prettyHexString(HexUtils.integerToHexString(broadcastRadius, 1)) + " (" + broadcastRadius + ")");
308                parameters.put("Options", HexUtils.prettyHexString(HexUtils.integerToHexString(transmitOptions, 1)));
309                if (rfData != null)
310                        parameters.put("RF data", HexUtils.prettyHexString(HexUtils.byteArrayToHexString(rfData)));
311                return parameters;
312        }
313}