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}