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.io;
013
014import java.util.HashMap;
015
016import com.digi.xbee.api.exceptions.OperationNotSupportedException;
017import com.digi.xbee.api.utils.ByteUtils;
018
019/**
020 * This class represents an IO Data Sample. The sample is built using the
021 * the constructor. The sample contains an analog and digital mask indicating 
022 * which IO lines are configured with that functionality.
023 * 
024 * <p>Depending on the protocol the XBee device is executing, the digital and 
025 * analog masks are retrieved in separated bytes (2 bytes for the digital 
026 * mask and 1 for the analog mask) or merged (digital and analog masks are 
027 * contained in the same 2 bytes).</p> 
028 * <br>
029 * <p><b>802.15.4 Protocol</b></p>
030 * <br>
031 * <p>Digital and analog channels masks</p>
032 * <p>------------------------------------------------------------------</p>
033 * <p>Indicates which digital and ADC IO lines are configured in the module. 
034 * Each bit corresponds to one digital or ADC IO line on the module:</p>
035 * <br>
036 * <BLOCKQUOTE>
037 *      <p>bit 0 =  DIO0</p>1
038 *      <p>bit 1 =  DIO1</p>0
039 *      <p>bit 2 =  DIO2</p>0
040 *      <p>bit 3 =  DIO3</p>1
041 *      <p>bit 4 =  DIO4</p>0
042 *      <p>bit 5 =  DIO5</p>1
043 *      <p>bit 6 =  DIO6</p>0
044 *      <p>bit 7 =  DIO7</p>0
045 *      <p>bit 8 =  DIO8</p>0
046 *      <p>bit 9 =  AD0</p>0
047 *      <p>bit 10 = AD1</p>1
048 *      <p>bit 11 = AD2</p>1
049 *      <p>bit 12 = AD3</p>0
050 *      <p>bit 13 = AD4</p>0
051 *      <p>bit 14 = AD5</p>0
052 *      <p>bit 15 = N/A</p>0
053 * <br>
054 *      <p>Example: mask of {@code 0x0C29} means DIO0, DIO3, DIO5, AD1 and 
055 *      AD2 enabled.</p>
056 *      <p>0 0 0 0 1 1 0 0 0 0 1 0 1 0 0 1</p>
057 * </BLOCKQUOTE>
058 * <br><br>
059 * <p><b>Other Protocols</b></p>
060 * <br>
061 * <p>Digital Channel Mask</p>
062 * <p>------------------------------------------------------------------</p>
063 * <p>Indicates which digital IO lines are configured in the module. Each bit 
064 * corresponds to one digital IO line on the module:</p>
065 * <br>
066 * <BLOCKQUOTE>
067 *      <p>bit 0 =  DIO0/AD0</p>
068 *      <p>bit 1 =  DIO1/AD1</p> 
069 *      <p>bit 2 =  DIO2/AD2</p>
070 *      <p>bit 3 =  DIO3/AD3</p>
071 *      <p>bit 4 =  DIO4/AD4</p>
072 *      <p>bit 5 =  DIO5/AD5/ASSOC</p>
073 *      <p>bit 6 =  DIO6/RTS</p>
074 *      <p>bit 7 =  DIO7/CTS</p>
075 *      <p>bit 8 =  DIO8/DTR/SLEEP_RQ</p>
076 *      <p>bit 9 =  DIO9/ON_SLEEP</p>
077 *      <p>bit 10 = DIO10/PWM0/RSSI</p>
078 *      <p>bit 11 = DIO11/PWM1</p>
079 *      <p>bit 12 = DIO12/CD</p>
080 *      <p>bit 13 = DIO13</p>
081 *      <p>bit 14 = DIO14</p>
082 *      <p>bit 15 = N/A</p>
083 * <br>
084 *      <p>Example: mask of {@code 0x040B} means DIO0, DIO1, DIO2, DIO3 and 
085 *      DIO10 enabled.</p>
086 *      <p>0 0 0 0 0 1 0 0 0 0 0 0 1 0 1 1</p>
087 * <br><br>
088 * </BLOCKQUOTE>
089 * <p>Analog Channel Mask</p>
090 * <p>-----------------------------------------------------------------------</p>
091 * <p>Indicates which lines are configured as ADC. Each bit in the analog 
092 * channel mask corresponds to one ADC line on the module.</p>
093 * <br>
094 * <BLOCKQUOTE>
095 *      <p>bit 0 = AD0/DIO0</p>
096 *      <p>bit 1 = AD1/DIO1</p>
097 *      <p>bit 2 = AD2/DIO2</p>
098 *      <p>bit 3 = AD3/DIO3</p>
099 *      <p>bit 4 = AD4/DIO4</p>
100 *      <p>bit 5 = AD5/DIO5/ASSOC</p>
101 *      <p>bit 6 = N/A</p>
102 *      <p>bit 7 = Supply Voltage Value</p>
103 * <br>
104 *      <p>Example: mask of {@code 0x03} means AD0, and AD1 enabled.</p>
105 *      <p>0 0 0 0 0 0 1 1</p>
106 * </BLOCKQUOTE>
107 */
108public class IOSample {
109        
110        // Variables.
111        private final byte[] ioSamplePayload;
112        
113        private int digitalHSBMask;
114        private int digitalLSBMask;
115        private int digitalMask;
116        private int analogMask;
117        private int digitalHSBValues;
118        private int digitalLSBValues;
119        private int digitalValues;
120        private int powerSupplyVoltage;
121        
122        private final HashMap<IOLine, Integer> analogValuesMap = new HashMap<IOLine, Integer>();
123        private final HashMap<IOLine, IOValue> digitalValuesMap = new HashMap<IOLine, IOValue>();
124        
125        /**
126         * Class constructor. Instantiates a new object of type {@code IOSample} 
127         * with the given IO sample payload.
128         * 
129         * @param ioSamplePayload The payload corresponding to an IO sample.
130         * 
131         * @throws IllegalArgumentException if {@code ioSamplePayload.length < 5}.
132         * @throws NullPointerException if {@code ioSamplePayload == null}.
133         */
134        public IOSample(byte[] ioSamplePayload) {
135                if (ioSamplePayload == null)
136                        throw new NullPointerException("IO sample payload cannot be null.");
137                
138                if (ioSamplePayload.length < 5)
139                        throw new IllegalArgumentException("IO sample payload must be longer than 4.");
140                
141                this.ioSamplePayload = ioSamplePayload;
142                if (ioSamplePayload.length % 2 != 0)
143                        parseRawIOSample();
144                else
145                        parseIOSample();
146        }
147        
148        /**
149         * Parses the information contained in the IO sample bytes reading the 
150         * value of each configured DIO and ADC.
151         */
152        private void parseRawIOSample() {
153                int dataIndex = 3;
154                
155                // Obtain the digital mask.                 // Available digital IOs in 802.15.4
156                digitalHSBMask = ioSamplePayload[1] & 0x01;     // 0 0 0 0 0 0 0 1
157                digitalLSBMask = ioSamplePayload[2] & 0xFF;     // 1 1 1 1 1 1 1 1
158                // Combine the masks.
159                digitalMask = (digitalHSBMask << 8) + digitalLSBMask;
160                // Obtain the analog mask.                                                          // Available analog IOs in 802.15.4
161                analogMask = ((ioSamplePayload[1] << 8) + (ioSamplePayload[2] & 0xFF)) & 0x7E00;        // 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0
162                
163                // Read the digital values (if any). There are 9 possible digital lines in 
164                // 802.15.4 protocol. The digital mask indicates if there is any digital 
165                // line enabled to read its value. If 0, no digital values are received.
166                if (digitalMask > 0) {
167                        // Obtain the digital values.
168                        digitalHSBValues = ioSamplePayload[3] & 0x7F;
169                        digitalLSBValues = ioSamplePayload[4] & 0xFF;
170                        // Combine the values.
171                        digitalValues = (digitalHSBValues << 8) + digitalLSBValues;
172                        
173                        for (int i = 0; i < 16; i++) {
174                                if (!ByteUtils.isBitEnabled(digitalMask, i))
175                                        continue;
176                                if (ByteUtils.isBitEnabled(digitalValues, i))
177                                        digitalValuesMap.put(IOLine.getDIO(i), IOValue.HIGH);
178                                else
179                                        digitalValuesMap.put(IOLine.getDIO(i), IOValue.LOW);
180                        }
181                        // Increase the data index to read the analog values.
182                        dataIndex += 2;
183                }
184                
185                // Read the analog values (if any). There are 6 possible analog lines.
186                // The analog mask indicates if there is any analog line enabled to read 
187                // its value. If 0, no analog values are received.
188                int adcIndex = 9;
189                while ((ioSamplePayload.length - dataIndex) > 1 && adcIndex < 16) {
190                        if (!ByteUtils.isBitEnabled(analogMask, adcIndex)) {
191                                adcIndex += 1;
192                                continue;
193                        }
194                        // 802.15.4 protocol does not provide power supply value, so get just the ADC data.
195                        analogValuesMap.put(IOLine.getDIO(adcIndex - 9), ((ioSamplePayload[dataIndex] & 0xFF) << 8) + (ioSamplePayload[dataIndex + 1] & 0xFF));
196                        // Increase the data index to read the next analog values.
197                        dataIndex += 2;
198                        adcIndex += 1;
199                }
200        }
201        
202        /**
203         * Parses the information contained in the IO sample bytes reading the 
204         * value of each configured DIO and ADC.
205         */
206        private void parseIOSample() {
207                int dataIndex = 4;
208                
209                // Obtain the digital masks.                // Available digital IOs
210                digitalHSBMask = ioSamplePayload[1] & 0x7F;     // 0 1 1 1 1 1 1 1
211                digitalLSBMask = ioSamplePayload[2] & 0xFF;     // 1 1 1 1 1 1 1 1
212                // Combine the masks.
213                digitalMask = (digitalHSBMask << 8) + digitalLSBMask;
214                // Obtain the analog mask.                  // Available analog IOs
215                analogMask = ioSamplePayload[3] & 0xBF;         // 1 0 1 1 1 1 1 1
216                
217                // Read the digital values (if any). There are 16 possible digital lines.
218                // The digital mask indicates if there is any digital line enabled to read 
219                // its value. If 0, no digital values are received.
220                if (digitalMask > 0) {
221                        // Obtain the digital values.
222                        digitalHSBValues = ioSamplePayload[4] & 0x7F;
223                        digitalLSBValues = ioSamplePayload[5] & 0xFF;
224                        // Combine the values.
225                        digitalValues = (digitalHSBValues << 8) + digitalLSBValues;
226                        
227                        for (int i = 0; i < 16; i++) {
228                                if (!ByteUtils.isBitEnabled(digitalMask, i))
229                                        continue;
230                                if (ByteUtils.isBitEnabled(digitalValues, i))
231                                        digitalValuesMap.put(IOLine.getDIO(i), IOValue.HIGH);
232                                else
233                                        digitalValuesMap.put(IOLine.getDIO(i), IOValue.LOW);
234                        }
235                        // Increase the data index to read the analog values.
236                        dataIndex += 2;
237                }
238                
239                // Read the analog values (if any). There are 6 possible analog lines.
240                // The analog mask indicates if there is any analog line enabled to read 
241                // its value. If 0, no analog values are received.
242                int adcIndex = 0;
243                while ((ioSamplePayload.length - dataIndex) > 1 && adcIndex < 8) {
244                        if (!ByteUtils.isBitEnabled(analogMask, adcIndex)) {
245                                adcIndex += 1;
246                                continue;
247                        }
248                        // When analog index is 7, it means that the analog value corresponds to the power 
249                        // supply voltage, therefore this value should be stored in a different value.
250                        if (adcIndex == 7)
251                                powerSupplyVoltage = ((ioSamplePayload[dataIndex] & 0xFF) << 8) + (ioSamplePayload[dataIndex + 1] & 0xFF);
252                        else
253                                analogValuesMap.put(IOLine.getDIO(adcIndex), ((ioSamplePayload[dataIndex] & 0xFF) << 8) + (ioSamplePayload[dataIndex + 1] & 0xFF));
254                        // Increase the data index to read the next analog values.
255                        dataIndex += 2;
256                        adcIndex += 1;
257                }
258        }
259        
260        /**
261         * Returns the HSB of the digital mask.
262         * 
263         * @return HSB of the digital mask.
264         * 
265         * @see #getDigitalLSBMask()
266         * @see #getDigitalMask()
267         */
268        public int getDigitalHSBMask() {
269                return digitalHSBMask;
270        }
271        
272        /**
273         * Returns the LSB of the digital mask.
274         * 
275         * @return LSB of the digital mask.
276         * 
277         * @see #getDigitalHSBMask()
278         * @see #getDigitalMask()
279         */
280        public int getDigitalLSBMask() {
281                return digitalLSBMask;
282        }
283        
284        /**
285         * Returns the combined (HSB + LSB) digital mask.
286         * 
287         * @return The combined digital mask.
288         * 
289         * @see #getDigitalLSBMask()
290         * @see #getDigitalHSBMask()
291         */
292        public int getDigitalMask() {
293                return digitalMask;
294        }
295        
296        /**
297         * Checks whether or not the {@code IOSample} has digital values.
298         * 
299         * @return {@code true} if there are digital values, {@code false} 
300         *         otherwise.
301         */
302        public boolean hasDigitalValues() {
303                return digitalValuesMap.size() > 0;
304        }
305        
306        /**
307         * Returns whether or not this IO sample contains a digital value for 
308         * the given IO line.
309         * 
310         * @param ioLine The IO line to check if has a digital value.
311         * 
312         * @return {@code true} if the given IO line has a digital value, 
313         *         {@code false} otherwise.
314         * 
315         * @see #hasDigitalValues()
316         * @see IOLine
317         */
318        public boolean hasDigitalValue(IOLine ioLine) {
319                return digitalValuesMap.containsKey(ioLine);
320        }
321        
322        /**
323         * Returns the digital values map.
324         * 
325         * <p>To verify if this sample contains a valid digital values, use the 
326         * method {@code hasDigitalValues()}.</p>
327         * 
328         * <pre>
329         * {@code
330         * if (ioSample.hasDigitalValues()) {
331         *     HashMap<IOLine, IOValue> values = ioSample.getDigitalValues();
332         *     ...
333         * } else {
334         *     ...
335         * }
336         * }
337         * </pre>
338         * 
339         * @return {@code HashMap} with the digital value of each configured IO 
340         *         line.
341         * 
342         * @see #getDigitalValue(IOLine)
343         * @see #hasDigitalValues()
344         * @see IOLine
345         * @see IOValue
346         */
347        public HashMap<IOLine, IOValue> getDigitalValues() {
348                return digitalValuesMap;
349        }
350        
351        /**
352         * Returns the digital value of the provided IO line.
353         * 
354         * <p>To verify if this sample contains a digital value for the given 
355         * {@code IOLine}, use the method {@code hasDigitalValue(IOLine)}.</p>
356         * 
357         * <pre>
358         * {@code
359         * if (ioSample.hasDigitalValue(IOLine.DIO0_AD0)) {
360         *     IOValue value = ioSample.getDigitalValue(IOLine.DIO0_AD0);
361         *     ...
362         * } else {
363         *     ...
364         * }
365         * }
366         * </pre>
367         * 
368         * @param ioLine The IO line to get its digital value.
369         * 
370         * @return The {@code IOValue} of the given IO line or {@code null} if the
371         *         IO sample does not contain a digital value for the given IO line.
372         * 
373         * @see #getDigitalValues()
374         * @see #hasDigitalValues()
375         * @see IOLine
376         * @see IOValue
377         */
378        public IOValue getDigitalValue(IOLine ioLine) {
379                if (!digitalValuesMap.containsKey(ioLine))
380                        return null;
381                return digitalValuesMap.get(ioLine);
382        }
383        
384        /**
385         * Returns the analog mask.
386         * 
387         * @return Analog mask.
388         */
389        public int getAnalogMask() {
390                return analogMask;
391        }
392        
393        /**
394         * Returns whether or not the {@code IOSample} has analog values.
395         *  
396         * @return {@code true} if there are analog values, {@code false} otherwise.
397         * 
398         * @see #getAnalogValue(IOLine)
399         * @see #getAnalogValues()
400         * @see #hasAnalogValue(IOLine)
401         * @see IOLine
402         */
403        public boolean hasAnalogValues() {
404                return analogValuesMap.size() > 0;
405        }
406        
407        /**
408         * Returns whether or not the given IO line has an analog value.
409         * 
410         * @param ioLine The IO line to check if has an analog value.
411         * 
412         * @return {@code true} if the given IO line has an analog value, 
413         *         {@code false} otherwise.
414         * 
415         * @see #getAnalogValue(IOLine)
416         * @see #getAnalogValues()
417         * @see #hasAnalogValues()
418         * @see IOLine
419         */
420        public boolean hasAnalogValue(IOLine ioLine) {
421                return analogValuesMap.containsKey(ioLine);
422        }
423        
424        /**
425         * Returns the analog values map.
426         * 
427         * <p>To verify if this sample contains a valid analog values, use the 
428         * method {@code hasAnalogValues()}.</p>
429         * 
430         * <pre>
431         * {@code
432         * if (ioSample.hasAnalogValues()) {
433         *     HashMap<IOLine, Integer> values = ioSample.getAnalogValues();
434         *     ...
435         * } else {
436         *     ...
437         * }
438         * }
439         * </pre>
440         * 
441         * @return {@code HashMap} with the analog value of each configured IO 
442         *         line.
443         * 
444         * @see #getAnalogValue(IOLine)
445         * @see #hasAnalogValue(IOLine)
446         * @see #hasAnalogValues()
447         * @see IOLine
448         */
449        public HashMap<IOLine, Integer> getAnalogValues() {
450                return analogValuesMap;
451        }
452        
453        /**
454         * Returns the analog value of the provided IO line.
455         * 
456         * <p>To verify if this sample contains an analog value for the given 
457         * {@code IOLine}, use the method {@code hasAnalogValue(IOLine)}.</p>
458         * 
459         * <pre>
460         * {@code
461         * if (ioSample.hasAnalogValue(IOLine.DIO0_AD0)) {
462         *     Integer value = ioSample.getAnalogValue(IOLine.DIO0_AD0);
463         *     ...
464         * } else {
465         *     ...
466         * }
467         * }
468         * </pre>
469         * 
470         * @param ioLine The IO line to get its analog value.
471         * 
472         * @return The analog value of the given IO line or {@code null} if the
473         *         IO sample does not contain an analog value for the given IO line.
474         * 
475         * @see #getAnalogValues()
476         * @see #hasAnalogValue(IOLine)
477         * @see #hasAnalogValues()
478         * @see IOLine
479         */
480        public Integer getAnalogValue(IOLine ioLine) {
481                if (!analogValuesMap.containsKey(ioLine))
482                        return null;
483                return analogValuesMap.get(ioLine);
484        }
485        
486        /**
487         * Returns whether or not the IOSample has power supply value.
488         * 
489         * @return {@code true} if the IOSample has power supply value, 
490         *         {@code false} otherwise.
491         * 
492         * @see #getPowerSupplyValue()
493         */
494        public boolean hasPowerSupplyValue() {
495                return ByteUtils.isBitEnabled(analogMask, 7);
496        }
497        
498        /**
499         * Returns the value of the power supply voltage.
500         * 
501         * <p>To verify if this sample contains the power supply voltage, use the 
502         * method {@code hasPowerSupplyValue()}.</p>
503         * 
504         * <pre>
505         * {@code
506         * if (ioSample.hasPowerSupplyValue()) {
507         *     int value = ioSample.getPowerSupplyValue();
508         *     ...
509         * } else {
510         *     ...
511         * }
512         * }
513         * </pre>
514         * 
515         * @return The value of the power supply voltage.
516         * 
517         * @throws OperationNotSupportedException if the IOSample does not have 
518         *         power supply value.
519         * 
520         * @see #hasPowerSupplyValue()
521         */
522        public int getPowerSupplyValue() throws OperationNotSupportedException {
523                if (!ByteUtils.isBitEnabled(analogMask, 7))
524                        throw new OperationNotSupportedException();
525                return powerSupplyVoltage;
526        }
527        
528        /*
529         * (non-Javadoc)
530         * @see java.lang.Object#toString()
531         */
532        @Override
533        public String toString() {
534                StringBuilder sb = new StringBuilder("{");
535                if (hasDigitalValues()) {
536                        for (IOLine line : digitalValuesMap.keySet()) {
537                                sb.append("[").append(line).append(": ").append(digitalValuesMap.get(line)).append("], ");
538                        }
539                }
540                if (hasAnalogValues()) {
541                        for (IOLine line : analogValuesMap.keySet()) {
542                                sb.append("[").append(line).append(": ").append(analogValuesMap.get(line)).append("], ");
543                        }
544                }
545                if (hasPowerSupplyValue()) {
546                        try {
547                                sb.append("[").append("Power supply voltage: ").append(getPowerSupplyValue()).append("], ");
548                        } catch (OperationNotSupportedException e) {}
549                }
550                
551                String s = sb.toString();
552                if (s.endsWith(", "))
553                        s = s.substring(0, s.length() - 2);
554                return s + "}";
555        }
556}