001package jmri.jmrix.loconet; 002 003import java.util.ArrayList; 004import java.util.Arrays; 005import java.util.Hashtable; 006import java.util.List; 007import java.util.Vector; 008import javax.annotation.Nonnull; 009import jmri.CommandStation; 010import jmri.ProgListener; 011import jmri.Programmer; 012import jmri.ProgrammingMode; 013import jmri.jmrix.AbstractProgrammer; 014import jmri.jmrix.loconet.SlotMapEntry.SlotType; 015 016/** 017 * Controls a collection of slots, acting as the counter-part of a LocoNet 018 * command station. 019 * <p> 020 * A SlotListener can register to hear changes. By registering here, the 021 * SlotListener is saying that it wants to be notified of a change in any slot. 022 * Alternately, the SlotListener can register with some specific slot, done via 023 * the LocoNetSlot object itself. 024 * <p> 025 * Strictly speaking, functions 9 through 28 are not in the actual slot, but 026 * it's convenient to imagine there's an "extended slot" and keep track of them 027 * here. This is a partial implementation, though, because setting is still done 028 * directly in {@link LocoNetThrottle}. In particular, if this slot has not been 029 * read from the command station, the first message directly setting F9 through 030 * F28 will not have a place to store information. Instead, it will trigger a 031 * slot read, so the following messages will be properly handled. 032 * <p> 033 * Some of the message formats used in this class are Copyright Digitrax, Inc. 034 * and used with permission as part of the JMRI project. That permission does 035 * not extend to uses in other software products. If you wish to use this code, 036 * algorithm or these message formats outside of JMRI, please contact Digitrax 037 * Inc for separate permission. 038 * <p> 039 * This Programmer implementation is single-user only. It's not clear whether 040 * the command stations can have multiple programming requests outstanding (e.g. 041 * service mode and ops mode, or two ops mode) at the same time, but this code 042 * definitely can't. 043 * 044 * @author Bob Jacobsen Copyright (C) 2001, 2003, 2024 045 * @author B. Milhaupt, Copyright (C) 2018 046 */ 047public class SlotManager extends AbstractProgrammer implements LocoNetListener, CommandStation { 048 049 /** 050 * Time to wait after programming operation complete on LocoNet 051 * before reporting completion and hence starting next operation 052 */ 053 static public int postProgDelay = 50; // this is public to allow changes via script 054 055 public int slotScanInterval = 50; // this is public to allow changes via script and tests 056 057 public int serviceModeReplyDelay = 20; // this is public to allow changes via script and tests. Adjusted by UsbDcs210PlusAdapter 058 059 public int opsModeReplyDelay = 100; // this is public to allow changes via script and tests. 060 061 public boolean pmManagerGotReply = false; //this is public to allow changes via script and tests 062 063 public boolean supportsSlot250; 064 065 /** 066 * a Map of the CS slots. 067 */ 068 public List<SlotMapEntry> slotMap = new ArrayList<SlotMapEntry>(); 069 070 /** 071 * Constructor for a SlotManager on a given TrafficController. 072 * 073 * @param tc Traffic Controller to be used by SlotManager for communication 074 * with LocoNet 075 */ 076 public SlotManager(LnTrafficController tc) { 077 this.tc = tc; 078 079 // change timeout values from AbstractProgrammer superclass 080 LONG_TIMEOUT = 180000; // Fleischmann command stations take forever 081 SHORT_TIMEOUT = 8000; // DCS240 reads 082 083 // dummy slot map until command station set (if ever) 084 slotMap = Arrays.asList(new SlotMapEntry(0,0,SlotType.SYSTEM), 085 new SlotMapEntry(1,120,SlotType.LOCO), 086 new SlotMapEntry(121,127,SlotType.SYSTEM), 087 new SlotMapEntry(128,247,SlotType.UNKNOWN), 088 new SlotMapEntry(248,256,SlotType.SYSTEM), // potential stat slots 089 new SlotMapEntry(257,375,SlotType.UNKNOWN), 090 new SlotMapEntry(376,384,SlotType.SYSTEM), 091 new SlotMapEntry(385,432,SlotType.UNKNOWN)); 092 093 loadSlots(true); 094 095 // listen to the LocoNet 096 tc.addLocoNetListener(~0, this); 097 098 } 099 100 /** 101 * Initialize the slots array. 102 * @param initialize if true a new slot is created else it is just updated with type 103 * and protocol 104 */ 105 protected void loadSlots(boolean initialize) { 106 // initialize slot array 107 for (SlotMapEntry item : slotMap) { 108 for (int slotIx = item.getFrom(); slotIx <= item.getTo() ; slotIx++) { 109 if (initialize) { 110 _slots[slotIx] = new LocoNetSlot( slotIx,getLoconetProtocol(),item.getSlotType()); 111 } 112 else { 113 _slots[slotIx].setSlotType(item.getSlotType()); 114 } 115 } 116 } 117 } 118 119 protected LnTrafficController tc; 120 121 /** 122 * Send a DCC packet to the rails. This implements the CommandStation 123 * interface. This mechanism can pass any valid NMRA packet of up to 124 * 6 data bytes (including the error-check byte). 125 * 126 * When available, these messages are forwarded to LocoNet using a 127 * "throttledTransmitter". This decreases the speed with which these 128 * messages are sent, resulting in lower throughput, but fewer 129 * rejections by the command station on account of "buffer-overflow". 130 * 131 * @param packet the data bytes of the raw NMRA packet to be sent. The 132 * "error check" byte must be included, even though the LocoNet 133 * message will not include that byte; the command station 134 * will re-create the error byte from the bytes encoded in 135 * the LocoNet message. LocoNet is unable to propagate packets 136 * longer than 6 bytes (including the error-check byte). 137 * 138 * @param sendCount the total number of times the packet is to be 139 * sent on the DCC track signal (not LocoNet!). Valid range is 140 * between 1 and 8. sendCount will be forced to this range if it 141 * is outside of this range. 142 */ 143 @Override 144 public boolean sendPacket(byte[] packet, int sendCount) { 145 if (sendCount > 8) { 146 log.warn("Ops Mode Accessory Packet 'Send count' reduced from {} to 8.", sendCount); // NOI18N 147 sendCount = 8; 148 } 149 if (sendCount < 1) { 150 log.warn("Ops Mode Accessory Packet 'Send count' of {} is illegal and is forced to 1.", sendCount); // NOI18N 151 sendCount = 1; 152 } 153 if (packet.length <= 1) { 154 log.error("Invalid DCC packet length: {}", packet.length); // NOI18N 155 } 156 if (packet.length > 6) { 157 log.error("DCC packet length is too great: {} bytes were passed; ignoring the request. ", packet.length); // NOI18N 158 } 159 160 LocoNetMessage m = new LocoNetMessage(11); 161 m.setElement(0, LnConstants.OPC_IMM_PACKET); 162 m.setElement(1, 0x0B); 163 m.setElement(2, 0x7F); 164 // the incoming packet includes a check byte that's not included in LocoNet packet 165 int length = packet.length - 1; 166 167 m.setElement(3, ((sendCount - 1) & 0x7) + 16 * (length & 0x7)); 168 169 int highBits = 0; 170 if (length >= 1 && ((packet[0] & 0x80) != 0)) { 171 highBits |= 0x01; 172 } 173 if (length >= 2 && ((packet[1] & 0x80) != 0)) { 174 highBits |= 0x02; 175 } 176 if (length >= 3 && ((packet[2] & 0x80) != 0)) { 177 highBits |= 0x04; 178 } 179 if (length >= 4 && ((packet[3] & 0x80) != 0)) { 180 highBits |= 0x08; 181 } 182 if (length >= 5 && ((packet[4] & 0x80) != 0)) { 183 highBits |= 0x10; 184 } 185 m.setElement(4, highBits); 186 187 m.setElement(5, 0); 188 m.setElement(6, 0); 189 m.setElement(7, 0); 190 m.setElement(8, 0); 191 m.setElement(9, 0); 192 for (int i = 0; i < packet.length - 1; i++) { 193 m.setElement(5 + i, packet[i] & 0x7F); 194 } 195 196 if (throttledTransmitter != null) { 197 throttledTransmitter.sendLocoNetMessage(m); 198 } else { 199 tc.sendLocoNetMessage(m); 200 } 201 return true; 202 } 203 204 /* 205 * command station switches 206 */ 207 private final int SLOTS_DCS240 = 433; 208 private int numSlots = SLOTS_DCS240; // This is the largest number so far. 209 private int slot248CommandStationType; 210 private int slot248CommandStationSerial; 211 private int slot250InUseSlots; 212 private int slot250IdleSlots; 213 private int slot250FreeSlots; 214 215 /** 216 * The network protocol. 217 */ 218 private int loconetProtocol = LnConstants.LOCONETPROTOCOL_UNKNOWN; // defaults to unknown 219 220 /** 221 * 222 * @param value the loconet protocol supported 223 */ 224 public void setLoconet2Supported(int value) { 225 loconetProtocol = value; 226 } 227 228 /** 229 * Get the Command Station type reported in slot 248 message 230 * @return model 231 */ 232 public String getSlot248CommandStationType() { 233 return LnConstants.IPL_NAME(slot248CommandStationType); 234 } 235 236 /** 237 * Get the total number of slots reported in the slot250 message; 238 * @return number of slots 239 */ 240 public int getSlot250CSSlots() { 241 return slot250InUseSlots + slot250IdleSlots + slot250FreeSlots; 242 } 243 244 /** 245 * 246 * @return the loconet protocol supported 247 */ 248 public int getLoconetProtocol() { 249 return loconetProtocol; 250 } 251 252 /** 253 * Information on slot state is stored in an array of LocoNetSlot objects. 254 * This is declared final because we never need to modify the array itself, 255 * just its contents. 256 */ 257 protected LocoNetSlot _slots[] = new LocoNetSlot[getNumSlots()]; 258 259 /** 260 * Access the information in a specific slot. Note that this is a mutable 261 * access, so that the information in the LocoNetSlot object can be changed. 262 * 263 * @param i Specific slot, counted starting from zero. 264 * @return The Slot object 265 */ 266 public LocoNetSlot slot(int i) { 267 return _slots[i]; 268 } 269 270 public int getNumSlots() { 271 return numSlots; 272 } 273 /** 274 * Obtain a slot for a particular loco address. 275 * <p> 276 * This requires access to the command station, even if the locomotive 277 * address appears in the current contents of the slot array, to ensure that 278 * our local image is up-to-date. 279 * <p> 280 * This method sends an info request. When the echo of this is returned from 281 * the LocoNet, the next slot-read is recognized as the response. 282 * <p> 283 * The object that's looking for this information must provide a 284 * SlotListener to notify when the slot ID becomes available. 285 * <p> 286 * The SlotListener is not subscribed for slot notifications; it can do that 287 * later if it wants. We don't currently think that's a race condition. 288 * 289 * @param i Specific slot, counted starting from zero. 290 * @param l The SlotListener to notify of the answer. 291 */ 292 public void slotFromLocoAddress (int i, SlotListener l) { 293 // store connection between this address and listener for later 294 mLocoAddrHash.put(Integer.valueOf(i), l); 295 296 // send info request 297 LocoNetMessage m = new LocoNetMessage(4); 298 if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO ) { 299 m.setOpCode(LnConstants.OPC_LOCO_ADR); // OPC_LOCO_ADR 300 } else { 301 m.setOpCode(LnConstants.OPC_EXP_REQ_SLOT); // Extended slot 302 } 303 m.setElement(1, (i / 128) & 0x7F); 304 m.setElement(2, i & 0x7F); 305 tc.sendLocoNetMessage(m); 306 } 307 308 javax.swing.Timer staleSlotCheckTimer = null; 309 310 /** 311 * Scan the slot array looking for slots that are in-use or common but have 312 * not had any updates in over 90s and issue a read slot request to update 313 * their state as the command station may have purged or stopped updating 314 * the slot without telling us via a LocoNet message. 315 * <p> 316 * This is intended to be called from the staleSlotCheckTimer 317 */ 318 private void checkStaleSlots() { 319 long staleTimeout = System.currentTimeMillis() - 90000; // 90 seconds ago 320 LocoNetSlot slot; 321 322 // We will just check the normal loco slots 1 to numSlots exclude systemslots 323 for (int i = 1; i < numSlots; i++) { 324 slot = _slots[i]; 325 if (!slot.isSystemSlot()) { 326 if ((slot.slotStatus() == LnConstants.LOCO_IN_USE || slot.slotStatus() == LnConstants.LOCO_COMMON) 327 && (slot.getLastUpdateTime() <= staleTimeout)) { 328 sendReadSlot(i); 329 break; // only send the first one found 330 } 331 } 332 } 333 } 334 335 336 java.util.TimerTask slot250Task = null; 337 /** 338 * Request slot data for 248 and 250 339 * Runs delayed 340 * <p> 341 * A call is trigger after the first slot response (PowerManager) received. 342 */ 343 private void pollSpecialSlots() { 344 sendReadSlot(248); 345 slot250Task = new java.util.TimerTask() { 346 @Override 347 public void run() { 348 try { 349 sendReadSlot(250); 350 } catch (Exception e) { 351 log.error("Exception occurred while checking slot250", e); 352 } 353 } 354 }; 355 jmri.util.TimerUtil.schedule(slot250Task,100); 356 } 357 358 /** 359 * Provide a mapping between locomotive addresses and the SlotListener 360 * that's interested in them. 361 */ 362 Hashtable<Integer, SlotListener> mLocoAddrHash = new Hashtable<>(); 363 364 // data members to hold contact with the slot listeners 365 final private Vector<SlotListener> slotListeners = new Vector<>(); 366 367 /** 368 * Add a slot listener, if it is not already registered 369 * <p> 370 * The slot listener will be invoked every time a slot changes state. 371 * 372 * @param l Slot Listener to be added 373 */ 374 public synchronized void addSlotListener(SlotListener l) { 375 // add only if not already registered 376 if (!slotListeners.contains(l)) { 377 slotListeners.addElement(l); 378 } 379 } 380 381 /** 382 * Add a slot listener, if it is registered. 383 * <p> 384 * The slot listener will be removed from the list of listeners which are 385 * invoked whenever a slot changes state. 386 * 387 * @param l Slot Listener to be removed 388 */ 389 public synchronized void removeSlotListener(SlotListener l) { 390 if (slotListeners.contains(l)) { 391 slotListeners.removeElement(l); 392 } 393 } 394 395 /** 396 * Trigger the notification of all SlotListeners. 397 * 398 * @param s The changed slot to notify. 399 */ 400 @SuppressWarnings("unchecked") 401 protected void notify(LocoNetSlot s) { 402 // make a copy of the listener vector to synchronized not needed for transmit 403 Vector<SlotListener> v; 404 synchronized (this) { 405 v = (Vector<SlotListener>) slotListeners.clone(); 406 } 407 log.debug("notify {} SlotListeners about slot {}", // NOI18N 408 v.size(), s.getSlot()); 409 // forward to all listeners 410 int cnt = v.size(); 411 for (int i = 0; i < cnt; i++) { 412 SlotListener client = v.elementAt(i); 413 client.notifyChangedSlot(s); 414 } 415 } 416 417 LocoNetMessage immedPacket; 418 419 /** 420 * Listen to the LocoNet. This is just a steering routine, which invokes 421 * others for the various processing steps. 422 * 423 * @param m incoming message 424 */ 425 @Override 426 public void message(LocoNetMessage m) { 427 if (m.getOpCode() == LnConstants.OPC_RE_LOCORESET_BUTTON) { 428 if (commandStationType.getSupportsLocoReset()) { 429 // Command station LocoReset button was triggered. 430 // 431 // Note that sending a LocoNet message using this OpCode to the command 432 // station does _not_ seem to trigger the equivalent effect; only 433 // pressing the button seems to do so. 434 // If the OpCode is received by JMRI, regardless of its source, 435 // JMRI will simply trigger a re-read of all slots. This will 436 // allow the JMRI slots to stay consistent with command station 437 // slot information, regardless of whether the command station 438 // just modified the slot information. 439 javax.swing.Timer t = new javax.swing.Timer(500, (java.awt.event.ActionEvent e) -> { 440 log.debug("Updating slots account received opcode 0x8a message"); // NOI18N 441 update(slotMap,slotScanInterval); 442 }); 443 t.stop(); 444 t.setInitialDelay(500); 445 t.setRepeats(false); 446 t.start(); 447 } 448 return; 449 } 450 451 // LACK processing for resend of immediate command 452 if (!mTurnoutNoRetry && immedPacket != null && 453 m.getOpCode() == LnConstants.OPC_LONG_ACK && 454 m.getElement(1) == 0x6D && m.getElement(2) == 0x00) { 455 // LACK reject, resend immediately 456 tc.sendLocoNetMessage(immedPacket); 457 immedPacket = null; 458 } 459 if (m.getOpCode() == LnConstants.OPC_IMM_PACKET && 460 m.getElement(1) == 0x0B && m.getElement(2) == 0x7F) { 461 immedPacket = m; 462 } else { 463 immedPacket = null; 464 } 465 466 // slot specific message? 467 int i = findSlotFromMessage(m); 468 if (i != -1) { 469 getMoreDetailsForSlot(m, i); 470 checkSpecialSlots(m, i); 471 forwardMessageToSlot(m, i); 472 respondToAddrRequest(m, i); 473 programmerOpMessage(m, i); 474 checkLoconetProtocol(m,i); 475 } 476 477 // LONG_ACK response? 478 if (m.getOpCode() == LnConstants.OPC_LONG_ACK) { 479 handleLongAck(m); 480 } 481 482 // see if extended function message 483 if (isExtFunctionMessage(m)) { 484 // yes, get address 485 int addr = getDirectFunctionAddress(m); 486 // find slot(s) containing this address 487 // and route message to them 488 boolean found = false; 489 for (int j = 0; j < 120; j++) { 490 LocoNetSlot slot = slot(j); 491 if (slot == null) { 492 continue; 493 } 494 if ((slot.locoAddr() != addr) 495 || (slot.slotStatus() == LnConstants.LOCO_FREE)) { 496 continue; 497 } 498 // found! 499 slot.functionMessage(getDirectDccPacket(m)); 500 found = true; 501 } 502 if (!found) { 503 // rats! Slot not loaded since program start. Request it be 504 // reloaded for later, but that'll be too late 505 // for this one. 506 LocoNetMessage mo = new LocoNetMessage(4); 507 mo.setOpCode(LnConstants.OPC_LOCO_ADR); // OPC_LOCO_ADR 508 mo.setElement(1, (addr / 128) & 0x7F); 509 mo.setElement(2, addr & 0x7F); 510 tc.sendLocoNetMessage(mo); 511 } 512 } 513 } 514 515 /* 516 * Collect data from specific slots 517 */ 518 void checkSpecialSlots(LocoNetMessage m, int slot) { 519 if (!pmManagerGotReply && slot == 0 && 520 (m.getOpCode() == LnConstants.OPC_EXP_RD_SL_DATA || m.getOpCode() == LnConstants.OPC_SL_RD_DATA)) { 521 pmManagerGotReply = true; 522 if (supportsSlot250) { 523 pollSpecialSlots(); 524 } 525 return; 526 } 527 switch (slot) { 528 case 250: 529 // slot info if we have serial, the serial number in this slot 530 // does not indicate whether in booster or cs mode. 531 if (slot248CommandStationSerial == ((m.getElement(19) & 0x3F) * 128) + m.getElement(18)) { 532 slot250InUseSlots = (m.getElement(4) + ((m.getElement(5) & 0x03) * 128)); 533 slot250IdleSlots = (m.getElement(6) + ((m.getElement(7) & 0x03) * 128)); 534 slot250FreeSlots = (m.getElement(8) + ((m.getElement(9) & 0x03) * 128)); 535 } 536 break; 537 case 248: 538 // Base HW Information 539 // If a CS in CS mode then byte 19 bit 6 in on. else its in 540 // booster mode 541 // The device type is in byte 14 542 if ((m.getElement(19) & 0x40) == 0x40) { 543 slot248CommandStationSerial = ((m.getElement(19) & 0x3F) * 128) + m.getElement(18); 544 slot248CommandStationType = m.getElement(14); 545 } 546 break; 547 default: 548 } 549 } 550 551 /* 552 * If protocol not yet established use slot status for protocol support 553 * System slots , except zero, do not have this info 554 */ 555 void checkLoconetProtocol(LocoNetMessage m, int slot) { 556 // detect protocol if not yet set 557 if (getLoconetProtocol() == LnConstants.LOCONETPROTOCOL_UNKNOWN) { 558 if (_slots[slot].getSlotType() != SlotType.SYSTEM || slot == 0) { 559 if ((m.getOpCode() == LnConstants.OPC_EXP_RD_SL_DATA && m.getNumDataElements() == 21) || 560 (m.getOpCode() == LnConstants.OPC_SL_RD_DATA)) { 561 if ((m.getElement(7) & 0b01000000) == 0b01000000) { 562 log.info("Setting protocol Loconet 2"); 563 setLoconet2Supported(LnConstants.LOCONETPROTOCOL_TWO); 564 } else { 565 log.info("Setting protocol Loconet 1"); 566 setLoconet2Supported(LnConstants.LOCONETPROTOCOL_ONE); 567 } 568 } 569 } 570 } 571 } 572 573 /** 574 * Checks a LocoNet message to see if it encodes a DCC "direct function" packet. 575 * 576 * @param m a LocoNet Message 577 * @return the loco address if the LocoNet message encodes a "direct function" packet, 578 * else returns -1 579 */ 580 int getDirectFunctionAddress(LocoNetMessage m) { 581 if (m.getOpCode() != LnConstants.OPC_IMM_PACKET) { 582 return -1; 583 } 584 if (m.getElement(1) != 0x0B) { 585 return -1; 586 } 587 if (m.getElement(2) != 0x7F) { 588 return -1; 589 } 590 // Direct packet, check length 591 if ((m.getElement(3) & 0x70) < 0x20) { 592 return -1; 593 } 594 int addr = -1; 595 // check long address 596 if ((m.getElement(4) & 0x01) == 0) { //bit 7=0 short 597 addr = (m.getElement(5) & 0xFF); 598 if ((m.getElement(4) & 0x01) != 0) { 599 addr += 128; // and high bit 600 } 601 } else if ((m.getElement(5) & 0x40) == 0x40) { // bit 7 = 1 if bit 6 = 1 then long 602 addr = (m.getElement(5) & 0x3F) * 256 + (m.getElement(6) & 0xFF); 603 if ((m.getElement(4) & 0x02) != 0) { 604 addr += 128; // and high bit 605 } 606 } else { // accessory decoder or extended accessory decoder 607 addr = (m.getElement(5) & 0x3F); 608 } 609 return addr; 610 } 611 612 /** 613 * Extracts a DCC "direct packet" from a LocoNet message, if possible. 614 * <p> 615 * if this is a direct DCC packet, return as one long 616 * else return -1. Packet does not include address bytes. 617 * 618 * @param m a LocoNet message to be inspected 619 * @return an integer containing the bytes of the DCC packet, except the address bytes. 620 */ 621 int getDirectDccPacket(LocoNetMessage m) { 622 if (m.getOpCode() != LnConstants.OPC_IMM_PACKET) { 623 return -1; 624 } 625 if (m.getElement(1) != 0x0B) { 626 return -1; 627 } 628 if (m.getElement(2) != 0x7F) { 629 return -1; 630 } 631 // Direct packet, check length 632 if ((m.getElement(3) & 0x70) < 0x20) { 633 return -1; 634 } 635 int result = 0; 636 int n = (m.getElement(3) & 0xF0) / 16; 637 int start; 638 int high = m.getElement(4); 639 // check long or short address 640 if ((m.getElement(4) & 0x01) == 1 && (m.getElement(5) & 0x40) == 0x40 ) { //long address bit 7 im1 = 1 and bit6 im1 = 1 641 start = 7; 642 high = high >> 2; 643 n = n - 2; 644 } else { //short or accessory 645 start = 6; 646 high = high >> 1; 647 n = n - 1; 648 } 649 // get result 650 for (int i = 0; i < n; i++) { 651 result = result * 256 + (m.getElement(start + i) & 0x7F); 652 if ((high & 0x01) != 0) { 653 result += 128; 654 } 655 high = high >> 1; 656 } 657 return result; 658 } 659 660 /** 661 * Determines if a LocoNet message encodes a direct request to control 662 * DCC functions F9 thru F28 663 * 664 * @param m the LocoNet message to be evaluated 665 * @return true if the message is an external DCC packet request for F9-F28, 666 * else false. 667 */ 668 boolean isExtFunctionMessage(LocoNetMessage m) { 669 int pkt = getDirectDccPacket(m); 670 if (pkt < 0) { 671 return false; 672 } 673 // check F9-12 674 if ((pkt & 0xFFFFFF0) == 0xA0) { 675 return true; 676 } 677 // check F13-28 678 if ((pkt & 0xFFFFFE00) == 0xDE00) { 679 return true; 680 } 681 return false; 682 } 683 684 /** 685 * Extracts the LocoNet slot number from a LocoNet message, if possible. 686 * <p> 687 * Find the slot number that a message references 688 * <p> 689 * This routine only looks for explicit slot references; it does not, for example, 690 * identify a loco address in the message and then work thru the slots to find a 691 * slot which references that loco address. 692 * 693 * @param m LocoNet Message to be inspected 694 * @return an integer representing the slot number encoded in the LocoNet 695 * message, or -1 if the message does not contain a slot reference 696 */ 697 public int findSlotFromMessage(LocoNetMessage m) { 698 699 int i = -1; // find the slot index in the message and store here 700 701 // decode the specific message type and hence slot number 702 switch (m.getOpCode()) { 703 case LnConstants.OPC_WR_SL_DATA: 704 case LnConstants.OPC_SL_RD_DATA: 705 i = m.getElement(2); 706 break; 707 case LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL: 708 if ( m.getElement(1) == LnConstants.RE_IB2_SPECIAL_FUNCS_TOKEN) { 709 i = m.getElement(2); 710 break; 711 } 712 i = ( (m.getElement(1) & 0x03 ) *128) + m.getElement(2); 713 break; 714 case LnConstants.OPC_LOCO_DIRF: 715 case LnConstants.OPC_LOCO_SND: 716 case LnConstants.OPC_LOCO_SPD: 717 case LnConstants.OPC_SLOT_STAT1: 718 case LnConstants.OPC_LINK_SLOTS: 719 case LnConstants.OPC_UNLINK_SLOTS: 720 i = m.getElement(1); 721 break; 722 723 case LnConstants.OPC_MOVE_SLOTS: // No follow on for some moves 724 if (m.getElement(1) != 0) { 725 i = m.getElement(1); 726 return i; 727 } 728 break; 729 case LnConstants.OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR: 730 i = ( (m.getElement(1) & 0x03 ) *128) + m.getElement(2); 731 break; 732 case LnConstants.OPC_EXP_RD_SL_DATA: 733 case LnConstants.OPC_EXP_WR_SL_DATA: 734 //only certain lengths get passed to slot 735 if (m.getElement(1) == 21) { 736 i = ( (m.getElement(2) & 0x03 ) *128) + m.getElement(3); 737 } 738 return i; 739 default: 740 // nothing here for us 741 return i; 742 } 743 // break gets to here 744 return i; 745 } 746 747 /** 748 * Check CV programming LONG_ACK message byte 1 749 * <p> 750 * The following methods are for parsing LACK as response to CV programming. 751 * It is divided into numerous small methods so that each bit can be 752 * overridden for special parsing for individual command station types. 753 * 754 * @param byte1 from the LocoNet message 755 * @return true if byte1 encodes a response to a OPC_SL_WRITE or an 756 * Expanded Slot Write 757 */ 758 protected boolean checkLackByte1(int byte1) { 759 if ((byte1 & 0xEF) == 0x6F) { 760 return true; 761 } else { 762 return false; 763 } 764 } 765 766 /** 767 * Checks the status byte of an OPC_LONG_ACK when performing CV programming 768 * operations. 769 * 770 * @param byte2 status byte 771 * @return True if status byte indicates acceptance of the command, else false. 772 */ 773 protected boolean checkLackTaskAccepted(int byte2) { 774 if (byte2 == 1 // task accepted 775 || byte2 == 0x23 || byte2 == 0x2B || byte2 == 0x6B // added as DCS51 fix 776 // deliberately ignoring 0x7F varient, see okToIgnoreLack 777 ) { 778 return true; 779 } else { 780 return false; 781 } 782 } 783 784 /** 785 * Checks the OPC_LONG_ACK status byte response to a programming 786 * operation. 787 * 788 * @param byte2 from the OPC_LONG_ACK message 789 * @return true if the programmer returned "busy" else false 790 */ 791 protected boolean checkLackProgrammerBusy(int byte2) { 792 if (byte2 == 0) { 793 return true; 794 } else { 795 return false; 796 } 797 } 798 799 /** 800 * Checks the OPC_LONG_ACK status byte response to a programming 801 * operation to see if the programmer accepted the operation "blindly". 802 * 803 * @param byte2 from the OPC_LONG_ACK message 804 * @return true if the programmer indicated a "blind operation", else false 805 */ 806 protected boolean checkLackAcceptedBlind(int byte2) { 807 if (byte2 == 0x40) { 808 return true; 809 } else { 810 return false; 811 } 812 } 813 814 /** 815 * Some LACKs with specific OPC_LONG_ACK status byte values can just be ignored. 816 * 817 * @param byte2 from the OPC_LONG_ACK message 818 * @return true if this form of LACK can be ignored without a warning message 819 */ 820 protected boolean okToIgnoreLack(int byte2) { 821 if (byte2 == 0x7F ) { 822 return true; 823 } else { 824 return false; 825 } 826 } 827 828 private boolean acceptAnyLACK = false; 829 /** 830 * Indicate that the command station LONG_ACK response details can be ignored 831 * for this operation. Typically this is used when accessing Loconet-attached boards. 832 */ 833 public final void setAcceptAnyLACK() { 834 acceptAnyLACK = true; 835 } 836 837 /** 838 * Handles OPC_LONG_ACK replies to programming slot operations. 839 * 840 * LACK 0x6D00 which requests a retransmission is handled 841 * separately in the message(..) method. 842 * 843 * @param m LocoNet message being analyzed 844 */ 845 protected void handleLongAck(LocoNetMessage m) { 846 // handle if reply to slot. There's no slot number in the LACK, unfortunately. 847 // If this is a LACK to a Slot op, and progState is command pending, 848 // assume its for us... 849 log.debug("LACK in state {} message: {}", progState, m.toString()); // NOI18N 850 if (checkLackByte1(m.getElement(1)) && progState == 1) { 851 // in programming state 852 if (acceptAnyLACK) { 853 log.debug("accepted LACK {} via acceptAnyLACK", m.getElement(2)); 854 // Any form of LACK response from CS is accepted here. 855 // Loconet-attached decoders (LOCONETOPSBOARD) receive the program commands 856 // directly via loconet and respond as required without needing any CS action, 857 // making the details of the LACK response irrelevant. 858 if (_progRead || _progConfirm) { 859 // move to commandExecuting state 860 startShortTimer(); 861 progState = 2; 862 } else { 863 // move to not programming state 864 progState = 0; 865 stopTimer(); 866 // allow the target device time to execute then notify ProgListener 867 notifyProgListenerEndAfterDelay(); 868 } 869 acceptAnyLACK = false; // restore normal state for next operation 870 } 871 // check status byte 872 else if (checkLackTaskAccepted(m.getElement(2))) { // task accepted 873 // 'not implemented' (op on main) 874 // but BDL16 and other devices can eventually reply, so 875 // move to commandExecuting state 876 log.debug("checkLackTaskAccepted accepted, next state 2"); // NOI18N 877 if ((_progRead || _progConfirm) && mServiceMode) { 878 startLongTimer(); 879 } else { 880 startShortTimer(); 881 } 882 progState = 2; 883 } else if (checkLackProgrammerBusy(m.getElement(2))) { // task aborted as busy 884 // move to not programming state 885 progState = 0; 886 // notify user ProgListener 887 stopTimer(); 888 notifyProgListenerLack(jmri.ProgListener.ProgrammerBusy); 889 } else if (checkLackAcceptedBlind(m.getElement(2))) { // task accepted blind 890 if ((_progRead || _progConfirm) && !mServiceMode) { // incorrect Reserved OpSw setting can cause this response to OpsMode Read 891 // just treat it as a normal OpsMode Read response 892 // move to commandExecuting state 893 log.debug("LACK accepted (ignoring incorrect OpSw), next state 2"); // NOI18N 894 startShortTimer(); 895 progState = 2; 896 } else { 897 // move to not programming state 898 progState = 0; 899 stopTimer(); 900 // allow command station time to execute then notify ProgListener 901 notifyProgListenerEndAfterDelay(); 902 } 903 } else if (okToIgnoreLack(m.getElement(2))) { 904 // this form of LACK can be silently ignored 905 log.debug("Ignoring LACK with {}", m.getElement(2)); 906 } else { // not sure how to cope, so complain 907 log.warn("unexpected LACK reply code {}", m.getElement(2)); // NOI18N 908 // move to not programming state 909 progState = 0; 910 // notify user ProgListener 911 stopTimer(); 912 notifyProgListenerLack(jmri.ProgListener.UnknownError); 913 } 914 } 915 } 916 917 /** 918 * Internal method to notify ProgListener after a short delay that the operation is complete. 919 * The delay ensures that the target device has completed the operation prior to the notification. 920 */ 921 protected void notifyProgListenerEndAfterDelay() { 922 javax.swing.Timer timer = new javax.swing.Timer(postProgDelay, new java.awt.event.ActionListener() { 923 @Override 924 public void actionPerformed(java.awt.event.ActionEvent e) { 925 notifyProgListenerEnd(-1, 0); // no value (e.g. -1), no error status (e.g.0) 926 } 927 }); 928 timer.stop(); 929 timer.setInitialDelay(postProgDelay); 930 timer.setRepeats(false); 931 timer.start(); 932 } 933 934 /** 935 * Forward Slot-related LocoNet message to the slot. 936 * 937 * @param m a LocoNet message targeted at a slot 938 * @param i the slot number to which the LocoNet message is targeted. 939 */ 940 public void forwardMessageToSlot(LocoNetMessage m, int i) { 941 942 // if here, i holds the slot number, and we expect to be able to parse 943 // and have the slot handle the message 944 if (i >= _slots.length || i < 0) { 945 log.error("Received slot number {} is greater than array length {} Message was {}", // NOI18N 946 i, _slots.length, m.toString()); // NOI18N 947 return; // prevents array index out-of-bounds when referencing _slots[i] 948 } 949 950 if ( !validateSlotNumber(i)) { 951 log.warn("Received slot number {} is not in the slot map, have you defined the wrong cammand station type? Message was {}", 952 i, m.toString()); 953 } 954 955 try { 956 _slots[i].setSlot(m); 957 } catch (LocoNetException e) { 958 // must not have been interesting, or at least routed right 959 log.error("slot rejected LocoNetMessage {}", m); // NOI18N 960 return; 961 } catch (Exception e) { 962 log.error("Unexplained error _slots[{}].setSlot({})",i,m,e); 963 return; 964 } 965 // notify listeners that slot may have changed 966 notify(_slots[i]); 967 } 968 969 /** 970 * A sort of slot listener which handles loco address requests 971 * 972 * @param m a LocoNet message 973 * @param i the slot to which it is directed 974 */ 975 protected void respondToAddrRequest(LocoNetMessage m, int i) { 976 // is called any time a LocoNet message is received. Note that we do _NOT_ know why a given message happens! 977 978 // if this is OPC_SL_RD_DATA 979 if (m.getOpCode() == LnConstants.OPC_SL_RD_DATA || m.getOpCode() == LnConstants.OPC_EXP_RD_SL_DATA ) { 980 // yes, see if request exists 981 // note that the appropriate _slots[] entry has already been updated 982 // to reflect the content of the LocoNet message, so _slots[i] 983 // has the locomotive address of this request 984 int addr = _slots[i].locoAddr(); 985 log.debug("LOCO_ADR resp is slot {} for addr {}", i, addr); // NOI18N 986 SlotListener l = mLocoAddrHash.get(Integer.valueOf(addr)); 987 if (l != null) { 988 // only notify once per request 989 mLocoAddrHash.remove(Integer.valueOf(addr)); 990 // and send the notification 991 log.debug("notify listener"); // NOI18N 992 l.notifyChangedSlot(_slots[i]); 993 } else { 994 log.debug("no request for addr {}", addr); // NOI18N 995 } 996 } 997 } 998 999 /** 1000 * If it is a slot being sent COMMON, 1001 * after a delay, get the new status of the slot 1002 * If it is a true slot move, not dispatch or null 1003 * after a delay, get the new status of the from slot, which varies by CS. 1004 * the to slot should come in the reply. 1005 * @param m a LocoNet message 1006 * @param i the slot to which it is directed 1007 */ 1008 protected void getMoreDetailsForSlot(LocoNetMessage m, int i) { 1009 // is called any time a LocoNet message is received. 1010 // sets up delayed slot read to update our effected slots to match the CS 1011 if (m.getOpCode() == LnConstants.OPC_SLOT_STAT1 && 1012 ((m.getElement(2) & LnConstants.LOCOSTAT_MASK) == LnConstants.LOCO_COMMON ) ) { 1013 // Changing a slot to common. Depending on a CS and its OpSw, and throttle speed 1014 // it could have its status changed a number of ways. 1015 sendReadSlotDelayed(i,100); 1016 } else if (m.getOpCode() == LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL) { 1017 boolean isSettingStatus = ((m.getElement(3) & 0b01110000) == 0b01100000); 1018 if (isSettingStatus) { 1019 int stat = m.getElement(4); 1020 if ((stat & LnConstants.LOCOSTAT_MASK) == LnConstants.LOCO_COMMON) { 1021 sendReadSlotDelayed(i,100); 1022 } 1023 } 1024 boolean isUnconsisting = ((m.getElement(3) & 0b01110000) == 0b01010000); 1025 if (isUnconsisting) { 1026 // read lead slot 1027 sendReadSlotDelayed(slot(i).getLeadSlot(),100); 1028 } 1029 boolean isConsisting = ((m.getElement(3) & 0b01110000) == 0b01000000); 1030 if (isConsisting) { 1031 // read 2nd slot 1032 int slotTwo = ((m.getElement(3) & 0b00000011) * 128 )+ m.getElement(4); 1033 sendReadSlotDelayed(slotTwo,100); 1034 } 1035 } else if (m.getOpCode() == LnConstants.OPC_MOVE_SLOTS) { 1036 // if a true move get the new from slot status 1037 // the to slot status is sent in the reply, but not if dispatch or null 1038 // as those return slot info. 1039 int slotTwo; 1040 slotTwo = m.getElement(2); 1041 if (i != 0 && slotTwo != 0 && i != slotTwo) { 1042 sendReadSlotDelayed(i,100); 1043 } 1044 } else if (m.getOpCode() == LnConstants.OPC_LINK_SLOTS || 1045 m.getOpCode() == LnConstants.OPC_UNLINK_SLOTS ) { 1046 // unlink and link return first slot by not second (to or from) 1047 // the to slot status is sent in the reply 1048 int slotTwo; 1049 slotTwo = m.getElement(2); 1050 if (i != 0 && slotTwo != 0) { 1051 sendReadSlotDelayed(slotTwo,100); 1052 } 1053 } 1054 } 1055 1056 /** 1057 * Schedule a delayed slot read. 1058 * @param slotNo - the slot. 1059 * @param delay - delay in msecs. 1060 */ 1061 protected void sendReadSlotDelayed(int slotNo, long delay) { 1062 java.util.TimerTask meterTask = new java.util.TimerTask() { 1063 int slotNumber = slotNo; 1064 1065 @Override 1066 public void run() { 1067 try { 1068 sendReadSlot(slotNumber); 1069 } catch (Exception e) { 1070 log.error("Exception occurred sendReadSlotDelayed:", e); 1071 } 1072 } 1073 }; 1074 jmri.util.TimerUtil.schedule(meterTask, delay); 1075 } 1076 1077 /** 1078 * Handle LocoNet messages related to CV programming operations 1079 * 1080 * @param m a LocoNet message 1081 * @param i the slot toward which the message is destined 1082 */ 1083 protected void programmerOpMessage(LocoNetMessage m, int i) { 1084 1085 // start checking for programming operations in slot 124 1086 if (i == 124) { 1087 // here its an operation on the programmer slot 1088 log.debug("Prog Message {} for slot 124 in state {}", // NOI18N 1089 m.getOpCodeHex(), progState); // NOI18N 1090 switch (progState) { 1091 case 0: // notProgramming 1092 break; 1093 case 1: // commandPending: waiting for an (optional) LACK 1094 case 2: // commandExecuting 1095 // waiting for slot read, is it present? 1096 if (m.getOpCode() == LnConstants.OPC_SL_RD_DATA) { 1097 log.debug(" was OPC_SL_RD_DATA"); // NOI18N 1098 // yes, this is the end 1099 // move to not programming state 1100 stopTimer(); 1101 progState = 0; 1102 1103 // parse out value returned 1104 int value = -1; 1105 int status = 0; 1106 if (_progConfirm) { 1107 // read command, get value; check if OK 1108 value = _slots[i].cvval(); 1109 if (value != _confirmVal) { 1110 status = status | jmri.ProgListener.ConfirmFailed; 1111 } 1112 } 1113 if (_progRead) { 1114 // read command, get value 1115 value = _slots[i].cvval(); 1116 } 1117 // parse out status 1118 if ((_slots[i].pcmd() & LnConstants.PSTAT_NO_DECODER) != 0) { 1119 status = (status | jmri.ProgListener.NoLocoDetected); 1120 } 1121 if ((_slots[i].pcmd() & LnConstants.PSTAT_WRITE_FAIL) != 0) { 1122 status = (status | jmri.ProgListener.NoAck); 1123 } 1124 if ((_slots[i].pcmd() & LnConstants.PSTAT_READ_FAIL) != 0) { 1125 status = (status | jmri.ProgListener.NoAck); 1126 } 1127 if ((_slots[i].pcmd() & LnConstants.PSTAT_USER_ABORTED) != 0) { 1128 status = (status | jmri.ProgListener.UserAborted); 1129 } 1130 1131 // and send the notification 1132 notifyProgListenerEnd(value, status); 1133 } 1134 break; 1135 default: // error! 1136 log.error("unexpected programming state {}", progState); // NOI18N 1137 break; 1138 } 1139 } 1140 } 1141 1142 ProgrammingMode csOpSwProgrammingMode = new ProgrammingMode( 1143 "LOCONETCSOPSWMODE", 1144 Bundle.getMessage("LOCONETCSOPSWMODE")); 1145 1146 // members for handling the programmer interface 1147 1148 /** 1149 * Return a list of ProgrammingModes supported by this interface 1150 * Types implemented here. 1151 * 1152 * @return a List of ProgrammingMode objects containing the supported 1153 * programming modes. 1154 */ 1155 1156 @Override 1157 @Nonnull 1158 public List<ProgrammingMode> getSupportedModes() { 1159 List<ProgrammingMode> ret = new ArrayList<>(); 1160 ret.add(ProgrammingMode.DIRECTBYTEMODE); 1161 ret.add(ProgrammingMode.PAGEMODE); 1162 ret.add(ProgrammingMode.REGISTERMODE); 1163 ret.add(ProgrammingMode.ADDRESSMODE); 1164 ret.add(csOpSwProgrammingMode); 1165 1166 return ret; 1167 } 1168 1169 /** 1170 * Remember whether the attached command station needs a sequence sent after 1171 * programming. The default operation is implemented in doEndOfProgramming 1172 * and turns power back on by sending a GPON message. 1173 */ 1174 private boolean mProgEndSequence = false; 1175 1176 /** 1177 * Remember whether the attached command station can read from Decoders. 1178 */ 1179 private boolean mCanRead = true; 1180 1181 /** 1182 * Determine whether this Programmer implementation is capable of reading 1183 * decoder contents. This is entirely determined by the attached command 1184 * station, not the code here, so it refers to the mCanRead member variable 1185 * which is recording the known state of that. 1186 * 1187 * @return True if reads are possible 1188 */ 1189 @Override 1190 public boolean getCanRead() { 1191 return mCanRead; 1192 } 1193 1194 /** 1195 * Return the write confirm mode implemented by the command station. 1196 * <p> 1197 * Service mode always checks for DecoderReply. (The DCS240 also seems to do 1198 * ReadAfterWrite, but that's not fully understood yet) 1199 * 1200 * @param addr This implementation ignores this parameter 1201 * @return the supported WriteConfirmMode 1202 */ 1203 @Nonnull 1204 @Override 1205 public Programmer.WriteConfirmMode getWriteConfirmMode(String addr) { return WriteConfirmMode.DecoderReply; } 1206 1207 /** 1208 * Set the command station type to one of the known types in the 1209 * {@link LnCommandStationType} enum. 1210 * 1211 * @param value contains the command station type 1212 */ 1213 public void setCommandStationType(LnCommandStationType value) { 1214 commandStationType = value; 1215 mCanRead = value.getCanRead(); 1216 mProgEndSequence = value.getProgPowersOff(); 1217 slotMap = commandStationType.getSlotMap(); 1218 supportsSlot250 = value.getSupportsSlot250(); 1219 1220 loadSlots(false); 1221 1222 // We will scan the slot table every 0.3 s for in-use slots that are stale 1223 final int slotScanDelay = 300; // Must be short enough that 128 can be scanned in 90 seconds, see checkStaleSlots() 1224 staleSlotCheckTimer = new javax.swing.Timer(slotScanDelay, new java.awt.event.ActionListener() { 1225 @Override 1226 public void actionPerformed(java.awt.event.ActionEvent e) { 1227 checkStaleSlots(); 1228 } 1229 }); 1230 1231 staleSlotCheckTimer.setRepeats(true); 1232 staleSlotCheckTimer.setInitialDelay(30000); // wait a bit at startup 1233 staleSlotCheckTimer.start(); 1234 1235 } 1236 1237 LocoNetThrottledTransmitter throttledTransmitter = null; 1238 boolean mTurnoutNoRetry = false; 1239 1240 /** 1241 * Provide a ThrottledTransmitter for sending immediate packets. 1242 * 1243 * @param value contains a LocoNetThrottledTransmitter object 1244 * @param m contains a boolean value indicating mTurnoutNoRetry 1245 */ 1246 public void setThrottledTransmitter(LocoNetThrottledTransmitter value, boolean m) { 1247 throttledTransmitter = value; 1248 mTurnoutNoRetry = m; 1249 } 1250 1251 /** 1252 * Get the command station type. 1253 * 1254 * @return an LnCommandStationType object 1255 */ 1256 public LnCommandStationType getCommandStationType() { 1257 return commandStationType; 1258 } 1259 1260 protected LnCommandStationType commandStationType = null; 1261 1262 /** 1263 * Internal routine to handle a timeout. 1264 */ 1265 @Override 1266 synchronized protected void timeout() { 1267 log.debug("timeout fires in state {}", progState); // NOI18N 1268 1269 if (progState != 0) { 1270 // we're programming, time to stop 1271 log.debug("timeout while programming"); // NOI18N 1272 1273 // perhaps no communications present? Fail back to end of programming 1274 progState = 0; 1275 // and send the notification; error code depends on state 1276 if (progState == 2 && !mServiceMode) { // ops mode command executing, 1277 // so did talk to command station at first 1278 notifyProgListenerEnd(_slots[124].cvval(), jmri.ProgListener.NoAck); 1279 } else { 1280 // all others 1281 notifyProgListenerEnd(_slots[124].cvval(), jmri.ProgListener.FailedTimeout); 1282 // might be leaving power off, but that's currently up to user to fix 1283 } 1284 acceptAnyLACK = false; // ensure cleared if timed out without getting a LACK 1285 } 1286 } 1287 1288 int progState = 0; 1289 // 1 is commandPending 1290 // 2 is commandExecuting 1291 // 0 is notProgramming 1292 boolean _progRead = false; 1293 boolean _progConfirm = false; 1294 int _confirmVal; 1295 boolean mServiceMode = true; 1296 1297 /** 1298 * Write a CV via Ops Mode programming. 1299 * 1300 * @param CVname CV number 1301 * @param val value to write to the CV 1302 * @param p programmer 1303 * @param addr address of decoder 1304 * @param longAddr true if the address is a long address 1305 * @throws jmri.ProgrammerException if an unsupported programming mode is exercised 1306 */ 1307 public void writeCVOpsMode(String CVname, int val, jmri.ProgListener p, 1308 int addr, boolean longAddr) throws jmri.ProgrammerException { 1309 final int CV = Integer.parseInt(CVname); 1310 lopsa = addr & 0x7f; 1311 hopsa = (addr / 128) & 0x7f; 1312 mServiceMode = false; 1313 doWrite(CV, val, p, 0x67); // ops mode byte write, with feedback 1314 } 1315 1316 /** 1317 * Write a CV via the Service Mode programmer. 1318 * 1319 * @param cvNum CV id as String 1320 * @param val value to write to the CV 1321 * @param p programmer 1322 * @throws jmri.ProgrammerException if an unsupported programming mode is exercised 1323 */ 1324 @Override 1325 public void writeCV(String cvNum, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 1326 log.debug("writeCV(string): cvNum={}, value={}", cvNum, val); 1327 if (getMode().equals(csOpSwProgrammingMode)) { 1328 log.debug("cvOpSw mode write!"); 1329 // handle Command Station OpSw programming here 1330 String[] parts = cvNum.split("\\."); 1331 if ((parts[0].equals("csOpSw")) && (parts.length==2)) { 1332 if (csOpSwAccessor == null) { 1333 csOpSwAccessor = new CsOpSwAccess(adaptermemo, p); 1334 } else { 1335 csOpSwAccessor.setProgrammerListener(p); 1336 } 1337 // perform the CsOpSwMode read access 1338 log.debug("going to try the opsw access"); 1339 csOpSwAccessor.writeCsOpSw(cvNum, val, p); 1340 return; 1341 1342 } else { 1343 log.warn("rejecting the cs opsw access account unsupported CV name format"); 1344 // unsupported format in "cv" name. Signal an error 1345 notifyProgListenerEnd(p, 1, ProgListener.SequenceError); 1346 return; 1347 1348 } 1349 } else { 1350 // regular CV case 1351 int CV = Integer.parseInt(cvNum); 1352 1353 lopsa = 0; 1354 hopsa = 0; 1355 mServiceMode = true; 1356 // parse the programming command 1357 int pcmd = 0x43; // LPE implies 0x40, but 0x43 is observed 1358 if (getMode().equals(ProgrammingMode.PAGEMODE)) { 1359 pcmd = pcmd | 0x20; 1360 } else if (getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) { 1361 pcmd = pcmd | 0x28; 1362 } else if (getMode().equals(ProgrammingMode.REGISTERMODE) 1363 || getMode().equals(ProgrammingMode.ADDRESSMODE)) { 1364 pcmd = pcmd | 0x10; 1365 } else { 1366 throw new jmri.ProgrammerException("mode not supported"); // NOI18N 1367 } 1368 1369 doWrite(CV, val, p, pcmd); 1370 } 1371 } 1372 1373 /** 1374 * Perform a write a CV via the Service Mode programmer. 1375 * 1376 * @param CV CV number 1377 * @param val value to write to the CV 1378 * @param p programmer 1379 * @param pcmd programming command 1380 * @throws jmri.ProgrammerException if an unsupported programming mode is exercised 1381 */ 1382 public void doWrite(int CV, int val, jmri.ProgListener p, int pcmd) throws jmri.ProgrammerException { 1383 log.debug("writeCV: {}", CV); // NOI18N 1384 1385 stopEndOfProgrammingTimer(); // still programming, so no longer waiting for power off 1386 1387 useProgrammer(p); 1388 _progRead = false; 1389 _progConfirm = false; 1390 // set commandPending state 1391 progState = 1; 1392 1393 // format and send message 1394 startShortTimer(); 1395 tc.sendLocoNetMessage(progTaskStart(pcmd, val, CV, true)); 1396 } 1397 1398 /** 1399 * Confirm a CV via the OpsMode programmer. 1400 * 1401 * @param CVname a String containing the CV name 1402 * @param val expected value 1403 * @param p programmer 1404 * @param addr address of loco to write to 1405 * @param longAddr true if addr is a long address 1406 * @throws jmri.ProgrammerException if an unsupported programming mode is exercised 1407 */ 1408 public void confirmCVOpsMode(String CVname, int val, jmri.ProgListener p, 1409 int addr, boolean longAddr) throws jmri.ProgrammerException { 1410 int CV = Integer.parseInt(CVname); 1411 lopsa = addr & 0x7f; 1412 hopsa = (addr / 128) & 0x7f; 1413 mServiceMode = false; 1414 doConfirm(CV, val, p, 0x2F); // although LPE implies 0x2C, 0x2F is observed 1415 } 1416 1417 /** 1418 * Confirm a CV via the Service Mode programmer. 1419 * 1420 * @param CVname a String containing the CV name 1421 * @param val expected value 1422 * @param p programmer 1423 * @throws jmri.ProgrammerException if an unsupported programming mode is exercised 1424 */ 1425 @Override 1426 public void confirmCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 1427 int CV = Integer.parseInt(CVname); 1428 lopsa = 0; 1429 hopsa = 0; 1430 mServiceMode = true; 1431 if (getMode().equals(csOpSwProgrammingMode)) { 1432 log.debug("cvOpSw mode!"); 1433 //handle Command Station OpSw programming here 1434 String[] parts = CVname.split("\\."); 1435 if ((parts[0].equals("csOpSw")) && (parts.length==2)) { 1436 if (csOpSwAccessor == null) { 1437 csOpSwAccessor = new CsOpSwAccess(adaptermemo, p); 1438 } else { 1439 csOpSwAccessor.setProgrammerListener(p); 1440 } 1441 // perform the CsOpSwMode read access 1442 log.debug("going to try the opsw access"); 1443 csOpSwAccessor.readCsOpSw(CVname, p); 1444 return; 1445 } else { 1446 log.warn("rejecting the cs opsw access account unsupported CV name format"); 1447 // unsupported format in "cv" name. Signal an error. 1448 notifyProgListenerEnd(p, 1, ProgListener.SequenceError); 1449 return; 1450 } 1451 } 1452 1453 // parse the programming command 1454 int pcmd = 0x03; // LPE implies 0x00, but 0x03 is observed 1455 if (getMode().equals(ProgrammingMode.PAGEMODE)) { 1456 pcmd = pcmd | 0x20; 1457 } else if (getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) { 1458 pcmd = pcmd | 0x28; 1459 } else if (getMode().equals(ProgrammingMode.REGISTERMODE) 1460 || getMode().equals(ProgrammingMode.ADDRESSMODE)) { 1461 pcmd = pcmd | 0x10; 1462 } else { 1463 throw new jmri.ProgrammerException("mode not supported"); // NOI18N 1464 } 1465 1466 doConfirm(CV, val, p, pcmd); 1467 } 1468 1469 /** 1470 * Perform a confirm operation of a CV via the Service Mode programmer. 1471 * 1472 * @param CV the CV number 1473 * @param val expected value 1474 * @param p programmer 1475 * @param pcmd programming command 1476 * @throws jmri.ProgrammerException if an unsupported programming mode is exercised 1477 */ 1478 public void doConfirm(int CV, int val, ProgListener p, 1479 int pcmd) throws jmri.ProgrammerException { 1480 1481 log.debug("confirmCV: {}, val: {}", CV, val); // NOI18N 1482 1483 stopEndOfProgrammingTimer(); // still programming, so no longer waiting for power off 1484 1485 useProgrammer(p); 1486 _progRead = false; 1487 _progConfirm = true; 1488 _confirmVal = val; 1489 1490 // set commandPending state 1491 progState = 1; 1492 1493 // format and send message 1494 startShortTimer(); 1495 tc.sendLocoNetMessage(progTaskStart(pcmd, val, CV, false)); 1496 } 1497 1498 int hopsa; // high address for CV read/write 1499 int lopsa; // low address for CV read/write 1500 1501 CsOpSwAccess csOpSwAccessor; 1502 1503 @Override 1504 public void readCV(String cvNum, jmri.ProgListener p) throws jmri.ProgrammerException { 1505 readCV(cvNum, p, 0); 1506 } 1507 1508 /** 1509 * Read a CV via the OpsMode programmer. 1510 * 1511 * @param cvNum a String containing the CV number 1512 * @param p programmer 1513 * @param startVal initial "guess" for value of CV, can improve speed if used 1514 * @throws jmri.ProgrammerException if an unsupported programming mode is exercised 1515 */ 1516 @Override 1517 public void readCV(String cvNum, jmri.ProgListener p, int startVal) throws jmri.ProgrammerException { 1518 log.debug("readCV(string): cvNum={}, startVal={}, mode={}", cvNum, startVal, getMode()); 1519 if (getMode().equals(csOpSwProgrammingMode)) { 1520 log.debug("cvOpSw mode!"); 1521 //handle Command Station OpSw programming here 1522 String[] parts = cvNum.split("\\."); 1523 if ((parts[0].equals("csOpSw")) && (parts.length==2)) { 1524 if (csOpSwAccessor == null) { 1525 csOpSwAccessor = new CsOpSwAccess(adaptermemo, p); 1526 } else { 1527 csOpSwAccessor.setProgrammerListener(p); 1528 } 1529 // perform the CsOpSwMode read access 1530 log.debug("going to try the opsw access"); 1531 csOpSwAccessor.readCsOpSw(cvNum, p); 1532 return; 1533 1534 } else { 1535 log.warn("rejecting the cs opsw access account unsupported CV name format"); 1536 // unsupported format in "cv" name. Signal an error. 1537 notifyProgListenerEnd(p, 1, ProgListener.SequenceError); 1538 return; 1539 1540 } 1541 } else { 1542 // regular integer address for DCC form 1543 int CV = Integer.parseInt(cvNum); 1544 1545 lopsa = 0; 1546 hopsa = 0; 1547 mServiceMode = true; 1548 // parse the programming command 1549 int pcmd = 0x03; // LPE implies 0x00, but 0x03 is observed 1550 if (getMode().equals(ProgrammingMode.PAGEMODE)) { 1551 pcmd = pcmd | 0x20; 1552 } else if (getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) { 1553 pcmd = pcmd | 0x28; 1554 } else if (getMode().equals(ProgrammingMode.REGISTERMODE) 1555 || getMode().equals(ProgrammingMode.ADDRESSMODE)) { 1556 pcmd = pcmd | 0x10; 1557 } else { 1558 throw new jmri.ProgrammerException("mode not supported"); // NOI18N 1559 } 1560 1561 doRead(CV, p, pcmd, startVal); 1562 1563 } 1564 } 1565 1566 /** 1567 * Invoked by LnOpsModeProgrammer to start an ops-mode read operation. 1568 * 1569 * @param CVname Which CV to read 1570 * @param p Who to notify on complete 1571 * @param addr Address of the locomotive 1572 * @param longAddr true if a long address, false if short address 1573 * @throws jmri.ProgrammerException if an unsupported programming mode is exercised 1574 */ 1575 public void readCVOpsMode(String CVname, jmri.ProgListener p, int addr, boolean longAddr) throws jmri.ProgrammerException { 1576 final int CV = Integer.parseInt(CVname); 1577 lopsa = addr & 0x7f; 1578 hopsa = (addr / 128) & 0x7f; 1579 mServiceMode = false; 1580 doRead(CV, p, 0x2F, 0); // although LPE implies 0x2C, 0x2F is observed 1581 } 1582 1583 /** 1584 * Perform a CV Read. 1585 * 1586 * @param CV the CV number 1587 * @param p programmer 1588 * @param progByte programming command 1589 * @param startVal initial "guess" for value of CV, can improve speed if used 1590 * @throws jmri.ProgrammerException if an unsupported programming mode is exercised 1591 */ 1592 void doRead(int CV, jmri.ProgListener p, int progByte, int startVal) throws jmri.ProgrammerException { 1593 1594 log.debug("readCV: {} with startVal: {}", CV, startVal); // NOI18N 1595 1596 stopEndOfProgrammingTimer(); // still programming, so no longer waiting for power off 1597 1598 useProgrammer(p); 1599 _progRead = true; 1600 _progConfirm = false; 1601 // set commandPending state 1602 progState = 1; 1603 1604 // format and send message 1605 startShortTimer(); 1606// tc.sendLocoNetMessage(progTaskStart(progByte, 0, CV, false)); 1607 tc.sendLocoNetMessage(progTaskStart(progByte, startVal, CV, false)); 1608 } 1609 1610 private jmri.ProgListener _usingProgrammer = null; 1611 1612 // internal method to remember who's using the programmer 1613 protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException { 1614 // test for only one! 1615 if (_usingProgrammer != null && _usingProgrammer != p) { 1616 1617 log.info("programmer already in use by {}", _usingProgrammer); // NOI18N 1618 1619 throw new jmri.ProgrammerException("programmer in use"); // NOI18N 1620 } else { 1621 _usingProgrammer = p; 1622 return; 1623 } 1624 } 1625 1626 /** 1627 * Internal method to create the LocoNetMessage for programmer task start. 1628 * 1629 * @param pcmd programmer command 1630 * @param val value to be used 1631 * @param cvnum CV number 1632 * @param write true if write, else false 1633 * @return a LocoNet message containing a programming task start operation 1634 */ 1635 protected LocoNetMessage progTaskStart(int pcmd, int val, int cvnum, boolean write) { 1636 1637 int addr = cvnum - 1; // cvnum is in human readable form; addr is what's sent over LocoNet 1638 1639 LocoNetMessage m = new LocoNetMessage(14); 1640 1641 m.setOpCode(LnConstants.OPC_WR_SL_DATA); 1642 m.setElement(1, 0x0E); 1643 m.setElement(2, LnConstants.PRG_SLOT); 1644 1645 m.setElement(3, pcmd); 1646 1647 // set zero, then HOPSA, LOPSA, TRK 1648 m.setElement(4, 0); 1649 m.setElement(5, hopsa); 1650 m.setElement(6, lopsa); 1651 m.setElement(7, 0); // TRK was 0, then 7 for PR2, now back to zero 1652 1653 // store address in CVH, CVL. Note CVH format is truely wierd... 1654 m.setElement(8, ((addr & 0x300)>>4) | ((addr & 0x80) >> 7) | ((val & 0x80) >> 6)); 1655 m.setElement(9, addr & 0x7F); 1656 1657 // store low bits of CV value 1658 m.setElement(10, val & 0x7F); 1659 1660 // throttle ID 1661 m.setElement(11, 0x7F); 1662 m.setElement(12, 0x7F); 1663 return m; 1664 } 1665 1666 /** 1667 * Internal method to notify of the final result. 1668 * 1669 * @param value The cv value to be returned 1670 * @param status The error code, if any 1671 */ 1672 protected void notifyProgListenerEnd(int value, int status) { 1673 log.debug(" notifyProgListenerEnd with {}, {} and _usingProgrammer = {}", value, status, _usingProgrammer); // NOI18N 1674 // (re)start power timer 1675 restartEndOfProgrammingTimer(); 1676 // and send the reply 1677 ProgListener p = _usingProgrammer; 1678 _usingProgrammer = null; 1679 if (p != null) { 1680 sendProgrammingReply(p, value, status); 1681 } 1682 } 1683 1684 /** 1685 * Internal method to notify of the LACK result. This is a separate routine 1686 * from nPLRead in case we need to handle something later. 1687 * 1688 * @param status The error code, if any 1689 */ 1690 protected void notifyProgListenerLack(int status) { 1691 // (re)start power timer 1692 restartEndOfProgrammingTimer(); 1693 // and send the reply 1694 sendProgrammingReply(_usingProgrammer, -1, status); 1695 _usingProgrammer = null; 1696 } 1697 1698 /** 1699 * Internal routine to forward a programming reply. This is delayed to 1700 * prevent overruns of the command station. 1701 * 1702 * @param p a ProgListener object 1703 * @param value the value to return 1704 * @param status The error code, if any 1705 */ 1706 protected void sendProgrammingReply(ProgListener p, int value, int status) { 1707 int delay = serviceModeReplyDelay; // value in service mode 1708 if (!mServiceMode) { 1709 delay = opsModeReplyDelay; // value in ops mode 1710 } 1711 1712 // delay and run on GUI thread 1713 javax.swing.Timer timer = new javax.swing.Timer(delay, new java.awt.event.ActionListener() { 1714 @Override 1715 public void actionPerformed(java.awt.event.ActionEvent e) { 1716 notifyProgListenerEnd(p, value, status); 1717 } 1718 }); 1719 timer.setInitialDelay(delay); 1720 timer.setRepeats(false); 1721 timer.start(); 1722 } 1723 1724 /** 1725 * Internal routine to stop end-of-programming timer, as another programming 1726 * operation has happened. 1727 */ 1728 protected void stopEndOfProgrammingTimer() { 1729 if (mPowerTimer != null) { 1730 mPowerTimer.stop(); 1731 } 1732 } 1733 1734 /** 1735 * Internal routine to handle timer restart if needed to restore power. This 1736 * is only needed in service mode. 1737 */ 1738 protected void restartEndOfProgrammingTimer() { 1739 final int delay = 10000; 1740 if (mProgEndSequence) { 1741 if (mPowerTimer == null) { 1742 mPowerTimer = new javax.swing.Timer(delay, new java.awt.event.ActionListener() { 1743 @Override 1744 public void actionPerformed(java.awt.event.ActionEvent e) { 1745 doEndOfProgramming(); 1746 } 1747 }); 1748 } 1749 mPowerTimer.stop(); 1750 mPowerTimer.setInitialDelay(delay); 1751 mPowerTimer.setRepeats(false); 1752 mPowerTimer.start(); 1753 } 1754 } 1755 1756 /** 1757 * Internal routine to handle a programming timeout by turning power off. 1758 */ 1759 synchronized protected void doEndOfProgramming() { 1760 if (progState == 0) { 1761 if ( mServiceMode ) { 1762 // finished service-track programming, time to power on 1763 log.debug("end service-mode programming: turn power on"); // NOI18N 1764 try { 1765 jmri.InstanceManager.getDefault(jmri.PowerManager.class).setPower(jmri.PowerManager.ON); 1766 } catch (jmri.JmriException e) { 1767 log.error("exception during power on at end of programming", e); // NOI18N 1768 } 1769 } else { 1770 log.debug("end ops-mode programming: no power change"); // NOI18N 1771 } 1772 } 1773 } 1774 1775 javax.swing.Timer mPowerTimer = null; 1776 1777 ReadAllSlots_Helper _rAS = null; 1778 1779 /** 1780 * Start the process of checking each slot for contents. 1781 * <p> 1782 * This is not invoked by this class, but can be invoked from elsewhere to 1783 * start the process of scanning all slots to update their contents. 1784 * 1785 * If an instance is already running then the request is ignored 1786 * 1787 * @param inputSlotMap array of from to pairs 1788 * @param interval ms between slt rds 1789 */ 1790 public synchronized void update(List<SlotMapEntry> inputSlotMap, int interval) { 1791 if (_rAS == null) { 1792 _rAS = new ReadAllSlots_Helper( inputSlotMap, interval); 1793 jmri.util.ThreadingUtil.newThread(_rAS, getUserName() + READ_ALL_SLOTS_THREADNAME).start(); 1794 } else { 1795 if (!_rAS.isRunning()) { 1796 jmri.util.ThreadingUtil.newThread(_rAS, getUserName() + READ_ALL_SLOTS_THREADNAME).start(); 1797 } 1798 } 1799 } 1800 1801 /** 1802 * String with name for Read all slots thread. 1803 * Requires getUserName prepending. 1804 */ 1805 public static final String READ_ALL_SLOTS_THREADNAME = " Read All Slots "; 1806 1807 /** 1808 * Checks slotNum valid for slot map 1809 * 1810 * @param slotNum the slot number 1811 * @return true if it is 1812 */ 1813 private boolean validateSlotNumber(int slotNum) { 1814 for (SlotMapEntry item : slotMap) { 1815 if (slotNum >= item.getFrom() && slotNum <= item.getTo()) { 1816 return true; 1817 } 1818 } 1819 return false; 1820 } 1821 1822 public void update() { 1823 update(slotMap, slotScanInterval); 1824 } 1825 1826 /** 1827 * Send a message requesting the data from a particular slot. 1828 * 1829 * @param slot Slot number 1830 */ 1831 public void sendReadSlot(int slot) { 1832 LocoNetMessage m = new LocoNetMessage(4); 1833 m.setOpCode(LnConstants.OPC_RQ_SL_DATA); 1834 m.setElement(1, slot & 0x7F); 1835 // one is always short 1836 // THis gets a little akward, slots 121 thru 127 incl. seem to always old slots. 1837 // All slots gt 127 are always expanded format. 1838 if ( slot > 127 || ( ( slot > 0 && slot < 121 ) && loconetProtocol == LnConstants.LOCONETPROTOCOL_TWO ) ) { 1839 m.setElement(2, (slot / 128 ) & 0b00000111 | 0x40 ); 1840 } 1841 tc.sendLocoNetMessage(m); 1842 } 1843 1844 protected int nextReadSlot = 0; 1845 1846 /** 1847 * Continue the sequence of reading all slots. 1848 * @param toSlot index of the next slot to read 1849 * @param interval wait time before operation, milliseconds 1850 */ 1851 synchronized protected void readNextSlot(int toSlot, int interval) { 1852 // send info request 1853 sendReadSlot(nextReadSlot++); 1854 1855 // schedule next read if needed 1856 if (nextReadSlot < toSlot) { 1857 javax.swing.Timer t = new javax.swing.Timer(interval, new java.awt.event.ActionListener() { 1858 @Override 1859 public void actionPerformed(java.awt.event.ActionEvent e) { 1860 readNextSlot(toSlot,interval); 1861 } 1862 }); 1863 t.setRepeats(false); 1864 t.start(); 1865 } 1866 } 1867 1868 /** 1869 * Provide a snapshot of the slots in use. 1870 * <p> 1871 * Note that the count of "in-use" slots may be somewhat misleading, 1872 * as slots in the "common" state can be controlled and are occupying 1873 * a slot in a meaningful way. 1874 * 1875 * @return the count of in-use LocoNet slots 1876 */ 1877 public int getInUseCount() { 1878 int result = 0; 1879 for (int i = 0; i <= 120; i++) { 1880 if (slot(i).slotStatus() == LnConstants.LOCO_IN_USE) { 1881 result++; 1882 } 1883 } 1884 return result; 1885 } 1886 1887 /** 1888 * Set the system connection memo. 1889 * 1890 * @param memo a LocoNetSystemConnectionMemo 1891 */ 1892 public void setSystemConnectionMemo(LocoNetSystemConnectionMemo memo) { 1893 adaptermemo = memo; 1894 } 1895 1896 LocoNetSystemConnectionMemo adaptermemo; 1897 1898 /** 1899 * Get the "user name" for the slot manager connection, from the memo. 1900 * 1901 * @return the connection's user name or "LocoNet" if the memo 1902 * does not exist 1903 */ 1904 @Override 1905 public String getUserName() { 1906 if (adaptermemo == null) { 1907 return "LocoNet"; // NOI18N 1908 } 1909 return adaptermemo.getUserName(); 1910 } 1911 1912 /** 1913 * Return the memo "system prefix". 1914 * 1915 * @return the system prefix or "L" if the memo 1916 * does not exist 1917 */ 1918 @Override 1919 public String getSystemPrefix() { 1920 if (adaptermemo == null) { 1921 return "L"; 1922 } 1923 return adaptermemo.getSystemPrefix(); 1924 } 1925 1926 boolean transpondingAvailable = false; 1927 public void setTranspondingAvailable(boolean val) { transpondingAvailable = val; } 1928 public boolean getTranspondingAvailable() { return transpondingAvailable; } 1929 1930 /** 1931 * 1932 * @param val If false then we only use protocol one. 1933 */ 1934 public void setLoconetProtocolAutoDetect(boolean val) { 1935 if (!val) { 1936 loconetProtocol = LnConstants.LOCONETPROTOCOL_ONE; 1937 // slots would have been created with unknown for auto detect 1938 for( int ix = 0; ix < 128; ix++ ) { 1939 slot(ix).setProtocol(loconetProtocol); 1940 } 1941 } 1942 } 1943 1944 /** 1945 * Get the memo. 1946 * 1947 * @return the memo 1948 */ 1949 public LocoNetSystemConnectionMemo getSystemConnectionMemo() { 1950 return adaptermemo; 1951 } 1952 1953 /** 1954 * Dispose of this by stopped it's ongoing actions 1955 */ 1956 @Override 1957 public void dispose() { 1958 if (staleSlotCheckTimer != null) { 1959 staleSlotCheckTimer.stop(); 1960 } 1961 if ( _rAS != null ) { 1962 _rAS.setAbort(); 1963 } 1964 } 1965 1966 // initialize logging 1967 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SlotManager.class); 1968 1969 // Read all slots 1970 class ReadAllSlots_Helper implements Runnable { 1971 1972 ReadAllSlots_Helper(List<SlotMapEntry> inputSlotMap, int interval) { 1973 this.interval = interval; 1974 } 1975 1976 private int interval; 1977 private boolean abort = false; 1978 private boolean isRunning = false; 1979 1980 /** 1981 * Aborts current run 1982 */ 1983 public void setAbort() { 1984 abort = true; 1985 } 1986 1987 /** 1988 * Gets the current stae of the run. 1989 * @return true if running 1990 */ 1991 public boolean isRunning() { 1992 return isRunning; 1993 } 1994 1995 @Override 1996 public void run() { 1997 abort = false; 1998 isRunning = true; 1999 // read all slots that are not of unknown type 2000 for (int slot = 0; slot < getNumSlots() && !abort; slot++) { 2001 if (_slots[slot].getSlotType() != SlotType.UNKNOWN) { 2002 sendReadSlot(slot); 2003 try { 2004 Thread.sleep(this.interval); 2005 } catch (Exception ex) { 2006 // just abort 2007 abort = true; 2008 break; 2009 } 2010 } 2011 } 2012 isRunning = false; 2013 } 2014 } 2015 2016}