001package jmri.jmrix.ieee802154.xbee;
002
003import com.digi.xbee.api.RemoteXBeeDevice;
004import com.digi.xbee.api.exceptions.InterfaceNotOpenException;
005import com.digi.xbee.api.exceptions.TimeoutException;
006import com.digi.xbee.api.exceptions.XBeeException;
007import com.digi.xbee.api.io.IOLine;
008import com.digi.xbee.api.io.IOSample;
009import com.digi.xbee.api.io.IOValue;
010import com.digi.xbee.api.listeners.IIOSampleReceiveListener;
011import jmri.Sensor;
012import jmri.implementation.AbstractSensor;
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016/**
017 * Extend jmri.AbstractSensor for XBee connections.
018 *
019 * @author Paul Bender Copyright (C) 2013
020 */
021public class XBeeSensor extends AbstractSensor implements IIOSampleReceiveListener {
022
023    private String nodeIdentifier; /* This is a string representation of
024     the XBee address in the system name.
025     It may be an address or it may be
026     the nodeIdentifier string stored in
027     the NI parameter on the node.*/
028
029    private int pin;         /* Which DIO pin does this sensor represent. */
030
031    private XBeeNode node = null; // Which node does this belong too.
032
033    protected XBeeTrafficController tc = null;
034
035    public XBeeSensor(String systemName, String userName, XBeeTrafficController controller) {
036        super(systemName, userName);
037        tc = controller;
038        init(systemName);
039    }
040
041    public XBeeSensor(String systemName, XBeeTrafficController controller) {
042        super(systemName);
043        tc = controller;
044        init(systemName);
045    }
046
047    /**
048     * Common initialization for both constructors.
049     */
050    private void init(String id) {
051        // store address
052        jmri.jmrix.ieee802154.IEEE802154SystemConnectionMemo m = tc.getAdapterMemo();
053        if( !(m instanceof XBeeConnectionMemo))
054        {
055           log.error("Memo associated with the traffic controller is not the right type");
056           throw new IllegalArgumentException("Memo associated with the traffic controller is not the right type");
057        } else {
058           XBeeConnectionMemo memo = (XBeeConnectionMemo) m;
059           String prefix = memo.getSensorManager().getSystemPrefix();
060
061           if (id.contains(":")) {
062               //Address format passed is in the form of encoderAddress:input or S:sensor address
063               int seperator = id.indexOf(":");
064               try {
065                   nodeIdentifier = id.substring(prefix.length() + 1, seperator);
066                   if ((node = (XBeeNode) tc.getNodeFromName(nodeIdentifier)) == null) {
067                       if ((node = (XBeeNode) tc.getNodeFromAddress(nodeIdentifier)) == null) {
068                           try {
069                               node = (XBeeNode) tc.getNodeFromAddress(Integer.parseInt(nodeIdentifier));
070                           } catch (java.lang.NumberFormatException nfe) {
071                               // if there was a number format exception, we couldn't
072                               // find the node.
073                               node = null;
074                           }
075                       }
076                   }
077                   pin = Integer.parseInt(id.substring(seperator + 1));
078               } catch (NumberFormatException ex) {
079                   log.debug("Unable to convert {} into the cab and input format of nn:xx", id);
080               }
081           } else {
082               try {
083                   nodeIdentifier = id.substring(prefix.length() + 1, id.length() - 1);
084                   int address = Integer.parseInt(id.substring(prefix.length() + 1));
085                   node = (XBeeNode) tc.getNodeFromAddress(address / 10);
086                   // calculate the pin to examine
087                   pin = ((address) % 10);
088               } catch (NumberFormatException ex) {
089                   log.debug("Unable to convert {} Hardware Address to a number", id);
090               }
091           }
092           if (log.isDebugEnabled()) {
093               log.debug("Created Sensor {} (NodeIdentifier {} ,D{})", id, nodeIdentifier, pin);
094           }
095
096           // register to hear XBee IO Sample events.
097          tc.getXBee().addIOSampleListener(this);
098
099           // Finally, request the current state from the layout.
100           this.requestUpdateFromLayout();
101        } 
102    }
103
104    /**
105     * Request an update on status by sending an XBee message.
106     */
107    @Override
108    public void requestUpdateFromLayout() {
109        // Request the sensor status from the XBee Node this sensor is
110        // attached to.  
111        try  {
112           IOValue value = node.getXBee().getDIOValue(IOLine.getDIO(pin));
113           if ((value==IOValue.HIGH) ^ _inverted) {
114               setOwnState(Sensor.ACTIVE);
115           } else {
116               setOwnState(Sensor.INACTIVE);
117           }
118        } catch (TimeoutException toe) {
119           log.error("Timeout retrieving IO line value for {} on {}",IOLine.getDIO(pin),node.getXBee());
120           // hidden terminal? Make sure the state appears as unknown.
121           setOwnState(Sensor.UNKNOWN);
122        } catch (InterfaceNotOpenException ino) {
123           log.error("Interface Not Open retrieving IO line value for {} on {}",IOLine.getDIO(pin),node.getXBee());
124        } catch (XBeeException xbe) {
125           log.error("Error retrieving IO line value for {} on {}",IOLine.getDIO(pin),node.getXBee());
126        }
127    }
128
129
130    // IIOSampleReceiveListener methods
131
132    @Override
133    public synchronized void ioSampleReceived(RemoteXBeeDevice remoteDevice,IOSample ioSample) {
134        if (log.isDebugEnabled()) {
135            log.debug("received io sample {} from {}", ioSample, remoteDevice);
136        }
137
138        XBeeNode sourcenode = (XBeeNode) tc.getNodeFromXBeeDevice(remoteDevice);
139
140        if (node.equals(sourcenode)) {
141          if ( ioSample.hasDigitalValues()){
142              if ((ioSample.getDigitalValue(IOLine.getDIO(pin))==IOValue.HIGH) ^ _inverted) {
143                 setOwnState(Sensor.ACTIVE);
144             } else {
145                 setOwnState(Sensor.INACTIVE);
146             }
147          }
148        }
149    }
150
151    /**
152     * Set the pull resistance.
153     * <p>
154     * In this default implementation, the input value is ignored.
155     *
156     * @param r PullResistance value to use
157     */
158    @Override
159    public void setPullResistance(PullResistance r){
160       try { 
161          node.setPRParameter(pin,r);
162       } catch (TimeoutException toe) {
163         log.error("Timeout retrieving PR value for {} on {}",IOLine.getDIO(pin),node.getXBee());
164       } catch (XBeeException xbe) {
165         log.error("Error retrieving PR value for {} on {}",IOLine.getDIO(pin),node.getXBee());
166       }
167    }
168
169    /**
170     * Get the pull resistance.
171     *
172     * @return the currently set PullResistance value
173     */
174    @Override
175    public PullResistance getPullResistance(){
176       try {
177          return node.getPRValueForPin(pin);
178       } catch (TimeoutException toe) {
179         log.error("Timeout retrieving PR value for {} on {}",IOLine.getDIO(pin),node.getXBee());
180       } catch (XBeeException xbe) {
181         log.error("Error retrieving PR value for {} on {}",IOLine.getDIO(pin),node.getXBee());
182       }
183       return PullResistance.PULL_UP; // return the default if we get this far.
184    }
185
186    @Override
187    public void dispose() {
188        tc.getXBee().removeIOSampleListener(this);
189        super.dispose();
190    }
191
192    private final static Logger log = LoggerFactory.getLogger(XBeeSensor.class);
193
194}