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