    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]) & 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}