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