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