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;
013
014import java.io.ByteArrayOutputStream;
015import java.io.IOException;
016import java.util.LinkedHashMap;
017
018import org.slf4j.Logger;
019import org.slf4j.LoggerFactory;
020
021import com.digi.xbee.api.utils.HexUtils;
022
023/**
024 * This abstract class provides the basic structure of a ZigBee API frame.
025 * 
026 * <p>Derived classes should implement their own methods to generate the API 
027 * data and frame ID in case they support it.</p>
028 * 
029 * <p>Basic operations such as frame type retrieval are performed in this class.
030 * </p>
031 * 
032 * @see XBeePacket
033 */
034public abstract class XBeeAPIPacket extends XBeePacket {
035
036        // Constants.
037        public final static int NO_FRAME_ID = 9999;
038        
039        // Variables.
040        protected int frameID = NO_FRAME_ID;
041        
042        private APIFrameType frameType = null;
043        
044        private int frameTypeValue;
045        
046        private Logger logger;
047
048        /**
049         * Class constructor. Instantiates a new {@code XBeeAPIPacket} object with 
050         * the given API frame type.
051         * 
052         * @param frameType XBee packet frame type.
053         * 
054         * @throws NullPointerException if {@code frameType == null}.
055         * 
056         * @see APIFrameType
057         */
058        protected XBeeAPIPacket(APIFrameType frameType) {
059                super();
060                
061                if (frameType == null)
062                        throw new NullPointerException("Frame type cannot be null.");
063                
064                this.frameType = frameType;
065                frameTypeValue = frameType.getValue();
066                
067                this.logger = LoggerFactory.getLogger(XBeeAPIPacket.class);
068        }
069        
070        /**
071         * Class constructor. Instantiates a new {@code XBeeAPIPacket} object with 
072         * the given frame type value.
073         * 
074         * @param frameTypeValue XBee packet frame type integer value.
075         * 
076         * @throws IllegalArgumentException if {@code frameTypeValue < 0} or 
077         *                                  if {@code frameTypeValue > 255}.
078         */
079        protected XBeeAPIPacket(int frameTypeValue) {
080                super();
081                
082                if (frameTypeValue < 0 || frameTypeValue > 255)
083                        throw new IllegalArgumentException("Frame type value must be between 0 and 255.");
084                
085                this.frameTypeValue = frameTypeValue;
086                this.frameType = APIFrameType.get(frameTypeValue);
087                
088                this.logger = LoggerFactory.getLogger(XBeeAPIPacket.class);
089        }
090        
091        /**
092         * Returns the XBee packet frame type.
093         * 
094         * If {@code APIFrameType#UNKNOWN} is returned, the real value of the frame
095         * type is returned by {@code #getFrameTypeValue()}.
096         * 
097         * @return The XBee packet frame type.
098         * 
099         * @see APIFrameType
100         */
101        public APIFrameType getFrameType() {
102                return frameType;
103        }
104        
105        /**
106         * Returns the XBee packet frame type integer value.
107         * 
108         * @return The XBee packet frame type integer value.
109         */
110        public int getFrameTypeValue() {
111                return frameTypeValue;
112        }
113        
114        /*
115         * (non-Javadoc)
116         * @see com.digi.xbee.api.packet.XBeePacket#getPacketData()
117         */
118        @Override
119        public byte[] getPacketData() {
120                ByteArrayOutputStream data = new ByteArrayOutputStream();
121                
122                data.write(frameTypeValue);
123                
124                byte[] apiData = getAPIData();
125                if (apiData == null)
126                        apiData = new byte[0];
127                if (apiData != null && apiData.length > 0) {
128                        try {
129                                data.write(apiData);
130                        } catch (IOException e) {
131                                logger.error(e.getMessage(), e);
132                        }
133                }
134                
135                return data.toByteArray();
136        }
137        
138        /**
139         * Returns the XBee API packet data.
140         * 
141         * <p>This does not include the frame ID if it is needed.</p>
142         * 
143         * @return The XBee API packet data.
144         */
145        public byte[] getAPIData() {
146                ByteArrayOutputStream data = new ByteArrayOutputStream();
147                
148                byte[] apiData = getAPIPacketSpecificData();
149                if (apiData == null)
150                        apiData = new byte[0];
151                
152                if (needsAPIFrameID())
153                        data.write(frameID);
154                
155                if (apiData != null && apiData.length > 0) {
156                        try {
157                                data.write(apiData);
158                        } catch (IOException e) {
159                                logger.error(e.getMessage(), e);
160                        }
161                }
162                
163                return data.toByteArray();
164        }
165        
166        /**
167         * Returns the XBee API packet specific data.
168         * 
169         * <p>This does not include the frame ID if it is needed.</p>
170         * 
171         * @return The XBee API packet data.
172         */
173        protected abstract byte[] getAPIPacketSpecificData();
174        
175        /**
176         * Returns whether the API packet needs API Frame ID or not.
177         * 
178         * @return {@code true} if the packet needs API Frame ID, {@code false} 
179         *         otherwise.
180         */
181        public abstract boolean needsAPIFrameID();
182        
183        /**
184         * Returns the Frame ID of the API packet.
185         * 
186         * <p>If the frame ID is not configured or if the API packet does not need 
187         * a Frame ID ({@code if (!needsAPIFrameID())}) this method returns 
188         * {@code NO_FRAME_ID} ({@value #NO_FRAME_ID}).</p>
189         * 
190         * @return The frame ID.
191         * 
192         * @see #NO_FRAME_ID
193         * @see #needsAPIFrameID()
194         * @see #setFrameID(int)
195         */
196        public int getFrameID() {
197                if (needsAPIFrameID())
198                        return frameID;
199                return NO_FRAME_ID;
200        }
201        
202        /**
203         * Sets the frame ID of the API packet.
204         * 
205         * <p>If the API packet does not need a frame ID 
206         * ({@code if (!needsAPIFrameID())}), this method does nothing.</p>
207         * 
208         * @param frameID The frame ID to set.
209         * 
210         * @throws IllegalArgumentException if {@code frameID < 0} or 
211         *                                  if {@code frameID > 255}.
212         * 
213         * @see #getFrameID()
214         */
215        public void setFrameID(int frameID) {
216                if (frameID < 0 || frameID > 255)
217                        throw new IllegalArgumentException("Frame ID must be between 0 and 255.");
218                
219                if (needsAPIFrameID())
220                        this.frameID = frameID;
221        }
222        
223        /**
224         * Returns whether or not the packet is a broadcast packet.
225         * 
226         * @return {@code true} if the packet is a broadcast packet, {@code false} 
227         *         otherwise.
228         */
229        public abstract boolean isBroadcast();
230        
231        /**
232         * Returns whether the given ID is the current frame ID.
233         * 
234         * @param id The frame id to check.
235         * 
236         * @return {@code true} if frame ID is equal to the {@code id} provided, 
237         *         {@code false} otherwise or if the frame does not need an ID.
238         * 
239         * @see #getFrameID()
240         * @see #needsAPIFrameID()
241         * @see #setFrameID(int)
242         */
243        public boolean checkFrameID(int id) {
244                return needsAPIFrameID() && getFrameID() == id;
245        }
246        
247        /*
248         * (non-Javadoc)
249         * @see com.digi.xbee.api.packet.XBeePacket#getPacketParameters()
250         */
251        @Override
252        protected LinkedHashMap<String, String> getPacketParameters() {
253                LinkedHashMap<String, String> parameters = new LinkedHashMap<String, String>();
254                if (getFrameType() != null)
255                        parameters.put("Frame type", HexUtils.prettyHexString(HexUtils.integerToHexString(frameTypeValue, 1)) + " (" + getFrameType().getName() + ")");
256                else
257                        parameters.put("Frame type", HexUtils.prettyHexString(HexUtils.integerToHexString(frameTypeValue, 1)));
258                
259                if (needsAPIFrameID()) {
260                        if (frameID == NO_FRAME_ID)
261                                parameters.put("Frame ID", "(NO FRAME ID)");
262                        else
263                                parameters.put("Frame ID", HexUtils.prettyHexString(HexUtils.integerToHexString(frameID, 1)) + " (" + frameID + ")");
264                }
265                
266                LinkedHashMap<String, String> apiParams = getAPIPacketParameters();
267                if (apiParams != null)
268                        parameters.putAll(apiParams);
269                return parameters;
270        }
271        
272        /**
273         * Returns a map with the XBee packet parameters and their values.
274         * 
275         * @return A sorted map containing the XBee packet parameters with their 
276         *         values.
277         */
278        protected abstract LinkedHashMap<String, String> getAPIPacketParameters();
279}