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.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.ATStringCommands;
023import com.digi.xbee.api.models.XBee16BitAddress;
024import com.digi.xbee.api.models.XBee64BitAddress;
025import com.digi.xbee.api.packet.APIFrameType;
026import com.digi.xbee.api.packet.XBeeAPIPacket;
027import com.digi.xbee.api.utils.ByteUtils;
028import com.digi.xbee.api.utils.HexUtils;
029
030/**
031 * This class represents a Remote AT Command Request packet. Packet is built
032 * using the parameters of the constructor or providing a valid API payload.
033 * 
034 * <p>Used to query or set module parameters on a remote device. For parameter 
035 * changes on the remote device to take effect, changes must be applied, either 
036 * by setting the apply changes options bit, or by sending an {@code AC} command 
037 * to the remote node.</p>
038 * 
039 * <p>Remote Command options are set as a bitfield.</p>
040 * 
041 * <p>If configured, command response is received as a 
042 * {@code RemoteATCommandResponse packet}.</p>
043 * 
044 * @see RemoteATCommandResponsePacket
045 * @see com.digi.xbee.api.packet.XBeeAPIPacket
046 */
047public class RemoteATCommandPacket extends XBeeAPIPacket {
048        
049        // Constants.
050        private static final int MIN_API_PAYLOAD_LENGTH = 15; // 1 (Frame type) + 1 (frame ID) + 8 (64-bit address) + 2 (16-bit address) + 1 (transmit options byte) + 2 (AT command)
051        
052        // Variables.
053        private final XBee64BitAddress destAddress64;
054        
055        private final XBee16BitAddress destAddress16;
056        
057        private final int transmitOptions;
058        
059        private final String command;
060        
061        private byte[] parameter;
062        
063        private Logger logger;
064        
065        /**
066         * Creates a new {@code RemoteATCommandPacket} object from the given 
067         * payload.
068         * 
069         * @param payload The API frame payload. It must start with the frame type 
070         *                corresponding to a Remote AT Command packet ({@code 0x17}).
071         *                The byte array must be in {@code OperatingMode.API} mode.
072         * 
073         * @return Parsed Remote AT Command Request packet.
074         * 
075         * @throws IllegalArgumentException if {@code payload[0] != APIFrameType.REMOTE_AT_COMMAND_REQUEST.getValue()} or
076         *                                  if {@code payload.length < }{@value #MIN_API_PAYLOAD_LENGTH} or
077         *                                  if {@code frameID < 0} or
078         *                                  if {@code frameID > 255} or
079         *                                  if {@code transmitOptions < 0} or
080         *                                  if {@code transmitOptions > 255}.
081         * @throws NullPointerException if {@code payload == null}.
082         */
083        public static RemoteATCommandPacket createPacket(byte[] payload) {
084                if (payload == null)
085                        throw new NullPointerException("Remote AT Command packet payload cannot be null.");
086                
087                // 1 (Frame type) + 1 (frame ID) + 8 (64-bit address) + 2 (16-bit address) + 1 (transmit options byte) + 2 (AT command)
088                if (payload.length < MIN_API_PAYLOAD_LENGTH)
089                        throw new IllegalArgumentException("Incomplete Remote AT Command packet.");
090                
091                if ((payload[0] & 0xFF) != APIFrameType.REMOTE_AT_COMMAND_REQUEST.getValue())
092                        throw new IllegalArgumentException("Payload is not a Remote AT Command packet.");
093                
094                // payload[0] is the frame type.
095                int index = 1;
096                
097                // Frame ID byte.
098                int frameID = payload[index] & 0xFF;
099                index = index + 1;
100                
101                // 8 bytes of 64-bit address.
102                XBee64BitAddress destAddress64 = new XBee64BitAddress(Arrays.copyOfRange(payload, index, index + 8));
103                index = index + 8;
104                
105                // 2 bytes of 16-bit address.
106                XBee16BitAddress destAddress16 = new XBee16BitAddress(payload[index] & 0xFF, payload[index + 1] & 0xFF);
107                index = index + 2;
108                
109                // Options byte.
110                int transmitOptions = payload[index] & 0xFF;
111                index = index + 1;
112                
113                // 2 bytes of AT command.
114                String command = new String(new byte[]{payload[index], payload[index + 1]});
115                index = index + 2;
116                
117                // Get data.
118                byte[] parameterData = null;
119                if (index < payload.length)
120                        parameterData = Arrays.copyOfRange(payload, index, payload.length);
121                
122                return new RemoteATCommandPacket(frameID, destAddress64, destAddress16, transmitOptions, 
123                                command, parameterData);
124        }
125        
126        /**
127         * Class constructor. Instantiates a new {@code RemoteATCommandRequest}
128         * object with the given parameters.
129         * 
130         * @param frameID The Frame ID.
131         * @param destAddress64 64-bit address of the destination device.
132         * @param destAddress16 16-bit address of the destination device.
133         * @param transmitOptions Bitfield of supported transmission options.
134         * @param command AT command.
135         * @param parameter AT command parameter as String.
136         * 
137         * @throws IllegalArgumentException if {@code frameID < 0} or
138         *                                  if {@code frameID > 255} or
139         *                                  if {@code transmitOptions < 0} or
140         *                                  if {@code transmitOptions > 255}.
141         * @throws NullPointerException if {@code destAddress64 == null} or
142         *                              if {@code destAddress16 == null} or
143         *                              if {@code command == null}.
144         * 
145         * @see com.digi.xbee.api.models.XBeeTransmitOptions
146         * @see com.digi.xbee.api.models.XBee16BitAddress
147         * @see com.digi.xbee.api.models.XBee64BitAddress
148         */
149        public RemoteATCommandPacket(int frameID, XBee64BitAddress destAddress64, XBee16BitAddress destAddress16, 
150                        int transmitOptions, String command, String parameter) {
151                super(APIFrameType.REMOTE_AT_COMMAND_REQUEST);
152                
153                if (destAddress64 == null)
154                        throw new NullPointerException("64-bit destination address cannot be null.");
155                if (destAddress16 == null)
156                        throw new NullPointerException("16-bit destination address cannot be null.");
157                if (command == null)
158                        throw new NullPointerException("AT command cannot be null.");
159                if (frameID < 0 || frameID > 255)
160                        throw new IllegalArgumentException("Frame ID must be between 0 and 255.");
161                if (transmitOptions < 0 || transmitOptions > 255)
162                        throw new IllegalArgumentException("Options value must be between 0 and 255.");
163                
164                this.frameID = frameID;
165                this.destAddress64 = destAddress64;
166                this.destAddress16 = destAddress16;
167                this.transmitOptions = transmitOptions;
168                this.command = command;
169                if (parameter != null)
170                        this.parameter = parameter.getBytes();
171                this.logger = LoggerFactory.getLogger(RemoteATCommandPacket.class);
172        }
173        
174        /**
175         * Class constructor. Instantiates a new {@code RemoteATCommandRequest} 
176         * object with the given parameters.
177         * 
178         * @param frameID Frame ID.
179         * @param destAddress64 64-bit address of the destination device.
180         * @param destAddress16 16-bit address of the destination device.
181         * @param transmitOptions Bitfield of supported transmission options.
182         * @param command AT command.
183         * @param parameter AT command parameter.
184         * 
185         * @throws IllegalArgumentException if {@code frameID < 0} or
186         *                                  if {@code frameID > 255} or
187         *                                  if {@code transmitOptions < 0} or
188         *                                  if {@code transmitOptions > 255}.
189         * @throws NullPointerException if {@code destAddress64 == null} or
190         *                              if {@code destAddress16 == null} or
191         *                              if {@code command == null}.
192         * 
193         * @see com.digi.xbee.api.models.XBeeTransmitOptions
194         * @see com.digi.xbee.api.models.XBee16BitAddress
195         * @see com.digi.xbee.api.models.XBee64BitAddress
196         */
197        public RemoteATCommandPacket(int frameID, XBee64BitAddress destAddress64, XBee16BitAddress destAddress16, 
198                        int transmitOptions, String command, byte[] parameter) {
199                super(APIFrameType.REMOTE_AT_COMMAND_REQUEST);
200                
201                if (destAddress64 == null)
202                        throw new NullPointerException("64-bit destination address cannot be null.");
203                if (destAddress16 == null)
204                        throw new NullPointerException("16-bit destination address cannot be null.");
205                if (command == null)
206                        throw new NullPointerException("AT command cannot be null.");
207                if (frameID < 0 || frameID > 255)
208                        throw new IllegalArgumentException("Frame ID must be between 0 and 255.");
209                if (transmitOptions < 0 || transmitOptions > 255)
210                        throw new IllegalArgumentException("Options value must be between 0 and 255.");
211                
212                this.frameID = frameID;
213                this.destAddress64 = destAddress64;
214                this.destAddress16 = destAddress16;
215                this.transmitOptions = transmitOptions;
216                this.command = command;
217                this.parameter = parameter;
218                this.logger = LoggerFactory.getLogger(RemoteATCommandPacket.class);
219        }
220        
221        /*
222         * (non-Javadoc)
223         * @see com.digi.xbee.api.packet.XBeeAPIPacket#getAPIPacketSpecificData()
224         */
225        @Override
226        protected byte[] getAPIPacketSpecificData() {
227                ByteArrayOutputStream data = new ByteArrayOutputStream();
228                try {
229                        data.write(destAddress64.getValue());
230                        data.write(destAddress16.getValue());
231                        data.write(transmitOptions);
232                        data.write(ByteUtils.stringToByteArray(command));
233                        if (parameter != null)
234                                data.write(parameter);
235                } catch (IOException e) {
236                        logger.error(e.getMessage(), e);
237                }
238                return data.toByteArray();
239        }
240        
241        /*
242         * (non-Javadoc)
243         * @see com.digi.xbee.api.packet.XBeeAPIPacket#needsAPIFrameID()
244         */
245        @Override
246        public boolean needsAPIFrameID() {
247                return true;
248        }
249        
250        /*
251         * (non-Javadoc)
252         * @see com.digi.xbee.api.packet.XBeeAPIPacket#isBroadcast()
253         */
254        @Override
255        public boolean isBroadcast() {
256                return get64bitDestinationAddress().equals(XBee64BitAddress.BROADCAST_ADDRESS) 
257                                || get16bitDestinationAddress().equals(XBee16BitAddress.BROADCAST_ADDRESS);
258        }
259        
260        /**
261         * Returns the 64 bit destination address.
262         * 
263         * @return The 64 bit destination address.
264         * 
265         * @see com.digi.xbee.api.models.XBee64BitAddress
266         */
267        public XBee64BitAddress get64bitDestinationAddress() {
268                return destAddress64;
269        }
270        
271        /**
272         * Returns the 16 bit destination address.
273         * 
274         * @return The 16 bit destination address.
275         * 
276         * @see com.digi.xbee.api.models.XBee16BitAddress
277         */
278        public XBee16BitAddress get16bitDestinationAddress() {
279                return destAddress16;
280        }
281        
282        /**
283         * Returns the transmit options bitfield.
284         * 
285         * @return Transmit options bitfield.
286         * 
287         * @see com.digi.xbee.api.models.XBeeTransmitOptions
288         */
289        public int getTransmitOptions() {
290                return transmitOptions;
291        }
292        
293        /**
294         * Returns the AT Command.
295         * 
296         * @return The AT Command.
297         */
298        public String getCommand() {
299                return command;
300        }
301        
302        /**
303         * Sets the AT command parameter as String.
304         * 
305         * @param parameter The AT command parameter as String.
306         */
307        public void setParameter(String parameter) {
308                if (parameter == null)
309                        this.parameter = null;
310                else
311                        this.parameter = parameter.getBytes();
312        }
313        
314        /**
315         * Sets the AT command parameter.
316         * 
317         * @param parameter The AT command parameter.
318         */
319        public void setParameter(byte[] parameter) {
320                this.parameter = parameter;
321        }
322        
323        /**
324         * Returns the AT command parameter.
325         * 
326         * @return The AT command parameter.
327         */
328        public byte[] getParameter() {
329                return parameter;
330        }
331        
332        /**
333         * Returns the AT command parameter as String.
334         * 
335         * @return The AT command parameter as String, {@code null} if not 
336         *         parameter is set.
337         */
338        public String getParameterAsString() {
339                if (parameter == null)
340                        return null;
341                return new String(parameter);
342        }
343        
344        /*
345         * (non-Javadoc)
346         * @see com.digi.xbee.api.packet.XBeeAPIPacket#getAPIPacketParameters()
347         */
348        @Override
349        public LinkedHashMap<String, String> getAPIPacketParameters() {
350                LinkedHashMap<String, String> parameters = new LinkedHashMap<String, String>();
351                parameters.put("64-bit dest. address", HexUtils.prettyHexString(destAddress64.toString()));
352                parameters.put("16-bit dest. address", HexUtils.prettyHexString(destAddress16.toString()));
353                parameters.put("Command options", HexUtils.prettyHexString(HexUtils.integerToHexString(transmitOptions, 1)));
354                parameters.put("AT Command", HexUtils.prettyHexString(HexUtils.byteArrayToHexString(command.getBytes())) + " (" + command + ")");
355                if (parameter != null) {
356                        if (ATStringCommands.get(command) != null)
357                                parameters.put("Parameter", HexUtils.prettyHexString(HexUtils.byteArrayToHexString(parameter)) + " (" + new String(parameter) + ")");
358                        else
359                                parameters.put("Parameter", HexUtils.prettyHexString(HexUtils.byteArrayToHexString(parameter)));
360                }
361                return parameters;
362        }
363}