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