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}