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;
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         * @return The XBee packet frame type.
095         * 
096         * @see APIFrameType
097         */
098        public APIFrameType getFrameType() {
099                return frameType;
100        }
101        
102        /**
103         * Returns the XBee packet frame type integer value.
104         * 
105         * @return The XBee packet frame type integer value.
106         */
107        public int getFrameTypeValue() {
108                return frameTypeValue;
109        }
110        
111        /*
112         * (non-Javadoc)
113         * @see com.digi.xbee.api.packet.XBeePacket#getPacketData()
114         */
115        @Override
116        public byte[] getPacketData() {
117                ByteArrayOutputStream data = new ByteArrayOutputStream();
118                
119                data.write(frameTypeValue);
120                
121                byte[] apiData = getAPIData();
122                if (apiData == null)
123                        apiData = new byte[0];
124                if (apiData != null && apiData.length > 0) {
125                        try {
126                                data.write(apiData);
127                        } catch (IOException e) {
128                                logger.error(e.getMessage(), e);
129                        }
130                }
131                
132                return data.toByteArray();
133        }
134        
135        /**
136         * Returns the XBee API packet data.
137         * 
138         * <p>This does not include the frame ID if it is needed.</p>
139         * 
140         * @return The XBee API packet data.
141         */
142        public byte[] getAPIData() {
143                ByteArrayOutputStream data = new ByteArrayOutputStream();
144                
145                byte[] apiData = getAPIPacketSpecificData();
146                if (apiData == null)
147                        apiData = new byte[0];
148                
149                if (needsAPIFrameID())
150                        data.write(frameID);
151                
152                if (apiData != null && apiData.length > 0) {
153                        try {
154                                data.write(apiData);
155                        } catch (IOException e) {
156                                logger.error(e.getMessage(), e);
157                        }
158                }
159                
160                return data.toByteArray();
161        }
162        
163        /**
164         * Returns the XBee API packet specific data.
165         * 
166         * <p>This does not include the frame ID if it is needed.</p>
167         * 
168         * @return The XBee API packet data.
169         */
170        protected abstract byte[] getAPIPacketSpecificData();
171        
172        /**
173         * Returns whether the API packet needs API Frame ID or not.
174         * 
175         * @return {@code true} if the packet needs API Frame ID, {@code false} 
176         *         otherwise.
177         */
178        public abstract boolean needsAPIFrameID();
179        
180        /**
181         * Returns the Frame ID of the API packet.
182         * 
183         * <p>If the frame ID is not configured or if the API packet does not need 
184         * a Frame ID ({@code if (!needsAPIFrameID())}) this method returns 
185         * {@code NO_FRAME_ID} ({@value #NO_FRAME_ID}).</p>
186         * 
187         * @return The frame ID.
188         * 
189         * @see #NO_FRAME_ID
190         * @see #needsAPIFrameID()
191         * @see #setFrameID(int)
192         */
193        public int getFrameID() {
194                if (needsAPIFrameID())
195                        return frameID;
196                return NO_FRAME_ID;
197        }
198        
199        /**
200         * Sets the frame ID of the API packet.
201         * 
202         * <p>If the API packet does not need a frame ID 
203         * ({@code if (!needsAPIFrameID())}), this method does nothing.</p>
204         * 
205         * @param frameID The frame ID to set.
206         * 
207         * @throws IllegalArgumentException if {@code frameID < 0} or 
208         *                                  if {@code frameID > 255}.
209         * 
210         * @see #getFrameID()
211         */
212        public void setFrameID(int frameID) {
213                if (frameID < 0 || frameID > 255)
214                        throw new IllegalArgumentException("Frame ID must be between 0 and 255.");
215                
216                if (needsAPIFrameID())
217                        this.frameID = frameID;
218        }
219        
220        /**
221         * Returns whether or not the packet is a broadcast packet.
222         * 
223         * @return {@code true} if the packet is a broadcast packet, {@code false} 
224         *         otherwise.
225         */
226        public abstract boolean isBroadcast();
227        
228        /**
229         * Returns whether the given ID is the current frame ID.
230         * 
231         * @param id The frame id to check.
232         * 
233         * @return {@code true} if frame ID is equal to the {@code id} provided, 
234         *         {@code false} otherwise or if the frame does not need an ID.
235         * 
236         * @see #getFrameID()
237         * @see #needsAPIFrameID()
238         * @see #setFrameID(int)
239         */
240        public boolean checkFrameID(int id) {
241                return needsAPIFrameID() && getFrameID() == id;
242        }
243        
244        /*
245         * (non-Javadoc)
246         * @see com.digi.xbee.api.packet.XBeePacket#getPacketParameters()
247         */
248        @Override
249        protected LinkedHashMap<String, String> getPacketParameters() {
250                LinkedHashMap<String, String> parameters = new LinkedHashMap<String, String>();
251                if (getFrameType() != null)
252                        parameters.put("Frame type", HexUtils.prettyHexString(HexUtils.integerToHexString(frameTypeValue, 1)) + " (" + getFrameType().getName() + ")");
253                else
254                        parameters.put("Frame type", HexUtils.prettyHexString(HexUtils.integerToHexString(frameTypeValue, 1)));
255                
256                if (needsAPIFrameID()) {
257                        if (frameID == NO_FRAME_ID)
258                                parameters.put("Frame ID", "(NO FRAME ID)");
259                        else
260                                parameters.put("Frame ID", HexUtils.prettyHexString(HexUtils.integerToHexString(frameID, 1)) + " (" + frameID + ")");
261                }
262                
263                LinkedHashMap<String, String> apiParams = getAPIPacketParameters();
264                if (apiParams != null)
265                        parameters.putAll(apiParams);
266                return parameters;
267        }
268        
269        /**
270         * Returns a map with the XBee packet parameters and their values.
271         * 
272         * @return A sorted map containing the XBee packet parameters with their 
273         *         values.
274         */
275        protected abstract LinkedHashMap<String, String> getAPIPacketParameters();
276}