001package jmri.jmrix.lenz; 002 003import java.util.Arrays; 004import java.util.LinkedList; 005import java.util.Queue; 006import jmri.implementation.AbstractTurnout; 007import javax.annotation.concurrent.GuardedBy; 008 009/** 010 * Extend jmri.AbstractTurnout for XNet layouts 011 * <p> 012 * Turnout operation on XpressNet based systems goes through the following 013 * sequence: 014 * <ul> 015 * <li> set the commanded state, and, Send request to command station to start 016 * sending DCC operations packet to track</li> 017 * <li> Wait for response message from command station. (valid response list 018 * follows)</li> 019 * <li> Send request to command station to stop sending DCC operations packet to 020 * track</li> 021 * <li> Wait for response from command station 022 * <ul> 023 * <li>If Success Message, set Known State to Commanded State</li> 024 * <li>If error message, repeat previous step</li> 025 * </ul> 026 * </li> 027 * </ul> 028 * <p> 029 * NOTE: Some XpressNet Command stations take no action when the message 030 * generated during the third step is received. 031 * <p> 032 * Valid response messages are command station dependent, but there are 4 033 * possibilities: 034 * <ul> 035 * <li> a "Command Successfully Received..." (aka "OK") message</li> 036 * <li> a "Feedback Response Message" indicating the message is for a turnout 037 * with feedback</li> 038 * <li> a "Feedback Response Message" indicating the message is for a turnout 039 * without feedback</li> 040 * <li> The XpressNet protocol allows for no response. </li> 041 * </ul> 042 * <p> 043 * Response NOTE 1: The "Command Successfully Received..." message is generated 044 * by the lenz LIxxx interfaces when it successfully transfers the command to 045 * the command station. When this happens, the command station generates no 046 * useable response message. 047 * <p> 048 * Response NOTE 2: Currently the only command stations known to generate 049 * Feedback response messages are the Lenz LZ100 and LZV100. 050 * <p> 051 * Response NOTE 3: Software version 3.2 and above LZ100 and LZV100 may send 052 * either a Feedback response or no response at all. All other known command 053 * stations generate no response. 054 * <p> 055 * Response NOTE 4: The Feedback response messages may be generated 056 * asynchronously 057 * <p> 058 * Response NOTE 5: Feedback response messages may contain feedback for more 059 * than one device. The devices included in the response may or may not be 060 * stationary decoders (they can also be feedback encoders see 061 * {@link XNetSensor}). 062 * <p> 063 * Response NOTE 6: The last situation situation is not currently handled. The 064 * supported interfaces garantee at least an "OK" message will be sent to the 065 * computer 066 * <p> 067 * What is done with each of the response messages depends on which feedback 068 * mode is in use. "DIRECT,"MONITORING", and "EXACT" feedback mode are supported 069 * directly by this class. 070 * <p> 071 * "DIRECT" mode instantly triggers step 3 when any valid response message for 072 * this turnout is received from the command station or computer interface. 073 * <p> 074 * "SIGNAL" mode is identical to "DIRECT" mode, except it skips step 2. i.e. it 075 * triggers step 3 without receiving any reply from the command station. 076 * <p> 077 * "MONITORING" mode is an extention to direct mode. In monitoring mode, a 078 * feedback response message (for a turnout with or without feedback) is 079 * interpreted to set the known state of the turnout based on information 080 * provided by the command station. 081 * <p> 082 * "MONITORING" mode will interpret the feedback response messages when they are 083 * generated by external sources (fascia controls or other XpressNet devices) 084 * and that information is received by the computer. 085 * <p> 086 * "EXACT" mode is an extention of "MONITORING" mode. In addition to 087 * interpretting all feedback messages from the command station, "EXACT" mode 088 * will monitor the "motion complete" bit of the feedback response. 089 * <p> 090 * For turnouts without feedback, the motion complete bit is always set, so 091 * "EXACT" mode handles these messages as though the specified feedback mode is 092 * "MONITORING" mode. 093 * <p> 094 * For turnouts with feedback, "EXACT" mode polls the command station until the 095 * motion complete bit is set before triggering step 3 of the turnout operation 096 * sequence. 097 * <p> 098 * "EXACT" mode will interpret the feedback response messages when they are 099 * generated by external sources (fascia controls or other XpressNet devices) 100 * and that information is received by the computer. 101 * <p> 102 * NOTE: For LZ100 and LZV100 command stations prior to version 3.2, it may be 103 * necessary to poll for the feedback response data. 104 * 105 * @author Bob Jacobsen Copyright (C) 2001 106 * @author Paul Bender Copyright (C) 2003-2010 107 */ 108public class XNetTurnout extends AbstractTurnout implements XNetListener { 109 110 /* State information */ 111 protected static final int OFFSENT = 1; 112 protected static final int COMMANDSENT = 2; 113 protected static final int STATUSREQUESTSENT = 4; 114 protected static final int QUEUEDMESSAGE = 8; 115 protected static final int IDLE = 0; 116 protected int internalState = IDLE; 117 118 /* Static arrays to hold Lenz specific feedback mode information */ 119 static String[] modeNames = null; 120 static int[] modeValues = null; 121 122 @GuardedBy("this") 123 protected int _mThrown = jmri.Turnout.THROWN; 124 @GuardedBy("this") 125 protected int _mClosed = jmri.Turnout.CLOSED; 126 127 protected int mNumber; // XpressNet turnout number 128 final XNetTurnoutStateListener _stateListener; // Internal class object 129 130 // A queue to hold outstanding messages 131 @GuardedBy("this") 132 protected final Queue<RequestMessage> requestList; 133 134 @GuardedBy("this") 135 protected RequestMessage lastMsg = null; 136 137 protected final String _prefix; // default 138 protected final XNetTrafficController tc; 139 140 public XNetTurnout(String prefix, int pNumber, XNetTrafficController controller) { // a human-readable turnout number must be specified! 141 super(prefix + "T" + pNumber); 142 tc = controller; 143 _prefix = prefix; 144 mNumber = pNumber; 145 146 requestList = new LinkedList<>(); 147 148 /* Add additional feedback types information */ 149 _validFeedbackTypes |= MONITORING | EXACT | SIGNAL; 150 151 // Default feedback mode is MONITORING 152 _activeFeedbackType = MONITORING; 153 154 setModeInformation(_validFeedbackNames, _validFeedbackModes); 155 156 // set the mode names and values based on the static values. 157 _validFeedbackNames = getModeNames(); 158 _validFeedbackModes = getModeValues(); 159 160 // Register to get property change information from the superclass 161 _stateListener = new XNetTurnoutStateListener(this); 162 this.addPropertyChangeListener(_stateListener); 163 // Finally, request the current state from the layout. 164 tc.getFeedbackMessageCache().requestCachedStateFromLayout(this); 165 } 166 167 /** 168 * Set the mode information for XpressNet Turnouts. 169 */ 170 private static synchronized void setModeInformation(String[] feedbackNames, int[] feedbackModes) { 171 // if it hasn't been done already, create static arrays to hold 172 // the Lenz specific feedback information. 173 if (modeNames == null) { 174 if (feedbackNames.length != feedbackModes.length) { 175 log.error("int and string feedback arrays different length"); 176 } 177 modeNames = Arrays.copyOf(feedbackNames, feedbackNames.length + 3); 178 modeValues = Arrays.copyOf(feedbackModes, feedbackNames.length + 3); 179 modeNames[feedbackNames.length] = "MONITORING"; 180 modeValues[feedbackNames.length] = MONITORING; 181 modeNames[feedbackNames.length + 1] = "EXACT"; 182 modeValues[feedbackNames.length + 1] = EXACT; 183 modeNames[feedbackNames.length + 2] = "SIGNAL"; 184 modeValues[feedbackNames.length + 2] = SIGNAL; 185 } 186 } 187 188 static int[] getModeValues() { 189 return modeValues; 190 } 191 192 static String[] getModeNames() { 193 return modeNames; 194 } 195 196 public int getNumber() { 197 return mNumber; 198 } 199 200 /** 201 * Set the Commanded State. 202 * This method overides {@link jmri.implementation.AbstractTurnout#setCommandedState(int)}. 203 */ 204 @Override 205 public void setCommandedState(int s) { 206 if (log.isDebugEnabled()) { 207 log.debug("set commanded state for XNet turnout {} to {}", getSystemName(), s); 208 } 209 synchronized (this) { 210 newCommandedState(s); 211 } 212 myOperator = getTurnoutOperator(); // MUST set myOperator before starting the thread 213 if (myOperator == null) { 214 forwardCommandChangeToLayout(s); 215 synchronized (this) { 216 newKnownState(INCONSISTENT); 217 } 218 } else { 219 myOperator.start(); 220 } 221 } 222 223 /** 224 * {@inheritDoc} 225 * Sends an XpressNet command. 226 */ 227 @Override 228 protected synchronized void forwardCommandChangeToLayout(int s) { 229 if (s != _mClosed && s != _mThrown) { 230 log.warn("Turnout {}: state {} not forwarded to layout.", mNumber, s); 231 return; 232 } 233 // get the right packet 234 XNetMessage msg = XNetMessage.getTurnoutCommandMsg(mNumber, 235 (s & _mClosed) != 0, 236 (s & _mThrown) != 0, 237 true); 238 if (getFeedbackMode() == SIGNAL) { 239 msg.setTimeout(0); // Set the timeout to 0, so the off message can 240 // be sent immediately. 241 // leave the next line commented out for now. 242 // It may be enabled later to allow SIGNAL mode to ignore 243 // directed replies, which lets the traffic controller move on 244 // to the next message without waiting. 245 //msg.setBroadcastReply(); 246 tc.sendXNetMessage(msg, null); 247 sendOffMessage(); 248 } else { 249 queueMessage(msg, COMMANDSENT, this); 250 } 251 } 252 253 @Override 254 protected void turnoutPushbuttonLockout(boolean _pushButtonLockout) { 255 log.debug("Send command to {} Pushbutton {}T{}", (_pushButtonLockout ? "Lock" : "Unlock"), _prefix, mNumber); 256 } 257 258 /** 259 * Request an update on status by sending an XpressNet message. 260 */ 261 @Override 262 public void requestUpdateFromLayout() { 263 // This will handle ONESENSOR and TWOSENSOR feedback modes. 264 super.requestUpdateFromLayout(); 265 266 // To do this, we send an XpressNet Accessory Decoder Information 267 // Request. 268 // The generated message works for Feedback modules and turnouts 269 // with feedback, but the address passed is translated as though it 270 // is a turnout address. As a result, we substitute our base 271 // address in for the address. after the message is returned. 272 XNetMessage msg = XNetMessage.getFeedbackRequestMsg(mNumber, 273 ((mNumber - 1) % 4) < 2); 274 queueMessage(msg,IDLE,null); //status is returned via the manager. 275 276 } 277 278 /** 279 * {@inheritDoc} 280 */ 281 @Override 282 public synchronized void setInverted(boolean inverted) { 283 log.debug("Inverting Turnout State for turnout {}T{}", _prefix, mNumber); 284 if (inverted) { 285 _mThrown = jmri.Turnout.CLOSED; 286 _mClosed = jmri.Turnout.THROWN; 287 } else { 288 _mThrown = jmri.Turnout.THROWN; 289 _mClosed = jmri.Turnout.CLOSED; 290 } 291 super.setInverted(inverted); 292 } 293 294 @Override 295 public boolean canInvert() { 296 return true; 297 } 298 299 /** 300 * Package protected class which allows the Manger to send 301 * a feedback message at initialization without changing the state of the 302 * turnout with respect to whether or not a feedback request was sent. This 303 * is used only when the turnout is created by on layout feedback. 304 * @param l Message to initialize 305 */ 306 synchronized void initmessage(XNetReply l) { 307 int oldState = internalState; 308 message(l); 309 internalState = oldState; 310 } 311 312 /** 313 * Handle an incoming message from the XpressNet. 314 */ 315 @Override 316 public synchronized void message(XNetReply l) { 317 log.debug("received message: {}", l); 318 if (internalState == OFFSENT) { 319 if (l.isOkMessage() && !l.isUnsolicited()) { 320 /* the command was successfully received */ 321 synchronized (this) { 322 newKnownState(getCommandedState()); 323 } 324 sendQueuedMessage(); 325 return; 326 } else if (l.isRetransmittableErrorMsg()) { 327 return; // don't do anything, the Traffic 328 // Controller is handling retransmitting 329 // this one. 330 } else { 331 /* Default Behavior: If anything other than an OK message 332 is received, Send another OFF message. */ 333 log.debug("Message is not OK message. Message received was: {}", l); 334 sendOffMessage(); 335 } 336 } 337 338 switch (getFeedbackMode()) { 339 case EXACT: 340 handleExactModeFeedback(l); 341 break; 342 case MONITORING: 343 handleMonitoringModeFeedback(l); 344 break; 345 case DIRECT: 346 default: 347 // Default is direct mode 348 handleDirectModeFeedback(l); 349 } 350 } 351 352 /** 353 * Listen for the messages to the LI100/LI101. 354 */ 355 @Override 356 public synchronized void message(XNetMessage l) { 357 log.debug("received outgoing message {} for turnout {}",l,getSystemName()); 358 // we want to verify this is the last message we sent 359 // so use == not .equals 360 if(lastMsg!=null && l == lastMsg.msg){ 361 //if this is the last message we sent, set the state appropriately 362 internalState = lastMsg.getState(); 363 // and set lastMsg to null 364 lastMsg = null; 365 } 366 } 367 368 /** 369 * Handle a timeout notification. 370 */ 371 @Override 372 public synchronized void notifyTimeout(XNetMessage msg) { 373 log.debug("Notified of timeout on message {}", msg); 374 // If we're in the OFFSENT state, we need to send another OFF message. 375 if (internalState == OFFSENT) { 376 sendOffMessage(); 377 } 378 } 379 380 /** 381 * With Direct Mode feedback, if we see ANY valid response to our 382 * request, we ask the command station to stop sending information 383 * to the stationary decoder. 384 * <p> 385 * No effort is made to interpret feedback when using direct mode. 386 * 387 * @param l an {@link XNetReply} message 388 */ 389 private synchronized void handleDirectModeFeedback(XNetReply l) { 390 /* If commanded state does not equal known state, we are 391 going to check to see if one of the following conditions 392 applies: 393 1) The received message is a feedback message for a turnout 394 and one of the two addresses to which it applies is our 395 address 396 2) We receive an "OK" message, indicating the command was 397 successfully sent 398 399 If either of these two cases occur, we trigger an off message 400 */ 401 402 log.debug("Handle Message for turnout {} in DIRECT feedback mode ", mNumber); 403 if (getCommandedState() != getKnownState() || internalState == COMMANDSENT) { 404 if (l.isOkMessage()) { 405 // Finally, we may just receive an OK message. 406 log.debug("Turnout {} DIRECT feedback mode - OK message triggering OFF message.", mNumber); 407 } else { 408 // implicitly checks for isFeedbackBroadcastMessage() 409 if (!l.selectTurnoutFeedback(mNumber).isPresent()) { 410 return; 411 } 412 log.debug("Turnout {} DIRECT feedback mode - directed reply received.", mNumber); 413 } 414 sendOffMessage(); 415 // Explicitly send two off messages in Direct Mode 416 sendOffMessage(); 417 } 418 } 419 420 /** 421 * With Monitoring Mode feedback, if we see a feedback message, we 422 * interpret that message and use it to display our feedback. 423 * <p> 424 * After we send a request to operate a turnout, We ask the command 425 * station to stop sending information to the stationary decoder 426 * when the either a feedback message or an "OK" message is received. 427 * 428 * @param l an {@link XNetReply} message 429 */ 430 private synchronized void handleMonitoringModeFeedback(XNetReply l) { 431 /* In Monitoring Mode, We have two cases to check if CommandedState 432 does not equal KnownState, otherwise, we only want to check to 433 see if the messages we receive indicate this turnout chagned 434 state 435 */ 436 log.debug("Handle Message for turnout {} in MONITORING feedback mode ", mNumber); 437 if (internalState == IDLE || internalState == STATUSREQUESTSENT) { 438 if (l.onTurnoutFeedback(mNumber, this::parseFeedbackMessage)) { 439 log.debug("Turnout {} MONITORING feedback mode - state change from feedback.", mNumber); 440 } 441 } else if (getCommandedState() != getKnownState() 442 || internalState == COMMANDSENT) { 443 if (l.isOkMessage()) { 444 // Finally, we may just receive an OK message. 445 log.debug("Turnout {} MONITORING feedback mode - OK message triggering OFF message.", mNumber); 446 sendOffMessage(); 447 } else { 448 // In Monitoring mode, treat both turnouts with feedback 449 // and turnouts without feedback as turnouts without 450 // feedback. i.e. just interpret the feedback 451 // message, don't check to see if the motion is complete 452 // implicitly checks for isFeedbackBroadcastMessage() 453 if (l.onTurnoutFeedback(mNumber, this::parseFeedbackMessage)) { 454 // We need to tell the turnout to shut off the output. 455 log.debug("Turnout {} MONITORING feedback mode - state change from feedback, CommandedState != KnownState.", mNumber); 456 sendOffMessage(); 457 } 458 } 459 } 460 } 461 462 /** 463 * With Exact Mode feedback, if we see a feedback message, we 464 * interpret that message and use it to display our feedback. 465 * <p> 466 * After we send a request to operate a turnout, We ask the command 467 * station to stop sending information to the stationary decoder 468 * when the either a feedback message or an "OK" message is received. 469 * 470 * @param reply The reply message to process 471 */ 472 private synchronized void handleExactModeFeedback(XNetReply reply) { 473 // We have three cases to check if CommandedState does 474 // not equal KnownState, otherwise, we only want to check to 475 // see if the messages we receive indicate this turnout chagned 476 // state 477 log.debug("Handle Message for turnout {} in EXACT feedback mode ", mNumber); 478 if (getCommandedState() == getKnownState() 479 && (internalState == IDLE || internalState == STATUSREQUESTSENT)) { 480 // This is a feedback message, we need to check and see if it 481 // indicates this turnout is to change state or if it is for 482 // another turnout. 483 if (reply.onTurnoutFeedback(mNumber, this::parseFeedbackMessage)) { 484 log.debug("Turnout {} EXACT feedback mode - state change from feedback.", mNumber); 485 } 486 } else if (getCommandedState() != getKnownState() 487 || internalState == COMMANDSENT 488 || internalState == STATUSREQUESTSENT) { 489 if (reply.isOkMessage()) { 490 // Finally, we may just receive an OK message. 491 log.debug("Turnout {} EXACT feedback mode - OK message triggering OFF message.", mNumber); 492 sendOffMessage(); 493 } else { 494 // implicitly checks for isFeedbackBroadcastMessage() 495 reply.selectTurnoutFeedback(mNumber).ifPresent(l -> { 496 int messageType = l.getType(); 497 switch (messageType) { 498 case 1: { 499 // The first case is that we receive a message for 500 // this turnout and this turnout provides feedback. 501 // In this case, we want to check to see if the 502 // turnout has completed its movement before doing 503 // anything else. 504 if (!l.isMotionComplete()) { 505 log.debug("Turnout {} EXACT feedback mode - state change from feedback, CommandedState!=KnownState - motion not complete", mNumber); 506 // If the motion is NOT complete, send a feedback 507 // request for this nibble 508 XNetMessage msg = XNetMessage.getFeedbackRequestMsg( 509 mNumber, ((mNumber % 4) <= 1)); 510 queueMessage(msg,STATUSREQUESTSENT ,null); //status is returned via the manager. 511 return; 512 } else { 513 log.debug("Turnout {} EXACT feedback mode - state change from feedback, CommandedState!=KnownState - motion complete", mNumber); 514 } 515 break; 516 } 517 case 0: 518 log.debug("Turnout {} EXACT feedback mode - state change from feedback, CommandedState!=KnownState - motion complete", mNumber); 519 // The second case is that we receive a message about 520 // this turnout, and this turnout does not provide 521 // feedback. In this case, we want to check the 522 // contents of the message and act accordingly. 523 break; 524 default: return; 525 } 526 parseFeedbackMessage(l); 527 // We need to tell the turnout to shut off the output. 528 sendOffMessage(); 529 }); 530 } 531 } 532 } 533 534 /** 535 * Send an "Off" message to the decoder for this output. 536 */ 537 @SuppressWarnings("deprecation") // The method getId() from the type Thread is deprecated since version 19 538 // The replacement Thread.threadId() isn't available before version 19 539 protected synchronized void sendOffMessage() { 540 // We need to tell the turnout to shut off the output. 541 if (log.isDebugEnabled()) { 542 log.debug("Sending off message for turnout {} commanded state={}", mNumber, getCommandedState()); 543 log.debug("Current Thread ID: {} Thread Name {}", java.lang.Thread.currentThread().getId(), java.lang.Thread.currentThread().getName()); 544 } 545 XNetMessage msg = getOffMessage(); 546 lastMsg = new RequestMessage(msg,OFFSENT,this); 547 this.internalState = OFFSENT; 548 newKnownState(getCommandedState()); 549 // Then send the message. 550 tc.sendHighPriorityXNetMessage(msg, this); 551 } 552 553 protected synchronized XNetMessage getOffMessage(){ 554 return ( XNetMessage.getTurnoutCommandMsg(mNumber, 555 getCommandedState() == _mClosed, 556 getCommandedState() == _mThrown, 557 false) ); 558 } 559 560 /** 561 * Parse the feedback message, and set the status of the turnout 562 * accordingly. 563 * 564 * @param l turnout feedback item 565 * 566 * @return 0 if address matches our turnout -1 otherwise 567 */ 568 private synchronized boolean parseFeedbackMessage(FeedbackItem l) { 569 log.debug("Message for turnout {}", mNumber); 570 switch (l.getTurnoutStatus()) { 571 case THROWN: 572 newKnownState(_mThrown); 573 return true; 574 case CLOSED: 575 newKnownState(_mClosed); 576 return true; 577 default: 578 // the state is unknown or inconsistent. If the command state 579 // does not equal the known state, and the command repeat the 580 // last command 581 if (getCommandedState() != getKnownState()) { 582 forwardCommandChangeToLayout(getCommandedState()); 583 } else { 584 sendQueuedMessage(); 585 } 586 return false; 587 } 588 } 589 590 @Override 591 public void dispose() { 592 this.removePropertyChangeListener(_stateListener); 593 super.dispose(); 594 } 595 596 /** 597 * Internal class to use for listening to state changes. 598 */ 599 private static class XNetTurnoutStateListener implements java.beans.PropertyChangeListener { 600 601 final XNetTurnout _turnout; 602 603 XNetTurnoutStateListener(XNetTurnout turnout) { 604 _turnout = turnout; 605 } 606 607 /** 608 * If we're not using DIRECT feedback mode, we need to listen for 609 * state changes to know when to send an OFF message after we set the 610 * known state. 611 * If we're using DIRECT mode, all of this is handled from the 612 * XpressNet Messages. 613 * @param event The event that causes this operation 614 */ 615 @Override 616 public void propertyChange(java.beans.PropertyChangeEvent event) { 617 log.debug("propertyChange called"); 618 // If we're using DIRECT feedback mode, we don't care what we see here 619 if (_turnout.getFeedbackMode() != DIRECT) { 620 if (log.isDebugEnabled()) { 621 log.debug("propertyChange Not Direct Mode property: {} old value {} new value {}", event.getPropertyName(), event.getOldValue(), event.getNewValue()); 622 } 623 if (event.getPropertyName().equals("KnownState")) { 624 // Check to see if this is a change in the status 625 // triggered by a device on the layout, or a change in 626 // status we triggered. 627 int oldKnownState = (Integer) event.getOldValue(); 628 int curKnownState = (Integer) event.getNewValue(); 629 log.debug("propertyChange KnownState - old value {} new value {}", oldKnownState, curKnownState); 630 if (curKnownState != INCONSISTENT 631 && _turnout.getCommandedState() == oldKnownState) { 632 // This was triggered by feedback on the layout, change 633 // the commanded state to reflect the new Known State 634 if (log.isDebugEnabled()) { 635 log.debug("propertyChange CommandedState: {}", _turnout.getCommandedState()); 636 } 637 _turnout.newCommandedState(curKnownState); 638 } else { 639 // Since we always set the KnownState to 640 // INCONSISTENT when we send a command, If the old 641 // known state is INCONSISTENT, we just want to send 642 // an off message 643 if (oldKnownState == INCONSISTENT) { 644 if (log.isDebugEnabled()) { 645 log.debug("propertyChange CommandedState: {}", _turnout.getCommandedState()); 646 } 647 _turnout.sendOffMessage(); 648 } 649 } 650 } 651 } 652 } 653 654 } 655 656 /** 657 * Send message from queue. 658 */ 659 protected synchronized void sendQueuedMessage() { 660 661 lastMsg = null; 662 // check to see if the queue has a message in it, and if it does, 663 // remove the first message 664 lastMsg = requestList.poll(); 665 // if the queue is not empty, remove the first message 666 // from the queue, send the message, and set the state machine 667 // to the required state. 668 if (lastMsg != null) { 669 log.debug("sending message to traffic controller"); 670 if(lastMsg.listener!=null) { 671 internalState = QUEUEDMESSAGE; 672 } else { 673 internalState = lastMsg.state; 674 } 675 tc.sendXNetMessage(lastMsg.getMsg(), lastMsg.getListener()); 676 } else { 677 log.debug("message queue empty"); 678 // if the queue is empty, set the state to idle. 679 internalState = IDLE; 680 } 681 } 682 683 /** 684 * Queue a message. 685 * @param m Message to send 686 * @param s sequence 687 * @param l Listener to get notification of completion 688 */ 689 protected synchronized void queueMessage(XNetMessage m, int s, XNetListener l) { 690 log.debug("adding message {} to message queue. Current Internal State {}",m,internalState); 691 // put the message in the queue 692 RequestMessage msg = new RequestMessage(m, s, l); 693 // the queue is unbounded; can't throw exceptions 694 requestList.add(msg); 695 // if the state is idle, trigger the message send 696 if (internalState == IDLE ) { 697 sendQueuedMessage(); 698 } 699 } 700 701 /** 702 * Internal class to hold a request message, along with the associated throttle state. 703 */ 704 protected static class RequestMessage { 705 706 private final int state; 707 private final XNetMessage msg; 708 private final XNetListener listener; 709 710 RequestMessage(XNetMessage m, int s, XNetListener listener) { 711 state = s; 712 msg = m; 713 this.listener = listener; 714 } 715 716 int getState() { 717 return state; 718 } 719 720 XNetMessage getMsg() { 721 return msg; 722 } 723 724 XNetListener getListener() { 725 return listener; 726 } 727 } 728 729 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(XNetTurnout.class); 730 731}