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.exceptions.OperationNotSupportedException;
023import com.digi.xbee.api.io.IOLine;
024import com.digi.xbee.api.io.IOSample;
025import com.digi.xbee.api.models.XBee16BitAddress;
026import com.digi.xbee.api.models.XBee64BitAddress;
027import com.digi.xbee.api.packet.APIFrameType;
028import com.digi.xbee.api.packet.XBeeAPIPacket;
029import com.digi.xbee.api.packet.raw.RX64Packet;
030import com.digi.xbee.api.utils.ByteUtils;
031import com.digi.xbee.api.utils.HexUtils;
032
033/**
034 * This class represents an IO Data Sample RX Indicator packet. Packet is built 
035 * using the parameters of the constructor or providing a valid API payload.
036 * 
037 * <p>When the module receives an IO sample frame from a remote device, it 
038 * sends the sample out the UART using this frame type (when AO=0). Only modules
039 * running API firmware will send IO samples out the UART.</p>
040 * 
041 * <p>Among received data, some options can also be received indicating 
042 * transmission parameters.</p>
043 * 
044 * @see com.digi.xbee.api.models.XBeeReceiveOptions
045 * @see com.digi.xbee.api.packet.XBeeAPIPacket
046 */
047public class IODataSampleRxIndicatorPacket extends XBeeAPIPacket {
048
049        // Constants.
050        private static final int MIN_API_PAYLOAD_LENGTH = 12; // 1 (Frame type) + 8 (32-bit address) + 2 (16-bit address) + 1 (receive options)
051        
052        // Variables.
053        private final XBee64BitAddress sourceAddress64;
054        private final XBee16BitAddress sourceAddress16;
055        
056        private final IOSample ioSample;
057        
058        private final int receiveOptions;
059        
060        private byte[] rfData;
061        
062        private Logger logger;
063        
064        /**
065         * Creates a new {@code IODataSampleRxIndicatorPacket} object from the 
066         * given payload.
067         * 
068         * @param payload The API frame payload. It must start with the frame type 
069         *                corresponding to a IO Data Sample RX Indicator packet ({@code 0x92}).
070         *                The byte array must be in {@code OperatingMode.API} mode.
071         * 
072         * @return Parsed ZigBee Receive packet.
073         * 
074         * @throws IllegalArgumentException if {@code payload[0] != APIFrameType.IO_DATA_SAMPLE_RX_INDICATOR.getValue()} or
075         *                                  if {@code payload.length < }{@value #MIN_API_PAYLOAD_LENGTH} or
076         *                                  if {@code receiveOptions < 0} or
077         *                                  if {@code receiveOptions > 255} or 
078         *                                  if {@code rfData.length < 5}.
079         * @throws NullPointerException if {@code payload == null}.
080         */
081        public static IODataSampleRxIndicatorPacket createPacket(byte[] payload) {
082                if (payload == null)
083                        throw new NullPointerException("IO Data Sample RX Indicator packet payload cannot be null.");
084                
085                // 1 (Frame type) + 8 (32-bit address) + 2 (16-bit address) + 1 (receive options)
086                if (payload.length < MIN_API_PAYLOAD_LENGTH)
087                        throw new IllegalArgumentException("Incomplete IO Data Sample RX Indicator packet.");
088                
089                if ((payload[0] & 0xFF) != APIFrameType.IO_DATA_SAMPLE_RX_INDICATOR.getValue())
090                        throw new IllegalArgumentException("Payload is not a IO Data Sample RX Indicator packet.");
091                
092                // payload[0] is the frame type.
093                int index = 1;
094                
095                // 2 bytes of 16-bit address.
096                XBee64BitAddress sourceAddress64 = new XBee64BitAddress(Arrays.copyOfRange(payload, index, index + 8));
097                index = index + 8;
098                
099                // 2 bytes of 16-bit address.
100                XBee16BitAddress sourceAddress16 = new XBee16BitAddress(payload[index] & 0xFF, payload[index + 1] & 0xFF);
101                index = index + 2;
102                
103                // Receive options
104                int receiveOptions = payload[index] & 0xFF;
105                index = index + 1;
106                
107                // Get data.
108                byte[] data = null;
109                if (index < payload.length)
110                        data = Arrays.copyOfRange(payload, index, payload.length);
111                
112                return new IODataSampleRxIndicatorPacket(sourceAddress64, sourceAddress16, receiveOptions, data);
113        }
114        
115        /**
116         * Class constructor. Instantiates a new 
117         * {@code IODataSampleRxIndicatorPacket} object with the given parameters.
118         * 
119         * @param sourceAddress64 64-bit address of the sender.
120         * @param sourceAddress16 16-bit address of the sender.
121         * @param receiveOptions Receive options.
122         * @param rfData Received RF data.
123         * 
124         * @throws IllegalArgumentException if {@code receiveOptions < 0} or
125         *                                  if {@code receiveOptions > 255} or
126         *                                  if {@code rfData.length < 5}.
127         * @throws NullPointerException if {@code sourceAddress64 == null} or 
128         *                              if {@code sourceAddress16 == null}.
129         * 
130         * @see com.digi.xbee.api.models.XBeeReceiveOptions
131         * @see com.digi.xbee.api.models.XBee16BitAddress
132         * @see com.digi.xbee.api.models.XBee64BitAddress 
133         */
134        public IODataSampleRxIndicatorPacket(XBee64BitAddress sourceAddress64, XBee16BitAddress sourceAddress16, int receiveOptions, byte[] rfData) {
135                super(APIFrameType.IO_DATA_SAMPLE_RX_INDICATOR);
136                
137                if (sourceAddress64 == null)
138                        throw new NullPointerException("64-bit source address cannot be null.");
139                if (sourceAddress16 == null)
140                        throw new NullPointerException("16-bit source address cannot be null.");
141                if (receiveOptions < 0 || receiveOptions > 255)
142                        throw new IllegalArgumentException("Receive options value must be between 0 and 255.");
143                
144                this.sourceAddress64 = sourceAddress64;
145                this.sourceAddress16 = sourceAddress16;
146                this.receiveOptions = receiveOptions;
147                this.rfData = rfData;
148                if (rfData != null)
149                        ioSample = new IOSample(rfData);
150                else
151                        ioSample = null;
152                this.logger = LoggerFactory.getLogger(RX64Packet.class);
153        }
154        
155        /*
156         * (non-Javadoc)
157         * @see com.digi.xbee.api.packet.XBeeAPIPacket#getAPIData()
158         */
159        @Override
160        protected byte[] getAPIPacketSpecificData() {
161                ByteArrayOutputStream os = new ByteArrayOutputStream();
162                try {
163                        os.write(sourceAddress64.getValue());
164                        os.write(sourceAddress16.getValue());
165                        os.write(receiveOptions);
166                        if (rfData != null)
167                                os.write(rfData);
168                } catch (IOException e) {
169                        logger.error(e.getMessage(), e);
170                }
171                return os.toByteArray();
172        }
173        
174        /*
175         * (non-Javadoc)
176         * @see com.digi.xbee.api.packet.XBeeAPIPacket#needsAPIFrameID()
177         */
178        @Override
179        public boolean needsAPIFrameID() {
180                return false;
181        }
182        
183        /*
184         * (non-Javadoc)
185         * @see com.digi.xbee.api.packet.XBeeAPIPacket#isBroadcast()
186         */
187        @Override
188        public boolean isBroadcast() {
189                return ByteUtils.isBitEnabled(getReceiveOptions(), 1);
190        }
191        
192        /**
193         * Returns the 64-bit sender/source address. 
194         * 
195         * @return The 64-bit sender/source address.
196         * 
197         * @see com.digi.xbee.api.models.XBee64BitAddress
198         */
199        public XBee64BitAddress get64bitSourceAddress() {
200                return sourceAddress64;
201        }
202        
203        /**
204         * Returns the 16-bit sender/source address. 
205         * 
206         * @return 16-bit sender/source address.
207         * 
208         * @see com.digi.xbee.api.models.XBee16BitAddress
209         */
210        public XBee16BitAddress get16bitSourceAddress() {
211                return sourceAddress16;
212        }
213        
214        /**
215         * Returns the receive options. 
216         * 
217         * @return Receive options.
218         * 
219         * @see com.digi.xbee.api.models.XBeeReceiveOptions
220         */
221        public int getReceiveOptions() {
222                return receiveOptions;
223        }
224        
225        /**
226         * Returns the IO sample corresponding to the data contained in the packet.
227         * 
228         * @return The IO sample of the packet, null if the packet has not any data 
229         *         or if the sample could not be generated correctly.
230         * 
231         * @see com.digi.xbee.api.io.IOSample
232         */
233        public IOSample getIOSample() {
234                return ioSample;
235        }
236        
237        /**
238         * Sets the received RF data.
239         * 
240         * @param rfData Received RF data.
241         */
242        public void setRFData(byte[] rfData) {
243                this.rfData = rfData;
244        }
245        
246        /**
247         * Returns the received RF data.
248         * 
249         * @return Received RF data.
250         */
251        public byte[] getRFData() {
252                return rfData;
253        }
254        
255        /*
256         * (non-Javadoc)
257         * @see com.digi.xbee.api.packet.XBeeAPIPacket#getAPIPacketParameters()
258         */
259        @Override
260        public LinkedHashMap<String, String> getAPIPacketParameters() {
261                LinkedHashMap<String, String> parameters = new LinkedHashMap<String, String>();
262                parameters.put("64-bit source address", HexUtils.prettyHexString(sourceAddress64.toString()));
263                parameters.put("16-bit source address", HexUtils.prettyHexString(sourceAddress16.toString()));
264                parameters.put("Receive options", HexUtils.prettyHexString(HexUtils.integerToHexString(receiveOptions, 1)));
265                if (ioSample != null) {
266                        parameters.put("Number of samples", HexUtils.prettyHexString(HexUtils.integerToHexString(1, 1))); // There is always 1 sample.
267                        parameters.put("Digital channel mask", HexUtils.prettyHexString(HexUtils.integerToHexString(ioSample.getDigitalMask(), 2)));
268                        parameters.put("Analog channel mask", HexUtils.prettyHexString(HexUtils.integerToHexString(ioSample.getAnalogMask(), 1)));
269                        for (int i = 0; i < 16; i++) {
270                                if (ioSample.hasDigitalValue(IOLine.getDIO(i)))
271                                        parameters.put(IOLine.getDIO(i).getName() + " digital value", ioSample.getDigitalValue(IOLine.getDIO(i)).getName());
272                        }
273                        for (int i = 0; i < 6; i++) {
274                                if (ioSample.hasAnalogValue(IOLine.getDIO(i)))
275                                        parameters.put(IOLine.getDIO(i).getName() + " analog value", HexUtils.prettyHexString(HexUtils.integerToHexString(ioSample.getAnalogValue(IOLine.getDIO(i)), 2)));
276                        }
277                        if (ioSample.hasPowerSupplyValue())
278                                try {
279                                        parameters.put("Power supply value", HexUtils.prettyHexString(HexUtils.integerToHexString(ioSample.getPowerSupplyValue(), 2)));
280                                } catch (OperationNotSupportedException e) { }
281                } else if (rfData != null)
282                        parameters.put("RF data", HexUtils.prettyHexString(HexUtils.byteArrayToHexString(rfData)));
283                return parameters;
284        }
285}