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