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(@Nonnull Warrant oldWar, @Nonnull Warrant newWar, int num, long limit) { 1118 oldWarrant = oldWar; 1119 newWarrant = newWar; 1120 waitTime = limit; 1121 setName("CheckForTermination from " + oldWarrant.getDisplayName() + " to " + newWarrant.getDisplayName()); 1122 if (log.isDebugEnabled()) { 1123 log.debug("checkForTermination of \"{}\", before launching \"{}\". waitTime= {})", 1124 oldWarrant.getDisplayName(), newWarrant.getDisplayName(), waitTime); 1125 } 1126 } 1127 1128 @Override 1129 public void run() { 1130 long time = 0; 1131 synchronized (this) { 1132 while (time <= waitTime || oldWarrant.getRunMode() != Warrant.MODE_NONE) { 1133 try { 1134 wait(100); 1135 time += 100; 1136 } catch (InterruptedException ie) { 1137 log.error("Engineer interrupted at CheckForTermination of \"{}\"", 1138 oldWarrant.getDisplayName(), ie); 1139 _warrant.debugInfo(); 1140 Thread.currentThread().interrupt(); 1141 time = waitTime; 1142 } finally { 1143 } 1144 } 1145 } 1146 if (time > waitTime || log.isDebugEnabled()) { 1147 log.info("Waited {}ms for warrant \"{}\" to terminate. runMode={}", 1148 time, oldWarrant.getDisplayName(), oldWarrant.getRunMode()); 1149 } 1150 checkerDone(oldWarrant, newWarrant); 1151 } 1152 1153 // send the messages on success of linked launch completion 1154 private void checkerDone(Warrant oldWarrant, Warrant newWarrant) { 1155 OBlock endBlock = oldWarrant.getLastOrder().getBlock(); 1156 if (oldWarrant.getRunMode() != Warrant.MODE_NONE) { 1157 log.error("{} : Cannot Launch", Bundle.getMessage("cannotLaunch", 1158 newWarrant.getDisplayName(), oldWarrant.getDisplayName(), endBlock.getDisplayName())); 1159 return; 1160 } 1161 1162 String msg = WarrantTableFrame.getDefault().runTrain(newWarrant, Warrant.MODE_RUN); 1163 java.awt.Color color = java.awt.Color.red; 1164 if (msg == null) { 1165 CommandValue cmdVal = _currentCommand.getValue(); 1166 int num = Math.round(cmdVal.getFloat()); 1167 if (oldWarrant.equals(newWarrant)) { 1168 msg = Bundle.getMessage("reLaunch", oldWarrant.getDisplayName(), (num<0 ? "unlimited" : num)); 1169 } else { 1170 msg = Bundle.getMessage("linkedLaunch", 1171 newWarrant.getDisplayName(), oldWarrant.getDisplayName(), 1172 newWarrant.getfirstOrder().getBlock().getDisplayName(), 1173 endBlock.getDisplayName()); 1174 } 1175 color = WarrantTableModel.myGreen; 1176 } 1177 if (Warrant._trace || log.isDebugEnabled()) { 1178 log.info("{} : Launch", msg); 1179 } 1180 Engineer.setFrameStatusText(msg, color); 1181 _checker = null; 1182 } 1183 1184 } 1185 1186 @SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification="rampDone is called by ramp thread to clear Engineer waiting for it to finish") 1187 private void rampDone(boolean stop, String speedType, int endBlockIdx) { 1188 setIsRamping(false); 1189 if (!stop && !speedType.equals(Warrant.Stop)) { 1190 _speedType = speedType; 1191 setSpeedRatio(speedType); 1192 setWaitforClear(false); 1193 setHalt(false); 1194 } 1195 _stopPending = false; 1196 if (!_waitForClear && !_atHalt && !_atClear && !_holdRamp) { 1197 synchronized (this) { 1198 notifyAll(); 1199 } 1200 log.debug("{}: rampDone called notify.", _warrant.getDisplayName()); 1201 if (_currentCommand != null && _currentCommand.getCommand().equals(Command.NOOP)) { 1202 _idxCurrentCommand--; // notify advances command. Repeat wait for entry to next block 1203 } 1204 } 1205 if (log.isDebugEnabled()) { 1206 log.debug("{}: ThrottleRamp {} for speedType \"{}\". Thread.State= {}}", _warrant.getDisplayName(), 1207 (stop?"stopped":"completed"), speedType, (_ramp != null?_ramp.getState():"_ramp is null!")); 1208 } 1209 } 1210 1211 /* 1212 * ************************************************************************************* 1213 */ 1214 1215 class ThrottleRamp extends Thread { 1216 1217 private String _endSpeedType; 1218 private int _endBlockIdx = -1; // index of block where down ramp ends. 1219 private boolean stop = false; // aborts ramping 1220 private boolean _rampDown = true; 1221 private boolean _die = false; // kills ramp for good 1222 RampData rampData; 1223 1224 ThrottleRamp() { 1225 setName("Ramp(" + _warrant.getTrainName() +")"); 1226 _endBlockIdx = -1; 1227 } 1228 1229 @SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification="quit is called by another thread to clear all ramp waits") 1230 void quit(boolean die) { 1231 stop = true; 1232 synchronized (this) { 1233 notifyAll(); // free waits at the ramping time intervals 1234 } 1235 if (die) { // once set to true, do not allow resetting to false 1236 _die = die; // permanent shutdown, warrant running ending 1237 synchronized (_rampLockObject) { 1238 _rampLockObject.notifyAll(); // free wait at ramp run 1239 } 1240 } 1241 log.debug("{}: ThrottleRamp clears _ramp waits", _warrant.getDisplayName()); 1242 } 1243 1244 void setParameters(String endSpeedType, int endBlockIdx) { 1245 _endSpeedType = endSpeedType; 1246 _endBlockIdx = endBlockIdx; 1247 _stopPending = endSpeedType.equals(Warrant.Stop); 1248 } 1249 1250 boolean duplicate(String endSpeedType, int endBlockIdx) { 1251 return !(endBlockIdx != _endBlockIdx || 1252 !endSpeedType.equals(_endSpeedType)); 1253 } 1254 1255 int getEndBlockIndex() { 1256 return _endBlockIdx; 1257 } 1258 1259 /** 1260 * @param blockIdx index of block order where ramp finishes 1261 * @param cmdIdx current command index 1262 * @return command index of block where commands should not be executed 1263 */ 1264 int getCommandIndexLimit(int blockIdx, int cmdIdx) { 1265 // get next block 1266 int limit = _commands.size(); 1267 String curBlkName = _warrant.getCurrentBlockName(); 1268 String endBlkName = _warrant.getBlockAt(blockIdx).getDisplayName(); 1269 if (!curBlkName.contentEquals(endBlkName)) { 1270 for (int cmd = cmdIdx; cmd < _commands.size(); cmd++) { 1271 ThrottleSetting ts = _commands.get(cmd); 1272 if (ts.getBeanDisplayName().equals(endBlkName) ) { 1273 cmdIdx = cmd; 1274 break; 1275 } 1276 } 1277 } 1278 endBlkName = _warrant.getBlockAt(blockIdx+1).getDisplayName(); 1279 for (int cmd = cmdIdx; cmd < _commands.size(); cmd++) { 1280 ThrottleSetting ts = _commands.get(cmd); 1281 if (ts.getBeanDisplayName().equals(endBlkName) && 1282 ts.getValue().getType().equals(ValueType.VAL_NOOP)) { 1283 limit = cmd; 1284 break; 1285 } 1286 } 1287 log.debug("getCommandIndexLimit: in end block {}, limitIdx = {} in block {}", 1288 curBlkName, limit+1, _warrant.getBlockAt(blockIdx).getDisplayName()); 1289 return limit; 1290 } 1291 1292 @Override 1293 @SuppressFBWarnings(value="UW_UNCOND_WAIT", justification="waits may be indefinite until satisfied or thread aborted") 1294 public void run() { 1295 while (!_die) { 1296 setIsRamping(false); 1297 synchronized (_rampLockObject) { 1298 try { 1299 _rampLockObject.wait(); // wait until notified by rampSpeedTo() calls quit() 1300 setIsRamping(true); 1301 } catch (InterruptedException ie) { 1302 log.debug("As expected", ie); 1303 } 1304 } 1305 if (_die) { 1306 break; 1307 } 1308 stop = false; 1309 doRamp(); 1310 } 1311 } 1312 1313 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 1314 public void doRamp() { 1315 // At the the time 'right now' is the command indexed by _idxCurrentCommand-1" 1316 // is done. The main thread (Engineer) is waiting to do the _idxCurrentCommand. 1317 // A non-scripted speed change is to begin now. 1318 // If moving, the current speed is _normalSpeed modified by the current _speedType, 1319 // that is, the actual throttle setting. 1320 // If _endBlockIdx >= 0, this indexes the block where the the speed change must be 1321 // completed. the final speed change should occur just before entry into the next 1322 // block. This final speed change must be the exit speed of block '_endBlockIdx' 1323 // modified by _endSpeedType. 1324 // If _endBlockIdx < 0, for down ramps this should be a user initiated stop (Halt) 1325 // the endSpeed should be 0. 1326 // For up ramps, the _endBlockIdx and endSpeed are unknown. The script may have 1327 // speed changes scheduled during the time needed to up ramp. Note the code below 1328 // to negotiate and modify the RampData so that the end speed of the ramp makes a 1329 // smooth transition to the speed of the script (modified by _endSpeedType) 1330 // when the script resumes. 1331 // Non-speed commands are executed at their proper times during ramps. 1332 // Ramp calculations are based on the fact that the distance traveled during the 1333 // ramp is the same as the distance the unmodified script would travel, albeit 1334 // the times of travel are quite different. 1335 // Note on ramp up endSpeed should match scripted speed modified by endSpeedType 1336 float speed = getSpeedSetting(); // current speed setting 1337 float endSpeed; // requested end speed 1338 int commandIndexLimit; 1339 if (_endBlockIdx >= 0) { 1340 commandIndexLimit = getCommandIndexLimit(_endBlockIdx, _idxCurrentCommand); 1341 endSpeed = _speedUtil.getBlockSpeedInfo(_endBlockIdx).getExitSpeed(); 1342 endSpeed = _speedUtil.modifySpeed(endSpeed, _endSpeedType); 1343 } else { 1344 commandIndexLimit = _commands.size(); 1345 endSpeed = _speedUtil.modifySpeed(_normalSpeed, _endSpeedType); 1346 } 1347 CommandValue cmdVal = _currentCommand.getValue(); 1348 long timeToSpeedCmd = getTimeToNextCommand(); 1349 _rampDown = endSpeed <= speed; 1350 1351 if (log.isDebugEnabled()) { 1352 log.debug("RAMP {} \"{}\" speed from {}, to {}, at block \"{}\" at Cmd#{} to Cmd#{}. timeToNextCmd= {}", 1353 (_rampDown ? "DOWN" : "UP"), _endSpeedType, speed, endSpeed, 1354 (_endBlockIdx>=0?_warrant.getBlockAt(_endBlockIdx).getDisplayName():"ahead"), 1355 _idxCurrentCommand+1, commandIndexLimit, timeToSpeedCmd); 1356 // Note: command indexes biased from 0 to 1 to match Warrant display of commands. 1357 } 1358 float scriptTrackSpeed = _speedUtil.getTrackSpeed(_normalSpeed); 1359 1360 int warBlockIdx = _warrant.getCurrentOrderIndex(); // block of current train position 1361 int cmdBlockIdx = -1; // block of script commnd's train position 1362 int cmdIdx = _idxCurrentCommand; 1363 while (cmdIdx >= 0) { 1364 ThrottleSetting cmd = _commands.get(--cmdIdx); 1365 if (cmd.getCommand().hasBlockName()) { 1366 OBlock blk = (OBlock)cmd.getBean(); 1367 int idx = _warrant.getIndexOfBlockBefore(warBlockIdx, blk); 1368 if (idx >= 0) { 1369 cmdBlockIdx = idx; 1370 } else { 1371 cmdBlockIdx = _warrant.getIndexOfBlockAfter(blk, warBlockIdx); 1372 } 1373 break; 1374 } 1375 } 1376 if (cmdBlockIdx < 0) { 1377 cmdBlockIdx = warBlockIdx; 1378 } 1379 1380 synchronized (this) { 1381 try { 1382 if (!_rampDown) { 1383 // Up ramp may advance the train beyond the point where the script is interrupted. 1384 // The ramp up will take time and the script may have other speed commands while 1385 // ramping up. So the actual script speed may not match the endSpeed when the ramp 1386 // up distance is traveled. We must compare 'endSpeed' to 'scriptSpeed' at each 1387 // step and skip scriptSpeeds to insure that endSpeed matches scriptSpeed when 1388 // the ramp ends. 1389 rampData = _speedUtil.getRampForSpeedChange(speed, 1.0f); 1390 int timeIncrement = rampData.getRampTimeIncrement(); 1391 ListIterator<Float> iter = rampData.speedIterator(true); 1392 speed = iter.next(); // skip repeat of current speed 1393 1394 float rampDist = 0; 1395 float cmdDist = timeToSpeedCmd * scriptTrackSpeed; 1396 1397 while (!stop && iter.hasNext()) { 1398 speed = iter.next(); 1399 float s = _speedUtil.modifySpeed(_normalSpeed, _endSpeedType); 1400 if (speed > s) { 1401 setSpeed(s); 1402 break; 1403 } 1404 setSpeed(speed); 1405 1406 // during ramp down the script may have non-speed commands that should be executed. 1407 if (!stop && rampDist >= cmdDist && _idxCurrentCommand < commandIndexLimit) { 1408 warBlockIdx = _warrant.getCurrentOrderIndex(); // current train position 1409 if (_currentCommand.getCommand().hasBlockName()) { 1410 int idx = _warrant.getIndexOfBlockBefore(warBlockIdx, (OBlock)_currentCommand.getBean()); 1411 if (idx >= 0) { 1412 cmdBlockIdx = idx; 1413 } 1414 } 1415 if (cmdBlockIdx <= warBlockIdx) { 1416 Command cmd = _currentCommand.getCommand(); 1417 if (cmd.equals(Command.SPEED)) { 1418 cmdVal = _currentCommand.getValue(); 1419 _normalSpeed = cmdVal.getFloat(); 1420 scriptTrackSpeed = _speedUtil.getTrackSpeed(_normalSpeed); 1421 if (log.isDebugEnabled()) { 1422 log.debug("Cmd #{} for speed= {} skipped.", 1423 _idxCurrentCommand+1, _normalSpeed); 1424 } 1425 cmdDist = 0; 1426 } else { 1427 executeComand(_currentCommand, timeIncrement); 1428 } 1429 if (_idxCurrentCommand < _commands.size() - 1) { 1430 _currentCommand = _commands.get(++_idxCurrentCommand); 1431 cmdDist = scriptTrackSpeed * _currentCommand.getTime(); 1432 } else { 1433 cmdDist = 0; 1434 } 1435 rampDist = 0; 1436 advanceToCommandIndex(_idxCurrentCommand); // skip up to this command 1437 } // else Do not advance script commands of block ahead of train position 1438 } 1439 1440 try { 1441 wait(timeIncrement); 1442 } catch (InterruptedException ie) { 1443 stop = true; 1444 } 1445 1446 rampDist += _speedUtil.getDistanceTraveled(speed, _speedType, timeIncrement); 1447 } 1448 1449 } else { // decreasing, ramp down to a modified speed 1450 // Down ramp may advance the train beyond the point where the script is interrupted. 1451 // Any down ramp requested with _endBlockIdx >= 0 is expected to end at the end of 1452 // a block i.e. the block of BlockOrder indexed by _endBlockIdx. 1453 // Therefore script should resume at the exit to this block. 1454 // During ramp down the script may have other Non speed commands that should be executed. 1455 _warrant.downRampBegun(_endBlockIdx); 1456 1457 rampData = _speedUtil.getRampForSpeedChange(speed, endSpeed); 1458 int timeIncrement = rampData.getRampTimeIncrement(); 1459 ListIterator<Float> iter = rampData.speedIterator(false); 1460 speed = iter.previous(); // skip repeat of current throttle setting 1461 1462 float rampDist = 0; 1463 float cmdDist = timeToSpeedCmd * scriptTrackSpeed; 1464 1465 while (!stop && iter.hasPrevious()) { 1466 speed = iter.previous(); 1467 setSpeed(speed); 1468 1469 if (_endBlockIdx >= 0) { // correction code for ramps that are too long or too short 1470 int curIdx = _warrant.getCurrentOrderIndex(); 1471 if (curIdx > _endBlockIdx) { 1472 // loco overran end block. Set end speed and leave ramp 1473 setSpeed(endSpeed); 1474 stop = true; 1475 log.warn("\"{}\" Ramp to speed \"{}\" ended due to overrun into block \"{}\". throttle {} set to {}.\"{}\"", 1476 _warrant.getTrainName(), _endSpeedType, _warrant.getBlockAt(curIdx).getDisplayName(), 1477 speed, endSpeed, _warrant.getDisplayName()); 1478 } else if ( curIdx < _endBlockIdx && 1479 _endSpeedType.equals(Warrant.Stop) && Math.abs(speed - endSpeed) <.001f) { 1480 // At last speed change to set throttle was endSpeed, but train has not 1481 // reached the last block. Let loco creep to end block at current setting. 1482 if (log.isDebugEnabled()) { 1483 log.debug("Extending ramp to reach block {}. speed= {}", 1484 _warrant.getBlockAt(_endBlockIdx).getDisplayName(), speed); 1485 } 1486 int waittime = 0; 1487 float throttleIncrement = _speedUtil.getRampThrottleIncrement(); 1488 while (_endBlockIdx > _warrant.getCurrentOrderIndex() 1489 && waittime <= 60*timeIncrement && getSpeedSetting() > 0) { 1490 // Until loco reaches end block, continue current speed. 1491 if (waittime == 5*timeIncrement || waittime == 10*timeIncrement || 1492 waittime == 15*timeIncrement || waittime == 20*timeIncrement) { 1493 // maybe train stalled on previous speed step. Bump speed up a notch at 3s, another at 9 1494 setSpeed(getSpeedSetting() + throttleIncrement); 1495 } 1496 try { 1497 wait(timeIncrement); 1498 waittime += timeIncrement; 1499 } catch (InterruptedException ie) { 1500 stop = true; 1501 } 1502 } 1503 try { 1504 wait(timeIncrement); 1505 } catch (InterruptedException ie) { 1506 stop = true; 1507 } 1508 } 1509 } 1510 1511 // during ramp down the script may have non-speed commands that should be executed. 1512 if (!stop && rampDist >= cmdDist && _idxCurrentCommand < commandIndexLimit) { 1513 warBlockIdx = _warrant.getCurrentOrderIndex(); // current train position 1514 if (_currentCommand.getCommand().hasBlockName()) { 1515 int idx = _warrant.getIndexOfBlockBefore(warBlockIdx, (OBlock)_currentCommand.getBean()); 1516 if (idx >= 0) { 1517 cmdBlockIdx = idx; 1518 } 1519 } 1520 if (cmdBlockIdx <= warBlockIdx) { 1521 Command cmd = _currentCommand.getCommand(); 1522 if (cmd.equals(Command.SPEED)) { 1523 cmdVal = _currentCommand.getValue(); 1524 _normalSpeed = cmdVal.getFloat(); 1525 scriptTrackSpeed = _speedUtil.getTrackSpeed(_normalSpeed); 1526 if (log.isDebugEnabled()) { 1527 log.debug("Cmd #{} for speed= {} skipped.", 1528 _idxCurrentCommand+1, _normalSpeed); 1529 } 1530 cmdDist = 0; 1531 } else { 1532 executeComand(_currentCommand, timeIncrement); 1533 } 1534 if (_idxCurrentCommand < _commands.size() - 1) { 1535 _currentCommand = _commands.get(++_idxCurrentCommand); 1536 cmdDist = scriptTrackSpeed * _currentCommand.getTime(); 1537 } else { 1538 cmdDist = 0; 1539 } 1540 rampDist = 0; 1541 advanceToCommandIndex(_idxCurrentCommand); // skip up to this command 1542 } // else Do not advance script commands of block ahead of train position 1543 } 1544 1545 try { 1546 wait(timeIncrement); 1547 } catch (InterruptedException ie) { 1548 stop = true; 1549 } 1550 1551 rampDist += _speedUtil.getDistanceTraveled(speed, _speedType, timeIncrement); // _speedType or Warrant.Normal?? 1552 //rampDist += getTrackSpeed(speed) * timeIncrement; 1553 } 1554 1555 // Ramp done, still in endBlock. Execute any remaining non-speed commands. 1556 if (_endBlockIdx >= 0 && commandIndexLimit < _commands.size()) { 1557 long cmdStart = System.currentTimeMillis(); 1558 while (_idxCurrentCommand < commandIndexLimit) { 1559 NamedBean bean = _currentCommand.getBean(); 1560 if (bean instanceof OBlock) { 1561 if (_endBlockIdx < _warrant.getIndexOfBlockAfter((OBlock)bean, _endBlockIdx)) { 1562 // script is past end point, command should be NOOP. 1563 // regardless, don't execute any more commands. 1564 break; 1565 } 1566 } 1567 Command cmd = _currentCommand.getCommand(); 1568 if (cmd.equals(Command.SPEED)) { 1569 cmdVal = _currentCommand.getValue(); 1570 _normalSpeed = cmdVal.getFloat(); 1571 if (log.isDebugEnabled()) { 1572 log.debug("Cmd #{} for speed {} skipped. warrant {}", 1573 _idxCurrentCommand+1, _normalSpeed, _warrant.getDisplayName()); 1574 } 1575 } else { 1576 executeComand(_currentCommand, System.currentTimeMillis() - cmdStart); 1577 } 1578 _currentCommand = _commands.get(++_idxCurrentCommand); 1579 advanceToCommandIndex(_idxCurrentCommand); // skip up to this command 1580 } 1581 } 1582 } 1583 1584 } finally { 1585 if (log.isDebugEnabled()) { 1586 log.debug("Ramp Done. End Blk= {}, _idxCurrentCommand={} resumeIdx={}, commandIndexLimit={}. warrant {}", 1587 (_endBlockIdx>=0?_warrant.getBlockAt(_endBlockIdx).getDisplayName():"not required"), 1588 _idxCurrentCommand+1, _idxSkipToSpeedCommand, commandIndexLimit, _warrant.getDisplayName()); 1589 } 1590 } 1591 } 1592 rampDone(stop, _endSpeedType, _endBlockIdx); 1593 if (!stop) { 1594 _warrant.fireRunStatus( PROPERTY_RAMP_DONE, _halt, _endSpeedType); // normal completion of ramp 1595 if (Warrant._trace || log.isDebugEnabled()) { 1596 log.info(Bundle.getMessage("RampSpeed", _warrant.getTrainName(), 1597 _endSpeedType, _warrant.getCurrentBlockName())); 1598 } 1599 } else { 1600 if (Warrant._trace || log.isDebugEnabled()) { 1601 log.info(Bundle.getMessage("RampSpeed", _warrant.getTrainName(), 1602 _endSpeedType, _warrant.getCurrentBlockName()) + "-Interrupted!"); 1603 } 1604 1605 } 1606 stop = false; 1607 1608 if (_rampDown) { // check for overrun status last 1609 _warrant.downRampDone(stop, _halt, _endSpeedType, _endBlockIdx); 1610 } 1611 } 1612 } 1613 1614 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Engineer.class); 1615 1616}