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