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