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 protected synchronized void sendOffMessage() { 538 // We need to tell the turnout to shut off the output. 539 if (log.isDebugEnabled()) { 540 log.debug("Sending off message for turnout {} commanded state={}", mNumber, getCommandedState()); 541 log.debug("Current Thread ID: {} Thread Name {}", java.lang.Thread.currentThread().getId(), java.lang.Thread.currentThread().getName()); 542 } 543 XNetMessage msg = getOffMessage(); 544 lastMsg = new RequestMessage(msg,OFFSENT,this); 545 this.internalState = OFFSENT; 546 newKnownState(getCommandedState()); 547 // Then send the message. 548 tc.sendHighPriorityXNetMessage(msg, this); 549 } 550 551 protected synchronized XNetMessage getOffMessage(){ 552 return ( XNetMessage.getTurnoutCommandMsg(mNumber, 553 getCommandedState() == _mClosed, 554 getCommandedState() == _mThrown, 555 false) ); 556 } 557 558 /** 559 * Parse the feedback message, and set the status of the turnout 560 * accordingly. 561 * 562 * @param l turnout feedback item 563 * 564 * @return 0 if address matches our turnout -1 otherwise 565 */ 566 private synchronized boolean parseFeedbackMessage(FeedbackItem l) { 567 log.debug("Message for turnout {}", mNumber); 568 switch (l.getTurnoutStatus()) { 569 case THROWN: 570 newKnownState(_mThrown); 571 return true; 572 case CLOSED: 573 newKnownState(_mClosed); 574 return true; 575 default: 576 // the state is unknown or inconsistent. If the command state 577 // does not equal the known state, and the command repeat the 578 // last command 579 if (getCommandedState() != getKnownState()) { 580 forwardCommandChangeToLayout(getCommandedState()); 581 } else { 582 sendQueuedMessage(); 583 } 584 return false; 585 } 586 } 587 588 @Override 589 public void dispose() { 590 this.removePropertyChangeListener(_stateListener); 591 super.dispose(); 592 } 593 594 /** 595 * Internal class to use for listening to state changes. 596 */ 597 private static class XNetTurnoutStateListener implements java.beans.PropertyChangeListener { 598 599 final XNetTurnout _turnout; 600 601 XNetTurnoutStateListener(XNetTurnout turnout) { 602 _turnout = turnout; 603 } 604 605 /** 606 * If we're not using DIRECT feedback mode, we need to listen for 607 * state changes to know when to send an OFF message after we set the 608 * known state. 609 * If we're using DIRECT mode, all of this is handled from the 610 * XpressNet Messages. 611 * @param event The event that causes this operation 612 */ 613 @Override 614 public void propertyChange(java.beans.PropertyChangeEvent event) { 615 log.debug("propertyChange called"); 616 // If we're using DIRECT feedback mode, we don't care what we see here 617 if (_turnout.getFeedbackMode() != DIRECT) { 618 if (log.isDebugEnabled()) { 619 log.debug("propertyChange Not Direct Mode property: {} old value {} new value {}", event.getPropertyName(), event.getOldValue(), event.getNewValue()); 620 } 621 if (event.getPropertyName().equals("KnownState")) { 622 // Check to see if this is a change in the status 623 // triggered by a device on the layout, or a change in 624 // status we triggered. 625 int oldKnownState = (Integer) event.getOldValue(); 626 int curKnownState = (Integer) event.getNewValue(); 627 log.debug("propertyChange KnownState - old value {} new value {}", oldKnownState, curKnownState); 628 if (curKnownState != INCONSISTENT 629 && _turnout.getCommandedState() == oldKnownState) { 630 // This was triggered by feedback on the layout, change 631 // the commanded state to reflect the new Known State 632 if (log.isDebugEnabled()) { 633 log.debug("propertyChange CommandedState: {}", _turnout.getCommandedState()); 634 } 635 _turnout.newCommandedState(curKnownState); 636 } else { 637 // Since we always set the KnownState to 638 // INCONSISTENT when we send a command, If the old 639 // known state is INCONSISTENT, we just want to send 640 // an off message 641 if (oldKnownState == INCONSISTENT) { 642 if (log.isDebugEnabled()) { 643 log.debug("propertyChange CommandedState: {}", _turnout.getCommandedState()); 644 } 645 _turnout.sendOffMessage(); 646 } 647 } 648 } 649 } 650 } 651 652 } 653 654 /** 655 * Send message from queue. 656 */ 657 protected synchronized void sendQueuedMessage() { 658 659 lastMsg = null; 660 // check to see if the queue has a message in it, and if it does, 661 // remove the first message 662 lastMsg = requestList.poll(); 663 // if the queue is not empty, remove the first message 664 // from the queue, send the message, and set the state machine 665 // to the required state. 666 if (lastMsg != null) { 667 log.debug("sending message to traffic controller"); 668 if(lastMsg.listener!=null) { 669 internalState = QUEUEDMESSAGE; 670 } else { 671 internalState = lastMsg.state; 672 } 673 tc.sendXNetMessage(lastMsg.getMsg(), lastMsg.getListener()); 674 } else { 675 log.debug("message queue empty"); 676 // if the queue is empty, set the state to idle. 677 internalState = IDLE; 678 } 679 } 680 681 /** 682 * Queue a message. 683 * @param m Message to send 684 * @param s sequence 685 * @param l Listener to get notification of completion 686 */ 687 protected synchronized void queueMessage(XNetMessage m, int s, XNetListener l) { 688 log.debug("adding message {} to message queue. Current Internal State {}",m,internalState); 689 // put the message in the queue 690 RequestMessage msg = new RequestMessage(m, s, l); 691 // the queue is unbounded; can't throw exceptions 692 requestList.add(msg); 693 // if the state is idle, trigger the message send 694 if (internalState == IDLE ) { 695 sendQueuedMessage(); 696 } 697 } 698 699 /** 700 * Internal class to hold a request message, along with the associated throttle state. 701 */ 702 protected static class RequestMessage { 703 704 private final int state; 705 private final XNetMessage msg; 706 private final XNetListener listener; 707 708 RequestMessage(XNetMessage m, int s, XNetListener listener) { 709 state = s; 710 msg = m; 711 this.listener = listener; 712 } 713 714 int getState() { 715 return state; 716 } 717 718 XNetMessage getMsg() { 719 return msg; 720 } 721 722 XNetListener getListener() { 723 return listener; 724 } 725 } 726 727 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(XNetTurnout.class); 728 729}