001package jmri.jmrit.logix; 002 003import java.awt.Color; 004import java.util.List; 005import java.util.ListIterator; 006 007import javax.annotation.Nonnull; 008 009import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 010import jmri.*; 011import jmri.jmrit.logix.ThrottleSetting.*; 012import jmri.util.ThreadingUtil; 013 014/** 015 * Execute a throttle command script for a warrant. 016 * <p> 017 * This generally operates on its own thread, but calls the warrant 018 * thread via Warrant.fireRunStatus to show status. fireRunStatus uses 019 * ThreadingUtil.runOnGUIEventually to display on the layout thread. 020 * 021 * @author Pete Cressman Copyright (C) 2009, 2010, 2020 022 */ 023/* 024 * ************************ Thread running the train **************** 025 */ 026class Engineer extends Thread implements java.beans.PropertyChangeListener { 027 028 private int _idxCurrentCommand; // current throttle command 029 private ThrottleSetting _currentCommand; 030 private long _commandTime = 0; // system time when command was executed. 031 private int _idxSkipToSpeedCommand; // skip to this index to reset script when ramping 032 private float _normalSpeed = 0; // current commanded throttle setting from script (unmodified) 033 // speed name of current motion. When train stopped, is the speed that will be restored when movement is permitted 034 private String _speedType = Warrant.Normal; // is never Stop or EStop 035 private float _timeRatio = 1.0f; // ratio to extend scripted time when speed is modified 036 private boolean _abort = false; 037 private boolean _halt = false; // halt/resume from user's control 038 private boolean _stopPending = false; // ramp slow down in progress 039 private boolean _waitForClear = false; // waits for signals/occupancy/allocation to clear 040 private boolean _waitForSensor = false; // wait for sensor event 041 private boolean _sensorStateReached = false; // wait sensor went to the wanted state 042 private boolean _releaseSensorWait = false; // operator released the sensor wait 043 private volatile boolean _stopRequested = false; // stopRun() called, end any sensor wait 044 private boolean _runOnET = false; // Execute commands on ET only - do not synch 045 private boolean _setRunOnET = false; // Need to delay _runOnET from the block that set it 046 protected DccThrottle _throttle; 047 private final Warrant _warrant; 048 private final List<ThrottleSetting> _commands; 049 private Sensor _waitSensor; 050 private int _sensorWaitState; 051 private final Object _rampLockObject = new Object(); 052 private final Object _synchLockObject = new Object(); 053 private final Object _clearLockObject = new Object(); 054 private boolean _atHalt = false; 055 private boolean _atClear = false; 056 private final SpeedUtil _speedUtil; 057 private OBlock _synchBlock = null; 058 private Thread _checker = null; 059 060 private ThrottleRamp _ramp; 061 private boolean _holdRamp = false; 062 private volatile boolean _isRamping = false; 063 private boolean _rampPending = false; // ramp requested, guarded by _rampLockObject 064 065 /** 066 * Property change constant for Wait For Sync. 067 */ 068 public static final String PROPERTY_WAIT_FOR_SYNC = "WaitForSync"; 069 070 /** 071 * Property change constant for Speed Change. 072 */ 073 public static final String PROPERTY_SPEED_CHANGE = "SpeedChange"; 074 075 /** 076 * Property change constant for Memory Set Command. 077 */ 078 public static final String PROPERTY_MEMORY_SET_COMMAND = "MemorySetCommand"; 079 080 /** 081 * Property change constant for Speed Set Command. 082 */ 083 public static final String PROPERTY_SENSOR_SET_COMMAND = "SensorSetCommand"; 084 085 /** 086 * Property change constant for Sensor Wait Command. 087 */ 088 public static final String PROPERTY_SENSOR_WAIT_COMMAND = "SensorWaitCommand"; 089 090 /** 091 * Property change constant for Ramp Done. 092 */ 093 public static final String PROPERTY_RAMP_DONE = "RampDone"; 094 095 /** 096 * Create a new Engineer for a given Warrant and Throttle. 097 * @param warrant The Warrant to execute. 098 * @param throttle The Engine throttle to command. 099 */ 100 Engineer(Warrant warrant, DccThrottle throttle) { 101 _warrant = warrant; 102 _throttle = throttle; 103 _speedUtil = warrant.getSpeedUtil(); 104 _commands = _warrant.getThrottleCommands(); 105 _idxCurrentCommand = 0; 106 _currentCommand = _commands.get(_idxCurrentCommand); 107 _idxSkipToSpeedCommand = 0; 108 _waitForSensor = false; 109 setName("Engineer(" + _warrant.getTrainName() +")"); 110 } 111 112 /** 113 * Run the Warrant commands on this Engineer thread. 114 */ 115 @Override 116 public void run() { 117 try { 118 runCommands(); 119 } catch (RuntimeException e) { 120 log.error("{}: Engineer stopped by error at Cmd #{}", _warrant.getDisplayName(), _idxCurrentCommand + 1, e); 121 _abort = true; 122 } finally { 123 // shut down 124 setSpeed(0.0f); // for safety to be sure train stops 125 _warrant.stopWarrant(_abort, true); 126 } 127 } 128 129 @SuppressFBWarnings(value="UW_UNCOND_WAIT", justification="waits may be indefinite until satisfied or thread aborted") 130 private void runCommands() { 131 if (log.isDebugEnabled()) { 132 log.debug("Engineer started warrant {} _throttle= {}", 133 _warrant.getDisplayName(), _throttle.getClass().getName()); 134 } 135 int cmdBlockIdx = 0; 136 while (_idxCurrentCommand < _commands.size()) { 137 while (_idxSkipToSpeedCommand > _idxCurrentCommand) { 138 if (log.isDebugEnabled()) { 139 ThrottleSetting ts = _commands.get(_idxCurrentCommand); 140 log.debug("{}: Skip Cmd #{}: {} Warrant", _warrant.getDisplayName(), _idxCurrentCommand+1, ts); 141 // Note: command indexes biased from 0 to 1 to match Warrant display of commands. 142 } 143 _idxCurrentCommand++; 144 } 145 if (_idxCurrentCommand == _commands.size()) { 146 // skip commands on last block may advance too far. Due to looking for a NOOP 147 break; 148 } 149 _currentCommand = _commands.get(_idxCurrentCommand); 150 long cmdWaitTime = _currentCommand.getTime(); // time to wait before executing command 151 ThrottleSetting.Command command = _currentCommand.getCommand(); 152 _runOnET = _setRunOnET; // OK to set here 153 if (command.hasBlockName()) { 154 int idx = _warrant.getIndexOfBlockAfter((OBlock)_currentCommand.getBean(), cmdBlockIdx); 155 if (idx >= 0) { 156 cmdBlockIdx = idx; 157 } 158 } 159 if (cmdBlockIdx < _warrant.getCurrentOrderIndex() || 160 (command.equals(Command.NOOP) && (cmdBlockIdx <= _warrant.getCurrentOrderIndex()))) { 161 // Train advancing too fast, need to process commands more quickly, 162 // allow some time for whistle toots etc. 163 cmdWaitTime = Math.min(cmdWaitTime, 200); // 200ms per command should be enough for toots etc. 164 if (log.isDebugEnabled()) { 165 log.debug("{}: Train reached block \"{}\" before script et={}ms", 166 _warrant.getDisplayName(), _warrant.getCurrentBlockName(), _currentCommand.getTime()); 167 } 168 } 169 if (_abort) { 170 break; 171 } 172 173 long cmdStart = System.currentTimeMillis(); 174 if (log.isDebugEnabled()) { 175 log.debug("{}: Start Cmd #{} for block \"{}\" currently in \"{}\". wait {}ms to do cmd {}", 176 _warrant.getDisplayName(), _idxCurrentCommand+1, _currentCommand.getBeanDisplayName(), 177 _warrant.getCurrentBlockName(), cmdWaitTime, command); 178 // Note: command indexes biased from 0 to 1 to match Warrant display of commands. 179 } 180 synchronized (this) { 181 if (!Warrant.Normal.equals(_speedType)) { 182 // extend it when speed has been modified from scripted speed 183 cmdWaitTime = (long)(cmdWaitTime*_timeRatio); 184 } 185 try { 186 if (cmdWaitTime > 0) { 187 wait(cmdWaitTime); 188 } 189 } catch (InterruptedException ie) { 190 log.debug("InterruptedException during time wait", ie); 191 _warrant.debugInfo(); 192 Thread.currentThread().interrupt(); 193 _abort = true; 194 } catch (java.lang.IllegalArgumentException iae) { 195 log.error("At time wait", iae); 196 } 197 } 198 if (_abort) { 199 break; 200 } 201 202 // Having waited, time=ts.getTime(), so blocks should agree. if not, 203 // wait for train to arrive at block and send sync notification. 204 // note, blind runs cannot detect entrance. 205 if (!_runOnET && cmdBlockIdx > _warrant.getCurrentOrderIndex()) { 206 // commands are ahead of current train position 207 // When the next block goes active or a control command is made, a clear sync call 208 // will test these indexes again and can trigger a notify() to free the wait 209 210 synchronized (_synchLockObject) { 211 _synchBlock = _warrant.getBlockAt(cmdBlockIdx); 212 _warrant.fireRunStatus( PROPERTY_WAIT_FOR_SYNC, _idxCurrentCommand - 1, _idxCurrentCommand); 213 if (log.isDebugEnabled()) { 214 log.debug("{}: Wait for train to enter \"{}\".", 215 _warrant.getDisplayName(), _synchBlock.getDisplayName()); 216 } 217 // Re-check under lock: block may have fired between the outer check and here 218 if (cmdBlockIdx > _warrant.getCurrentOrderIndex()) { 219 try { 220 _synchLockObject.wait(); 221 } catch (InterruptedException ie) { 222 log.debug("InterruptedException during _waitForSync", ie); 223 _warrant.debugInfo(); 224 Thread.currentThread().interrupt(); 225 _abort = true; 226 } 227 } 228 _synchBlock = null; 229 } 230 if (_abort) { 231 break; 232 } 233 } 234 235 synchronized (_clearLockObject) { 236 // block position and elapsed time are as expected, but track conditions 237 // such as signals or rogue occupancy requires waiting 238 if (_waitForClear) { 239 try { 240 _atClear = true; 241 if (log.isDebugEnabled()) { 242 log.debug("{}: Waiting for clearance. _waitForClear= {} _halt= {} at block \"{}\" Cmd#{}.", 243 _warrant.getDisplayName(), _waitForClear, _halt, 244 _warrant.getBlockAt(cmdBlockIdx).getDisplayName(), _idxCurrentCommand+1); 245 } 246 _clearLockObject.wait(); 247 _waitForClear = false; 248 _atClear = false; 249 } catch (InterruptedException ie) { 250 log.debug("InterruptedException during _atClear", ie); 251 _warrant.debugInfo(); 252 Thread.currentThread().interrupt(); 253 _abort = true; 254 } 255 } 256 } 257 if (_abort) { 258 break; 259 } 260 261 synchronized (this) { 262 // user's command to halt requires waiting 263 if (_halt) { 264 try { 265 _atHalt = true; 266 if (log.isDebugEnabled()) { 267 log.debug("{}: Waiting to Resume. _halt= {}, _waitForClear= {}, Block \"{}\".", 268 _warrant.getDisplayName(), _halt, _waitForClear, 269 _warrant.getBlockAt(cmdBlockIdx).getDisplayName()); 270 } 271 wait(); 272 _halt = false; 273 _atHalt = false; 274 } catch (InterruptedException ie) { 275 log.debug("InterruptedException during _atHalt", ie); 276 _warrant.debugInfo(); 277 Thread.currentThread().interrupt(); 278 _abort = true; 279 } 280 } 281 } 282 if (_abort) { 283 break; 284 } 285 286 synchronized (this) { 287 while (_isRamping || _holdRamp) { 288 int idx = _idxCurrentCommand; 289 try { 290 if (log.isDebugEnabled()) { 291 log.debug("{}: Waiting for ramp to finish at Cmd #{}.", 292 _warrant.getDisplayName(), _idxCurrentCommand+1); 293 } 294 wait(); 295 } catch (InterruptedException ie) { 296 _warrant.debugInfo(); 297 Thread.currentThread().interrupt(); 298 _abort = true; 299 } 300 // ramp will decide whether to skip or execute _currentCommand 301 if (log.isDebugEnabled()) { 302 log.debug("{}: Cmd #{} held for {}ms. {}", _warrant.getDisplayName(), 303 idx+1, System.currentTimeMillis() - cmdStart, _currentCommand); 304 } 305 if (_abort) { 306 break; 307 } 308 } 309 if (!_abort && _idxSkipToSpeedCommand <= _idxCurrentCommand) { 310 executeComand(_currentCommand, System.currentTimeMillis() - cmdStart); 311 _idxCurrentCommand++; 312 } 313 } 314 } 315 } 316 317 private void executeComand(ThrottleSetting ts, long et) { 318 Command command = ts.getCommand(); 319 CommandValue cmdVal = ts.getValue(); 320 switch (command) { 321 case SPEED: 322 _normalSpeed = cmdVal.getFloat(); 323 float speedMod = _speedUtil.modifySpeed(_normalSpeed, _speedType); 324 if (_normalSpeed > speedMod) { 325 float trackSpeed = _speedUtil.getTrackSpeed(speedMod); 326 _timeRatio = _speedUtil.getTrackSpeed(_normalSpeed) / trackSpeed; 327 _speedUtil.speedChange(speedMod); // call before this setting to compute travel of last setting 328 setSpeed(speedMod); 329 } else { 330 _timeRatio = 1.0f; 331 _speedUtil.speedChange(_normalSpeed); // call before this setting to compute travel of last setting 332 setSpeed(_normalSpeed); 333 } 334 break; 335 case NOOP: 336 break; 337 case SET_SENSOR: 338 ThreadingUtil.runOnGUIEventually(() -> 339 setSensor(ts.getNamedBeanHandle(), cmdVal)); 340 break; 341 case FKEY: 342 setFunction(ts.getKeyNum(), cmdVal.getType()); 343 break; 344 case FORWARD: 345 setForward(cmdVal.getType()); 346 break; 347 case LATCHF: 348 setFunctionMomentary(ts.getKeyNum(), cmdVal.getType()); 349 break; 350 case WAIT_SENSOR: 351 waitForSensor(ts.getNamedBeanHandle(), cmdVal); 352 break; 353 case RUN_WARRANT: 354 // must run on the GUI thread because runWarrant() calls WarrantTableFrame.runTrain() directly 355 ThreadingUtil.runOnGUIEventually(() -> 356 runWarrant(ts.getNamedBeanHandle(), cmdVal)); 357 break; 358 case SPEEDSTEP: 359 break; 360 case SET_MEMORY: 361 ThreadingUtil.runOnGUIEventually(() -> 362 setMemory(ts.getNamedBeanHandle(), cmdVal)); 363 break; 364 default: 365 } 366 _commandTime = System.currentTimeMillis(); 367 if (log.isDebugEnabled()) { 368 log.debug("{}: Cmd #{} done. et={}. {}", 369 _warrant.getDisplayName(), _idxCurrentCommand + 1, et, ts); 370 } 371 } 372 373 protected int getCurrentCommandIndex() { 374 return _idxCurrentCommand; 375 } 376 377 /** 378 * Delayed ramp has started. 379 * Currently informational only 380 * Do non-speed commands only until idx is reached? maybe not. 381 * @param idx index 382 */ 383 private void advanceToCommandIndex(int idx) { 384 _idxSkipToSpeedCommand = idx; 385 if (log.isTraceEnabled()) { 386 log.debug("advanceToCommandIndex to {} - {}", _idxSkipToSpeedCommand+1, _commands.get(idx)); 387 // Note: command indexes biased from 0 to 1 to match Warrant display of commands, which are 1-based. 388 } 389 } 390 391 /** 392 * Cannot set _runOnET to true until current NOOP command completes 393 * so there is the intermediate flag _setRunOnET 394 * @param set true to run on elapsed time calculations only, false to 395 * consider other inputs 396 */ 397 protected void setRunOnET(boolean set) { 398 if (log.isDebugEnabled() && _setRunOnET != set) { 399 log.debug("{}: setRunOnET {} command #{}", _warrant.getDisplayName(), 400 set, _idxCurrentCommand+1); 401 // Note: command indexes biased from 0 to 1 to match Warrant display of commands. 402 } 403 _setRunOnET = set; 404 if (!set) { // OK to be set false immediately 405 _runOnET = false; 406 } 407 } 408 409 protected boolean getRunOnET() { 410 return _setRunOnET; 411 } 412 413 protected OBlock getSynchBlock() { 414 return _synchBlock; 415 } 416 417 /** 418 * Called by the warrant when a the block ahead of a moving train goes occupied. 419 * typically when this thread is on a timed wait. The call will free the wait. 420 * @param block going active. 421 */ 422 protected void clearWaitForSync(OBlock block) { 423 // block went active. if waiting on sync, clear it 424 if (_synchBlock != null) { 425 synchronized (_synchLockObject) { 426 if (block.equals(_synchBlock)) { 427 _synchLockObject.notifyAll(); 428 if (log.isDebugEnabled()) { 429 log.debug("{}: clearWaitForSync from block \"{}\". notifyAll() called. isRamping()={}", 430 _warrant.getDisplayName(), block.getDisplayName(), isRamping()); 431 } 432 return; 433 } 434 } 435 } 436 if (log.isDebugEnabled()) { 437 log.debug("{}: clearWaitForSync from block \"{}\". _synchBlock= {} SpeedState={} _atClear={} _atHalt={}", 438 _warrant.getDisplayName(), block.getDisplayName(), 439 (_synchBlock==null?"null":_synchBlock.getDisplayName()), getSpeedState(), _atClear, _atHalt); 440 } 441 } 442 443 /** 444 * Set the Warrant Table Frame Status Text. 445 * Saves status to log. 446 * @param m the status String. 447 * @param c the status colour. 448 */ 449 private static void setFrameStatusText(String m, Color c ) { 450 ThreadingUtil.runOnGUIEventually(()-> WarrantTableFrame.getDefault().setStatusText(m, c, true)); 451 } 452 453 /** 454 * Occupancy of blocks, user halts and aspects of Portal signals will modify 455 * normal scripted train speeds. 456 * Ramp speed change for smooth prototypical look. 457 * 458 * @param endSpeedType signal aspect speed name 459 * @param endBlockIdx BlockOrder index of the block where ramp is to end. 460 * -1 if an end block is not specified. 461 */ 462 @SuppressFBWarnings(value={"SLF4J_FORMAT_SHOULD_BE_CONST","FE_FLOATING_POINT_EQUALITY"}, 463 justification="False assumption; Not result of calculation") 464 protected synchronized void rampSpeedTo(@Nonnull String endSpeedType, int endBlockIdx) { 465 float speed = _speedUtil.modifySpeed(_normalSpeed, endSpeedType); 466 if (log.isDebugEnabled()) { 467 log.debug("{}: rampSpeedTo: type= {}, throttle from {} to {}.", 468 _warrant.getDisplayName(), endSpeedType, getSpeedSetting(), 469 speed); 470 } 471 _speedUtil.speedChange(-1); // Notify not to measure speed for speedProfile 472 if (endSpeedType.equals(Warrant.EStop)) { 473 setStop(true); 474 return; 475 } 476 if (endSpeedType.equals(Warrant.Stop) && getSpeedSetting() <= 0) { 477 setStop(false); 478 return; // already stopped, do nothing 479 } 480 if (_isRamping) { 481 if (endSpeedType.equals(_ramp._endSpeedType)) { 482 return; // already ramping to speedType 483 } 484 } else if (speed == getSpeedSetting()){ 485 // to be sure flags and notification is done 486 rampDone(false, endSpeedType, endBlockIdx); 487 return; // already at speedType speed 488 } 489 if (_ramp == null) { 490 _ramp = new ThrottleRamp(); 491 _ramp.start(); 492 } else if (_isRamping) { 493 // for repeated command already ramping 494 if (_ramp.duplicate(endSpeedType, endBlockIdx)) { 495 return; 496 } 497 // stop the ramp and replace it 498 _holdRamp = true; 499 _ramp.quit(false); 500 } 501 long time = 0; 502 int pause = 2 *_speedUtil.getRampTimeIncrement(); 503 while (time <= pause && _isRamping) { 504 // may need a bit of time for quit() of the replaced ramp 505 try { 506 wait(40); 507 time += 40; 508 _ramp.quit(false); 509 } 510 catch (InterruptedException ie) { // ignore 511 } 512 } 513 514 if (!_isRamping) { 515 if (Warrant._trace || log.isDebugEnabled()) { 516 log.info(Bundle.getMessage("RampStart", _warrant.getTrainName(), 517 endSpeedType, _warrant.getCurrentBlockName())); 518 } 519 _ramp.setParameters(endSpeedType, endBlockIdx); 520 synchronized (_rampLockObject) { 521 _ramp._rampDown = (endBlockIdx >= 0) || endSpeedType.equals(Warrant.Stop); 522 _ramp.stop = false; 523 _rampPending = true; // kept until ThrottleRamp.run() takes it, so the request is not lost 524 setIsRamping(true); 525 _holdRamp = false; 526 setWaitforClear(true); 527 _rampLockObject.notifyAll(); // free wait at ThrottleRamp.run() 528 log.debug("{}: rampSpeedTo calls notify _rampLockObject", _warrant.getDisplayName()); 529 } 530 } else { 531 log.error("Can't launch ramp for speed {}! _ramp Thread.State= {}. Waited {}ms", 532 endSpeedType, _ramp.getState(), time); 533 _warrant.debugInfo(); 534 setSpeedToType(endSpeedType); 535 _ramp.quit(true); 536 _ramp.interrupt(); 537 _ramp = null; 538 } 539 } 540 541 /** 542 * Get if Engineer is ramping up or down the speed. 543 * @return true if is ramping. 544 */ 545 protected boolean isRamping() { 546 return _isRamping; 547 } 548 549 private void setIsRamping(boolean set) { 550 _isRamping = set; 551 } 552 553 /** 554 * Get the Speed type name. _speedType is the type when moving. Used to restore 555 * speeds aspects of signals when halts or other conditions have stopped the train. 556 * If 'absolute' is true return the absolute speed of the train, i.e. 'Stop' if 557 * train is not moving. 558 * @param absolute which speed type, absolute or allowed movement 559 * @return speed type 560 */ 561 protected String getSpeedType(boolean absolute) { 562 if (absolute) { 563 if (isRamping()) { // return pending type 564 return _ramp._endSpeedType; 565 } 566 if (_waitForClear || _halt) { 567 return Warrant.Stop; 568 } 569 } 570 return _speedType; 571 } 572 573 /** 574 * warrant.cancelDelayRamp() called for immediate Stop commands 575 * When die==true for ending the warrant run. 576 * @param die true for ending the warrant run 577 * @return true if _ramp not null and die is false and _isRamping 578 */ 579 protected synchronized boolean cancelRamp(boolean die) { 580 // _ramp.quit sets "stop" and notifies "waits" 581 if (_ramp != null) { 582 if (die) { 583 _ramp.quit(true); 584 _ramp.interrupt(); 585 } else { 586 if(_isRamping) { 587 _ramp.quit(false); 588 return true; 589 } 590 } 591 } 592 return false; 593 } 594 595 /** 596 * do throttle setting 597 * @param speed throttle setting about to be set. Modified to sType if from script. 598 * UnModified if from ThrottleRamp or stop speeds. 599 */ 600 protected void setSpeed(float speed) { 601 _throttle.setSpeedSetting(speed); 602 // Late update to GUI is OK, this is just an informational status display 603 if (!_abort) { 604 _warrant.fireRunStatus( PROPERTY_SPEED_CHANGE, null, null); 605 } 606 if (log.isDebugEnabled()) { 607 log.debug("{}: _throttle.setSpeedSetting({}) called, ({}).", 608 _warrant.getDisplayName(), speed, _speedType); 609 } 610 } 611 612 /** 613 * Get the current Throttle speed. 614 * If Speed is negative ( i.e. E-Stop ), sets Throttle speed to 0. 615 * @return speed setting reported by Throttle. 616 */ 617 protected float getSpeedSetting() { 618 float speed = _throttle.getSpeedSetting(); 619 if (speed < 0.0f) { 620 _throttle.setSpeedSetting(0.0f); 621 speed = _throttle.getSpeedSetting(); 622 } 623 return speed; 624 } 625 626 /** 627 * Get the current commanded speed as set in the Warrant script. 628 * @return the most recent commanded speed. 629 */ 630 protected float getScriptSpeed() { 631 return _normalSpeed; 632 } 633 634 /** 635 * Utility for unscripted speed changes. 636 * Records current type and sets time ratio. 637 * @param speedType name of speed change type 638 */ 639 private void setSpeedRatio(String speedType) { 640 if (speedType.equals(Warrant.Normal)) { 641 _timeRatio = 1.0f; 642 } else if (_normalSpeed > 0.0f) { 643 float speedMod = _speedUtil.modifySpeed(_normalSpeed, _speedType); 644 if (_normalSpeed > speedMod) { 645 float trackSpeed = _speedUtil.getTrackSpeed(speedMod); 646 _timeRatio = _speedUtil.getTrackSpeed(_normalSpeed) / trackSpeed; 647 } else { 648 _timeRatio = 1.0f; 649 } 650 } else { 651 _timeRatio = 1.0f; 652 } 653 } 654 655 /** 656 * Do immediate speed change. 657 * @param speedType speed type 658 */ 659 protected synchronized void setSpeedToType(String speedType) { 660 float speed = getSpeedSetting(); 661 if (log.isDebugEnabled()) { 662 log.debug("{}: setSpeedToType({}) speed={} scriptSpeed={}", 663 _warrant.getDisplayName(), speedType, speed, _normalSpeed); 664 } 665 if (speedType.equals(Warrant.Stop)) { 666 setStop(false); 667 advanceToCommandIndex(_idxCurrentCommand + 1); // skip current command 668 } else if (speedType.equals(Warrant.EStop)) { 669 setStop(true); 670 advanceToCommandIndex(_idxCurrentCommand + 1); // skip current command 671 } else if (speedType.equals(getSpeedType(true))) { 672 return; 673 } else { 674 _speedType = speedType; // set speedType regardless 675 setSpeedRatio(speedType); 676 float speedMod = _speedUtil.modifySpeed(_normalSpeed, _speedType); 677 _speedUtil.speedChange(speedMod); // call before this setting to compute travel of last setting 678 setSpeed(speedMod); 679 } 680 } 681 682 /** 683 * Command to stop (or resume speed) of train from Warrant.controlRunTrain() 684 * of user's override of throttle script. Also from error conditions 685 * such as losing detection of train's location. 686 * @param halt true if train should halt 687 */ 688 protected synchronized void setHalt(boolean halt) { 689 if (log.isDebugEnabled()) { 690 log.debug("{}: setHalt({}): _atHalt= {}, _waitForClear= {}", 691 _warrant.getDisplayName(), halt, _atHalt, _waitForClear); 692 } 693 if (!halt) { // resume normal running 694 _halt = false; 695 if (!_atClear) { 696 log.debug("setHalt calls notify()"); 697 notifyAll(); // free wait at _atHalt 698 } 699 } else { 700 _halt = true; 701 } 702 } 703 704 private long getTimeToNextCommand() { 705 if (_commandTime > 0) { 706 // millisecs already moving on pending command's time. 707 long elapsedTime = System.currentTimeMillis() - _commandTime; 708 return Math.max(0, (_currentCommand.getTime() - elapsedTime)); 709 } 710 return 0; 711 } 712 713 /** 714 * Command to stop or smoothly resume speed. Stop due to 715 * signal or occupation stopping condition ahead. Caller 716 * follows with call for type of stop to make. 717 * Track condition override of throttle script. 718 * @param wait true if train should stop 719 */ 720 protected void setWaitforClear(boolean wait) { 721 if (log.isDebugEnabled()) { 722 log.debug("{}: setWaitforClear({}): _atClear= {}, throttle speed= {}, _halt= {}", 723 _warrant.getDisplayName(), wait, _atClear, getSpeedSetting(), _halt); 724 } 725 if (!wait) { // resume normal running 726 synchronized (_clearLockObject) { 727 log.debug("setWaitforClear calls notify"); 728 _waitForClear = false; 729 _clearLockObject.notifyAll(); // free wait at _atClear 730 } 731 } else { 732 _waitForClear = true; 733 } 734 } 735 736 /** 737 * Generate Debug Info for Warrant progression state. 738 * @return String for use in debug output. 739 */ 740 String debugInfo() { 741 StringBuilder info = new StringBuilder("\n"); 742 info.append(getName()); info.append(" on warrant= "); info.append(_warrant.getDisplayName()); 743 info.append("\nThread.State= "); info.append(getState()); 744 info.append(", isAlive= "); info.append(isAlive()); 745 info.append(", isInterrupted= "); info.append(isInterrupted()); 746 info.append("\n\tThrottle setting= "); info.append(getSpeedSetting()); 747 info.append(", scriptSpeed= "); info.append(getScriptSpeed()); 748 info.append(". runstate= "); info.append(Warrant.RUN_STATE[getRunState()]); 749 int cmdIdx = getCurrentCommandIndex(); 750 751 if (cmdIdx < _commands.size()) { 752 info.append("\n\tCommand #"); info.append(cmdIdx + 1); 753 info.append(": "); info.append(_commands.get(cmdIdx).toString()); 754 } else { 755 info.append("\n\t\tAt last command."); 756 } 757 // Note: command indexes biased from 0 to 1 to match Warrant's 1-based display of commands. 758 info.append("\n\tEngineer flags: _waitForClear= "); info.append(_waitForClear); 759 info.append(", _atclear= "); info.append(_atClear); 760 info.append(", _halt= "); info.append(_halt); 761 info.append(", _atHalt= "); info.append(_atHalt); 762 if (_synchBlock != null) { 763 info.append("\n\t\tWaiting for Sync at \"");info.append(_synchBlock.getDisplayName()); 764 } 765 info.append("\"\n\t\t_setRunOnET= "); info.append(_setRunOnET); 766 info.append(", _runOnET= "); info.append(_runOnET); 767 info.append("\n\t\t_stopPending= "); info.append(_stopPending); 768 info.append(", _abort= "); info.append(_abort); 769 info.append("\n\t_speedType= \""); info.append(_speedType); info.append("\" SpeedState= "); 770 info.append(getSpeedState().toString()); info.append("\n\tStack trace:"); 771 for (StackTraceElement elem : getStackTrace()) { 772 info.append("\n\t\t"); 773 info.append(elem.getClassName()); info.append("."); info.append(elem.getMethodName()); 774 info.append(", line "); info.append(elem.getLineNumber()); 775 } 776 if (_ramp != null) { 777 info.append("\n\tRamp Thread.State= "); info.append(_ramp.getState()); 778 info.append(", isAlive= "); info.append(_ramp.isAlive()); 779 info.append(", isInterrupted= "); info.append(_ramp.isInterrupted()); 780 info.append("\n\tRamp flags: _isRamping= "); info.append(_isRamping); 781 info.append(", stop= "); info.append(_ramp.stop); 782 info.append(", _die= "); info.append(_ramp._die); 783 info.append("\n\tRamp Type: "); info.append(_ramp._rampDown ? "DOWN" : "UP");info.append(" ramp"); 784 info.append("\n\t\tEndSpeedType= \""); info.append(_ramp._endSpeedType); 785 int endIdx = _ramp.getEndBlockIndex(); 786 info.append("\"\n\t\tEndBlockIdx= "); info.append(endIdx); 787 if (endIdx >= 0) { 788 info.append(" EndBlock= \""); 789 info.append(_warrant.getBlockAt(endIdx).getDisplayName()); 790 } 791 info.append("\""); info.append("\n\tStack trace:"); 792 for (StackTraceElement elem : _ramp.getStackTrace()) { 793 info.append("\n\t\t"); 794 info.append(elem.getClassName()); info.append("."); info.append(elem.getMethodName()); 795 info.append(", line "); info.append(elem.getLineNumber()); 796 } 797 } else { 798 info.append("\n\tNo ramp."); 799 } 800 return info.toString(); 801 } 802 803 /** 804 * Immediate stop command from Warrant.controlRunTrain()-user 805 * or from Warrant.goingInactive()-train lost 806 * or from setMovement()-overrun, possible collision risk. 807 * Do not ramp. 808 * @param eStop true for emergency stop 809 */ 810 private synchronized void setStop(boolean eStop) { 811 float speed = _throttle.getSpeedSetting(); 812 if (speed <= 0.0f && (_waitForClear || _halt)) { 813 return; 814 } 815 cancelRamp(false); 816 if (eStop) { 817 setHalt(true); 818 setSpeed(-0.1f); 819 setSpeed(0.0f); 820 } else { 821 setSpeed(0.0f); 822 setWaitforClear(true); 823 } 824 log.debug("{}: setStop({}) from speed={} scriptSpeed={}", 825 _warrant.getDisplayName(), eStop, speed, _normalSpeed); 826 } 827 828 /** 829 * Get the Warrant SpeedState ENUM. 830 * @return e.g. Warrant.SpeedState.RAMPING_DOWN or SpeedState.STEADY_SPEED. 831 */ 832 protected Warrant.SpeedState getSpeedState() { 833 if (isRamping()) { 834 if (_ramp._rampDown) { 835 return Warrant.SpeedState.RAMPING_DOWN; 836 } else { 837 return Warrant.SpeedState.RAMPING_UP; 838 } 839 } 840 return Warrant.SpeedState.STEADY_SPEED; 841 } 842 843 /** 844 * Get the Warrant run state. 845 * @return e.g. Warrant.WAIT_FOR_SENSOR or Warrant.RUNNING. 846 */ 847 protected int getRunState() { 848 if (_stopPending) { 849 if (_halt) { 850 return Warrant.RAMP_HALT; 851 } 852 return Warrant.STOP_PENDING; 853 } else if (_halt) { 854 return Warrant.HALT; 855 } else if (_waitForClear) { 856 return Warrant.WAIT_FOR_CLEAR; 857 } else if (_waitForSensor) { 858 return Warrant.WAIT_FOR_SENSOR; 859 } else if (_abort) { 860 return Warrant.ABORT; 861 } else if (_synchBlock != null) { 862 return Warrant.WAIT_FOR_TRAIN; 863 } else if (isRamping()) { 864 return Warrant.SPEED_RESTRICTED; 865 } 866 return Warrant.RUNNING; 867 } 868 869 /** 870 * Stop the Engineer run. 871 * @param abort true to abort, else false. 872 * @param turnOffFunctions true to set Functions 0, 1, 2 and 3 to off ( if speed is > 0 ). 873 */ 874 @SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification="quit is called another thread to clear all ramp waits") 875 public void stopRun(boolean abort, boolean turnOffFunctions) { 876 if (abort) { 877 _abort =true; 878 } 879 _stopRequested = true; 880 881 synchronized (_synchLockObject) { 882 _synchLockObject.notifyAll(); 883 } 884 synchronized (_clearLockObject) { 885 _clearLockObject.notifyAll(); 886 } 887 synchronized (this) { 888 notifyAll(); 889 } 890 891 cancelRamp(true); 892 if (_waitSensor != null) { 893 _waitSensor.removePropertyChangeListener(this); 894 } 895 896 if (_throttle != null) { 897 if (_throttle.getSpeedSetting() > 0.0f) { 898 if (abort) { 899 _throttle.setSpeedSetting(-1.0f); 900 } 901 setSpeed(0.0f); 902 if (turnOffFunctions) { 903 _throttle.setFunction(0, false); 904 _throttle.setFunction(1, false); 905 _throttle.setFunction(2, false); 906 _throttle.setFunction(3, false); 907 } 908 } 909 _warrant.releaseThrottle(_throttle); 910 } 911 } 912 913 private void setForward(ValueType type) { 914 if (type == ValueType.VAL_TRUE) { 915 _throttle.setIsForward(true); 916 } else if (type == ValueType.VAL_FALSE) { 917 _throttle.setIsForward(false); 918 } else { 919 throw new java.lang.IllegalArgumentException("setForward type " + type + " wrong"); 920 } 921 } 922 923 private void setFunction(int cmdNum, ValueType type) { 924 if ( cmdNum < 0 || cmdNum >= _throttle.getFunctions().length ) { 925 throw new java.lang.IllegalArgumentException("setFunction " + cmdNum + " out of range"); 926 } 927 if (type == ValueType.VAL_ON) { 928 _throttle.setFunction(cmdNum, true); 929 } else if (type == ValueType.VAL_OFF) { 930 _throttle.setFunction(cmdNum,false); 931 } else { 932 throw new java.lang.IllegalArgumentException("setFunction type " + type + " wrong"); 933 } 934 } 935 936 private void setFunctionMomentary(int cmdNum, ValueType type) { 937 if ( cmdNum < 0 || cmdNum >= _throttle.getFunctionsMomentary().length ) { 938 log.error("Function value {} out of range",cmdNum); 939 throw new java.lang.IllegalArgumentException("setFunctionMomentary " + cmdNum + " out of range"); 940 } 941 if (type == ValueType.VAL_ON) { 942 _throttle.setFunctionMomentary(cmdNum, true); 943 } else if (type == ValueType.VAL_OFF) { 944 _throttle.setFunctionMomentary(cmdNum,false); 945 } else { 946 throw new java.lang.IllegalArgumentException("setFunctionMomentary type " + type + " wrong"); 947 } 948 } 949 950 /** 951 * Set Memory value 952 */ 953 private void setMemory(NamedBeanHandle<?> handle, CommandValue cmdVal) { 954 NamedBean bean = handle.getBean(); 955 if (!(bean instanceof Memory)) { 956 log.error("setMemory: {} not a Memory!", bean ); 957 return; 958 } 959 Memory m = (Memory)bean; 960 ValueType type = cmdVal.getType(); 961 962 if (Warrant._trace || log.isDebugEnabled()) { 963 log.info("{} : Set memory", Bundle.getMessage("setMemory", 964 _warrant.getTrainName(), m.getDisplayName(), cmdVal.getText())); 965 } 966 _warrant.fireRunStatus( PROPERTY_MEMORY_SET_COMMAND, type.toString(), m.getDisplayName()); 967 m.setValue(cmdVal.getText()); 968 } 969 970 /** 971 * Set Sensor state 972 */ 973 private void setSensor(NamedBeanHandle<?> handle, CommandValue cmdVal) { 974 NamedBean bean = handle.getBean(); 975 if (!(bean instanceof Sensor)) { 976 log.error("setSensor: {} not a Sensor!", bean ); 977 return; 978 } 979 Sensor s = (Sensor)bean; 980 ValueType type = cmdVal.getType(); 981 try { 982 if (Warrant._trace || log.isDebugEnabled()) { 983 log.info("{} : Set Sensor", Bundle.getMessage("setSensor", 984 _warrant.getTrainName(), s.getDisplayName(), type.toString())); 985 } 986 _warrant.fireRunStatus( PROPERTY_SENSOR_SET_COMMAND, type.toString(), s.getDisplayName()); 987 if (type == ValueType.VAL_ACTIVE) { 988 s.setKnownState( Sensor.ACTIVE); 989 } else if (type == ValueType.VAL_INACTIVE) { 990 s.setKnownState( Sensor.INACTIVE); 991 } else { 992 throw new java.lang.IllegalArgumentException("setSensor type " + type + " wrong"); 993 } 994 } catch (jmri.JmriException e) { 995 log.warn("Exception setting sensor {} in action", handle.toString()); 996 } 997 } 998 999 /** 1000 * Wait for Sensor state event 1001 */ 1002 private void waitForSensor(NamedBeanHandle<?> handle, CommandValue cmdVal) { 1003 if (_waitSensor != null) { 1004 _waitSensor.removePropertyChangeListener(this); 1005 } 1006 NamedBean bean = handle.getBean(); 1007 if (!(bean instanceof Sensor)) { 1008 log.error("setSensor: {} not a Sensor!", bean ); 1009 return; 1010 } 1011 _waitSensor = (Sensor)bean; 1012 ThrottleSetting.ValueType type = cmdVal.getType(); 1013 if (type == ValueType.VAL_ACTIVE) { 1014 _sensorWaitState = Sensor.ACTIVE; 1015 } else if (type == ValueType.VAL_INACTIVE) { 1016 _sensorWaitState = Sensor.INACTIVE; 1017 } else { 1018 throw new java.lang.IllegalArgumentException("waitForSensor type " + type + " wrong"); 1019 } 1020 synchronized (this) { 1021 _sensorStateReached = false; 1022 _releaseSensorWait = false; 1023 } 1024 // listen before reading the state so that a change in between is not missed 1025 _waitSensor.addPropertyChangeListener(this); 1026 int state = _waitSensor.getKnownState(); 1027 if (state == _sensorWaitState) { 1028 log.info("Engineer: state of event sensor {} already at state {}", 1029 _waitSensor.getDisplayName(), type.toString()); 1030 clearSensor(); 1031 return; 1032 } 1033 if (log.isDebugEnabled()) { 1034 log.debug("Listen for propertyChange of {}, wait for State= {}", 1035 _waitSensor.getDisplayName(), _sensorWaitState); 1036 } 1037 // suspend commands until sensor changes state 1038 synchronized (this) { // DO NOT USE _waitForSensor for synch 1039 _waitForSensor = true; 1040 while (_waitForSensor) { 1041 try { 1042 if (Warrant._trace || log.isDebugEnabled()) { 1043 log.info("{} : waitSensor", Bundle.getMessage("waitSensor", 1044 _warrant.getTrainName(), _waitSensor.getDisplayName(), type.toString())); 1045 } 1046 _warrant.fireRunStatus( PROPERTY_SENSOR_WAIT_COMMAND, type.toString(), _waitSensor.getDisplayName()); 1047 // Other events, such as the end of a speed ramp, also notify this thread, 1048 // so wait until the sensor has reached the state or the operator releases the train. 1049 while (!_sensorStateReached && !_releaseSensorWait && !_abort && !_stopRequested) { 1050 wait(); 1051 } 1052 if (!_abort && !_stopRequested) { 1053 String name = _waitSensor.getDisplayName(); // save name, _waitSensor will be null 'eventually' 1054 if (!_sensorStateReached) { 1055 log.info("{}: wait for sensor {} released by the operator", _warrant.getTrainName(), name); 1056 } 1057 if (Warrant._trace || log.isDebugEnabled()) { 1058 log.info("{} : wait Sensor Change", Bundle.getMessage("waitSensorChange", 1059 _warrant.getTrainName(), name)); 1060 } 1061 _warrant.fireRunStatus( PROPERTY_SENSOR_WAIT_COMMAND, null, name); 1062 } 1063 } catch (InterruptedException ie) { 1064 if (_stopRequested) { 1065 log.debug("Engineer stopped at waitForSensor \"{}\"", _waitSensor.getDisplayName(), ie); 1066 } else { 1067 log.error("Engineer interrupted at waitForSensor \"{}\"", _waitSensor.getDisplayName(), ie); 1068 } 1069 _warrant.debugInfo(); 1070 Thread.currentThread().interrupt(); 1071 } finally { 1072 clearSensor(); 1073 } 1074 } 1075 } 1076 } 1077 1078 /** 1079 * Release a train held by a Wait Sensor command in its script, because 1080 * the operator has asked it to run, with Resume, Increase Speed or Move 1081 * into next block. This is the way past a sensor that does not change, 1082 * for example because it has failed. Other wake-ups of this thread, such 1083 * as the end of a speed ramp, do not end a sensor wait. 1084 */ 1085 protected synchronized void releaseSensorWait() { 1086 if (_waitForSensor) { 1087 _releaseSensorWait = true; 1088 notifyAll(); 1089 } 1090 } 1091 1092 private void clearSensor() { 1093 if (_waitSensor != null) { 1094 _waitSensor.removePropertyChangeListener(this); 1095 } 1096 _sensorWaitState = 0; 1097 _waitForSensor = false; 1098 _waitSensor = null; 1099 } 1100 1101 protected Sensor getWaitSensor() { 1102 return _waitSensor; 1103 } 1104 1105 @Override 1106 @SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification="Sensor change on another thread is expected even when Engineer (this) has not done any modifing") 1107 public void propertyChange(java.beans.PropertyChangeEvent evt) { 1108 if (log.isDebugEnabled()) { 1109 log.debug("propertyChange {} new value= {}", evt.getPropertyName(), evt.getNewValue()); 1110 } 1111 if (( Sensor.PROPERTY_KNOWN_STATE.equals(evt.getPropertyName()) 1112 && ((Number) evt.getNewValue()).intValue() == _sensorWaitState)) { 1113 synchronized (this) { 1114 _sensorStateReached = true; 1115 notifyAll(); // free sensor wait 1116 } 1117 } 1118 } 1119 1120 private void runWarrant(NamedBeanHandle<?> handle, CommandValue cmdVal) { 1121 NamedBean bean = handle.getBean(); 1122 if (!(bean instanceof Warrant)) { 1123 log.error("runWarrant: {} not a warrant!", bean ); 1124 return; 1125 } 1126 Warrant warrant = (Warrant)bean; 1127 1128 int num = Math.round(cmdVal.getFloat()); // repeated loops 1129 if (num == 0) { 1130 return; 1131 } 1132 if (num > 0) { // do the countdown for all linked warrants. 1133 num--; // decrement loop count 1134 cmdVal.setFloat(num); 1135 } 1136 1137 if (_warrant.getSpeedUtil().getDccAddress().equals(warrant.getSpeedUtil().getDccAddress())) { 1138 // Same loco, perhaps different warrant 1139 if (log.isDebugEnabled()) { 1140 log.debug("Loco address {} finishes warrant {} and starts warrant {}", 1141 warrant.getSpeedUtil().getDccAddress(), _warrant.getDisplayName(), warrant.getDisplayName()); 1142 } 1143 long time = 0; 1144 for (int i = _idxCurrentCommand+1; i < _commands.size(); i++) { 1145 ThrottleSetting cmd = _commands.get(i); 1146 time += cmd.getTime(); 1147 } 1148 // same address so this warrant (_warrant) must release the throttle before (warrant) can acquire it 1149 _checker = new CheckForTermination(_warrant, warrant, num, time); 1150 _checker.start(); 1151 log.debug("Exit runWarrant"); 1152 } else { 1153 java.awt.Color color = java.awt.Color.red; 1154 String msg = WarrantTableFrame.getDefault().runTrain(warrant, Warrant.MODE_RUN); 1155 if (msg == null) { 1156 msg = Bundle.getMessage("linkedLaunch", 1157 warrant.getDisplayName(), _warrant.getDisplayName(), 1158 warrant.getfirstOrder().getBlock().getDisplayName(), 1159 _warrant.getfirstOrder().getBlock().getDisplayName()); 1160 color = WarrantTableModel.myGreen; 1161 } 1162 if (Warrant._trace || log.isDebugEnabled()) { 1163 log.info("{} : Warrant Status", msg); 1164 } 1165 Engineer.setFrameStatusText(msg, color); 1166 } 1167 } 1168 1169 private class CheckForTermination extends Thread { 1170 Warrant oldWarrant; 1171 Warrant newWarrant; 1172 long waitTime; // time to finish remaining commands 1173 1174 CheckForTermination(@Nonnull Warrant oldWar, @Nonnull Warrant newWar, int num, long limit) { 1175 oldWarrant = oldWar; 1176 newWarrant = newWar; 1177 waitTime = limit; 1178 setName("CheckForTermination from " + oldWarrant.getDisplayName() + " to " + newWarrant.getDisplayName()); 1179 if (log.isDebugEnabled()) { 1180 log.debug("checkForTermination of \"{}\", before launching \"{}\". waitTime= {})", 1181 oldWarrant.getDisplayName(), newWarrant.getDisplayName(), waitTime); 1182 } 1183 } 1184 1185 @Override 1186 public void run() { 1187 long time = 0; 1188 synchronized (this) { 1189 while (time <= waitTime || oldWarrant.getRunMode() != Warrant.MODE_NONE) { 1190 try { 1191 wait(100); 1192 time += 100; 1193 } catch (InterruptedException ie) { 1194 log.error("Engineer interrupted at CheckForTermination of \"{}\"", 1195 oldWarrant.getDisplayName(), ie); 1196 _warrant.debugInfo(); 1197 Thread.currentThread().interrupt(); 1198 time = waitTime; 1199 } finally { 1200 } 1201 } 1202 } 1203 if (time > waitTime || log.isDebugEnabled()) { 1204 log.info("Waited {}ms for warrant \"{}\" to terminate. runMode={}", 1205 time, oldWarrant.getDisplayName(), oldWarrant.getRunMode()); 1206 } 1207 checkerDone(oldWarrant, newWarrant); 1208 } 1209 1210 // send the messages on success of linked launch completion 1211 // runs on CheckForTermination thread, not the GUI thread — avoid Swing calls here as they may cause race conditions or non-deterministic behavior 1212 private void checkerDone(Warrant oldWarrant, Warrant newWarrant) { 1213 OBlock endBlock = oldWarrant.getLastOrder().getBlock(); 1214 if (oldWarrant.getRunMode() != Warrant.MODE_NONE) { 1215 log.error("{} : Cannot Launch", Bundle.getMessage("cannotLaunch", 1216 newWarrant.getDisplayName(), oldWarrant.getDisplayName(), endBlock.getDisplayName())); 1217 return; 1218 } 1219 1220 String msg = WarrantTableFrame.getDefault().runTrain(newWarrant, Warrant.MODE_RUN); 1221 java.awt.Color color = java.awt.Color.red; 1222 if (msg == null) { 1223 CommandValue cmdVal = _currentCommand.getValue(); 1224 int num = Math.round(cmdVal.getFloat()); 1225 if (oldWarrant.equals(newWarrant)) { 1226 msg = Bundle.getMessage("reLaunch", oldWarrant.getDisplayName(), (num<0 ? "unlimited" : num)); 1227 } else { 1228 msg = Bundle.getMessage("linkedLaunch", 1229 newWarrant.getDisplayName(), oldWarrant.getDisplayName(), 1230 newWarrant.getfirstOrder().getBlock().getDisplayName(), 1231 endBlock.getDisplayName()); 1232 } 1233 color = WarrantTableModel.myGreen; 1234 } 1235 if (Warrant._trace || log.isDebugEnabled()) { 1236 log.info("{} : Launch", msg); 1237 } 1238 Engineer.setFrameStatusText(msg, color); 1239 _checker = null; 1240 } 1241 1242 } 1243 1244 @SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification="rampDone is called by ramp thread to clear Engineer waiting for it to finish") 1245 private void rampDone(boolean stop, String speedType, int endBlockIdx) { 1246 setIsRamping(false); 1247 if (!stop && !speedType.equals(Warrant.Stop)) { 1248 _speedType = speedType; 1249 setSpeedRatio(speedType); 1250 setWaitforClear(false); 1251 setHalt(false); 1252 } 1253 _stopPending = false; 1254 if (!_waitForClear && !_atHalt && !_atClear && !_holdRamp) { 1255 synchronized (this) { 1256 notifyAll(); 1257 } 1258 log.debug("{}: rampDone called notify.", _warrant.getDisplayName()); 1259 if (_currentCommand != null && _currentCommand.getCommand().equals(Command.NOOP)) { 1260 _idxCurrentCommand--; // notify advances command. Repeat wait for entry to next block 1261 } 1262 } 1263 if (log.isDebugEnabled()) { 1264 log.debug("{}: ThrottleRamp {} for speedType \"{}\". Thread.State= {}}", _warrant.getDisplayName(), 1265 (stop?"stopped":"completed"), speedType, (_ramp != null?_ramp.getState():"_ramp is null!")); 1266 } 1267 } 1268 1269 /* 1270 * ************************************************************************************* 1271 */ 1272 1273 class ThrottleRamp extends Thread { 1274 1275 private String _endSpeedType; 1276 private int _endBlockIdx = -1; // index of block where down ramp ends. 1277 private boolean stop = false; // aborts ramping 1278 private boolean _rampDown = true; 1279 private boolean _die = false; // kills ramp for good 1280 RampData rampData; 1281 1282 ThrottleRamp() { 1283 setName("Ramp(" + _warrant.getTrainName() +")"); 1284 _endBlockIdx = -1; 1285 } 1286 1287 @SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification="quit is called by another thread to clear all ramp waits") 1288 void quit(boolean die) { 1289 stop = true; 1290 synchronized (this) { 1291 notifyAll(); // free waits at the ramping time intervals 1292 } 1293 if (die) { // once set to true, do not allow resetting to false 1294 _die = die; // permanent shutdown, warrant running ending 1295 synchronized (_rampLockObject) { 1296 _rampLockObject.notifyAll(); // free wait at ramp run 1297 } 1298 } 1299 log.debug("{}: ThrottleRamp clears _ramp waits", _warrant.getDisplayName()); 1300 } 1301 1302 void setParameters(String endSpeedType, int endBlockIdx) { 1303 _endSpeedType = endSpeedType; 1304 _endBlockIdx = endBlockIdx; 1305 _stopPending = endSpeedType.equals(Warrant.Stop); 1306 } 1307 1308 boolean duplicate(String endSpeedType, int endBlockIdx) { 1309 return !(endBlockIdx != _endBlockIdx || 1310 !endSpeedType.equals(_endSpeedType)); 1311 } 1312 1313 int getEndBlockIndex() { 1314 return _endBlockIdx; 1315 } 1316 1317 /** 1318 * @param blockIdx index of block order where ramp finishes 1319 * @param cmdIdx current command index 1320 * @return command index of block where commands should not be executed 1321 */ 1322 int getCommandIndexLimit(int blockIdx, int cmdIdx) { 1323 // get next block 1324 int limit = _commands.size(); 1325 String curBlkName = _warrant.getCurrentBlockName(); 1326 String endBlkName = _warrant.getBlockAt(blockIdx).getDisplayName(); 1327 if (!curBlkName.contentEquals(endBlkName)) { 1328 for (int cmd = cmdIdx; cmd < _commands.size(); cmd++) { 1329 ThrottleSetting ts = _commands.get(cmd); 1330 if (ts.getBeanDisplayName().equals(endBlkName) ) { 1331 cmdIdx = cmd; 1332 break; 1333 } 1334 } 1335 } 1336 endBlkName = _warrant.getBlockAt(blockIdx+1).getDisplayName(); 1337 for (int cmd = cmdIdx; cmd < _commands.size(); cmd++) { 1338 ThrottleSetting ts = _commands.get(cmd); 1339 if (ts.getBeanDisplayName().equals(endBlkName) && 1340 ts.getValue().getType().equals(ValueType.VAL_NOOP)) { 1341 limit = cmd; 1342 break; 1343 } 1344 } 1345 log.debug("getCommandIndexLimit: in end block {}, limitIdx = {} in block {}", 1346 curBlkName, limit+1, _warrant.getBlockAt(blockIdx).getDisplayName()); 1347 return limit; 1348 } 1349 1350 @Override 1351 @SuppressFBWarnings(value="UW_UNCOND_WAIT", justification="waits may be indefinite until satisfied or thread aborted") 1352 public void run() { 1353 boolean failed = false; 1354 try { 1355 while (!_die) { 1356 synchronized (_rampLockObject) { 1357 while (!_rampPending && !_die) { 1358 try { 1359 _rampLockObject.wait(); // wait until notified by rampSpeedTo() or quit() 1360 } catch (InterruptedException ie) { 1361 log.debug("As expected", ie); 1362 } 1363 } 1364 _rampPending = false; 1365 } 1366 if (_die) { 1367 break; 1368 } 1369 doRamp(); 1370 } 1371 } catch (RuntimeException e) { 1372 log.error("{}: Speed ramp stopped by error, train stopped", _warrant.getDisplayName(), e); 1373 failed = true; 1374 } finally { 1375 if (failed) { 1376 _warrant.stopWarrant(true, false); 1377 } 1378 synchronized (Engineer.this) { 1379 if (_ramp == this || _ramp == null) { 1380 // flags belong to a replacing ramp otherwise 1381 setIsRamping(false); 1382 _holdRamp = false; 1383 } 1384 Engineer.this.notifyAll(); // free Engineer waiting for the ramp 1385 } 1386 } 1387 } 1388 1389 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 1390 public void doRamp() { 1391 // At the the time 'right now' is the command indexed by _idxCurrentCommand-1" 1392 // is done. The main thread (Engineer) is waiting to do the _idxCurrentCommand. 1393 // A non-scripted speed change is to begin now. 1394 // If moving, the current speed is _normalSpeed modified by the current _speedType, 1395 // that is, the actual throttle setting. 1396 // If _endBlockIdx >= 0, this indexes the block where the the speed change must be 1397 // completed. the final speed change should occur just before entry into the next 1398 // block. This final speed change must be the exit speed of block '_endBlockIdx' 1399 // modified by _endSpeedType. 1400 // If _endBlockIdx < 0, for down ramps this should be a user initiated stop (Halt) 1401 // the endSpeed should be 0. 1402 // For up ramps, the _endBlockIdx and endSpeed are unknown. The script may have 1403 // speed changes scheduled during the time needed to up ramp. Note the code below 1404 // to negotiate and modify the RampData so that the end speed of the ramp makes a 1405 // smooth transition to the speed of the script (modified by _endSpeedType) 1406 // when the script resumes. 1407 // Non-speed commands are executed at their proper times during ramps. 1408 // Ramp calculations are based on the fact that the distance traveled during the 1409 // ramp is the same as the distance the unmodified script would travel, albeit 1410 // the times of travel are quite different. 1411 // Note on ramp up endSpeed should match scripted speed modified by endSpeedType 1412 float speed = getSpeedSetting(); // current speed setting 1413 float endSpeed; // requested end speed 1414 int commandIndexLimit; 1415 if (_endBlockIdx >= 0) { 1416 commandIndexLimit = getCommandIndexLimit(_endBlockIdx, _idxCurrentCommand); 1417 endSpeed = _speedUtil.getBlockSpeedInfo(_endBlockIdx).getExitSpeed(); 1418 endSpeed = _speedUtil.modifySpeed(endSpeed, _endSpeedType); 1419 } else { 1420 commandIndexLimit = _commands.size(); 1421 endSpeed = _speedUtil.modifySpeed(_normalSpeed, _endSpeedType); 1422 } 1423 CommandValue cmdVal = _currentCommand.getValue(); 1424 long timeToSpeedCmd = getTimeToNextCommand(); 1425 _rampDown = endSpeed <= speed; 1426 1427 if (log.isDebugEnabled()) { 1428 log.debug("RAMP {} \"{}\" speed from {}, to {}, at block \"{}\" at Cmd#{} to Cmd#{}. timeToNextCmd= {}", 1429 (_rampDown ? "DOWN" : "UP"), _endSpeedType, speed, endSpeed, 1430 (_endBlockIdx>=0?_warrant.getBlockAt(_endBlockIdx).getDisplayName():"ahead"), 1431 _idxCurrentCommand+1, commandIndexLimit, timeToSpeedCmd); 1432 // Note: command indexes biased from 0 to 1 to match Warrant display of commands. 1433 } 1434 float scriptTrackSpeed = _speedUtil.getTrackSpeed(_normalSpeed); 1435 1436 int warBlockIdx = _warrant.getCurrentOrderIndex(); // block of current train position 1437 int cmdBlockIdx = -1; // block of script commnd's train position 1438 int cmdIdx = _idxCurrentCommand; 1439 while (cmdIdx > 0) { 1440 ThrottleSetting cmd = _commands.get(--cmdIdx); 1441 if (cmd.getCommand().hasBlockName()) { 1442 OBlock blk = (OBlock)cmd.getBean(); 1443 int idx = _warrant.getIndexOfBlockBefore(warBlockIdx, blk); 1444 if (idx >= 0) { 1445 cmdBlockIdx = idx; 1446 } else { 1447 cmdBlockIdx = _warrant.getIndexOfBlockAfter(blk, warBlockIdx); 1448 } 1449 break; 1450 } 1451 } 1452 if (cmdBlockIdx < 0) { 1453 cmdBlockIdx = warBlockIdx; 1454 } 1455 1456 synchronized (this) { 1457 try { 1458 if (!_rampDown) { 1459 // Up ramp may advance the train beyond the point where the script is interrupted. 1460 // The ramp up will take time and the script may have other speed commands while 1461 // ramping up. So the actual script speed may not match the endSpeed when the ramp 1462 // up distance is traveled. We must compare 'endSpeed' to 'scriptSpeed' at each 1463 // step and skip scriptSpeeds to insure that endSpeed matches scriptSpeed when 1464 // the ramp ends. 1465 rampData = _speedUtil.getRampForSpeedChange(speed, 1.0f); 1466 int timeIncrement = rampData.getRampTimeIncrement(); 1467 ListIterator<Float> iter = rampData.speedIterator(true); 1468 speed = iter.next(); // skip repeat of current speed 1469 1470 float rampDist = 0; 1471 float cmdDist = timeToSpeedCmd * scriptTrackSpeed; 1472 1473 while (!stop && iter.hasNext()) { 1474 speed = iter.next(); 1475 float s = _speedUtil.modifySpeed(_normalSpeed, _endSpeedType); 1476 if (speed > s) { 1477 setSpeed(s); 1478 break; 1479 } 1480 setSpeed(speed); 1481 1482 // during ramp down the script may have non-speed commands that should be executed. 1483 if (!stop && rampDist >= cmdDist && _idxCurrentCommand < commandIndexLimit) { 1484 warBlockIdx = _warrant.getCurrentOrderIndex(); // current train position 1485 if (_currentCommand.getCommand().hasBlockName()) { 1486 int idx = _warrant.getIndexOfBlockBefore(warBlockIdx, (OBlock)_currentCommand.getBean()); 1487 if (idx >= 0) { 1488 cmdBlockIdx = idx; 1489 } 1490 } 1491 if (cmdBlockIdx <= warBlockIdx) { 1492 Command cmd = _currentCommand.getCommand(); 1493 if (cmd.equals(Command.SPEED)) { 1494 cmdVal = _currentCommand.getValue(); 1495 _normalSpeed = cmdVal.getFloat(); 1496 scriptTrackSpeed = _speedUtil.getTrackSpeed(_normalSpeed); 1497 if (log.isDebugEnabled()) { 1498 log.debug("Cmd #{} for speed= {} skipped.", 1499 _idxCurrentCommand+1, _normalSpeed); 1500 } 1501 cmdDist = 0; 1502 } else { 1503 executeComand(_currentCommand, timeIncrement); 1504 } 1505 if (_idxCurrentCommand < _commands.size() - 1) { 1506 _currentCommand = _commands.get(++_idxCurrentCommand); 1507 cmdDist = scriptTrackSpeed * _currentCommand.getTime(); 1508 } else { 1509 cmdDist = 0; 1510 } 1511 rampDist = 0; 1512 advanceToCommandIndex(_idxCurrentCommand); // skip up to this command 1513 } // else Do not advance script commands of block ahead of train position 1514 } 1515 1516 try { 1517 wait(timeIncrement); 1518 } catch (InterruptedException ie) { 1519 stop = true; 1520 } 1521 1522 rampDist += _speedUtil.getDistanceTraveled(speed, _speedType, timeIncrement); 1523 } 1524 1525 } else { // decreasing, ramp down to a modified speed 1526 // Down ramp may advance the train beyond the point where the script is interrupted. 1527 // Any down ramp requested with _endBlockIdx >= 0 is expected to end at the end of 1528 // a block i.e. the block of BlockOrder indexed by _endBlockIdx. 1529 // Therefore script should resume at the exit to this block. 1530 // During ramp down the script may have other Non speed commands that should be executed. 1531 _warrant.downRampBegun(_endBlockIdx); 1532 1533 rampData = _speedUtil.getRampForSpeedChange(speed, endSpeed); 1534 int timeIncrement = rampData.getRampTimeIncrement(); 1535 ListIterator<Float> iter = rampData.speedIterator(false); 1536 speed = iter.previous(); // skip repeat of current throttle setting 1537 1538 float rampDist = 0; 1539 float cmdDist = timeToSpeedCmd * scriptTrackSpeed; 1540 1541 while (!stop && iter.hasPrevious()) { 1542 speed = iter.previous(); 1543 setSpeed(speed); 1544 1545 if (_endBlockIdx >= 0) { // correction code for ramps that are too long or too short 1546 int curIdx = _warrant.getCurrentOrderIndex(); 1547 if (curIdx > _endBlockIdx) { 1548 // loco overran end block. Set end speed and leave ramp 1549 setSpeed(endSpeed); 1550 stop = true; 1551 log.warn("\"{}\" Ramp to speed \"{}\" ended due to overrun into block \"{}\". throttle {} set to {}.\"{}\"", 1552 _warrant.getTrainName(), _endSpeedType, _warrant.getBlockAt(curIdx).getDisplayName(), 1553 speed, endSpeed, _warrant.getDisplayName()); 1554 } else if ( curIdx < _endBlockIdx && 1555 _endSpeedType.equals(Warrant.Stop) && Math.abs(speed - endSpeed) <.001f) { 1556 // At last speed change to set throttle was endSpeed, but train has not 1557 // reached the last block. Let loco creep to end block at current setting. 1558 if (log.isDebugEnabled()) { 1559 log.debug("Extending ramp to reach block {}. speed= {}", 1560 _warrant.getBlockAt(_endBlockIdx).getDisplayName(), speed); 1561 } 1562 int waittime = 0; 1563 float throttleIncrement = _speedUtil.getRampThrottleIncrement(); 1564 while (_endBlockIdx > _warrant.getCurrentOrderIndex() 1565 && waittime <= 60*timeIncrement && getSpeedSetting() > 0) { 1566 // Until loco reaches end block, continue current speed. 1567 if (waittime == 5*timeIncrement || waittime == 10*timeIncrement || 1568 waittime == 15*timeIncrement || waittime == 20*timeIncrement) { 1569 // maybe train stalled on previous speed step. Bump speed up a notch at 3s, another at 9 1570 setSpeed(getSpeedSetting() + throttleIncrement); 1571 } 1572 try { 1573 wait(timeIncrement); 1574 waittime += timeIncrement; 1575 } catch (InterruptedException ie) { 1576 stop = true; 1577 } 1578 } 1579 try { 1580 wait(timeIncrement); 1581 } catch (InterruptedException ie) { 1582 stop = true; 1583 } 1584 } 1585 } 1586 1587 // during ramp down the script may have non-speed commands that should be executed. 1588 if (!stop && rampDist >= cmdDist && _idxCurrentCommand < commandIndexLimit) { 1589 warBlockIdx = _warrant.getCurrentOrderIndex(); // current train position 1590 if (_currentCommand.getCommand().hasBlockName()) { 1591 int idx = _warrant.getIndexOfBlockBefore(warBlockIdx, (OBlock)_currentCommand.getBean()); 1592 if (idx >= 0) { 1593 cmdBlockIdx = idx; 1594 } 1595 } 1596 if (cmdBlockIdx <= warBlockIdx) { 1597 Command cmd = _currentCommand.getCommand(); 1598 if (cmd.equals(Command.SPEED)) { 1599 cmdVal = _currentCommand.getValue(); 1600 _normalSpeed = cmdVal.getFloat(); 1601 scriptTrackSpeed = _speedUtil.getTrackSpeed(_normalSpeed); 1602 if (log.isDebugEnabled()) { 1603 log.debug("Cmd #{} for speed= {} skipped.", 1604 _idxCurrentCommand+1, _normalSpeed); 1605 } 1606 cmdDist = 0; 1607 } else { 1608 executeComand(_currentCommand, timeIncrement); 1609 } 1610 if (_idxCurrentCommand < _commands.size() - 1) { 1611 _currentCommand = _commands.get(++_idxCurrentCommand); 1612 cmdDist = scriptTrackSpeed * _currentCommand.getTime(); 1613 } else { 1614 cmdDist = 0; 1615 } 1616 rampDist = 0; 1617 advanceToCommandIndex(_idxCurrentCommand); // skip up to this command 1618 } // else Do not advance script commands of block ahead of train position 1619 } 1620 1621 try { 1622 wait(timeIncrement); 1623 } catch (InterruptedException ie) { 1624 stop = true; 1625 } 1626 1627 rampDist += _speedUtil.getDistanceTraveled(speed, _speedType, timeIncrement); // _speedType or Warrant.Normal?? 1628 //rampDist += getTrackSpeed(speed) * timeIncrement; 1629 } 1630 1631 // Ramp done, still in endBlock. Execute any remaining non-speed commands. 1632 if (_endBlockIdx >= 0 && commandIndexLimit < _commands.size()) { 1633 long cmdStart = System.currentTimeMillis(); 1634 while (_idxCurrentCommand < commandIndexLimit) { 1635 NamedBean bean = _currentCommand.getBean(); 1636 if (bean instanceof OBlock) { 1637 if (_endBlockIdx < _warrant.getIndexOfBlockAfter((OBlock)bean, _endBlockIdx)) { 1638 // script is past end point, command should be NOOP. 1639 // regardless, don't execute any more commands. 1640 break; 1641 } 1642 } 1643 Command cmd = _currentCommand.getCommand(); 1644 if (cmd.equals(Command.SPEED)) { 1645 cmdVal = _currentCommand.getValue(); 1646 _normalSpeed = cmdVal.getFloat(); 1647 if (log.isDebugEnabled()) { 1648 log.debug("Cmd #{} for speed {} skipped. warrant {}", 1649 _idxCurrentCommand+1, _normalSpeed, _warrant.getDisplayName()); 1650 } 1651 } else { 1652 executeComand(_currentCommand, System.currentTimeMillis() - cmdStart); 1653 } 1654 _currentCommand = _commands.get(++_idxCurrentCommand); 1655 advanceToCommandIndex(_idxCurrentCommand); // skip up to this command 1656 } 1657 } 1658 } 1659 1660 } finally { 1661 if (log.isDebugEnabled()) { 1662 log.debug("Ramp Done. End Blk= {}, _idxCurrentCommand={} resumeIdx={}, commandIndexLimit={}. warrant {}", 1663 (_endBlockIdx>=0?_warrant.getBlockAt(_endBlockIdx).getDisplayName():"not required"), 1664 _idxCurrentCommand+1, _idxSkipToSpeedCommand, commandIndexLimit, _warrant.getDisplayName()); 1665 } 1666 } 1667 } 1668 rampDone(stop, _endSpeedType, _endBlockIdx); 1669 if (!stop) { 1670 _warrant.fireRunStatus( PROPERTY_RAMP_DONE, _halt, _endSpeedType); // normal completion of ramp 1671 if (Warrant._trace || log.isDebugEnabled()) { 1672 log.info(Bundle.getMessage("RampSpeed", _warrant.getTrainName(), 1673 _endSpeedType, _warrant.getCurrentBlockName())); 1674 } 1675 } else { 1676 if (Warrant._trace || log.isDebugEnabled()) { 1677 log.info(Bundle.getMessage("RampSpeed", _warrant.getTrainName(), 1678 _endSpeedType, _warrant.getCurrentBlockName()) + "-Interrupted!"); 1679 } 1680 1681 } 1682 stop = false; 1683 1684 if (_rampDown) { // check for overrun status last 1685 _warrant.downRampDone(stop, _halt, _endSpeedType, _endBlockIdx); 1686 } 1687 } 1688 } 1689 1690 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Engineer.class); 1691 1692}