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