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 s.f0to4packet(address.getNumber(), address.isLongAddress(), f0, f0Momentary, 348 f1, f1Momentary, 349 f2, f2Momentary, 350 f3, f3Momentary, 351 f4, f4Momentary); 352 notifySlotListeners(s); 353 } 354 355 public void function5Through8Packet(DccLocoAddress address, 356 boolean f5, boolean f5Momentary, 357 boolean f6, boolean f6Momentary, 358 boolean f7, boolean f7Momentary, 359 boolean f8, boolean f8Momentary) { 360 SprogSlot s = this.findF5to8Packet(address); 361 s.f5to8packet(address.getNumber(), address.isLongAddress(), f5, f5Momentary, f6, f6Momentary, f7, f7Momentary, f8, f8Momentary); 362 notifySlotListeners(s); 363 } 364 365 public void function9Through12Packet(DccLocoAddress address, 366 boolean f9, boolean f9Momentary, 367 boolean f10, boolean f10Momentary, 368 boolean f11, boolean f11Momentary, 369 boolean f12, boolean f12Momentary) { 370 SprogSlot s = this.findF9to12Packet(address); 371 s.f9to12packet(address.getNumber(), address.isLongAddress(), f9, f9Momentary, f10, f10Momentary, f11, f11Momentary, f12, f12Momentary); 372 notifySlotListeners(s); 373 } 374 375 public void function13Through20Packet(DccLocoAddress address, 376 boolean f13, boolean f13Momentary, 377 boolean f14, boolean f14Momentary, 378 boolean f15, boolean f15Momentary, 379 boolean f16, boolean f16Momentary, 380 boolean f17, boolean f17Momentary, 381 boolean f18, boolean f18Momentary, 382 boolean f19, boolean f19Momentary, 383 boolean f20, boolean f20Momentary) { 384 SprogSlot s = this.findF13to20Packet(address); 385 s.f13to20packet(address.getNumber(), address.isLongAddress(), 386 f13, f13Momentary, f14, f14Momentary, f15, f15Momentary, f16, f16Momentary, 387 f17, f17Momentary, f18, f18Momentary, f19, f19Momentary, f20, f20Momentary); 388 notifySlotListeners(s); 389 } 390 391 public void function21Through28Packet(DccLocoAddress address, 392 boolean f21, boolean f21Momentary, 393 boolean f22, boolean f22Momentary, 394 boolean f23, boolean f23Momentary, 395 boolean f24, boolean f24Momentary, 396 boolean f25, boolean f25Momentary, 397 boolean f26, boolean f26Momentary, 398 boolean f27, boolean f27Momentary, 399 boolean f28, boolean f28Momentary) { 400 SprogSlot s = this.findF21to28Packet(address); 401 s.f21to28packet(address.getNumber(), address.isLongAddress(), 402 f21, f21Momentary, f22, f22Momentary, f23, f23Momentary, f24, f24Momentary, 403 f25, f25Momentary, f26, f26Momentary, f27, f27Momentary, f28, f28Momentary); 404 notifySlotListeners(s); 405 } 406 407 public void function29Through36Packet(DccLocoAddress address, 408 boolean a, boolean am, 409 boolean b, boolean bm, 410 boolean c, boolean cm, 411 boolean d, boolean dm, 412 boolean e, boolean em, 413 boolean f, boolean fm, 414 boolean g, boolean gm, 415 boolean h, boolean hm) { 416 SprogSlot s = this.findF29to36Packet(address); 417 s.f29to36packet(address.getNumber(), address.isLongAddress(), 418 a, am, b, bm, c, cm, d, dm, 419 e, em, f, fm, g, gm, h, hm); 420 notifySlotListeners(s); 421 } 422 423 public void function37Through44Packet(DccLocoAddress address, 424 boolean a, boolean am, 425 boolean b, boolean bm, 426 boolean c, boolean cm, 427 boolean d, boolean dm, 428 boolean e, boolean em, 429 boolean f, boolean fm, 430 boolean g, boolean gm, 431 boolean h, boolean hm) { 432 SprogSlot s = this.findF37to44Packet(address); 433 s.f37to44packet(address.getNumber(), address.isLongAddress(), 434 a, am, b, bm, c, cm, d, dm, 435 e, em, f, fm, g, gm, h, hm); 436 notifySlotListeners(s); 437 } 438 439 public void function45Through52Packet(DccLocoAddress address, 440 boolean a, boolean am, 441 boolean b, boolean bm, 442 boolean c, boolean cm, 443 boolean d, boolean dm, 444 boolean e, boolean em, 445 boolean f, boolean fm, 446 boolean g, boolean gm, 447 boolean h, boolean hm) { 448 SprogSlot s = this.findF45to52Packet(address); 449 s.f45to52packet(address.getNumber(), address.isLongAddress(), 450 a, am, b, bm, c, cm, d, dm, 451 e, em, f, fm, g, gm, h, hm); 452 notifySlotListeners(s); 453 } 454 455 public void function53Through60Packet(DccLocoAddress address, 456 boolean a, boolean am, 457 boolean b, boolean bm, 458 boolean c, boolean cm, 459 boolean d, boolean dm, 460 boolean e, boolean em, 461 boolean f, boolean fm, 462 boolean g, boolean gm, 463 boolean h, boolean hm) { 464 SprogSlot s = this.findF53to60Packet(address); 465 s.f53to60packet(address.getNumber(), address.isLongAddress(), 466 a, am, b, bm, c, cm, d, dm, 467 e, em, f, fm, g, gm, h, hm); 468 notifySlotListeners(s); 469 } 470 471 public void function61Through68Packet(DccLocoAddress address, 472 boolean a, boolean am, 473 boolean b, boolean bm, 474 boolean c, boolean cm, 475 boolean d, boolean dm, 476 boolean e, boolean em, 477 boolean f, boolean fm, 478 boolean g, boolean gm, 479 boolean h, boolean hm) { 480 SprogSlot s = this.findF61to68Packet(address); 481 s.f61to68packet(address.getNumber(), address.isLongAddress(), 482 a, am, b, bm, c, cm, d, dm, 483 e, em, f, fm, g, gm, h, hm); 484 notifySlotListeners(s); 485 } 486 487 /** 488 * Handle speed changes from throttle. 489 * <p> 490 * As well as updating an existing slot, 491 * or creating a new on where necessary, the speed command is added to the 492 * queue of packets to be sent immediately.This ensures minimum latency 493 * between the user adjusting the throttle and a loco responding, rather 494 * than possibly waiting for a complete traversal of all slots before the 495 * new speed is actually sent to the hardware. 496 * 497 * @param mode speed step mode. 498 * @param address loco address. 499 * @param spd speed to send. 500 * @param isForward true if forward, else false. 501 */ 502 public void setSpeed(jmri.SpeedStepMode mode, DccLocoAddress address, int spd, boolean isForward) { 503 SprogSlot s = this.findAddressSpeedPacket(address); 504 if (s != null) { // May need an error here - if all slots are full! 505 s.setSpeed(mode, address.getNumber(), address.isLongAddress(), spd, isForward); 506 notifySlotListeners(s); 507 log.debug("Registering new speed"); 508 sendNow.add(s); 509 } 510 } 511 512 public SprogSlot opsModepacket(int address, boolean longAddr, int cv, int val) { 513 SprogSlot s = findFree(); 514 if (s != null) { 515 s.setOps(address, longAddr, cv, val); 516 if (log.isDebugEnabled()) { 517 log.debug("opsModePacket() Notify ops mode packet for address {}", address); 518 } 519 notifySlotListeners(s); 520 return (s); 521 } else { 522 return (null); 523 } 524 } 525 526 public void release(DccLocoAddress address) { 527 SprogSlot s; 528 while ((s = findAddress(address)) != null) { 529 s.clear(); 530 notifySlotListeners(s); 531 } 532 } 533 534 /** 535 * Send emergency stop to all slots. 536 */ 537 public void estopAll() { 538 slots.stream().filter((s) -> ((s.getRepeat() == -1) 539 && s.slotStatus() != SprogConstants.SLOT_FREE 540 && s.speed() != 1)).forEach((s) -> { 541 eStopSlot(s); 542 }); 543 } 544 545 /** 546 * Send emergency stop to a slot. 547 * 548 * @param s SprogSlot to eStop 549 */ 550 protected void eStopSlot(SprogSlot s) { 551 log.debug("Estop slot: {} for address: {}", s.getSlotNumber(), s.getAddr()); 552 s.eStop(); 553 notifySlotListeners(s); 554 } 555 556 // data members to hold contact with the slot listeners 557 final private Vector<SprogSlotListener> slotListeners = new Vector<>(); 558 559 public synchronized void addSlotListener(SprogSlotListener l) { 560 // add only if not already registered 561 slotListeners.addElement(l); 562 } 563 564 public synchronized void removeSlotListener(SprogSlotListener l) { 565 slotListeners.removeElement(l); 566 } 567 568 /** 569 * Trigger the notification of all SlotListeners. 570 * 571 * @param s The changed slot to notify. 572 */ 573 private synchronized void notifySlotListeners(SprogSlot s) { 574 log.debug("notifySlotListeners() notify {} SlotListeners about slot for address {}", 575 slotListeners.size(), s.getAddr()); 576 577 // forward to all listeners 578 slotListeners.stream().forEach((client) -> { 579 client.notifyChangedSlot(s); 580 }); 581 } 582 583 /** 584 * Set initial power state 585 * 586 * If connection option is set for track power on the property change is sent 587 * before we are registered with the power manager so force a change in the 588 * slot thread 589 * 590 * @param powerOption true if power on at startup 591 */ 592 public void setPowerState(boolean powerOption) { 593 if (powerOption == true) { 594 powerChanged = true; 595 powerState = PowerManager.ON; 596 } 597 } 598 599 @Override 600 /** 601 * The run() method will only be called (from SprogSystemConnectionMemo 602 * ConfigureCommandStation()) if the connected SPROG is in Command Station mode. 603 * 604 */ 605 public void run() { 606 log.debug("Command station slot thread starts"); 607 while(true) { 608 try { 609 synchronized(lock) { 610 lock.wait(SprogConstants.CS_REPLY_TIMEOUT); 611 } 612 } catch (InterruptedException e) { 613 log.debug("Slot thread interrupted"); 614 // We'll loop around if there's no reply available yet 615 // Save the interrupted status for anyone who may be interested 616 Thread.currentThread().interrupt(); 617 // and exit 618 return; 619 } 620 log.debug("Slot thread wakes"); 621 622 if (powerMgr == null) { 623 // Wait until power manager is available 624 powerMgr = InstanceManager.getNullableDefault(jmri.PowerManager.class); 625 if (powerMgr == null) { 626 log.info("No power manager instance found"); 627 } else { 628 log.info("Registering with power manager"); 629 powerMgr.addPropertyChangeListener(this); 630 } 631 } else { 632 if (sendSprogAddress) { 633 // If we need to change the SPROGs default address, do that immediately, 634 // regardless of the power state. 635 log.debug("Set new address"); 636 sendMessage(new SprogMessage("A " + currentSprogAddress + " 0")); 637 replyAvailable = false; 638 sendSprogAddress = false; 639 } else if (powerChanged && (powerState == PowerManager.ON) && !waitingForReply) { 640 // Power has been turned on so send an idle packet to start the 641 // message/reply handshake 642 log.debug("Send idle to start message/reply handshake"); 643 sendPacket(jmri.NmraPacket.idlePacket(), SprogConstants.S_REPEATS); 644 powerChanged = false; 645 time = System.currentTimeMillis(); 646 } else if (replyAvailable && (powerState == PowerManager.ON)) { 647 log.debug("Reply available"); 648 // Received a reply whilst power is on, so send another packet 649 // Get next packet to send if track power is on 650 byte[] p; 651 SprogSlot s = sendNow.poll(); 652 if (s != null) { 653 // New throttle action to be sent immediately 654 p = s.getPayload(); 655 log.debug("Packet from immediate send queue"); 656 } else { 657 // Or take the next one from the stack 658 p = getNextPacket(); 659 if (p != null) { 660 log.debug("Packet from stack"); 661 } 662 } 663 replyAvailable = false; 664 if (p != null) { 665 // Send the packet 666 sendPacket(p, SprogConstants.S_REPEATS); 667 log.debug("Packet sent"); 668 } else { 669 // Send a decoder idle packet to prompt a reply from hardware and keep things running 670 log.debug("Idle sent"); 671 sendPacket(jmri.NmraPacket.idlePacket(), SprogConstants.S_REPEATS); 672 } 673 timeNow = System.currentTimeMillis(); 674 packetDelay = timeNow - time; 675 time = timeNow; 676 // Useful for debug if packets are being delayed 677 if (packetDelay > SprogConstants.PACKET_DELAY_WARN_THRESHOLD) { 678 log.warn("Packet delay was {} ms", packetDelay); 679 } 680 } else { 681 if (powerState == PowerManager.ON) { 682 683 // Should never get here. Something is wrong so turn power off 684 // Kill reply wait so send doesn't block 685 log.warn("Slot thread timeout - removing power"); 686 waitingForReply = false; 687 try { 688 powerMgr.setPower(PowerManager.OFF); 689 } catch (JmriException ex) { 690 log.error("Exception turning power off", ex); 691 } 692 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("CSErrorFrameDialogString"), 693 Bundle.getMessage("SprogCSTitle"), JmriJOptionPane.ERROR_MESSAGE); 694 } 695 } 696 } 697 } 698 } 699 700 /** 701 * Get the next packet to be transmitted. 702 * 703 * @return byte[] null if no packet 704 */ 705 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "PZLA_PREFER_ZERO_LENGTH_ARRAYS", 706 justification = "API defined by Sprog docs") 707 private byte[] getNextPacket() { 708 SprogSlot s; 709 710 if (!isBusy()) { 711 return null; 712 } 713 while (slots.get(currentSlot).isFree()) { 714 currentSlot++; 715 currentSlot = currentSlot % numSlots; 716 } 717 s = slots.get(currentSlot); 718 byte[] ret = s.getPayload(); 719 // Resend ops packets until repeat count is exhausted so that 720 // decoder receives contiguous identical packets, otherwsie find 721 // next packet to send 722 if (!s.isOpsPkt() || (s.getRepeat() == 0)) { 723 currentSlot++; 724 currentSlot = currentSlot % numSlots; 725 } 726 727 if (s.isFinished()) { 728 notifySlotListeners(s); 729 //return null; 730 } 731 732 return ret; 733 } 734 735 /* 736 * 737 * @param m the sprog message received 738 */ 739 @Override 740 public void notifyMessage(SprogMessage m) { 741 } 742 743 /** 744 * Handle replies. 745 * <p> 746 * Handle replies from the hardware, ignoring those that were not sent from 747 * the command station. 748 * 749 * @param m The SprogReply to be handled 750 */ 751 @Override 752 public void notifyReply(SprogReply m) { 753 if (m.getId() != lastId) { 754 // Not my id, so not interested, message send still blocked 755 log.debug("Ignore reply with mismatched id {} looking for {}", m.getId(), lastId); 756 return; 757 } else { 758 log.debug("Reply received [{}]", m.toString()); 759 // Log the reply and wake the slot thread 760 synchronized (lock) { 761 replyAvailable = true; 762 lock.notifyAll(); 763 } 764 } 765 } 766 767 /** 768 * implement a property change listener for power 769 */ 770 @Override 771 public void propertyChange(java.beans.PropertyChangeEvent evt) { 772 log.debug("propertyChange {} = {}", evt.getPropertyName(), evt.getNewValue()); 773 if (evt.getPropertyName().equals(PowerManager.POWER)) { 774 powerState = powerMgr.getPower(); 775 powerChanged = true; 776 } 777 } 778 779 /** 780 * Provide a count of the slots in use. 781 * 782 * @return the number of slots in use 783 */ 784 public int getInUseCount() { 785 int result = 0; 786 for (SprogSlot s : slots) { 787 if (!s.isFree()) { 788 result++; 789 } 790 } 791 return result; 792 } 793 794 /** 795 * 796 * @return a boolean if the command station is busy - i.e. it has at least 797 * one occupied slot 798 */ 799 public boolean isBusy() { 800 return slots.stream().anyMatch((s) -> (!s.isFree())); 801 } 802 803 public void setSystemConnectionMemo(SprogSystemConnectionMemo memo) { 804 adaptermemo = memo; 805 } 806 807 SprogSystemConnectionMemo adaptermemo; 808 809 /** 810 * Get user name. 811 * 812 * @return the user name 813 */ 814 @Override 815 public String getUserName() { 816 if (adaptermemo == null) { 817 return "Sprog"; 818 } 819 return adaptermemo.getUserName(); 820 } 821 822 /** 823 * Get system prefix. 824 * 825 * @return the system prefix 826 */ 827 @Override 828 public String getSystemPrefix() { 829 if (adaptermemo == null) { 830 return "S"; 831 } 832 return adaptermemo.getSystemPrefix(); 833 } 834 835 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SprogCommandStation.class); 836 837}