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 // for mode ONESENSOR or TWOSENSOR CommandedState not set KnownState 217 if ( ! (getFeedbackMode() == ONESENSOR || getFeedbackMode() == TWOSENSOR)) { 218 newKnownState(INCONSISTENT); 219 } 220 } 221 } else { 222 myOperator.start(); 223 } 224 } 225 226 /** 227 * {@inheritDoc} 228 * Sends an XpressNet command. 229 */ 230 @Override 231 protected synchronized void forwardCommandChangeToLayout(int s) { 232 if (s != _mClosed && s != _mThrown) { 233 log.warn("Turnout {}: state {} not forwarded to layout.", mNumber, s); 234 return; 235 } 236 // get the right packet 237 XNetMessage msg = XNetMessage.getTurnoutCommandMsg(mNumber, 238 (s & _mClosed) != 0, 239 (s & _mThrown) != 0, 240 true); 241 if (getFeedbackMode() == SIGNAL) { 242 msg.setTimeout(0); // Set the timeout to 0, so the off message can 243 // be sent immediately. 244 // leave the next line commented out for now. 245 // It may be enabled later to allow SIGNAL mode to ignore 246 // directed replies, which lets the traffic controller move on 247 // to the next message without waiting. 248 //msg.setBroadcastReply(); 249 tc.sendXNetMessage(msg, null); 250 sendOffMessage(); 251 } else { 252 queueMessage(msg, COMMANDSENT, this); 253 } 254 } 255 256 @Override 257 protected void turnoutPushbuttonLockout(boolean _pushButtonLockout) { 258 log.debug("Send command to {} Pushbutton {}T{}", (_pushButtonLockout ? "Lock" : "Unlock"), _prefix, mNumber); 259 } 260 261 /** 262 * Request an update on status by sending an XpressNet message. 263 */ 264 @Override 265 public void requestUpdateFromLayout() { 266 // This will handle ONESENSOR and TWOSENSOR feedback modes. 267 super.requestUpdateFromLayout(); 268 269 // To do this, we send an XpressNet Accessory Decoder Information 270 // Request. 271 // The generated message works for Feedback modules and turnouts 272 // with feedback, but the address passed is translated as though it 273 // is a turnout address. As a result, we substitute our base 274 // address in for the address. after the message is returned. 275 XNetMessage msg = XNetMessage.getFeedbackRequestMsg(mNumber, 276 ((mNumber - 1) % 4) < 2); 277 queueMessage(msg,IDLE,null); //status is returned via the manager. 278 279 } 280 281 /** 282 * {@inheritDoc} 283 */ 284 @Override 285 public synchronized void setInverted(boolean inverted) { 286 log.debug("Inverting Turnout State for turnout {}T{}", _prefix, mNumber); 287 if (inverted) { 288 _mThrown = jmri.Turnout.CLOSED; 289 _mClosed = jmri.Turnout.THROWN; 290 } else { 291 _mThrown = jmri.Turnout.THROWN; 292 _mClosed = jmri.Turnout.CLOSED; 293 } 294 super.setInverted(inverted); 295 } 296 297 @Override 298 public boolean canInvert() { 299 return true; 300 } 301 302 /** 303 * Package protected class which allows the Manger to send 304 * a feedback message at initialization without changing the state of the 305 * turnout with respect to whether or not a feedback request was sent. This 306 * is used only when the turnout is created by on layout feedback. 307 * @param l Message to initialize 308 */ 309 synchronized void initmessage(XNetReply l) { 310 int oldState = internalState; 311 message(l); 312 internalState = oldState; 313 } 314 315 /** 316 * Handle an incoming message from the XpressNet. 317 */ 318 @Override 319 public synchronized void message(XNetReply l) { 320 log.debug("received message: {}", l); 321 if (internalState == OFFSENT) { 322 if (l.isOkMessage() && !l.isUnsolicited()) { 323 /* the command was successfully received */ 324 synchronized (this) { 325 // for mode ONESENSOR or TWOSENSOR CommandedState not set KnownState 326 if ( ! (getFeedbackMode() == ONESENSOR || getFeedbackMode() == TWOSENSOR)) { 327 newKnownState(getCommandedState()); 328 } 329 } 330 sendQueuedMessage(); 331 return; 332 } else if (l.isRetransmittableErrorMsg()) { 333 return; // don't do anything, the Traffic 334 // Controller is handling retransmitting 335 // this one. 336 } else { 337 /* Default Behavior: If anything other than an OK message 338 is received, Send another OFF message. */ 339 log.debug("Message is not OK message. Message received was: {}", l); 340 sendOffMessage(); 341 } 342 } 343 344 switch (getFeedbackMode()) { 345 case EXACT: 346 handleExactModeFeedback(l); 347 break; 348 case MONITORING: 349 handleMonitoringModeFeedback(l); 350 break; 351 case ONESENSOR: 352 case TWOSENSOR: 353 handleSensorModeFeedback(l); 354 break; 355 case DIRECT: 356 default: 357 // Default is direct mode 358 handleDirectModeFeedback(l); 359 } 360 } 361 362 /** 363 * Listen for the messages to the LI100/LI101. 364 */ 365 @Override 366 public synchronized void message(XNetMessage l) { 367 log.debug("received outgoing message {} for turnout {}",l,getSystemName()); 368 // we want to verify this is the last message we sent 369 // so use == not .equals 370 if(lastMsg!=null && l == lastMsg.msg){ 371 //if this is the last message we sent, set the state appropriately 372 internalState = lastMsg.getState(); 373 // and set lastMsg to null 374 lastMsg = null; 375 } 376 } 377 378 /** 379 * Handle a timeout notification. 380 */ 381 @Override 382 public synchronized void notifyTimeout(XNetMessage msg) { 383 log.debug("Notified of timeout on message {}", msg); 384 // If we're in the OFFSENT state, we need to send another OFF message. 385 if (internalState == OFFSENT) { 386 sendOffMessage(); 387 } 388 } 389 390 /** 391 * With Direct Mode feedback, if we see ANY valid response to our 392 * request, we ask the command station to stop sending information 393 * to the stationary decoder. 394 * <p> 395 * No effort is made to interpret feedback when using direct mode. 396 * 397 * @param l an {@link XNetReply} message 398 */ 399 private synchronized void handleDirectModeFeedback(XNetReply l) { 400 /* If commanded state does not equal known state, we are 401 going to check to see if one of the following conditions 402 applies: 403 1) The received message is a feedback message for a turnout 404 and one of the two addresses to which it applies is our 405 address 406 2) We receive an "OK" message, indicating the command was 407 successfully sent 408 409 If either of these two cases occur, we trigger an off message 410 */ 411 412 log.debug("Handle Message for turnout {} in DIRECT feedback mode ", mNumber); 413 if (getCommandedState() != getKnownState() || internalState == COMMANDSENT) { 414 if (l.isOkMessage()) { 415 // Finally, we may just receive an OK message. 416 log.debug("Turnout {} DIRECT feedback mode - OK message triggering OFF message.", mNumber); 417 } else { 418 // implicitly checks for isFeedbackBroadcastMessage() 419 if (!l.selectTurnoutFeedback(mNumber).isPresent()) { 420 return; 421 } 422 log.debug("Turnout {} DIRECT feedback mode - directed reply received.", mNumber); 423 } 424 sendOffMessage(); 425 // Explicitly send two off messages in Direct Mode 426 sendOffMessage(); 427 } 428 } 429 430 /** 431 * With Monitoring Mode feedback, if we see a feedback message, we 432 * interpret that message and use it to display our feedback. 433 * <p> 434 * After we send a request to operate a turnout, We ask the command 435 * station to stop sending information to the stationary decoder 436 * when the either a feedback message or an "OK" message is received. 437 * 438 * @param l an {@link XNetReply} message 439 */ 440 private synchronized void handleMonitoringModeFeedback(XNetReply l) { 441 /* In Monitoring Mode, We have two cases to check if CommandedState 442 does not equal KnownState, otherwise, we only want to check to 443 see if the messages we receive indicate this turnout chagned 444 state 445 */ 446 log.debug("Handle Message for turnout {} in MONITORING feedback mode ", mNumber); 447 if (internalState == IDLE || internalState == STATUSREQUESTSENT) { 448 if (l.onTurnoutFeedback(mNumber, this::parseFeedbackMessage)) { 449 log.debug("Turnout {} MONITORING feedback mode - state change from feedback.", mNumber); 450 } 451 } else if (getCommandedState() != getKnownState() 452 || internalState == COMMANDSENT) { 453 if (l.isOkMessage()) { 454 // Finally, we may just receive an OK message. 455 log.debug("Turnout {} MONITORING feedback mode - OK message triggering OFF message.", mNumber); 456 sendOffMessage(); 457 } else { 458 // In Monitoring mode, treat both turnouts with feedback 459 // and turnouts without feedback as turnouts without 460 // feedback. i.e. just interpret the feedback 461 // message, don't check to see if the motion is complete 462 // implicitly checks for isFeedbackBroadcastMessage() 463 if (l.onTurnoutFeedback(mNumber, this::parseFeedbackMessage)) { 464 // We need to tell the turnout to shut off the output. 465 log.debug("Turnout {} MONITORING feedback mode - state change from feedback, CommandedState != KnownState.", mNumber); 466 sendOffMessage(); 467 } 468 } 469 } 470 } 471 472 /** 473 * With Exact Mode feedback, if we see a feedback message, we 474 * interpret that message and use it to display our feedback. 475 * <p> 476 * After we send a request to operate a turnout, We ask the command 477 * station to stop sending information to the stationary decoder 478 * when the either a feedback message or an "OK" message is received. 479 * 480 * @param reply The reply message to process 481 */ 482 private synchronized void handleExactModeFeedback(XNetReply reply) { 483 // We have three cases to check if CommandedState does 484 // not equal KnownState, otherwise, we only want to check to 485 // see if the messages we receive indicate this turnout chagned 486 // state 487 log.debug("Handle Message for turnout {} in EXACT feedback mode ", mNumber); 488 if (getCommandedState() == getKnownState() 489 && (internalState == IDLE || internalState == STATUSREQUESTSENT)) { 490 // This is a feedback message, we need to check and see if it 491 // indicates this turnout is to change state or if it is for 492 // another turnout. 493 if (reply.onTurnoutFeedback(mNumber, this::parseFeedbackMessage)) { 494 log.debug("Turnout {} EXACT feedback mode - state change from feedback.", mNumber); 495 } 496 } else if (getCommandedState() != getKnownState() 497 || internalState == COMMANDSENT 498 || internalState == STATUSREQUESTSENT) { 499 if (reply.isOkMessage()) { 500 // Finally, we may just receive an OK message. 501 log.debug("Turnout {} EXACT feedback mode - OK message triggering OFF message.", mNumber); 502 sendOffMessage(); 503 } else { 504 // implicitly checks for isFeedbackBroadcastMessage() 505 reply.selectTurnoutFeedback(mNumber).ifPresent(l -> { 506 int messageType = l.getType(); 507 switch (messageType) { 508 case 1: { 509 // The first case is that we receive a message for 510 // this turnout and this turnout provides feedback. 511 // In this case, we want to check to see if the 512 // turnout has completed its movement before doing 513 // anything else. 514 if (!l.isMotionComplete()) { 515 log.debug("Turnout {} EXACT feedback mode - state change from feedback, CommandedState!=KnownState - motion not complete", mNumber); 516 // If the motion is NOT complete, send a feedback 517 // request for this nibble 518 XNetMessage msg = XNetMessage.getFeedbackRequestMsg( 519 mNumber, ((mNumber % 4) <= 1)); 520 queueMessage(msg,STATUSREQUESTSENT ,null); //status is returned via the manager. 521 return; 522 } else { 523 log.debug("Turnout {} EXACT feedback mode - state change from feedback, CommandedState!=KnownState - motion complete", mNumber); 524 } 525 break; 526 } 527 case 0: 528 log.debug("Turnout {} EXACT feedback mode - state change from feedback, CommandedState!=KnownState - motion complete", mNumber); 529 // The second case is that we receive a message about 530 // this turnout, and this turnout does not provide 531 // feedback. In this case, we want to check the 532 // contents of the message and act accordingly. 533 break; 534 default: return; 535 } 536 parseFeedbackMessage(l); 537 // We need to tell the turnout to shut off the output. 538 sendOffMessage(); 539 }); 540 } 541 } 542 } 543 544 /** 545 * Derived from handleDirectModeFeedback. 546 * With ONESENSOR od TWOSENSOR Mode feedback, if we see ANY valid response to our 547 * request, we ask the command station to stop sending information 548 * to the stationary decoder. 549 * Only one OffMessage is sent. 550 * <p> 551 * No effort is made to interpret feedback when using direct mode. 552 * 553 * @param l an {@link XNetReply} message 554 */ 555 private synchronized void handleSensorModeFeedback(XNetReply l) { 556 /* If no sendOff was sent we are 557 going to check to see if one of the following conditions 558 applies: 559 1) The received message is a feedback message for a turnout 560 and one of the two addresses to which it applies is our 561 address 562 2) We receive an "OK" message, indicating the command was 563 successfully sent 564 565 If either of these two cases occur, we trigger an off message 566 */ 567 568 log.debug("Handle Message for turnout {} in ONESENOSR or TWOSENSOR feedback mode ", mNumber); 569 if (internalState != OFFSENT) { 570 if (l.isOkMessage()) { 571 // Finally, we may just receive an OK message. 572 log.debug("Turnout {} DIRECT feedback mode - OK message triggering OFF message.", mNumber); 573 } else { 574 // implicitly checks for isFeedbackBroadcastMessage() 575 if (!l.selectTurnoutFeedback(mNumber).isPresent()) { 576 return; 577 } 578 log.debug("Turnout {} DIRECT feedback mode - directed reply received.", mNumber); 579 } 580 sendOffMessage(); 581 } 582 } 583 584 /** 585 * Send an "Off" message to the decoder for this output. 586 */ 587 @SuppressWarnings("deprecation") // The method getId() from the type Thread is deprecated since version 19 588 // The replacement Thread.threadId() isn't available before version 19 589 protected synchronized void sendOffMessage() { 590 // We need to tell the turnout to shut off the output. 591 if (log.isDebugEnabled()) { 592 log.debug("Sending off message for turnout {} commanded state={}", mNumber, getCommandedState()); 593 log.debug("Current Thread ID: {} Thread Name {}", java.lang.Thread.currentThread().getId(), java.lang.Thread.currentThread().getName()); 594 } 595 XNetMessage msg = getOffMessage(); 596 lastMsg = new RequestMessage(msg,OFFSENT,this); 597 this.internalState = OFFSENT; 598 // for mode ONESENSOR or TWOSENSOR CommandedState not set KnownState 599 if ( ! (getFeedbackMode() == ONESENSOR || getFeedbackMode() == TWOSENSOR)) { 600 newKnownState(getCommandedState()); 601 } 602 // Then send the message. 603 tc.sendHighPriorityXNetMessage(msg, this); 604 } 605 606 protected synchronized XNetMessage getOffMessage(){ 607 return ( XNetMessage.getTurnoutCommandMsg(mNumber, 608 getCommandedState() == _mClosed, 609 getCommandedState() == _mThrown, 610 false) ); 611 } 612 613 /** 614 * Parse the feedback message, and set the status of the turnout 615 * accordingly. 616 * 617 * @param l turnout feedback item 618 * 619 * @return 0 if address matches our turnout -1 otherwise 620 */ 621 private synchronized boolean parseFeedbackMessage(FeedbackItem l) { 622 log.debug("Message for turnout {}", mNumber); 623 switch (l.getTurnoutStatus()) { 624 case THROWN: 625 newKnownState(_mThrown); 626 return true; 627 case CLOSED: 628 newKnownState(_mClosed); 629 return true; 630 default: 631 // the state is unknown or inconsistent. If the command state 632 // does not equal the known state, and the command repeat the 633 // last command 634 if (getCommandedState() != getKnownState()) { 635 forwardCommandChangeToLayout(getCommandedState()); 636 } else { 637 sendQueuedMessage(); 638 } 639 return false; 640 } 641 } 642 643 @Override 644 public void dispose() { 645 this.removePropertyChangeListener(_stateListener); 646 super.dispose(); 647 } 648 649 /** 650 * Internal class to use for listening to state changes. 651 */ 652 private static class XNetTurnoutStateListener implements java.beans.PropertyChangeListener { 653 654 final XNetTurnout _turnout; 655 656 XNetTurnoutStateListener(XNetTurnout turnout) { 657 _turnout = turnout; 658 } 659 660 /** 661 * If we're not using DIRECT feedback mode, we need to listen for 662 * state changes to know when to send an OFF message after we set the 663 * known state. 664 * If we're using DIRECT mode, all of this is handled from the 665 * XpressNet Messages. 666 * @param event The event that causes this operation 667 */ 668 @Override 669 public void propertyChange(java.beans.PropertyChangeEvent event) { 670 log.debug("propertyChange called"); 671 // If we're using DIRECT feedback mode, we don't care what we see here 672 if (_turnout.getFeedbackMode() != DIRECT) { 673 if (log.isDebugEnabled()) { 674 log.debug("propertyChange Not Direct Mode property: {} old value {} new value {}", event.getPropertyName(), event.getOldValue(), event.getNewValue()); 675 } 676 if (event.getPropertyName().equals("KnownState")) { 677 // Check to see if this is a change in the status 678 // triggered by a device on the layout, or a change in 679 // status we triggered. 680 int oldKnownState = (Integer) event.getOldValue(); 681 int curKnownState = (Integer) event.getNewValue(); 682 log.debug("propertyChange KnownState - old value {} new value {}", oldKnownState, curKnownState); 683 if (curKnownState != INCONSISTENT 684 && _turnout.getCommandedState() == oldKnownState) { 685 // This was triggered by feedback on the layout, change 686 // the commanded state to reflect the new Known State 687 if (log.isDebugEnabled()) { 688 log.debug("propertyChange CommandedState: {}", _turnout.getCommandedState()); 689 } 690 _turnout.newCommandedState(curKnownState); 691 } else { 692 // Since we always set the KnownState to 693 // INCONSISTENT when we send a command, If the old 694 // known state is INCONSISTENT, we just want to send 695 // an off message 696 if (oldKnownState == INCONSISTENT) { 697 if (log.isDebugEnabled()) { 698 log.debug("propertyChange CommandedState: {}", _turnout.getCommandedState()); 699 } 700 _turnout.sendOffMessage(); 701 } 702 } 703 } 704 } 705 } 706 707 } 708 709 /** 710 * Send message from queue. 711 */ 712 protected synchronized void sendQueuedMessage() { 713 714 lastMsg = null; 715 // check to see if the queue has a message in it, and if it does, 716 // remove the first message 717 lastMsg = requestList.poll(); 718 // if the queue is not empty, remove the first message 719 // from the queue, send the message, and set the state machine 720 // to the required state. 721 if (lastMsg != null) { 722 log.debug("sending message to traffic controller"); 723 if(lastMsg.listener!=null) { 724 internalState = QUEUEDMESSAGE; 725 } else { 726 internalState = lastMsg.state; 727 } 728 tc.sendXNetMessage(lastMsg.getMsg(), lastMsg.getListener()); 729 } else { 730 log.debug("message queue empty"); 731 // if the queue is empty, set the state to idle. 732 internalState = IDLE; 733 } 734 } 735 736 /** 737 * Queue a message. 738 * @param m Message to send 739 * @param s sequence 740 * @param l Listener to get notification of completion 741 */ 742 protected synchronized void queueMessage(XNetMessage m, int s, XNetListener l) { 743 log.debug("adding message {} to message queue. Current Internal State {}",m,internalState); 744 // put the message in the queue 745 RequestMessage msg = new RequestMessage(m, s, l); 746 // the queue is unbounded; can't throw exceptions 747 requestList.add(msg); 748 // if the state is idle, trigger the message send 749 if (internalState == IDLE ) { 750 sendQueuedMessage(); 751 } 752 } 753 754 /** 755 * Internal class to hold a request message, along with the associated throttle state. 756 */ 757 protected static class RequestMessage { 758 759 private final int state; 760 private final XNetMessage msg; 761 private final XNetListener listener; 762 763 RequestMessage(XNetMessage m, int s, XNetListener listener) { 764 state = s; 765 msg = m; 766 this.listener = listener; 767 } 768 769 int getState() { 770 return state; 771 } 772 773 XNetMessage getMsg() { 774 return msg; 775 } 776 777 XNetListener getListener() { 778 return listener; 779 } 780 } 781 782 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(XNetTurnout.class); 783 784}