001/**
002 * Copyright 2017-2019, Digi International Inc.
003 *
004 * This Source Code Form is subject to the terms of the Mozilla Public
005 * License, v. 2.0. If a copy of the MPL was not distributed with this
006 * file, you can obtain one at http://mozilla.org/MPL/2.0/.
007 *
008 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 
009 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 
010 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 
011 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 
012 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 
013 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 
014 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
015 */
016package com.digi.xbee.api.io;
017
018import java.io.ByteArrayOutputStream;
019import java.io.IOException;
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.HashMap;
023
024import com.digi.xbee.api.exceptions.OperationNotSupportedException;
025import com.digi.xbee.api.utils.ByteUtils;
026
027/**
028 * This class represents an IO Data Sample. The sample is built using the
029 * the constructor. The sample contains an analog and digital mask indicating 
030 * which IO lines are configured with that functionality.
031 * 
032 * <p>Depending on the protocol the XBee device is executing, the digital and 
033 * analog masks are retrieved in separated bytes (2 bytes for the digital 
034 * mask and 1 for the analog mask) or merged (digital and analog masks are 
035 * contained in the same 2 bytes).</p> 
036 * <br>
037 * <p><b>802.15.4 Protocol</b></p>
038 * <br>
039 * <p>Digital and analog channels masks</p>
040 * <p>------------------------------------------------------------------</p>
041 * <p>Indicates which digital and ADC IO lines are configured in the module. 
042 * Each bit corresponds to one digital or ADC IO line on the module:</p>
043 * <br>
044 * <BLOCKQUOTE>
045 *      <p>bit 0 =  DIO0</p>1
046 *      <p>bit 1 =  DIO1</p>0
047 *      <p>bit 2 =  DIO2</p>0
048 *      <p>bit 3 =  DIO3</p>1
049 *      <p>bit 4 =  DIO4</p>0
050 *      <p>bit 5 =  DIO5</p>1
051 *      <p>bit 6 =  DIO6</p>0
052 *      <p>bit 7 =  DIO7</p>0
053 *      <p>bit 8 =  DIO8</p>0
054 *      <p>bit 9 =  AD0</p>0
055 *      <p>bit 10 = AD1</p>1
056 *      <p>bit 11 = AD2</p>1
057 *      <p>bit 12 = AD3</p>0
058 *      <p>bit 13 = AD4</p>0
059 *      <p>bit 14 = AD5</p>0
060 *      <p>bit 15 = N/A</p>0
061 * <br>
062 *      <p>Example: mask of {@code 0x0C29} means DIO0, DIO3, DIO5, AD1 and 
063 *      AD2 enabled.</p>
064 *      <p>0 0 0 0 1 1 0 0 0 0 1 0 1 0 0 1</p>
065 * </BLOCKQUOTE>
066 * <br><br>
067 * <p><b>Other Protocols</b></p>
068 * <br>
069 * <p>Digital Channel Mask</p>
070 * <p>------------------------------------------------------------------</p>
071 * <p>Indicates which digital IO lines are configured in the module. Each bit 
072 * corresponds to one digital IO line on the module:</p>
073 * <br>
074 * <BLOCKQUOTE>
075 *      <p>bit 0 =  DIO0/AD0</p>
076 *      <p>bit 1 =  DIO1/AD1</p> 
077 *      <p>bit 2 =  DIO2/AD2</p>
078 *      <p>bit 3 =  DIO3/AD3</p>
079 *      <p>bit 4 =  DIO4/AD4</p>
080 *      <p>bit 5 =  DIO5/AD5/ASSOC</p>
081 *      <p>bit 6 =  DIO6/RTS</p>
082 *      <p>bit 7 =  DIO7/CTS</p>
083 *      <p>bit 8 =  DIO8/DTR/SLEEP_RQ</p>
084 *      <p>bit 9 =  DIO9/ON_SLEEP</p>
085 *      <p>bit 10 = DIO10/PWM0/RSSI</p>
086 *      <p>bit 11 = DIO11/PWM1</p>
087 *      <p>bit 12 = DIO12/CD</p>
088 *      <p>bit 13 = DIO13</p>
089 *      <p>bit 14 = DIO14</p>
090 *      <p>bit 15 = N/A</p>
091 * <br>
092 *      <p>Example: mask of {@code 0x040B} means DIO0, DIO1, DIO2, DIO3 and 
093 *      DIO10 enabled.</p>
094 *      <p>0 0 0 0 0 1 0 0 0 0 0 0 1 0 1 1</p>
095 * <br><br>
096 * </BLOCKQUOTE>
097 * <p>Analog Channel Mask</p>
098 * <p>-----------------------------------------------------------------------</p>
099 * <p>Indicates which lines are configured as ADC. Each bit in the analog 
100 * channel mask corresponds to one ADC line on the module.</p>
101 * <br>
102 * <BLOCKQUOTE>
103 *      <p>bit 0 = AD0/DIO0</p>
104 *      <p>bit 1 = AD1/DIO1</p>
105 *      <p>bit 2 = AD2/DIO2</p>
106 *      <p>bit 3 = AD3/DIO3</p>
107 *      <p>bit 4 = AD4/DIO4</p>
108 *      <p>bit 5 = AD5/DIO5/ASSOC</p>
109 *      <p>bit 6 = N/A</p>
110 *      <p>bit 7 = Supply Voltage Value</p>
111 * <br>
112 *      <p>Example: mask of {@code 0x03} means AD0, and AD1 enabled.</p>
113 *      <p>0 0 0 0 0 0 1 1</p>
114 * </BLOCKQUOTE>
115 */
116public class IOSample {
117        
118        // Variables.
119        private final byte[] ioSamplePayload;
120        
121        private ByteArrayOutputStream rawMask;
122        private ByteArrayOutputStream rawSample;
123        
124        private int digitalHSBMask;
125        private int digitalLSBMask;
126        private int digitalMask;
127        private int analogMask;
128        private int digitalHSBValues;
129        private int digitalLSBValues;
130        private int digitalValues;
131        private int powerSupplyVoltage;
132        
133        private final HashMap<IOLine, Integer> analogValuesMap = new HashMap<IOLine, Integer>();
134        private final HashMap<IOLine, IOValue> digitalValuesMap = new HashMap<IOLine, IOValue>();
135        
136        /**
137         * Class constructor. Instantiates a new object of type {@code IOSample} 
138         * with the given IO sample payload.
139         * 
140         * @param ioSamplePayload The payload corresponding to an IO sample.
141         * 
142         * @throws IllegalArgumentException if {@code ioSamplePayload.length < 5}.
143         * @throws NullPointerException if {@code ioSamplePayload == null}.
144         */
145        public IOSample(byte[] ioSamplePayload) {
146                if (ioSamplePayload == null)
147                        throw new NullPointerException("IO sample payload cannot be null.");
148                
149                if (ioSamplePayload.length < 5)
150                        throw new IllegalArgumentException("IO sample payload must be longer than 4.");
151                
152                this.ioSamplePayload = ioSamplePayload;
153                rawMask = new ByteArrayOutputStream();
154                rawSample = new ByteArrayOutputStream();
155                if (ioSamplePayload.length % 2 != 0)
156                        parseRawIOSample();
157                else
158                        parseIOSample();
159        }
160        
161        /**
162         * Parses the information contained in the IO sample bytes reading the 
163         * value of each configured DIO and ADC.
164         */
165        private void parseRawIOSample() {
166                int dataIndex = 3;
167                
168                // Obtain the digital mask.                 // Available digital IOs in 802.15.4
169                digitalHSBMask = ioSamplePayload[1] & 0x01;     // 0 0 0 0 0 0 0 1
170                rawMask.write(ioSamplePayload[1] & 0xFF);
171                digitalLSBMask = ioSamplePayload[2] & 0xFF;     // 1 1 1 1 1 1 1 1
172                rawMask.write(ioSamplePayload[2] & 0xFF);
173                // Combine the masks.
174                digitalMask = (digitalHSBMask << 8) + digitalLSBMask;
175                // Obtain the analog mask.                                                          // Available analog IOs in 802.15.4
176                analogMask = (((ioSamplePayload[1] & 0xFF) << 8) + (ioSamplePayload[2] & 0xFF)) & 0x7E00;       // 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0
177                
178                // Read the digital values (if any). There are 9 possible digital lines in 
179                // 802.15.4 protocol. The digital mask indicates if there is any digital 
180                // line enabled to read its value. If 0, no digital values are received.
181                if (digitalMask > 0) {
182                        // Obtain the digital values.
183                        digitalHSBValues = ioSamplePayload[3] & 0x7F;
184                        rawSample.write(ioSamplePayload[3] & 0xFF);
185                        digitalLSBValues = ioSamplePayload[4] & 0xFF;
186                        rawSample.write(ioSamplePayload[4] & 0xFF);
187                        // Combine the values.
188                        digitalValues = (digitalHSBValues << 8) + digitalLSBValues;
189                        
190                        for (int i = 0; i < 16; i++) {
191                                if (!ByteUtils.isBitEnabled(digitalMask, i))
192                                        continue;
193                                if (ByteUtils.isBitEnabled(digitalValues, i))
194                                        digitalValuesMap.put(IOLine.getDIO(i), IOValue.HIGH);
195                                else
196                                        digitalValuesMap.put(IOLine.getDIO(i), IOValue.LOW);
197                        }
198                        // Increase the data index to read the analog values.
199                        dataIndex += 2;
200                }
201                
202                // Read the analog values (if any). There are 6 possible analog lines.
203                // The analog mask indicates if there is any analog line enabled to read 
204                // its value. If 0, no analog values are received.
205                int adcIndex = 9;
206                while ((ioSamplePayload.length - dataIndex) > 1 && adcIndex < 16) {
207                        if (!ByteUtils.isBitEnabled(analogMask, adcIndex)) {
208                                adcIndex += 1;
209                                continue;
210                        }
211                        // 802.15.4 protocol does not provide power supply value, so get just the ADC data.
212                        analogValuesMap.put(IOLine.getDIO(adcIndex - 9), ((ioSamplePayload[dataIndex] & 0xFF) << 8) + (ioSamplePayload[dataIndex + 1] & 0xFF));
213                        rawSample.write(ioSamplePayload[dataIndex] & 0xFF);
214                        rawSample.write(ioSamplePayload[dataIndex + 1] & 0xFF);
215                        // Increase the data index to read the next analog values.
216                        dataIndex += 2;
217                        adcIndex += 1;
218                }
219        }
220        
221        /**
222         * Parses the information contained in the IO sample bytes reading the 
223         * value of each configured DIO and ADC.
224         */
225        private void parseIOSample() {
226                int dataIndex = 4;
227                
228                // Obtain the digital masks.                // Available digital IOs
229                digitalHSBMask = ioSamplePayload[1] & 0x7F;     // 0 1 1 1 1 1 1 1
230                rawMask.write(ioSamplePayload[1] & 0xFF);
231                digitalLSBMask = ioSamplePayload[2] & 0xFF;     // 1 1 1 1 1 1 1 1
232                rawMask.write(ioSamplePayload[2] & 0xFF);
233                // Combine the masks.
234                digitalMask = (digitalHSBMask << 8) + digitalLSBMask;
235                // Obtain the analog mask.                  // Available analog IOs
236                analogMask = ioSamplePayload[3] & 0xBF;         // 1 0 1 1 1 1 1 1
237                rawMask.write(ioSamplePayload[3] & 0xFF);
238                
239                // Read the digital values (if any). There are 16 possible digital lines.
240                // The digital mask indicates if there is any digital line enabled to read 
241                // its value. If 0, no digital values are received.
242                if (digitalMask > 0) {
243                        // Obtain the digital values.
244                        digitalHSBValues = ioSamplePayload[4] & 0x7F;
245                        rawSample.write(ioSamplePayload[4] & 0xFF);
246                        digitalLSBValues = ioSamplePayload[5] & 0xFF;
247                        rawSample.write(ioSamplePayload[5] & 0xFF);
248                        // Combine the values.
249                        digitalValues = (digitalHSBValues << 8) + digitalLSBValues;
250                        
251                        for (int i = 0; i < 16; i++) {
252                                if (!ByteUtils.isBitEnabled(digitalMask, i))
253                                        continue;
254                                if (ByteUtils.isBitEnabled(digitalValues, i))
255                                        digitalValuesMap.put(IOLine.getDIO(i), IOValue.HIGH);
256                                else
257                                        digitalValuesMap.put(IOLine.getDIO(i), IOValue.LOW);
258                        }
259                        // Increase the data index to read the analog values.
260                        dataIndex += 2;
261                }
262                
263                // Read the analog values (if any). There are 6 possible analog lines.
264                // The analog mask indicates if there is any analog line enabled to read 
265                // its value. If 0, no analog values are received.
266                int adcIndex = 0;
267                while ((ioSamplePayload.length - dataIndex) > 1 && adcIndex < 8) {
268                        if (!ByteUtils.isBitEnabled(analogMask, adcIndex)) {
269                                adcIndex += 1;
270                                continue;
271                        }
272                        // When analog index is 7, it means that the analog value corresponds to the power 
273                        // supply voltage, therefore this value should be stored in a different value.
274                        if (adcIndex == 7)
275                                powerSupplyVoltage = ((ioSamplePayload[dataIndex] & 0xFF) << 8) + (ioSamplePayload[dataIndex + 1] & 0xFF);
276                        else
277                                analogValuesMap.put(IOLine.getDIO(adcIndex), ((ioSamplePayload[dataIndex] & 0xFF) << 8) + (ioSamplePayload[dataIndex + 1] & 0xFF));
278                        rawSample.write(ioSamplePayload[dataIndex] & 0xFF);
279                        rawSample.write(ioSamplePayload[dataIndex + 1] & 0xFF);
280                        // Increase the data index to read the next analog values.
281                        dataIndex += 2;
282                        adcIndex += 1;
283                }
284        }
285        
286        /**
287         * Returns the HSB of the digital mask.
288         * 
289         * @return HSB of the digital mask.
290         * 
291         * @see #getDigitalLSBMask()
292         * @see #getDigitalMask()
293         */
294        public int getDigitalHSBMask() {
295                return digitalHSBMask;
296        }
297        
298        /**
299         * Returns the LSB of the digital mask.
300         * 
301         * @return LSB of the digital mask.
302         * 
303         * @see #getDigitalHSBMask()
304         * @see #getDigitalMask()
305         */
306        public int getDigitalLSBMask() {
307                return digitalLSBMask;
308        }
309        
310        /**
311         * Returns the combined (HSB + LSB) digital mask.
312         * 
313         * @return The combined digital mask.
314         * 
315         * @see #getDigitalLSBMask()
316         * @see #getDigitalHSBMask()
317         */
318        public int getDigitalMask() {
319                return digitalMask;
320        }
321        
322        /**
323         * Checks whether or not the {@code IOSample} has digital values.
324         * 
325         * @return {@code true} if there are digital values, {@code false} 
326         *         otherwise.
327         */
328        public boolean hasDigitalValues() {
329                return digitalValuesMap.size() > 0;
330        }
331        
332        /**
333         * Returns whether or not this IO sample contains a digital value for 
334         * the given IO line.
335         * 
336         * @param ioLine The IO line to check if has a digital value.
337         * 
338         * @return {@code true} if the given IO line has a digital value, 
339         *         {@code false} otherwise.
340         * 
341         * @see #hasDigitalValues()
342         * @see IOLine
343         */
344        public boolean hasDigitalValue(IOLine ioLine) {
345                return digitalValuesMap.containsKey(ioLine);
346        }
347        
348        /**
349         * Returns the digital values map.
350         * 
351         * <p>To verify if this sample contains a valid digital values, use the 
352         * method {@code hasDigitalValues()}.</p>
353         * 
354         * <pre>
355         * {@code
356         * if (ioSample.hasDigitalValues()) {
357         *     HashMap<IOLine, IOValue> values = ioSample.getDigitalValues();
358         *     ...
359         * } else {
360         *     ...
361         * }
362         * }
363         * </pre>
364         * 
365         * @return {@code HashMap} with the digital value of each configured IO 
366         *         line.
367         * 
368         * @see #getDigitalValue(IOLine)
369         * @see #hasDigitalValues()
370         * @see IOLine
371         * @see IOValue
372         */
373        public HashMap<IOLine, IOValue> getDigitalValues() {
374                return (HashMap<IOLine, IOValue>) digitalValuesMap.clone();
375        }
376        
377        /**
378         * Returns the digital value of the provided IO line.
379         * 
380         * <p>To verify if this sample contains a digital value for the given 
381         * {@code IOLine}, use the method {@code hasDigitalValue(IOLine)}.</p>
382         * 
383         * <pre>
384         * {@code
385         * if (ioSample.hasDigitalValue(IOLine.DIO0_AD0)) {
386         *     IOValue value = ioSample.getDigitalValue(IOLine.DIO0_AD0);
387         *     ...
388         * } else {
389         *     ...
390         * }
391         * }
392         * </pre>
393         * 
394         * @param ioLine The IO line to get its digital value.
395         * 
396         * @return The {@code IOValue} of the given IO line or {@code null} if the
397         *         IO sample does not contain a digital value for the given IO line.
398         * 
399         * @see #getDigitalValues()
400         * @see #hasDigitalValues()
401         * @see IOLine
402         * @see IOValue
403         */
404        public IOValue getDigitalValue(IOLine ioLine) {
405                if (!digitalValuesMap.containsKey(ioLine))
406                        return null;
407                return digitalValuesMap.get(ioLine);
408        }
409        
410        /**
411         * Returns the analog mask.
412         * 
413         * @return Analog mask.
414         */
415        public int getAnalogMask() {
416                return analogMask;
417        }
418        
419        /**
420         * Returns whether or not the {@code IOSample} has analog values.
421         *  
422         * @return {@code true} if there are analog values, {@code false} otherwise.
423         * 
424         * @see #getAnalogValue(IOLine)
425         * @see #getAnalogValues()
426         * @see #hasAnalogValue(IOLine)
427         * @see IOLine
428         */
429        public boolean hasAnalogValues() {
430                return analogValuesMap.size() > 0;
431        }
432        
433        /**
434         * Returns whether or not the given IO line has an analog value.
435         * 
436         * @param ioLine The IO line to check if has an analog value.
437         * 
438         * @return {@code true} if the given IO line has an analog value, 
439         *         {@code false} otherwise.
440         * 
441         * @see #getAnalogValue(IOLine)
442         * @see #getAnalogValues()
443         * @see #hasAnalogValues()
444         * @see IOLine
445         */
446        public boolean hasAnalogValue(IOLine ioLine) {
447                return analogValuesMap.containsKey(ioLine);
448        }
449        
450        /**
451         * Returns the analog values map.
452         * 
453         * <p>To verify if this sample contains a valid analog values, use the 
454         * method {@code hasAnalogValues()}.</p>
455         * 
456         * <pre>
457         * {@code
458         * if (ioSample.hasAnalogValues()) {
459         *     HashMap<IOLine, Integer> values = ioSample.getAnalogValues();
460         *     ...
461         * } else {
462         *     ...
463         * }
464         * }
465         * </pre>
466         * 
467         * @return {@code HashMap} with the analog value of each configured IO 
468         *         line.
469         * 
470         * @see #getAnalogValue(IOLine)
471         * @see #hasAnalogValue(IOLine)
472         * @see #hasAnalogValues()
473         * @see IOLine
474         */
475        public HashMap<IOLine, Integer> getAnalogValues() {
476                return (HashMap<IOLine, Integer>) analogValuesMap.clone();
477        }
478        
479        /**
480         * Returns the analog value of the provided IO line.
481         * 
482         * <p>To verify if this sample contains an analog value for the given 
483         * {@code IOLine}, use the method {@code hasAnalogValue(IOLine)}.</p>
484         * 
485         * <pre>
486         * {@code
487         * if (ioSample.hasAnalogValue(IOLine.DIO0_AD0)) {
488         *     Integer value = ioSample.getAnalogValue(IOLine.DIO0_AD0);
489         *     ...
490         * } else {
491         *     ...
492         * }
493         * }
494         * </pre>
495         * 
496         * @param ioLine The IO line to get its analog value.
497         * 
498         * @return The analog value of the given IO line or {@code null} if the
499         *         IO sample does not contain an analog value for the given IO line.
500         * 
501         * @see #getAnalogValues()
502         * @see #hasAnalogValue(IOLine)
503         * @see #hasAnalogValues()
504         * @see IOLine
505         */
506        public Integer getAnalogValue(IOLine ioLine) {
507                if (!analogValuesMap.containsKey(ioLine))
508                        return null;
509                return analogValuesMap.get(ioLine);
510        }
511        
512        /**
513         * Returns whether or not the IOSample has power supply value.
514         * 
515         * @return {@code true} if the IOSample has power supply value, 
516         *         {@code false} otherwise.
517         * 
518         * @see #getPowerSupplyValue()
519         */
520        public boolean hasPowerSupplyValue() {
521                return ByteUtils.isBitEnabled(analogMask, 7);
522        }
523        
524        /**
525         * Returns the value of the power supply voltage.
526         * 
527         * <p>To verify if this sample contains the power supply voltage, use the 
528         * method {@code hasPowerSupplyValue()}.</p>
529         * 
530         * <pre>
531         * {@code
532         * if (ioSample.hasPowerSupplyValue()) {
533         *     int value = ioSample.getPowerSupplyValue();
534         *     ...
535         * } else {
536         *     ...
537         * }
538         * }
539         * </pre>
540         * 
541         * @return The value of the power supply voltage.
542         * 
543         * @throws OperationNotSupportedException if the IOSample does not have 
544         *         power supply value.
545         * 
546         * @see #hasPowerSupplyValue()
547         */
548        public int getPowerSupplyValue() throws OperationNotSupportedException {
549                if (!ByteUtils.isBitEnabled(analogMask, 7))
550                        throw new OperationNotSupportedException();
551                return powerSupplyVoltage;
552        }
553        
554        /**
555         * Returns the raw mask of the IO sample.
556         * 
557         * @return The raw mask of the IO sample.
558         * 
559         * @since 1.3.0
560         */
561        byte[] getRawMask() {
562                return rawMask.toByteArray();
563        }
564        
565        /**
566         * Returns the raw sample of the IO sample.
567         * 
568         * @return The raw sample of the IO sample.
569         * 
570         * @since 1.3.0
571         */
572        byte[] getRawSample() {
573                return rawSample.toByteArray();
574        }
575        
576        /**
577         * Parses the IO samples contained in the given payload.
578         * 
579         * @param samplePayload The payload corresponding to one or multiple IO
580         *                      samples.
581         * 
582         * @return List of IO samples.
583         * 
584         * @since 1.3.0
585         */
586        public static ArrayList<IOSample> parseIOSamples(byte[] samplePayload) {
587                ArrayList<IOSample> samples = new ArrayList<IOSample>();
588                if (samplePayload == null)
589                        return samples;
590                
591                int nSamples = ByteUtils.byteToInt(samplePayload[0]);
592                
593                // Parse the first IO sample.
594                samples.add(new IOSample(samplePayload));
595                
596                if (nSamples > 1) {
597                        // Get the mask of the first sample (fixed, the same for all samples).
598                        byte[] rawMask = samples.get(0).getRawMask();
599                        // Remove the number of samples and mask from the remaining bytes.
600                        byte[] remaining = Arrays.copyOfRange(samplePayload, 1 + rawMask.length, samplePayload.length);
601                        for (int i = 1; i < nSamples; i++) {
602                                // Get the raw sample of the previous sample.
603                                byte[] rawSample = samples.get(i - 1).getRawSample();
604                                // Remove the previous sample from the remaining bytes.
605                                remaining = Arrays.copyOfRange(remaining, rawSample.length, remaining.length);
606                                // Create the byte array for the next sample.
607                                ByteArrayOutputStream nextSampleData = new ByteArrayOutputStream();
608                                try {
609                                        nextSampleData.write(samplePayload[0]);
610                                        nextSampleData.write(rawMask);
611                                        nextSampleData.write(remaining);
612                                } catch (IOException e) {
613                                        e.printStackTrace();
614                                        break;
615                                }
616                                // Parse and add the next IO sample.
617                                samples.add(new IOSample(nextSampleData.toByteArray()));
618                        }
619                }
620                
621                return samples;
622        }
623        
624        /*
625         * (non-Javadoc)
626         * @see java.lang.Object#toString()
627         */
628        @Override
629        public String toString() {
630                StringBuilder sb = new StringBuilder("{");
631                if (hasDigitalValues()) {
632                        for (IOLine line : digitalValuesMap.keySet()) {
633                                sb.append("[").append(line).append(": ").append(digitalValuesMap.get(line)).append("], ");
634                        }
635                }
636                if (hasAnalogValues()) {
637                        for (IOLine line : analogValuesMap.keySet()) {
638                                sb.append("[").append(line).append(": ").append(analogValuesMap.get(line)).append("], ");
639                        }
640                }
641                if (hasPowerSupplyValue()) {
642                        try {
643                                sb.append("[").append("Power supply voltage: ").append(getPowerSupplyValue()).append("], ");
644                        } catch (OperationNotSupportedException e) {}
645                }
646                
647                String s = sb.toString();
648                if (s.endsWith(", "))
649                        s = s.substring(0, s.length() - 2);
650                return s + "}";
651        }
652        
653        /*
654         * (non-Javadoc)
655         * @see java.lang.Object#equals(java.lang.Object)
656         */
657        @Override
658        public boolean equals(Object obj) {
659                try {
660                        IOSample sample = (IOSample)obj;
661                        return Arrays.equals(ioSamplePayload, sample.ioSamplePayload);
662                } catch (ClassCastException e) {
663                        return false;
664                }
665        }
666}