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.ATCommandStatus;
023import com.digi.xbee.api.models.ATStringCommands;
024import com.digi.xbee.api.models.XBee16BitAddress;
025import com.digi.xbee.api.models.XBee64BitAddress;
026import com.digi.xbee.api.packet.APIFrameType;
027import com.digi.xbee.api.packet.XBeeAPIPacket;
028import com.digi.xbee.api.utils.ByteUtils;
029import com.digi.xbee.api.utils.HexUtils;
030
031/**
032 * This class represents a Remote AT Command Response packet. Packet is built 
033 * using the parameters of the constructor or providing a valid API payload.
034 * 
035 * <p>If a module receives a remote command response RF data frame in response 
036 * to a Remote AT Command Request, the module will send a Remote AT Command 
037 * Response message out the UART. Some commands may send back multiple frames--
038 * for example, Node Discover ({@code ND}) command.</p>
039 * 
040 * <p>This packet is received in response of a {@code RemoteATCommandPacket}.
041 * </p>
042 * 
043 * <p>Response also includes an {@code ATCommandStatus} object with the status 
044 * of the AT command.</p>
045 * 
046 * @see RemoteATCommandPacket
047 * @see com.digi.xbee.api.models.ATCommandStatus
048 * @see com.digi.xbee.api.packet.XBeeAPIPacket
049 */
050public class RemoteATCommandResponsePacket extends XBeeAPIPacket {
051        
052        // Constants.
053        private static final int MIN_API_PAYLOAD_LENGTH = 15; // 1 (Frame type) + 1 (frame ID) + 8 (32-bit address) + 2 (16-bit address) + 2 (AT command) + 1 (status)
054        
055        // Variables.
056        private final XBee64BitAddress sourceAddress64;
057        
058        private final XBee16BitAddress sourceAddress16;
059        
060        private final ATCommandStatus status;
061        
062        private final String command;
063        
064        private byte[] commandValue;
065        
066        private Logger logger;
067        
068        /**
069         * Creates an new {@code RemoteATCommandResponsePacket} object from the 
070         * given payload.
071         * 
072         * @param payload The API frame payload. It must start with the frame type 
073         *                corresponding to a Remote AT Command Response packet ({@code 0x97}).
074         *                The byte array must be in {@code OperatingMode.API} mode.
075         * 
076         * @return Parsed Remote AT Command Response packet.
077         * 
078         * @throws IllegalArgumentException if {@code payload[0] != APIFrameType.REMOTE_AT_COMMAND_RESPONSE.getValue()} or
079         *                                  if {@code payload.length < }{@value #MIN_API_PAYLOAD_LENGTH} or
080         *                                  if {@code frameID < 0} or
081         *                                  if {@code frameID > 255}.
082         * @throws NullPointerException if {@code payload == null}.
083         */
084        public static RemoteATCommandResponsePacket createPacket(byte[] payload) {
085                if (payload == null)
086                        throw new NullPointerException("Remote AT Command Response packet payload cannot be null.");
087                
088                // 1 (Frame type) + 1 (frame ID) + 8 (32-bit address) + 2 (16-bit address) + 2 (AT command) + 1 (status)
089                if (payload.length < MIN_API_PAYLOAD_LENGTH)
090                        throw new IllegalArgumentException("Incomplete Remote AT Command Response packet.");
091                
092                if ((payload[0] & 0xFF) != APIFrameType.REMOTE_AT_COMMAND_RESPONSE.getValue())
093                        throw new IllegalArgumentException("Payload is not a Remote AT Command Response packet.");
094                
095                // payload[0] is the frame type.
096                int index = 1;
097                
098                // Frame ID byte.
099                int frameID = payload[index] & 0xFF;
100                index = index + 1;
101                
102                // 8 bytes of 64-bit address.
103                XBee64BitAddress sourceAddress64 = new XBee64BitAddress(Arrays.copyOfRange(payload, index, index + 8));
104                index = index + 8;
105                
106                // 2 bytes of 16-bit address.
107                XBee16BitAddress sourceAddress16 = new XBee16BitAddress(payload[index] & 0xFF, payload[index + 1] & 0xFF);
108                index = index + 2;
109                
110                // 2 bytes of AT command.
111                String command = new String(new byte[]{payload[index], payload[index + 1]});
112                index = index + 2;
113                                
114                // Status byte.
115                int status = payload[index] & 0xFF;
116                index = index + 1;
117                                
118                // Get data.
119                byte[] commandData = null;
120                if (index < payload.length)
121                        commandData = Arrays.copyOfRange(payload, index, payload.length);
122                
123                // TODO if ATCommandStatus is unknown????
124                return new RemoteATCommandResponsePacket(frameID, sourceAddress64, 
125                                sourceAddress16, command, ATCommandStatus.get(status), commandData);
126        }
127        
128        /**
129         * Class constructor. Instantiates a new 
130         * {@code RemoteATCommandResponsePacket} object with the given parameters.
131         * 
132         * @param frameID frame ID.
133         * @param sourceAddress64 64-bit address of the remote radio returning 
134         *                        response.
135         * @param sourceAddress16 16-bit network address of the remote.
136         * @param command The AT command.
137         * @param status The command status.
138         * @param commandValue The AT command response value.
139         * 
140         * @throws IllegalArgumentException if {@code frameID < 0} or
141         *                                  if {@code frameID > 255}.
142         * @throws NullPointerException if {@code sourceAddress64 == null} or
143         *                              if {@code sourceAddress16 == null} or
144         *                              if {@code command == null} or
145         *                              if {@code status == null}.
146         * 
147         * @see com.digi.xbee.api.models.ATCommandStatus
148         * @see com.digi.xbee.api.models.XBee16BitAddress
149         * @see com.digi.xbee.api.models.XBee64BitAddress
150         */
151        public RemoteATCommandResponsePacket(int frameID, XBee64BitAddress sourceAddress64, XBee16BitAddress sourceAddress16, 
152                        String command, ATCommandStatus status, byte[] commandValue) {
153                super(APIFrameType.REMOTE_AT_COMMAND_RESPONSE);
154                
155                if (sourceAddress64 == null)
156                        throw new NullPointerException("64-bit source address cannot be null.");
157                if (sourceAddress16 == null)
158                        throw new NullPointerException("16-bit source address cannot be null.");
159                if (command == null)
160                        throw new NullPointerException("AT command cannot be null.");
161                if (status == null)
162                        throw new NullPointerException("AT command status cannot be null.");
163                if (frameID < 0 || frameID > 255)
164                        throw new IllegalArgumentException("Frame ID must be between 0 and 255.");
165                
166                this.frameID = frameID;
167                this.sourceAddress64 = sourceAddress64;
168                this.sourceAddress16 = sourceAddress16;
169                this.command = command;
170                this.status = status;
171                this.commandValue = commandValue;
172                this.logger = LoggerFactory.getLogger(RemoteATCommandResponsePacket.class);
173        }
174        
175        /*
176         * (non-Javadoc)
177         * @see com.digi.xbee.api.packet.XBeeAPIPacket#getAPIPacketSpecificData()
178         */
179        @Override
180        protected byte[] getAPIPacketSpecificData() {
181                ByteArrayOutputStream data = new ByteArrayOutputStream();
182                try {
183                        data.write(sourceAddress64.getValue());
184                        data.write(sourceAddress16.getValue());
185                        data.write(ByteUtils.stringToByteArray(command));
186                        data.write(status.getId());
187                        if (commandValue != null)
188                                data.write(commandValue);
189                } catch (IOException e) {
190                        logger.error(e.getMessage(), e);
191                }
192                return data.toByteArray();
193        }
194        
195        /*
196         * (non-Javadoc)
197         * @see com.digi.xbee.api.packet.XBeeAPIPacket#needsAPIFrameID()
198         */
199        @Override
200        public boolean needsAPIFrameID() {
201                return true;
202        }
203        
204        /**
205         * Returns the 64-bit source address. 
206         * 
207         * @return The 64-bit source address.
208         * 
209         * @see com.digi.xbee.api.models.XBee64BitAddress
210         */
211        public XBee64BitAddress get64bitSourceAddress() {
212                return sourceAddress64;
213        }
214        
215        /**
216         * Returns the 16-bit source address.
217         * 
218         * @return The 16-bit source address.
219         * 
220         * @see com.digi.xbee.api.models.XBee16BitAddress
221         */
222        public XBee16BitAddress get16bitSourceAddress() {
223                return sourceAddress16;
224        }
225        
226        /**
227         * Returns the AT command response status.
228         * 
229         * @return The AT command response status.
230         * 
231         * @see com.digi.xbee.api.models.ATCommandStatus
232         */
233        public ATCommandStatus getStatus() {
234                return status;
235        }
236        
237        /**
238         * Returns the AT command.
239         * 
240         * @return The AT command.
241         */
242        public String getCommand() {
243                return command;
244        }
245        
246        /**
247         * Sets the AT command response value as String.
248         * 
249         * @param commandValue The AT command response value as String.
250         */
251        public void setCommandValue(String commandValue) {
252                if (commandValue == null)
253                        this.commandValue = null;
254                else
255                        this.commandValue = commandValue.getBytes();
256        }
257        
258        /**
259         * Sets the AT response response value.
260         * 
261         * @param commandValue The AT command response value.
262         */
263        public void setCommandValue(byte[] commandValue) {
264                this.commandValue = commandValue;
265        }
266        
267        /**
268         * Retrieves the AT command response value.
269         * 
270         * @return The AT command response value.
271         */
272        public byte[] getCommandValue() {
273                return commandValue;
274        }
275        
276        /**
277         * Returns the AT command response value as String.
278         * 
279         * @return The AT command response value as String, {@code null} if no 
280         *         value is set.
281         */
282        public String getCommandValueAsString() {
283                if (commandValue == null)
284                        return null;
285                return new String(commandValue);
286        }
287        
288        /*
289         * (non-Javadoc)
290         * @see com.digi.xbee.api.packet.XBeeAPIPacket#isBroadcast()
291         */
292        @Override
293        public boolean isBroadcast() {
294                return false;
295        }
296        
297        /*
298         * (non-Javadoc)
299         * @see com.digi.xbee.api.packet.XBeeAPIPacket#getAPIPacketParameters()
300         */
301        @Override
302        public LinkedHashMap<String, String> getAPIPacketParameters() {
303                LinkedHashMap<String, String> parameters = new LinkedHashMap<String, String>();
304                parameters.put("64-bit source address", HexUtils.prettyHexString(sourceAddress64.toString()));
305                parameters.put("16-bit source address", HexUtils.prettyHexString(sourceAddress16.toString()));
306                parameters.put("AT Command", HexUtils.prettyHexString(HexUtils.byteArrayToHexString(command.getBytes())) + " (" + command + ")");
307                parameters.put("Status", HexUtils.prettyHexString(HexUtils.integerToHexString(status.getId(), 1)) + " (" + status.getDescription() + ")");
308                if (commandValue != null) {
309                        if (ATStringCommands.get(command) != null)
310                                parameters.put("Response", HexUtils.prettyHexString(HexUtils.byteArrayToHexString(commandValue)) + " (" + new String(commandValue) + ")");
311                        else
312                                parameters.put("Response", HexUtils.prettyHexString(HexUtils.byteArrayToHexString(commandValue)));
313                }
314                return parameters;
315        }
316}