001package jmri.jmrix.ieee802154.xbee;
002
003import com.digi.xbee.api.RemoteXBeeDevice;
004import com.digi.xbee.api.XBeeDevice;
005import com.digi.xbee.api.exceptions.TimeoutException;
006import com.digi.xbee.api.exceptions.XBeeException;
007import com.digi.xbee.api.listeners.IDataReceiveListener;
008import com.digi.xbee.api.listeners.IModemStatusReceiveListener;
009import com.digi.xbee.api.listeners.IPacketReceiveListener;
010import com.digi.xbee.api.models.ModemStatusEvent;
011import com.digi.xbee.api.packet.XBeePacket;
012import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
013import jmri.jmrix.AbstractMRListener;
014import jmri.jmrix.AbstractMRMessage;
015import jmri.jmrix.AbstractMRReply;
016import jmri.jmrix.AbstractPortController;
017import jmri.jmrix.ieee802154.IEEE802154Listener;
018import jmri.jmrix.ieee802154.IEEE802154Message;
019import jmri.jmrix.ieee802154.IEEE802154Reply;
020import jmri.jmrix.ieee802154.IEEE802154TrafficController;
021import org.slf4j.Logger;
022import org.slf4j.LoggerFactory;
023
024/**
025 * Traffic Controller interface for communicating with XBee devices directly
026 * using the XBee API.
027 *
028 * @author Paul Bender Copyright (C) 2013, 2016
029 */
030public class XBeeTrafficController extends IEEE802154TrafficController implements IPacketReceiveListener, IModemStatusReceiveListener, IDataReceiveListener, XBeeInterface {
031
032    private XBeeDevice xbee = null;
033
034    /**
035     * Get a message of a specific length for filling in.
036     * <p>
037     * This is a default, null implementation, which must be overridden in an
038     * adapter-specific subclass.
039     */
040    @Override
041    public IEEE802154Message getIEEE802154Message(int length) {
042        return null;
043    }
044
045    /**
046     * Get a message of zero length.
047     */
048    @Override
049    protected AbstractMRReply newReply() {
050        return new XBeeReply();
051    }
052
053    /**
054     * Make connection to an existing PortController object.
055     */
056    @Override
057    @SuppressFBWarnings(value = {"UW_UNCOND_WAIT", "WA_NOT_IN_LOOP"}, justification="The unconditional wait outside of a loop is used to allow the hardware to react to a reset request.")
058    public void connectPort(AbstractPortController p) {
059        // Attach XBee to the port
060        try {
061            if( p instanceof XBeeAdapter) {
062               XBeeAdapter xbp = (XBeeAdapter) p;
063               xbee = new XBeeDevice(xbp);
064               xbee.open();
065               xbee.reset();
066               try {
067                  synchronized(this){
068                     wait(2000);
069                  }
070               } catch (java.lang.InterruptedException e) {
071               }
072               xbee.addPacketListener(this);
073               xbee.addModemStatusListener(this);
074               xbee.addDataListener(this);
075
076            } else {
077               throw new java.lang.IllegalArgumentException("Wrong adapter type specified when connecting to the port.");
078            }
079        } catch (TimeoutException te) {
080            log.error("Timeout during communication with Local XBee on communication start up. Error was {} cause {} ",te,te.getCause());
081        } catch (XBeeException xbe ) {
082            log.error("Exception during XBee communication start up. Error was {} cause {} ",xbe,xbe.getCause());
083        }
084    }
085
086    /**
087     * Actually transmit the next message to the port.
088     */
089    @Override
090    synchronized protected void forwardToPort(AbstractMRMessage m, AbstractMRListener reply) {
091        if (log.isDebugEnabled()) {
092            log.debug("forwardToPort message: [{}]", m);
093        }
094        // remember who sent this
095        mLastSender = reply;
096
097        // forward the message to the registered recipients,
098        // which includes the communications monitor, except the sender.
099        // Schedule notification via the Swing event queue to ensure order
100        Runnable r = new XmtNotifier(m, mLastSender, this);
101        javax.swing.SwingUtilities.invokeLater(r);
102
103        /* TODO: Check to see if we need to do any of the error handling
104         in AbstractMRTrafficController here */
105        // forward using XBee Specific message format
106        try {
107            xbee.sendPacketAsync(((XBeeMessage) m).getXBeeRequest());
108        } catch (XBeeException xbe) {
109            log.error("Error Sending message to XBee: {}", xbe);
110        }
111    }
112
113    /**
114     * Invoked if it's appropriate to do low-priority polling of the command
115     * station, this should return the next message to send, or null if the TC
116     * should just sleep.
117     */
118    @Override
119    @SuppressWarnings("deprecation") // until there's a replacement for getPreferedTransmitAddress()
120    protected AbstractMRMessage pollMessage() {
121        if (numNodes <= 0) {
122            return null;
123        }
124        XBeeMessage msg = null;
125        if (getNode(curSerialNodeIndex).getSensorsActive()) {
126            msg = XBeeMessage.getForceSampleMessage(((XBeeNode) getNode(curSerialNodeIndex)).getPreferedTransmitAddress());
127        }
128        curSerialNodeIndex = (curSerialNodeIndex + 1) % numNodes;
129        return msg;
130    }
131
132    @Override
133    protected AbstractMRListener pollReplyHandler() {
134        return null;
135    }
136
137    /*
138     * enterProgMode() and enterNormalMode() return any message that
139     * needs to be returned to the command station to change modes.
140     *
141     * If no message is needed, you may return null.
142     *
143     * If the programmerIdle() function returns true, enterNormalMode() is
144     * called after a timeout while in IDLESTATE during programming to
145     * return the system to normal mode.
146     *
147     */
148    @Override
149    protected AbstractMRMessage enterProgMode() {
150        return null;
151    }
152
153    @Override
154    protected AbstractMRMessage enterNormalMode() {
155        return null;
156    }
157
158    /*
159     * For this implementation, the receive is handled by the
160     * XBee Library, so we are suppressing the standard receive
161     * loop.
162     */
163    @Override
164    public void receiveLoop() {
165    }
166
167    /**
168     * Register a node.
169     */
170    @Override
171    public void registerNode(jmri.jmrix.AbstractNode node) {
172        if(node instanceof XBeeNode) {
173           super.registerNode(node);
174           XBeeNode xbnode = (XBeeNode) node;
175           xbnode.setTrafficController(this);
176        } else {
177           throw new java.lang.IllegalArgumentException("Attempt to register node of incorrect type for this connection");
178        }
179    }
180
181    @SuppressFBWarnings(value="VO_VOLATILE_INCREMENT", justification="synchronized method provides locking")
182    public synchronized void deleteNode(XBeeNode node) {
183        // find the serial node
184        int index = 0;
185        for (int i = 0; i < numNodes; i++) {
186            if (nodeArray[i] == node) {
187                index = i;
188            }
189        }
190        if (index == curSerialNodeIndex) {
191            log.warn("Deleting the serial node active in the polling loop");
192        }
193        // Delete the node from the node list
194        numNodes--;
195        if (index < numNodes) {
196            // did not delete the last node, shift
197            for (int j = index; j < numNodes; j++) {
198                nodeArray[j] = nodeArray[j + 1];
199            }
200        }
201        nodeArray[numNodes] = null;
202        // remove this node from the network too.
203        getXBee().getNetwork().addRemoteDevice(node.getXBee());
204    }
205
206    // XBee IPacketReceiveListener interface methods
207
208    @Override
209    public void packetReceived(XBeePacket response) {
210        // because of the XBee library architecture, we don't
211        // do anything here with the responses.
212        log.debug("packetReceived called with {}", response);
213    }
214
215    // XBee IModemStatusReceiveListener interface methods
216
217    @Override
218    public void modemStatusEventReceived(ModemStatusEvent modemStatusEvent){
219        // because of the XBee library architecture, we don't
220        // do anything here with the responses.
221        log.debug("modemStatusEventReceived called with event {} ", modemStatusEvent);
222    }
223
224    // XBee IDataReceiveListener interface methods
225
226    @Override
227    public void dataReceived(com.digi.xbee.api.models.XBeeMessage xbm){
228        // because of the XBee library architecture, we don't
229        // do anything here with the responses.
230        log.debug("dataReceived called with message {} ", xbm);
231    }
232
233    /*
234     * Build a new IEEE802154 Node.
235     *
236     * @return new IEEE802154Node
237     */
238    @Override
239    public jmri.jmrix.ieee802154.IEEE802154Node newNode() {
240        return new XBeeNode();
241    }
242
243    @Override
244    public void addXBeeListener(XBeeListener l) {
245        this.addListener(l);
246    }
247
248    @Override
249    public void removeXBeeListener(XBeeListener l) {
250        this.addListener(l);
251    }
252
253    @Override
254    public void sendXBeeMessage(XBeeMessage m, XBeeListener l) {
255        sendMessage(m, l);
256    }
257
258    /**
259     * This is invoked with messages to be forwarded to the port. It queues
260     * them, then notifies the transmission thread.
261     */
262    @Override
263    synchronized protected void sendMessage(AbstractMRMessage m, AbstractMRListener reply) {
264        msgQueue.addLast(m);
265        listenerQueue.addLast(reply);
266        if (m != null) {
267            log.debug("just notified transmit thread with message {}", m.toString());
268        }
269    }
270
271    /**
272     * Forward a XBeeMessage to all registered XBeeInterface listeners.
273     */
274    @Override
275    protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) {
276
277        try {
278            ((XBeeListener) client).message((XBeeMessage) m);
279        } catch (java.lang.ClassCastException cce) {
280            // try sending as an IEEE message.
281            ((IEEE802154Listener) client).message((IEEE802154Message) m);
282        }
283    }
284
285    /**
286     * Forward a reply to all registered XBeeInterface listeners.
287     */
288    @Override
289    protected void forwardReply(AbstractMRListener client, AbstractMRReply r) {
290        if (client instanceof XBeeListener) {
291            ((XBeeListener) client).reply((XBeeReply) r);
292        } else {
293            // we're using some non-XBee specific code, like the monitor
294            // that only registeres as an IEEE802154Listener.
295            ((IEEE802154Listener) client).reply((IEEE802154Reply) r);
296        }
297    }
298
299    /**
300     * Public method to identify an XBeeNode from its node identifier
301     *
302     * @param Name the node identifier search string.
303     * @return the node if found, or null otherwise.
304     */
305    synchronized public jmri.jmrix.AbstractNode getNodeFromName(String Name) {
306        log.debug("getNodeFromName called with {}",Name);
307        for (int i = 0; i < numNodes; i++) {
308            XBeeNode node = (XBeeNode) getNode(i);
309            if (node.getIdentifier().equals(Name)) {
310                return node;
311            }
312        }
313        return (null);
314    }
315
316   /**
317     * Public method to identify an XBeeNode from its RemoteXBeeDevice object.
318     *
319     * @param device the RemoteXBeeDevice to search for.
320     * @return the node if found, or null otherwise.
321     */
322    synchronized public jmri.jmrix.AbstractNode getNodeFromXBeeDevice(RemoteXBeeDevice device) {
323        log.debug("getNodeFromXBeeDevice called with {}",device);
324        for (int i = 0; i < numNodes; i++) {
325            XBeeNode node = (XBeeNode) getNode(i);
326            // examine the addresses of the two XBee Devices to see
327            // if they are the same.
328            RemoteXBeeDevice nodeXBee = node.getXBee();
329            if(nodeXBee.get16BitAddress().equals(device.get16BitAddress())
330               && nodeXBee.get64BitAddress().equals(device.get64BitAddress())) {
331                return node;
332            }
333        }
334        return (null);
335    }
336
337    /*
338     * @return the XBeeDevice associated with this traffic controller.
339     */
340    public XBeeDevice getXBee(){
341        return xbee;
342    }
343
344    @Override
345    protected void terminate(){
346       if(xbee!=null) {
347          xbee.close();
348          xbee=null;
349       }
350    }
351
352    private final static Logger log = LoggerFactory.getLogger(XBeeTrafficController.class);
353
354}