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 @GuardedBy("this") 030 protected XNetTrafficController tc; 031 032 // status of the throttle 033 protected static final int THROTTLEIDLE = 0; // Idle Throttle 034 protected static final int THROTTLESTATSENT = 1; // Sent Status request 035 protected static final int THROTTLESPEEDSENT = 2; // Sent speed/dir command to locomotive 036 protected static final int THROTTLEFUNCSENT = 4; // Sent a function command to locomotive. 037 protected static final int THROTTLEMOMSTATSENT = 8; // Sent Momentary Status request for F0-F12 038 protected static final int THROTTLEHIGHSTATSENT = 16; // Sent Status request for F13-F28 039 protected static final int THROTTLEHIGHMOMSTATSENT = 32; // Sent Momentary Status request for F13-F28 040 041 protected int requestState = THROTTLEIDLE; 042 043 protected int address; 044 045 // Get the number of valid functions from the software version number. 046 // Declared private static so it can be called as an argument to super(..) 047 private static int numberOfFuns(XNetTrafficController controller) { 048 int version = (int) controller.getCommandStation().getCommandStationSoftwareVersionBCD(); 049 if (version < 0x40) return 29; // 0 - 28 050 return 69; // 0 - 68 051 } 052 /** 053 * Constructor 054 * @param memo system connection. 055 * @param controller system connection traffic controller. 056 */ 057 public XNetThrottle(XNetSystemConnectionMemo memo, XNetTrafficController controller) { 058 super(memo, numberOfFuns(controller)); 059 tc = controller; 060 requestList = new LinkedBlockingQueue<>(); 061 062 log.info("Throttle created with no address and version {}", controller.getCommandStation().getCommandStationSoftwareVersionBCD()); 063 } 064 065 /** 066 * Constructor. 067 * @param memo system connection. 068 * @param address loco address. 069 * @param controller system connection traffic controller. 070 */ 071 public XNetThrottle(XNetSystemConnectionMemo memo, LocoAddress address, XNetTrafficController controller) { 072 super(memo, numberOfFuns(controller)); 073 this.tc = controller; 074 this.setDccAddress(address.getNumber()); 075 this.speedStepMode = jmri.SpeedStepMode.NMRA_DCC_128; 076 setIsAvailable(false); 077 078 requestList = new LinkedBlockingQueue<>(); 079 sendStatusInformationRequest(); 080 log.info("Throttle created for address {} with version {}", 081 address, controller.getCommandStation().getCommandStationSoftwareVersionBCD()); 082 } 083 084 /* 085 * Set the traffic controller used with this throttle. 086 */ 087 public synchronized void setXNetTrafficController(XNetTrafficController controller) { 088 tc = controller; 089 } 090 091 protected synchronized boolean csVersionSupportFn13to28() { 092 if (tc.getCommandStation().getCommandStationSoftwareVersionBCD() < 0x36) { 093 log.info("Functions F13-F28 unavailable in CS software version {}", 094 tc.getCommandStation().getCommandStationSoftwareVersion()); 095 return false; 096 } 097 return true; 098 } 099 100 protected synchronized boolean csVersionSupportFn29to68() { 101 if (tc.getCommandStation().getCommandStationSoftwareVersionBCD() < 0x40) { 102 log.info("Functions F29-68 unavailable in CS software version {}", 103 tc.getCommandStation().getCommandStationSoftwareVersion()); 104 return false; 105 } 106 return true; 107 } 108 109 /** 110 * Send the XpressNet message to set the state of locomotive direction and 111 * functions F0, F1, F2, F3, F4. 112 */ 113 @Override 114 protected void sendFunctionGroup1() { 115 XNetMessage msg = XNetMessage.getFunctionGroup1OpsMsg(this.getDccAddress(), 116 getFunction(0), getFunction(1), getFunction(2), getFunction(3), getFunction(4)); 117 // now, queue the message for sending to the command station 118 queueMessage(msg, THROTTLEFUNCSENT); 119 } 120 121 /** 122 * Send the XpressNet message to set the state of functions F5, F6, F7, F8. 123 */ 124 @Override 125 protected void sendFunctionGroup2() { 126 XNetMessage msg = XNetMessage.getFunctionGroup2OpsMsg(this.getDccAddress(), 127 getFunction(5), getFunction(6), getFunction(7), getFunction(8)); 128 // now, queue the message for sending to the command station 129 queueMessage(msg, THROTTLEFUNCSENT); 130 } 131 132 /** 133 * Send the XpressNet message to set the state of functions F9, F10, F11, 134 * F12. 135 */ 136 @Override 137 protected void sendFunctionGroup3() { 138 XNetMessage msg = XNetMessage.getFunctionGroup3OpsMsg(this.getDccAddress(), 139 getFunction(9), getFunction(10), getFunction(11), getFunction(12)); 140 // now, queue the message for sending to the command station 141 queueMessage(msg, THROTTLEFUNCSENT); 142 } 143 144 /** 145 * Send the XpressNet message to set the state of functions F13, F14, F15, 146 * F16, F17, F18, F19, F20. 147 */ 148 @Override 149 protected void sendFunctionGroup4() { 150 if (csVersionSupportFn13to28()) { 151 XNetMessage msg = XNetMessage.getFunctionGroup4OpsMsg(this.getDccAddress(), 152 getFunction(13), getFunction(14), getFunction(15), getFunction(16), 153 getFunction(17), getFunction(18), getFunction(19), getFunction(20)); 154 // now, queue the message for sending to the command station 155 queueMessage(msg, THROTTLEFUNCSENT); 156 } 157 } 158 159 /** 160 * Send the XpressNet message to set the state of functions F21, F22, F23, 161 * F24, F25, F26, F27, F28. 162 */ 163 @Override 164 protected void sendFunctionGroup5() { 165 if (csVersionSupportFn13to28()) { 166 XNetMessage msg = XNetMessage.getFunctionGroup5OpsMsg(this.getDccAddress(), 167 getFunction(21), getFunction(22), getFunction(23), getFunction(24), 168 getFunction(25), getFunction(26), getFunction(27), getFunction(28)); 169 // now, queue the message for sending to the command station 170 queueMessage(msg, THROTTLEFUNCSENT); 171 } 172 } 173 174 /** 175 * Send the XpressNet message to set the state of functions F29-36 176 */ 177 @Override 178 protected void sendFunctionGroup6() { 179 if (csVersionSupportFn29to68()) { 180 int i = 29; 181 XNetMessage msg = XNetMessage.getFunctionGroup6OpsMsg(this.getDccAddress(), 182 getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3), 183 getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7)); 184 // now, queue the message for sending to the command station 185 queueMessage(msg, THROTTLEFUNCSENT); 186 } 187 } 188 189 /** 190 * Send the XpressNet message to set the state of functions F37-44 191 */ 192 @Override 193 protected void sendFunctionGroup7() { 194 if (csVersionSupportFn29to68()) { 195 int i = 37; 196 XNetMessage msg = XNetMessage.getFunctionGroup7OpsMsg(this.getDccAddress(), 197 getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3), 198 getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7)); 199 // now, queue the message for sending to the command station 200 queueMessage(msg, THROTTLEFUNCSENT); 201 } 202 } 203 204 /** 205 * Send the XpressNet message to set the state of functions F45-52 206 */ 207 @Override 208 protected void sendFunctionGroup8() { 209 if (csVersionSupportFn29to68()) { 210 int i = 45; 211 XNetMessage msg = XNetMessage.getFunctionGroup8OpsMsg(this.getDccAddress(), 212 getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3), 213 getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7)); 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 F53-60 221 */ 222 @Override 223 protected void sendFunctionGroup9() { 224 if (csVersionSupportFn29to68()) { 225 int i = 53; 226 XNetMessage msg = XNetMessage.getFunctionGroup9OpsMsg(this.getDccAddress(), 227 getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3), 228 getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7)); 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 F61-68 236 */ 237 @Override 238 protected void sendFunctionGroup10() { 239 if (csVersionSupportFn29to68()) { 240 int i = 61; 241 XNetMessage msg = XNetMessage.getFunctionGroup10OpsMsg(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 Momentary state of locomotive 251 * functions F0, F1, F2, F3, F4. 252 */ 253 @Override 254 protected void sendMomentaryFunctionGroup1() { 255 XNetMessage msg = XNetMessage.getFunctionGroup1SetMomMsg(this.getDccAddress(), 256 getFunctionMomentary(0), getFunctionMomentary(1), getFunctionMomentary(2), 257 getFunctionMomentary(3), getFunctionMomentary(4)); 258 // now, queue the message for sending to the command station 259 queueMessage(msg, THROTTLEFUNCSENT); 260 } 261 262 /** 263 * Send the XpressNet message to set the momentary state of functions F5, 264 * F6, F7, F8. 265 */ 266 @Override 267 protected void sendMomentaryFunctionGroup2() { 268 XNetMessage msg = XNetMessage.getFunctionGroup2SetMomMsg(this.getDccAddress(), 269 getFunctionMomentary(5), getFunctionMomentary(6), 270 getFunctionMomentary(7), getFunctionMomentary(8)); 271 // now, queue the message for sending to the command station 272 queueMessage(msg, THROTTLEFUNCSENT); 273 } 274 275 /** 276 * Send the XpressNet message to set the momentary state of functions F9, 277 * F10, F11, F12. 278 */ 279 @Override 280 protected void sendMomentaryFunctionGroup3() { 281 XNetMessage msg = XNetMessage.getFunctionGroup3SetMomMsg(this.getDccAddress(), 282 getFunctionMomentary(9), getFunctionMomentary(10), 283 getFunctionMomentary(11), getFunctionMomentary(12)); 284 // now, queue the message for sending to the command station 285 queueMessage(msg, THROTTLEFUNCSENT); 286 } 287 288 /** 289 * Send the XpressNet message to set the momentary state of functions 290 * F13, F14, F15, F16, F17, F18, F19, F20. 291 */ 292 @Override 293 protected void sendMomentaryFunctionGroup4() { 294 if (csVersionSupportFn13to28()) { 295 XNetMessage msg = XNetMessage.getFunctionGroup4SetMomMsg(this.getDccAddress(), 296 getFunctionMomentary(13), getFunctionMomentary(14), 297 getFunctionMomentary(15), getFunctionMomentary(16), 298 getFunctionMomentary(17), getFunctionMomentary(18), 299 getFunctionMomentary(19), getFunctionMomentary(20)); 300 // now, queue the message for sending to the command station 301 queueMessage(msg, THROTTLEFUNCSENT); 302 } 303 } 304 305 /** 306 * Send the XpressNet message to set the momentary state of functions F21, 307 * F22, F23, F24, F25, F26, F27, F28. 308 */ 309 @Override 310 protected void sendMomentaryFunctionGroup5() { 311 if (csVersionSupportFn13to28()) { 312 XNetMessage msg = XNetMessage.getFunctionGroup5SetMomMsg(this.getDccAddress(), 313 getFunctionMomentary(21), getFunctionMomentary(22), getFunctionMomentary(23), 314 getFunctionMomentary(24), getFunctionMomentary(25), getFunctionMomentary(26), 315 getFunctionMomentary(27), getFunctionMomentary(28)); 316 // now, queue the message for sending to the command station 317 queueMessage(msg, THROTTLEFUNCSENT); 318 } 319 } 320 321 /** 322 * Send the XpressNet message to set the momentary state of functions F29-36 323 */ 324 @Override 325 protected void sendMomentaryFunctionGroup6() { 326 if (csVersionSupportFn29to68()) { 327 int i = 29; 328 XNetMessage msg = XNetMessage.getFunctionGroup6SetMomMsg(this.getDccAddress(), 329 getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2), 330 getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5), 331 getFunctionMomentary(i+6), getFunctionMomentary(i+7)); 332 // now, queue the message for sending to the command station 333 queueMessage(msg, THROTTLEFUNCSENT); 334 } 335 } 336 337 /** 338 * Send the XpressNet message to set the momentary state of functions F37-44 339 */ 340 @Override 341 protected void sendMomentaryFunctionGroup7() { 342 if (csVersionSupportFn29to68()) { 343 int i = 37; 344 XNetMessage msg = XNetMessage.getFunctionGroup7SetMomMsg(this.getDccAddress(), 345 getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2), 346 getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5), 347 getFunctionMomentary(i+6), getFunctionMomentary(i+7)); 348 // now, queue the message for sending to the command station 349 queueMessage(msg, THROTTLEFUNCSENT); 350 } 351 } 352 353 /** 354 * Send the XpressNet message to set the momentary state of functions F45-52 355 */ 356 @Override 357 protected void sendMomentaryFunctionGroup8() { 358 if (csVersionSupportFn29to68()) { 359 int i = 45; 360 XNetMessage msg = XNetMessage.getFunctionGroup8SetMomMsg(this.getDccAddress(), 361 getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2), 362 getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5), 363 getFunctionMomentary(i+6), getFunctionMomentary(i+7)); 364 // now, queue the message for sending to the command station 365 queueMessage(msg, THROTTLEFUNCSENT); 366 } 367 } 368 369 /** 370 * Send the XpressNet message to set the momentary state of functions F53-60 371 */ 372 @Override 373 protected void sendMomentaryFunctionGroup9() { 374 if (csVersionSupportFn29to68()) { 375 int i = 53; 376 XNetMessage msg = XNetMessage.getFunctionGroup9SetMomMsg(this.getDccAddress(), 377 getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2), 378 getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5), 379 getFunctionMomentary(i+6), getFunctionMomentary(i+7)); 380 // now, queue the message for sending to the command station 381 queueMessage(msg, THROTTLEFUNCSENT); 382 } 383 } 384 385 /** 386 * Send the XpressNet message to set the momentary state of functions F61-68 387 */ 388 @Override 389 protected void sendMomentaryFunctionGroup10() { 390 if (csVersionSupportFn29to68()) { 391 int i = 61; 392 XNetMessage msg = XNetMessage.getFunctionGroup10SetMomMsg(this.getDccAddress(), 393 getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2), 394 getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5), 395 getFunctionMomentary(i+6), getFunctionMomentary(i+7)); 396 // now, queue the message for sending to the command station 397 queueMessage(msg, THROTTLEFUNCSENT); 398 } 399 } 400 401 /** 402 * Notify listeners and send the new speed to the command station. 403 */ 404 @Override 405 public synchronized void setSpeedSetting(float speed) { 406 log.debug("set Speed to: {} Current step mode is: {}", speed, this.speedStepMode); 407 super.setSpeedSetting(speed); 408 if (speed < 0) { 409 /* we're sending an emergency stop to this locomotive only */ 410 sendEmergencyStop(); 411 } else { 412 if (speed > 1) { 413 speed = (float) 1.0; 414 } 415 /* we're sending a speed to the locomotive */ 416 XNetMessage msg = XNetMessage.getSpeedAndDirectionMsg(getDccAddress(), 417 this.speedStepMode, 418 speed, 419 this.isForward); 420 // now, queue the message for sending to the command station 421 queueMessage(msg, THROTTLESPEEDSENT); 422 } 423 } 424 425 /** 426 * Since XpressNet has a seperate Opcode for emergency stop, we're setting 427 * this up as a seperate protected function. 428 */ 429 protected void sendEmergencyStop() { 430 /* Emergency stop sent */ 431 XNetMessage msg = XNetMessage.getAddressedEmergencyStop(this.getDccAddress()); 432 // now, queue the message for sending to the command station 433 queueMessage(msg, THROTTLESPEEDSENT); 434 } 435 436 /** 437 * When we set the direction, we're going to set the speed to zero as well. 438 */ 439 @Override 440 public void setIsForward(boolean forward) { 441 super.setIsForward(forward); 442 synchronized(this) { 443 setSpeedSetting(this.speedSetting); 444 } 445 } 446 447 /** 448 * Set the speed step value and the related speedIncrement value. 449 * 450 * @param Mode the current speed step mode - default should be 128 speed 451 * step mode in most cases 452 */ 453 @Override 454 public void setSpeedStepMode(SpeedStepMode Mode) { 455 super.setSpeedStepMode(Mode); 456 // On a Lenz system, we need to send the speed to make sure the 457 // command station knows about the change. 458 synchronized(this) { 459 setSpeedSetting(this.speedSetting); 460 } 461 } 462 463 /** 464 * Dispose when finished with this object. After this, further usage of this 465 * Throttle object will result in a JmriException. 466 * <p> 467 * This is quite problematic, because a using object doesn't know when it's 468 * the last user. 469 */ 470 @Override 471 public void throttleDispose() { 472 active = false; 473 stopStatusTimer(); 474 finishRecord(); 475 } 476 477 public int setDccAddress(int newaddress) { 478 address = newaddress; 479 return address; 480 } 481 482 public int getDccAddress() { 483 return address; 484 } 485 486 protected int getDccAddressHigh() { 487 return LenzCommandStation.getDCCAddressHigh(this.address); 488 } 489 490 protected int getDccAddressLow() { 491 return LenzCommandStation.getDCCAddressLow(this.address); 492 } 493 494 /** 495 * Send a request to get the speed, direction and function status from the 496 * command station. 497 */ 498 protected synchronized void sendStatusInformationRequest() { 499 /* Send the request for status */ 500 XNetMessage msg = XNetMessage.getLocomotiveInfoRequestMsg(this.address); 501 msg.setRetries(1); // Since we repeat this ourselves, don't ask the 502 // traffic controller to do this for us. 503 // now, we queue the message for sending to the command station 504 queueMessage(msg, THROTTLESTATSENT); 505 } 506 507 /** 508 * Send a request to get the status of functions from the command station. 509 */ 510 protected synchronized void sendFunctionStatusInformationRequest() { 511 log.debug("Throttle {} sending request for function momentary status.", address); 512 /* Send the request for Function status */ 513 XNetMessage msg = XNetMessage.getLocomotiveFunctionStatusMsg(this.address); 514 queueMessage(msg, (THROTTLEMOMSTATSENT | THROTTLESTATSENT)); 515 } 516 517 /** 518 * Send a request to get the on/off status of functions 13-28 from the 519 * command station. 520 */ 521 protected synchronized void sendFunctionHighInformationRequest() { 522 if (csVersionSupportFn13to28()) { 523 log.debug("Throttle {} sending request for high function momentary status.", address); 524 /* Send the request for Function status */ 525 XNetMessage msg = XNetMessage.getLocomotiveFunctionHighOnStatusMsg(this.address); 526 // now, we send the message to the command station 527 queueMessage(msg, THROTTLEHIGHSTATSENT | THROTTLESTATSENT); 528 } 529 } 530 531 /** 532 * Send a request to get the status of functions from the command station. 533 */ 534 protected synchronized void sendFunctionHighMomentaryStatusRequest() { 535 if (csVersionSupportFn13to28()) { 536 log.debug("Throttle {} sending request for function momentary status.", address); 537 /* Send the request for Function status */ 538 XNetMessage msg = XNetMessage.getLocomotiveFunctionHighMomStatusMsg(this.address); 539 // now, we send the message to the command station 540 queueMessage(msg, (THROTTLEHIGHMOMSTATSENT | THROTTLESTATSENT)); 541 } 542 } 543 544 // Handle incoming messages for This throttle. 545 @Override 546 public void message(XNetReply l) { 547 // First, we want to see if this throttle is waiting for a message 548 //or not. 549 log.debug("Throttle {} - received message {}", getDccAddress(), l); 550 if (requestState == THROTTLEIDLE) { 551 log.trace("Current throttle status is THROTTLEIDLE"); 552 // We haven't sent anything, but we might be told someone else 553 // has taken over this address 554 if (l.getElement(0) == XNetConstants.LOCO_INFO_RESPONSE) { 555 log.trace("Throttle - message is LOCO_INFO_RESPONSE "); 556 if (l.getElement(1) == XNetConstants.LOCO_NOT_AVAILABLE 557 && getDccAddressHigh() == l.getElement(2) 558 && getDccAddressLow() == l.getElement(3)) { 559 locoInUse(); 560 } 561 } 562 } else if ((requestState & THROTTLESPEEDSENT) == THROTTLESPEEDSENT 563 || (requestState & THROTTLEFUNCSENT) == THROTTLEFUNCSENT) { 564 log.trace("Current throttle status is THROTTLESPEEDSENT"); 565 // For a Throttle Command, we're just looking for a return 566 // acknowledgment, Either a Success or Failure message. 567 if (l.isOkMessage()) { 568 log.trace("Last Command processed successfully."); 569 // Since we received an "ok", we want to make sure 570 // "isAvailable reflects we are in control 571 setIsAvailable(true); 572 requestState = THROTTLEIDLE; 573 sendQueuedMessage(); 574 } else if (l.isRetransmittableErrorMsg()) { 575 /* this is a communications error */ 576 log.trace("Communications error occurred - message received was: {}", l); 577 } else if (l.isUnsupportedError()) { 578 /* The Command Station does not support this command */ 579 log.error("Unsupported Command Sent to command station"); 580 requestState = THROTTLEIDLE; 581 sendQueuedMessage(); 582 } else { 583 /* this is an unknown error */ 584 requestState = THROTTLEIDLE; 585 sendQueuedMessage(); 586 log.trace("Received unhandled response: {}", l); 587 } 588 } else if ((requestState & THROTTLESTATSENT) == THROTTLESTATSENT) { 589 log.trace("Current throttle status is THROTTLESTATSENT"); 590 // This throttle has requested status information, so we need 591 // to process those messages. 592 if (l.getElement(0) == XNetConstants.LOCO_INFO_NORMAL_UNIT) { 593 if (l.getElement(1) == XNetConstants.LOCO_FUNCTION_STATUS_HIGH_MOM) { 594 /* handle information response about F13-F28 Momentary 595 Status*/ 596 log.trace("Throttle - message is LOCO_FUNCTION_STATUS_HIGH_MOM"); 597 int b3 = l.getElement(2); 598 int b4 = l.getElement(3); 599 parseFunctionHighMomentaryInformation(b3, b4); 600 //We've processed this request, so set the status to Idle. 601 requestState = THROTTLEIDLE; 602 sendQueuedMessage(); 603 } else { 604 log.trace("Throttle - message is LOCO_INFO_NORMAL_UNIT"); 605 /* there is no address sent with this information */ 606 int b1 = l.getElement(1); 607 int b2 = l.getElement(2); 608 int b3 = l.getElement(3); 609 int b4 = l.getElement(4); 610 611 parseSpeedAndAvailability(b1); 612 parseSpeedAndDirection(b2); 613 parseFunctionInformation(b3, b4); 614 615 //We've processed this request, so set the status to Idle. 616 requestState = THROTTLEIDLE; 617 sendQueuedMessage(); 618 // And then we want to request the Function Momentary Status 619 sendFunctionStatusInformationRequest(); 620 } 621 } else if (l.getElement(0) == XNetConstants.LOCO_INFO_MUED_UNIT) { 622 log.trace("Throttle - message is LOCO_INFO_MUED_UNIT "); 623 /* there is no address sent with this information */ 624 int b1 = l.getElement(1); 625 int b2 = l.getElement(2); 626 int b3 = l.getElement(3); 627 int b4 = l.getElement(4); 628 // Element 5 is the consist address, it can only be in the 629 // range 1-99 630 int b5 = l.getElement(5); 631 632 log.trace("Locomotive {} inconsist {} ", getDccAddress(), b5); 633 634 parseSpeedAndAvailability(b1); 635 parseSpeedAndDirection(b2); 636 parseFunctionInformation(b3, b4); 637 638 // We've processed this request, so set the status to Idle. 639 requestState = THROTTLEIDLE; 640 sendQueuedMessage(); 641 // And then we want to request the Function Momentary Status 642 sendFunctionStatusInformationRequest(); 643 } else if (l.getElement(0) == XNetConstants.LOCO_INFO_DH_UNIT) { 644 log.trace("Throttle - message is LOCO_INFO_DH_UNIT "); 645 /* there is no address sent with this information */ 646 int b1 = l.getElement(1); 647 int b2 = l.getElement(2); 648 int b3 = l.getElement(3); 649 int b4 = l.getElement(4); 650 651 // elements 5 and 6 contain the address of the other unit 652 // in the DH 653 int b5 = l.getElement(5); 654 int b6 = l.getElement(6); 655 656 if (log.isDebugEnabled()) { 657 int address2 = (b5 == 0x00) ? b6 : ((b5 * 256) & 0xFF00) + (b6 & 0xFF) - 0xC000; 658 log.trace("Locomotive {} in Double Header with {}", 659 getDccAddress(), address2); 660 } 661 662 parseSpeedAndAvailability(b1); 663 parseSpeedAndDirection(b2); 664 parseFunctionInformation(b3, b4); 665 666 // We've processed this request, so set the status to Idle. 667 requestState = THROTTLEIDLE; 668 sendQueuedMessage(); 669 // And then we want to request the Function Momentary Status 670 sendFunctionStatusInformationRequest(); 671 } else if (l.getElement(0) == XNetConstants.LOCO_INFO_MU_ADDRESS) { 672 log.trace("Throttle - message is LOCO_INFO_MU ADDRESS "); 673 /* there is no address sent with this information */ 674 int b1 = l.getElement(1); 675 int b2 = l.getElement(2); 676 677 parseSpeedAndAvailability(b1); 678 parseSpeedAndDirection(b2); 679 680 //We've processed this request, so set the status to Idle. 681 requestState = THROTTLEIDLE; 682 sendQueuedMessage(); 683 // And then we want to request the Function Momentary Status 684 sendFunctionStatusInformationRequest(); 685 } else if (l.getElement(0) == XNetConstants.LOCO_INFO_RESPONSE) { 686 log.trace("Throttle - message is LOCO_INFO_RESPONSE "); 687 if (l.getElement(1) == XNetConstants.LOCO_NOT_AVAILABLE) { 688 /* the address is in bytes 3 and 4*/ 689 if (getDccAddressHigh() == l.getElement(2) && getDccAddressLow() == l.getElement(3)) { 690 locoInUse(); 691 } 692 // We've processed this request, so set the status to Idle. 693 requestState = THROTTLEIDLE; 694 sendQueuedMessage(); 695 } else if (l.getElement(1) == XNetConstants.LOCO_FUNCTION_STATUS) { 696 /* Bytes 3 and 4 contain function momentary status information */ 697 int b3 = l.getElement(2); 698 int b4 = l.getElement(3); 699 parseFunctionMomentaryInformation(b3, b4); 700 // We've processed this request, so set the status to Idle. 701 requestState = THROTTLEIDLE; 702 sendQueuedMessage(); 703 // And then we want to request the Function Status for F13-F28 704 sendFunctionHighInformationRequest(); 705 706 } else if (l.getElement(1) == XNetConstants.LOCO_FUNCTION_STATUS_HIGH) { 707 /* Bytes 3 and 4 contain function status information for F13-F28*/ 708 int b3 = l.getElement(2); 709 int b4 = l.getElement(3); 710 parseFunctionHighInformation(b3, b4); 711 //We've processed this request, so set the status to Idle. 712 requestState = THROTTLEIDLE; 713 sendQueuedMessage(); 714 // And then we want to request the Function Momentary Status 715 // for functions F13-F28 716 sendFunctionHighMomentaryStatusRequest(); 717 } else { 718 // The reply to the outstanding request has been consumed, 719 // so no timeout will ever fire for it. Without this 720 // branch an unrecognized subtype leaves requestState stuck 721 // (never returns to THROTTLEIDLE) and every command queued 722 // afterwards is silently held back forever. 723 log.debug("Unhandled LOCO_INFO_RESPONSE subtype {} while in THROTTLESTATSENT, resuming queue", 724 l.getElement(1)); 725 requestState = THROTTLEIDLE; 726 sendQueuedMessage(); 727 } 728 } else if (l.isRetransmittableErrorMsg()) { 729 /* this is a communications error */ 730 log.trace("Communications error occurred - message received was: {}", l); 731 } else if (l.isUnsupportedError()) { 732 /* The Command Station does not support this command */ 733 log.error("Unsupported Command Sent to command station"); 734 if ((requestState & THROTTLEMOMSTATSENT) == THROTTLEMOMSTATSENT) { 735 // if momentary is not supported, try requesting the 736 // high function state. 737 requestState = THROTTLEIDLE; 738 sendFunctionHighInformationRequest(); 739 } else { 740 requestState = THROTTLEIDLE; 741 sendQueuedMessage(); 742 } 743 } else { 744 /* this is an unknown error */ 745 requestState = THROTTLEIDLE; 746 sendQueuedMessage(); 747 log.trace("Received unhandled response: {}", l); 748 } 749 } 750 } 751 752 private void locoInUse() { 753 if (isAvailable) { 754 //Set the Is available flag to Throttle.False 755 log.info("Loco {} In use by another device", getDccAddress()); 756 setIsAvailable(false); 757 } 758 } 759 760 /** 761 * {@inheritDoc} 762 */ 763 @Override 764 public void message(XNetMessage l) { 765 // no need to handle outgoing messages. 766 } 767 768 /** 769 * {@inheritDoc} 770 */ 771 @Override 772 public void notifyTimeout(XNetMessage msg) { 773 log.debug("Notified of timeout on message {} , {} retries available.", 774 msg, msg.getRetries()); 775 if (msg.getRetries() > 0) { 776 // If the message still has retries available, send it back to 777 // the traffic controller. 778 synchronized (this) { 779 tc.sendXNetMessage(msg, this); 780 } 781 } else { 782 // Try to send the next queued message, if one is available. 783 sendQueuedMessage(); 784 } 785 } 786 787 // Status Information processing routines 788 // Used for return values from Status requests. 789 /** 790 * Get SpeedStep and availability information. 791 * @param b1 1st byte of message to examine 792 */ 793 protected void parseSpeedAndAvailability(int b1) { 794 /* the first data bite indicates the speed step mode, and 795 if the locomotive is being controlled by another throttle */ 796 797 if ((b1 & 0x08) == 0x08 && this.isAvailable) { 798 locoInUse(); 799 } else if ((b1 & 0x08) == 0x00 && !this.isAvailable) { 800 log.trace("Loco Is Available"); 801 setIsAvailable(true); 802 } 803 if ((b1 & 0x01) == 0x01) { 804 log.trace("Speed Step setting 27"); 805 notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_27); 806 } else if ((b1 & 0x02) == 0x02) { 807 log.trace("Speed Step setting 28"); 808 notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_28); 809 } else if ((b1 & 0x04) == 0x04) { 810 log.trace("Speed Step setting 128"); 811 notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_128); 812 } else { 813 log.trace("Speed Step setting 14"); 814 notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_14); 815 } 816 } 817 818 protected void notifyNewSpeedStepMode(SpeedStepMode mode) { 819 if (this.speedStepMode != mode) { 820 firePropertyChange(SPEEDSTEPS, 821 this.speedStepMode, 822 this.speedStepMode = mode); 823 } 824 } 825 826 /** 827 * Get Speed and Direction information. 828 * @param b2 2nd byte of message to examine 829 */ 830 protected void parseSpeedAndDirection(int b2) { 831 /* the second byte indicates the speed and direction setting */ 832 833 if ((b2 & 0x80) == 0x80 && !this.isForward) { 834 notifyNewDirection(true); 835 } else if ((b2 & 0x80) == 0x00 && this.isForward) { 836 notifyNewDirection(false); 837 } 838 839 if (this.speedStepMode == SpeedStepMode.NMRA_DCC_128) { 840 // We're in 128 speed step mode 841 int speedVal = b2 & 0x7f; 842 // The first speed step used is actually at 2 for 128 843 // speed step mode. 844 if (speedVal >= 1) { 845 speedVal -= 1; 846 } 847 if (java.lang.Math.abs( 848 this.getSpeedSetting() - ((float) speedVal / (float) 126)) >= 0.0079) { 849 synchronized(this) { 850 firePropertyChange(SPEEDSETTING, this.speedSetting, 851 this.speedSetting = (float) speedVal / (float) 126); 852 } 853 } 854 } else if (this.speedStepMode == SpeedStepMode.NMRA_DCC_28) { 855 // We're in 28 speed step mode 856 // We have to re-arrange the bits, since bit 4 is the LSB, 857 // but other bits are in order from 0-3 858 int speedVal = ((b2 & 0x0F) << 1) 859 + ((b2 & 0x10) >> 4); 860 // The first speed step used is actually at 4 for 28 861 // speed step mode. 862 if (speedVal >= 3) { 863 speedVal -= 3; 864 } else { 865 speedVal = 0; 866 } 867 if (java.lang.Math.abs( 868 this.getSpeedSetting() - ((float) speedVal / (float) 28)) >= 0.035) { 869 synchronized(this) { 870 firePropertyChange(SPEEDSETTING, this.speedSetting, 871 this.speedSetting = (float) speedVal / (float) 28); 872 } 873 } 874 } else if (this.speedStepMode == SpeedStepMode.NMRA_DCC_27) { 875 // We're in 27 speed step mode 876 // We have to re-arrange the bits, since bit 4 is the LSB, 877 // but other bits are in order from 0-3 878 int speedVal = ((b2 & 0x0F) << 1) 879 + ((b2 & 0x10) >> 4); 880 // The first speed step used is actually at 4 for 27 881 // speed step mode. 882 if (speedVal >= 3) { 883 speedVal -= 3; 884 } else { 885 speedVal = 0; 886 } 887 if (java.lang.Math.abs( 888 this.getSpeedSetting() - ((float) speedVal / (float) 27)) >= 0.037) { 889 synchronized(this) { 890 firePropertyChange(SPEEDSETTING, this.speedSetting, 891 this.speedSetting = (float) speedVal / (float) 27); 892 } 893 } 894 } else { 895 // Assume we're in 14 speed step mode. 896 int speedVal = (b2 & 0x0F); 897 if (speedVal >= 1) { 898 speedVal -= 1; 899 } 900 if (java.lang.Math.abs( 901 this.getSpeedSetting() - ((float) speedVal / (float) 14)) >= 0.071) { 902 synchronized(this) { 903 firePropertyChange(SPEEDSETTING, this.speedSetting, 904 this.speedSetting = (float) speedVal / (float) 14); 905 } 906 } 907 } 908 } 909 910 protected void notifyNewDirection(boolean forward) { 911 firePropertyChange(ISFORWARD, this.isForward, this.isForward = forward); 912 log.trace("Throttle - Changed direction to {} Locomotive: {}", forward ? "forward" : "reverse", getDccAddress()); 913 } 914 915 protected void parseFunctionInformation(int b3, int b4) { 916 log.trace("Parsing Function F0-F12 status, function bytes: {} and {}", 917 b3, b4); 918 /* data byte 3 is the status of F0 F4 F3 F2 F1 */ 919 updateFunction(0, (b3 & 0x10) == 0x10); 920 updateFunction(1, (b3 & 0x01) == 0x01); 921 updateFunction(2, (b3 & 0x02) == 0x02); 922 updateFunction(3, (b3 & 0x04) == 0x04); 923 updateFunction(4, (b3 & 0x08) == 0x08); 924 /* data byte 4 is the status of F12 F11 F10 F9 F8 F7 F6 F5 */ 925 updateFunction(5, (b4 & 0x01) == 0x01); 926 updateFunction(6, (b4 & 0x02) == 0x02); 927 updateFunction(7, (b4 & 0x04) == 0x04); 928 updateFunction(8, (b4 & 0x08) == 0x08); 929 updateFunction(9, (b4 & 0x10) == 0x10); 930 updateFunction(10, (b4 & 0x20) == 0x20); 931 updateFunction(11, (b4 & 0x40) == 0x40); 932 updateFunction(12, (b4 & 0x80) == 0x80); 933 } 934 935 protected void parseFunctionHighInformation(int b3, int b4) { 936 log.trace("Parsing Function F13-F28 status, function bytes: {} and {}", 937 b3, b4); 938 /* data byte 3 is the status of F20 F19 F18 F17 F16 F15 F14 F13 */ 939 updateFunction(13, (b3 & 0x01) == 0x01); 940 updateFunction(14, (b3 & 0x02) == 0x02); 941 updateFunction(15, (b3 & 0x04) == 0x04); 942 updateFunction(16, (b3 & 0x08) == 0x08); 943 updateFunction(17, (b3 & 0x10) == 0x10); 944 updateFunction(18, (b3 & 0x20) == 0x20); 945 updateFunction(19, (b3 & 0x40) == 0x40); 946 updateFunction(20, (b3 & 0x80) == 0x80); 947 /* data byte 4 is the status of F28 F27 F26 F25 F24 F23 F22 F21 */ 948 updateFunction(21, (b4 & 0x01) == 0x01); 949 updateFunction(22, (b4 & 0x02) == 0x02); 950 updateFunction(23, (b4 & 0x04) == 0x04); 951 updateFunction(24, (b4 & 0x08) == 0x08); 952 updateFunction(25, (b4 & 0x10) == 0x10); 953 updateFunction(26, (b4 & 0x20) == 0x20); 954 updateFunction(27, (b4 & 0x40) == 0x40); 955 updateFunction(28, (b4 & 0x80) == 0x80); 956 957 } 958 959 protected void parseFunctionMomentaryInformation(int b3, int b4) { 960 log.trace("Parsing Function Momentary status, function bytes: {} and {}", 961 b3, b4); 962 /* data byte 3 is the momentary status of F0 F4 F3 F2 F1 */ 963 checkForFunctionMomentaryValueChange(0, b3, 0x10, getFunctionMomentary(0)); 964 checkForFunctionMomentaryValueChange(1, b3, 0x01, getFunctionMomentary(1)); 965 checkForFunctionMomentaryValueChange(2, b3, 0x02, getFunctionMomentary(2)); 966 checkForFunctionMomentaryValueChange(3, b3, 0x04, getFunctionMomentary(3)); 967 checkForFunctionMomentaryValueChange(4, b3, 0x08, getFunctionMomentary(4)); 968 /* data byte 4 is the momentary status of F12 F11 F10 F9 F8 F7 F6 F5 */ 969 checkForFunctionMomentaryValueChange(5, b4, 0x01, getFunctionMomentary(5)); 970 checkForFunctionMomentaryValueChange(6, b4, 0x02, getFunctionMomentary(6)); 971 checkForFunctionMomentaryValueChange(7, b4, 0x04, getFunctionMomentary(7)); 972 checkForFunctionMomentaryValueChange(8, b4, 0x08, getFunctionMomentary(8)); 973 checkForFunctionMomentaryValueChange(9, b4, 0x10, getFunctionMomentary(9)); 974 checkForFunctionMomentaryValueChange(10, b4, 0x20, getFunctionMomentary(10)); 975 checkForFunctionMomentaryValueChange(11, b4, 0x40, getFunctionMomentary(11)); 976 checkForFunctionMomentaryValueChange(12, b4, 0x80, getFunctionMomentary(12)); 977 } 978 979 protected void parseFunctionHighMomentaryInformation(int b3, int b4) { 980 log.trace("Parsing Function F13-F28 Momentary status, function bytes: {} and {}", 981 b3, b4); 982 /* data byte 3 is the momentary status of F20 F19 F17 F16 F15 F14 F13 */ 983 checkForFunctionMomentaryValueChange(13, b3, 0x01, getFunctionMomentary(13)); 984 checkForFunctionMomentaryValueChange(14, b3, 0x02, getFunctionMomentary(14)); 985 checkForFunctionMomentaryValueChange(15, b3, 0x04, getFunctionMomentary(15)); 986 checkForFunctionMomentaryValueChange(16, b3, 0x08, getFunctionMomentary(16)); 987 checkForFunctionMomentaryValueChange(17, b3, 0x10, getFunctionMomentary(17)); 988 checkForFunctionMomentaryValueChange(18, b3, 0x20, getFunctionMomentary(18)); 989 checkForFunctionMomentaryValueChange(19, b3, 0x40, getFunctionMomentary(19)); 990 checkForFunctionMomentaryValueChange(20, b3, 0x80, getFunctionMomentary(20)); 991 /* data byte 4 is the momentary status of F28 F27 F26 F25 F24 F23 F22 F21 */ 992 checkForFunctionMomentaryValueChange(21, b4, 0x01, getFunctionMomentary(21)); 993 checkForFunctionMomentaryValueChange(22, b4, 0x02, getFunctionMomentary(22)); 994 checkForFunctionMomentaryValueChange(23, b4, 0x04, getFunctionMomentary(23)); 995 checkForFunctionMomentaryValueChange(24, b4, 0x08, getFunctionMomentary(24)); 996 checkForFunctionMomentaryValueChange(25, b4, 0x10, getFunctionMomentary(25)); 997 checkForFunctionMomentaryValueChange(26, b4, 0x20, getFunctionMomentary(26)); 998 checkForFunctionMomentaryValueChange(27, b4, 0x40, getFunctionMomentary(27)); 999 checkForFunctionMomentaryValueChange(28, b4, 0x80, getFunctionMomentary(28)); 1000 } 1001 1002 protected void checkForFunctionMomentaryValueChange(int funcNum, int bytevalue, int bitmask, boolean currentValue) { 1003 if ((bytevalue & bitmask) == bitmask && !currentValue) { 1004 updateFunctionMomentary(funcNum, true); 1005 } else if ((bytevalue & bitmask) == 0x00 && currentValue) { 1006 updateFunctionMomentary(funcNum, false); 1007 } 1008 } 1009 1010 /** 1011 * Set the internal isAvailable property. 1012 * 1013 * @param available true if available; false otherwise 1014 */ 1015 protected void setIsAvailable(boolean available) { 1016 firePropertyChange("IsAvailable", this.isAvailable, this.isAvailable = available); 1017 /* if we're setting this to true, stop the timer, 1018 otherwise start the timer. */ 1019 if (available) { 1020 stopStatusTimer(); 1021 } else { 1022 startStatusTimer(); 1023 } 1024 } 1025 1026 /** 1027 * Set up the status timer, and start it. 1028 */ 1029 protected void startStatusTimer() { 1030 log.debug("Status Timer Started"); 1031 1032 if (statusTask != null) { 1033 statusTask.cancel(); 1034 statusTask = null; 1035 } 1036 statusTask = new java.util.TimerTask() { 1037 @Override 1038 public void run() { 1039 /* If the timer times out, just send a status 1040 request message */ 1041 sendStatusInformationRequest(); 1042 } 1043 }; 1044 1045 jmri.util.TimerUtil.schedule(statusTask, statTimeoutValue, statTimeoutValue); 1046 } 1047 1048 /** 1049 * Stop the Status Timer 1050 */ 1051 protected void stopStatusTimer() { 1052 log.debug("Status Timer Stopped"); 1053 if (statusTask != null) { 1054 try { 1055 statusTask.cancel(); 1056 } catch (IllegalStateException ise) { 1057 log.debug("Timer already canceled"); 1058 } 1059 statusTask = null; 1060 } 1061 } 1062 1063 @Override 1064 public LocoAddress getLocoAddress() { 1065 return new DccLocoAddress(address, XNetThrottleManager.isLongAddress(address)); 1066 } 1067 1068 // A queue to hold outstanding messages 1069 protected LinkedBlockingQueue<RequestMessage> requestList; 1070 1071 /** 1072 * Send message from queue. 1073 */ 1074 protected synchronized void sendQueuedMessage() { 1075 1076 RequestMessage msg; 1077 // check to see if the queue has a message in it, and if it does, 1078 // remove the first message 1079 if (!requestList.isEmpty()) { 1080 log.debug("sending message to traffic controller"); 1081 // if the queue is not empty, remove the first message 1082 // from the queue, send the message, and set the state machine 1083 // to the required state. 1084 try { 1085 msg = requestList.take(); 1086 } catch (java.lang.InterruptedException ie) { 1087 return; // if there was an error, exit. 1088 } 1089 requestState = msg.getState(); 1090 tc.sendXNetMessage(msg.getMsg(), this); 1091 } else { 1092 log.debug("message queue empty"); 1093 // if the queue is empty, set the state to idle. 1094 requestState = THROTTLEIDLE; 1095 } 1096 } 1097 1098 /** 1099 * Queue a message. 1100 * @param m message to send 1101 * @param s state 1102 */ 1103 protected synchronized void queueMessage(XNetMessage m, int s) { 1104 log.debug("adding message to message queue"); 1105 // put the message in the queue 1106 RequestMessage msg = new RequestMessage(m, s); 1107 try { 1108 requestList.put(msg); 1109 } catch (java.lang.InterruptedException ie) { 1110 log.trace("Interrupted while queueing message {}", msg); 1111 } 1112 // if the state is idle, trigger the message send 1113 if (requestState == THROTTLEIDLE) { 1114 sendQueuedMessage(); 1115 } 1116 } 1117 1118 /** 1119 * Internal class to hold a request message, along with the associated 1120 * throttle state. 1121 */ 1122 protected static class RequestMessage { 1123 1124 private final int state; 1125 private final XNetMessage msg; 1126 1127 RequestMessage(XNetMessage m, int s) { 1128 state = s; 1129 msg = m; 1130 } 1131 1132 int getState() { 1133 return state; 1134 } 1135 1136 XNetMessage getMsg() { 1137 return msg; 1138 } 1139 } 1140 1141 // register for notification 1142 private static final Logger log = LoggerFactory.getLogger(XNetThrottle.class); 1143 1144}