001package jmri.jmrix.sprog; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import java.util.LinkedList; 006import java.util.Queue; 007import java.util.Vector; 008 009import jmri.CommandStation; 010import jmri.DccLocoAddress; 011import jmri.InstanceManager; 012import jmri.JmriException; 013import jmri.PowerManager; 014import jmri.util.swing.JmriJOptionPane; 015 016/** 017 * Control a collection of slots, acting as a soft command station for SPROG 018 * <p> 019 * A SlotListener can register to hear changes. By registering here, the 020 * SlotListener is saying that it wants to be notified of a change in any slot. 021 * Alternately, the SlotListener can register with some specific slot, done via 022 * the SprogSlot object itself. 023 * <p> 024 * This Programmer implementation is single-user only. It's not clear whether 025 * the command stations can have multiple programming requests outstanding (e.g. 026 * service mode and ops mode, or two ops mode) at the same time, but this code 027 * definitely can't. 028 * <p> 029 * Updated by Andrew Berridge, January 2010 - state management code now safer, 030 * uses enum, etc. Amalgamated with Sprog Slot Manager into a single class - 031 * reduces code duplication. 032 * <p> 033 * Updated by Andrew Crosland February 2012 to allow slots to hold 28 step speed 034 * packets 035 * <p> 036 * Re-written by Andrew Crosland to send the next packet as soon as a reply is 037 * notified. This removes a race between the old state machine running before 038 * the traffic controller despatches a reply, missing the opportunity to send a 039 * new packet to the layout until the next JVM time slot, which can be 15ms on 040 * Windows platforms. 041 * <p> 042 * May-17 Moved status reply handling to the slot monitor. Monitor messages from 043 * other sources and suppress messages from here to prevent queueing messages in 044 * the traffic controller. 045 * <p> 046 * Jan-18 Re-written again due to threading issues. Previous changes removed 047 * activity from the slot thread, which could result in loading the swing thread 048 * to the extent that the gui becomes very slow to respond. 049 * Moved status message generation to the slot monitor. 050 * Interact with power control as a way to allow the user to recover after a 051 * timeout error due to loss of communication with the hardware. 052 * 053 * @author Bob Jacobsen Copyright (C) 2001, 2003 054 * @author Andrew Crosland (C) 2006 ported to SPROG, 2012, 2016, 2018 055 */ 056public class SprogCommandStation implements CommandStation, SprogListener, Runnable, 057 java.beans.PropertyChangeListener { 058 059 protected int currentSlot = 0; 060 protected int currentSprogAddress = -1; 061 062 protected LinkedList<SprogSlot> slots; 063 protected int numSlots = SprogConstants.MIN_SLOTS; 064 protected Queue<SprogSlot> sendNow; 065 066 private SprogTrafficController tc = null; 067 068 final Object lock = new Object(); 069 070 private boolean waitingForReply = false; 071 private boolean replyAvailable = false; 072 private boolean sendSprogAddress = false; 073 private long time, timeNow, packetDelay; 074 private int lastId; 075 076 PowerManager powerMgr = null; 077 int powerState = PowerManager.OFF; 078 boolean powerChanged = false; 079 080 public SprogCommandStation(SprogTrafficController controller) { 081 sendNow = new LinkedList<>(); 082 /** 083 * Create a default length queue 084 */ 085 slots = new LinkedList<>(); 086 numSlots = controller.getAdapterMemo().getNumSlots(); 087 for (int i = 0; i < numSlots; i++) { 088 slots.add(new SprogSlot(i)); 089 } 090 tc = controller; 091 tc.addSprogListener(this); 092 } 093 094 /** 095 * Send a specific packet as a SprogMessage. 096 * 097 * @param packet Byte array representing the packet, including the 098 * error-correction byte. Must not be null. 099 * @param repeats number of times to repeat the packet 100 */ 101 @Override 102 public boolean sendPacket(byte[] packet, int repeats) { 103 if (packet.length <= 1) { 104 log.error("Invalid DCC packet length: {}", packet.length); 105 } 106 if (packet.length >= 7) { 107 log.error("Maximum 6-byte packets accepted: {}", packet.length); 108 } 109 final SprogMessage m = new SprogMessage(packet); 110 sendMessage(m); 111 return true; 112 } 113 114 /** 115 * Send the SprogMessage to the hardware. 116 * <p> 117 * sendSprogMessage will block until the message can be sent. When it returns 118 * we set the reply status for the message just sent. 119 * 120 * @param m The message to be sent 121 */ 122 protected void sendMessage(SprogMessage m) { 123 log.debug("Sending message [{}] id {}", m.toString(tc.isSIIBootMode()), m.getId()); 124 lastId = m.getId(); 125 tc.sendSprogMessage(m, this); 126 } 127 128 /** 129 * Return contents of Queue slot i. 130 * 131 * @param i int of slot requested 132 * @return SprogSlot slot i 133 */ 134 public SprogSlot slot(int i) { 135 return slots.get(i); 136 } 137 138 /** 139 * Clear all slots. 140 */ 141 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification="was previously marked with @SuppressWarnings, reason unknown") 142 private void clearAllSlots() { 143 slots.stream().forEach((s) -> { 144 s.clear(); 145 }); 146 } 147 148 /** 149 * Find a free slot entry. 150 * 151 * @return SprogSlot the next free Slot or null if all slots are full 152 */ 153 protected SprogSlot findFree() { 154 for (SprogSlot s : slots) { 155 if (s.isFree()) { 156 if (log.isDebugEnabled()) { 157 log.debug("Found free slot {}", s.getSlotNumber()); 158 } 159 return s; 160 } 161 } 162 return (null); 163 } 164 165 /** 166 * Find a queue entry matching the address. 167 * 168 * @param address The address to locate 169 * @return The slot or null if the address is not in the queue 170 */ 171 private SprogSlot findAddress(DccLocoAddress address) { 172 for (SprogSlot s : slots) { 173 if ( s.isActiveAddressMatch(address) ) { 174 return s; 175 } 176 } 177 return (null); 178 } 179 180 private SprogSlot findAddressSpeedPacket(DccLocoAddress address) { 181 // SPROG doesn't use IDLE packets but sends speed commands to last address selected by "A" command. 182 // We may need to move these pseudo-idle packets to an unused long address so locos will not receive conflicting speed commands. 183 // Some short-address-only decoders may also respond to same-numbered long address so we avoid any number match irrespective of type 184 // We need to find a suitable free long address, save (currentSprogAddress) and use it for pseudo-idle packets 185 int lastSprogAddress = currentSprogAddress; 186 while ( (currentSprogAddress <= 0) || // initialisation || avoid address 0 for reason above 187 ( (address.getNumber() == currentSprogAddress ) ) || // avoid this address (slot may not exist but we will be creating one) 188 ( findAddress(new DccLocoAddress(currentSprogAddress,true)) != null) || ( findAddress(new DccLocoAddress(currentSprogAddress,false)) != null) // avoid in-use (both long or short versions of) address 189 ) { 190 currentSprogAddress++; 191 currentSprogAddress = currentSprogAddress % 10240; 192 } 193 if (currentSprogAddress != lastSprogAddress) { 194 log.info("Changing currentSprogAddress (for pseudo-idle packets) to {}(L)", currentSprogAddress); 195 // We want to ignore the reply to this message so it does not trigger an extra packet 196 // Set a flag to send this from the slot thread and avoid swing thread waiting 197 //sendMessage(new SprogMessage("A " + currentSprogAddress + " 0")); 198 sendSprogAddress = true; 199 } 200 for (SprogSlot s : slots) { 201 if (s.isActiveAddressMatch(address) && s.isSpeedPacket()) { 202 return s; 203 } 204 } 205 if (getInUseCount() < numSlots) { 206 return findFree(); 207 } 208 return (null); 209 } 210 211 private SprogSlot findF0to4Packet(DccLocoAddress address) { 212 for (SprogSlot s : slots) { 213 if (s.isActiveAddressMatch(address) && s.isF0to4Packet()) { 214 return s; 215 } 216 } 217 if (getInUseCount() < numSlots) { 218 return findFree(); 219 } 220 return (null); 221 } 222 223 private SprogSlot findF5to8Packet(DccLocoAddress address) { 224 for (SprogSlot s : slots) { 225 if (s.isActiveAddressMatch(address) && s.isF5to8Packet()) { 226 return s; 227 } 228 } 229 if (getInUseCount() < numSlots) { 230 return findFree(); 231 } 232 return (null); 233 } 234 235 private SprogSlot findF9to12Packet(DccLocoAddress address) { 236 for (SprogSlot s : slots) { 237 if (s.isActiveAddressMatch(address) && s.isF9to12Packet()) { 238 return s; 239 } 240 } 241 if (getInUseCount() < numSlots) { 242 return findFree(); 243 } 244 return (null); 245 } 246 247 private SprogSlot findF13to20Packet(DccLocoAddress address) { 248 for (SprogSlot s : slots) { 249 if (s.isActiveAddressMatch(address) && s.isF13to20Packet()) { 250 return s; 251 } 252 } 253 if (getInUseCount() < numSlots) { 254 return findFree(); 255 } 256 return (null); 257 } 258 259 private SprogSlot findF21to28Packet(DccLocoAddress address) { 260 for (SprogSlot s : slots) { 261 if (s.isActiveAddressMatch(address) && s.isF21to28Packet()) { 262 return s; 263 } 264 } 265 if (getInUseCount() < numSlots) { 266 return findFree(); 267 } 268 return (null); 269 } 270 271 private SprogSlot findF29to36Packet(DccLocoAddress address) { 272 for (SprogSlot s : slots) { 273 if (s.isActiveAddressMatch(address) && s.isF29to36Packet()) { 274 return s; 275 } 276 } 277 if (getInUseCount() < numSlots) { 278 return findFree(); 279 } 280 return (null); 281 } 282 283 private SprogSlot findF37to44Packet(DccLocoAddress address) { 284 for (SprogSlot s : slots) { 285 if (s.isActiveAddressMatch(address) && s.isF37to44Packet()) { 286 return s; 287 } 288 } 289 if (getInUseCount() < numSlots) { 290 return findFree(); 291 } 292 return (null); 293 } 294 295 private SprogSlot findF45to52Packet(DccLocoAddress address) { 296 for (SprogSlot s : slots) { 297 if (s.isActiveAddressMatch(address) && s.isF45to52Packet()) { 298 return s; 299 } 300 } 301 if (getInUseCount() < numSlots) { 302 return findFree(); 303 } 304 return (null); 305 } 306 307 private SprogSlot findF53to60Packet(DccLocoAddress address) { 308 for (SprogSlot s : slots) { 309 if (s.isActiveAddressMatch(address) && s.isF53to60Packet()) { 310 return s; 311 } 312 } 313 if (getInUseCount() < numSlots) { 314 return findFree(); 315 } 316 return (null); 317 } 318 319 private SprogSlot findF61to68Packet(DccLocoAddress address) { 320 for (SprogSlot s : slots) { 321 if (s.isActiveAddressMatch(address) && s.isF61to68Packet()) { 322 return s; 323 } 324 } 325 if (getInUseCount() < numSlots) { 326 return findFree(); 327 } 328 return (null); 329 } 330 331 public void forwardCommandChangeToLayout(int address, boolean closed) { 332 333 SprogSlot s = this.findFree(); 334 if (s != null) { 335 s.setAccessoryPacket(address, closed, SprogConstants.S_REPEATS); 336 notifySlotListeners(s); 337 } 338 } 339 340 public void function0Through4Packet(DccLocoAddress address, 341 boolean f0, boolean f0Momentary, 342 boolean f1, boolean f1Momentary, 343 boolean f2, boolean f2Momentary, 344 boolean f3, boolean f3Momentary, 345 boolean f4, boolean f4Momentary) { 346 SprogSlot s = this.findF0to4Packet(address); 347 if(s==null){ 348 log.warn("Insufficient Sprogslots available, command not sent - increase number of Sprogslots in preferences"); 349 }else{ 350 s.f0to4packet(address.getNumber(), address.isLongAddress(), f0, f0Momentary, 351 f1, f1Momentary, 352 f2, f2Momentary, 353 f3, f3Momentary, 354 f4, f4Momentary); 355 notifySlotListeners(s); 356 } 357 } 358 359 public void function5Through8Packet(DccLocoAddress address, 360 boolean f5, boolean f5Momentary, 361 boolean f6, boolean f6Momentary, 362 boolean f7, boolean f7Momentary, 363 boolean f8, boolean f8Momentary) { 364 SprogSlot s = this.findF5to8Packet(address); 365 if(s==null){ 366 log.warn("Insufficient Sprogslots available, command not sent - increase number of Sprogslots in preferences"); 367 }else{ 368 s.f5to8packet(address.getNumber(), address.isLongAddress(), f5, f5Momentary, f6, f6Momentary, f7, f7Momentary, f8, f8Momentary); 369 notifySlotListeners(s); 370 } 371 } 372 373 public void function9Through12Packet(DccLocoAddress address, 374 boolean f9, boolean f9Momentary, 375 boolean f10, boolean f10Momentary, 376 boolean f11, boolean f11Momentary, 377 boolean f12, boolean f12Momentary) { 378 SprogSlot s = this.findF9to12Packet(address); 379 if(s==null){ 380 log.warn("Insufficient Sprogslots available, command not sent - increase number of Sprogslots in preferences"); 381 }else{ 382 s.f9to12packet(address.getNumber(), address.isLongAddress(), f9, f9Momentary, f10, f10Momentary, f11, f11Momentary, f12, f12Momentary); 383 notifySlotListeners(s); 384 } 385 } 386 387 public void function13Through20Packet(DccLocoAddress address, 388 boolean f13, boolean f13Momentary, 389 boolean f14, boolean f14Momentary, 390 boolean f15, boolean f15Momentary, 391 boolean f16, boolean f16Momentary, 392 boolean f17, boolean f17Momentary, 393 boolean f18, boolean f18Momentary, 394 boolean f19, boolean f19Momentary, 395 boolean f20, boolean f20Momentary) { 396 SprogSlot s = this.findF13to20Packet(address); 397 if(s==null){ 398 log.warn("Insufficient Sprogslots available, command not sent - increase number of Sprogslots in preferences"); 399 }else{ 400 s.f13to20packet(address.getNumber(), address.isLongAddress(), 401 f13, f13Momentary, f14, f14Momentary, f15, f15Momentary, f16, f16Momentary, 402 f17, f17Momentary, f18, f18Momentary, f19, f19Momentary, f20, f20Momentary); 403 notifySlotListeners(s); 404 } 405 } 406 407 public void function21Through28Packet(DccLocoAddress address, 408 boolean f21, boolean f21Momentary, 409 boolean f22, boolean f22Momentary, 410 boolean f23, boolean f23Momentary, 411 boolean f24, boolean f24Momentary, 412 boolean f25, boolean f25Momentary, 413 boolean f26, boolean f26Momentary, 414 boolean f27, boolean f27Momentary, 415 boolean f28, boolean f28Momentary) { 416 SprogSlot s = this.findF21to28Packet(address); 417 if(s==null){ 418 log.warn("Insufficient Sprogslots available, command not sent - increase number of Sprogslots in preferences"); 419 }else{ 420 s.f21to28packet(address.getNumber(), address.isLongAddress(), 421 f21, f21Momentary, f22, f22Momentary, f23, f23Momentary, f24, f24Momentary, 422 f25, f25Momentary, f26, f26Momentary, f27, f27Momentary, f28, f28Momentary); 423 notifySlotListeners(s); 424 } 425 } 426 427 public void function29Through36Packet(DccLocoAddress address, 428 boolean a, boolean am, 429 boolean b, boolean bm, 430 boolean c, boolean cm, 431 boolean d, boolean dm, 432 boolean e, boolean em, 433 boolean f, boolean fm, 434 boolean g, boolean gm, 435 boolean h, boolean hm) { 436 SprogSlot s = this.findF29to36Packet(address); 437 if(s==null){ 438 log.warn("Insufficient Sprogslots available, command not sent - increase number of Sprogslots in preferences"); 439 }else{ 440 s.f29to36packet(address.getNumber(), address.isLongAddress(), 441 a, am, b, bm, c, cm, d, dm, 442 e, em, f, fm, g, gm, h, hm); 443 notifySlotListeners(s); 444 } 445 } 446 447 public void function37Through44Packet(DccLocoAddress address, 448 boolean a, boolean am, 449 boolean b, boolean bm, 450 boolean c, boolean cm, 451 boolean d, boolean dm, 452 boolean e, boolean em, 453 boolean f, boolean fm, 454 boolean g, boolean gm, 455 boolean h, boolean hm) { 456 SprogSlot s = this.findF37to44Packet(address); 457 if(s==null){ 458 log.warn("Insufficient Sprogslots available, command not sent - increase number of Sprogslots in preferences"); 459 }else{ 460 s.f37to44packet(address.getNumber(), address.isLongAddress(), 461 a, am, b, bm, c, cm, d, dm, 462 e, em, f, fm, g, gm, h, hm); 463 notifySlotListeners(s); 464 } 465 } 466 467 public void function45Through52Packet(DccLocoAddress address, 468 boolean a, boolean am, 469 boolean b, boolean bm, 470 boolean c, boolean cm, 471 boolean d, boolean dm, 472 boolean e, boolean em, 473 boolean f, boolean fm, 474 boolean g, boolean gm, 475 boolean h, boolean hm) { 476 SprogSlot s = this.findF45to52Packet(address); 477 if(s==null){ 478 log.warn("Insufficient Sprogslots available, command not sent - increase number of Sprogslots in preferences"); 479 }else{ 480 s.f45to52packet(address.getNumber(), address.isLongAddress(), 481 a, am, b, bm, c, cm, d, dm, 482 e, em, f, fm, g, gm, h, hm); 483 notifySlotListeners(s); 484 } 485 } 486 487 public void function53Through60Packet(DccLocoAddress address, 488 boolean a, boolean am, 489 boolean b, boolean bm, 490 boolean c, boolean cm, 491 boolean d, boolean dm, 492 boolean e, boolean em, 493 boolean f, boolean fm, 494 boolean g, boolean gm, 495 boolean h, boolean hm) { 496 SprogSlot s = this.findF53to60Packet(address); 497 if(s==null){ 498 log.warn("Insufficient Sprogslots available, command not sent - increase number of Sprogslots in preferences"); 499 }else{ 500 s.f53to60packet(address.getNumber(), address.isLongAddress(), 501 a, am, b, bm, c, cm, d, dm, 502 e, em, f, fm, g, gm, h, hm); 503 notifySlotListeners(s); 504 } 505 } 506 507 public void function61Through68Packet(DccLocoAddress address, 508 boolean a, boolean am, 509 boolean b, boolean bm, 510 boolean c, boolean cm, 511 boolean d, boolean dm, 512 boolean e, boolean em, 513 boolean f, boolean fm, 514 boolean g, boolean gm, 515 boolean h, boolean hm) { 516 SprogSlot s = this.findF61to68Packet(address); 517 if(s==null){ 518 log.warn("Insufficient Sprogslots available, command not sent - increase number of Sprogslots in preferences"); 519 }else{ 520 s.f61to68packet(address.getNumber(), address.isLongAddress(), 521 a, am, b, bm, c, cm, d, dm, 522 e, em, f, fm, g, gm, h, hm); 523 notifySlotListeners(s); 524 } 525 } 526 527 /** 528 * Handle speed changes from throttle. 529 * <p> 530 * As well as updating an existing slot, 531 * or creating a new on where necessary, the speed command is added to the 532 * queue of packets to be sent immediately.This ensures minimum latency 533 * between the user adjusting the throttle and a loco responding, rather 534 * than possibly waiting for a complete traversal of all slots before the 535 * new speed is actually sent to the hardware. 536 * 537 * @param mode speed step mode. 538 * @param address loco address. 539 * @param spd speed to send. 540 * @param isForward true if forward, else false. 541 */ 542 public void setSpeed(jmri.SpeedStepMode mode, DccLocoAddress address, int spd, boolean isForward) { 543 SprogSlot s = this.findAddressSpeedPacket(address); 544 if (s != null) { // May need an error here - if all slots are full! 545 s.setSpeed(mode, address.getNumber(), address.isLongAddress(), spd, isForward); 546 notifySlotListeners(s); 547 log.debug("Registering new speed"); 548 sendNow.add(s); 549 } else { 550 log.warn("Insufficient Sprogslots available, command not sent - increase number of Sprogslots in preferences"); 551 } 552 } 553 554 public SprogSlot opsModepacket(int address, boolean longAddr, int cv, int val) { 555 SprogSlot s = findFree(); 556 if (s != null) { 557 s.setOps(address, longAddr, cv, val); 558 if (log.isDebugEnabled()) { 559 log.debug("opsModePacket() Notify ops mode packet for address {}", address); 560 } 561 notifySlotListeners(s); 562 return (s); 563 } else { 564 return (null); 565 } 566 } 567 568 public void release(DccLocoAddress address) { 569 SprogSlot s; 570 while ((s = findAddress(address)) != null) { 571 s.clear(); 572 notifySlotListeners(s); 573 } 574 } 575 576 /** 577 * Send emergency stop to all slots. 578 */ 579 public void estopAll() { 580 slots.stream().filter((s) -> ((s.getRepeat() == -1) 581 && s.slotStatus() != SprogConstants.SLOT_FREE 582 && s.speed() != 1)).forEach((s) -> { 583 eStopSlot(s); 584 }); 585 } 586 587 /** 588 * Send emergency stop to a slot. 589 * 590 * @param s SprogSlot to eStop 591 */ 592 protected void eStopSlot(SprogSlot s) { 593 log.debug("Estop slot: {} for address: {}", s.getSlotNumber(), s.getAddr()); 594 s.eStop(); 595 notifySlotListeners(s); 596 } 597 598 // data members to hold contact with the slot listeners 599 final private Vector<SprogSlotListener> slotListeners = new Vector<>(); 600 601 public synchronized void addSlotListener(SprogSlotListener l) { 602 // add only if not already registered 603 slotListeners.addElement(l); 604 } 605 606 public synchronized void removeSlotListener(SprogSlotListener l) { 607 slotListeners.removeElement(l); 608 } 609 610 /** 611 * Trigger the notification of all SlotListeners. 612 * 613 * @param s The changed slot to notify. 614 */ 615 private synchronized void notifySlotListeners(SprogSlot s) { 616 log.debug("notifySlotListeners() notify {} SlotListeners about slot for address {}", 617 slotListeners.size(), s.getAddr()); 618 619 // forward to all listeners 620 slotListeners.stream().forEach((client) -> { 621 client.notifyChangedSlot(s); 622 }); 623 } 624 625 /** 626 * Set initial power state 627 * 628 * If connection option is set for track power on the property change is sent 629 * before we are registered with the power manager so force a change in the 630 * slot thread 631 * 632 * @param powerOption true if power on at startup 633 */ 634 public void setPowerState(boolean powerOption) { 635 if (powerOption == true) { 636 powerChanged = true; 637 powerState = PowerManager.ON; 638 } 639 } 640 641 @Override 642 /** 643 * The run() method will only be called (from SprogSystemConnectionMemo 644 * ConfigureCommandStation()) if the connected SPROG is in Command Station mode. 645 * 646 */ 647 public void run() { 648 log.debug("Command station slot thread starts"); 649 while(true) { 650 try { 651 synchronized(lock) { 652 lock.wait(SprogConstants.CS_REPLY_TIMEOUT); 653 } 654 } catch (InterruptedException e) { 655 log.debug("Slot thread interrupted"); 656 // We'll loop around if there's no reply available yet 657 // Save the interrupted status for anyone who may be interested 658 Thread.currentThread().interrupt(); 659 // and exit 660 return; 661 } 662 log.debug("Slot thread wakes"); 663 664 if (powerMgr == null) { 665 // Wait until power manager is available 666 powerMgr = InstanceManager.getNullableDefault(jmri.PowerManager.class); 667 if (powerMgr == null) { 668 log.info("No power manager instance found"); 669 } else { 670 log.info("Registering with power manager"); 671 powerMgr.addPropertyChangeListener(this); 672 } 673 } else { 674 if (sendSprogAddress) { 675 // If we need to change the SPROGs default address, do that immediately, 676 // regardless of the power state. 677 log.debug("Set new address"); 678 sendMessage(new SprogMessage("A " + currentSprogAddress + " 0")); 679 replyAvailable = false; 680 sendSprogAddress = false; 681 } else if (powerChanged && (powerState == PowerManager.ON) && !waitingForReply) { 682 // Power has been turned on so send an idle packet to start the 683 // message/reply handshake 684 log.debug("Send idle to start message/reply handshake"); 685 sendPacket(jmri.NmraPacket.idlePacket(), SprogConstants.S_REPEATS); 686 powerChanged = false; 687 time = System.currentTimeMillis(); 688 } else if (replyAvailable && (powerState == PowerManager.ON)) { 689 log.debug("Reply available"); 690 // Received a reply whilst power is on, so send another packet 691 // Get next packet to send if track power is on 692 byte[] p; 693 SprogSlot s = sendNow.poll(); 694 if (s != null) { 695 // New throttle action to be sent immediately 696 p = s.getPayload(); 697 log.debug("Packet from immediate send queue"); 698 } else { 699 // Or take the next one from the stack 700 p = getNextPacket(); 701 if (p != null) { 702 log.debug("Packet from stack"); 703 } 704 } 705 replyAvailable = false; 706 if (p != null) { 707 // Send the packet 708 sendPacket(p, SprogConstants.S_REPEATS); 709 log.debug("Packet sent"); 710 } else { 711 // Send a decoder idle packet to prompt a reply from hardware and keep things running 712 log.debug("Idle sent"); 713 sendPacket(jmri.NmraPacket.idlePacket(), SprogConstants.S_REPEATS); 714 } 715 timeNow = System.currentTimeMillis(); 716 packetDelay = timeNow - time; 717 time = timeNow; 718 // Useful for debug if packets are being delayed 719 if (packetDelay > SprogConstants.PACKET_DELAY_WARN_THRESHOLD) { 720 log.warn("Packet delay was {} ms", packetDelay); 721 } 722 } else { 723 if (powerState == PowerManager.ON) { 724 725 // Should never get here. Something is wrong so turn power off 726 // Kill reply wait so send doesn't block 727 log.warn("Slot thread timeout - removing power"); 728 waitingForReply = false; 729 try { 730 powerMgr.setPower(PowerManager.OFF); 731 } catch (JmriException ex) { 732 log.error("Exception turning power off", ex); 733 } 734 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("CSErrorFrameDialogString"), 735 Bundle.getMessage("SprogCSTitle"), JmriJOptionPane.ERROR_MESSAGE); 736 } 737 } 738 } 739 } 740 } 741 742 /** 743 * Get the next packet to be transmitted. 744 * 745 * @return byte[] null if no packet 746 */ 747 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "PZLA_PREFER_ZERO_LENGTH_ARRAYS", 748 justification = "API defined by Sprog docs") 749 private byte[] getNextPacket() { 750 SprogSlot s; 751 752 if (!isBusy()) { 753 return null; 754 } 755 while (slots.get(currentSlot).isFree()) { 756 currentSlot++; 757 currentSlot = currentSlot % numSlots; 758 } 759 s = slots.get(currentSlot); 760 byte[] ret = s.getPayload(); 761 // Resend ops packets until repeat count is exhausted so that 762 // decoder receives contiguous identical packets, otherwsie find 763 // next packet to send 764 if (!s.isOpsPkt() || (s.getRepeat() == 0)) { 765 currentSlot++; 766 currentSlot = currentSlot % numSlots; 767 } 768 769 if (s.isFinished()) { 770 notifySlotListeners(s); 771 //return null; 772 } 773 774 return ret; 775 } 776 777 /* 778 * 779 * @param m the sprog message received 780 */ 781 @Override 782 public void notifyMessage(SprogMessage m) { 783 } 784 785 /** 786 * Handle replies. 787 * <p> 788 * Handle replies from the hardware, ignoring those that were not sent from 789 * the command station. 790 * 791 * @param m The SprogReply to be handled 792 */ 793 @Override 794 public void notifyReply(SprogReply m) { 795 if (m.getId() != lastId) { 796 // Not my id, so not interested, message send still blocked 797 log.debug("Ignore reply with mismatched id {} looking for {}", m.getId(), lastId); 798 return; 799 } else { 800 log.debug("Reply received [{}]", m.toString()); 801 // Log the reply and wake the slot thread 802 synchronized (lock) { 803 replyAvailable = true; 804 lock.notifyAll(); 805 } 806 } 807 } 808 809 /** 810 * implement a property change listener for power 811 */ 812 @Override 813 public void propertyChange(java.beans.PropertyChangeEvent evt) { 814 log.debug("propertyChange {} = {}", evt.getPropertyName(), evt.getNewValue()); 815 if (evt.getPropertyName().equals(PowerManager.POWER)) { 816 powerState = powerMgr.getPower(); 817 powerChanged = true; 818 } 819 } 820 821 /** 822 * Provide a count of the slots in use. 823 * 824 * @return the number of slots in use 825 */ 826 public int getInUseCount() { 827 int result = 0; 828 for (SprogSlot s : slots) { 829 if (!s.isFree()) { 830 result++; 831 } 832 } 833 return result; 834 } 835 836 /** 837 * 838 * @return a boolean if the command station is busy - i.e. it has at least 839 * one occupied slot 840 */ 841 public boolean isBusy() { 842 return slots.stream().anyMatch((s) -> (!s.isFree())); 843 } 844 845 public void setSystemConnectionMemo(SprogSystemConnectionMemo memo) { 846 adaptermemo = memo; 847 } 848 849 SprogSystemConnectionMemo adaptermemo; 850 851 /** 852 * Get user name. 853 * 854 * @return the user name 855 */ 856 @Override 857 public String getUserName() { 858 if (adaptermemo == null) { 859 return "Sprog"; 860 } 861 return adaptermemo.getUserName(); 862 } 863 864 /** 865 * Get system prefix. 866 * 867 * @return the system prefix 868 */ 869 @Override 870 public String getSystemPrefix() { 871 if (adaptermemo == null) { 872 return "S"; 873 } 874 return adaptermemo.getSystemPrefix(); 875 } 876 877 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SprogCommandStation.class); 878 879}