001package jmri.jmrix.dccpp; 002 003import java.util.HashMap; 004import java.util.concurrent.LinkedBlockingQueue; 005import jmri.jmrix.AbstractMRListener; 006import jmri.jmrix.AbstractMRMessage; 007import jmri.jmrix.AbstractMRReply; 008import jmri.jmrix.AbstractMRTrafficController; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012/** 013 * Abstract base class for implementations of DCCppInterface. 014 * <p> 015 * This provides just the basic interface, plus the "" static method for 016 * locating the local implementation. 017 * 018 * @author Bob Jacobsen Copyright (C) 2002 019 * @author Paul Bender Copyright (C) 2004-2010 020 * @author Mark Underwood Copyright (C) 2015 021 * 022 * Based on XNetTrafficController by Bob Jacobsen and Paul Bender 023 */ 024public abstract class DCCppTrafficController extends AbstractMRTrafficController implements DCCppInterface { 025 026 public int startUpDelay = 1500; //in ms, will be overridden by config option 027 public volatile boolean anyReceived = false; //will be turned on as soon as any incoming traffic seen 028 029 @Override 030 protected void transmitLoop() { 031 int totalDelay = 0; 032 int loopDelay = 100; 033 log.debug("Transmit loop paused for up to {}ms to avoid Arduino restart", startUpDelay); 034 while (!anyReceived && (totalDelay <= startUpDelay)) { 035 try { 036 Thread.sleep(loopDelay); 037 } catch (InterruptedException ignore) { 038 log.debug("Transmit loop pause interrupted"); 039 Thread.currentThread().interrupt(); 040 break; // Breaks the while loop 041 } 042 totalDelay += loopDelay; 043 } 044 log.debug("Transmit loop resumed after {}ms", totalDelay); 045 super.transmitLoop(); 046 } 047 048 /** 049 * Create a new DCCppTrafficController instance. 050 * Must provide a DCCppCommandStation reference at creation time. 051 * 052 * @param pCommandStation reference to associated command station object, 053 * preserved for later. 054 */ 055 DCCppTrafficController(DCCppCommandStation pCommandStation) { 056 mCommandStation = pCommandStation; 057 setAllowUnexpectedReply(true); 058 mListenerMasks = new HashMap<>(); 059 highPriorityQueue = new LinkedBlockingQueue<>(); 060 highPriorityListeners = new LinkedBlockingQueue<>(); 061 log.debug("DCCppTrafficController created"); 062 } 063 064 protected HashMap<DCCppListener, Integer> mListenerMasks; 065 066 // Abstract methods for the DCCppInterface 067 068 /** 069 * Forward a preformatted DCCppMessage to the actual interface. 070 * 071 * @param m Message to send; will be updated with CRC 072 */ 073 @Override 074 abstract public void sendDCCppMessage(DCCppMessage m, DCCppListener reply); 075 076 @Override 077 protected int lengthOfByteStream(AbstractMRMessage m) { 078 int len = m.getNumDataElements(); 079 return len + 2; 080 } 081 082 /** 083 * Forward a preformatted DCCppMessage to a specific listener interface. 084 * 085 * @param m Message to send; 086 */ 087 @Override 088 public void forwardMessage(AbstractMRListener reply, AbstractMRMessage m) { 089 if (reply instanceof DCCppListener && m instanceof DCCppMessage) { 090 ((DCCppListener) reply).message((DCCppMessage) m); 091 } 092 } 093 094 /** 095 * Forward a preformatted DCCppMessage to the registered DCCppListeners. 096 * NOTE: this drops the packet if the checksum is bad. 097 * 098 * @param client Client to send message to 099 * @param m Message to send 100 */ 101 // TODO: This should be fleshed out to allow listeners to register for only 102 // certain types of DCCppReply-s. The analogous code from the Lenz interface 103 // has been left here and commented out for future reference. 104 @Override 105 public void forwardReply(AbstractMRListener client, AbstractMRReply m) { 106 // check parity 107 if (!(client instanceof DCCppListener)) { // split check to prevent class cast exception later 108 return; 109 } 110 if (!(m instanceof DCCppReply)){ 111 return; 112 } 113 //note if any incoming data received, will be used to release send queue 114 this.anyReceived = true; 115 116 try { 117 // NOTE: For now, just forward ALL messages without filtering 118 ((DCCppListener) client).message((DCCppReply) m); 119 // NOTE: For now, all listeners should register for DCCppInterface.ALL 120 /* 121 int mask = (mListenerMasks.get(client)).intValue(); 122 if (mask == DCCppInterface.ALL) { 123 ((DCCppListener) client).message((DCCppReply) m); 124 } else if ((mask & DCCppInterface.COMMINFO) 125 == DCCppInterface.COMMINFO 126 && (((DCCppReply) m).getElement(0) 127 == DCCppConstants.LI_MESSAGE_RESPONSE_HEADER)) { 128 ((DCCppListener) client).message((DCCppReply) m); 129 } else if ((mask & DCCppInterface.CS_INFO) 130 == DCCppInterface.CS_INFO 131 && (((DCCppReply) m).getElement(0) 132 == DCCppConstants.CS_INFO 133 || ((DCCppReply) m).getElement(0) 134 == DCCppConstants.CS_SERVICE_MODE_RESPONSE 135 || ((DCCppReply) m).getElement(0) 136 == DCCppConstants.CS_REQUEST_RESPONSE 137 || ((DCCppReply) m).getElement(0) 138 == DCCppConstants.BC_EMERGENCY_STOP)) { 139 ((DCCppListener) client).message((DCCppReply) m); 140 } else if ((mask & DCCppInterface.FEEDBACK) 141 == DCCppInterface.FEEDBACK 142 && (((DCCppReply) m).isFeedbackMessage() 143 || ((DCCppReply) m).isFeedbackBroadcastMessage())) { 144 ((DCCppListener) client).message((DCCppReply) m); 145 } else if ((mask & DCCppInterface.THROTTLE) 146 == DCCppInterface.THROTTLE 147 && ((DCCppReply) m).isThrottleMessage()) { 148 ((DCCppListener) client).message((DCCppReply) m); 149 } else if ((mask & DCCppInterface.CONSIST) 150 == DCCppInterface.CONSIST 151 && ((DCCppReply) m).isConsistMessage()) { 152 ((DCCppListener) client).message((DCCppReply) m); 153 } else if ((mask & DCCppInterface.INTERFACE) 154 == DCCppInterface.INTERFACE 155 && (((DCCppReply) m).getElement(0) 156 == DCCppConstants.LI_VERSION_RESPONSE 157 || ((DCCppReply) m).getElement(0) 158 == DCCppConstants.LI101_REQUEST)) { 159 ((DCCppListener) client).message((DCCppReply) m); 160 } 161 */ 162 } catch (NullPointerException e) { 163 // catch null pointer exceptions, caused by a client 164 // that sent a message without being a registered listener 165 ((DCCppListener) client).message((DCCppReply) m); 166 } 167 } 168 169 // We use the pollMessage routines for high priority messages. 170 // This means responses to time critical messages (turnout off 171 // messages). 172 LinkedBlockingQueue<DCCppMessage> highPriorityQueue; 173 LinkedBlockingQueue<DCCppListener> highPriorityListeners; 174 175 public void sendHighPriorityDCCppMessage(DCCppMessage m, DCCppListener reply) { 176 try { 177 highPriorityQueue.put(m); 178 highPriorityListeners.put(reply); 179 } catch (java.lang.InterruptedException ie) { 180 log.error("Interrupted while adding High Priority Message to Queue"); 181 } 182 } 183 184 @Override 185 protected AbstractMRMessage pollMessage() { 186 try { 187 if (highPriorityQueue.peek() == null) { 188 return null; 189 } else { 190 return highPriorityQueue.take(); 191 } 192 } catch (java.lang.InterruptedException ie) { 193 log.error("Interrupted while removing High Priority Message from Queue"); 194 } 195 return null; 196 } 197 198 @Override 199 protected AbstractMRListener pollReplyHandler() { 200 try { 201 if (highPriorityListeners.peek() == null) { 202 return null; 203 } else { 204 return highPriorityListeners.take(); 205 } 206 } catch (java.lang.InterruptedException ie) { 207 log.error("Interrupted while removing High Priority Message Listener from Queue"); 208 } 209 return null; 210 } 211 212 @Override 213 public synchronized void addDCCppListener(int mask, DCCppListener l) { 214 addListener(l); 215 // This adds all the mask information. A better way to do 216 // this would be to allow updating individual bits 217 mListenerMasks.put(l, mask); 218 } 219 220 @Override 221 public synchronized void removeDCCppListener(int mask, DCCppListener l) { 222 removeListener(l); 223 // This removes all the mask information. A better way to do 224 // this would be to allow updating of individual bits 225 mListenerMasks.remove(l); 226 } 227 228 /** 229 * Has to be available, even though it doesn't do anything 230 * on DCC++. 231 */ 232 @Override 233 protected AbstractMRMessage enterProgMode() { 234 return null; 235 } 236 237 /** 238 * 239 * @return the value of getExitProgModeMsg(); 240 */ 241 @Override 242 protected AbstractMRMessage enterNormalMode() { 243 //return DCCppMessage.getExitProgModeMsg(); 244 return null; 245 } 246 247 /** 248 * Check to see if the programmer associated with this 249 * interface is idle or not. 250 */ 251 @Override 252 protected boolean programmerIdle() { 253 if (mMemo == null) { 254 return true; 255 } 256 DCCppProgrammer progrmr = (jmri.jmrix.dccpp.DCCppProgrammer) mMemo.getProgrammerManager().getGlobalProgrammer(); 257 if ( progrmr!=null ) { 258 return !(progrmr.programmerBusy()); 259 } 260 log.warn("Unable to fetch DCCppProgrammer"); 261 return true; 262 } 263 264 @Override 265 // endOfMessage() not really used in DCC++ .. it's handled in the Packetizer. 266 protected boolean endOfMessage(AbstractMRReply msg) { 267 return msg.getElement(msg.getNumDataElements() - 1) == '>'; 268 } 269 270 @Override 271 protected AbstractMRReply newReply() { 272 return new DCCppReply(); 273 } 274 275 // /** 276 // * Get characters from the input source, and file a message. 277 // * <p> 278 // * Returns only when the message is complete. 279 // * <p> 280 // * Only used in the Receive thread. 281 // * 282 // * @param msg message to fill 283 // * @param istream character source. 284 // * @throws java.io.IOException when presented by the input source. 285 // */ 286 // protected void loadChars(AbstractMRReply msg, java.io.DataInputStream istream) throws java.io.IOException { 287 // // Spin waiting for start-of-frame '<' character (and toss it) 288 // String s = new String(); 289 // byte char1; 290 // boolean found_start = false; 291 // 292 // log.debug("Calling DCCppTrafficController.loadChars()"); 293 // 294 // while (!found_start) { 295 // char1 = readByteProtected(istream); 296 // log.debug("Char1: {}", char1); 297 // if ((char1 & 0xFF) == '<') { 298 // found_start = true; 299 // log.debug("Found starting < "); 300 // break; // A bit redundant with setting the loop condition true (false) 301 // } else { 302 // //char1 = readByteProtected(istream); 303 // } 304 // } 305 // 306 // // Now, suck in the rest of the message... 307 // for (int i = 0; i < DCCppConstants.MAX_MESSAGE_SIZE; i++) { 308 // char1 = readByteProtected(istream); 309 // if (char1 == '>') { 310 // log.debug("msg found > "); 311 // // Don't store the > 312 // break; 313 // } else { 314 // log.debug("msg read byte {}", char1); 315 // char c = (char) (char1 & 0x00FF); 316 // s += Character.toString(c); 317 // } 318 // } 319 // // TODO: Still need to strip leading and trailing whitespace. 320 // log.debug("Complete message = {}", s); 321 // ((DCCppReply)msg).parseReply(s); 322 // } 323 324 @Override 325 protected void handleTimeout(AbstractMRMessage msg, AbstractMRListener l) { 326 super.handleTimeout(msg, l); 327 if (l != null) { 328 ((DCCppListener) l).notifyTimeout((DCCppMessage) msg); 329 } 330 } 331 332 /** 333 * Reference to the command station in communication here. 334 */ 335 DCCppCommandStation mCommandStation; 336 337 /** 338 * Get access to communicating command station object. 339 * 340 * @return associated Command Station object 341 */ 342 public DCCppCommandStation getCommandStation() { 343 return mCommandStation; 344 } 345 346 /** 347 * Reference to the system connection memo. 348 */ 349 DCCppSystemConnectionMemo mMemo = null; 350 351 /** 352 * Get access to the system connection memo associated with this traffic 353 * controller. 354 * 355 * @return associated systemConnectionMemo object 356 */ 357 public DCCppSystemConnectionMemo getSystemConnectionMemo() { 358 return (mMemo); 359 } 360 361 /** 362 * Set the system connection memo associated with this traffic controller. 363 * 364 * @param m associated systemConnectionMemo object 365 */ 366 public void setSystemConnectionMemo(DCCppSystemConnectionMemo m) { 367 mMemo = m; 368 } 369 370 private DCCppTurnoutReplyCache _TurnoutReplyCache = null; 371 372 /** 373 * 374 * @return an DCCppTurnoutReplyCache object associated with this traffic 375 * controller 376 */ 377 public DCCppTurnoutReplyCache getTurnoutReplyCache() { 378 if (_TurnoutReplyCache == null) { 379 _TurnoutReplyCache = new DCCppTurnoutReplyCache(this); 380 } 381 return _TurnoutReplyCache; 382 } 383 384 private final static Logger log = LoggerFactory.getLogger(DCCppTrafficController.class); 385 386}