001package jmri.jmrix.loconet; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import jmri.jmrix.loconet.SlotMapEntry.SlotType; 005 006import java.util.ArrayList; 007import java.util.List; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011/** 012 * Represents the contents of a single slot in the LocoNet command station. 013 * <p> 014 * A SlotListener can be registered to hear of changes in this slot. All changes 015 * in values will result in notification. 016 * <p> 017 * Strictly speaking, functions 9 through 28 are not in the actual slot, but 018 * it's convenient to imagine there's an "extended slot" and keep track of them 019 * here. This is a partial implementation, though, because setting is still done 020 * directly in {@link LocoNetThrottle}. In particular, if this slot has not been 021 * read from the command station, the first message directly setting F9 through 022 * F28 will not have a place to store information. Instead, it will trigger a 023 * slot read, so the following messages will be properly handled. 024 * <p> 025 * Some of the message formats used in this class are Copyright Digitrax, Inc. 026 * and used with permission as part of the JMRI project. That permission does 027 * not extend to uses in other software products. If you wish to use this code, 028 * algorithm or these message formats outside of JMRI, please contact Digitrax 029 * Inc for separate permission. 030 * 031 * @author Bob Jacobsen Copyright (C) 2001 032 * @author Stephen Williams Copyright (C) 2008 033 * @author B. Milhaupt, Copyright (C) 2018 034 * @author S. Gigiel, Copyright (C) 2018 035 */ 036public class LocoNetSlot { 037 038 // create a specific slot 039 /** 040 * Create a slot based solely on a slot number. The remainder of the slot is 041 * left un-initialized. 042 * 043 * @param slotNum slot number to be assigned to the new LocoNetSlot object 044 */ 045 public LocoNetSlot(int slotNum) { 046 this(slotNum, LnConstants.LOCONETPROTOCOL_UNKNOWN); 047 } 048 049 /** 050 * Create a slot based solely on a slot number. The remainder of the slot is 051 * left un-initialized. 052 * 053 * @param slotNum - slot number to be assigned to the new LocoNetSlot object 054 * @param inLoconetProtocol - can be 0 = unknown, 1 = version 1.1, 2 = Expandedslot 055 */ 056 public LocoNetSlot(int slotNum, int inLoconetProtocol) { 057 this(slotNum, inLoconetProtocol, SlotType.LOCO); 058 if ((slotNum == 0) || (slotNum > 120 && slot < 128) 059 || (slotNum > 247 && slotNum < 257) 060 || (slotNum > 375 && slotNum < 385)) { 061 slotType = SlotType.SYSTEM; 062 } else { 063 slotType = SlotType.LOCO; 064 } 065 } 066 067 /** 068 * Create a slot , initialize slotnum, protocol and slot type 069 * 070 * @param slotNum - slot number to be assigned to the new LocoNetSlot object 071 * @param inLoconetProtocol - can be 0 = unknown, 1 = version 1.1, 2 = Expandedslot 072 * @param inSlotType - SLotType enum 073 */ 074 public LocoNetSlot(int slotNum, int inLoconetProtocol, SlotType inSlotType) { 075 slot = slotNum; 076 loconetProtocol = inLoconetProtocol; 077 if (slotNum > 127 ) { 078 // has to be 2 079 loconetProtocol = LnConstants.LOCONETPROTOCOL_TWO; 080 } 081 slotType = inSlotType; 082 } 083 084 085 /** 086 * Creates a slot object based on the contents of a LocoNet message. 087 * The slot number is assumed to be found in byte 2 of the message if message is 0xE6 or bytes 2 and 3 for 0xE7 088 * 089 * @param l a LocoNet message 090 * @throws LocoNetException if the slot does not have an easily-found 091 * slot number 092 */ 093 public LocoNetSlot(LocoNetMessage l) throws LocoNetException { 094 // TODO: Consider removing, only used in testing. 095 // TODO: Consider limiting the types of LocoNet message which can be 096 // used to construct the object to only LocoNet slot write or slot 097 // report messages, since a LocoNetSlot object constructed from a LocoNet 098 // "speed" message or "dir/func" message does not give any other useful 099 // information for object initialization. 100 if ( l.getOpCode() == LnConstants.OPC_SL_RD_DATA || l.getOpCode() == LnConstants.OPC_WR_SL_DATA) { 101 slot = l.getElement(2); 102 loconetProtocol = LnConstants.LOCONETPROTOCOL_ONE; 103 } else if (l.getOpCode() == LnConstants.OPC_EXP_RD_SL_DATA || l.getOpCode() == LnConstants.OPC_EXP_WR_SL_DATA) { 104 slot = ( (l.getElement(2) & 0x03 ) *128) + l.getElement(3); 105 loconetProtocol = LnConstants.LOCONETPROTOCOL_TWO; 106 } else { 107 throw new LocoNetException("Invalid loconet message for setting up a slot"); 108 } 109 setSlot(l); 110 } 111 112 // accessors to specific information 113 /** 114 * Returns the slot number which was either specified or inferred at object 115 * creation time. 116 * 117 * @return the slot number 118 */ 119 public int getSlot() { 120 return slot; 121 } // cannot modify the slot number once created 122 123 /** 124 * Set the Slot Type 125 * @param value enum for slottype 126 */ 127 public void setSlotType(SlotType value) { 128 slotType = value; 129 } 130 131 /*** 132 * 133 * @return true if this is a systems slot else false 134 */ 135 public boolean isSystemSlot() { 136 return slotType == SlotType.SYSTEM; 137 } 138 139 /*** 140 * 141 * @return true if this is a systems slot else false 142 */ 143 public SlotType getSlotType() { 144 return slotType; 145 } 146 147 /** 148 * 149 * @return the protocol level support by the slot. 150 */ 151 public int getProtocol() { 152 return loconetProtocol; 153 } 154 155 /** 156 * set the protocol to be used 157 * @param value one,two or unknown 158 */ 159 protected void setProtocol(int value) { 160 loconetProtocol = value; 161 } 162 163 /** 164 * Get decoder mode. 165 * 166 * The decoder (operating) mode is taken from those bits in the slot's STAT 167 * byte which reflect the "speed steps" and "consisting" mode. Note that 168 * the other bits from the STAT byte are not visible via this method. 169 * this 170 * <p> 171 * For slot numbers not normally associated with mobile decoders, these bits 172 * may have other meanings. 173 * <p> 174 * Possible values are 175 * {@link LnConstants#DEC_MODE_128A}, 176 * {@link LnConstants#DEC_MODE_28A}, 177 * {@link LnConstants#DEC_MODE_128}, 178 * {@link LnConstants#DEC_MODE_14}, 179 * {@link LnConstants#DEC_MODE_28TRI}, 180 * {@link LnConstants#DEC_MODE_28} 181 * 182 * @return the encoded decoder operating mode. 183 */ 184 public int decoderType() { 185 return stat & LnConstants.DEC_MODE_MASK; 186 } 187 188 /** 189 * Get slot status. 190 * <p> 191 * These bits are set based on the STAT byte as seen in LocoNet slot write and 192 * slot read messages. These bits determine whether the command station is 193 * actively "refreshing" the loco's speed and direction information on the 194 * DCC track signal, and whether the slot is able to be re-assigned for use 195 * by another locomotive. 196 * <p> 197 * For slot numbers not normally associated with mobile decoders, these bits 198 * may have other meanings. 199 * <p> 200 * This returns only those bits of the slot's STAT byte which are related to 201 * the slot's "status". 202 * <p> 203 * Possible values are 204 * {@link LnConstants#LOCO_IN_USE}, 205 * {@link LnConstants#LOCO_IDLE}, 206 * {@link LnConstants#LOCO_COMMON}, 207 * {@link LnConstants#LOCO_FREE} 208 * @return the slot status bits associated with the slot 209 */ 210 public int slotStatus() { 211 return stat & LnConstants.LOCOSTAT_MASK; 212 } 213 214 /** 215 * Get secondary slot status. 216 * <p> 217 * These bits are set based on the STAT2 byte as seen in LocoNet slot write and 218 * slot read messages. These bits determine how the command station interprets 219 * the "address" field of the slot. 220 * <p> 221 * For slot numbers not normally associated with mobile decoders, these bits 222 * may have other meanings. 223 * <p> 224 * This returns only those bits of the slot's STAT2 byte which are related to 225 * the slot's "secondary status". 226 * 227 * @return the slot secondary status bits associated with the slot 228 */ 229 230 public int ss2() { 231 return ss2; 232 } 233 234 /** 235 * Get the state of the Acquire Throttle / slot. 236 * It is fully initialized if this is true. 237 * If it is false then any changes to its state may be lost. 238 * @return true 239 */ 240 public boolean getIsInitilized() { 241 return isInitialized; 242 } 243 244 protected void setIsInitialized(boolean state) { 245 isInitialized = state; 246 } 247 248 /** 249 * Get consist status. 250 * <p> 251 * This returns only those bits of the slot's STAT byte which are related to 252 * the slot's "consisting status". 253 * <p> 254 * For slot numbers not normally associated with mobile decoders, these bits 255 * may have other meanings. 256 * <p> 257 * Possible values are 258 * {@link LnConstants#CONSIST_NO}, 259 * {@link LnConstants#CONSIST_TOP}, 260 * {@link LnConstants#CONSIST_MID}, 261 * {@link LnConstants#CONSIST_SUB} 262 * @return the slot "consist status", with unrelated bits zeroed 263 */ 264 public int consistStatus() { 265 return stat & LnConstants.CONSIST_MASK; 266 } 267 268 // direction and functions 269 /** 270 * Returns the direction of loco movement which applies when the slot's speed 271 * is set for movement. 272 * <p> 273 * For slot numbers not normally associated with mobile decoders, this bit 274 * may have other meanings. 275 * 276 * @return true if slot is set for forward movement, else false 277 */ 278 public boolean isForward() { 279 return 0 == (dirf & LnConstants.DIRF_DIR); 280 } 281 282 private boolean[] getFuncArray() { 283 return new boolean[]{isF0(),isF1(),isF2(),isF3(),isF4(),isF5(),isF6(),isF7(),isF8(), 284 isF9(),isF10(),isF11(),isF12(),isF13(),isF14(),isF15(),isF16(),isF17(),isF18(), 285 isF19(),isF20(),isF21(),isF22(),isF23(),isF24(),isF25(),isF26(),isF27(),isF28()}; 286 } 287 288 /** 289 * Return a slot Function state. 290 * <p> 291 * See individual Functions for meanings. 292 * 293 * @param Fn Function number, 0-28 294 * @return true if Function is "on", else false 295 */ 296 public boolean isFunction(int Fn){ 297 return getFuncArray()[Fn]; 298 } 299 300 /** 301 * Returns the slot's F0 state 302 * <p> 303 * For slot numbers not normally associated with mobile decoders, this bit 304 * may have other meanings. 305 * 306 * @return true if F0 is "on", else false 307 */ 308 public boolean isF0() { 309 // TODO: Consider throwing an exception (here and in similar methods) 310 // if the slot is one of the "special" slots where the slot is not 311 // storing mobile decoder funciton state in the associated bit. 312 return 0 != (dirf & LnConstants.DIRF_F0); 313 } 314 315 /** 316 * Returns the slot's F1 state 317 * <p> 318 * For slot numbers not normally associated with mobile decoders, this bit 319 * may have other meanings. 320 * 321 * @return true if F1 is "on", else false 322 */ 323 public boolean isF1() { 324 return 0 != (dirf & LnConstants.DIRF_F1); 325 } 326 327 /** 328 * Returns the slot's F2 state 329 * <p> 330 * For slot numbers not normally associated with mobile decoders, this bit 331 * may have other meanings. 332 * 333 * @return true if F2 is "on", else false 334 */ 335 public boolean isF2() { 336 return 0 != (dirf & LnConstants.DIRF_F2); 337 } 338 339 /** 340 * Returns the slot's F3 state 341 * <p> 342 * For slot numbers not normally associated with mobile decoders, this bit 343 * may have other meanings. 344 * 345 * @return true if F3 is "on", else false 346 */ 347 public boolean isF3() { 348 return 0 != (dirf & LnConstants.DIRF_F3); 349 } 350 351 /** 352 * Returns the slot's F4 state 353 * <p> 354 * For slot numbers not normally associated with mobile decoders, this bit 355 * may have other meanings. 356 * 357 * @return true if F4 is "on", else false 358 */ 359 public boolean isF4() { 360 return 0 != (dirf & LnConstants.DIRF_F4); 361 } 362 363 /** 364 * Returns the slot's F5 state 365 * <p> 366 * For slot numbers not normally associated with mobile decoders, this bit 367 * may have other meanings. 368 * 369 * @return true if F5 is "on", else false 370 */ 371 public boolean isF5() { 372 return 0 != (snd & LnConstants.SND_F5); 373 } 374 375 /** 376 * Returns the slot's F6 state 377 * <p> 378 * For slot numbers not normally associated with mobile decoders, this bit 379 * may have other meanings. 380 * 381 * @return true if F6 is "on", else false 382 */ 383 public boolean isF6() { 384 return 0 != (snd & LnConstants.SND_F6); 385 } 386 387 /** 388 * Returns the slot's F7 state 389 * <p> 390 * For slot numbers not normally associated with mobile decoders, this bit 391 * may have other meanings. 392 * 393 * @return true if F7 is "on", else false 394 */ 395 public boolean isF7() { 396 return 0 != (snd & LnConstants.SND_F7); 397 } 398 399 /** 400 * Returns the slot's F8 state 401 * <p> 402 * For slot numbers not normally associated with mobile decoders, this bit 403 * may have other meanings. 404 * 405 * @return true if F8 is "on", else false 406 */ 407 public boolean isF8() { 408 return 0 != (snd & LnConstants.SND_F8); 409 } 410 411 /** 412 * Returns the slot's F9 state 413 * <p> 414 * Some command stations do not actively remember the state of this function. 415 * JMRI attempts to track the messages which control this function, but may not 416 * reliably do so in some cases. 417 * <p> 418 * For slot numbers not normally associated with mobile decoders, this bit 419 * may have other meanings. 420 * 421 * @return true if F9 is "on", else false 422 */ 423 public boolean isF9() { 424 return localF9; 425 } 426 427 /** 428 * Returns the slot's F10 state 429 * <p> 430 * Some command stations do not actively remember the state of this function. 431 * JMRI attempts to track the messages which control this function, but may not 432 * reliably do so in some cases. 433 * <p> 434 * For slot numbers not normally associated with mobile decoders, this bit 435 * may have other meanings. 436 * 437 * @return true if F10 is "on", else false 438 */ 439 public boolean isF10() { 440 return localF10; 441 } 442 443 /** 444 * Returns the slot's F11 state 445 * <p> 446 * Some command stations do not actively remember the state of this function. 447 * JMRI attempts to track the messages which control this function, but may not 448 * reliably do so in some cases. 449 * <p> 450 * For slot numbers not normally associated with mobile decoders, this bit 451 * may have other meanings. 452 * 453 * @return true if F11 is "on", else false 454 */ 455 public boolean isF11() { 456 return localF11; 457 } 458 459 /** 460 * Returns the slot's F12 state 461 * <p> 462 * Some command stations do not actively remember the state of this function. 463 * JMRI attempts to track the messages which control this function, but may not 464 * reliably do so in some cases. 465 * <p> 466 * For slot numbers not normally associated with mobile decoders, this bit 467 * may have other meanings. 468 * 469 * @return true if F12 is "on", else false 470 */ 471 public boolean isF12() { 472 return localF12; 473 } 474 475 /** 476 * Returns the slot's F13 state 477 * <p> 478 * Some command stations do not actively remember the state of this function. 479 * JMRI attempts to track the messages which control this function, but may not 480 * reliably do so in some cases. 481 * <p> 482 * For slot numbers not normally associated with mobile decoders, this bit 483 * may have other meanings. 484 * 485 * @return true if F13 is "on", else false 486 */ 487 public boolean isF13() { 488 return localF13; 489 } 490 491 /** 492 * Returns the slot's F14 state 493 * <p> 494 * Some command stations do not actively remember the state of this function. 495 * JMRI attempts to track the messages which control this function, but may not 496 * reliably do so in some cases. 497 * <p> 498 * For slot numbers not normally associated with mobile decoders, this bit 499 * may have other meanings. 500 * 501 * @return true if F14 is "on", else false 502 */ 503 public boolean isF14() { 504 return localF14; 505 } 506 507 /** 508 * Returns the slot's F15 state 509 * <p> 510 * Some command stations do not actively remember the state of this function. 511 * JMRI attempts to track the messages which control this function, but may not 512 * reliably do so in some cases. 513 * <p> 514 * For slot numbers not normally associated with mobile decoders, this bit 515 * may have other meanings. 516 * 517 * @return true if F15 is "on", else false 518 */ 519 public boolean isF15() { 520 return localF15; 521 } 522 523 /** 524 * Returns the slot's F16 state 525 * <p> 526 * Some command stations do not actively remember the state of this function. 527 * JMRI attempts to track the messages which control this function, but may not 528 * reliably do so in some cases. 529 * <p> 530 * For slot numbers not normally associated with mobile decoders, this bit 531 * may have other meanings. 532 * 533 * @return true if F16 is "on", else false 534 */ 535 public boolean isF16() { 536 return localF16; 537 } 538 539 /** 540 * Returns the slot's F17 state 541 * <p> 542 * Some command stations do not actively remember the state of this function. 543 * JMRI attempts to track the messages which control this function, but may not 544 * reliably do so in some cases. 545 * <p> 546 * For slot numbers not normally associated with mobile decoders, this bit 547 * may have other meanings. 548 * 549 * @return true if F17 is "on", else false 550 */ 551 public boolean isF17() { 552 return localF17; 553 } 554 555 /** 556 * Returns the slot's F1 state 557 * <p> 558 * Some command stations do not actively remember the state of this function. 559 * JMRI attempts to track the messages which control this function, but may not 560 * reliably do so in some cases. 561 * <p> 562 * For slot numbers not normally associated with mobile decoders, this bit 563 * may have other meanings. 564 * 565 * @return true if F1 is "on", else false 566 */ 567 public boolean isF18() { 568 return localF18; 569 } 570 571 /** 572 * Returns the slot's F19 state 573 * <p> 574 * Some command stations do not actively remember the state of this function. 575 * JMRI attempts to track the messages which control this function, but may not 576 * reliably do so in some cases. 577 * <p> 578 * For slot numbers not normally associated with mobile decoders, this bit 579 * may have other meanings. 580 * 581 * @return true if F19 is "on", else false 582 */ 583 public boolean isF19() { 584 return localF19; 585 } 586 587 /** 588 * Returns the slot's F20 state 589 * <p> 590 * Some command stations do not actively remember the state of this function. 591 * JMRI attempts to track the messages which control this function, but may not 592 * reliably do so in some cases. 593 * <p> 594 * For slot numbers not normally associated with mobile decoders, this bit 595 * may have other meanings. 596 * 597 * @return true if F20 is "on", else false 598 */ 599 public boolean isF20() { 600 return localF20; 601 } 602 603 /** 604 * Returns the slot's F21 state 605 * <p> 606 * Some command stations do not actively remember the state of this function. 607 * JMRI attempts to track the messages which control this function, but may not 608 * reliably do so in some cases. 609 * <p> 610 * For slot numbers not normally associated with mobile decoders, this bit 611 * may have other meanings. 612 * 613 * @return true if F21 is "on", else false 614 */ 615 public boolean isF21() { 616 return localF21; 617 } 618 619 /** 620 * Returns the slot's F22 state 621 * <p> 622 * Some command stations do not actively remember the state of this function. 623 * JMRI attempts to track the messages which control this function, but may not 624 * reliably do so in some cases. 625 * <p> 626 * For slot numbers not normally associated with mobile decoders, this bit 627 * may have other meanings. 628 * 629 * @return true if F22 is "on", else false 630 */ 631 public boolean isF22() { 632 return localF22; 633 } 634 635 /** 636 * Returns the slot's F23 state 637 * <p> 638 * Some command stations do not actively remember the state of this function. 639 * JMRI attempts to track the messages which control this function, but may not 640 * reliably do so in some cases. 641 * <p> 642 * For slot numbers not normally associated with mobile decoders, this bit 643 * may have other meanings. 644 * 645 * @return true if F23 is "on", else false 646 */ 647 public boolean isF23() { 648 return localF23; 649 } 650 651 /** 652 * Returns the slot's F24 state 653 * <p> 654 * Some command stations do not actively remember the state of this function. 655 * JMRI attempts to track the messages which control this function, but may not 656 * reliably do so in some cases. 657 * <p> 658 * For slot numbers not normally associated with mobile decoders, this bit 659 * may have other meanings. 660 * 661 * @return true if F24 is "on", else false 662 */ 663 public boolean isF24() { 664 return localF24; 665 } 666 667 /** 668 * Returns the slot's F25 state 669 * <p> 670 * Some command stations do not actively remember the state of this function. 671 * JMRI attempts to track the messages which control this function, but may not 672 * reliably do so in some cases. 673 * <p> 674 * For slot numbers not normally associated with mobile decoders, this bit 675 * may have other meanings. 676 * 677 * @return true if F25 is "on", else false 678 */ 679 public boolean isF25() { 680 return localF25; 681 } 682 683 /** 684 * Returns the slot's F26 state 685 * <p> 686 * Some command stations do not actively remember the state of this function. 687 * JMRI attempts to track the messages which control this function, but may not 688 * reliably do so in some cases. 689 * <p> 690 * For slot numbers not normally associated with mobile decoders, this bit 691 * may have other meanings. 692 * 693 * @return true if F26 is "on", else false 694 */ 695 public boolean isF26() { 696 return localF26; 697 } 698 699 /** 700 * Returns the slot's F27 state 701 * <p> 702 * Some command stations do not actively remember the state of this function. 703 * JMRI attempts to track the messages which control this function, but may not 704 * reliably do so in some cases. 705 * <p> 706 * For slot numbers not normally associated with mobile decoders, this bit 707 * may have other meanings. 708 * 709 * @return true if F27 is "on", else false 710 */ 711 public boolean isF27() { 712 return localF27; 713 } 714 715 /** 716 * Returns the slot's F28 state 717 * <p> 718 * Some command stations do not actively remember the state of this function. 719 * JMRI attempts to track the messages which control this function, but may not 720 * reliably do so in some cases. 721 * <p> 722 * For slot numbers not normally associated with mobile decoders, this bit 723 * may have other meanings. 724 * 725 * @return true if F28 is "on", else false 726 */ 727 public boolean isF28() { 728 return localF28; 729 } 730 731 // loco address, speed 732 /** 733 * Returns the mobile decoder address associated with the slot. 734 * <p> 735 * Note that the returned address can encode a "short" address, a "long" 736 * address or an "alias". 737 * <p> 738 * For slot numbers not normally associated with mobile decoders, these bits 739 * may have other meanings. 740 * 741 * @return the mobile decoder address 742 */ 743 public int locoAddr() { 744 return addr; 745 } 746 747 /** 748 * Returns the mobile decoder speed associated with the slot 749 * <p> 750 * If this slot object is consisted to another slot and is not the "top" of 751 * the consist, then the return value is the slot number to which this slot 752 * is consisted. 753 * <p> 754 * For slot numbers not normally associated with mobile decoders, these bits 755 * may have other meanings. 756 * 757 * @return the current speed step associated with the slot. 758 */ 759 public int speed() { 760 return spd; 761 } 762 763 /** 764 * Returns the mobile decoder direction and F0-F4 bits, as used in the DIRF bits 765 * of various LocoNet messages. 766 * <p> 767 * If this slot object is consisted to another slot and is not the "top" of 768 * the consist, then the "direction" bit reflects the relative direction of this 769 * loco with respect to the loco it is consisted to, where "Reverse" means it 770 * travels in the "reverse" direction with respect to the loco to which it is 771 * consisted. 772 * <p> 773 * For slot numbers not normally associated with mobile decoders, these bits 774 * may have other meanings. 775 * 776 * @return the <DIRF> byte value 777 */ 778 public int dirf() { 779 return dirf; 780 } 781 782 /** 783 * Returns the mobile decoder F5-F8 bits, as used in the SND bits 784 * of various LocoNet messages. 785 * <p> 786 * For slot numbers not normally associated with mobile decoders, these bits 787 * may have other meanings. 788 * 789 * @return the <SND> byte value 790 */ 791 public int snd() { 792 return snd; 793 } 794 795 /** 796 * Returns the "Throttle ID" associated with the slot. 797 * <p> 798 * The returned value is a 14-bit integer comprised of ID1 as the least-significant 799 * bits and ID2 as the most-significant bits. 800 * <p> 801 * For slot numbers not normally associated with mobile decoders, these bits 802 * may have other meanings. 803 * 804 * @return an integer representing the throttle ID number 805 */ 806 public int id() { 807 return id; 808 } 809 810 // programmer track special case accessors 811 /** 812 * Returns the programmer command associated with the slot. 813 * <p> 814 * The returned value is taken from the <PCMD> byte of programmer slot read 815 * and write LocoNet messages. 816 * <p> 817 * For slot numbers other than the programmer slot, these bits 818 * may have other meanings. 819 * 820 * @return the <PCMD> byte 821 */ 822 public int pcmd() { 823 return _pcmd; 824 } 825 826 public int cvval() { 827 return snd + (ss2 & 2) * 64; 828 } 829 830 boolean localF9 = false; 831 boolean localF10 = false; 832 boolean localF11 = false; 833 boolean localF12 = false; 834 boolean localF13 = false; 835 boolean localF14 = false; 836 boolean localF15 = false; 837 boolean localF16 = false; 838 boolean localF17 = false; 839 boolean localF18 = false; 840 boolean localF19 = false; 841 boolean localF20 = false; 842 boolean localF21 = false; 843 boolean localF22 = false; 844 boolean localF23 = false; 845 boolean localF24 = false; 846 boolean localF25 = false; 847 boolean localF26 = false; 848 boolean localF27 = false; 849 boolean localF28 = false; 850 851 // methods to interact with LocoNet 852 853 /** 854 * Update the slot object to reflect the specific contents of a 855 * LocoNet message. 856 * <p>Note that the object's "slot" field 857 * is not updated by this method. 858 * 859 * @param l a LocoNet message 860 * @throws LocoNetException if the message is not one which 861 * contains slot-related data 862 */ 863 @SuppressWarnings("fallthrough") 864 @SuppressFBWarnings(value = "SF_SWITCH_FALLTHROUGH") 865 public void setSlot(LocoNetMessage l) throws LocoNetException { // exception if message can't be parsed 866 // sort out valid messages, handle 867 if (slotType != SlotType.LOCO && slotType != SlotType.SYSTEM) { 868 slotType = SlotType.LOCO; 869 log.warn("Slot [{}] not in map but reports loco, check command station type",slot); 870 } 871 switch (l.getOpCode()) { 872 case LnConstants.OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR: //speed and functions 873 if (l.getElement(3) != expandedThrottleControllingID) { 874 // Message is not from owning throttle 875 log.debug("OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR for slot[{}] sent from throttle[{}], slot owned by [{}]",slot,l.getElement(3),expandedThrottleControllingID); 876 return; 877 } 878 if (((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_SPEED) == 0) 879 && (((stat & LnConstants.CONSIST_MASK) != LnConstants.CONSIST_MID) && 880 ((stat & LnConstants.CONSIST_MASK) != LnConstants.CONSIST_SUB))) { 881 // speed and direction 882 spd = l.getElement(4); 883 dirf = dirf & 0b11011111; 884 if ((l.getElement(1) & 0b00001000) != 0) { 885 dirf = dirf | 0b00100000; 886 } 887 } else if ((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F0F6) { 888 // function grp 1 889 dirf = dirf & 0b11100000; 890 dirf = dirf | (l.getElement(4) & 0b00011111); 891 snd = snd & 0b11111100; 892 snd = snd | ((l.getElement(4) & 0b01100000) >> 5); 893 } else if ((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F7F13) { 894 // function grp 2 895 snd = snd & 0b11110011; 896 snd = snd | ((l.getElement(4) & 0b00000011) << 2); 897 localF9 = ((l.getElement(4) & 0b00000100) != 0); 898 localF10 = ((l.getElement(4) & 0b00001000) != 0); 899 localF11 = ((l.getElement(4) & 0b00010000) != 0); 900 localF12 = ((l.getElement(4) & 0b00100000) != 0); 901 localF13 = ((l.getElement(4) & 0b01000000) != 0); 902 } else if ((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F14F20) { 903 localF14 = ((l.getElement(4) & 0b00000001) != 0); 904 localF15 = ((l.getElement(4) & 0b00000010) != 0); 905 localF16 = ((l.getElement(4) & 0b00000100) != 0); 906 localF17 = ((l.getElement(4) & 0b00001000) != 0); 907 localF18 = ((l.getElement(4) & 0b00010000) != 0); 908 localF19 = ((l.getElement(4) & 0b00100000) != 0); 909 localF20 = ((l.getElement(4) & 0b01000000) != 0); 910 } else if ((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F21F28_F28OFF 911 || (l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F21F28_F28ON) { 912 localF21 = ((l.getElement(4) & 0b00000001) != 0); 913 localF22 = ((l.getElement(4) & 0b00000010) != 0); 914 localF23 = ((l.getElement(4) & 0b00000100) != 0); 915 localF24 = ((l.getElement(4) & 0b00001000) != 0); 916 localF25 = ((l.getElement(4) & 0b00010000) != 0); 917 localF26 = ((l.getElement(4) & 0b00100000) != 0); 918 localF27 = ((l.getElement(4) & 0b01000000) != 0); 919 localF28 = ((l.getElement(1) & 0b00010000) != 0); 920 } 921 notifySlotListeners(); 922 break; 923 case LnConstants.OPC_EXP_RD_SL_DATA: 924 case LnConstants.OPC_EXP_WR_SL_DATA: 925 lastUpdateTime = System.currentTimeMillis(); 926 stat = l.getElement(4); 927 addr = l.getElement(5) + 128 * l.getElement(6); 928 spd = l.getElement(8); 929 if (loconetProtocol == LnConstants.LOCONETPROTOCOL_UNKNOWN) { 930 // it has to be 2 931 loconetProtocol = LnConstants.LOCONETPROTOCOL_TWO; 932 } 933 dirf = l.getElement(10) & 0b00111111; 934 id = l.getElement(18) + 128 * l.getElement(19); 935 expandedThrottleControllingID = l.getElement(18); 936 snd = snd & 0b11111100; 937 snd = snd | ( (l.getElement(11) & 0b01100000) >> 5) ; 938 snd = l.getElement(11) & 0b00001111; 939 trk = l.getElement(7); 940 localF9 = ((l.getElement(11) & 0b00010000 ) != 0); 941 localF10 = ((l.getElement(11) & 0b00100000 ) != 0); 942 localF11 = ((l.getElement(11) & 0b01000000 ) != 0); 943 localF12 = ((l.getElement(9) & 0b00010000 ) != 0); 944 localF13 = ((l.getElement(12) & 0b00000001 ) != 0); 945 localF14 = ((l.getElement(12) & 0b00000010 ) != 0); 946 localF15 = ((l.getElement(12) & 0b00000100 ) != 0); 947 localF16 = ((l.getElement(12) & 0b00001000 ) != 0); 948 localF17 = ((l.getElement(12) & 0b00010000 ) != 0); 949 localF18 = ((l.getElement(12) & 0b00100000 ) != 0); 950 localF19 = ((l.getElement(12) & 0b01000000 ) != 0); 951 localF20 = ((l.getElement(9) & 0b00100000 ) != 0); 952 localF21 = ((l.getElement(13) & 0b00000001 ) != 0); 953 localF22 = ((l.getElement(13) & 0b00000010 ) != 0); 954 localF23 = ((l.getElement(13) & 0b00000100 ) != 0); 955 localF24 = ((l.getElement(13) & 0b00001000 ) != 0); 956 localF25 = ((l.getElement(13) & 0b00010000 ) != 0); 957 localF26 = ((l.getElement(13) & 0b00100000 ) != 0); 958 localF27 = ((l.getElement(13) & 0b01000000 ) != 0); 959 localF28 = ((l.getElement(9) & 0b01000000 ) != 0); 960 leadSlot = (((l.getElement(9) & 0x03) * 128) + l.getElement(8) ); 961 notifySlotListeners(); 962 break; 963 case LnConstants.OPC_SL_RD_DATA: 964 lastUpdateTime = System.currentTimeMillis(); 965 //fall through 966 case LnConstants.OPC_WR_SL_DATA: { 967 if (l.getElement(1) != 0x0E) { 968 return; // not an appropriate reply 969 } // valid, so fill contents 970 if (slot != l.getElement(2)) { 971 log.error("Asked to handle message not for this slot ({}) {}", slot, l); 972 } 973 if (loconetProtocol == LnConstants.LOCONETPROTOCOL_UNKNOWN) { 974 loconetProtocol = LnConstants.LOCONETPROTOCOL_ONE; // all it can be... 975 } 976 stat = l.getElement(3); 977 _pcmd = l.getElement(4); 978 addr = l.getElement(4) + 128 * l.getElement(9); 979 spd = l.getElement(5); 980 dirf = l.getElement(6); 981 trk = l.getElement(7); 982 ss2 = l.getElement(8); 983 // item 9 is in add2 984 snd = l.getElement(10); 985 id = l.getElement(11) + 128 * l.getElement(12); 986 expandedThrottleControllingID = l.getElement(11); 987 988 notifySlotListeners(); 989 return; 990 } 991 case LnConstants.OPC_SLOT_STAT1: 992 if (slot != l.getElement(1)) { 993 log.error("Asked to handle message not for this slot {}", l); 994 } 995 stat = l.getElement(2); 996 notifySlotListeners(); 997 lastUpdateTime = System.currentTimeMillis(); 998 return; 999 case LnConstants.OPC_LOCO_SND: { 1000 // set sound functions in slot - first, clear bits 1001 snd &= ~(LnConstants.SND_F5 | LnConstants.SND_F6 1002 | LnConstants.SND_F7 | LnConstants.SND_F8); 1003 // and set them as masked 1004 snd |= ((LnConstants.SND_F5 | LnConstants.SND_F6 1005 | LnConstants.SND_F7 | LnConstants.SND_F8) & l.getElement(2)); 1006 notifySlotListeners(); 1007 lastUpdateTime = System.currentTimeMillis(); 1008 return; 1009 } 1010 case LnConstants.OPC_LOCO_DIRF: { 1011 // When slot is consist-mid or consist-sub, this LocoNet Opcode 1012 // can only change the functions; direction cannot be changed. 1013 if (((stat & LnConstants.CONSIST_MASK) == LnConstants.CONSIST_MID) || 1014 ((stat & LnConstants.CONSIST_MASK) == LnConstants.CONSIST_SUB)) { 1015 // set functions in slot - first, clear bits, preserving DIRF_DIR bit 1016 dirf &= LnConstants.DIRF_DIR | (~(LnConstants.DIRF_F0 1017 | LnConstants.DIRF_F1 | LnConstants.DIRF_F2 1018 | LnConstants.DIRF_F3 | LnConstants.DIRF_F4)); 1019 // and set the function bits from the LocoNet message 1020 dirf += ((LnConstants.DIRF_F0 1021 | LnConstants.DIRF_F1 | LnConstants.DIRF_F2 1022 | LnConstants.DIRF_F3 | LnConstants.DIRF_F4) & l.getElement(2)); 1023 } else { 1024 // set direction, functions in slot - first, clear bits 1025 dirf &= ~(LnConstants.DIRF_DIR | LnConstants.DIRF_F0 1026 | LnConstants.DIRF_F1 | LnConstants.DIRF_F2 1027 | LnConstants.DIRF_F3 | LnConstants.DIRF_F4); 1028 // and set them as masked 1029 dirf += ((LnConstants.DIRF_DIR | LnConstants.DIRF_F0 1030 | LnConstants.DIRF_F1 | LnConstants.DIRF_F2 1031 | LnConstants.DIRF_F3 | LnConstants.DIRF_F4) & l.getElement(2)); 1032 1033 } 1034 notifySlotListeners(); 1035 lastUpdateTime = System.currentTimeMillis(); 1036 return; 1037 } 1038 case LnConstants.OPC_MOVE_SLOTS: 1039 case LnConstants.OPC_LINK_SLOTS: 1040 case LnConstants.OPC_UNLINK_SLOTS: { 1041 // change in slot status, if any, will be reported by the reply, 1042 // so don't need to do anything here (but could) 1043 lastUpdateTime = System.currentTimeMillis(); 1044 notifySlotListeners(); 1045 return; 1046 } 1047 case LnConstants.OPC_LOCO_SPD: { 1048 // This opcode has no effect on the slot's speed setting if the 1049 // slot is mid-consist or sub-consist. 1050 if (((stat & LnConstants.CONSIST_MASK) != LnConstants.CONSIST_MID) && 1051 ((stat & LnConstants.CONSIST_MASK) != LnConstants.CONSIST_SUB)) { 1052 1053 spd = l.getElement(2); 1054 notifySlotListeners(); 1055 lastUpdateTime = System.currentTimeMillis(); 1056 } else { 1057 log.info("Ignoring speed change for slot {} marked as consist-mid or consist-sub.", slot); 1058 } 1059 return; 1060 } 1061 case LnConstants.OPC_CONSIST_FUNC: { 1062 // This opcode can be sent to a slot which is marked as mid-consist 1063 // or sub-consist. Do not pay attention to this message if the 1064 // slot is not mid-consist or sub-consist. 1065 if (((stat & LnConstants.CONSIST_MASK) == LnConstants.CONSIST_MID) || 1066 ((stat & LnConstants.CONSIST_MASK) == LnConstants.CONSIST_SUB)) { 1067 // set functions in slot - first, clear bits, preserving DIRF_DIR bit 1068 dirf &= LnConstants.DIRF_DIR | (~(LnConstants.DIRF_F0 1069 | LnConstants.DIRF_F1 | LnConstants.DIRF_F2 1070 | LnConstants.DIRF_F3 | LnConstants.DIRF_F4)); 1071 // and set the function bits from the LocoNet message 1072 dirf += ((LnConstants.DIRF_F0 1073 | LnConstants.DIRF_F1 | LnConstants.DIRF_F2 1074 | LnConstants.DIRF_F3 | LnConstants.DIRF_F4) & l.getElement(2)); 1075 notifySlotListeners(); 1076 lastUpdateTime = System.currentTimeMillis(); 1077 } 1078 return; 1079 } 1080 case LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL: { 1081 if (l.getElement(1) == LnConstants.RE_IB2_SPECIAL_FUNCS_TOKEN) { 1082 // IB function message 1083 int data = l.getElement(4); 1084 switch (l.getElement(3)) { 1085 case LnConstants.RE_IB1_SPECIAL_F5_F11_TOKEN: 1086 // under 8 are kept in the slot, not local variables 1087 localF9 = ((data & LnConstants.RE_IB1_F9_MASK) != 0); 1088 localF10 = ((data & LnConstants.RE_IB1_F10_MASK) != 0); 1089 localF11 = ((data & LnConstants.RE_IB1_F11_MASK) != 0); 1090 return; 1091 case LnConstants.RE_IB2_SPECIAL_F13_F19_TOKEN: 1092 localF13 = ((data & LnConstants.RE_IB2_F13_MASK) != 0); 1093 localF14 = ((data & LnConstants.RE_IB2_F14_MASK) != 0); 1094 localF15 = ((data & LnConstants.RE_IB2_F15_MASK) != 0); 1095 localF16 = ((data & LnConstants.RE_IB2_F16_MASK) != 0); 1096 localF17 = ((data & LnConstants.RE_IB2_F17_MASK) != 0); 1097 localF18 = ((data & LnConstants.RE_IB2_F18_MASK) != 0); 1098 localF19 = ((data & LnConstants.RE_IB2_F19_MASK) != 0); 1099 return; 1100 case LnConstants.RE_IB2_SPECIAL_F21_F27_TOKEN: 1101 localF21 = ((data & LnConstants.RE_IB2_F21_MASK) != 0); 1102 localF22 = ((data & LnConstants.RE_IB2_F22_MASK) != 0); 1103 localF23 = ((data & LnConstants.RE_IB2_F23_MASK) != 0); 1104 localF24 = ((data & LnConstants.RE_IB2_F24_MASK) != 0); 1105 localF25 = ((data & LnConstants.RE_IB2_F25_MASK) != 0); 1106 localF26 = ((data & LnConstants.RE_IB2_F26_MASK) != 0); 1107 localF27 = ((data & LnConstants.RE_IB2_F27_MASK) != 0); 1108 return; 1109 case LnConstants.RE_IB2_SPECIAL_F20_F28_TOKEN: 1110 localF12 = ((data & LnConstants.RE_IB2_SPECIAL_F12_MASK) != 0); 1111 localF20 = ((data & LnConstants.RE_IB2_SPECIAL_F20_MASK) != 0); 1112 localF28 = ((data & LnConstants.RE_IB2_SPECIAL_F28_MASK) != 0); 1113 return; 1114 default: 1115 log.debug("Found IB RE_OPC_IB2_SPECIAL message of {}", l); 1116 return; 1117 } 1118 } 1119 int src = slot; 1120 int dest = ((l.getElement(3) & 0x07) * 128) + (l.getElement(4) & 0x7f); 1121 // null move or change status or consisting or? 1122 if ((l.getElement(1) & 0b11111000) == 0b00111000) { 1123 if (((l.getElement(3) & 0b01110000) == 0b01100000)) { 1124 stat = l.getElement(4); 1125 notifySlotListeners(); 1126 return; 1127 } else if ((l.getElement(3) & 0b01110000) == 0b01010000) { 1128 // unconsisting returns slot contents so do nothing to this slot 1129 return; 1130 } else if ((l.getElement(3) & 0b01110000) == 0b01000000) { 1131 //consisting do something? 1132 //Set From slot as slave to slot as master 1133 stat = stat | LnConstants.CONSIST_TOP; 1134 notifySlotListeners(); 1135 return; 1136 } else if (src == 0 && dest == 0) { 1137 stat = stat & ~LnConstants.LOCO_IN_USE; 1138 log.debug("set idle"); 1139 notifySlotListeners(); 1140 return; 1141 } 1142 } 1143 return; 1144 } 1145 default: { 1146 throw new LocoNetException("message can't be parsed"); // NOI18N 1147 } 1148 } 1149 } 1150 1151 /** 1152 * Sets F9 through F28 (as appropriate) from data extracted from LocoNet 1153 * "OPC_IMM_PACKET" message. 1154 * <p>If the pkt parameter does not contain data from an appropriate 1155 * OPC_IMM_PACKET message, the pkt is ignored and the slot object remains 1156 * unchanged. 1157 * 1158 * @param pkt is a "long" consisting of four bytes extracted from a LocoNet 1159 * "OPC_IMM_PACKET" message. 1160 * <p> 1161 * {@link jmri.jmrix.loconet.SlotManager#getDirectDccPacket(LocoNetMessage m)} 1162 */ 1163 public void functionMessage(long pkt) { 1164 // parse for which set of functions 1165 if ((pkt & 0xFFFFFF0) == 0xA0) { 1166 // F9-12 1167 localF9 = ((pkt & 0x01) != 0); 1168 localF10 = ((pkt & 0x02) != 0); 1169 localF11 = ((pkt & 0x04) != 0); 1170 localF12 = ((pkt & 0x08) != 0); 1171 notifySlotListeners(); 1172 } else if ((pkt & 0xFFFFFF00) == 0xDE00) { 1173 // check F13-20 1174 localF13 = ((pkt & 0x01) != 0); 1175 localF14 = ((pkt & 0x02) != 0); 1176 localF15 = ((pkt & 0x04) != 0); 1177 localF16 = ((pkt & 0x08) != 0); 1178 localF17 = ((pkt & 0x10) != 0); 1179 localF18 = ((pkt & 0x20) != 0); 1180 localF19 = ((pkt & 0x40) != 0); 1181 localF20 = ((pkt & 0x80) != 0); 1182 notifySlotListeners(); 1183 } else if ((pkt & 0xFFFFFF00) == 0xDF00) { 1184 // check F21-28 1185 localF21 = ((pkt & 0x01) != 0); 1186 localF22 = ((pkt & 0x02) != 0); 1187 localF23 = ((pkt & 0x04) != 0); 1188 localF24 = ((pkt & 0x08) != 0); 1189 localF25 = ((pkt & 0x10) != 0); 1190 localF26 = ((pkt & 0x20) != 0); 1191 localF27 = ((pkt & 0x40) != 0); 1192 localF28 = ((pkt & 0x80) != 0); 1193 notifySlotListeners(); 1194 } 1195 } 1196 1197 /** 1198 * Update the decoder type bits in STAT1 (D2, D1, D0) 1199 * 1200 * @param status New values for STAT1 (D2, D1, D0) 1201 * @return Formatted LocoNet message to change value. 1202 */ 1203 public LocoNetMessage writeMode(int status) { 1204 if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO ) { 1205 LocoNetMessage l = new LocoNetMessage(4); 1206 l.setOpCode(LnConstants.OPC_SLOT_STAT1); 1207 l.setElement(1, slot); 1208 l.setElement(2, (stat & ~LnConstants.DEC_MODE_MASK) | status); 1209 return l; 1210 } else { 1211 LocoNetMessage l = new LocoNetMessage(6); 1212 l.setOpCode(LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL); 1213 l.setElement(1, ((slot / 128) & 0x03) | 0b00111000 ) ; 1214 l.setElement(2, slot & 0x7f); 1215 l.setElement(3, 0x60); 1216 l.setElement(4, (stat & ~LnConstants.DEC_MODE_MASK) | status); 1217 return l; 1218 } 1219 } 1220 1221 /** 1222 * Sets the object's ID value and returns a LocoNet message to inform the 1223 * command station that the throttle ID has been changed. 1224 * @param newID the new ID number to set into the slot object 1225 * @return a LocoNet message containing a "Slot Write" message to inform the 1226 * command station that a specific throttle is controlling the slot. 1227 */ 1228 public LocoNetMessage writeThrottleID(int newID) { 1229 id = (newID & 0x17F); 1230 return writeSlot(); 1231 } 1232 1233 /** 1234 * Set the throttle ID in the slot 1235 * 1236 * @param throttleId full id 1237 */ 1238 public void setThrottleIdentity(int throttleId) { 1239 id = throttleId; 1240 } 1241 1242 /** 1243 * Get the throttle ID in the slot 1244 * 1245 *@return the Id of the Throttle 1246 */ 1247 public int getThrottleIdentity() { 1248 return id; 1249 } 1250 1251 public int getLeadSlot() { 1252 return leadSlot; 1253 } 1254 1255 /** 1256 * Update the status mode bits in STAT1 (D5, D4) 1257 * 1258 * @param status New values for STAT1 (D5, D4) 1259 * @return Formatted LocoNet message to change value. 1260 */ 1261 public LocoNetMessage writeStatus(int status) { 1262 if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO ) { 1263 LocoNetMessage l = new LocoNetMessage(4); 1264 l.setOpCode(LnConstants.OPC_SLOT_STAT1); 1265 l.setElement(1, slot); 1266 l.setElement(2, (stat & ~LnConstants.LOCOSTAT_MASK) | status); 1267 return l; 1268 } else { 1269 LocoNetMessage l = new LocoNetMessage(6); 1270 l.setOpCode(LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL); 1271 l.setElement(1, ((slot / 128) & 0x03) | 0b00111000 ) ; 1272 l.setElement(2, slot & 0x7f); 1273 l.setElement(3, 0x60); 1274 l.setElement(4, (stat & ~LnConstants.LOCOSTAT_MASK) | status); 1275 return l; 1276 } 1277 } 1278 1279 /** 1280 * Update Speed 1281 * 1282 * @param speed new speed 1283 * @return Formatted LocoNet message to change value. 1284 */ 1285 public LocoNetMessage writeSpeed(int speed) { 1286 if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO) { 1287 LocoNetMessage l = new LocoNetMessage(4); 1288 l.setOpCode(LnConstants.OPC_LOCO_SPD); 1289 l.setElement(1, slot ); 1290 l.setElement(2, speed); 1291 return l; 1292 } else { 1293 LocoNetMessage l = new LocoNetMessage(6); 1294 l.setOpCode(LnConstants.OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR); 1295 l.setElement(1, ((slot / 128) & 0x03) | ((dirf & LnConstants.DIRF_DIR ) >> 2) ); 1296 l.setElement(2, slot & 0x7f); 1297 l.setElement(3, (id & 0x7f)); 1298 l.setElement(4, speed); 1299 return l; 1300 } 1301 } 1302 1303 /** 1304 * Create LocoNet message which dispatches this slot 1305 * 1306 * Note that the invoking method ought to invoke the slot's NotifySlotListeners 1307 * method to inform any other interested parties that the slot status has changed. 1308 * 1309 * @return LocoNet message which "dispatches" the slot 1310 */ 1311 public LocoNetMessage dispatchSlot() { 1312 if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO) { 1313 LocoNetMessage l = new LocoNetMessage(4); 1314 l.setOpCode(LnConstants.OPC_MOVE_SLOTS); 1315 l.setElement(1, slot); 1316 l.setElement(2, 0); 1317 return l; 1318 } else { 1319 LocoNetMessage l = new LocoNetMessage(6); 1320 l.setOpCode(LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL); 1321 l.setElement(1, ((slot / 128) & 0x03) | 0b00111000 ) ; 1322 l.setElement(2, slot & 0x7f); 1323 l.setElement(3, 0); 1324 l.setElement(4, 0); 1325 return l; 1326 } 1327 } 1328 1329 /** 1330 * Create a message to perform a null move on this slot. 1331 * @return correct LocoNetMessage for protocol being used. 1332 */ 1333 public LocoNetMessage writeNullMove() { 1334 if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO) { 1335 // perform the null slot move for low numbered slots 1336 LocoNetMessage msg = new LocoNetMessage(4); 1337 msg.setOpCode(LnConstants.OPC_MOVE_SLOTS); 1338 msg.setElement(1, slot); 1339 msg.setElement(2, slot); 1340 return (msg); 1341 } 1342 // or the null move for higher numbered slots 1343 LocoNetMessage msg = new LocoNetMessage(6); 1344 msg.setOpCode(0xd4); 1345 msg.setElement(1, (slot / 128) | 0b00111000); 1346 msg.setElement(2, slot & 0b01111111); 1347 msg.setElement(3, (slot / 128) & 0b00000111); 1348 msg.setElement(4, slot & 0b01111111); 1349 return msg; 1350 } 1351 1352 /** 1353 * Create a LocoNet OPC_SLOT_STAT1 message which releases this slot to the 1354 * "Common" state 1355 * 1356 * The invoking method must send the returned LocoNet message to LocoNet in 1357 * order to have a useful effect. 1358 * 1359 * Upon receipt of the echo of the transmitted OPC_SLOT_STAT1 message, the 1360 * LocoNetSlot object will notify its listeners. 1361 * 1362 * @return LocoNet message which "releases" the slot to the "Common" state 1363 */ 1364 public LocoNetMessage releaseSlot() { 1365 return writeStatus(LnConstants.LOCO_COMMON); 1366 } 1367 1368 /** 1369 * Creates a LocoNet "OPC_WR_SL_DATA" message containing the current state of 1370 * the LocoNetSlot object. 1371 * 1372 * @return a LocoNet message which can be used to inform the command station 1373 * of a change in the slot contents. 1374 */ 1375 public LocoNetMessage writeSlot() { 1376 if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO || slot == LnConstants.FC_SLOT) { //special case for fc 1377 LocoNetMessage l = new LocoNetMessage(14); 1378 l.setOpCode(LnConstants.OPC_WR_SL_DATA); 1379 l.setElement(1, 0x0E); 1380 l.setElement(2, slot & 0x7F); 1381 l.setElement(3, stat & 0x7F); 1382 l.setElement(4, addr & 0x7F); 1383 l.setElement(9, (addr / 128) & 0x7F); 1384 l.setElement(5, spd & 0x7F); 1385 l.setElement(6, dirf & 0x7F); 1386 l.setElement(7, trk & 0x7F); 1387 l.setElement(8, ss2 & 0x7F); 1388 // item 9 is add2 1389 l.setElement(10, snd & 0x7F); 1390 l.setElement(11, id & 0x7F); 1391 l.setElement(12, (id / 128) & 0x7F); 1392 return l; 1393 } 1394 LocoNetMessage l = new LocoNetMessage(21); 1395 l.setOpCode(LnConstants.OPC_EXP_WR_SL_DATA); 1396 l.setElement(1, 0x15); 1397 l.setElement(2, (slot / 128) & 0x03); 1398 l.setElement(3, slot & 0x7F); 1399 l.setElement(4, stat & 0x7F); 1400 l.setElement(6, (addr / 128) & 0x7F); 1401 l.setElement(5, addr & 0x7F); 1402 l.setElement(7, ( trk | 0x40 ) & 0x7F); // track power status and Expanded slot protocol 1403 l.setElement(8, spd & 0x7F); 1404 l.setElement(9, (isF12() ? 0b00010000 : 0x00 ) 1405 | (isF20() ? 0b00100000 : 0x00) 1406 | (isF28() ? 0b01000000 : 0x00)); 1407 l.setElement(10, ( isForward() ? 0x00 : 0x00100000) 1408 | (isF0() ? 0b00010000 : 0x00) 1409 | (isF1() ? 0b00000001 : 0x00) 1410 | (isF2() ? 0b00000010 : 0x00) 1411 | (isF3() ? 0b00000100 : 0x00) 1412 | (isF4() ? 0b00001000 : 0x00)); 1413 l.setElement(11, ( isF5() ? 0b00000001 : 0x00) 1414 | ( isF6() ? 0b00000010 : 0x00) 1415 | ( isF7() ? 0b00000100 : 0x00) 1416 | ( isF8() ? 0b00001000 : 0x00) 1417 | ( isF9() ? 0b00010000 : 0x00) 1418 | (isF10() ? 0b00100000 : 0x00) 1419 | (isF11() ? 0b01000000 : 0x00)); 1420 l.setElement(12,( isF13() ? 0b00000001 : 0x00) 1421 | (isF14() ? 0b00000010 : 0x00) 1422 | (isF15() ? 0b00000100 : 0x00) 1423 | (isF16() ? 0b00001000 : 0x00) 1424 | (isF17() ? 0b00010000 : 0x00) 1425 | (isF18() ? 0b00100000 : 0x00) 1426 | (isF19() ? 0b01000000 : 0x00)); 1427 l.setElement(13,( isF21() ? 0b00000001 : 0x00) 1428 | (isF22() ? 0b00000010 : 0x00) 1429 | (isF23() ? 0b00000100 : 0x00) 1430 | (isF24() ? 0b00001000 : 0x00) 1431 | (isF25() ? 0b00010000 : 0x00) 1432 | (isF26() ? 0b00100000 : 0x00) 1433 | (isF27() ? 0b01000000 : 0x00)); 1434 l.setElement(18, id & 0x7F); 1435 l.setElement(19, (id / 128) & 0x7F); 1436 return l; 1437 } 1438 1439 // data values to echo slot contents 1440 final private int slot; // <SLOT#> is the number of the slot that was read. 1441 private boolean isInitialized; // set when full initilization is complete with the throttle ID. 1442 private int loconetProtocol; // protocol used by the slot. 1443 private SlotType slotType; // system, loco, unknown 1444 private int stat; // <STAT> is the status of the slot 1445 private int addr; // full address of the loco, made from 1446 // <ADDR> is the low 7 (0-6) bits of the Loco address 1447 // <ADD2> is the high 7 bits (7-13) of the 14-bit loco address 1448 private int spd; // <SPD> is the current speed (0-127) 1449 private int dirf; // <DIRF> is the current Direction and the setting for functions F0-F4 1450 private int trk = 7; // <TRK> is the global track status 1451 private int ss2; // <SS2> is the an additional slot status 1452 private int snd; // <SND> is the settings for functions F5-F8 1453 private int id; // throttle id, made from 1454 // <ID1> and <ID2> normally identify the throttle controlling the loco 1455 private int expandedThrottleControllingID; //the throttle ID byte that is used in sending commands that require a throttle ID. (ID1) 1456 private int leadSlot; // the top slot for this slot in a consist. 1457 1458 private int _pcmd; // hold pcmd and pstat for programmer 1459 1460 private long lastUpdateTime; // Time of last update for detecting stale slots 1461 1462 // data members to hold contact with the slot listeners 1463 final private List<SlotListener> slotListeners = new ArrayList<>(); 1464 1465 /** 1466 * Registers a slot listener if it is not already registered. 1467 * 1468 * @param l a slot listener 1469 */ 1470 public synchronized void addSlotListener(SlotListener l) { 1471 // add only if not already registered 1472 if (!slotListeners.contains(l)) { 1473 slotListeners.add(l); 1474 } 1475 } 1476 1477 /** 1478 * Un-registers a slot listener. 1479 * 1480 * @param l a slot listener 1481 */ 1482 public synchronized void removeSlotListener(SlotListener l) { 1483 if (slotListeners.contains(l)) { 1484 slotListeners.remove(l); 1485 } 1486 } 1487 1488 /** 1489 * Returns the timestamp when this LocoNetSlot was updated by some LocoNet 1490 * message. 1491 * 1492 * @return last time the slot info was updated 1493 */ 1494 public long getLastUpdateTime() { 1495 return lastUpdateTime; 1496 } 1497 1498 /** 1499 * Notifies all listeners that this slot has been changed in some way. 1500 */ 1501 public void notifySlotListeners() { 1502 // make a copy of the listener list to synchronized not needed for transmit 1503 List<SlotListener> v; 1504 synchronized (this) { 1505 v = new ArrayList<>(slotListeners); 1506 } 1507 log.debug("notify {} SlotListeners",v.size()); // NOI18N 1508 // forward to all listeners 1509 int cnt = v.size(); 1510 for (int i = 0; i < cnt; i++) { 1511 SlotListener client = v.get(i); 1512 client.notifyChangedSlot(this); 1513 } 1514 } 1515 1516 /** 1517 * For fast-clock slot, set a "CLK_CNTRL" bit On. This method logs an error 1518 * if invoked for a slot other than the fast-clock slot. 1519 * 1520 * @param val is the new "CLK_CNTRL" bit value to turn On 1521 */ 1522 public void setFcCntrlBitOn(int val) { 1523 // TODO: consider throwing a LocoNetException if issued for a slot other 1524 // than the "fast clock slot". 1525 if (getSlot() != LnConstants.FC_SLOT) { 1526 log.error("setFcCntrl invalid for slot [{}]", getSlot()); 1527 } 1528 snd |= val; 1529 } 1530 1531 /** 1532 * For fast-clock slot, set a "CLK_CNTRL" bit Off. This method logs an error 1533 * if invoked for a slot other than the fast-clock slot. 1534 * 1535 * @param val is the new "CLK_CNTRL" bit value to turn Off 1536 */ 1537 public void setFcCntrlBitOff(int val) { 1538 // TODO: consider throwing a LocoNetException if issued for a slot other 1539 // than the "fast clock slot". 1540 if (getSlot() != LnConstants.FC_SLOT) { 1541 log.error("setFcCntrl invalid for slot [{}]" , getSlot()); 1542 } 1543 snd &= ~val; 1544 } 1545 1546 /** 1547 * Get the track status byte (location 7) 1548 * <p> 1549 * Note that the <TRK> byte is not accurate on some command stations. 1550 * 1551 * @return the effective <TRK> byte 1552 */ 1553 public int getTrackStatus() { return trk; } 1554 1555 /** 1556 * Set the track status byte (location 7) 1557 * <p> 1558 * Note that setting the LocoNetSlot object's track status may result in a 1559 * change to the command station's actual track status if the slot's status 1560 * is communicated to the command station via an OPC_WR_DL_DATA LocoNet message. 1561 * 1562 * @param status is the new track status value. 1563 */ 1564 public void setTrackStatus(int status) { trk = status; } 1565 1566 /** 1567 * Return the days value from the slot. Only valid for fast-clock slot. 1568 * <p> 1569 * This method logs an error if invoked for a slot other than the fast-clock slot. 1570 * 1571 * @return "Days" value currently in fast-clock slot. 1572 */ 1573 public int getFcDays() { 1574 // TODO: consider throwing a LocoNetException if issued for a slot other 1575 // than the "fast clock slot". 1576 if (getSlot() != LnConstants.FC_SLOT) { 1577 log.error("getFcDays invalid for slot {}", getSlot()); 1578 } 1579 return (addr & 0x3f80) / 0x80; 1580 } 1581 1582 /** 1583 * For fast-clock slot, set "days" value. 1584 * <p> 1585 * Note that the new days value is not effective until a LocoNet 1586 * message is sent which writes the fast-clock slot data. 1587 * <p> 1588 * This method logs an error if invoked for a slot other than the fast-clock slot. 1589 * 1590 * @param val is the new fast-clock "days" value 1591 */ 1592 public void setFcDays(int val) { 1593 // TODO: consider throwing a LocoNetException if issued for a slot other 1594 // than the "fast clock slot". 1595 if (getSlot() != LnConstants.FC_SLOT) { 1596 log.error("setFcDays invalid for slot {}", getSlot()); 1597 } 1598 addr = val * 128 + (addr & 0x7f); 1599 } 1600 1601 /** 1602 * Return the hours value from the slot. Only valid for fast-clock slot. 1603 * <p> 1604 * This method logs an error if invoked for a slot other than the fast-clock slot. 1605 * 1606 * @return "Hours" value currently stored in fast clock slot. 1607 */ 1608 public int getFcHours() { 1609 // TODO: consider throwing a LocoNetException if issued for a slot other 1610 // than the "fast clock slot". 1611 if (getSlot() != LnConstants.FC_SLOT) { 1612 log.error("getFcHours invalid for slot {}", getSlot()); 1613 } 1614 int temp = ((256 - ss2) & 0x7F) % 24; 1615 return (24 - temp) % 24; 1616 } 1617 1618 /** 1619 * For fast-clock slot, set "hours" value. 1620 * <p> 1621 * Note that the new hours value is not effective until a LocoNet 1622 * message is sent which writes the fast-clock slot data. 1623 * <p> 1624 * This method logs an error if invoked for a slot other than the fast-clock slot. 1625 * 1626 * @param val is the new fast-clock "hours" value 1627 */ 1628 public void setFcHours(int val) { 1629 // TODO: consider throwing a LocoNetException if issued for a slot other 1630 // than the "fast clock slot". 1631 if (getSlot() != LnConstants.FC_SLOT) { 1632 log.error("setFcHours invalid for slot {}", getSlot()); 1633 } 1634 ss2 = (256 - (24 - val)) & 0x7F; 1635 } 1636 1637 /** 1638 * Return the minutes value from the slot. Only valid for fast-clock slot. 1639 * <p> 1640 * This method logs an error if invoked for a slot other than the fast-clock slot. 1641 * 1642 * @return Return minutes value currently stored in the fast clock slot. 1643 */ 1644 public int getFcMinutes() { 1645 // TODO: consider throwing a LocoNetException if issued for a slot other 1646 // than the "fast clock slot". 1647 if (getSlot() != LnConstants.FC_SLOT) { 1648 log.error("getFcMinutes invalid for slot {}", getSlot()); 1649 } 1650 int temp = ((255 - dirf) & 0x7F) % 60; 1651 return (60 - temp) % 60; 1652 } 1653 1654 /** 1655 * For fast-clock slot, set "minutes" value. 1656 * <p> 1657 * Note that the new minutes value is not effective until a LocoNet 1658 * message is sent which writes the fast-clock slot data. 1659 * <p> 1660 * This method logs an error if invoked for a slot other than the fast-clock slot. 1661 * 1662 * @param val is the new fast-clock "minutes" value 1663 */ 1664 public void setFcMinutes(int val) { 1665 // TODO: consider throwing a LocoNetException if issued for a slot other 1666 // than the "fast clock slot". 1667 if (getSlot() != LnConstants.FC_SLOT) { 1668 log.error("setFcMinutes invalid for slot {}", getSlot()); 1669 } 1670 dirf = (255 - (60 - val)) & 0x7F; 1671 } 1672 1673 /** 1674 * Return the fractional minutes value from the slot. Only valid for fast- 1675 * clock slot. 1676 * <p> 1677 * This method logs an error if invoked for a slot other than the fast-clock slot. 1678 * 1679 * @return Return frac_mins field which is the number of 65ms ticks until 1680 * then next minute rollover. These ticks step at the current fast 1681 * clock rate 1682 */ 1683 public int getFcFracMins() { 1684 // TODO: consider throwing a LocoNetException if issued for a slot other 1685 // than the "fast clock slot". 1686 if (getSlot() != LnConstants.FC_SLOT) { 1687 log.error("getFcFracMins invalid for slot {}", getSlot()); 1688 } 1689 return ((addr & 0x7F) | ((spd & 0x7F) << 8)); 1690 } 1691 1692 /** 1693 * Set the "frac_mins" value. 1694 * This has to be calculated as required by the Command Station, 1695 * then bit shifted if required. 1696 * It is comprised of a base number and the distance from the base to 0x8000 1697 * or 0x4000 deoending on command station. 1698 * It is read and written as is LO,HO and loses the bit 7 of the LO. 1699 * It was never intended for external use. 1700 * The base can be found by setting the clock to 0xXX7F, with a rate of 1 1701 * and pounding the clock every 250 to 100 msecs until it roles. 1702 * <p> 1703 * Note 1: The new fractional minutes value is not effective until a LocoNet slot write happens 1704 * <p> 1705 * Note 2: DT40x & DT500 throttles ignore this value, and set only the whole minutes. 1706 * <p> 1707 * This method logs an error if invoked for a slot other than the fast-clock slot. 1708 * @param val is the new fast-clock "fractional minutes" including the base, and bit shifted if required. 1709 */ 1710 public void setFcFracMins(int val) { 1711 // TODO: consider throwing a LocoNetException if issued for a slot other 1712 // than the "fast clock slot". 1713 if (getSlot() != LnConstants.FC_SLOT) { 1714 log.error("setFcFracMins invalid for slot {}", getSlot()); 1715 } 1716 int temp = 0x7F7F & val; 1717 addr = (addr & 0x7F00) | (temp & 0x7F); 1718 spd = (temp >> 8) & 0x7F; 1719 } 1720 1721 /** 1722 * Get the fast-clock rate. Only valid for fast-clock slot. 1723 * <p> 1724 * This method logs an error if invoked for a slot other than the fast-clock slot. 1725 * 1726 * @return Rate stored in fast clock slot. 1727 */ 1728 public int getFcRate() { 1729 // TODO: consider throwing a LocoNetException if issued for a slot other 1730 // than the "fast clock slot". 1731 if (getSlot() != LnConstants.FC_SLOT) { 1732 log.error("getFcRate invalid for slot {}", getSlot()); 1733 } 1734 return stat; 1735 } 1736 1737 /** 1738 * For fast-clock slot, set "rate" value. 1739 * <p> 1740 * Note that the new rate is not effective until a LocoNet message is sent 1741 * which writes the fast-clock slot data. 1742 * <p> 1743 * This method logs an error if invoked for a slot other than the fast-clock slot. 1744 * 1745 * @param val is the new fast-clock rate 1746 */ 1747 public void setFcRate(int val) { 1748 // TODO: consider throwing a LocoNetException if issued for a slot other 1749 // than the "fast clock slot". 1750 if (getSlot() != LnConstants.FC_SLOT) { 1751 log.error("setFcRate invalid for slot {}", getSlot()); 1752 } 1753 stat = val & 0x7F; 1754 } 1755 1756 private final static Logger log = LoggerFactory.getLogger(LocoNetSlot.class); 1757}