001package jmri.jmrix.ieee802154.xbee; 002 003import com.digi.xbee.api.RemoteXBeeDevice; 004import com.digi.xbee.api.exceptions.TimeoutException; 005import com.digi.xbee.api.exceptions.XBeeException; 006import com.digi.xbee.api.models.XBee16BitAddress; 007import com.digi.xbee.api.models.XBee64BitAddress; 008import java.util.HashMap; 009import java.util.concurrent.locks.Lock; 010import java.util.concurrent.locks.ReadWriteLock; 011import java.util.concurrent.locks.ReentrantReadWriteLock; 012import jmri.NamedBean; 013import jmri.jmrix.AbstractMRListener; 014import jmri.jmrix.AbstractMRMessage; 015import jmri.jmrix.ieee802154.IEEE802154Node; 016import jmri.util.StringUtil; 017import org.slf4j.Logger; 018import org.slf4j.LoggerFactory; 019 020/** 021 * Implementation of a Node for XBee networks. 022 * <p> 023 * Integrated with {@link XBeeTrafficController}. 024 * <p> 025 * Each node has 3 addresses associated with it: 026 * <ol> 027 * <li>A 16 bit PAN (Personal Area Network) ID assigned by the user</li> 028 * <li>A 16 bit User Assigned Address</li> 029 * <li>A 64 bit Globally Unique ID assigned by the manufacturer</li> 030 * </ol> 031 * <p> 032 * All nodes in a given network must have the same PAN ID 033 * 034 * @author Paul Bender Copyright 2013 035 */ 036public class XBeeNode extends IEEE802154Node { 037 038 private String identifier; 039 private HashMap<Integer, NamedBean> pinObjects = null; 040 private boolean isPolled; 041 private XBeeTrafficController tc = null; 042 private RemoteXBeeDevice device = null; 043 private XBee16BitAddress userAddress = null; 044 private XBee64BitAddress globalAddress = null; 045 046 private final static byte[] DefaultPanID = {0x00,0x00}; 047 048 /** 049 * Create a new instance of XBeeNode. 050 */ 051 public XBeeNode() { 052 identifier = ""; 053 pinObjects = new HashMap<>(); 054 isPolled = false; 055 } 056 057 public XBeeNode(byte[] pan, byte[] user, byte[] global) { 058 super(pan, user, global); 059 identifier = ""; 060 log.debug("Created new node with panId: {} userId: {} and GUID: {}", 061 StringUtil.arrayToString(pan), 062 StringUtil.arrayToString(user), 063 StringUtil.arrayToString(global)); 064 pinObjects = new HashMap<>(); 065 isPolled = false; 066 userAddress = new XBee16BitAddress(user); 067 globalAddress = new XBee64BitAddress(global); 068 } 069 070 public XBeeNode(RemoteXBeeDevice rxd) throws XBeeException { 071 super(DefaultPanID, rxd.get16BitAddress().getValue(), rxd.get64BitAddress().getValue()); 072 identifier = rxd.getNodeID(); 073 074 try{ 075 setPANAddress(rxd.getPANID()); 076 } catch (TimeoutException t) { 077 // we dont need the PAN ID for communicaiton,so just continue. 078 } 079 080 log.debug("Created new node from RemoteXBeeDevice: {}", rxd ); 081 pinObjects = new HashMap<>(); 082 isPolled = false; 083 device = rxd; 084 userAddress = device.get16BitAddress(); 085 globalAddress = device.get64BitAddress(); 086 } 087 088 /** 089 * Set the Traffic Controller associated with this node. 090 * @param controller system connection traffic controller. 091 */ 092 public void setTrafficController(XBeeTrafficController controller) { 093 tc = controller; 094 } 095 096 /** 097 * Create the needed Initialization packet (AbstractMRMessage) for this 098 * node. 099 * 100 * @return null because not needed for XBeeNode 101 */ 102 @Override 103 public AbstractMRMessage createInitPacket() { 104 return null; 105 } 106 107 /** 108 * Create an Transmit packet (AbstractMRMessage) to send current state. 109 */ 110 @Override 111 public AbstractMRMessage createOutPacket() { 112 return null; 113 } // TODO 114 115 /** 116 * Are sensors present, and hence will this node need to be polled? 117 * 118 * @return 'true' if at least one sensor is active for this node 119 */ 120 @Override 121 public boolean getSensorsActive() { 122 if (getPoll()) { 123 for (Object bean : pinObjects.values()) { 124 if (bean instanceof XBeeSensor) { 125 return true; 126 } 127 } 128 } 129 return false; 130 } 131 132 /** 133 * Set the isPolled attribute. 134 * @param poll true to set flag polled, else false. 135 */ 136 public void setPoll(boolean poll) { 137 isPolled = poll; 138 } 139 140 /** 141 * Get the isPolled attribute. 142 * @return true if isPolled flag set, else false. 143 */ 144 public boolean getPoll() { 145 return isPolled; 146 } 147 148 /** 149 * Deal with a timeout in the transmission controller. 150 * 151 * @param m message that didn't receive a reply 152 * @param l listener that sent the message 153 * @return true if initialization required 154 */ 155 @Override 156 public boolean handleTimeout(AbstractMRMessage m, AbstractMRListener l) { 157 return false; 158 } 159 160 /** 161 * A reply was received, so there was not timeout; do any needed processing. 162 * Implementation does nothing. 163 * @param m message to process. 164 */ 165 @Override 166 public void resetTimeout(AbstractMRMessage m) { 167 // intentionally empty 168 } 169 170 /** 171 * Convert the 16 bit user address to an XBee16BitAddress object. 172 * @return converted address object. 173 */ 174 public XBee16BitAddress getXBeeAddress16() { 175 if(device!=null) { 176 return device.get16BitAddress(); 177 } else { 178 return userAddress; 179 } 180 } 181 182 /** 183 * Convert the 64 bit address to an XBee64BitAddress object. 184 * @return converted address object. 185 */ 186 public XBee64BitAddress getXBeeAddress64() { 187 if(device!=null) { 188 return device.get64BitAddress(); 189 } else { 190 return globalAddress; 191 } 192 } 193 194 /** 195 * XBee Nodes store an identifier. we want to be able to store and retrieve 196 * this information. 197 * 198 * @param id text id for node 199 */ 200 public void setIdentifier(String id) { 201 try { 202 device.setNodeID(id); 203 } catch(XBeeException xbe) { // includes TimeoutException 204 // ignore the error, failed to set. 205 } 206 } 207 208 public String getIdentifier() { 209 return device.getNodeID(); 210 } 211 212 /** 213 * Set the bean associated with the specified pin. 214 * 215 * @param pin is the XBee pin assigned. 216 * @param bean is the bean we are attempting to add. 217 * @return true if bean added, false if previous assignment exists. 218 * 219 */ 220 public boolean setPinBean(int pin, NamedBean bean) { 221 if (pinObjects.containsKey(pin)) { 222 log.error("Pin {} already Assigned to object {}", pin, pinObjects.get(pin)); 223 return false; 224 } else { 225 pinObjects.put(pin, bean); 226 } 227 return true; 228 } 229 230 /** 231 * Remove the bean associated with the specified pin. 232 * 233 * @param pin is the XBee pin assigned. 234 * @param bean is the bean we are attempting to remove. 235 * @return true if bean removed, false if specified bean was not assigned to 236 * the pin. 237 * 238 */ 239 public boolean removePinBean(int pin, NamedBean bean) { 240 if (bean == getPinBean(pin)) { 241 pinObjects.remove(pin); 242 return true; 243 } 244 return false; 245 } 246 247 /** 248 * Get the bean associated with the specified pin. 249 * 250 * @param pin is the XBee pin assigned. 251 * @return the bean assigned to the pin, or null if no bean is assigned. 252 * 253 */ 254 public NamedBean getPinBean(int pin) { 255 return pinObjects.get(pin); 256 } 257 258 /** 259 * Ask if a specified pin is assigned to a bean. 260 * 261 * @param pin is the XBee pin assigned. 262 * @return true if the pin has a bean assigned to it, false otherwise. 263 */ 264 public boolean getPinAssigned(int pin) { 265 return (pinObjects.containsKey(pin)); 266 } 267 268 /** 269 * Get the prefered name for this XBee Node. 270 * 271 * @return the identifier string if it is not blank then a string 272 representation of the bytes of the 16 bit address if it is not a 273 broadcast address. Otherwise return the 64 bit GUID. 274 */ 275 public String getPreferedName() { 276 if (!identifier.equals("")) { 277 return identifier; 278 } else if (!(getXBeeAddress16().equals(XBee16BitAddress.BROADCAST_ADDRESS)) 279 && !(getXBeeAddress16().equals(XBee16BitAddress.UNKNOWN_ADDRESS))) { 280 return jmri.util.StringUtil.hexStringFromBytes(useraddress); 281 } else { 282 return jmri.util.StringUtil.hexStringFromBytes(globaladdress); 283 } 284 285 } 286 287 /** 288 * Get the prefered transmit address for this XBee Node. 289 * 290 * @return the 16 bit address if it is not a broadcast address. Otherwise 291 * return the 64 bit GUID. 292 */ 293 public Object getPreferedTransmitAddress() { 294 if (!(getXBeeAddress16().equals(XBee16BitAddress.BROADCAST_ADDRESS)) 295 && !(getXBeeAddress16().equals(XBee16BitAddress.UNKNOWN_ADDRESS))) { 296 return getXBeeAddress16(); 297 } else { 298 return getXBeeAddress64(); 299 } 300 } 301 302 /** 303 * @return RemoteXBeeDevice associated with this node 304 */ 305 public RemoteXBeeDevice getXBee() { 306 if( device == null && tc !=null) { 307 device = new RemoteXBeeDevice(tc.getXBee(),globalAddress, 308 userAddress,identifier); 309 } 310 return device; 311 } 312 313 /** 314 * Set the RemoteXBeeDevice associated with this node and 315 * configure address information. 316 * 317 * @param rxd the RemoteXBeeDevice associated with this node. 318 */ 319 public void setXBee(RemoteXBeeDevice rxd) { 320 device=rxd; 321 userAddress = device.get16BitAddress(); 322 globalAddress = device.get64BitAddress(); 323 setUserAddress(rxd.get16BitAddress().getValue()); 324 setGlobalAddress(rxd.get64BitAddress().getValue()); 325 identifier = rxd.getNodeID(); 326 327 } 328 329 /** 330 * Get the stream object associated with this node. 331 * @return stream object, created if does not exist. 332 */ 333 public XBeeIOStream getIOStream() { 334 if (mStream == null) { 335 mStream = new XBeeIOStream(this, tc); 336 mStream.configure(); // start the threads for the stream. 337 } 338 return mStream; 339 } 340 341 private XBeeIOStream mStream = null; 342 343 /** 344 * Connect and configure a StreamPortController object to the XBeeIOStream 345 * associated with this node. 346 * 347 * @param cont AbstractSTreamPortController object to connect 348 */ 349 public void connectPortController(jmri.jmrix.AbstractStreamPortController cont) { 350 connectedController = cont; 351 connectedController.configure(); 352 } 353 354 /** 355 * Connect a StreamPortController object to the XBeeIOStream 356 * associated with this node. 357 * 358 * @param cont AbstractSTreamPortController object to connect 359 */ 360 public void setPortController(jmri.jmrix.AbstractStreamPortController cont) { 361 connectedController = cont; 362 } 363 364 /** 365 * Get the StreamPortController ojbect associated with the XBeeIOStream 366 * associated with this node. 367 * 368 * @return connected {@link jmri.jmrix.AbstractStreamPortController} 369 */ 370 public jmri.jmrix.AbstractStreamPortController getPortController() { 371 return connectedController; 372 } 373 374 private jmri.jmrix.AbstractStreamPortController connectedController = null; 375 376 /** 377 * Connect and configure a StreamConnectionConfig object to the XBeeIOStream 378 * associated with this node. 379 * 380 * @param cfg AbstractStreamConnectionConfig object to connect 381 */ 382 public void connectPortController(jmri.jmrix.AbstractStreamConnectionConfig cfg) { 383 connectedConfig = cfg; 384 connectPortController(cfg.getAdapter()); 385 } 386 387 /** 388 * Connect a StreamConnectionConfig object to the XBeeIOStream 389 * associated with this node. 390 * 391 * @param cfg AbstractStreamConnectionConfig object to connect 392 */ 393 public void setPortController(jmri.jmrix.AbstractStreamConnectionConfig cfg) { 394 connectedConfig = cfg; 395 setPortController(cfg.getAdapter()); 396 } 397 398 /** 399 * Get the StreamConnectionConfig ojbect associated with the XBeeIOStream 400 * associated with this node. 401 * 402 * @return connected {@link jmri.jmrix.AbstractStreamConnectionConfig} 403 */ 404 public jmri.jmrix.AbstractStreamConnectionConfig getConnectionConfig() { 405 return connectedConfig; 406 } 407 408 private jmri.jmrix.AbstractStreamConnectionConfig connectedConfig = null; 409 410 /** 411 * Provide a string representation of this XBee Node. 412 */ 413 @Override 414 public String toString(){ 415 return "(" + jmri.util.StringUtil.hexStringFromBytes(getUserAddress()) + 416 "," + jmri.util.StringUtil.hexStringFromBytes(getGlobalAddress()) + 417 "," + getIdentifier() + ")"; 418 } 419 420 421 private byte[] PRValue = null; 422 private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); 423 private final Lock readLock = readWriteLock.readLock(); 424 private final Lock writeLock = readWriteLock.writeLock(); 425 426 /** 427 * Package protected method to set the PR (Pull Resistance) parameter of the node. 428 * 429 * @param pin the pin number to change. 430 * @param pr a jmri.Sensor.PullResistance value used to configure the pin. 431 * @throws TimeoutException lock timed out 432 * @throws XBeeException invalid Xbee values, pins 433 */ 434 void setPRParameter(int pin, jmri.Sensor.PullResistance pr) throws TimeoutException, XBeeException { 435 // flip the bits in the PR data byte, and then send to the node. 436 if(pin>7 || pin < 0){ 437 throw new IllegalArgumentException("Invalid pin specified"); 438 } 439 try { 440 // always try to get the PR value when writing. 441 writeLock.lock(); 442 PRValue = device.getParameter("PR"); 443 switch(pin){ 444 case 0: 445 if(pr==jmri.Sensor.PullResistance.PULL_UP) { 446 PRValue[0]=(byte) (PRValue[0] | 0x01); 447 } else { 448 PRValue[0]=(byte) (PRValue[0] & 0xFE); 449 } 450 break; 451 case 1: 452 if(pr==jmri.Sensor.PullResistance.PULL_UP) { 453 PRValue[0]=(byte) (PRValue[0] | 0x02); 454 } else { 455 PRValue[0]=(byte) (PRValue[0] & 0xFD); 456 } 457 break; 458 case 2: 459 if(pr==jmri.Sensor.PullResistance.PULL_UP) { 460 PRValue[0]=(byte) (PRValue[0] | 0x04); 461 } else { 462 PRValue[0]=(byte) (PRValue[0] & 0xFB); 463 } 464 break; 465 case 3: 466 if(pr==jmri.Sensor.PullResistance.PULL_UP) { 467 PRValue[0]=(byte) (PRValue[0] | 0x08); 468 } else { 469 PRValue[0]=(byte) (PRValue[0] & 0xF7); 470 } 471 break; 472 case 4: 473 if(pr==jmri.Sensor.PullResistance.PULL_UP) { 474 PRValue[0]=(byte) (PRValue[0] | 0x10); 475 } else { 476 PRValue[0]=(byte) (PRValue[0] & 0xEF); 477 } 478 break; 479 case 5: 480 if(pr==jmri.Sensor.PullResistance.PULL_UP) { 481 PRValue[0]=(byte) (PRValue[0] | 0x20); 482 } else { 483 PRValue[0]=(byte) (PRValue[0] & 0xDF); 484 } 485 break; 486 case 6: 487 if(pr==jmri.Sensor.PullResistance.PULL_UP) { 488 PRValue[0]=(byte) (PRValue[0] | 0x40); 489 } else { 490 PRValue[0]=(byte) (PRValue[0] & 0xBF); 491 } 492 break; 493 case 7: 494 if(pr==jmri.Sensor.PullResistance.PULL_UP) { 495 PRValue[0]=(byte) (PRValue[0] | (byte) 0x80); 496 } else { 497 PRValue[0]=(byte) (PRValue[0] & (byte) 0x7F); 498 } 499 break; 500 default: 501 log.warn("Unhandled pin value: {}", pin); 502 break; 503 504 } 505 device.setParameter("PR",PRValue); 506 device.applyChanges(); // force the XBee to start using the new value. 507 // we may also want to use writeChanges to set 508 // the value on the device perminantly. 509 } finally { 510 writeLock.unlock(); 511 } 512 } 513 514 /** 515 * Package protected method to check to see if the PR parameter indicates 516 * the specified pin has the pull-up resistor enabled. 517 * 518 * @param pin the pin number 519 * @return a jmri.Sensor.PullResistance value indicating the current state of 520 * the pullup resistor. 521 * @throws TimeoutException lock timeout 522 * @throws XBeeException invalid pins or values 523 */ 524 jmri.Sensor.PullResistance getPRValueForPin(int pin) throws TimeoutException, XBeeException { 525 if(pin>7 || pin < 0){ 526 throw new IllegalArgumentException("Invalid pin specified"); 527 } 528 // when reading, used the cached PRValue, if it is available 529 byte prbyte; 530 try { 531 readLock.lock(); 532 if(PRValue == null){ 533 PRValue = device.getParameter("PR"); 534 } 535 prbyte = PRValue[0]; 536 } finally { 537 readLock.unlock(); 538 } 539 jmri.Sensor.PullResistance retval = jmri.Sensor.PullResistance.PULL_OFF; 540 switch(pin){ 541 case 0: 542 if((prbyte & 0x01)==0x01){ 543 retval = jmri.Sensor.PullResistance.PULL_UP; 544 } else { 545 retval = jmri.Sensor.PullResistance.PULL_OFF; 546 } 547 break; 548 case 1: 549 if((prbyte & 0x02)==0x02){ 550 retval = jmri.Sensor.PullResistance.PULL_UP; 551 } else { 552 retval = jmri.Sensor.PullResistance.PULL_OFF; 553 } 554 break; 555 case 2: 556 if((prbyte & 0x04)==0x04){ 557 retval = jmri.Sensor.PullResistance.PULL_UP; 558 } else { 559 retval = jmri.Sensor.PullResistance.PULL_OFF; 560 } 561 break; 562 case 3: 563 if((prbyte & 0x08)==0x08){ 564 retval = jmri.Sensor.PullResistance.PULL_UP; 565 } else { 566 retval = jmri.Sensor.PullResistance.PULL_OFF; 567 } 568 break; 569 case 4: 570 if((prbyte & 0x10)==0x10){ 571 retval = jmri.Sensor.PullResistance.PULL_UP; 572 } else { 573 retval = jmri.Sensor.PullResistance.PULL_OFF; 574 } 575 break; 576 case 5: 577 if((prbyte & 0x20)==0x20){ 578 retval = jmri.Sensor.PullResistance.PULL_UP; 579 } else { 580 retval = jmri.Sensor.PullResistance.PULL_OFF; 581 } 582 break; 583 case 6: 584 if((prbyte & 0x40)==0x40){ 585 retval = jmri.Sensor.PullResistance.PULL_UP; 586 } else { 587 retval = jmri.Sensor.PullResistance.PULL_OFF; 588 } 589 break; 590 case 7: 591 if((prbyte & 0x80)==0x80){ 592 retval = jmri.Sensor.PullResistance.PULL_UP; 593 } else { 594 retval = jmri.Sensor.PullResistance.PULL_OFF; 595 } 596 break; 597 default: 598 retval = jmri.Sensor.PullResistance.PULL_OFF; 599 } 600 return retval; 601 } 602 603 private final static Logger log = LoggerFactory.getLogger(XBeeNode.class); 604 605}