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, 2025 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 lastUpdateTime = System.currentTimeMillis(); 888 } else if ((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F0F6) { 889 // function grp 1 890 dirf = dirf & 0b11100000; 891 dirf = dirf | (l.getElement(4) & 0b00011111); 892 snd = snd & 0b11111100; 893 snd = snd | ((l.getElement(4) & 0b01100000) >> 5); 894 lastUpdateTime = System.currentTimeMillis(); 895 } else if ((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F7F13) { 896 // function grp 2 897 snd = snd & 0b11110011; 898 snd = snd | ((l.getElement(4) & 0b00000011) << 2); 899 localF9 = ((l.getElement(4) & 0b00000100) != 0); 900 localF10 = ((l.getElement(4) & 0b00001000) != 0); 901 localF11 = ((l.getElement(4) & 0b00010000) != 0); 902 localF12 = ((l.getElement(4) & 0b00100000) != 0); 903 localF13 = ((l.getElement(4) & 0b01000000) != 0); 904 lastUpdateTime = System.currentTimeMillis(); 905 } else if ((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F14F20) { 906 localF14 = ((l.getElement(4) & 0b00000001) != 0); 907 localF15 = ((l.getElement(4) & 0b00000010) != 0); 908 localF16 = ((l.getElement(4) & 0b00000100) != 0); 909 localF17 = ((l.getElement(4) & 0b00001000) != 0); 910 localF18 = ((l.getElement(4) & 0b00010000) != 0); 911 localF19 = ((l.getElement(4) & 0b00100000) != 0); 912 localF20 = ((l.getElement(4) & 0b01000000) != 0); 913 lastUpdateTime = System.currentTimeMillis(); 914 } else if ((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F21F28_F28OFF 915 || (l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F21F28_F28ON) { 916 localF21 = ((l.getElement(4) & 0b00000001) != 0); 917 localF22 = ((l.getElement(4) & 0b00000010) != 0); 918 localF23 = ((l.getElement(4) & 0b00000100) != 0); 919 localF24 = ((l.getElement(4) & 0b00001000) != 0); 920 localF25 = ((l.getElement(4) & 0b00010000) != 0); 921 localF26 = ((l.getElement(4) & 0b00100000) != 0); 922 localF27 = ((l.getElement(4) & 0b01000000) != 0); 923 localF28 = ((l.getElement(1) & 0b00010000) != 0); 924 lastUpdateTime = System.currentTimeMillis(); 925 } 926 notifySlotListeners(); 927 break; 928 case LnConstants.OPC_EXP_RD_SL_DATA: 929 case LnConstants.OPC_EXP_WR_SL_DATA: 930 lastUpdateTime = System.currentTimeMillis(); 931 stat = l.getElement(4); 932 addr = l.getElement(5) + 128 * l.getElement(6); 933 spd = l.getElement(8); 934 if (loconetProtocol == LnConstants.LOCONETPROTOCOL_UNKNOWN) { 935 // it has to be 2 936 loconetProtocol = LnConstants.LOCONETPROTOCOL_TWO; 937 } 938 dirf = l.getElement(10) & 0b00111111; 939 id = l.getElement(18) + 128 * l.getElement(19); 940 expandedThrottleControllingID = l.getElement(18); 941 snd = snd & 0b11111100; 942 snd = snd | ( (l.getElement(11) & 0b01100000) >> 5) ; 943 snd = l.getElement(11) & 0b00001111; 944 trk = l.getElement(7); 945 localF9 = ((l.getElement(11) & 0b00010000 ) != 0); 946 localF10 = ((l.getElement(11) & 0b00100000 ) != 0); 947 localF11 = ((l.getElement(11) & 0b01000000 ) != 0); 948 localF12 = ((l.getElement(9) & 0b00010000 ) != 0); 949 localF13 = ((l.getElement(12) & 0b00000001 ) != 0); 950 localF14 = ((l.getElement(12) & 0b00000010 ) != 0); 951 localF15 = ((l.getElement(12) & 0b00000100 ) != 0); 952 localF16 = ((l.getElement(12) & 0b00001000 ) != 0); 953 localF17 = ((l.getElement(12) & 0b00010000 ) != 0); 954 localF18 = ((l.getElement(12) & 0b00100000 ) != 0); 955 localF19 = ((l.getElement(12) & 0b01000000 ) != 0); 956 localF20 = ((l.getElement(9) & 0b00100000 ) != 0); 957 localF21 = ((l.getElement(13) & 0b00000001 ) != 0); 958 localF22 = ((l.getElement(13) & 0b00000010 ) != 0); 959 localF23 = ((l.getElement(13) & 0b00000100 ) != 0); 960 localF24 = ((l.getElement(13) & 0b00001000 ) != 0); 961 localF25 = ((l.getElement(13) & 0b00010000 ) != 0); 962 localF26 = ((l.getElement(13) & 0b00100000 ) != 0); 963 localF27 = ((l.getElement(13) & 0b01000000 ) != 0); 964 localF28 = ((l.getElement(9) & 0b01000000 ) != 0); 965 leadSlot = (((l.getElement(9) & 0x03) * 128) + l.getElement(8) ); 966 notifySlotListeners(); 967 break; 968 case LnConstants.OPC_SL_RD_DATA: 969 lastUpdateTime = System.currentTimeMillis(); 970 //fall through 971 case LnConstants.OPC_WR_SL_DATA: { 972 if (l.getElement(1) != 0x0E) { 973 return; // not an appropriate reply 974 } // valid, so fill contents 975 if (slot != l.getElement(2)) { 976 log.error("Asked to handle message not for this slot ({}) {}", slot, l); 977 } 978 if (loconetProtocol == LnConstants.LOCONETPROTOCOL_UNKNOWN) { 979 loconetProtocol = LnConstants.LOCONETPROTOCOL_ONE; // all it can be... 980 } 981 stat = l.getElement(3); 982 _pcmd = l.getElement(4); 983 addr = l.getElement(4) + 128 * l.getElement(9); 984 spd = l.getElement(5); 985 dirf = l.getElement(6); 986 trk = l.getElement(7); 987 ss2 = l.getElement(8); 988 // item 9 is in add2 989 snd = l.getElement(10); 990 id = l.getElement(11) + 128 * l.getElement(12); 991 expandedThrottleControllingID = l.getElement(11); 992 993 notifySlotListeners(); 994 return; 995 } 996 case LnConstants.OPC_SLOT_STAT1: 997 if (slot != l.getElement(1)) { 998 log.error("Asked to handle message not for this slot {}", l); 999 } 1000 stat = l.getElement(2); 1001 notifySlotListeners(); 1002 lastUpdateTime = System.currentTimeMillis(); 1003 return; 1004 case LnConstants.OPC_LOCO_SND: { 1005 // set sound functions in slot - first, clear bits 1006 snd &= ~(LnConstants.SND_F5 | LnConstants.SND_F6 1007 | LnConstants.SND_F7 | LnConstants.SND_F8); 1008 // and set them as masked 1009 snd |= ((LnConstants.SND_F5 | LnConstants.SND_F6 1010 | LnConstants.SND_F7 | LnConstants.SND_F8) & l.getElement(2)); 1011 notifySlotListeners(); 1012 lastUpdateTime = System.currentTimeMillis(); 1013 return; 1014 } 1015 case LnConstants.OPC_LOCO_DIRF: { 1016 // When slot is consist-mid or consist-sub, this LocoNet Opcode 1017 // can only change the functions; direction cannot be changed. 1018 if (((stat & LnConstants.CONSIST_MASK) == LnConstants.CONSIST_MID) || 1019 ((stat & LnConstants.CONSIST_MASK) == LnConstants.CONSIST_SUB)) { 1020 // set functions in slot - first, clear bits, preserving DIRF_DIR bit 1021 dirf &= LnConstants.DIRF_DIR | (~(LnConstants.DIRF_F0 1022 | LnConstants.DIRF_F1 | LnConstants.DIRF_F2 1023 | LnConstants.DIRF_F3 | LnConstants.DIRF_F4)); 1024 // and set the function bits from the LocoNet message 1025 dirf += ((LnConstants.DIRF_F0 1026 | LnConstants.DIRF_F1 | LnConstants.DIRF_F2 1027 | LnConstants.DIRF_F3 | LnConstants.DIRF_F4) & l.getElement(2)); 1028 } else { 1029 // set direction, functions in slot - first, clear bits 1030 dirf &= ~(LnConstants.DIRF_DIR | LnConstants.DIRF_F0 1031 | LnConstants.DIRF_F1 | LnConstants.DIRF_F2 1032 | LnConstants.DIRF_F3 | LnConstants.DIRF_F4); 1033 // and set them as masked 1034 dirf += ((LnConstants.DIRF_DIR | LnConstants.DIRF_F0 1035 | LnConstants.DIRF_F1 | LnConstants.DIRF_F2 1036 | LnConstants.DIRF_F3 | LnConstants.DIRF_F4) & l.getElement(2)); 1037 1038 } 1039 notifySlotListeners(); 1040 lastUpdateTime = System.currentTimeMillis(); 1041 return; 1042 } 1043 case LnConstants.OPC_MOVE_SLOTS: 1044 case LnConstants.OPC_LINK_SLOTS: 1045 case LnConstants.OPC_UNLINK_SLOTS: { 1046 // change in slot status, if any, will be reported by the reply, 1047 // so don't need to do anything here (but could) 1048 lastUpdateTime = System.currentTimeMillis(); 1049 notifySlotListeners(); 1050 return; 1051 } 1052 case LnConstants.OPC_LOCO_SPD: { 1053 // This opcode has no effect on the slot's speed setting if the 1054 // slot is mid-consist or sub-consist. 1055 if (((stat & LnConstants.CONSIST_MASK) != LnConstants.CONSIST_MID) && 1056 ((stat & LnConstants.CONSIST_MASK) != LnConstants.CONSIST_SUB)) { 1057 1058 spd = l.getElement(2); 1059 notifySlotListeners(); 1060 lastUpdateTime = System.currentTimeMillis(); 1061 } else { 1062 log.info("Ignoring speed change for slot {} marked as consist-mid or consist-sub.", slot); 1063 } 1064 return; 1065 } 1066 case LnConstants.OPC_CONSIST_FUNC: { 1067 // This opcode can be sent to a slot which is marked as mid-consist 1068 // or sub-consist. Do not pay attention to this message if the 1069 // slot is not mid-consist or sub-consist. 1070 if (((stat & LnConstants.CONSIST_MASK) == LnConstants.CONSIST_MID) || 1071 ((stat & LnConstants.CONSIST_MASK) == LnConstants.CONSIST_SUB)) { 1072 // set functions in slot - first, clear bits, preserving DIRF_DIR bit 1073 dirf &= LnConstants.DIRF_DIR | (~(LnConstants.DIRF_F0 1074 | LnConstants.DIRF_F1 | LnConstants.DIRF_F2 1075 | LnConstants.DIRF_F3 | LnConstants.DIRF_F4)); 1076 // and set the function bits from the LocoNet message 1077 dirf += ((LnConstants.DIRF_F0 1078 | LnConstants.DIRF_F1 | LnConstants.DIRF_F2 1079 | LnConstants.DIRF_F3 | LnConstants.DIRF_F4) & l.getElement(2)); 1080 notifySlotListeners(); 1081 lastUpdateTime = System.currentTimeMillis(); 1082 } 1083 return; 1084 } 1085 case LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL: { 1086 if (l.getElement(1) == LnConstants.RE_IB2_SPECIAL_FUNCS_TOKEN) { 1087 // IB function message 1088 int data = l.getElement(4); 1089 switch (l.getElement(3)) { 1090 case LnConstants.RE_IB1_SPECIAL_F5_F11_TOKEN: 1091 // under 8 are kept in the slot, not local variables 1092 localF9 = ((data & LnConstants.RE_IB1_F9_MASK) != 0); 1093 localF10 = ((data & LnConstants.RE_IB1_F10_MASK) != 0); 1094 localF11 = ((data & LnConstants.RE_IB1_F11_MASK) != 0); 1095 return; 1096 case LnConstants.RE_IB2_SPECIAL_F13_F19_TOKEN: 1097 localF13 = ((data & LnConstants.RE_IB2_F13_MASK) != 0); 1098 localF14 = ((data & LnConstants.RE_IB2_F14_MASK) != 0); 1099 localF15 = ((data & LnConstants.RE_IB2_F15_MASK) != 0); 1100 localF16 = ((data & LnConstants.RE_IB2_F16_MASK) != 0); 1101 localF17 = ((data & LnConstants.RE_IB2_F17_MASK) != 0); 1102 localF18 = ((data & LnConstants.RE_IB2_F18_MASK) != 0); 1103 localF19 = ((data & LnConstants.RE_IB2_F19_MASK) != 0); 1104 return; 1105 case LnConstants.RE_IB2_SPECIAL_F21_F27_TOKEN: 1106 localF21 = ((data & LnConstants.RE_IB2_F21_MASK) != 0); 1107 localF22 = ((data & LnConstants.RE_IB2_F22_MASK) != 0); 1108 localF23 = ((data & LnConstants.RE_IB2_F23_MASK) != 0); 1109 localF24 = ((data & LnConstants.RE_IB2_F24_MASK) != 0); 1110 localF25 = ((data & LnConstants.RE_IB2_F25_MASK) != 0); 1111 localF26 = ((data & LnConstants.RE_IB2_F26_MASK) != 0); 1112 localF27 = ((data & LnConstants.RE_IB2_F27_MASK) != 0); 1113 return; 1114 case LnConstants.RE_IB2_SPECIAL_F20_F28_TOKEN: 1115 localF12 = ((data & LnConstants.RE_IB2_SPECIAL_F12_MASK) != 0); 1116 localF20 = ((data & LnConstants.RE_IB2_SPECIAL_F20_MASK) != 0); 1117 localF28 = ((data & LnConstants.RE_IB2_SPECIAL_F28_MASK) != 0); 1118 return; 1119 default: 1120 log.debug("Found IB RE_OPC_IB2_SPECIAL message of {}", l); 1121 return; 1122 } 1123 } 1124 int src = slot; 1125 int dest = ((l.getElement(3) & 0x07) * 128) + (l.getElement(4) & 0x7f); 1126 // null move or change status or consisting or? 1127 if ((l.getElement(1) & 0b11111000) == 0b00111000) { 1128 if (((l.getElement(3) & 0b01110000) == 0b01100000)) { 1129 stat = l.getElement(4); 1130 notifySlotListeners(); 1131 return; 1132 } else if ((l.getElement(3) & 0b01110000) == 0b01010000) { 1133 // unconsisting returns slot contents so do nothing to this slot 1134 return; 1135 } else if ((l.getElement(3) & 0b01110000) == 0b01000000) { 1136 //consisting do something? 1137 //Set From slot as slave to slot as master 1138 stat = stat | LnConstants.CONSIST_TOP; 1139 notifySlotListeners(); 1140 return; 1141 } else if (src == 0 && dest == 0) { 1142 stat = stat & ~LnConstants.LOCO_IN_USE; 1143 log.debug("set idle"); 1144 notifySlotListeners(); 1145 return; 1146 } 1147 } 1148 return; 1149 } 1150 default: { 1151 throw new LocoNetException("message can't be parsed"); // NOI18N 1152 } 1153 } 1154 } 1155 1156 /** 1157 * Sets F9 through F28 (as appropriate) from data extracted from LocoNet 1158 * "OPC_IMM_PACKET" message. 1159 * <p>If the pkt parameter does not contain data from an appropriate 1160 * OPC_IMM_PACKET message, the pkt is ignored and the slot object remains 1161 * unchanged. 1162 * 1163 * @param pkt is a "long" consisting of four bytes extracted from a LocoNet 1164 * "OPC_IMM_PACKET" message. 1165 * <p> 1166 * {@link jmri.jmrix.loconet.SlotManager#getDirectDccPacket(LocoNetMessage m)} 1167 */ 1168 public void functionMessage(long pkt) { 1169 // parse for which set of functions 1170 if ((pkt & 0xFFFFFF0) == 0xA0) { 1171 // F9-12 1172 localF9 = ((pkt & 0x01) != 0); 1173 localF10 = ((pkt & 0x02) != 0); 1174 localF11 = ((pkt & 0x04) != 0); 1175 localF12 = ((pkt & 0x08) != 0); 1176 notifySlotListeners(); 1177 } else if ((pkt & 0xFFFFFF00) == 0xDE00) { 1178 // check F13-20 1179 localF13 = ((pkt & 0x01) != 0); 1180 localF14 = ((pkt & 0x02) != 0); 1181 localF15 = ((pkt & 0x04) != 0); 1182 localF16 = ((pkt & 0x08) != 0); 1183 localF17 = ((pkt & 0x10) != 0); 1184 localF18 = ((pkt & 0x20) != 0); 1185 localF19 = ((pkt & 0x40) != 0); 1186 localF20 = ((pkt & 0x80) != 0); 1187 notifySlotListeners(); 1188 } else if ((pkt & 0xFFFFFF00) == 0xDF00) { 1189 // check F21-28 1190 localF21 = ((pkt & 0x01) != 0); 1191 localF22 = ((pkt & 0x02) != 0); 1192 localF23 = ((pkt & 0x04) != 0); 1193 localF24 = ((pkt & 0x08) != 0); 1194 localF25 = ((pkt & 0x10) != 0); 1195 localF26 = ((pkt & 0x20) != 0); 1196 localF27 = ((pkt & 0x40) != 0); 1197 localF28 = ((pkt & 0x80) != 0); 1198 notifySlotListeners(); 1199 } 1200 } 1201 1202 /** 1203 * Update the decoder type bits in STAT1 (D2, D1, D0) 1204 * 1205 * @param status New values for STAT1 (D2, D1, D0) 1206 * @return Formatted LocoNet message to change value. 1207 */ 1208 public LocoNetMessage writeMode(int status) { 1209 if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO ) { 1210 LocoNetMessage l = new LocoNetMessage(4); 1211 l.setOpCode(LnConstants.OPC_SLOT_STAT1); 1212 l.setElement(1, slot); 1213 l.setElement(2, (stat & ~LnConstants.DEC_MODE_MASK) | status); 1214 return l; 1215 } else { 1216 LocoNetMessage l = new LocoNetMessage(6); 1217 l.setOpCode(LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL); 1218 l.setElement(1, ((slot / 128) & 0x03) | 0b00111000 ) ; 1219 l.setElement(2, slot & 0x7f); 1220 l.setElement(3, 0x60); 1221 l.setElement(4, (stat & ~LnConstants.DEC_MODE_MASK) | status); 1222 return l; 1223 } 1224 } 1225 1226 /** 1227 * Sets the object's ID value and returns a LocoNet message to inform the 1228 * command station that the throttle ID has been changed. 1229 * @param newID the new ID number to set into the slot object 1230 * @return a LocoNet message containing a "Slot Write" message to inform the 1231 * command station that a specific throttle is controlling the slot. 1232 */ 1233 public LocoNetMessage writeThrottleID(int newID) { 1234 id = (newID & 0x17F); 1235 return writeSlot(); 1236 } 1237 1238 /** 1239 * Set the throttle ID in the slot 1240 * 1241 * @param throttleId full id 1242 */ 1243 public void setThrottleIdentity(int throttleId) { 1244 id = throttleId; 1245 } 1246 1247 /** 1248 * Get the throttle ID in the slot 1249 * 1250 *@return the Id of the Throttle 1251 */ 1252 public int getThrottleIdentity() { 1253 return id; 1254 } 1255 1256 public int getLeadSlot() { 1257 return leadSlot; 1258 } 1259 1260 /** 1261 * Update the status mode bits in STAT1 (D5, D4) 1262 * 1263 * @param status New values for STAT1 (D5, D4) 1264 * @return Formatted LocoNet message to change value. 1265 */ 1266 public LocoNetMessage writeStatus(int status) { 1267 if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO ) { 1268 LocoNetMessage l = new LocoNetMessage(4); 1269 l.setOpCode(LnConstants.OPC_SLOT_STAT1); 1270 l.setElement(1, slot); 1271 l.setElement(2, (stat & ~LnConstants.LOCOSTAT_MASK) | status); 1272 return l; 1273 } else { 1274 LocoNetMessage l = new LocoNetMessage(6); 1275 l.setOpCode(LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL); 1276 l.setElement(1, ((slot / 128) & 0x03) | 0b00111000 ) ; 1277 l.setElement(2, slot & 0x7f); 1278 l.setElement(3, 0x60); 1279 l.setElement(4, (stat & ~LnConstants.LOCOSTAT_MASK) | status); 1280 return l; 1281 } 1282 } 1283 1284 /** 1285 * Update Speed 1286 * 1287 * @param speed new speed 1288 * @return Formatted LocoNet message to change value. 1289 */ 1290 public LocoNetMessage writeSpeed(int speed) { 1291 if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO) { 1292 LocoNetMessage l = new LocoNetMessage(4); 1293 l.setOpCode(LnConstants.OPC_LOCO_SPD); 1294 l.setElement(1, slot ); 1295 l.setElement(2, speed); 1296 return l; 1297 } else { 1298 LocoNetMessage l = new LocoNetMessage(6); 1299 l.setOpCode(LnConstants.OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR); 1300 l.setElement(1, ((slot / 128) & 0x03) | ((dirf & LnConstants.DIRF_DIR ) >> 2) ); 1301 l.setElement(2, slot & 0x7f); 1302 l.setElement(3, (id & 0x7f)); 1303 l.setElement(4, speed); 1304 return l; 1305 } 1306 } 1307 1308 /** 1309 * Create LocoNet message which dispatches this slot 1310 * 1311 * Note that the invoking method ought to invoke the slot's NotifySlotListeners 1312 * method to inform any other interested parties that the slot status has changed. 1313 * 1314 * @return LocoNet message which "dispatches" the slot 1315 */ 1316 public LocoNetMessage dispatchSlot() { 1317 if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO) { 1318 LocoNetMessage l = new LocoNetMessage(4); 1319 l.setOpCode(LnConstants.OPC_MOVE_SLOTS); 1320 l.setElement(1, slot); 1321 l.setElement(2, 0); 1322 return l; 1323 } else { 1324 LocoNetMessage l = new LocoNetMessage(6); 1325 l.setOpCode(LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL); 1326 l.setElement(1, ((slot / 128) & 0x03) | 0b00111000 ) ; 1327 l.setElement(2, slot & 0x7f); 1328 l.setElement(3, 0); 1329 l.setElement(4, 0); 1330 return l; 1331 } 1332 } 1333 1334 /** 1335 * Create a message to perform a null move on this slot. 1336 * @return correct LocoNetMessage for protocol being used. 1337 */ 1338 public LocoNetMessage writeNullMove() { 1339 if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO) { 1340 // perform the null slot move for low numbered slots 1341 LocoNetMessage msg = new LocoNetMessage(4); 1342 msg.setOpCode(LnConstants.OPC_MOVE_SLOTS); 1343 msg.setElement(1, slot); 1344 msg.setElement(2, slot); 1345 return (msg); 1346 } 1347 // or the null move for higher numbered slots 1348 LocoNetMessage msg = new LocoNetMessage(6); 1349 msg.setOpCode(0xd4); 1350 msg.setElement(1, (slot / 128) | 0b00111000); 1351 msg.setElement(2, slot & 0b01111111); 1352 msg.setElement(3, (slot / 128) & 0b00000111); 1353 msg.setElement(4, slot & 0b01111111); 1354 return msg; 1355 } 1356 1357 /** 1358 * Create a LocoNet OPC_SLOT_STAT1 message which releases this slot to the 1359 * "Common" state 1360 * 1361 * The invoking method must send the returned LocoNet message to LocoNet in 1362 * order to have a useful effect. 1363 * 1364 * Upon receipt of the echo of the transmitted OPC_SLOT_STAT1 message, the 1365 * LocoNetSlot object will notify its listeners. 1366 * 1367 * @return LocoNet message which "releases" the slot to the "Common" state 1368 */ 1369 public LocoNetMessage releaseSlot() { 1370 return writeStatus(LnConstants.LOCO_COMMON); 1371 } 1372 1373 /** 1374 * Creates a LocoNet "OPC_WR_SL_DATA" message containing the current state of 1375 * the LocoNetSlot object. 1376 * 1377 * @return a LocoNet message which can be used to inform the command station 1378 * of a change in the slot contents. 1379 */ 1380 public LocoNetMessage writeSlot() { 1381 if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO || slot == LnConstants.FC_SLOT) { //special case for fc 1382 LocoNetMessage l = new LocoNetMessage(14); 1383 l.setOpCode(LnConstants.OPC_WR_SL_DATA); 1384 l.setElement(1, 0x0E); 1385 l.setElement(2, slot & 0x7F); 1386 l.setElement(3, stat & 0x7F); 1387 l.setElement(4, addr & 0x7F); 1388 l.setElement(9, (addr / 128) & 0x7F); 1389 l.setElement(5, spd & 0x7F); 1390 l.setElement(6, dirf & 0x7F); 1391 l.setElement(7, trk & 0x7F); 1392 l.setElement(8, ss2 & 0x7F); 1393 // item 9 is add2 1394 l.setElement(10, snd & 0x7F); 1395 l.setElement(11, id & 0x7F); 1396 l.setElement(12, (id / 128) & 0x7F); 1397 return l; 1398 } 1399 LocoNetMessage l = new LocoNetMessage(21); 1400 l.setOpCode(LnConstants.OPC_EXP_WR_SL_DATA); 1401 l.setElement(1, 0x15); 1402 l.setElement(2, (slot / 128) & 0x03); 1403 l.setElement(3, slot & 0x7F); 1404 l.setElement(4, stat & 0x7F); 1405 l.setElement(6, (addr / 128) & 0x7F); 1406 l.setElement(5, addr & 0x7F); 1407 l.setElement(7, ( trk | 0x40 ) & 0x7F); // track power status and Expanded slot protocol 1408 l.setElement(8, spd & 0x7F); 1409 l.setElement(9, (isF12() ? 0b00010000 : 0x00 ) 1410 | (isF20() ? 0b00100000 : 0x00) 1411 | (isF28() ? 0b01000000 : 0x00)); 1412 l.setElement(10, ( isForward() ? 0x00 : 0x00100000) 1413 | (isF0() ? 0b00010000 : 0x00) 1414 | (isF1() ? 0b00000001 : 0x00) 1415 | (isF2() ? 0b00000010 : 0x00) 1416 | (isF3() ? 0b00000100 : 0x00) 1417 | (isF4() ? 0b00001000 : 0x00)); 1418 l.setElement(11, ( isF5() ? 0b00000001 : 0x00) 1419 | ( isF6() ? 0b00000010 : 0x00) 1420 | ( isF7() ? 0b00000100 : 0x00) 1421 | ( isF8() ? 0b00001000 : 0x00) 1422 | ( isF9() ? 0b00010000 : 0x00) 1423 | (isF10() ? 0b00100000 : 0x00) 1424 | (isF11() ? 0b01000000 : 0x00)); 1425 l.setElement(12,( isF13() ? 0b00000001 : 0x00) 1426 | (isF14() ? 0b00000010 : 0x00) 1427 | (isF15() ? 0b00000100 : 0x00) 1428 | (isF16() ? 0b00001000 : 0x00) 1429 | (isF17() ? 0b00010000 : 0x00) 1430 | (isF18() ? 0b00100000 : 0x00) 1431 | (isF19() ? 0b01000000 : 0x00)); 1432 l.setElement(13,( isF21() ? 0b00000001 : 0x00) 1433 | (isF22() ? 0b00000010 : 0x00) 1434 | (isF23() ? 0b00000100 : 0x00) 1435 | (isF24() ? 0b00001000 : 0x00) 1436 | (isF25() ? 0b00010000 : 0x00) 1437 | (isF26() ? 0b00100000 : 0x00) 1438 | (isF27() ? 0b01000000 : 0x00)); 1439 l.setElement(18, id & 0x7F); 1440 l.setElement(19, (id / 128) & 0x7F); 1441 return l; 1442 } 1443 1444 // data values to echo slot contents 1445 final private int slot; // <SLOT#> is the number of the slot that was read. 1446 private boolean isInitialized; // set when full initilization is complete with the throttle ID. 1447 private int loconetProtocol; // protocol used by the slot. 1448 private SlotType slotType; // system, loco, unknown 1449 private int stat; // <STAT> is the status of the slot 1450 private int addr; // full address of the loco, made from 1451 // <ADDR> is the low 7 (0-6) bits of the Loco address 1452 // <ADD2> is the high 7 bits (7-13) of the 14-bit loco address 1453 private int spd; // <SPD> is the current speed (0-127) 1454 private int dirf; // <DIRF> is the current Direction and the setting for functions F0-F4 1455 private int trk = 7; // <TRK> is the global track status 1456 private int ss2; // <SS2> is the an additional slot status 1457 private int snd; // <SND> is the settings for functions F5-F8 1458 private int id; // throttle id, made from 1459 // <ID1> and <ID2> normally identify the throttle controlling the loco 1460 private int expandedThrottleControllingID; //the throttle ID byte that is used in sending commands that require a throttle ID. (ID1) 1461 private int leadSlot; // the top slot for this slot in a consist. 1462 1463 private int _pcmd; // hold pcmd and pstat for programmer 1464 1465 private long lastUpdateTime; // Time of last update for detecting stale slots 1466 1467 // data members to hold contact with the slot listeners 1468 final private List<SlotListener> slotListeners = new ArrayList<>(); 1469 1470 /** 1471 * Registers a slot listener if it is not already registered. 1472 * 1473 * @param l a slot listener 1474 */ 1475 public synchronized void addSlotListener(SlotListener l) { 1476 // add only if not already registered 1477 if (!slotListeners.contains(l)) { 1478 slotListeners.add(l); 1479 } 1480 } 1481 1482 /** 1483 * Un-registers a slot listener. 1484 * 1485 * @param l a slot listener 1486 */ 1487 public synchronized void removeSlotListener(SlotListener l) { 1488 if (slotListeners.contains(l)) { 1489 slotListeners.remove(l); 1490 } 1491 } 1492 1493 /** 1494 * Returns the timestamp when this LocoNetSlot was updated by some LocoNet 1495 * message. 1496 * 1497 * @return last time the slot info was updated 1498 */ 1499 public long getLastUpdateTime() { 1500 return lastUpdateTime; 1501 } 1502 1503 /** 1504 * Notifies all listeners that this slot has been changed in some way. 1505 */ 1506 public void notifySlotListeners() { 1507 // make a copy of the listener list to synchronized not needed for transmit 1508 List<SlotListener> v; 1509 synchronized (this) { 1510 v = new ArrayList<>(slotListeners); 1511 } 1512 log.debug("notify {} SlotListeners",v.size()); // NOI18N 1513 // forward to all listeners 1514 int cnt = v.size(); 1515 for (int i = 0; i < cnt; i++) { 1516 SlotListener client = v.get(i); 1517 client.notifyChangedSlot(this); 1518 } 1519 } 1520 1521 /** 1522 * For fast-clock slot, set a "CLK_CNTRL" bit On. This method logs an error 1523 * if invoked for a slot other than the fast-clock slot. 1524 * 1525 * @param val is the new "CLK_CNTRL" bit value to turn On 1526 */ 1527 public void setFcCntrlBitOn(int val) { 1528 // TODO: consider throwing a LocoNetException if issued for a slot other 1529 // than the "fast clock slot". 1530 if (getSlot() != LnConstants.FC_SLOT) { 1531 log.error("setFcCntrl invalid for slot [{}]", getSlot()); 1532 } 1533 snd |= val; 1534 } 1535 1536 /** 1537 * For fast-clock slot, set a "CLK_CNTRL" bit Off. This method logs an error 1538 * if invoked for a slot other than the fast-clock slot. 1539 * 1540 * @param val is the new "CLK_CNTRL" bit value to turn Off 1541 */ 1542 public void setFcCntrlBitOff(int val) { 1543 // TODO: consider throwing a LocoNetException if issued for a slot other 1544 // than the "fast clock slot". 1545 if (getSlot() != LnConstants.FC_SLOT) { 1546 log.error("setFcCntrl invalid for slot [{}]" , getSlot()); 1547 } 1548 snd &= ~val; 1549 } 1550 1551 /** 1552 * Get the track status byte (location 7) 1553 * <p> 1554 * Note that the <TRK> byte is not accurate on some command stations. 1555 * 1556 * @return the effective <TRK> byte 1557 */ 1558 public int getTrackStatus() { return trk; } 1559 1560 /** 1561 * Set the track status byte (location 7) 1562 * <p> 1563 * Note that setting the LocoNetSlot object's track status may result in a 1564 * change to the command station's actual track status if the slot's status 1565 * is communicated to the command station via an OPC_WR_DL_DATA LocoNet message. 1566 * 1567 * @param status is the new track status value. 1568 */ 1569 public void setTrackStatus(int status) { trk = status; } 1570 1571 /** 1572 * Return the days value from the slot. Only valid for fast-clock slot. 1573 * <p> 1574 * This method logs an error if invoked for a slot other than the fast-clock slot. 1575 * 1576 * @return "Days" value currently in fast-clock slot. 1577 */ 1578 public int getFcDays() { 1579 // TODO: consider throwing a LocoNetException if issued for a slot other 1580 // than the "fast clock slot". 1581 if (getSlot() != LnConstants.FC_SLOT) { 1582 log.error("getFcDays invalid for slot {}", getSlot()); 1583 } 1584 return (addr & 0x3f80) / 0x80; 1585 } 1586 1587 /** 1588 * For fast-clock slot, set "days" value. 1589 * <p> 1590 * Note that the new days value is not effective until a LocoNet 1591 * message is sent which writes the fast-clock slot data. 1592 * <p> 1593 * This method logs an error if invoked for a slot other than the fast-clock slot. 1594 * 1595 * @param val is the new fast-clock "days" value 1596 */ 1597 public void setFcDays(int val) { 1598 // TODO: consider throwing a LocoNetException if issued for a slot other 1599 // than the "fast clock slot". 1600 if (getSlot() != LnConstants.FC_SLOT) { 1601 log.error("setFcDays invalid for slot {}", getSlot()); 1602 } 1603 addr = val * 128 + (addr & 0x7f); 1604 } 1605 1606 /** 1607 * Return the hours value from the slot. Only valid for fast-clock slot. 1608 * <p> 1609 * This method logs an error if invoked for a slot other than the fast-clock slot. 1610 * 1611 * @return "Hours" value currently stored in fast clock slot. 1612 */ 1613 public int getFcHours() { 1614 // TODO: consider throwing a LocoNetException if issued for a slot other 1615 // than the "fast clock slot". 1616 if (getSlot() != LnConstants.FC_SLOT) { 1617 log.error("getFcHours invalid for slot {}", getSlot()); 1618 } 1619 int temp = ((256 - ss2) & 0x7F) % 24; 1620 return (24 - temp) % 24; 1621 } 1622 1623 /** 1624 * For fast-clock slot, set "hours" value. 1625 * <p> 1626 * Note that the new hours value is not effective until a LocoNet 1627 * message is sent which writes the fast-clock slot data. 1628 * <p> 1629 * This method logs an error if invoked for a slot other than the fast-clock slot. 1630 * 1631 * @param val is the new fast-clock "hours" value 1632 */ 1633 public void setFcHours(int val) { 1634 // TODO: consider throwing a LocoNetException if issued for a slot other 1635 // than the "fast clock slot". 1636 if (getSlot() != LnConstants.FC_SLOT) { 1637 log.error("setFcHours invalid for slot {}", getSlot()); 1638 } 1639 ss2 = (256 - (24 - val)) & 0x7F; 1640 } 1641 1642 /** 1643 * Return the minutes value from the slot. Only valid for fast-clock slot. 1644 * <p> 1645 * This method logs an error if invoked for a slot other than the fast-clock slot. 1646 * 1647 * @return Return minutes value currently stored in the fast clock slot. 1648 */ 1649 public int getFcMinutes() { 1650 // TODO: consider throwing a LocoNetException if issued for a slot other 1651 // than the "fast clock slot". 1652 if (getSlot() != LnConstants.FC_SLOT) { 1653 log.error("getFcMinutes invalid for slot {}", getSlot()); 1654 } 1655 int temp = ((255 - dirf) & 0x7F) % 60; 1656 return (60 - temp) % 60; 1657 } 1658 1659 /** 1660 * For fast-clock slot, set "minutes" value. 1661 * <p> 1662 * Note that the new minutes value is not effective until a LocoNet 1663 * message is sent which writes the fast-clock slot data. 1664 * <p> 1665 * This method logs an error if invoked for a slot other than the fast-clock slot. 1666 * 1667 * @param val is the new fast-clock "minutes" value 1668 */ 1669 public void setFcMinutes(int val) { 1670 // TODO: consider throwing a LocoNetException if issued for a slot other 1671 // than the "fast clock slot". 1672 if (getSlot() != LnConstants.FC_SLOT) { 1673 log.error("setFcMinutes invalid for slot {}", getSlot()); 1674 } 1675 dirf = (255 - (60 - val)) & 0x7F; 1676 } 1677 1678 /** 1679 * Return the fractional minutes value from the slot. Only valid for fast- 1680 * clock slot. 1681 * <p> 1682 * This method logs an error if invoked for a slot other than the fast-clock slot. 1683 * 1684 * @return Return frac_mins field which is the number of 65ms ticks until 1685 * then next minute rollover. These ticks step at the current fast 1686 * clock rate 1687 */ 1688 public int getFcFracMins() { 1689 // TODO: consider throwing a LocoNetException if issued for a slot other 1690 // than the "fast clock slot". 1691 if (getSlot() != LnConstants.FC_SLOT) { 1692 log.error("getFcFracMins invalid for slot {}", getSlot()); 1693 } 1694 return ((addr & 0x7F) | ((spd & 0x7F) << 8)); 1695 } 1696 1697 /** 1698 * Set the "frac_mins" value. 1699 * This has to be calculated as required by the Command Station, 1700 * then bit shifted if required. 1701 * It is comprised of a base number and the distance from the base to 0x8000 1702 * or 0x4000 deoending on command station. 1703 * It is read and written as is LO,HO and loses the bit 7 of the LO. 1704 * It was never intended for external use. 1705 * The base can be found by setting the clock to 0xXX7F, with a rate of 1 1706 * and pounding the clock every 250 to 100 msecs until it roles. 1707 * <p> 1708 * Note 1: The new fractional minutes value is not effective until a LocoNet slot write happens 1709 * <p> 1710 * Note 2: DT40x & DT500 throttles ignore this value, and set only the whole minutes. 1711 * <p> 1712 * This method logs an error if invoked for a slot other than the fast-clock slot. 1713 * @param val is the new fast-clock "fractional minutes" including the base, and bit shifted if required. 1714 */ 1715 public void setFcFracMins(int val) { 1716 // TODO: consider throwing a LocoNetException if issued for a slot other 1717 // than the "fast clock slot". 1718 if (getSlot() != LnConstants.FC_SLOT) { 1719 log.error("setFcFracMins invalid for slot {}", getSlot()); 1720 } 1721 int temp = 0x7F7F & val; 1722 addr = (addr & 0x7F00) | (temp & 0x7F); 1723 spd = (temp >> 8) & 0x7F; 1724 } 1725 1726 /** 1727 * Get the fast-clock rate. Only valid for fast-clock slot. 1728 * <p> 1729 * This method logs an error if invoked for a slot other than the fast-clock slot. 1730 * 1731 * @return Rate stored in fast clock slot. 1732 */ 1733 public int getFcRate() { 1734 // TODO: consider throwing a LocoNetException if issued for a slot other 1735 // than the "fast clock slot". 1736 if (getSlot() != LnConstants.FC_SLOT) { 1737 log.error("getFcRate invalid for slot {}", getSlot()); 1738 } 1739 return stat; 1740 } 1741 1742 /** 1743 * For fast-clock slot, set "rate" value. 1744 * <p> 1745 * Note that the new rate is not effective until a LocoNet message is sent 1746 * which writes the fast-clock slot data. 1747 * <p> 1748 * This method logs an error if invoked for a slot other than the fast-clock slot. 1749 * 1750 * @param val is the new fast-clock rate 1751 */ 1752 public void setFcRate(int val) { 1753 // TODO: consider throwing a LocoNetException if issued for a slot other 1754 // than the "fast clock slot". 1755 if (getSlot() != LnConstants.FC_SLOT) { 1756 log.error("setFcRate invalid for slot {}", getSlot()); 1757 } 1758 stat = val & 0x7F; 1759 } 1760 1761 private final static Logger log = LoggerFactory.getLogger(LocoNetSlot.class); 1762}