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 static private so it can be called as an argument to super(..) 047 static private 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 } 718 } else if (l.isRetransmittableErrorMsg()) { 719 /* this is a communications error */ 720 log.trace("Communications error occurred - message received was: {}", l); 721 } else if (l.isUnsupportedError()) { 722 /* The Command Station does not support this command */ 723 log.error("Unsupported Command Sent to command station"); 724 if ((requestState & THROTTLEMOMSTATSENT) == THROTTLEMOMSTATSENT) { 725 // if momentary is not supported, try requesting the 726 // high function state. 727 requestState = THROTTLEIDLE; 728 sendFunctionHighInformationRequest(); 729 } else { 730 requestState = THROTTLEIDLE; 731 sendQueuedMessage(); 732 } 733 } else { 734 /* this is an unknown error */ 735 requestState = THROTTLEIDLE; 736 sendQueuedMessage(); 737 log.trace("Received unhandled response: {}", l); 738 } 739 } 740 } 741 742 private void locoInUse() { 743 if (isAvailable) { 744 //Set the Is available flag to Throttle.False 745 log.info("Loco {} In use by another device", getDccAddress()); 746 setIsAvailable(false); 747 } 748 } 749 750 /** 751 * {@inheritDoc} 752 */ 753 @Override 754 public void message(XNetMessage l) { 755 // no need to handle outgoing messages. 756 } 757 758 /** 759 * {@inheritDoc} 760 */ 761 @Override 762 public void notifyTimeout(XNetMessage msg) { 763 log.debug("Notified of timeout on message {} , {} retries available.", 764 msg, msg.getRetries()); 765 if (msg.getRetries() > 0) { 766 // If the message still has retries available, send it back to 767 // the traffic controller. 768 synchronized (this) { 769 tc.sendXNetMessage(msg, this); 770 } 771 } else { 772 // Try to send the next queued message, if one is available. 773 sendQueuedMessage(); 774 } 775 } 776 777 // Status Information processing routines 778 // Used for return values from Status requests. 779 /** 780 * Get SpeedStep and availability information. 781 * @param b1 1st byte of message to examine 782 */ 783 protected void parseSpeedAndAvailability(int b1) { 784 /* the first data bite indicates the speed step mode, and 785 if the locomotive is being controlled by another throttle */ 786 787 if ((b1 & 0x08) == 0x08 && this.isAvailable) { 788 locoInUse(); 789 } else if ((b1 & 0x08) == 0x00 && !this.isAvailable) { 790 log.trace("Loco Is Available"); 791 setIsAvailable(true); 792 } 793 if ((b1 & 0x01) == 0x01) { 794 log.trace("Speed Step setting 27"); 795 notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_27); 796 } else if ((b1 & 0x02) == 0x02) { 797 log.trace("Speed Step setting 28"); 798 notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_28); 799 } else if ((b1 & 0x04) == 0x04) { 800 log.trace("Speed Step setting 128"); 801 notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_128); 802 } else { 803 log.trace("Speed Step setting 14"); 804 notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_14); 805 } 806 } 807 808 protected void notifyNewSpeedStepMode(SpeedStepMode mode) { 809 if (this.speedStepMode != mode) { 810 firePropertyChange(SPEEDSTEPS, 811 this.speedStepMode, 812 this.speedStepMode = mode); 813 } 814 } 815 816 /** 817 * Get Speed and Direction information. 818 * @param b2 2nd byte of message to examine 819 */ 820 protected void parseSpeedAndDirection(int b2) { 821 /* the second byte indicates the speed and direction setting */ 822 823 if ((b2 & 0x80) == 0x80 && !this.isForward) { 824 notifyNewDirection(true); 825 } else if ((b2 & 0x80) == 0x00 && this.isForward) { 826 notifyNewDirection(false); 827 } 828 829 if (this.speedStepMode == SpeedStepMode.NMRA_DCC_128) { 830 // We're in 128 speed step mode 831 int speedVal = b2 & 0x7f; 832 // The first speed step used is actually at 2 for 128 833 // speed step mode. 834 if (speedVal >= 1) { 835 speedVal -= 1; 836 } 837 if (java.lang.Math.abs( 838 this.getSpeedSetting() - ((float) speedVal / (float) 126)) >= 0.0079) { 839 synchronized(this) { 840 firePropertyChange(SPEEDSETTING, this.speedSetting, 841 this.speedSetting = (float) speedVal / (float) 126); 842 } 843 } 844 } else if (this.speedStepMode == SpeedStepMode.NMRA_DCC_28) { 845 // We're in 28 speed step mode 846 // We have to re-arrange the bits, since bit 4 is the LSB, 847 // but other bits are in order from 0-3 848 int speedVal = ((b2 & 0x0F) << 1) 849 + ((b2 & 0x10) >> 4); 850 // The first speed step used is actually at 4 for 28 851 // speed step mode. 852 if (speedVal >= 3) { 853 speedVal -= 3; 854 } else { 855 speedVal = 0; 856 } 857 if (java.lang.Math.abs( 858 this.getSpeedSetting() - ((float) speedVal / (float) 28)) >= 0.035) { 859 synchronized(this) { 860 firePropertyChange(SPEEDSETTING, this.speedSetting, 861 this.speedSetting = (float) speedVal / (float) 28); 862 } 863 } 864 } else if (this.speedStepMode == SpeedStepMode.NMRA_DCC_27) { 865 // We're in 27 speed step mode 866 // We have to re-arrange the bits, since bit 4 is the LSB, 867 // but other bits are in order from 0-3 868 int speedVal = ((b2 & 0x0F) << 1) 869 + ((b2 & 0x10) >> 4); 870 // The first speed step used is actually at 4 for 27 871 // speed step mode. 872 if (speedVal >= 3) { 873 speedVal -= 3; 874 } else { 875 speedVal = 0; 876 } 877 if (java.lang.Math.abs( 878 this.getSpeedSetting() - ((float) speedVal / (float) 27)) >= 0.037) { 879 synchronized(this) { 880 firePropertyChange(SPEEDSETTING, this.speedSetting, 881 this.speedSetting = (float) speedVal / (float) 27); 882 } 883 } 884 } else { 885 // Assume we're in 14 speed step mode. 886 int speedVal = (b2 & 0x0F); 887 if (speedVal >= 1) { 888 speedVal -= 1; 889 } 890 if (java.lang.Math.abs( 891 this.getSpeedSetting() - ((float) speedVal / (float) 14)) >= 0.071) { 892 synchronized(this) { 893 firePropertyChange(SPEEDSETTING, this.speedSetting, 894 this.speedSetting = (float) speedVal / (float) 14); 895 } 896 } 897 } 898 } 899 900 protected void notifyNewDirection(boolean forward) { 901 firePropertyChange(ISFORWARD, this.isForward, this.isForward = forward); 902 log.trace("Throttle - Changed direction to {} Locomotive: {}", forward ? "forward" : "reverse", getDccAddress()); 903 } 904 905 protected void parseFunctionInformation(int b3, int b4) { 906 log.trace("Parsing Function F0-F12 status, function bytes: {} and {}", 907 b3, b4); 908 /* data byte 3 is the status of F0 F4 F3 F2 F1 */ 909 updateFunction(0, (b3 & 0x10) == 0x10); 910 updateFunction(1, (b3 & 0x01) == 0x01); 911 updateFunction(2, (b3 & 0x02) == 0x02); 912 updateFunction(3, (b3 & 0x04) == 0x04); 913 updateFunction(4, (b3 & 0x08) == 0x08); 914 /* data byte 4 is the status of F12 F11 F10 F9 F8 F7 F6 F5 */ 915 updateFunction(5, (b4 & 0x01) == 0x01); 916 updateFunction(6, (b4 & 0x02) == 0x02); 917 updateFunction(7, (b4 & 0x04) == 0x04); 918 updateFunction(8, (b4 & 0x08) == 0x08); 919 updateFunction(9, (b4 & 0x10) == 0x10); 920 updateFunction(10, (b4 & 0x20) == 0x20); 921 updateFunction(11, (b4 & 0x40) == 0x40); 922 updateFunction(12, (b4 & 0x80) == 0x80); 923 } 924 925 protected void parseFunctionHighInformation(int b3, int b4) { 926 log.trace("Parsing Function F13-F28 status, function bytes: {} and {}", 927 b3, b4); 928 /* data byte 3 is the status of F20 F19 F18 F17 F16 F15 F14 F13 */ 929 updateFunction(13, (b3 & 0x01) == 0x01); 930 updateFunction(14, (b3 & 0x02) == 0x02); 931 updateFunction(15, (b3 & 0x04) == 0x04); 932 updateFunction(16, (b3 & 0x08) == 0x08); 933 updateFunction(17, (b3 & 0x10) == 0x10); 934 updateFunction(18, (b3 & 0x20) == 0x20); 935 updateFunction(19, (b3 & 0x40) == 0x40); 936 updateFunction(20, (b3 & 0x80) == 0x80); 937 /* data byte 4 is the status of F28 F27 F26 F25 F24 F23 F22 F21 */ 938 updateFunction(21, (b4 & 0x01) == 0x01); 939 updateFunction(22, (b4 & 0x02) == 0x02); 940 updateFunction(23, (b4 & 0x04) == 0x04); 941 updateFunction(24, (b4 & 0x08) == 0x08); 942 updateFunction(25, (b4 & 0x10) == 0x10); 943 updateFunction(26, (b4 & 0x20) == 0x20); 944 updateFunction(27, (b4 & 0x40) == 0x40); 945 updateFunction(28, (b4 & 0x80) == 0x80); 946 947 } 948 949 protected void parseFunctionMomentaryInformation(int b3, int b4) { 950 log.trace("Parsing Function Momentary status, function bytes: {} and {}", 951 b3, b4); 952 /* data byte 3 is the momentary status of F0 F4 F3 F2 F1 */ 953 checkForFunctionMomentaryValueChange(0, b3, 0x10, getFunctionMomentary(0)); 954 checkForFunctionMomentaryValueChange(1, b3, 0x01, getFunctionMomentary(1)); 955 checkForFunctionMomentaryValueChange(2, b3, 0x02, getFunctionMomentary(2)); 956 checkForFunctionMomentaryValueChange(3, b3, 0x04, getFunctionMomentary(3)); 957 checkForFunctionMomentaryValueChange(4, b3, 0x08, getFunctionMomentary(4)); 958 /* data byte 4 is the momentary status of F12 F11 F10 F9 F8 F7 F6 F5 */ 959 checkForFunctionMomentaryValueChange(5, b4, 0x01, getFunctionMomentary(5)); 960 checkForFunctionMomentaryValueChange(6, b4, 0x02, getFunctionMomentary(6)); 961 checkForFunctionMomentaryValueChange(7, b4, 0x04, getFunctionMomentary(7)); 962 checkForFunctionMomentaryValueChange(8, b4, 0x08, getFunctionMomentary(8)); 963 checkForFunctionMomentaryValueChange(9, b4, 0x10, getFunctionMomentary(9)); 964 checkForFunctionMomentaryValueChange(10, b4, 0x20, getFunctionMomentary(10)); 965 checkForFunctionMomentaryValueChange(11, b4, 0x40, getFunctionMomentary(11)); 966 checkForFunctionMomentaryValueChange(12, b4, 0x80, getFunctionMomentary(12)); 967 } 968 969 protected void parseFunctionHighMomentaryInformation(int b3, int b4) { 970 log.trace("Parsing Function F13-F28 Momentary status, function bytes: {} and {}", 971 b3, b4); 972 /* data byte 3 is the momentary status of F20 F19 F17 F16 F15 F14 F13 */ 973 checkForFunctionMomentaryValueChange(13, b3, 0x01, getFunctionMomentary(13)); 974 checkForFunctionMomentaryValueChange(14, b3, 0x02, getFunctionMomentary(14)); 975 checkForFunctionMomentaryValueChange(15, b3, 0x04, getFunctionMomentary(15)); 976 checkForFunctionMomentaryValueChange(16, b3, 0x08, getFunctionMomentary(16)); 977 checkForFunctionMomentaryValueChange(17, b3, 0x10, getFunctionMomentary(17)); 978 checkForFunctionMomentaryValueChange(18, b3, 0x20, getFunctionMomentary(18)); 979 checkForFunctionMomentaryValueChange(19, b3, 0x40, getFunctionMomentary(19)); 980 checkForFunctionMomentaryValueChange(20, b3, 0x80, getFunctionMomentary(20)); 981 /* data byte 4 is the momentary status of F28 F27 F26 F25 F24 F23 F22 F21 */ 982 checkForFunctionMomentaryValueChange(21, b4, 0x01, getFunctionMomentary(21)); 983 checkForFunctionMomentaryValueChange(22, b4, 0x02, getFunctionMomentary(22)); 984 checkForFunctionMomentaryValueChange(23, b4, 0x04, getFunctionMomentary(23)); 985 checkForFunctionMomentaryValueChange(24, b4, 0x08, getFunctionMomentary(24)); 986 checkForFunctionMomentaryValueChange(25, b4, 0x10, getFunctionMomentary(25)); 987 checkForFunctionMomentaryValueChange(26, b4, 0x20, getFunctionMomentary(26)); 988 checkForFunctionMomentaryValueChange(27, b4, 0x40, getFunctionMomentary(27)); 989 checkForFunctionMomentaryValueChange(28, b4, 0x80, getFunctionMomentary(28)); 990 } 991 992 protected void checkForFunctionMomentaryValueChange(int funcNum, int bytevalue, int bitmask, boolean currentValue) { 993 if ((bytevalue & bitmask) == bitmask && !currentValue) { 994 updateFunctionMomentary(funcNum, true); 995 } else if ((bytevalue & bitmask) == 0x00 && currentValue) { 996 updateFunctionMomentary(funcNum, false); 997 } 998 } 999 1000 /** 1001 * Set the internal isAvailable property. 1002 * 1003 * @param available true if available; false otherwise 1004 */ 1005 protected void setIsAvailable(boolean available) { 1006 firePropertyChange("IsAvailable", this.isAvailable, this.isAvailable = available); 1007 /* if we're setting this to true, stop the timer, 1008 otherwise start the timer. */ 1009 if (available) { 1010 stopStatusTimer(); 1011 } else { 1012 startStatusTimer(); 1013 } 1014 } 1015 1016 /** 1017 * Set up the status timer, and start it. 1018 */ 1019 protected void startStatusTimer() { 1020 log.debug("Status Timer Started"); 1021 1022 if (statusTask != null) { 1023 statusTask.cancel(); 1024 statusTask = null; 1025 } 1026 statusTask = new java.util.TimerTask() { 1027 @Override 1028 public void run() { 1029 /* If the timer times out, just send a status 1030 request message */ 1031 sendStatusInformationRequest(); 1032 } 1033 }; 1034 1035 jmri.util.TimerUtil.schedule(statusTask, statTimeoutValue, statTimeoutValue); 1036 } 1037 1038 /** 1039 * Stop the Status Timer 1040 */ 1041 protected void stopStatusTimer() { 1042 log.debug("Status Timer Stopped"); 1043 if (statusTask != null) { 1044 try { 1045 statusTask.cancel(); 1046 } catch (IllegalStateException ise) { 1047 log.debug("Timer already canceled"); 1048 } 1049 statusTask = null; 1050 } 1051 } 1052 1053 @Override 1054 public LocoAddress getLocoAddress() { 1055 return new DccLocoAddress(address, XNetThrottleManager.isLongAddress(address)); 1056 } 1057 1058 // A queue to hold outstanding messages 1059 protected LinkedBlockingQueue<RequestMessage> requestList; 1060 1061 /** 1062 * Send message from queue. 1063 */ 1064 protected synchronized void sendQueuedMessage() { 1065 1066 RequestMessage msg; 1067 // check to see if the queue has a message in it, and if it does, 1068 // remove the first message 1069 if (!requestList.isEmpty()) { 1070 log.debug("sending message to traffic controller"); 1071 // if the queue is not empty, remove the first message 1072 // from the queue, send the message, and set the state machine 1073 // to the required state. 1074 try { 1075 msg = requestList.take(); 1076 } catch (java.lang.InterruptedException ie) { 1077 return; // if there was an error, exit. 1078 } 1079 requestState = msg.getState(); 1080 tc.sendXNetMessage(msg.getMsg(), this); 1081 } else { 1082 log.debug("message queue empty"); 1083 // if the queue is empty, set the state to idle. 1084 requestState = THROTTLEIDLE; 1085 } 1086 } 1087 1088 /** 1089 * Queue a message. 1090 * @param m message to send 1091 * @param s state 1092 */ 1093 protected synchronized void queueMessage(XNetMessage m, int s) { 1094 log.debug("adding message to message queue"); 1095 // put the message in the queue 1096 RequestMessage msg = new RequestMessage(m, s); 1097 try { 1098 requestList.put(msg); 1099 } catch (java.lang.InterruptedException ie) { 1100 log.trace("Interrupted while queueing message {}", msg); 1101 } 1102 // if the state is idle, trigger the message send 1103 if (requestState == THROTTLEIDLE) { 1104 sendQueuedMessage(); 1105 } 1106 } 1107 1108 /** 1109 * Internal class to hold a request message, along with the associated 1110 * throttle state. 1111 */ 1112 protected static class RequestMessage { 1113 1114 private final int state; 1115 private final XNetMessage msg; 1116 1117 RequestMessage(XNetMessage m, int s) { 1118 state = s; 1119 msg = m; 1120 } 1121 1122 int getState() { 1123 return state; 1124 } 1125 1126 XNetMessage getMsg() { 1127 return msg; 1128 } 1129 } 1130 1131 // register for notification 1132 private static final Logger log = LoggerFactory.getLogger(XNetThrottle.class); 1133 1134}