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<Integer, NamedBean>();
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<Integer, NamedBean>();
065        isPolled = false;
066        userAddress = new XBee16BitAddress(user);
067        globalAddress = new XBee64BitAddress(global);
068    }
069
070    public XBeeNode(RemoteXBeeDevice rxd) throws TimeoutException, 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.toString() );
081        pinObjects = new HashMap<Integer, NamedBean>();
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        return;
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
414    /**
415     * Provide a string representation of this XBee Node.
416     */
417    @Override
418    public String toString(){
419       return "(" + jmri.util.StringUtil.hexStringFromBytes(getUserAddress()) +
420              "," + jmri.util.StringUtil.hexStringFromBytes(getGlobalAddress()) +
421              "," + getIdentifier() + ")";
422    }
423
424
425    private byte PRValue[] = null;
426    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
427    private final Lock readLock = readWriteLock.readLock();
428    private final Lock writeLock = readWriteLock.writeLock();
429
430    /**
431     * Package protected method to set the PR (Pull Resistance) parameter of the node.
432     *
433     * @param pin the pin number to change.
434     * @param pr a jmri.Sensor.PullResistance value used to configure the pin.
435     * @throws TimeoutException lock timed out
436     * @throws XBeeException invalid Xbee values, pins
437     */
438    void setPRParameter(int pin, jmri.Sensor.PullResistance pr) throws TimeoutException, XBeeException {
439       // flip the bits in the PR data byte, and then send to the node.
440       if(pin>7 || pin < 0){
441          throw new IllegalArgumentException("Invalid pin specified");
442       }
443       try {
444          // always try to get the PR value when writing.
445          writeLock.lock();
446          PRValue = device.getParameter("PR");
447          switch(pin){
448          case 0:
449              if(pr==jmri.Sensor.PullResistance.PULL_UP) {
450                PRValue[0]=(byte) (PRValue[0] | 0x01);
451              } else {
452                PRValue[0]=(byte) (PRValue[0] & 0xFE);
453              }
454              break;
455          case 1:
456              if(pr==jmri.Sensor.PullResistance.PULL_UP) {
457                PRValue[0]=(byte) (PRValue[0] | 0x02);
458              } else {
459                PRValue[0]=(byte) (PRValue[0] & 0xFD);
460              }
461              break;
462          case 2:
463              if(pr==jmri.Sensor.PullResistance.PULL_UP) {
464                PRValue[0]=(byte) (PRValue[0] | 0x04);
465              } else {
466                PRValue[0]=(byte) (PRValue[0] & 0xFB);
467              }
468              break;
469          case 3:
470              if(pr==jmri.Sensor.PullResistance.PULL_UP) {
471                PRValue[0]=(byte) (PRValue[0] | 0x08);
472              } else {
473                PRValue[0]=(byte) (PRValue[0] & 0xF7);
474              }
475              break;
476          case 4:
477              if(pr==jmri.Sensor.PullResistance.PULL_UP) {
478                PRValue[0]=(byte) (PRValue[0] | 0x10);
479              } else {
480                PRValue[0]=(byte) (PRValue[0] & 0xEF);
481              }
482              break;
483          case 5:
484              if(pr==jmri.Sensor.PullResistance.PULL_UP) {
485                PRValue[0]=(byte) (PRValue[0] | 0x20);
486              } else {
487                PRValue[0]=(byte) (PRValue[0] & 0xDF);
488              }
489              break;
490          case 6:
491              if(pr==jmri.Sensor.PullResistance.PULL_UP) {
492                PRValue[0]=(byte) (PRValue[0] | 0x40);
493              } else {
494                PRValue[0]=(byte) (PRValue[0] & 0xBF);
495              }
496              break;
497          case 7:
498              if(pr==jmri.Sensor.PullResistance.PULL_UP) {
499                PRValue[0]=(byte) (PRValue[0] |  (byte) 0x80);
500              } else {
501                PRValue[0]=(byte) (PRValue[0] & (byte) 0x7F);
502              }
503                break;
504            default:
505                log.warn("Unhandled pin value: {}", pin);
506                break;
507
508          }
509          device.setParameter("PR",PRValue);
510          device.applyChanges();  // force the XBee to start using the new value.
511                                  // we may also want to use writeChanges to set
512                                  // the value on the device perminantly.
513       } finally {
514           writeLock.unlock();
515       }
516    }
517
518   /**
519    * Package protected method to check to see if the PR parameter indicates
520    * the specified pin has the pull-up resistor enabled.
521    *
522    * @param pin the pin number
523    * @return a jmri.Sensor.PullResistance value indicating the current state of
524    * the pullup resistor.
525    * @throws TimeoutException lock timeout
526    * @throws XBeeException invalid pins or values
527    */
528    jmri.Sensor.PullResistance getPRValueForPin(int pin) throws TimeoutException, XBeeException {
529       if(pin>7 || pin < 0){
530          throw new IllegalArgumentException("Invalid pin specified");
531       }
532       // when reading, used the cached PRValue, if it is available
533       byte prbyte;
534       try {
535          readLock.lock();
536          if(PRValue == null){
537             PRValue = device.getParameter("PR");
538          }
539          prbyte = PRValue[0];
540       } finally {
541          readLock.unlock();
542       }
543       jmri.Sensor.PullResistance retval = jmri.Sensor.PullResistance.PULL_OFF;
544       switch(pin){
545       case 0:
546           if((prbyte & 0x01)==0x01){
547              retval = jmri.Sensor.PullResistance.PULL_UP;
548           } else {
549              retval = jmri.Sensor.PullResistance.PULL_OFF;
550           }
551           break;
552       case 1:
553           if((prbyte & 0x02)==0x02){
554              retval = jmri.Sensor.PullResistance.PULL_UP;
555           } else {
556              retval = jmri.Sensor.PullResistance.PULL_OFF;
557           }
558           break;
559       case 2:
560           if((prbyte & 0x04)==0x04){
561              retval = jmri.Sensor.PullResistance.PULL_UP;
562           } else {
563              retval = jmri.Sensor.PullResistance.PULL_OFF;
564           }
565           break;
566       case 3:
567           if((prbyte & 0x08)==0x08){
568              retval = jmri.Sensor.PullResistance.PULL_UP;
569           } else {
570              retval = jmri.Sensor.PullResistance.PULL_OFF;
571           }
572           break;
573       case 4:
574           if((prbyte & 0x10)==0x10){
575              retval = jmri.Sensor.PullResistance.PULL_UP;
576           } else {
577              retval = jmri.Sensor.PullResistance.PULL_OFF;
578           }
579           break;
580       case 5:
581           if((prbyte & 0x20)==0x20){
582              retval = jmri.Sensor.PullResistance.PULL_UP;
583           } else {
584              retval = jmri.Sensor.PullResistance.PULL_OFF;
585           }
586           break;
587       case 6:
588           if((prbyte & 0x40)==0x40){
589              retval = jmri.Sensor.PullResistance.PULL_UP;
590           } else {
591              retval = jmri.Sensor.PullResistance.PULL_OFF;
592           }
593           break;
594       case 7:
595           if((prbyte & 0x80)==0x80){
596              retval = jmri.Sensor.PullResistance.PULL_UP;
597           } else {
598              retval = jmri.Sensor.PullResistance.PULL_OFF;
599           }
600           break;
601       default:
602          retval = jmri.Sensor.PullResistance.PULL_OFF;
603       }
604       return retval;
605    }
606
607    private final static Logger log = LoggerFactory.getLogger(XBeeNode.class);
608
609}