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