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