001package jmri.jmrit.logix; 002 003import java.util.List; 004import java.util.concurrent.LinkedBlockingQueue; 005 006import jmri.*; 007import jmri.implementation.SignalSpeedMap; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011/** 012 * An SCWarrant is a warrant that is controlled by the signals on a layout. 013 * It will not run unless you have your layout fully covered with sensors and 014 * signals. 015 * 016 * @author Karl Johan Lisby Copyright (C) 2016 017 */ 018public class SCWarrant extends Warrant { 019 020 private static final String WAIT_UNEXPECTED_EXCEPTION = "{} wait unexpected exception {}"; 021 private NamedBean _nextSignal = null; // The signal that we are currently looking at to determine speed. 022 public static final float SPEED_STOP = 0.0f; 023 public static final float SPEED_TO_PLATFORM = 0.2f; 024 public static final float SPEED_UNSIGNALLED = 0.4f; 025 private long timeToPlatform = 500; 026 private float speedFactor = 0.8f; 027 private boolean forward = true; 028 private final boolean _allowShallowAllocation = false; 029 private DccThrottle _throttle = null; 030 031 /** 032 * Create an object with no route defined. 033 * <p> 034 * The list of BlockOrders is the route from an Origin to a Destination. 035 * @param sName system name. 036 * @param uName username. 037 * @param TTP time to platform. 038 */ 039 public SCWarrant(String sName, String uName, long TTP) { 040 super(sName, uName); 041 log.debug("new SCWarrant {} TTP={}",uName,TTP); 042 timeToPlatform = TTP; 043 setNoRamp(true); 044 } 045 046 public long getTimeToPlatform() { 047 return timeToPlatform; 048 } 049 050 public void setTimeToPlatform(long TTP) { 051 timeToPlatform = TTP; 052 } 053 054 public void setForward(boolean set) { 055 forward = set; 056 } 057 058 public boolean getForward() { 059 return forward; 060 } 061 062 public void setSpeedFactor(float factor) { 063 if (factor > 1.0) { 064 speedFactor = 1.0f; 065 } else if (factor < 0.1) { 066 speedFactor = 0.1f; 067 } else { 068 speedFactor = factor; 069 } 070 } 071 072 public float getSpeedFactor() { 073 return speedFactor; 074 } 075 076 float _maxBlockLength = 0; 077 float getMaxBlockLength() { 078 return _maxBlockLength; 079 } 080 void setMaxBlockLength() { 081 float blockLength; 082 for (int i=0; i <= getBlockOrders().size()-1; i++) { 083 blockLength = getBlockOrderAt(i).getBlock().getLengthCm(); 084 if (blockLength > _maxBlockLength) { 085 _maxBlockLength = blockLength; 086 } 087 } 088 } 089 090 private String allocateStartBlock() { 091 BlockOrder bo = getBlockOrderAt(0); 092 OBlock block = bo.getBlock(); 093 String message = block.allocate(this); 094 if (message != null) { 095 log.info("{} START-block allocation failed {} ",_trainName,message); 096 return message; 097 } 098 message = bo.setPath(this); 099 if (message != null) { 100 log.info("{} setting path in START-block failed {}",_trainName,message); 101 return message; 102 } 103 return null; 104 } 105 106 /** 107 * This method has been overridden in order to avoid allocation of occupied blocks. 108 */ 109 @Override 110 public String setRoute(boolean delay, List<BlockOrder> orders) { 111 return allocateStartBlock(); 112 } 113 114 boolean allTurnoutsSet() { 115 for (int i=0; i<getBlockOrders().size(); i++) { 116 OBlock block_i = getBlockOrderAt(i).getBlock(); 117 OPath path_i = getBlockOrderAt(i).getPath(); 118 if (!path_i.checkPathSet()) { 119 log.debug("{}: turnouts at block {} are not set yet (in allTurnoutsSet).",_trainName,block_i.getDisplayName()); 120 return false; 121 } 122 } 123 return true; 124 } 125 126 public boolean isRouteFree() { 127 for (int i=0; i<getBlockOrders().size(); i++) { 128 OBlock block_i = getBlockOrderAt(i).getBlock(); 129 if ((block_i.getState() & OBlock.ALLOCATED) == OBlock.ALLOCATED) { 130 log.debug("{}: block {} is allocated to {} (in isRouteFree).",_trainName,block_i.getDisplayName(),block_i.getAllocatingWarrantName()); 131 if (!block_i.isAllocatedTo(this)) { 132 return false; 133 } 134 } 135 if ( ((block_i.getState() & Block.OCCUPIED) == Block.OCCUPIED) && (i>0) ) { 136 log.debug("{}: block {} is not free (in isRouteFree).",_trainName,block_i.getDisplayName()); 137 return false; 138 } 139 } 140 return true; 141 } 142 143 boolean isRouteAllocated() { 144 for (int i=0; i<getBlockOrders().size(); i++) { 145 OBlock block_i = getBlockOrderAt(i).getBlock(); 146 if (!block_i.isAllocatedTo(this)) { 147 log.debug("{}: block {} is not allocated to this warrant (in isRouteAllocated).",_trainName,block_i.getDisplayName()); 148 return false; 149 } 150 } 151 return true; 152 } 153 154 /** 155 * Callback from acquireThrottle() when the throttle has become available.sync 156 */ 157 @Override 158 public void notifyThrottleFound(DccThrottle throttle) { 159 _throttle = throttle; 160 if (throttle == null) { 161 abortWarrant("notifyThrottleFound: null throttle(?)!"); 162 firePropertyChange("throttleFail", null, Bundle.getMessage("noThrottle")); 163 return; 164 } 165 if (_runMode == MODE_LEARN) { 166 abortWarrant("notifyThrottleFound: No LEARN mode for SCWarrant"); 167 InstanceManager.throttleManagerInstance().releaseThrottle(throttle, this); 168 firePropertyChange("throttleFail", null, Bundle.getMessage("noThrottle")); 169 return; 170 } 171 log.debug("{} notifyThrottleFound address= {} _runMode= {}",_trainName,throttle.getLocoAddress(),_runMode); 172 173 startupWarrant(); 174 175 firePropertyChange("WarrantStart", Integer.valueOf(MODE_NONE), Integer.valueOf(_runMode)); 176 runSignalControlledTrain(); 177 } 178 179 /** 180 * Generate status message to show in warrant table. 181 * {@inheritDoc} 182 **/ 183 @Override 184 protected synchronized String getRunningMessage() { 185 if (_throttle == null) { 186 // The warrant is not active 187 return super.getRunningMessage(); 188 } else if (_runMode != MODE_RUN) { 189 return ("Idle"); 190 } else { 191 String block = getBlockOrderAt(getCurrentOrderIndex()).getBlock().getDisplayName(); 192 String signal = "no signal"; 193 String aspect = "none"; 194 if (_nextSignal != null) { 195 signal = _nextSignal.getDisplayName(); 196 if (_nextSignal instanceof SignalHead) { 197 int appearance = ((SignalHead) _nextSignal).getAppearance(); 198 aspect = "appearance "+appearance; 199 } else { 200 aspect = ((SignalMast) _nextSignal).getAspect(); 201 } 202 } 203 return Bundle.getMessage("SCWStatus", block, getCurrentOrderIndex(), _throttle.getSpeedSetting(),signal,aspect); 204 } 205 } 206 207 /****************************************************************************************************** 208 * Use _throttle to control the train. 209 * 210 * Get notified of signals, block occupancy and take care of block allocation status to determine speed. 211 * 212 * We have three speeds: Stop == SPEED_STOP 213 * Normal == SPEED_NORMAL 214 * Anything else == SPEED_MID (Limited, Medium, Slow, Restricted) 215 * 216 * If you have blocks large enough to ramp speed nicely up and down and to have further control 217 * of speed settings: Use a normal warrant and not a signal controlled one. 218 * 219 * This is "the main loop" for running a Signal Controlled Warrant 220 ******************************************************************************************************/ 221 protected void runSignalControlledTrain () { 222 waitForStartblockToGetOccupied(); 223 allocateBlocksAndSetTurnouts(0); 224 setTrainDirection(); 225 SCTrainRunner thread = new SCTrainRunner(this); 226 Thread t = jmri.util.ThreadingUtil.newThread(thread); 227 t.setName("SCTrainRunner"); 228 t.start(); 229 } 230 231 /** 232 * Wait until there is a train in the start block. 233 * @return true if block not UNOCCUPIED 234 */ 235 protected boolean isStartBlockOccupied() { 236 int blockState = getBlockOrderAt(0).getBlock().getState(); 237 return (blockState & Block.UNOCCUPIED) != Block.UNOCCUPIED; 238 } 239 240 protected synchronized void waitForStartblockToGetOccupied() { 241 while (!isStartBlockOccupied()) { 242 log.debug("{} waiting for start block {} to become occupied",_trainName,getBlockOrderAt(0).getBlock().getDisplayName()); 243 try { 244 // We will not be woken up by goingActive, since we have not allocated the start block yet. 245 // So do a timed wait. 246 wait(2500); 247 } catch (InterruptedException ie) { 248 log.debug("{} waitForStartblockToGetOccupied InterruptedException {}",_trainName,ie,ie); 249 } 250 catch(Exception e){ 251 log.debug("{} waitForStartblockToGetOccupied unexpected exception {}",_trainName,e,e); 252 } 253 } 254 } 255 256 /** 257 * Set this train to run backwards or forwards as specified in the command list. 258 */ 259 public void setTrainDirection () { 260 _throttle.setIsForward(forward); 261 } 262 263 /** 264 * Is the next block free or occupied, i.e do we risk to crash into an other train, if we proceed? 265 * And is it allocated to us? 266 * @return true if allocated to us and unoccupied, else false. 267 */ 268 public boolean isNextBlockFreeAndAllocated() { 269 BlockOrder bo = getBlockOrderAt(getCurrentOrderIndex()+1); 270 if (bo == null) return false; 271 int blockState = bo.getBlock().getState(); 272 if (blockState == (Block.UNOCCUPIED | OBlock.ALLOCATED)) { 273 return getBlockOrderAt(getCurrentOrderIndex()+1).getBlock().isAllocatedTo(this); 274 } else { 275 return false; 276 } 277 } 278 279 /** 280 * Find the next signal along our route and setup subscription for status changes on that signal. 281 */ 282 public void getAndGetNotifiedFromNextSignal() { 283 if (_nextSignal != null) { 284 log.debug("{} getAndGetNotifiedFromNextSignal removing property listener for signal {}",_trainName,_nextSignal.getDisplayName()); 285 _nextSignal.removePropertyChangeListener(this); 286 _nextSignal = null; 287 } 288 for (int i = getCurrentOrderIndex()+1; i <= getBlockOrders().size()-1; i++) { 289 BlockOrder bo = getBlockOrderAt(i); 290 if (bo == null) { 291 log.debug("{} getAndGetNotifiedFromNextSignal could not find a BlockOrder for index {}",_trainName,i); 292 } else if (bo.getEntryName().equals("")) { 293 log.debug("{} getAndGetNotifiedFromNextSignal could not find an entry to Block for index {}",_trainName,i); 294 } else { 295 log.debug("{} getAndGetNotifiedFromNextSignal examines block {} with entryname = {}",_trainName,bo.getBlock().getDisplayName(),bo.getEntryName()); 296 _nextSignal = bo.getSignal(); 297 if (_nextSignal != null) { 298 log.debug("{} getAndGetNotifiedFromNextSignal found a new signal to listen to: {}",_trainName,_nextSignal.getDisplayName()); 299 break; 300 } 301 } 302 } 303 if (_nextSignal != null) { 304 _nextSignal.addPropertyChangeListener(this); 305 } 306 } 307 308 /** 309 * Are we still in the start block? 310 * @return true if still in start block 311 */ 312 boolean inStartBlock() { 313 return (getCurrentOrderIndex() == 0); 314 } 315 316 /** 317 * Are we close to the destination block? 318 * @return true if close 319 */ 320 boolean approchingDestination() { 321 float distance = 0; 322 float blockLength; 323 if (getCurrentOrderIndex() == getBlockOrders().size()-2) { 324 // We are in the block just before destination 325 return true; 326 } 327 // Calculate the distance to destination 328 for (int i = getCurrentOrderIndex(); i <= getBlockOrders().size()-2; i++) { 329 blockLength = getBlockOrderAt(i).getBlock().getLengthCm(); 330 if (blockLength < 1) { 331 // block length not set for at least one block 332 return false; 333 } 334 distance += blockLength; 335 } 336 return (distance < 1.5*getMaxBlockLength()); 337 } 338 339 /** 340 * Move the train if _nextSignal permits. If there is no next signal, we will move forward with half speed. 341 */ 342 SignalSpeedMap _speedMap = InstanceManager.getDefault(SignalSpeedMap.class); 343 public void setSpeedFromNextSignal () { 344 String speed = null; 345 if (_nextSignal == null) { 346 _throttle.setSpeedSetting(speedFactor*SPEED_UNSIGNALLED); 347 } else { 348 if (_nextSignal instanceof SignalHead) { 349 int appearance = ((SignalHead) _nextSignal).getAppearance(); 350 speed = _speedMap.getAppearanceSpeed(((SignalHead) _nextSignal).getAppearanceName(appearance)); 351 log.debug("{} SignalHead {} shows appearance {} which maps to speed {}",_trainName,((SignalHead) _nextSignal).getDisplayName(),appearance,speed); 352 } else { 353 String aspect = ((SignalMast) _nextSignal).getAspect(); 354 speed = _speedMap.getAspectSpeed((aspect == null ? "" : aspect), 355 ((SignalMast) _nextSignal).getSignalSystem()); 356 log.debug("{} SignalMast {} shows aspect {} which maps to speed {}",_trainName,((SignalMast) _nextSignal).getDisplayName(),aspect,speed); 357 } 358 float speed_f = (float) (_speedMap.getSpeed(speed) / 125.); 359 // Ease the speed, if we are approaching the destination block 360 if ((approchingDestination() || inStartBlock()) && (speed_f > SPEED_UNSIGNALLED)) { 361 speed_f = SPEED_UNSIGNALLED; 362 } 363 _throttle.setSpeedSetting(speedFactor*speed_f); 364 } 365 } 366 367 /** 368 * Do what the title says. But make sure not to set the turnouts if already done, since that 369 * would just cause all signals to go to Stop aspects and thus cause a jerky train movement. 370 * @param startIndex Allocate starting with this index 371 */ 372 protected void allocateBlocksAndSetTurnouts(int startIndex) { 373 log.debug("{} allocateBlocksAndSetTurnouts startIndex={} _orders.size()={}",_trainName,startIndex,getBlockOrders().size()); 374 for (int i = startIndex; i < getBlockOrders().size(); i++) { 375 log.debug("{} allocateBlocksAndSetTurnouts for loop #{}",_trainName,i); 376 BlockOrder bo = getBlockOrderAt(i); 377 OBlock block = bo.getBlock(); 378 String pathAlreadySet = block.isPathSet(bo.getPathName()); 379 if (pathAlreadySet == null) { 380 String message = null; 381 if ((block.getState() & Block.OCCUPIED) != 0) { 382 log.info("{} block allocation failed {} not allocated, but Occupied.",_trainName,block.getDisplayName()); 383 message = " block allocation failed "; 384 } 385 if (message == null) { 386 message = block.allocate(this); 387 if (message != null) { 388 log.info("{} block allocation failed {}",_trainName,message); 389 } 390 } 391 if (message == null) { 392 message = bo.setPath(this); 393 } 394 if (message != null) { 395 log.debug("{} path setting failed for {} at block {} {}",_trainName,getDisplayName(),block.getDisplayName(),message); 396 if (_stoppingBlock != null) { 397 _stoppingBlock.removePropertyChangeListener(this); 398 } 399 _stoppingBlock = block; 400 _stoppingBlock.addPropertyChangeListener(this); 401 // This allocation failed. Do not attempt to allocate the rest of the route.allocation 402 // That would potentially lead to deadlock situations where two warrants are competing 403 // and each getting every second block along the same route. 404 return; 405 } 406 } else if (pathAlreadySet.equals(this.getDisplayName())) { 407 log.debug("{} Path {} already set (and thereby block allocated) for {}",_trainName,bo.getPathName(),pathAlreadySet); 408 } else { 409 log.info("{} Block allocation failed: Path {} already set (and thereby block allocated) for {}",_trainName,bo.getPathName(),pathAlreadySet); 410 return; 411 } 412 } 413 } 414 415 /** 416 * Block in the route going active. 417 * Make sure to allocate the rest of the route, update our present location and then tell 418 * the main loop to find a new throttle setting. 419 */ 420 @Override 421 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification="NotifyAll call triggers recomputation") 422 protected void goingActive(OBlock block) { 423 int activeIdx = getIndexOfBlockAfter(block, getCurrentOrderIndex()); 424 log.debug("{} **Block \"{}\" goingActive. activeIdx= {}" 425 + ", getCurrentOrderIndex()= {}" 426 + " - warrant= {} _runMode = {} _throttle==null: {}",_trainName,block.getDisplayName(),activeIdx,getCurrentOrderIndex(),getDisplayName(),_runMode,(_throttle==null)); 427 if (_runMode != MODE_RUN) { 428 // if we are not running, we must not think that we are going to the next block - it must be another train 429 return; 430 } 431 if (_throttle == null || _throttle.getSpeedSetting() == SPEED_STOP) { 432 // if we are not running, we must not think that we are going to the next block - it must be another train 433 return; 434 } 435 if (activeIdx <= 0) { 436 // The block going active is not part of our route ahead 437 log.debug("{} Block going active is not part of this trains route forward",_trainName); 438 } else if (activeIdx == getCurrentOrderIndex()) { 439 // Unusual case of current block losing detection, then regaining it. i.e. dirty track, derail etc. 440 log.debug("{} Current block becoming active - ignored",_trainName); 441 } else if (activeIdx == getCurrentOrderIndex() + 1) { 442 // not necessary: It is done in the main loop in SCTrainRunner.run: allocateBlocksAndSetTurnouts(getCurrentOrderIndex()+1) 443 // update our present location 444 incrementCurrentOrderIndex(); 445 // fire property change (entered new block) 446 firePropertyChange("blockChange", getBlockAt(getCurrentOrderIndex() - 1), getBlockAt(getCurrentOrderIndex())); 447 // now let the main loop adjust speed. 448 synchronized(this) { 449 notifyAll(); 450 } 451 } else { 452 log.debug("{} Rogue occupation of block.",_trainName); 453 // now let the main loop stop for a train that is coming in our immediate way. 454 synchronized(this) { 455 notifyAll(); 456 } 457 } 458 } 459 460 /** 461 * Block in the route is going Inactive. 462 * Release the blocks that we have left. 463 * Check if current block has been left (i.e. we have left our route) and stop the train in that case. 464 */ 465 @Override 466 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification="See comment above notify call") 467 protected void goingInactive(OBlock block) { 468 int idx = getIndexOfBlockAfter(block, 0); // if idx >= 0, it is in this warrant 469 log.debug("{} Block \"{}\" goingInactive. idx= {}" 470 + ", getCurrentOrderIndex()= {}" 471 + " - warrant= {}",_trainName,block.getDisplayName(),idx,getCurrentOrderIndex(),getDisplayName()); 472 if (_runMode != MODE_RUN) { 473 return; 474 } 475 if (idx < getCurrentOrderIndex()) { 476 if (_allowShallowAllocation) { 477 deallocateUpToBlock(idx); 478 } 479 } else if (idx == getCurrentOrderIndex()) { 480 // train is lost 481 log.debug("{} LOST TRAIN firePropertyChange(\"blockChange\", {}" 482 + ", null) - warrant= {}",_trainName,block.getDisplayName(),getDisplayName()); 483 } 484 // now let the main loop stop our train if this means that the train is now entirely within the last block. 485 // Or let the train continue if an other train that was in its way has now moved. 486 synchronized(this) { 487 notifyAll(); 488 } 489 } 490 491 /** 492 * Deallocate all blocks up to and including idx, but only on these conditions in order to ensure that only a consecutive list of blocks are allocated at any time: 493 * 1. Only if our train has left not only this block, but also all previous blocks. 494 * 2. Only if the block shall not be re-used ahead and all block up until the block are allocated. 495 * @param idx Index of final block 496 */ 497 protected void deallocateUpToBlock(int idx) { 498 for (int i=0; i<=idx; i++) { 499 OBlock block_i = getBlockOrderAt(i).getBlock(); 500 if (block_i.isAllocatedTo(this)) { 501 if ((block_i.getState() & Block.UNOCCUPIED) != Block.UNOCCUPIED) { 502 //Do not deallocate further blocks, since this one is still allocated to us and not free. 503 log.debug("{} Block {} occupied. Not de-allocating any further",_trainName,block_i.getDisplayName()); 504 return; 505 } 506 boolean deAllocate = true; 507 // look ahead to see if block_i is reused in the remaining part of the route. 508 for (int j= getCurrentOrderIndex(); j<getBlockOrders().size(); j++) { 509 OBlock block_j = getBlockOrderAt(j).getBlock(); 510 if (!block_j.isAllocatedTo(this)) { 511 // There is an unallocated block ahead before we have found block_i is re-used. So deallocate block_i 512 break; 513 } 514 if (block_i == block_j) { 515 // clock_i is re-used, and we have no "holes" in the string of allocated blocks before it. So do not deallocate. 516 deAllocate = false; 517 break; 518 } 519 } 520 if (deAllocate) { 521 log.debug("{} De-allocating block {}",_trainName,block_i.getDisplayName()); 522 block_i.deAllocate(this); 523 } 524 } 525 } 526 } 527 528 /** 529 * Something has fired a property change event. 530 * React if: 531 * - it is a warrant that we need to synchronize with. And then again: Why? 532 * - it is _nextSignal 533 * Do not worry about sensors and blocks. They are handled by goingActive and goingInactive. 534 */ 535 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = {"UW_UNCOND_WAIT", "NN_NAKED_NOTIFY"}, 536 justification = "Unconditional wait is give the warrant that now has _stoppingBlock allocated a little time to deallocate it. This occurs after this method sets _stoppingBlock to null. NotifyAll passing event, not state.") 537 @Override 538 public void propertyChange(java.beans.PropertyChangeEvent evt) { 539 if (!(evt.getSource() instanceof NamedBean)) { 540 log.debug("{} propertyChange \"{}\" old= {} new= {}",_trainName,evt.getPropertyName(),evt.getOldValue(),evt.getNewValue()); 541 return; 542 } 543 String property = evt.getPropertyName(); 544 log.debug("{} propertyChange \"{}\" new= {} source= {} - warrant= {}",_trainName,property,evt.getNewValue(),((NamedBean) evt.getSource()).getDisplayName(),getDisplayName()); 545 if (_nextSignal != null && _nextSignal == evt.getSource()) { 546 if (property.equals("Aspect") || property.equals("Appearance")) { 547 // The signal controlling this warrant has changed. Adjust the speed (in runSignalControlledTrain) 548 synchronized(this) { 549 notifyAll(); 550 } 551 return; 552 } 553 } 554 synchronized(this) { 555 if (_stoppingBlock != null) { 556 log.debug("{} CHECKING STOPPINGBLOCKEVENT ((NamedBean) evt.getSource()).getDisplayName() = '{}' evt.getPropertyName() = '{}' evt.getNewValue() = {} _throttle==null: {}",_trainName,((NamedBean) evt.getSource()).getDisplayName(),evt.getPropertyName(),evt.getNewValue(),(_throttle==null)); 557 if (((NamedBean) evt.getSource()).getDisplayName().equals(_stoppingBlock.getDisplayName()) && 558 evt.getPropertyName().equals("state") && 559 (((Number) evt.getNewValue()).intValue() & Block.UNOCCUPIED) == Block.UNOCCUPIED) { 560 log.debug("{} being aware that Block {} has become free",_trainName,((NamedBean) evt.getSource()).getDisplayName()); 561 _stoppingBlock.removePropertyChangeListener(this); 562 _stoppingBlock = null; 563 // we might be waiting for this block to become free 564 // Give the warrant that now has _stoppingBlock allocated a little time to deallocate it 565 try { 566 wait(100); 567 } catch (InterruptedException e) { 568 // ignoring interrupted exceptions 569 } catch(Exception e){ 570 log.debug(WAIT_UNEXPECTED_EXCEPTION,_trainName,e,e); 571 } 572 // And then let our main loop continue 573 notifyAll(); 574 return; 575 } 576 if (((NamedBean) evt.getSource()).getDisplayName().equals(getBlockOrderAt(0).getBlock().getDisplayName()) && 577 evt.getPropertyName().equals("state") && 578 (((Number) evt.getOldValue()).intValue() & Block.UNOCCUPIED) == Block.UNOCCUPIED && 579 (((Number) evt.getNewValue()).intValue() & Block.UNOCCUPIED) != Block.UNOCCUPIED && 580 _throttle==null && _runMode==MODE_RUN) { 581 // We are waiting for the train to arrive at the starting block, and that has just happened now. 582 log.debug("{} has arrived at starting block",_trainName); 583 String msg = null; 584 msg = acquireThrottle(); 585 if (msg != null) { 586 log.warn("propertyChange of \"{}\" has message: {}", property, msg); 587 _message = msg; 588 abortWarrant(msg); 589 } 590 } 591 } 592 } 593 } 594 595 596 /** 597 * Make sure to free up additional resources for a running SCWarrant. 598 */ 599 @Override 600 public synchronized void stopWarrant(boolean abort, boolean turnOffFunctions) { 601 if (_nextSignal != null) { 602 _nextSignal.removePropertyChangeListener(this); 603 _nextSignal = null; 604 } 605 super.stopWarrant(abort, false); 606 _message = null; 607 } 608 609 /******************************************************************************************************************************* 610 * The waiting for event must happen in a separate thread. 611 * Therefore the main code of runSignalControlledTrain is put in this class. 612 *******************************************************************************************************************************/ 613 static LinkedBlockingQueue<SCWarrant> waitToRunQ = new LinkedBlockingQueue<>(); 614 private class SCTrainRunner implements Runnable { 615 private static final String INTERRUPTED_EXCEPTION = "{} InterruptedException {}"; 616 SCWarrant _warrant = null; 617 SCTrainRunner(SCWarrant warrant) { 618 _warrant = warrant; 619 } 620 621 /** 622 * When not using shallow allocation, warrants will have to wait until the entire route 623 * is free and allocated to that particular warrant, before strting to run the train. 624 * This method uses the waitToRunQ to ensure that warrants do not just compete about 625 * resources, but waits in line until their route is free and unallocated. 626 */ 627 boolean isItOurTurn() { 628 for (SCWarrant e : waitToRunQ) { 629 try { // using another SCWarrant might be dangerous - it might no longer exist 630 log.debug("{} isItOurTurn is checking {}",_trainName,e.getDisplayName()); 631 if (e.isRouteFree()) { 632 if (e == _warrant) { 633 log.debug("{} isItOurTurn: We are first in line",_trainName); 634 return true; 635 } else { 636 log.debug("{} isItOurTurn: An other warrant is before us",_trainName); 637 return false; 638 } 639 } else { 640 if (e == _warrant) { 641 log.debug("{} isItOurTurn: our route is not free - keep waiting",_trainName); 642 return false; 643 } 644 } 645 } catch (Exception ex) { 646 log.debug("{} isItOurTurn exception ignored: {}",_trainName,ex,ex); 647 } 648 } 649 // we should not reach this point, but if we do, we should try to run 650 log.debug("{} isItOurTurn: No warrant with a free route is waiting. Let us try our luck, so that we are not all waiting for each other.",_trainName); 651 return true; 652 } 653 654 @Override 655 public void run() { 656 synchronized(_warrant) { 657 658 // Make sure the entire route is allocated before attemting to start the train 659 if (!_allowShallowAllocation) { 660 boolean AllocationDone = false; 661 log.debug("{} ENTERING QUEUE ",_trainName); 662 try { 663 waitToRunQ.put(_warrant); 664 } catch (InterruptedException ie) { 665 log.debug("{} waitToRunQ.put InterruptedException {}",_trainName,ie,ie); 666 } 667 668 while (!AllocationDone) { 669 log.debug("{} Route is not allocated yet..... ",_trainName); 670 while (!isItOurTurn()) { 671 deAllocate(); 672 log.debug("{} Waiting for route to become free ....",_trainName); 673 try { 674 _warrant.wait(2500 + Math.round(1000*Math.random())); 675 } catch (InterruptedException ie) { 676 log.debug("{} _warrant.wait InterruptedException {}",_trainName,ie,ie); 677 } 678 catch(Exception e){ 679 log.debug("{} _warrant.wait unexpected exception {}",_trainName,e,e); 680 } 681 } 682 allocateStartBlock(); 683 allocateBlocksAndSetTurnouts(1); 684 AllocationDone = isRouteAllocated(); 685 if (!AllocationDone) { 686 deAllocate(); 687 try { 688 _warrant.wait(10000 + Math.round(1000*Math.random())); 689 } catch (InterruptedException ie) { 690 log.debug("{} _warrant.wait !AllocationDone InterruptedException {}",_trainName,ie,ie); 691 } 692 catch(Exception e){ 693 log.debug("{} _warrant.wait !AllocationDone unexpected exception {}",_trainName,e,e); 694 } 695 } 696 } 697 698 log.debug("{} LEAVING QUEUE ",_trainName); 699 waitToRunQ.remove(_warrant); 700 701 while (!allTurnoutsSet()) { 702 log.debug("{} Waiting for turnouts to settle ....",_trainName); 703 try { 704 _warrant.wait(2500); 705 } catch (InterruptedException ie) { 706 log.debug("{} _warrant.wait InterruptedException {}",_trainName,ie,ie); 707 } 708 catch(Exception e){ 709 log.debug("{} _warrant.wait unexpected exception {}",_trainName,e,e); 710 } 711 } 712 // And then wait another 3 seconds to make the last turnout settle - just in case the command station is not giving correct feedback 713 try { 714 _warrant.wait(3000); 715 } catch (InterruptedException ie) { 716 log.debug(INTERRUPTED_EXCEPTION,_trainName,ie,ie); 717 } 718 catch(Exception e){ 719 log.debug(WAIT_UNEXPECTED_EXCEPTION,_trainName,e,e); 720 } 721 } 722 723 // Do not include the stopping block in this while loop. It will be handled after the loop. 724 List<BlockOrder> orders = getBlockOrders(); 725 while (_warrant.getCurrentOrderIndex() < orders.size()-1 && _runMode == MODE_RUN) { 726 log.debug("{} runSignalControlledTrain entering while loop. getCurrentOrderIndex()={} _orders.size()={}",_warrant._trainName,getCurrentOrderIndex(),orders.size()); 727 if (_throttle == null) { 728 // We lost our throttle, so we might have a runaway train 729 emergencyStop(); 730 } 731 if (_allowShallowAllocation) { 732 allocateBlocksAndSetTurnouts(_warrant.getCurrentOrderIndex()); 733 } 734 if (isNextBlockFreeAndAllocated()) { 735 getAndGetNotifiedFromNextSignal(); 736 setSpeedFromNextSignal(); 737 } else { 738 try { 739 _throttle.setSpeedSetting(SPEED_STOP); 740 getBlockOrderAt(getCurrentOrderIndex()+1).getBlock().addPropertyChangeListener(_warrant); 741 log.debug("{} runSignalControlledTrain stops train due to block not free: {}",_warrant._trainName,getBlockOrderAt(getCurrentOrderIndex()+1).getBlock().getDisplayName()); 742 } catch (Exception e) { 743 emergencyStop(); 744 log.debug("{} exception trying to stop train due to block not free: {}",_warrant._trainName,e,e); 745 } 746 } 747 log.debug("{} {} before wait {} getCurrentOrderIndex(): {} orders.size(): {}",_warrant._trainName,_warrant.getDisplayName(),_warrant.getRunningMessage(),_warrant.getCurrentOrderIndex(),orders.size()); 748 try { 749 // We do a timed wait for the sake of robustness, even though we will be woken up by all relevant events. 750 _warrant.wait(2000); 751 } catch (InterruptedException ie) { 752 log.debug(INTERRUPTED_EXCEPTION,_warrant._trainName,ie,ie); 753 } 754 catch(Exception e){ 755 log.debug(WAIT_UNEXPECTED_EXCEPTION,_trainName,e,e); 756 } 757 log.debug("{} {} after wait {} getCurrentOrderIndex(): {} orders.size(): {}",_warrant._trainName,_warrant.getDisplayName(),_warrant.getRunningMessage(),_warrant.getCurrentOrderIndex(),orders.size()); 758 } 759 // We are now in the stop block. Move forward for half a second with half speed until the block before the stop block is free. 760 log.debug("{} runSignalControlledTrain out of while loop, i.e. train entered stop block getCurrentOrderIndex()={}" 761 + " orders.size()={} waiting for train to clear block {}", 762 _warrant._trainName,getCurrentOrderIndex(),orders.size(),getBlockAt(orders.size()-2).getDisplayName()); 763 if (_throttle==null) { 764 emergencyStop(); 765 log.debug("Throttle lost at stop block"); 766 } else { 767 _throttle.setSpeedSetting(speedFactor*SPEED_TO_PLATFORM); 768 } 769 while ((getBlockAt(orders.size()-2).getState()&Block.OCCUPIED)==Block.OCCUPIED && getBlockAt(orders.size()-2).isAllocatedTo(_warrant)) { 770 log.debug(" runSignalControlledTrain {} entering wait. Block {} free: {} allocated to this warrant: {}", 771 _warrant._trainName, 772 getBlockAt(orders.size()-2).getDisplayName(), 773 getBlockAt(orders.size()-2).isFree(), 774 getBlockAt(orders.size()-2).isAllocatedTo(_warrant)); 775 try { 776 // This does not need to be a timed wait, since we will get interrupted once the block is free 777 // However, the functionality is more robust with a timed wait. 778 _warrant.wait(500); 779 } catch (InterruptedException ie) { 780 log.debug(INTERRUPTED_EXCEPTION,_warrant._trainName,ie,ie); 781 } 782 catch(Exception e){ 783 log.debug(WAIT_UNEXPECTED_EXCEPTION,_trainName,e,e); 784 } 785 log.debug("{} runSignalControlledTrain woken after last wait.... _orders.size()={}",_warrant._trainName,orders.size()); 786 } 787 if (timeToPlatform > 100) { 788 log.debug("{} runSignalControlledTrain is now fully into the stopping block. Proceeding for {} miliseconds",_warrant._trainName,timeToPlatform); 789 long timeWhenDone = System.currentTimeMillis() + timeToPlatform; 790 long remaining; 791 while ((remaining = timeWhenDone - System.currentTimeMillis()) > 0) { 792 try { 793 log.debug("{} running slowly to platform for {} miliseconds",_warrant._trainName,remaining); 794 _warrant.wait(remaining); 795 } catch (InterruptedException e) { 796 log.debug(INTERRUPTED_EXCEPTION,_warrant._trainName,e,e); 797 } 798 } 799 } 800 log.debug("{} runSignalControlledTrain STOPPING TRAIN IN STOP BLOCK",_warrant._trainName); 801 if (_throttle==null) { 802 emergencyStop(); 803 log.debug("Throttle lost after stop block"); 804 } else { 805 _throttle.setSpeedSetting(SPEED_STOP); 806 } 807 stopWarrant(false, false); 808 } 809 } 810 811 /** 812 * If we think we might have a runaway train - take the power of the entire layout. 813 */ 814 private void emergencyStop() { 815 PowerManager manager = InstanceManager.getNullableDefault(PowerManager.class); 816 if (manager == null) { 817 log.debug("{} EMERGENCY STOP IMPOSSIBLE: NO POWER MANAGER",_trainName); 818 return; 819 } 820 try { 821 manager.setPower(PowerManager.OFF); 822 } catch (Exception e) { 823 log.debug("{} EMERGENCY STOP FAILED WITH EXCEPTION: {}",_trainName,e,e); 824 } 825 log.debug("{} EMERGENCY STOP",_trainName); 826 } 827 828 } 829 830 /* What super does currently is fine. But FindBug reports EQ_DOESNT_OVERRIDE_EQUALS 831 * FindBug wants us to duplicate and override anyway 832 */ 833 @Override 834 public boolean equals(Object obj) { 835 return super.equals(obj); 836 } 837 838 /* What super does currently is fine. But FindBug reports HE_EQUALS_NO_HASHCODE 839 * FindBug wants us to duplicate and override anyway 840 */ 841 @Override 842 public int hashCode() { 843 return super.hashCode(); 844 } 845 846 /** 847 * 848 */ 849 private static final Logger log = LoggerFactory.getLogger(SCWarrant.class); 850}