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 if (getBlockOrderAt(getCurrentOrderIndex()) == null) { 192 return ("Uninitialized"); 193 } 194 String block = getBlockOrderAt(getCurrentOrderIndex()).getBlock().getDisplayName(); 195 String signal = "no signal"; 196 String aspect = "none"; 197 if (_nextSignal != null) { 198 signal = _nextSignal.getDisplayName(); 199 if (_nextSignal instanceof SignalHead) { 200 int appearance = ((SignalHead) _nextSignal).getAppearance(); 201 aspect = "appearance "+appearance; 202 } else { 203 aspect = ((SignalMast) _nextSignal).getAspect(); 204 } 205 } 206 return Bundle.getMessage("SCWStatus", block, getCurrentOrderIndex(), _throttle.getSpeedSetting(),signal,aspect); 207 } 208 } 209 210 /****************************************************************************************************** 211 * Use _throttle to control the train. 212 * 213 * Get notified of signals, block occupancy and take care of block allocation status to determine speed. 214 * 215 * We have three speeds: Stop == SPEED_STOP 216 * Normal == SPEED_NORMAL 217 * Anything else == SPEED_MID (Limited, Medium, Slow, Restricted) 218 * 219 * If you have blocks large enough to ramp speed nicely up and down and to have further control 220 * of speed settings: Use a normal warrant and not a signal controlled one. 221 * 222 * This is "the main loop" for running a Signal Controlled Warrant 223 ******************************************************************************************************/ 224 protected void runSignalControlledTrain () { 225 allocateBlocksAndSetTurnouts(0); 226 setTrainDirection(); 227 SCTrainRunner thread = new SCTrainRunner(this); 228 Thread t = jmri.util.ThreadingUtil.newThread(thread); 229 t.setName("SCTrainRunner"); 230 t.start(); 231 } 232 233 /** 234 * Wait until there is a train in the start block. 235 * @return true if block not UNOCCUPIED 236 */ 237 protected boolean isStartBlockOccupied() { 238 int blockState = getBlockOrderAt(0).getBlock().getState(); 239 return (blockState & Block.UNOCCUPIED) != Block.UNOCCUPIED; 240 } 241 242 protected synchronized void waitForStartblockToGetOccupied() { 243 while (!isStartBlockOccupied()) { 244 log.debug("{} waiting for start block {} to become occupied",_trainName,getBlockOrderAt(0).getBlock().getDisplayName()); 245 try { 246 // We will not be woken up by goingActive, since we have not allocated the start block yet. 247 // So do a timed wait. 248 wait(2500); 249 } catch (InterruptedException ie) { 250 log.debug("{} waitForStartblockToGetOccupied InterruptedException {}",_trainName,ie,ie); 251 } 252 catch(Exception e){ 253 log.debug("{} waitForStartblockToGetOccupied unexpected exception {}",_trainName,e,e); 254 } 255 } 256 } 257 258 /** 259 * Set this train to run backwards or forwards as specified in the command list. 260 */ 261 public void setTrainDirection () { 262 _throttle.setIsForward(forward); 263 } 264 265 /** 266 * Is the next block free or occupied, i.e do we risk to crash into an other train, if we proceed? 267 * And is it allocated to us? 268 * @return true if allocated to us and unoccupied, else false. 269 */ 270 public boolean isNextBlockFreeAndAllocated() { 271 BlockOrder bo = getBlockOrderAt(getCurrentOrderIndex()+1); 272 if (bo == null) return false; 273 int blockState = bo.getBlock().getState(); 274 if (blockState == (Block.UNOCCUPIED | OBlock.ALLOCATED)) { 275 return getBlockOrderAt(getCurrentOrderIndex()+1).getBlock().isAllocatedTo(this); 276 } else { 277 return false; 278 } 279 } 280 281 /** 282 * Find the next signal along our route and setup subscription for status changes on that signal. 283 */ 284 public void getAndGetNotifiedFromNextSignal() { 285 if (_nextSignal != null) { 286 log.debug("{} getAndGetNotifiedFromNextSignal removing property listener for signal {}",_trainName,_nextSignal.getDisplayName()); 287 _nextSignal.removePropertyChangeListener(this); 288 _nextSignal = null; 289 } 290 for (int i = getCurrentOrderIndex()+1; i <= getBlockOrders().size()-1; i++) { 291 BlockOrder bo = getBlockOrderAt(i); 292 if (bo == null) { 293 log.debug("{} getAndGetNotifiedFromNextSignal could not find a BlockOrder for index {}",_trainName,i); 294 } else if (bo.getEntryName().equals("")) { 295 log.debug("{} getAndGetNotifiedFromNextSignal could not find an entry to Block for index {}",_trainName,i); 296 } else { 297 log.debug("{} getAndGetNotifiedFromNextSignal examines block {} with entryname = {}",_trainName,bo.getBlock().getDisplayName(),bo.getEntryName()); 298 _nextSignal = bo.getSignal(); 299 if (_nextSignal != null) { 300 log.debug("{} getAndGetNotifiedFromNextSignal found a new signal to listen to: {}",_trainName,_nextSignal.getDisplayName()); 301 break; 302 } 303 } 304 } 305 if (_nextSignal != null) { 306 _nextSignal.addPropertyChangeListener(this); 307 } 308 } 309 310 /** 311 * Are we still in the start block? 312 * @return true if still in start block 313 */ 314 boolean inStartBlock() { 315 return (getCurrentOrderIndex() == 0); 316 } 317 318 /** 319 * Are we close to the destination block? 320 * @return true if close 321 */ 322 boolean approchingDestination() { 323 float distance = 0; 324 float blockLength; 325 if (getCurrentOrderIndex() == getBlockOrders().size()-2) { 326 // We are in the block just before destination 327 return true; 328 } 329 // Calculate the distance to destination 330 for (int i = getCurrentOrderIndex(); i <= getBlockOrders().size()-2; i++) { 331 blockLength = getBlockOrderAt(i).getBlock().getLengthCm(); 332 if (blockLength < 1) { 333 // block length not set for at least one block 334 return false; 335 } 336 distance += blockLength; 337 } 338 return (distance < 1.5*getMaxBlockLength()); 339 } 340 341 /** 342 * Move the train if _nextSignal permits. If there is no next signal, we will move forward with half speed. 343 */ 344 SignalSpeedMap _speedMap = InstanceManager.getDefault(SignalSpeedMap.class); 345 public void setSpeedFromNextSignal () { 346 String speed = null; 347 if (_nextSignal == null) { 348 _throttle.setSpeedSetting(speedFactor*SPEED_UNSIGNALLED); 349 } else { 350 if (_nextSignal instanceof SignalHead) { 351 int appearance = ((SignalHead) _nextSignal).getAppearance(); 352 speed = _speedMap.getAppearanceSpeed(((SignalHead) _nextSignal).getAppearanceName(appearance)); 353 log.debug("{} SignalHead {} shows appearance {} which maps to speed {}", 354 _trainName, _nextSignal.getDisplayName(),appearance,speed); 355 } else { 356 String aspect = ((SignalMast) _nextSignal).getAspect(); 357 speed = _speedMap.getAspectSpeed((aspect == null ? "" : aspect), 358 ((SignalMast) _nextSignal).getSignalSystem()); 359 log.debug("{} SignalMast {} shows aspect {} which maps to speed {}", 360 _trainName, _nextSignal.getDisplayName(),aspect,speed); 361 } 362 float speed_f = (float) (_speedMap.getSpeed(speed) / 125.); 363 // Ease the speed, if we are approaching the destination block 364 if ((approchingDestination() || inStartBlock()) && (speed_f > SPEED_UNSIGNALLED)) { 365 speed_f = SPEED_UNSIGNALLED; 366 } 367 _throttle.setSpeedSetting(speedFactor*speed_f); 368 } 369 } 370 371 /** 372 * Do what the title says. But make sure not to set the turnouts if already done, since that 373 * would just cause all signals to go to Stop aspects and thus cause a jerky train movement. 374 * @param startIndex Allocate starting with this index 375 */ 376 protected void allocateBlocksAndSetTurnouts(int startIndex) { 377 log.debug("{} allocateBlocksAndSetTurnouts startIndex={} _orders.size()={}",_trainName,startIndex,getBlockOrders().size()); 378 for (int i = startIndex; i < getBlockOrders().size(); i++) { 379 log.debug("{} allocateBlocksAndSetTurnouts for loop #{}",_trainName,i); 380 BlockOrder bo = getBlockOrderAt(i); 381 OBlock block = bo.getBlock(); 382 String pathAlreadySet = block.isPathSet(bo.getPathName()); 383 if (pathAlreadySet == null) { 384 String message = null; 385 if ((block.getState() & Block.OCCUPIED) != 0) { 386 log.info("{} block allocation failed {} not allocated, but Occupied.",_trainName,block.getDisplayName()); 387 message = " block allocation failed "; 388 } 389 if (message == null) { 390 message = block.allocate(this); 391 if (message != null) { 392 log.info("{} block allocation failed {}",_trainName,message); 393 } 394 } 395 if (message == null) { 396 message = bo.setPath(this); 397 } 398 if (message != null) { 399 log.debug("{} path setting failed for {} at block {} {}",_trainName,getDisplayName(),block.getDisplayName(),message); 400 if (_stoppingBlock != null) { 401 _stoppingBlock.removePropertyChangeListener(this); 402 } 403 _stoppingBlock = block; 404 _stoppingBlock.addPropertyChangeListener(this); 405 // This allocation failed. Do not attempt to allocate the rest of the route.allocation 406 // That would potentially lead to deadlock situations where two warrants are competing 407 // and each getting every second block along the same route. 408 return; 409 } 410 } else if (pathAlreadySet.equals(this.getDisplayName())) { 411 log.debug("{} Path {} already set (and thereby block allocated) for {}",_trainName,bo.getPathName(),pathAlreadySet); 412 } else { 413 log.info("{} Block allocation failed: Path {} already set (and thereby block allocated) for {}",_trainName,bo.getPathName(),pathAlreadySet); 414 return; 415 } 416 } 417 } 418 419 @Override 420 public String setRunMode(int mode, DccLocoAddress address, 421 LearnThrottleFrame student, 422 List<ThrottleSetting> commands, boolean runBlind) { 423 if (log.isDebugEnabled()) { 424 log.debug("{}: SCWarrant::setRunMode({}) ({}) called with _runMode= {}.", 425 getDisplayName(), mode, MODES[mode], MODES[_runMode]); 426 } 427 _message = null; 428 if (_runMode != MODE_NONE) { 429 _message = getRunModeMessage(); 430 log.debug("setRunMode called, but SCWarrant is already running"); 431 return _message; 432 } 433 if (mode == MODE_RUN) { 434 // set mode before setStoppingBlock and callback to notifyThrottleFound are called 435 _runMode = mode; 436 } else { 437 deAllocate(); 438 return _message; 439 } 440 getBlockAt(0)._entryTime = System.currentTimeMillis(); 441 _message = acquireThrottle(); 442 return _message; 443 } // end setRunMode 444 445 /** 446 * Block in the route going active. 447 * Make sure to allocate the rest of the route, update our present location and then tell 448 * the main loop to find a new throttle setting. 449 */ 450 @Override 451 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification="NotifyAll call triggers recomputation") 452 protected void goingActive(OBlock block) { 453 log.debug("{} **Block \"{}\" goingActive. " 454 + " - warrant= {} _runMode = {} _throttle==null: {}",_trainName,block.getDisplayName(),getDisplayName(),_runMode,(_throttle==null)); 455 if (_runMode != MODE_RUN) { 456 // if we are not running, we must not think that we are going to the next block - it must be another train 457 log.debug("_runMode != MODE_RUN - ignored"); 458 return; 459 } 460 if (_throttle == null || _throttle.getSpeedSetting() == SPEED_STOP) { 461 // if we are not running, we must not think that we are going to the next block - it must be another train 462 log.debug("Train is not running - ignored"); 463 return; 464 } 465 int activeIdx = getIndexOfBlockAfter(block, getCurrentOrderIndex()); 466 log.debug("{} **Block \"{}\" goingActive. activeIdx= {}" 467 + ", getCurrentOrderIndex()= {}" 468 + " - warrant= {} _runMode = {} _throttle==null: {}",_trainName,block.getDisplayName(),activeIdx,getCurrentOrderIndex(),getDisplayName(),_runMode,(_throttle==null)); 469 if (activeIdx <= 0) { 470 // The block going active is not part of our route ahead 471 log.debug("{} Block going active is not part of this trains route forward",_trainName); 472 } else if (activeIdx == getCurrentOrderIndex()) { 473 // Unusual case of current block losing detection, then regaining it. i.e. dirty track, derail etc. 474 log.debug("{} Current block becoming active - ignored",_trainName); 475 } else if (activeIdx == getCurrentOrderIndex() + 1) { 476 // not necessary: It is done in the main loop in SCTrainRunner.run: allocateBlocksAndSetTurnouts(getCurrentOrderIndex()+1) 477 // update our present location 478 incrementCurrentOrderIndex(); 479 block.setValue(_trainName); 480 block.setState(block.getState() | OBlock.RUNNING); 481 // fire property change (entered new block) 482 firePropertyChange("blockChange", getBlockAt(getCurrentOrderIndex() - 1), getBlockAt(getCurrentOrderIndex())); 483 // now let the main loop adjust speed. 484 synchronized(this) { 485 notifyAll(); 486 } 487 } else { 488 log.debug("{} Rogue occupation of block.",_trainName); 489 // now let the main loop stop for a train that is coming in our immediate way. 490 synchronized(this) { 491 notifyAll(); 492 } 493 } 494 } 495 496 /** 497 * Block in the route is going Inactive. 498 * Release the blocks that we have left. 499 * Check if current block has been left (i.e. we have left our route) and stop the train in that case. 500 */ 501 @Override 502 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification="See comment above notify call") 503 protected void goingInactive(OBlock block) { 504 int idx = getIndexOfBlockAfter(block, 0); // if idx >= 0, it is in this warrant 505 log.debug("{} Block \"{}\" goingInactive. idx= {}" 506 + ", getCurrentOrderIndex()= {}" 507 + " - warrant= {}",_trainName,block.getDisplayName(),idx,getCurrentOrderIndex(),getDisplayName()); 508 if (_runMode != MODE_RUN) { 509 return; 510 } 511 if (idx < getCurrentOrderIndex()) { 512 if (_allowShallowAllocation) { 513 deallocateUpToBlock(idx); 514 } 515 } else if (idx == getCurrentOrderIndex()) { 516 // train is lost 517 log.debug("{} LOST TRAIN firePropertyChange(\"blockChange\", {}" 518 + ", null) - warrant= {}",_trainName,block.getDisplayName(),getDisplayName()); 519 } 520 // now let the main loop stop our train if this means that the train is now entirely within the last block. 521 // Or let the train continue if an other train that was in its way has now moved. 522 synchronized(this) { 523 notifyAll(); 524 } 525 } 526 527 /** 528 * 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: 529 * 1. Only if our train has left not only this block, but also all previous blocks. 530 * 2. Only if the block shall not be re-used ahead and all block up until the block are allocated. 531 * @param idx Index of final block 532 */ 533 protected void deallocateUpToBlock(int idx) { 534 for (int i=0; i<=idx; i++) { 535 OBlock block_i = getBlockOrderAt(i).getBlock(); 536 if (block_i.isAllocatedTo(this)) { 537 if ((block_i.getState() & Block.UNOCCUPIED) != Block.UNOCCUPIED) { 538 //Do not deallocate further blocks, since this one is still allocated to us and not free. 539 log.debug("{} Block {} occupied. Not de-allocating any further",_trainName,block_i.getDisplayName()); 540 return; 541 } 542 boolean deAllocate = true; 543 // look ahead to see if block_i is reused in the remaining part of the route. 544 for (int j= getCurrentOrderIndex(); j<getBlockOrders().size(); j++) { 545 OBlock block_j = getBlockOrderAt(j).getBlock(); 546 if (!block_j.isAllocatedTo(this)) { 547 // There is an unallocated block ahead before we have found block_i is re-used. So deallocate block_i 548 break; 549 } 550 if (block_i == block_j) { 551 // clock_i is re-used, and we have no "holes" in the string of allocated blocks before it. So do not deallocate. 552 deAllocate = false; 553 break; 554 } 555 } 556 if (deAllocate) { 557 log.debug("{} De-allocating block {}",_trainName,block_i.getDisplayName()); 558 block_i.deAllocate(this); 559 } 560 } 561 } 562 } 563 564 /** 565 * Something has fired a property change event. 566 * React if: 567 * - it is a warrant that we need to synchronize with. And then again: Why? 568 * - it is _nextSignal 569 * Do not worry about sensors and blocks. They are handled by goingActive and goingInactive. 570 */ 571 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = {"UW_UNCOND_WAIT", "NN_NAKED_NOTIFY"}, 572 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.") 573 @Override 574 public void propertyChange(java.beans.PropertyChangeEvent evt) { 575 if (!(evt.getSource() instanceof NamedBean)) { 576 log.debug("{} propertyChange \"{}\" old= {} new= {}",_trainName,evt.getPropertyName(),evt.getOldValue(),evt.getNewValue()); 577 return; 578 } 579 String property = evt.getPropertyName(); 580 log.debug("{} propertyChange \"{}\" new= {} source= {} - warrant= {}",_trainName,property,evt.getNewValue(),((NamedBean) evt.getSource()).getDisplayName(),getDisplayName()); 581 if (_nextSignal != null && _nextSignal == evt.getSource()) { 582 if (property.equals("Aspect") || property.equals("Appearance")) { 583 // The signal controlling this warrant has changed. Adjust the speed (in runSignalControlledTrain) 584 synchronized(this) { 585 notifyAll(); 586 } 587 return; 588 } 589 } 590 synchronized(this) { 591 if (_stoppingBlock != null) { 592 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)); 593 if (((NamedBean) evt.getSource()).getDisplayName().equals(_stoppingBlock.getDisplayName()) && 594 evt.getPropertyName().equals("state") && 595 (((Number) evt.getNewValue()).intValue() & Block.UNOCCUPIED) == Block.UNOCCUPIED) { 596 log.debug("{} being aware that Block {} has become free",_trainName,((NamedBean) evt.getSource()).getDisplayName()); 597 _stoppingBlock.removePropertyChangeListener(this); 598 _stoppingBlock = null; 599 // we might be waiting for this block to become free 600 // Give the warrant that now has _stoppingBlock allocated a little time to deallocate it 601 try { 602 wait(100); 603 } catch (InterruptedException e) { 604 // ignoring interrupted exceptions 605 } catch(Exception e){ 606 log.debug(WAIT_UNEXPECTED_EXCEPTION,_trainName,e,e); 607 } 608 // And then let our main loop continue 609 notifyAll(); 610 return; 611 } 612 if (((NamedBean) evt.getSource()).getDisplayName().equals(getBlockOrderAt(0).getBlock().getDisplayName()) && 613 evt.getPropertyName().equals("state") && 614 (((Number) evt.getOldValue()).intValue() & Block.UNOCCUPIED) == Block.UNOCCUPIED && 615 (((Number) evt.getNewValue()).intValue() & Block.UNOCCUPIED) != Block.UNOCCUPIED && 616 _throttle==null && _runMode==MODE_RUN) { 617 // We are waiting for the train to arrive at the starting block, and that has just happened now. 618 log.debug("{} has arrived at starting block",_trainName); 619 String msg = null; 620 msg = acquireThrottle(); 621 if (msg != null) { 622 log.warn("propertyChange of \"{}\" has message: {}", property, msg); 623 _message = msg; 624 abortWarrant(msg); 625 } 626 } 627 } 628 } 629 } 630 631 632 /** 633 * Make sure to free up additional resources for a running SCWarrant. 634 */ 635 @Override 636 public synchronized void stopWarrant(boolean abort, boolean turnOffFunctions) { 637 if (_nextSignal != null) { 638 _nextSignal.removePropertyChangeListener(this); 639 _nextSignal = null; 640 } 641 super.stopWarrant(abort, false); 642 _message = null; 643 } 644 645 /******************************************************************************************************************************* 646 * The waiting for event must happen in a separate thread. 647 * Therefore the main code of runSignalControlledTrain is put in this class. 648 *******************************************************************************************************************************/ 649 static LinkedBlockingQueue<SCWarrant> waitToRunQ = new LinkedBlockingQueue<>(); 650 private class SCTrainRunner implements Runnable { 651 private static final String INTERRUPTED_EXCEPTION = "{} InterruptedException {}"; 652 SCWarrant _warrant = null; 653 SCTrainRunner(SCWarrant warrant) { 654 _warrant = warrant; 655 } 656 657 /** 658 * When not using shallow allocation, warrants will have to wait until the entire route 659 * is free and allocated to that particular warrant, before strting to run the train. 660 * This method uses the waitToRunQ to ensure that warrants do not just compete about 661 * resources, but waits in line until their route is free and unallocated. 662 */ 663 boolean isItOurTurn() { 664 for (SCWarrant e : waitToRunQ) { 665 try { // using another SCWarrant might be dangerous - it might no longer exist 666 log.debug("{} isItOurTurn is checking {}",_trainName,e.getDisplayName()); 667 if (e.isRouteFree()) { 668 if (e == _warrant) { 669 log.debug("{} isItOurTurn: We are first in line",_trainName); 670 return true; 671 } else { 672 log.debug("{} isItOurTurn: An other warrant is before us",_trainName); 673 return false; 674 } 675 } else { 676 if (e == _warrant) { 677 log.debug("{} isItOurTurn: our route is not free - keep waiting",_trainName); 678 return false; 679 } 680 } 681 } catch (Exception ex) { 682 log.debug("{} isItOurTurn exception ignored: {}",_trainName,ex,ex); 683 } 684 } 685 // we should not reach this point, but if we do, we should try to run 686 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); 687 return true; 688 } 689 690 @Override 691 public void run() { 692 synchronized(_warrant) { 693 694 waitForStartblockToGetOccupied(); 695 696 // Make sure the entire route is allocated before attemting to start the train 697 if (!_allowShallowAllocation) { 698 boolean AllocationDone = false; 699 log.debug("{} ENTERING QUEUE ",_trainName); 700 try { 701 waitToRunQ.put(_warrant); 702 } catch (InterruptedException ie) { 703 log.debug("{} waitToRunQ.put InterruptedException {}",_trainName,ie,ie); 704 } 705 706 while (!AllocationDone) { 707 log.debug("{} Route is not allocated yet..... ",_trainName); 708 while (!isItOurTurn()) { 709 deAllocate(); 710 log.debug("{} Waiting for route to become free ....",_trainName); 711 try { 712 _warrant.wait(2500 + Math.round(1000*Math.random())); 713 } catch (InterruptedException ie) { 714 log.debug("{} _warrant.wait InterruptedException {}",_trainName,ie,ie); 715 } 716 catch(Exception e){ 717 log.debug("{} _warrant.wait unexpected exception {}",_trainName,e,e); 718 } 719 } 720 allocateStartBlock(); 721 allocateBlocksAndSetTurnouts(1); 722 AllocationDone = isRouteAllocated(); 723 if (!AllocationDone) { 724 deAllocate(); 725 try { 726 _warrant.wait(10000 + Math.round(1000*Math.random())); 727 } catch (InterruptedException ie) { 728 log.debug("{} _warrant.wait !AllocationDone InterruptedException {}",_trainName,ie,ie); 729 } 730 catch(Exception e){ 731 log.debug("{} _warrant.wait !AllocationDone unexpected exception {}",_trainName,e,e); 732 } 733 } 734 } 735 736 log.debug("{} LEAVING QUEUE ",_trainName); 737 waitToRunQ.remove(_warrant); 738 739 while (!allTurnoutsSet()) { 740 log.debug("{} Waiting for turnouts to settle ....",_trainName); 741 try { 742 _warrant.wait(2500); 743 } catch (InterruptedException ie) { 744 log.debug("{} _warrant.wait InterruptedException {}",_trainName,ie,ie); 745 } 746 catch(Exception e){ 747 log.debug("{} _warrant.wait unexpected exception {}",_trainName,e,e); 748 } 749 } 750 // And then wait another 3 seconds to make the last turnout settle - just in case the command station is not giving correct feedback 751 try { 752 _warrant.wait(3000); 753 } catch (InterruptedException ie) { 754 log.debug(INTERRUPTED_EXCEPTION,_trainName,ie,ie); 755 } 756 catch(Exception e){ 757 log.debug(WAIT_UNEXPECTED_EXCEPTION,_trainName,e,e); 758 } 759 } 760 761 // Do not include the stopping block in this while loop. It will be handled after the loop. 762 List<BlockOrder> orders = getBlockOrders(); 763 while (_warrant.getCurrentOrderIndex() < orders.size()-1 && _runMode == MODE_RUN) { 764 log.debug("{} runSignalControlledTrain entering while loop. getCurrentOrderIndex()={} _orders.size()={}",_warrant._trainName,getCurrentOrderIndex(),orders.size()); 765 if (_throttle == null) { 766 // We lost our throttle, so we might have a runaway train 767 emergencyStop(); 768 } 769 if (_allowShallowAllocation) { 770 allocateBlocksAndSetTurnouts(_warrant.getCurrentOrderIndex()); 771 } 772 if (isNextBlockFreeAndAllocated()) { 773 getAndGetNotifiedFromNextSignal(); 774 setSpeedFromNextSignal(); 775 } else { 776 try { 777 _throttle.setSpeedSetting(SPEED_STOP); 778 getBlockOrderAt(getCurrentOrderIndex()+1).getBlock().addPropertyChangeListener(_warrant); 779 log.debug("{} runSignalControlledTrain stops train due to block not free: {}",_warrant._trainName,getBlockOrderAt(getCurrentOrderIndex()+1).getBlock().getDisplayName()); 780 } catch (Exception e) { 781 emergencyStop(); 782 log.debug("{} exception trying to stop train due to block not free: {}",_warrant._trainName,e,e); 783 } 784 } 785 log.debug("{} {} before wait {} getCurrentOrderIndex(): {} orders.size(): {}",_warrant._trainName,_warrant.getDisplayName(),_warrant.getRunningMessage(),_warrant.getCurrentOrderIndex(),orders.size()); 786 try { 787 // We do a timed wait for the sake of robustness, even though we will be woken up by all relevant events. 788 _warrant.wait(2000); 789 } catch (InterruptedException ie) { 790 log.debug(INTERRUPTED_EXCEPTION,_warrant._trainName,ie,ie); 791 } 792 catch(Exception e){ 793 log.debug(WAIT_UNEXPECTED_EXCEPTION,_trainName,e,e); 794 } 795 log.debug("{} {} after wait {} getCurrentOrderIndex(): {} orders.size(): {}",_warrant._trainName,_warrant.getDisplayName(),_warrant.getRunningMessage(),_warrant.getCurrentOrderIndex(),orders.size()); 796 } 797 // 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. 798 log.debug("{} runSignalControlledTrain out of while loop, i.e. train entered stop block getCurrentOrderIndex()={}" 799 + " orders.size()={} waiting for train to clear block {}", 800 _warrant._trainName,getCurrentOrderIndex(),orders.size(),getBlockAt(orders.size()-2).getDisplayName()); 801 if (_throttle==null) { 802 emergencyStop(); 803 log.debug("Throttle lost at stop block"); 804 } else { 805 _throttle.setSpeedSetting(speedFactor*SPEED_TO_PLATFORM); 806 } 807 while ((getBlockAt(orders.size()-2).getState()&Block.OCCUPIED)==Block.OCCUPIED && getBlockAt(orders.size()-2).isAllocatedTo(_warrant)) { 808 log.debug(" runSignalControlledTrain {} entering wait. Block {} free: {} allocated to this warrant: {}", 809 _warrant._trainName, 810 getBlockAt(orders.size()-2).getDisplayName(), 811 getBlockAt(orders.size()-2).isFree(), 812 getBlockAt(orders.size()-2).isAllocatedTo(_warrant)); 813 try { 814 // This does not need to be a timed wait, since we will get interrupted once the block is free 815 // However, the functionality is more robust with a timed wait. 816 _warrant.wait(500); 817 } catch (InterruptedException ie) { 818 log.debug(INTERRUPTED_EXCEPTION,_warrant._trainName,ie,ie); 819 } 820 catch(Exception e){ 821 log.debug(WAIT_UNEXPECTED_EXCEPTION,_trainName,e,e); 822 } 823 log.debug("{} runSignalControlledTrain woken after last wait.... _orders.size()={}",_warrant._trainName,orders.size()); 824 } 825 if (timeToPlatform > 100) { 826 log.debug("{} runSignalControlledTrain is now fully into the stopping block. Proceeding for {} miliseconds",_warrant._trainName,timeToPlatform); 827 long timeWhenDone = System.currentTimeMillis() + timeToPlatform; 828 long remaining; 829 while ((remaining = timeWhenDone - System.currentTimeMillis()) > 0) { 830 try { 831 log.debug("{} running slowly to platform for {} miliseconds",_warrant._trainName,remaining); 832 _warrant.wait(remaining); 833 } catch (InterruptedException e) { 834 log.debug(INTERRUPTED_EXCEPTION,_warrant._trainName,e,e); 835 } 836 } 837 } 838 log.debug("{} runSignalControlledTrain STOPPING TRAIN IN STOP BLOCK",_warrant._trainName); 839 if (_throttle==null) { 840 emergencyStop(); 841 log.debug("Throttle lost after stop block"); 842 } else { 843 _throttle.setSpeedSetting(SPEED_STOP); 844 } 845 stopWarrant(false, false); 846 } 847 } 848 849 /** 850 * If we think we might have a runaway train - take the power of the entire layout. 851 */ 852 private void emergencyStop() { 853 PowerManager manager = InstanceManager.getNullableDefault(PowerManager.class); 854 if (manager == null) { 855 log.debug("{} EMERGENCY STOP IMPOSSIBLE: NO POWER MANAGER",_trainName); 856 return; 857 } 858 try { 859 manager.setPower(PowerManager.OFF); 860 } catch (Exception e) { 861 log.debug("{} EMERGENCY STOP FAILED WITH EXCEPTION: {}",_trainName,e,e); 862 } 863 log.debug("{} EMERGENCY STOP",_trainName); 864 } 865 866 } 867 868 /* What super does currently is fine. But FindBug reports EQ_DOESNT_OVERRIDE_EQUALS 869 * FindBug wants us to duplicate and override anyway 870 */ 871 @Override 872 public boolean equals(Object obj) { 873 return super.equals(obj); 874 } 875 876 /* What super does currently is fine. But FindBug reports HE_EQUALS_NO_HASHCODE 877 * FindBug wants us to duplicate and override anyway 878 */ 879 @Override 880 public int hashCode() { 881 return super.hashCode(); 882 } 883 884 /** 885 * 886 */ 887 private static final Logger log = LoggerFactory.getLogger(SCWarrant.class); 888}