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}