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}