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