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