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