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