001package jmri.jmrix.lenz; 002 003import java.util.concurrent.LinkedBlockingQueue; 004 005import jmri.DccLocoAddress; 006import jmri.LocoAddress; 007import jmri.SpeedStepMode; 008import jmri.jmrix.AbstractThrottle; 009 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012 013import javax.annotation.concurrent.GuardedBy; 014 015/** 016 * An implementation of DccThrottle with code specific to an XpressNet 017 * connection. 018 * 019 * @author Paul Bender (C) 2002-2019 020 * @author Bob Jacobsen (C) 2023 021 */ 022public class XNetThrottle extends AbstractThrottle implements XNetListener { 023 024 protected boolean isAvailable; // Flag stating if the throttle is in use or not. 025 026 protected java.util.TimerTask statusTask; // Timer Task used to periodically get current 027 // status of the throttle when throttle not available. 028 protected static final int statTimeoutValue = 1000; // Interval to check the 029 030 /** 031 * Default interval, in milliseconds, after which a throttle still waiting 032 * for the reply to the message it sent gives up and restarts its queue, 033 * provided the traffic controller reports itself at rest. 034 * <p> 035 * When the traffic controller has nothing outstanding and nothing queued 036 * while this throttle is still waiting, the reply it waits for is already 037 * gone - typically delivered to another listener because a broadcast 038 * message was taken for it. No delay can make it arrive, so there is 039 * nothing to gain by waiting longer. 040 * <p> 041 * That state is otherwise absorbing: a throttle which is not idle never 042 * hands another message to the traffic controller, so it never becomes the 043 * current destination again, so it can never receive the reply that would 044 * release it. Every command queued afterwards is held back for the lifetime 045 * of the session. 046 */ 047 protected static final int THROTTLE_WATCHDOG_INTERVAL = 5000; 048 049 /** 050 * Unconditional deadline, in milliseconds, expressed as a multiple of 051 * {@link #THROTTLE_WATCHDOG_INTERVAL}. 052 * <p> 053 * While the traffic controller is busy, a reply can legitimately be late: 054 * the message may still be queued behind a message of its own which is 055 * consuming its full timeout. This longer deadline stays clear of that, and 056 * every retransmission re-arms the watchdog, so a message being retried 057 * never trips it. 058 */ 059 protected static final int THROTTLE_WATCHDOG_MAX_MULTIPLE = 3; 060 061 @GuardedBy("this") 062 protected XNetTrafficController tc; 063 064 // status of the throttle 065 protected static final int THROTTLEIDLE = 0; // Idle Throttle 066 protected static final int THROTTLESTATSENT = 1; // Sent Status request 067 protected static final int THROTTLESPEEDSENT = 2; // Sent speed/dir command to locomotive 068 protected static final int THROTTLEFUNCSENT = 4; // Sent a function command to locomotive. 069 protected static final int THROTTLEMOMSTATSENT = 8; // Sent Momentary Status request for F0-F12 070 protected static final int THROTTLEHIGHSTATSENT = 16; // Sent Status request for F13-F28 071 protected static final int THROTTLEHIGHMOMSTATSENT = 32; // Sent Momentary Status request for F13-F28 072 073 // Written by the receive thread in message(), by the GUI thread in 074 // queueMessage() and read by the watchdog timer thread, so it has to be 075 // volatile for the watchdog to observe the current state rather than a 076 // stale one. 077 protected volatile int requestState = THROTTLEIDLE; 078 079 protected int address; 080 081 // Watchdog for the reply to the message currently handed to the traffic 082 // controller. Null when no message is outstanding. 083 @GuardedBy("this") 084 private java.util.TimerTask watchdogTask; 085 086 @GuardedBy("this") 087 private int watchdogInterval = THROTTLE_WATCHDOG_INTERVAL; 088 089 // When the message the watchdog is guarding was handed to the traffic 090 // controller, so that a re-check can tell how long the wait has lasted. 091 @GuardedBy("this") 092 private long watchdogArmedAt; 093 094 // Cleared by throttleDispose(). A disposed throttle never arms its 095 // watchdog again, not even from a timer which was already running when it 096 // was disposed of. Read from the timer thread, hence volatile. 097 private volatile boolean watchdogEnabled = true; 098 099 // Set once the watchdog has reported this throttle stuck, cleared by any 100 // reply. A command station which stopped answering altogether is then 101 // reported once instead of every time the watchdog restarts the queue. 102 @GuardedBy("this") 103 private boolean watchdogReported; 104 105 // Get the number of valid functions from the software version number. 106 // Declared private static so it can be called as an argument to super(..) 107 private static int numberOfFuns(XNetTrafficController controller) { 108 int version = (int) controller.getCommandStation().getCommandStationSoftwareVersionBCD(); 109 if (version < 0x40) return 29; // 0 - 28 110 return 69; // 0 - 68 111 } 112 /** 113 * Constructor 114 * @param memo system connection. 115 * @param controller system connection traffic controller. 116 */ 117 public XNetThrottle(XNetSystemConnectionMemo memo, XNetTrafficController controller) { 118 super(memo, numberOfFuns(controller)); 119 tc = controller; 120 requestList = new LinkedBlockingQueue<>(); 121 122 log.info("Throttle created with no address and version {}", controller.getCommandStation().getCommandStationSoftwareVersionBCD()); 123 } 124 125 /** 126 * Constructor. 127 * @param memo system connection. 128 * @param address loco address. 129 * @param controller system connection traffic controller. 130 */ 131 public XNetThrottle(XNetSystemConnectionMemo memo, LocoAddress address, XNetTrafficController controller) { 132 super(memo, numberOfFuns(controller)); 133 this.tc = controller; 134 this.setDccAddress(address.getNumber()); 135 this.speedStepMode = jmri.SpeedStepMode.NMRA_DCC_128; 136 setIsAvailable(false); 137 138 requestList = new LinkedBlockingQueue<>(); 139 sendStatusInformationRequest(); 140 log.info("Throttle created for address {} with version {}", 141 address, controller.getCommandStation().getCommandStationSoftwareVersionBCD()); 142 } 143 144 /* 145 * Set the traffic controller used with this throttle. 146 */ 147 public synchronized void setXNetTrafficController(XNetTrafficController controller) { 148 tc = controller; 149 } 150 151 protected synchronized boolean csVersionSupportFn13to28() { 152 if (tc.getCommandStation().getCommandStationSoftwareVersionBCD() < 0x36) { 153 log.info("Functions F13-F28 unavailable in CS software version {}", 154 tc.getCommandStation().getCommandStationSoftwareVersion()); 155 return false; 156 } 157 return true; 158 } 159 160 protected synchronized boolean csVersionSupportFn29to68() { 161 if (tc.getCommandStation().getCommandStationSoftwareVersionBCD() < 0x40) { 162 log.info("Functions F29-68 unavailable in CS software version {}", 163 tc.getCommandStation().getCommandStationSoftwareVersion()); 164 return false; 165 } 166 return true; 167 } 168 169 /** 170 * Send the XpressNet message to set the state of locomotive direction and 171 * functions F0, F1, F2, F3, F4. 172 */ 173 @Override 174 protected void sendFunctionGroup1() { 175 XNetMessage msg = XNetMessage.getFunctionGroup1OpsMsg(this.getDccAddress(), 176 getFunction(0), getFunction(1), getFunction(2), getFunction(3), getFunction(4)); 177 // now, queue the message for sending to the command station 178 queueMessage(msg, THROTTLEFUNCSENT); 179 } 180 181 /** 182 * Send the XpressNet message to set the state of functions F5, F6, F7, F8. 183 */ 184 @Override 185 protected void sendFunctionGroup2() { 186 XNetMessage msg = XNetMessage.getFunctionGroup2OpsMsg(this.getDccAddress(), 187 getFunction(5), getFunction(6), getFunction(7), getFunction(8)); 188 // now, queue the message for sending to the command station 189 queueMessage(msg, THROTTLEFUNCSENT); 190 } 191 192 /** 193 * Send the XpressNet message to set the state of functions F9, F10, F11, 194 * F12. 195 */ 196 @Override 197 protected void sendFunctionGroup3() { 198 XNetMessage msg = XNetMessage.getFunctionGroup3OpsMsg(this.getDccAddress(), 199 getFunction(9), getFunction(10), getFunction(11), getFunction(12)); 200 // now, queue the message for sending to the command station 201 queueMessage(msg, THROTTLEFUNCSENT); 202 } 203 204 /** 205 * Send the XpressNet message to set the state of functions F13, F14, F15, 206 * F16, F17, F18, F19, F20. 207 */ 208 @Override 209 protected void sendFunctionGroup4() { 210 if (csVersionSupportFn13to28()) { 211 XNetMessage msg = XNetMessage.getFunctionGroup4OpsMsg(this.getDccAddress(), 212 getFunction(13), getFunction(14), getFunction(15), getFunction(16), 213 getFunction(17), getFunction(18), getFunction(19), getFunction(20)); 214 // now, queue the message for sending to the command station 215 queueMessage(msg, THROTTLEFUNCSENT); 216 } 217 } 218 219 /** 220 * Send the XpressNet message to set the state of functions F21, F22, F23, 221 * F24, F25, F26, F27, F28. 222 */ 223 @Override 224 protected void sendFunctionGroup5() { 225 if (csVersionSupportFn13to28()) { 226 XNetMessage msg = XNetMessage.getFunctionGroup5OpsMsg(this.getDccAddress(), 227 getFunction(21), getFunction(22), getFunction(23), getFunction(24), 228 getFunction(25), getFunction(26), getFunction(27), getFunction(28)); 229 // now, queue the message for sending to the command station 230 queueMessage(msg, THROTTLEFUNCSENT); 231 } 232 } 233 234 /** 235 * Send the XpressNet message to set the state of functions F29-36 236 */ 237 @Override 238 protected void sendFunctionGroup6() { 239 if (csVersionSupportFn29to68()) { 240 int i = 29; 241 XNetMessage msg = XNetMessage.getFunctionGroup6OpsMsg(this.getDccAddress(), 242 getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3), 243 getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7)); 244 // now, queue the message for sending to the command station 245 queueMessage(msg, THROTTLEFUNCSENT); 246 } 247 } 248 249 /** 250 * Send the XpressNet message to set the state of functions F37-44 251 */ 252 @Override 253 protected void sendFunctionGroup7() { 254 if (csVersionSupportFn29to68()) { 255 int i = 37; 256 XNetMessage msg = XNetMessage.getFunctionGroup7OpsMsg(this.getDccAddress(), 257 getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3), 258 getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7)); 259 // now, queue the message for sending to the command station 260 queueMessage(msg, THROTTLEFUNCSENT); 261 } 262 } 263 264 /** 265 * Send the XpressNet message to set the state of functions F45-52 266 */ 267 @Override 268 protected void sendFunctionGroup8() { 269 if (csVersionSupportFn29to68()) { 270 int i = 45; 271 XNetMessage msg = XNetMessage.getFunctionGroup8OpsMsg(this.getDccAddress(), 272 getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3), 273 getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7)); 274 // now, queue the message for sending to the command station 275 queueMessage(msg, THROTTLEFUNCSENT); 276 } 277 } 278 279 /** 280 * Send the XpressNet message to set the state of functions F53-60 281 */ 282 @Override 283 protected void sendFunctionGroup9() { 284 if (csVersionSupportFn29to68()) { 285 int i = 53; 286 XNetMessage msg = XNetMessage.getFunctionGroup9OpsMsg(this.getDccAddress(), 287 getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3), 288 getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7)); 289 // now, queue the message for sending to the command station 290 queueMessage(msg, THROTTLEFUNCSENT); 291 } 292 } 293 294 /** 295 * Send the XpressNet message to set the state of functions F61-68 296 */ 297 @Override 298 protected void sendFunctionGroup10() { 299 if (csVersionSupportFn29to68()) { 300 int i = 61; 301 XNetMessage msg = XNetMessage.getFunctionGroup10OpsMsg(this.getDccAddress(), 302 getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3), 303 getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7)); 304 // now, queue the message for sending to the command station 305 queueMessage(msg, THROTTLEFUNCSENT); 306 } 307 } 308 309 /** 310 * Send the XpressNet message to set the Momentary state of locomotive 311 * functions F0, F1, F2, F3, F4. 312 */ 313 @Override 314 protected void sendMomentaryFunctionGroup1() { 315 XNetMessage msg = XNetMessage.getFunctionGroup1SetMomMsg(this.getDccAddress(), 316 getFunctionMomentary(0), getFunctionMomentary(1), getFunctionMomentary(2), 317 getFunctionMomentary(3), getFunctionMomentary(4)); 318 // now, queue the message for sending to the command station 319 queueMessage(msg, THROTTLEFUNCSENT); 320 } 321 322 /** 323 * Send the XpressNet message to set the momentary state of functions F5, 324 * F6, F7, F8. 325 */ 326 @Override 327 protected void sendMomentaryFunctionGroup2() { 328 XNetMessage msg = XNetMessage.getFunctionGroup2SetMomMsg(this.getDccAddress(), 329 getFunctionMomentary(5), getFunctionMomentary(6), 330 getFunctionMomentary(7), getFunctionMomentary(8)); 331 // now, queue the message for sending to the command station 332 queueMessage(msg, THROTTLEFUNCSENT); 333 } 334 335 /** 336 * Send the XpressNet message to set the momentary state of functions F9, 337 * F10, F11, F12. 338 */ 339 @Override 340 protected void sendMomentaryFunctionGroup3() { 341 XNetMessage msg = XNetMessage.getFunctionGroup3SetMomMsg(this.getDccAddress(), 342 getFunctionMomentary(9), getFunctionMomentary(10), 343 getFunctionMomentary(11), getFunctionMomentary(12)); 344 // now, queue the message for sending to the command station 345 queueMessage(msg, THROTTLEFUNCSENT); 346 } 347 348 /** 349 * Send the XpressNet message to set the momentary state of functions 350 * F13, F14, F15, F16, F17, F18, F19, F20. 351 */ 352 @Override 353 protected void sendMomentaryFunctionGroup4() { 354 if (csVersionSupportFn13to28()) { 355 XNetMessage msg = XNetMessage.getFunctionGroup4SetMomMsg(this.getDccAddress(), 356 getFunctionMomentary(13), getFunctionMomentary(14), 357 getFunctionMomentary(15), getFunctionMomentary(16), 358 getFunctionMomentary(17), getFunctionMomentary(18), 359 getFunctionMomentary(19), getFunctionMomentary(20)); 360 // now, queue the message for sending to the command station 361 queueMessage(msg, THROTTLEFUNCSENT); 362 } 363 } 364 365 /** 366 * Send the XpressNet message to set the momentary state of functions F21, 367 * F22, F23, F24, F25, F26, F27, F28. 368 */ 369 @Override 370 protected void sendMomentaryFunctionGroup5() { 371 if (csVersionSupportFn13to28()) { 372 XNetMessage msg = XNetMessage.getFunctionGroup5SetMomMsg(this.getDccAddress(), 373 getFunctionMomentary(21), getFunctionMomentary(22), getFunctionMomentary(23), 374 getFunctionMomentary(24), getFunctionMomentary(25), getFunctionMomentary(26), 375 getFunctionMomentary(27), getFunctionMomentary(28)); 376 // now, queue the message for sending to the command station 377 queueMessage(msg, THROTTLEFUNCSENT); 378 } 379 } 380 381 /** 382 * Send the XpressNet message to set the momentary state of functions F29-36 383 */ 384 @Override 385 protected void sendMomentaryFunctionGroup6() { 386 if (csVersionSupportFn29to68()) { 387 int i = 29; 388 XNetMessage msg = XNetMessage.getFunctionGroup6SetMomMsg(this.getDccAddress(), 389 getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2), 390 getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5), 391 getFunctionMomentary(i+6), getFunctionMomentary(i+7)); 392 // now, queue the message for sending to the command station 393 queueMessage(msg, THROTTLEFUNCSENT); 394 } 395 } 396 397 /** 398 * Send the XpressNet message to set the momentary state of functions F37-44 399 */ 400 @Override 401 protected void sendMomentaryFunctionGroup7() { 402 if (csVersionSupportFn29to68()) { 403 int i = 37; 404 XNetMessage msg = XNetMessage.getFunctionGroup7SetMomMsg(this.getDccAddress(), 405 getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2), 406 getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5), 407 getFunctionMomentary(i+6), getFunctionMomentary(i+7)); 408 // now, queue the message for sending to the command station 409 queueMessage(msg, THROTTLEFUNCSENT); 410 } 411 } 412 413 /** 414 * Send the XpressNet message to set the momentary state of functions F45-52 415 */ 416 @Override 417 protected void sendMomentaryFunctionGroup8() { 418 if (csVersionSupportFn29to68()) { 419 int i = 45; 420 XNetMessage msg = XNetMessage.getFunctionGroup8SetMomMsg(this.getDccAddress(), 421 getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2), 422 getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5), 423 getFunctionMomentary(i+6), getFunctionMomentary(i+7)); 424 // now, queue the message for sending to the command station 425 queueMessage(msg, THROTTLEFUNCSENT); 426 } 427 } 428 429 /** 430 * Send the XpressNet message to set the momentary state of functions F53-60 431 */ 432 @Override 433 protected void sendMomentaryFunctionGroup9() { 434 if (csVersionSupportFn29to68()) { 435 int i = 53; 436 XNetMessage msg = XNetMessage.getFunctionGroup9SetMomMsg(this.getDccAddress(), 437 getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2), 438 getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5), 439 getFunctionMomentary(i+6), getFunctionMomentary(i+7)); 440 // now, queue the message for sending to the command station 441 queueMessage(msg, THROTTLEFUNCSENT); 442 } 443 } 444 445 /** 446 * Send the XpressNet message to set the momentary state of functions F61-68 447 */ 448 @Override 449 protected void sendMomentaryFunctionGroup10() { 450 if (csVersionSupportFn29to68()) { 451 int i = 61; 452 XNetMessage msg = XNetMessage.getFunctionGroup10SetMomMsg(this.getDccAddress(), 453 getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2), 454 getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5), 455 getFunctionMomentary(i+6), getFunctionMomentary(i+7)); 456 // now, queue the message for sending to the command station 457 queueMessage(msg, THROTTLEFUNCSENT); 458 } 459 } 460 461 /** 462 * Notify listeners and send the new speed to the command station. 463 */ 464 @Override 465 public synchronized void setSpeedSetting(float speed) { 466 log.debug("set Speed to: {} Current step mode is: {}", speed, this.speedStepMode); 467 super.setSpeedSetting(speed); 468 if (speed < 0) { 469 /* we're sending an emergency stop to this locomotive only */ 470 sendEmergencyStop(); 471 } else { 472 if (speed > 1) { 473 speed = (float) 1.0; 474 } 475 /* we're sending a speed to the locomotive */ 476 XNetMessage msg = XNetMessage.getSpeedAndDirectionMsg(getDccAddress(), 477 this.speedStepMode, 478 speed, 479 this.isForward); 480 // now, queue the message for sending to the command station 481 queueMessage(msg, THROTTLESPEEDSENT); 482 } 483 } 484 485 /** 486 * Since XpressNet has a seperate Opcode for emergency stop, we're setting 487 * this up as a seperate protected function. 488 */ 489 protected void sendEmergencyStop() { 490 /* Emergency stop sent */ 491 XNetMessage msg = XNetMessage.getAddressedEmergencyStop(this.getDccAddress()); 492 // now, queue the message for sending to the command station 493 queueMessage(msg, THROTTLESPEEDSENT); 494 } 495 496 /** 497 * When we set the direction, we're going to set the speed to zero as well. 498 */ 499 @Override 500 public void setIsForward(boolean forward) { 501 super.setIsForward(forward); 502 synchronized(this) { 503 setSpeedSetting(this.speedSetting); 504 } 505 } 506 507 /** 508 * Set the speed step value and the related speedIncrement value. 509 * 510 * @param Mode the current speed step mode - default should be 128 speed 511 * step mode in most cases 512 */ 513 @Override 514 public void setSpeedStepMode(SpeedStepMode Mode) { 515 super.setSpeedStepMode(Mode); 516 // On a Lenz system, we need to send the speed to make sure the 517 // command station knows about the change. 518 synchronized(this) { 519 setSpeedSetting(this.speedSetting); 520 } 521 } 522 523 /** 524 * Dispose when finished with this object. After this, further usage of this 525 * Throttle object will result in a JmriException. 526 * <p> 527 * This is quite problematic, because a using object doesn't know when it's 528 * the last user. 529 */ 530 @Override 531 public void throttleDispose() { 532 active = false; 533 watchdogEnabled = false; 534 stopStatusTimer(); 535 cancelWatchdog(); 536 finishRecord(); 537 } 538 539 public int setDccAddress(int newaddress) { 540 address = newaddress; 541 return address; 542 } 543 544 public int getDccAddress() { 545 return address; 546 } 547 548 protected int getDccAddressHigh() { 549 return LenzCommandStation.getDCCAddressHigh(this.address); 550 } 551 552 protected int getDccAddressLow() { 553 return LenzCommandStation.getDCCAddressLow(this.address); 554 } 555 556 /** 557 * Send a request to get the speed, direction and function status from the 558 * command station. 559 */ 560 protected synchronized void sendStatusInformationRequest() { 561 /* Send the request for status */ 562 XNetMessage msg = XNetMessage.getLocomotiveInfoRequestMsg(this.address); 563 msg.setRetries(1); // Since we repeat this ourselves, don't ask the 564 // traffic controller to do this for us. 565 // now, we queue the message for sending to the command station 566 queueMessage(msg, THROTTLESTATSENT); 567 } 568 569 /** 570 * Send a request to get the status of functions from the command station. 571 */ 572 protected synchronized void sendFunctionStatusInformationRequest() { 573 log.debug("Throttle {} sending request for function momentary status.", address); 574 /* Send the request for Function status */ 575 XNetMessage msg = XNetMessage.getLocomotiveFunctionStatusMsg(this.address); 576 queueMessage(msg, (THROTTLEMOMSTATSENT | THROTTLESTATSENT)); 577 } 578 579 /** 580 * Send a request to get the on/off status of functions 13-28 from the 581 * command station. 582 */ 583 protected synchronized void sendFunctionHighInformationRequest() { 584 if (csVersionSupportFn13to28()) { 585 log.debug("Throttle {} sending request for high function momentary status.", address); 586 /* Send the request for Function status */ 587 XNetMessage msg = XNetMessage.getLocomotiveFunctionHighOnStatusMsg(this.address); 588 // now, we send the message to the command station 589 queueMessage(msg, THROTTLEHIGHSTATSENT | THROTTLESTATSENT); 590 } 591 } 592 593 /** 594 * Send a request to get the status of functions from the command station. 595 */ 596 protected synchronized void sendFunctionHighMomentaryStatusRequest() { 597 if (csVersionSupportFn13to28()) { 598 log.debug("Throttle {} sending request for function momentary status.", address); 599 /* Send the request for Function status */ 600 XNetMessage msg = XNetMessage.getLocomotiveFunctionHighMomStatusMsg(this.address); 601 // now, we send the message to the command station 602 queueMessage(msg, (THROTTLEHIGHMOMSTATSENT | THROTTLESTATSENT)); 603 } 604 } 605 606 // Handle incoming messages for This throttle. 607 @Override 608 public void message(XNetReply l) { 609 // First, we want to see if this throttle is waiting for a message 610 //or not. 611 log.debug("Throttle {} - received message {}", getDccAddress(), l); 612 synchronized (this) { 613 // the command station is answering again, a later silence is worth 614 // reporting anew. 615 watchdogReported = false; 616 } 617 if (requestState == THROTTLEIDLE) { 618 log.trace("Current throttle status is THROTTLEIDLE"); 619 // We haven't sent anything, but we might be told someone else 620 // has taken over this address 621 if (l.getElement(0) == XNetConstants.LOCO_INFO_RESPONSE) { 622 log.trace("Throttle - message is LOCO_INFO_RESPONSE "); 623 if (l.getElement(1) == XNetConstants.LOCO_NOT_AVAILABLE 624 && getDccAddressHigh() == l.getElement(2) 625 && getDccAddressLow() == l.getElement(3)) { 626 locoInUse(); 627 } 628 } 629 } else if ((requestState & THROTTLESPEEDSENT) == THROTTLESPEEDSENT 630 || (requestState & THROTTLEFUNCSENT) == THROTTLEFUNCSENT) { 631 log.trace("Current throttle status is THROTTLESPEEDSENT"); 632 // For a Throttle Command, we're just looking for a return 633 // acknowledgment, Either a Success or Failure message. 634 if (l.isOkMessage()) { 635 log.trace("Last Command processed successfully."); 636 // Since we received an "ok", we want to make sure 637 // "isAvailable reflects we are in control 638 setIsAvailable(true); 639 requestState = THROTTLEIDLE; 640 sendQueuedMessage(); 641 } else if (l.isRetransmittableErrorMsg()) { 642 /* this is a communications error */ 643 log.trace("Communications error occurred - message received was: {}", l); 644 } else if (l.isUnsupportedError()) { 645 /* The Command Station does not support this command */ 646 log.error("Unsupported Command Sent to command station"); 647 requestState = THROTTLEIDLE; 648 sendQueuedMessage(); 649 } else { 650 /* this is an unknown error */ 651 requestState = THROTTLEIDLE; 652 sendQueuedMessage(); 653 log.trace("Received unhandled response: {}", l); 654 } 655 } else if ((requestState & THROTTLESTATSENT) == THROTTLESTATSENT) { 656 log.trace("Current throttle status is THROTTLESTATSENT"); 657 // This throttle has requested status information, so we need 658 // to process those messages. 659 if (l.getElement(0) == XNetConstants.LOCO_INFO_NORMAL_UNIT) { 660 if (l.getElement(1) == XNetConstants.LOCO_FUNCTION_STATUS_HIGH_MOM) { 661 /* handle information response about F13-F28 Momentary 662 Status*/ 663 log.trace("Throttle - message is LOCO_FUNCTION_STATUS_HIGH_MOM"); 664 int b3 = l.getElement(2); 665 int b4 = l.getElement(3); 666 parseFunctionHighMomentaryInformation(b3, b4); 667 //We've processed this request, so set the status to Idle. 668 requestState = THROTTLEIDLE; 669 sendQueuedMessage(); 670 } else { 671 log.trace("Throttle - message is LOCO_INFO_NORMAL_UNIT"); 672 /* there is no address sent with this information */ 673 int b1 = l.getElement(1); 674 int b2 = l.getElement(2); 675 int b3 = l.getElement(3); 676 int b4 = l.getElement(4); 677 678 parseSpeedAndAvailability(b1); 679 parseSpeedAndDirection(b2); 680 parseFunctionInformation(b3, b4); 681 682 //We've processed this request, so set the status to Idle. 683 requestState = THROTTLEIDLE; 684 sendQueuedMessage(); 685 // And then we want to request the Function Momentary Status 686 sendFunctionStatusInformationRequest(); 687 } 688 } else if (l.getElement(0) == XNetConstants.LOCO_INFO_MUED_UNIT) { 689 log.trace("Throttle - message is LOCO_INFO_MUED_UNIT "); 690 /* there is no address sent with this information */ 691 int b1 = l.getElement(1); 692 int b2 = l.getElement(2); 693 int b3 = l.getElement(3); 694 int b4 = l.getElement(4); 695 // Element 5 is the consist address, it can only be in the 696 // range 1-99 697 int b5 = l.getElement(5); 698 699 log.trace("Locomotive {} inconsist {} ", getDccAddress(), b5); 700 701 parseSpeedAndAvailability(b1); 702 parseSpeedAndDirection(b2); 703 parseFunctionInformation(b3, b4); 704 705 // We've processed this request, so set the status to Idle. 706 requestState = THROTTLEIDLE; 707 sendQueuedMessage(); 708 // And then we want to request the Function Momentary Status 709 sendFunctionStatusInformationRequest(); 710 } else if (l.getElement(0) == XNetConstants.LOCO_INFO_DH_UNIT) { 711 log.trace("Throttle - message is LOCO_INFO_DH_UNIT "); 712 /* there is no address sent with this information */ 713 int b1 = l.getElement(1); 714 int b2 = l.getElement(2); 715 int b3 = l.getElement(3); 716 int b4 = l.getElement(4); 717 718 // elements 5 and 6 contain the address of the other unit 719 // in the DH 720 int b5 = l.getElement(5); 721 int b6 = l.getElement(6); 722 723 if (log.isDebugEnabled()) { 724 int address2 = (b5 == 0x00) ? b6 : ((b5 * 256) & 0xFF00) + (b6 & 0xFF) - 0xC000; 725 log.trace("Locomotive {} in Double Header with {}", 726 getDccAddress(), address2); 727 } 728 729 parseSpeedAndAvailability(b1); 730 parseSpeedAndDirection(b2); 731 parseFunctionInformation(b3, b4); 732 733 // We've processed this request, so set the status to Idle. 734 requestState = THROTTLEIDLE; 735 sendQueuedMessage(); 736 // And then we want to request the Function Momentary Status 737 sendFunctionStatusInformationRequest(); 738 } else if (l.getElement(0) == XNetConstants.LOCO_INFO_MU_ADDRESS) { 739 log.trace("Throttle - message is LOCO_INFO_MU ADDRESS "); 740 /* there is no address sent with this information */ 741 int b1 = l.getElement(1); 742 int b2 = l.getElement(2); 743 744 parseSpeedAndAvailability(b1); 745 parseSpeedAndDirection(b2); 746 747 //We've processed this request, so set the status to Idle. 748 requestState = THROTTLEIDLE; 749 sendQueuedMessage(); 750 // And then we want to request the Function Momentary Status 751 sendFunctionStatusInformationRequest(); 752 } else if (l.getElement(0) == XNetConstants.LOCO_INFO_RESPONSE) { 753 log.trace("Throttle - message is LOCO_INFO_RESPONSE "); 754 if (l.getElement(1) == XNetConstants.LOCO_NOT_AVAILABLE) { 755 /* the address is in bytes 3 and 4*/ 756 if (getDccAddressHigh() == l.getElement(2) && getDccAddressLow() == l.getElement(3)) { 757 locoInUse(); 758 } 759 // We've processed this request, so set the status to Idle. 760 requestState = THROTTLEIDLE; 761 sendQueuedMessage(); 762 } else if (l.getElement(1) == XNetConstants.LOCO_FUNCTION_STATUS) { 763 /* Bytes 3 and 4 contain function momentary status information */ 764 int b3 = l.getElement(2); 765 int b4 = l.getElement(3); 766 parseFunctionMomentaryInformation(b3, b4); 767 // We've processed this request, so set the status to Idle. 768 requestState = THROTTLEIDLE; 769 sendQueuedMessage(); 770 // And then we want to request the Function Status for F13-F28 771 sendFunctionHighInformationRequest(); 772 773 } else if (l.getElement(1) == XNetConstants.LOCO_FUNCTION_STATUS_HIGH) { 774 /* Bytes 3 and 4 contain function status information for F13-F28*/ 775 int b3 = l.getElement(2); 776 int b4 = l.getElement(3); 777 parseFunctionHighInformation(b3, b4); 778 //We've processed this request, so set the status to Idle. 779 requestState = THROTTLEIDLE; 780 sendQueuedMessage(); 781 // And then we want to request the Function Momentary Status 782 // for functions F13-F28 783 sendFunctionHighMomentaryStatusRequest(); 784 } else { 785 // The reply to the outstanding request has been consumed, 786 // so no timeout will ever fire for it. Without this 787 // branch an unrecognized subtype leaves requestState stuck 788 // (never returns to THROTTLEIDLE) and every command queued 789 // afterwards is silently held back forever. 790 log.debug("Unhandled LOCO_INFO_RESPONSE subtype {} while in THROTTLESTATSENT, resuming queue", 791 l.getElement(1)); 792 requestState = THROTTLEIDLE; 793 sendQueuedMessage(); 794 } 795 } else if (l.isRetransmittableErrorMsg()) { 796 /* this is a communications error */ 797 log.trace("Communications error occurred - message received was: {}", l); 798 } else if (l.isUnsupportedError()) { 799 /* The Command Station does not support this command */ 800 log.error("Unsupported Command Sent to command station"); 801 if ((requestState & THROTTLEMOMSTATSENT) == THROTTLEMOMSTATSENT) { 802 // if momentary is not supported, try requesting the 803 // high function state. 804 requestState = THROTTLEIDLE; 805 sendFunctionHighInformationRequest(); 806 } else { 807 requestState = THROTTLEIDLE; 808 sendQueuedMessage(); 809 } 810 } else { 811 /* this is an unknown error */ 812 requestState = THROTTLEIDLE; 813 sendQueuedMessage(); 814 log.trace("Received unhandled response: {}", l); 815 } 816 } 817 } 818 819 private void locoInUse() { 820 if (isAvailable) { 821 //Set the Is available flag to Throttle.False 822 log.info("Loco {} In use by another device", getDccAddress()); 823 setIsAvailable(false); 824 } 825 } 826 827 /** 828 * {@inheritDoc} 829 */ 830 @Override 831 public void message(XNetMessage l) { 832 // no need to handle outgoing messages. 833 } 834 835 /** 836 * {@inheritDoc} 837 */ 838 @Override 839 public void notifyTimeout(XNetMessage msg) { 840 log.debug("Notified of timeout on message {} , {} retries available.", 841 msg, msg.getRetries()); 842 if (msg.getRetries() > 0) { 843 // If the message still has retries available, send it back to 844 // the traffic controller. 845 synchronized (this) { 846 tc.sendXNetMessage(msg, this); 847 // the reply is expected again, restart the watchdog from here 848 armWatchdog(); 849 } 850 } else { 851 // Try to send the next queued message, if one is available. 852 sendQueuedMessage(); 853 } 854 } 855 856 // Status Information processing routines 857 // Used for return values from Status requests. 858 /** 859 * Get SpeedStep and availability information. 860 * @param b1 1st byte of message to examine 861 */ 862 protected void parseSpeedAndAvailability(int b1) { 863 /* the first data bite indicates the speed step mode, and 864 if the locomotive is being controlled by another throttle */ 865 866 if ((b1 & 0x08) == 0x08 && this.isAvailable) { 867 locoInUse(); 868 } else if ((b1 & 0x08) == 0x00 && !this.isAvailable) { 869 log.trace("Loco Is Available"); 870 setIsAvailable(true); 871 } 872 if ((b1 & 0x01) == 0x01) { 873 log.trace("Speed Step setting 27"); 874 notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_27); 875 } else if ((b1 & 0x02) == 0x02) { 876 log.trace("Speed Step setting 28"); 877 notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_28); 878 } else if ((b1 & 0x04) == 0x04) { 879 log.trace("Speed Step setting 128"); 880 notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_128); 881 } else { 882 log.trace("Speed Step setting 14"); 883 notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_14); 884 } 885 } 886 887 protected void notifyNewSpeedStepMode(SpeedStepMode mode) { 888 if (this.speedStepMode != mode) { 889 firePropertyChange(SPEEDSTEPS, 890 this.speedStepMode, 891 this.speedStepMode = mode); 892 } 893 } 894 895 /** 896 * Get Speed and Direction information. 897 * @param b2 2nd byte of message to examine 898 */ 899 protected void parseSpeedAndDirection(int b2) { 900 /* the second byte indicates the speed and direction setting */ 901 902 if ((b2 & 0x80) == 0x80 && !this.isForward) { 903 notifyNewDirection(true); 904 } else if ((b2 & 0x80) == 0x00 && this.isForward) { 905 notifyNewDirection(false); 906 } 907 908 if (this.speedStepMode == SpeedStepMode.NMRA_DCC_128) { 909 // We're in 128 speed step mode 910 int speedVal = b2 & 0x7f; 911 // The first speed step used is actually at 2 for 128 912 // speed step mode. 913 if (speedVal >= 1) { 914 speedVal -= 1; 915 } 916 if (java.lang.Math.abs( 917 this.getSpeedSetting() - ((float) speedVal / (float) 126)) >= 0.0079) { 918 synchronized(this) { 919 firePropertyChange(SPEEDSETTING, this.speedSetting, 920 this.speedSetting = (float) speedVal / (float) 126); 921 } 922 } 923 } else if (this.speedStepMode == SpeedStepMode.NMRA_DCC_28) { 924 // We're in 28 speed step mode 925 // We have to re-arrange the bits, since bit 4 is the LSB, 926 // but other bits are in order from 0-3 927 int speedVal = ((b2 & 0x0F) << 1) 928 + ((b2 & 0x10) >> 4); 929 // The first speed step used is actually at 4 for 28 930 // speed step mode. 931 if (speedVal >= 3) { 932 speedVal -= 3; 933 } else { 934 speedVal = 0; 935 } 936 if (java.lang.Math.abs( 937 this.getSpeedSetting() - ((float) speedVal / (float) 28)) >= 0.035) { 938 synchronized(this) { 939 firePropertyChange(SPEEDSETTING, this.speedSetting, 940 this.speedSetting = (float) speedVal / (float) 28); 941 } 942 } 943 } else if (this.speedStepMode == SpeedStepMode.NMRA_DCC_27) { 944 // We're in 27 speed step mode 945 // We have to re-arrange the bits, since bit 4 is the LSB, 946 // but other bits are in order from 0-3 947 int speedVal = ((b2 & 0x0F) << 1) 948 + ((b2 & 0x10) >> 4); 949 // The first speed step used is actually at 4 for 27 950 // speed step mode. 951 if (speedVal >= 3) { 952 speedVal -= 3; 953 } else { 954 speedVal = 0; 955 } 956 if (java.lang.Math.abs( 957 this.getSpeedSetting() - ((float) speedVal / (float) 27)) >= 0.037) { 958 synchronized(this) { 959 firePropertyChange(SPEEDSETTING, this.speedSetting, 960 this.speedSetting = (float) speedVal / (float) 27); 961 } 962 } 963 } else { 964 // Assume we're in 14 speed step mode. 965 int speedVal = (b2 & 0x0F); 966 if (speedVal >= 1) { 967 speedVal -= 1; 968 } 969 if (java.lang.Math.abs( 970 this.getSpeedSetting() - ((float) speedVal / (float) 14)) >= 0.071) { 971 synchronized(this) { 972 firePropertyChange(SPEEDSETTING, this.speedSetting, 973 this.speedSetting = (float) speedVal / (float) 14); 974 } 975 } 976 } 977 } 978 979 protected void notifyNewDirection(boolean forward) { 980 firePropertyChange(ISFORWARD, this.isForward, this.isForward = forward); 981 log.trace("Throttle - Changed direction to {} Locomotive: {}", forward ? "forward" : "reverse", getDccAddress()); 982 } 983 984 protected void parseFunctionInformation(int b3, int b4) { 985 log.trace("Parsing Function F0-F12 status, function bytes: {} and {}", 986 b3, b4); 987 /* data byte 3 is the status of F0 F4 F3 F2 F1 */ 988 updateFunction(0, (b3 & 0x10) == 0x10); 989 updateFunction(1, (b3 & 0x01) == 0x01); 990 updateFunction(2, (b3 & 0x02) == 0x02); 991 updateFunction(3, (b3 & 0x04) == 0x04); 992 updateFunction(4, (b3 & 0x08) == 0x08); 993 /* data byte 4 is the status of F12 F11 F10 F9 F8 F7 F6 F5 */ 994 updateFunction(5, (b4 & 0x01) == 0x01); 995 updateFunction(6, (b4 & 0x02) == 0x02); 996 updateFunction(7, (b4 & 0x04) == 0x04); 997 updateFunction(8, (b4 & 0x08) == 0x08); 998 updateFunction(9, (b4 & 0x10) == 0x10); 999 updateFunction(10, (b4 & 0x20) == 0x20); 1000 updateFunction(11, (b4 & 0x40) == 0x40); 1001 updateFunction(12, (b4 & 0x80) == 0x80); 1002 } 1003 1004 protected void parseFunctionHighInformation(int b3, int b4) { 1005 log.trace("Parsing Function F13-F28 status, function bytes: {} and {}", 1006 b3, b4); 1007 /* data byte 3 is the status of F20 F19 F18 F17 F16 F15 F14 F13 */ 1008 updateFunction(13, (b3 & 0x01) == 0x01); 1009 updateFunction(14, (b3 & 0x02) == 0x02); 1010 updateFunction(15, (b3 & 0x04) == 0x04); 1011 updateFunction(16, (b3 & 0x08) == 0x08); 1012 updateFunction(17, (b3 & 0x10) == 0x10); 1013 updateFunction(18, (b3 & 0x20) == 0x20); 1014 updateFunction(19, (b3 & 0x40) == 0x40); 1015 updateFunction(20, (b3 & 0x80) == 0x80); 1016 /* data byte 4 is the status of F28 F27 F26 F25 F24 F23 F22 F21 */ 1017 updateFunction(21, (b4 & 0x01) == 0x01); 1018 updateFunction(22, (b4 & 0x02) == 0x02); 1019 updateFunction(23, (b4 & 0x04) == 0x04); 1020 updateFunction(24, (b4 & 0x08) == 0x08); 1021 updateFunction(25, (b4 & 0x10) == 0x10); 1022 updateFunction(26, (b4 & 0x20) == 0x20); 1023 updateFunction(27, (b4 & 0x40) == 0x40); 1024 updateFunction(28, (b4 & 0x80) == 0x80); 1025 1026 } 1027 1028 protected void parseFunctionMomentaryInformation(int b3, int b4) { 1029 log.trace("Parsing Function Momentary status, function bytes: {} and {}", 1030 b3, b4); 1031 /* data byte 3 is the momentary status of F0 F4 F3 F2 F1 */ 1032 checkForFunctionMomentaryValueChange(0, b3, 0x10, getFunctionMomentary(0)); 1033 checkForFunctionMomentaryValueChange(1, b3, 0x01, getFunctionMomentary(1)); 1034 checkForFunctionMomentaryValueChange(2, b3, 0x02, getFunctionMomentary(2)); 1035 checkForFunctionMomentaryValueChange(3, b3, 0x04, getFunctionMomentary(3)); 1036 checkForFunctionMomentaryValueChange(4, b3, 0x08, getFunctionMomentary(4)); 1037 /* data byte 4 is the momentary status of F12 F11 F10 F9 F8 F7 F6 F5 */ 1038 checkForFunctionMomentaryValueChange(5, b4, 0x01, getFunctionMomentary(5)); 1039 checkForFunctionMomentaryValueChange(6, b4, 0x02, getFunctionMomentary(6)); 1040 checkForFunctionMomentaryValueChange(7, b4, 0x04, getFunctionMomentary(7)); 1041 checkForFunctionMomentaryValueChange(8, b4, 0x08, getFunctionMomentary(8)); 1042 checkForFunctionMomentaryValueChange(9, b4, 0x10, getFunctionMomentary(9)); 1043 checkForFunctionMomentaryValueChange(10, b4, 0x20, getFunctionMomentary(10)); 1044 checkForFunctionMomentaryValueChange(11, b4, 0x40, getFunctionMomentary(11)); 1045 checkForFunctionMomentaryValueChange(12, b4, 0x80, getFunctionMomentary(12)); 1046 } 1047 1048 protected void parseFunctionHighMomentaryInformation(int b3, int b4) { 1049 log.trace("Parsing Function F13-F28 Momentary status, function bytes: {} and {}", 1050 b3, b4); 1051 /* data byte 3 is the momentary status of F20 F19 F17 F16 F15 F14 F13 */ 1052 checkForFunctionMomentaryValueChange(13, b3, 0x01, getFunctionMomentary(13)); 1053 checkForFunctionMomentaryValueChange(14, b3, 0x02, getFunctionMomentary(14)); 1054 checkForFunctionMomentaryValueChange(15, b3, 0x04, getFunctionMomentary(15)); 1055 checkForFunctionMomentaryValueChange(16, b3, 0x08, getFunctionMomentary(16)); 1056 checkForFunctionMomentaryValueChange(17, b3, 0x10, getFunctionMomentary(17)); 1057 checkForFunctionMomentaryValueChange(18, b3, 0x20, getFunctionMomentary(18)); 1058 checkForFunctionMomentaryValueChange(19, b3, 0x40, getFunctionMomentary(19)); 1059 checkForFunctionMomentaryValueChange(20, b3, 0x80, getFunctionMomentary(20)); 1060 /* data byte 4 is the momentary status of F28 F27 F26 F25 F24 F23 F22 F21 */ 1061 checkForFunctionMomentaryValueChange(21, b4, 0x01, getFunctionMomentary(21)); 1062 checkForFunctionMomentaryValueChange(22, b4, 0x02, getFunctionMomentary(22)); 1063 checkForFunctionMomentaryValueChange(23, b4, 0x04, getFunctionMomentary(23)); 1064 checkForFunctionMomentaryValueChange(24, b4, 0x08, getFunctionMomentary(24)); 1065 checkForFunctionMomentaryValueChange(25, b4, 0x10, getFunctionMomentary(25)); 1066 checkForFunctionMomentaryValueChange(26, b4, 0x20, getFunctionMomentary(26)); 1067 checkForFunctionMomentaryValueChange(27, b4, 0x40, getFunctionMomentary(27)); 1068 checkForFunctionMomentaryValueChange(28, b4, 0x80, getFunctionMomentary(28)); 1069 } 1070 1071 protected void checkForFunctionMomentaryValueChange(int funcNum, int bytevalue, int bitmask, boolean currentValue) { 1072 if ((bytevalue & bitmask) == bitmask && !currentValue) { 1073 updateFunctionMomentary(funcNum, true); 1074 } else if ((bytevalue & bitmask) == 0x00 && currentValue) { 1075 updateFunctionMomentary(funcNum, false); 1076 } 1077 } 1078 1079 /** 1080 * Set the internal isAvailable property. 1081 * 1082 * @param available true if available; false otherwise 1083 */ 1084 protected void setIsAvailable(boolean available) { 1085 firePropertyChange("IsAvailable", this.isAvailable, this.isAvailable = available); 1086 /* if we're setting this to true, stop the timer, 1087 otherwise start the timer. */ 1088 if (available) { 1089 stopStatusTimer(); 1090 } else { 1091 startStatusTimer(); 1092 } 1093 } 1094 1095 /** 1096 * Set up the status timer, and start it. 1097 */ 1098 protected void startStatusTimer() { 1099 log.debug("Status Timer Started"); 1100 1101 if (statusTask != null) { 1102 statusTask.cancel(); 1103 statusTask = null; 1104 } 1105 statusTask = new java.util.TimerTask() { 1106 @Override 1107 public void run() { 1108 /* If the timer times out, just send a status 1109 request message */ 1110 sendStatusInformationRequest(); 1111 } 1112 }; 1113 1114 jmri.util.TimerUtil.schedule(statusTask, statTimeoutValue, statTimeoutValue); 1115 } 1116 1117 /** 1118 * Stop the Status Timer 1119 */ 1120 protected void stopStatusTimer() { 1121 log.debug("Status Timer Stopped"); 1122 if (statusTask != null) { 1123 try { 1124 statusTask.cancel(); 1125 } catch (IllegalStateException ise) { 1126 log.debug("Timer already canceled"); 1127 } 1128 statusTask = null; 1129 } 1130 } 1131 1132 /** 1133 * Arm the watchdog for the message just handed to the traffic controller. 1134 * <p> 1135 * Any watchdog already armed is cancelled first, so the delay is always 1136 * counted from the most recent transmission and a legitimate retransmission 1137 * never trips it. 1138 */ 1139 protected synchronized void armWatchdog() { 1140 cancelWatchdog(); 1141 if (!watchdogEnabled) { 1142 return; // disposed of, nothing is waiting for a reply any more 1143 } 1144 watchdogArmedAt = System.currentTimeMillis(); 1145 scheduleWatchdog(watchdogInterval); 1146 } 1147 1148 private synchronized void scheduleWatchdog(long delay) { 1149 java.util.TimerTask task = new java.util.TimerTask() { 1150 @Override 1151 public void run() { 1152 watchdogExpired(this); 1153 } 1154 }; 1155 watchdogTask = task; 1156 jmri.util.TimerUtil.schedule(task, delay); 1157 } 1158 1159 /** 1160 * Disarm the watchdog, the throttle is no longer waiting for a reply. 1161 */ 1162 protected synchronized void cancelWatchdog() { 1163 if (watchdogTask != null) { 1164 watchdogTask.cancel(); 1165 watchdogTask = null; 1166 } 1167 } 1168 1169 /** 1170 * The reply to the outstanding message never arrived. Return to idle and 1171 * restart the queue, otherwise this throttle would stay silent until the 1172 * end of the session. 1173 * 1174 * @param task the expiring task, used to ignore an expiry which a later 1175 * transmission has already superseded 1176 */ 1177 private synchronized void watchdogExpired(java.util.TimerTask task) { 1178 if (task != watchdogTask) { 1179 return; // superseded, the message sent since owns the watchdog 1180 } 1181 watchdogTask = null; 1182 if (!watchdogEnabled || requestState == THROTTLEIDLE) { 1183 return; 1184 } 1185 long waited = System.currentTimeMillis() - watchdogArmedAt; 1186 long maxWait = (long) watchdogInterval * THROTTLE_WATCHDOG_MAX_MULTIPLE; 1187 if (tc.isAtRest()) { 1188 // Nothing is in flight and nothing is queued, so the reply this 1189 // throttle is waiting for is already gone. Waiting longer cannot 1190 // help: this is the state the whole watchdog exists for. 1191 if (watchdogAlreadyReported()) { 1192 log.debug("Throttle {} - traffic controller at rest with a reply still due after" 1193 + " {}ms in state {}, restarting the queue", getDccAddress(), waited, requestState); 1194 } else { 1195 log.info("Throttle {} - traffic controller at rest with a reply still due after" 1196 + " {}ms in state {}, restarting the queue", getDccAddress(), waited, requestState); 1197 } 1198 } else if (waited >= maxWait) { 1199 if (watchdogAlreadyReported()) { 1200 log.debug("Throttle {} - no reply after {}ms in state {}, restarting the queue", 1201 getDccAddress(), waited, requestState); 1202 } else { 1203 log.warn("Throttle {} - no reply after {}ms in state {}, restarting the queue", 1204 getDccAddress(), waited, requestState); 1205 } 1206 } else { 1207 // The traffic controller still has work in hand, the reply may yet 1208 // be legitimately late. Look again later, up to the hard deadline. 1209 scheduleWatchdog(Math.min(watchdogInterval, maxWait - waited)); 1210 return; 1211 } 1212 requestState = THROTTLEIDLE; 1213 sendQueuedMessage(); 1214 } 1215 1216 /** 1217 * Tell whether this stuck episode has already been reported, and mark it 1218 * reported. 1219 * <p> 1220 * The first expiry is worth a warning: a reply was lost and the queue had 1221 * to be restarted. Until a reply is received again, further expiries only 1222 * repeat that the command station is still not answering, which would 1223 * otherwise put a warning in the log every 1224 * {@value #THROTTLE_WATCHDOG_INTERVAL} milliseconds for as long as the 1225 * throttle exists. 1226 * 1227 * @return true if a warning has already been issued since the last reply 1228 */ 1229 @GuardedBy("this") 1230 private boolean watchdogAlreadyReported() { 1231 boolean reported = watchdogReported; 1232 watchdogReported = true; 1233 return reported; 1234 } 1235 1236 /** 1237 * Set how long this throttle waits for a reply, once the traffic controller 1238 * is at rest, before giving up and restarting its queue. While the traffic 1239 * controller is busy the throttle waits up to 1240 * {@value #THROTTLE_WATCHDOG_MAX_MULTIPLE} times as long. 1241 * Takes effect on the next message sent. 1242 * 1243 * @param interval interval in milliseconds 1244 */ 1245 public synchronized void setWatchdogInterval(int interval) { 1246 watchdogInterval = interval; 1247 } 1248 1249 @Override 1250 public LocoAddress getLocoAddress() { 1251 return new DccLocoAddress(address, XNetThrottleManager.isLongAddress(address)); 1252 } 1253 1254 // A queue to hold outstanding messages 1255 protected LinkedBlockingQueue<RequestMessage> requestList; 1256 1257 /** 1258 * Send message from queue. 1259 */ 1260 protected synchronized void sendQueuedMessage() { 1261 1262 RequestMessage msg; 1263 // check to see if the queue has a message in it, and if it does, 1264 // remove the first message 1265 if (!requestList.isEmpty()) { 1266 log.debug("sending message to traffic controller"); 1267 // if the queue is not empty, remove the first message 1268 // from the queue, send the message, and set the state machine 1269 // to the required state. 1270 try { 1271 msg = requestList.take(); 1272 } catch (java.lang.InterruptedException ie) { 1273 return; // if there was an error, exit. 1274 } 1275 requestState = msg.getState(); 1276 tc.sendXNetMessage(msg.getMsg(), this); 1277 if (requestState == THROTTLEIDLE) { 1278 // a message sent without waiting for its reply, e.g. Z21 1279 cancelWatchdog(); 1280 } else { 1281 // a reply is expected, guard against never getting one 1282 armWatchdog(); 1283 } 1284 } else { 1285 log.debug("message queue empty"); 1286 // if the queue is empty, set the state to idle. 1287 requestState = THROTTLEIDLE; 1288 cancelWatchdog(); 1289 } 1290 } 1291 1292 /** 1293 * Queue a message. 1294 * @param m message to send 1295 * @param s state 1296 */ 1297 protected synchronized void queueMessage(XNetMessage m, int s) { 1298 log.debug("adding message to message queue"); 1299 // put the message in the queue 1300 RequestMessage msg = new RequestMessage(m, s); 1301 try { 1302 requestList.put(msg); 1303 } catch (java.lang.InterruptedException ie) { 1304 log.trace("Interrupted while queueing message {}", msg); 1305 } 1306 // if the state is idle, trigger the message send 1307 if (requestState == THROTTLEIDLE) { 1308 sendQueuedMessage(); 1309 } 1310 } 1311 1312 /** 1313 * Internal class to hold a request message, along with the associated 1314 * throttle state. 1315 */ 1316 protected static class RequestMessage { 1317 1318 private final int state; 1319 private final XNetMessage msg; 1320 1321 RequestMessage(XNetMessage m, int s) { 1322 state = s; 1323 msg = m; 1324 } 1325 1326 int getState() { 1327 return state; 1328 } 1329 1330 XNetMessage getMsg() { 1331 return msg; 1332 } 1333 } 1334 1335 // register for notification 1336 private static final Logger log = LoggerFactory.getLogger(XNetThrottle.class); 1337 1338}