001package jmri.jmrit.logix; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import java.util.ArrayList; 006import java.util.List; 007import java.util.ListIterator; 008 009import javax.annotation.concurrent.GuardedBy; 010import javax.annotation.CheckForNull; 011import javax.annotation.Nonnull; 012 013import jmri.*; 014import jmri.implementation.SignalSpeedMap; 015import jmri.util.ThreadingUtil; 016import jmri.jmrit.logix.ThrottleSetting.Command; 017import jmri.jmrit.logix.ThrottleSetting.CommandValue; 018import jmri.jmrit.logix.ThrottleSetting.ValueType; 019import jmri.util.swing.JmriJOptionPane; 020 021/** 022 * A Warrant contains the operating permissions and directives needed for a 023 * train to proceed from an Origin to a Destination. 024 * There are three modes that a Warrant may execute; 025 * <p> 026 * MODE_LEARN - Warrant is created or edited in WarrantFrame and then launched 027 * from WarrantFrame who records throttle commands from "_student" throttle. 028 * Warrant fires PropertyChanges for WarrantFrame to record when blocks are 029 * entered. "_engineer" thread is null. 030 * <p> 031 * MODE_RUN - Warrant may be launched from several places. An array of 032 * BlockOrders, _savedOrders, and corresponding _throttleCommands allow an 033 * "_engineer" thread to execute the throttle commands. The blockOrders 034 * establish the route for the Warrant to acquire and reserve OBlocks. The 035 * Warrant monitors block activity (entrances and exits, signals, rogue 036 * occupancy etc) and modifies speed as needed. 037 * <p> 038 * MODE_MANUAL - Warrant may be launched from several places. The Warrant to 039 * acquires and reserves the route from the array of BlockOrders. Throttle 040 * commands are done by a human operator. "_engineer" and "_throttleCommands" 041 * are not used. Warrant monitors block activity but does not set _stoppingBlock 042 * or _protectSignal since it cannot control speed. It does attempt to realign 043 * the route as needed, but can be thwarted. 044 * <p> 045 * Version 1.11 - remove setting of SignalHeads 046 * 047 * @author Pete Cressman Copyright (C) 2009, 2010, 2022 048 */ 049public class Warrant extends jmri.implementation.AbstractNamedBean implements ThrottleListener, java.beans.PropertyChangeListener { 050 051 public static final String Stop = InstanceManager.getDefault(SignalSpeedMap.class).getNamedSpeed(0.0f); // aspect name 052 public static final String EStop = Bundle.getMessage("EStop"); 053 public static final String Normal ="Normal"; // Cannot determine which SignalSystem(s) and their name(s) for "Clear" 054 055 /** 056 * String constant for property warrant start. 057 */ 058 public static final String PROPERTY_WARRANT_START = "WarrantStart"; 059 060 /** 061 * String constant for property stop warrant. 062 */ 063 public static final String PROPERTY_STOP_WARRANT = "StopWarrant"; 064 065 /** 066 * String constant for property throttle fail. 067 */ 068 public static final String PROPERTY_THROTTLE_FAIL = "throttleFail"; 069 070 /** 071 * String constant for property abort learn. 072 */ 073 public static final String PROPERTY_ABORT_LEARN = "abortLearn"; 074 075 /** 076 * String constant for property control change. 077 */ 078 public static final String PROPERTY_CONTROL_CHANGE = "controlChange"; 079 080 /** 081 * String constant for property control failed. 082 */ 083 public static final String PROPERTY_CONTROL_FAILED = "controlFailed"; 084 085 /** 086 * String constant for property ready to run. 087 */ 088 public static final String PROPERTY_READY_TO_RUN = "ReadyToRun"; 089 090 /** 091 * String constant for property cannot run. 092 */ 093 public static final String PROPERTY_CANNOT_RUN = "cannotRun"; 094 095 /** 096 * String constant for property block change. 097 */ 098 public static final String PROPERTY_BLOCK_CHANGE = "blockChange"; 099 100 /** 101 * String constant for property signal overrun. 102 */ 103 public static final String PROPERTY_SIGNAL_OVERRUN = "SignalOverrun"; 104 105 /** 106 * String constant for property warrant overrun. 107 */ 108 public static final String PROPERTY_WARRANT_OVERRUN = "WarrantOverrun"; 109 110 /** 111 * String constant for property warrant start. 112 */ 113 public static final String PROPERTY_OCCUPY_OVERRUN = "OccupyOverrun"; 114 115 // permanent members. 116 private List<BlockOrder> _orders; 117 private BlockOrder _viaOrder; 118 private BlockOrder _avoidOrder; 119 private List<ThrottleSetting> _commands = new ArrayList<>(); 120 protected String _trainName; // User train name for icon 121 private SpeedUtil _speedUtil; 122 private boolean _runBlind; // Unable to use block detection, must run on et only 123 private boolean _shareRoute;// only allocate one block at a time for sharing route. 124 private boolean _addTracker; // start tracker when warrant ends normally. 125 private boolean _haltStart; // Hold train in Origin block until Resume command 126 private boolean _noRamp; // do not ramp speed changes. make immediate speed change when entering approach block. 127 private boolean _nxWarrant = false; 128 129 // transient members 130 private LearnThrottleFrame _student; // need to callback learning throttle in learn mode 131 private boolean _tempRunBlind; // run mode flag to allow running on ET only 132 private boolean _delayStart; // allows start block unoccupied and wait for train 133 private boolean _lost; // helps recovery if _idxCurrentOrder block goes inactive 134 private boolean _overrun; // train overran a signal or warrant stop 135 private boolean _rampBlkOccupied; // test for overruns when speed change block occupied by another train 136 private int _idxCurrentOrder; 137 protected volatile int _runMode = MODE_NONE; // volatile: polled by CheckForTermination from its own thread 138 private Engineer _engineer; // thread that runs the train 139 @GuardedBy("this") 140 private CommandDelay _delayCommand; // thread for delayed ramp down 141 private boolean _allocated; // initial Blocks of _orders have been allocated 142 private boolean _totalAllocated; // All Blocks of _orders have been allocated 143 private boolean _routeSet; // all allocated Blocks of _orders have paths set for route 144 protected OBlock _stoppingBlock; // Block occupied by rogue train or halted 145 private int _idxStoppingBlock; // BlockOrder index of _stoppingBlock 146 private NamedBean _protectSignal; // Signal stopping train movement 147 private int _idxProtectSignal; // BlockOrder index of _protectSignal 148 149 private boolean _waitForSignal; // train may not move until false 150 private boolean _waitForBlock; // train may not move until false 151 private boolean _waitForWarrant; 152 private String _curSignalAspect; // speed type to restore when flags are cleared; 153 protected String _message; // last message returned from an action 154 private final ThrottleManager tm; 155 156 // Running modes 157 public static final int MODE_NONE = 0; 158 public static final int MODE_LEARN = 1; // Record a command list 159 public static final int MODE_RUN = 2; // Autorun, playback the command list 160 public static final int MODE_MANUAL = 3; // block detection of manually run train 161 static final String[] MODES = {"none", "LearnMode", "RunAuto", "RunManual", "Abort"}; 162 public static final int MODE_ABORT = 4; // used to set status string in WarrantTableFrame 163 164 // control states 165 public static final int STOP = 0; 166 public static final int HALT = 1; 167 public static final int RESUME = 2; 168 public static final int ABORT = 3; 169 public static final int RETRY_FWD = 4; 170 public static final int ESTOP = 5; 171 protected static final int RAMP_HALT = 6; // used only to distinguish User halt from speed change halts 172 public static final int SPEED_UP = 7; 173 public static final int RETRY_BKWD = 8; 174 public static final int DEBUG = 9; 175 static final String[] CNTRL_CMDS = {"Stop", "Halt", "Resume", "Abort", "MoveToNext", 176 "EStop", "ramp", "SpeedUp", "MoveToPrevious","Debug"}; // RAMP_HALT is not a control command 177 178 // engineer running states 179 protected static final int RUNNING = 7; 180 protected static final int SPEED_RESTRICTED = 8; 181 protected static final int WAIT_FOR_CLEAR = 9; 182 protected static final int WAIT_FOR_SENSOR = 10; 183 protected static final int WAIT_FOR_TRAIN = 11; 184 protected static final int WAIT_FOR_DELAYED_START = 12; 185 protected static final int LEARNING = 13; 186 protected static final int STOP_PENDING = 14; 187 static final String[] RUN_STATE = {"HaltStart", "atHalt", "Resumed", "Aborts", "Retried", 188 "EStop", "HaltPending", "Running", "changeSpeed", "WaitingForClear", "WaitingForSensor", 189 "RunningLate", "WaitingForStart", "RecordingScript", "StopPending"}; 190 191 static final float BUFFER_DISTANCE = 50*12*25.4F / WarrantPreferences.getDefault().getLayoutScale(); // 50 scale feet for safety distance 192 protected static boolean _trace = WarrantPreferences.getDefault().getTrace(); 193 194 // Speed states: steady, increasing, decreasing 195 static final int AT_SPEED = 1; 196 static final int RAMP_DOWN = 2; 197 static final int RAMP_UP = 3; 198 public enum SpeedState { 199 STEADY_SPEED(AT_SPEED, "SteadySpeed"), 200 RAMPING_DOWN(RAMP_DOWN, "RampingDown"), 201 RAMPING_UP(RAMP_UP, "RampingUp"); 202 203 int _speedStateId; // state id 204 String _bundleKey; // key to get state display name 205 206 SpeedState(int id, String bundleName) { 207 _speedStateId = id; 208 _bundleKey = bundleName; 209 } 210 211 public int getIntId() { 212 return _speedStateId; 213 } 214 215 @Override 216 public String toString() { 217 return Bundle.getMessage(_bundleKey); 218 } 219 } 220 221 /** 222 * Create an object with no route defined. The list of BlockOrders is the 223 * route from an Origin to a Destination 224 * 225 * @param sName system name 226 * @param uName user name 227 */ 228 public Warrant(String sName, String uName) { 229 super(sName, uName); 230 _idxCurrentOrder = -1; 231 _idxProtectSignal = -1; 232 _orders = new ArrayList<>(); 233 _runBlind = false; 234 _speedUtil = new SpeedUtil(); 235 tm = InstanceManager.getNullableDefault(ThrottleManager.class); 236 } 237 238 protected void setNXWarrant(boolean set) { 239 _nxWarrant = set; 240 } 241 protected boolean isNXWarrant() { 242 return _nxWarrant; 243 } 244 245 @Override 246 public int getState() { 247 if (_engineer != null) { 248 return _engineer.getRunState(); 249 } 250 if (_delayStart) { 251 return WAIT_FOR_DELAYED_START; 252 } 253 if (_runMode == MODE_LEARN) { 254 return LEARNING; 255 } 256 if (_runMode != MODE_NONE) { 257 return RUNNING; 258 } 259 return -1; 260 } 261 262 @Override 263 public void setState(int state) { 264 // warrant state is computed from other values 265 } 266 267 public SpeedUtil getSpeedUtil() { 268 return _speedUtil; 269 } 270 271 public void setSpeedUtil(SpeedUtil su) { 272 _speedUtil = su; 273 } 274 275 /** 276 * Return BlockOrders. 277 * 278 * @return list of block orders 279 */ 280 public List<BlockOrder> getBlockOrders() { 281 return _orders; 282 } 283 284 /** 285 * Add permanently saved BlockOrder. 286 * 287 * @param order block order 288 */ 289 public void addBlockOrder(BlockOrder order) { 290 _orders.add(order); 291 } 292 293 public void setBlockOrders(List<BlockOrder> orders) { 294 _orders = orders; 295 } 296 297 /** 298 * Return permanently saved Origin. 299 * 300 * @return origin block order 301 */ 302 public BlockOrder getfirstOrder() { 303 if (_orders.isEmpty()) { 304 return null; 305 } 306 return new BlockOrder(_orders.get(0)); 307 } 308 309 /** 310 * Return permanently saved Destination. 311 * 312 * @return destination block order 313 */ 314 public BlockOrder getLastOrder() { 315 int size = _orders.size(); 316 if (size < 2) { 317 return null; 318 } 319 return new BlockOrder(_orders.get(size - 1)); 320 } 321 322 /** 323 * Return permanently saved BlockOrder that must be included in the route. 324 * 325 * @return via block order 326 */ 327 public BlockOrder getViaOrder() { 328 if (_viaOrder == null) { 329 return null; 330 } 331 return new BlockOrder(_viaOrder); 332 } 333 334 public void setViaOrder(BlockOrder order) { 335 _viaOrder = order; 336 } 337 338 public BlockOrder getAvoidOrder() { 339 if (_avoidOrder == null) { 340 return null; 341 } 342 return new BlockOrder(_avoidOrder); 343 } 344 345 public void setAvoidOrder(BlockOrder order) { 346 _avoidOrder = order; 347 } 348 349 /** 350 * @return block order currently at the train position 351 */ 352 public final BlockOrder getCurrentBlockOrder() { 353 return getBlockOrderAt(_idxCurrentOrder); 354 } 355 356 /** 357 * @return index of block order currently at the train position 358 */ 359 public final int getCurrentOrderIndex() { 360 return _idxCurrentOrder; 361 } 362 363 protected int getNumOrders() { 364 return _orders.size(); 365 } 366 /* 367 * Used only by SCWarrant 368 * SCWarrant overrides goingActive 369 */ 370 protected void incrementCurrentOrderIndex() { 371 _idxCurrentOrder++; 372 } 373 374 /** 375 * Find index of a block AFTER BlockOrder index. 376 * 377 * @param block used by the warrant 378 * @param idx start index of search 379 * @return index of block after of block order index, -1 if not found 380 */ 381 protected int getIndexOfBlockAfter(OBlock block, int idx) { 382 for (int i = idx; i < _orders.size(); i++) { 383 if (_orders.get(i).getBlock().equals(block)) { 384 return i; 385 } 386 } 387 return -1; 388 } 389 390 /** 391 * Find index of block BEFORE BlockOrder index. 392 * 393 * @param idx start index of search 394 * @param block used by the warrant 395 * @return index of block before of block order index, -1 if not found 396 */ 397 protected int getIndexOfBlockBefore(int idx, OBlock block) { 398 for (int i = idx; i >= 0; i--) { 399 if (_orders.get(i).getBlock().equals(block)) { 400 return i; 401 } 402 } 403 return -1; 404 } 405 406 /** 407 * Call is only valid when in MODE_LEARN and MODE_RUN. 408 * 409 * @param index index of block order 410 * @return block order or null if not found 411 */ 412 protected BlockOrder getBlockOrderAt(int index) { 413 if (index >= 0 && index < _orders.size()) { 414 return _orders.get(index); 415 } 416 return null; 417 } 418 419 /** 420 * Call is only valid when in MODE_LEARN and MODE_RUN. 421 * 422 * @param idx index of block order 423 * @return block of the block order 424 */ 425 protected OBlock getBlockAt(int idx) { 426 427 BlockOrder bo = getBlockOrderAt(idx); 428 if (bo != null) { 429 return bo.getBlock(); 430 } 431 return null; 432 } 433 434 /** 435 * Call is only valid when in MODE_LEARN and MODE_RUN. 436 * 437 * @return Name of OBlock currently occupied 438 */ 439 public String getCurrentBlockName() { 440 OBlock block = getBlockAt(_idxCurrentOrder); 441 if (block == null || !block.isOccupied()) { 442 return Bundle.getMessage("Unknown"); 443 } else { 444 return block.getDisplayName(); 445 } 446 } 447 448 /** 449 * @return throttle commands 450 */ 451 public List<ThrottleSetting> getThrottleCommands() { 452 return _commands; 453 } 454 455 public void setThrottleCommands(List<ThrottleSetting> list) { 456 _commands = list; 457 } 458 459 public void addThrottleCommand(ThrottleSetting ts) { 460 if (ts == null) { 461 log.error("warrant {} cannot add null ThrottleSetting", getDisplayName()); 462 } else { 463 _commands.add(ts); 464 } 465 } 466 467 public void setTrackSpeeds() { 468 float speed = 0.0f; 469 for (ThrottleSetting ts :_commands) { 470 CommandValue cmdVal = ts.getValue(); 471 ValueType valType = cmdVal.getType(); 472 switch (valType) { 473 case VAL_FLOAT: 474 speed = _speedUtil.getTrackSpeed(cmdVal.getFloat()); 475 break; 476 case VAL_TRUE: 477 _speedUtil.setIsForward(true); 478 break; 479 case VAL_FALSE: 480 _speedUtil.setIsForward(false); 481 break; 482 default: 483 } 484 ts.setTrackSpeed(speed); 485 } 486 } 487 488 public void setNoRamp(boolean set) { 489 _noRamp = set; 490 } 491 492 public void setShareRoute(boolean set) { 493 _shareRoute = set; 494 } 495 496 public void setAddTracker (boolean set) { 497 _addTracker = set; 498 } 499 500 public void setHaltStart (boolean set) { 501 _haltStart = set; 502 } 503 504 public boolean getNoRamp() { 505 return _noRamp; 506 } 507 508 public boolean getShareRoute() { 509 return _shareRoute; 510 } 511 512 public boolean getAddTracker() { 513 return _addTracker; 514 } 515 516 public boolean getHaltStart() { 517 return _haltStart; 518 } 519 520 public String getTrainName() { 521 return _trainName; 522 } 523 524 public void setTrainName(String name) { 525 if (_runMode == MODE_NONE) { 526 _trainName = name; 527 } 528 } 529 530 public boolean getRunBlind() { 531 return _runBlind; 532 } 533 534 public void setRunBlind(boolean runBlind) { 535 _runBlind = runBlind; 536 } 537 538 /* 539 * Engineer reports its status 540 */ 541 protected void fireRunStatus(String property, Object old, Object status) { 542// jmri.util.ThreadingUtil.runOnLayout(() -> { // Will hang GUI! 543 ThreadingUtil.runOnGUIEventually(() -> { // OK but can be quite late in reporting speed changes 544 firePropertyChange(property, old, status); 545 }); 546 } 547 548 /** 549 * ****************************** state queries **************** 550 */ 551 /** 552 * @return true if listeners are installed enough to run 553 */ 554 public boolean isAllocated() { 555 return _allocated; 556 } 557 558 /** 559 * @return true if listeners are installed for entire route 560 */ 561 public boolean isTotalAllocated() { 562 return _totalAllocated; 563 } 564 565 /** 566 * Turnouts are set for the route 567 * 568 * @return true if turnouts are set 569 */ 570 public boolean hasRouteSet() { 571 return _routeSet; 572 } 573 574 /** 575 * Test if the permanent saved blocks of this warrant are free (unoccupied 576 * and unallocated) 577 * 578 * @return true if route is free 579 */ 580 public boolean routeIsFree() { 581 for (int i = 0; i < _orders.size(); i++) { 582 OBlock block = _orders.get(i).getBlock(); 583 if (!block.isFree()) { 584 return false; 585 } 586 } 587 return true; 588 } 589 590 /** 591 * Test if the permanent saved blocks of this warrant are occupied 592 * 593 * @return true if any block is occupied 594 */ 595 public boolean routeIsOccupied() { 596 for (int i = 1; i < _orders.size(); i++) { 597 OBlock block = _orders.get(i).getBlock(); 598 if ((block.getState() & Block.OCCUPIED) != 0) { 599 return true; 600 } 601 } 602 return false; 603 } 604 605 public String getMessage() { 606 return _message; 607 } 608 609 /* ************* Methods for running trains *****************/ 610/* 611 protected void setWaitingForSignal(Boolean set) { 612 _waitForSignal = set; 613 } 614 protected void setWaitingForBlock(Boolean set) { 615 _waitForBlock = set; 616 } 617 protected void setWaitingForWarrant(Boolean set) { 618 _waitForWarrant = set; 619 } 620 */ 621 protected boolean isWaitingForSignal() { 622 return _waitForSignal; 623 } 624 protected boolean isWaitingForBlock() { 625 return _waitForBlock; 626 } 627 protected boolean isWaitingForWarrant() { 628 return _waitForWarrant; 629 } 630 protected Warrant getBlockingWarrant() { 631 if (_stoppingBlock != null && !this.equals(_stoppingBlock.getWarrant())) { 632 return _stoppingBlock.getWarrant(); 633 } 634 return null; 635 } 636 637 /** 638 * @return ID of run mode 639 */ 640 public int getRunMode() { 641 return _runMode; 642 } 643 644 protected String getRunModeMessage() { 645 String modeDesc = null; 646 switch (_runMode) { 647 case MODE_NONE: 648 return Bundle.getMessage("NotRunning", getDisplayName()); 649 case MODE_LEARN: 650 modeDesc = Bundle.getMessage("Recording"); 651 break; 652 case MODE_RUN: 653 modeDesc = Bundle.getMessage("AutoRun"); 654 break; 655 case MODE_MANUAL: 656 modeDesc = Bundle.getMessage("ManualRun"); 657 break; 658 default: 659 } 660 return Bundle.getMessage("WarrantInUse", modeDesc, getDisplayName()); 661 662 } 663 664 /** 665 * Get the warrant speed message for the current throttle speed setting. 666 * This is public to provide access for scripts and LogixNG formulas. 667 * @return the current speed message or "Not available". 668 */ 669 public String getWarrantSpeedMessage() { 670 var msg = Bundle.getMessage("SpeedNotAvailable"); 671 if (_runMode == Warrant.MODE_RUN && _engineer != null) { 672 msg = getSpeedMessage(_engineer.getSpeedType(true)); 673 } 674 return msg; 675 } 676 677 @SuppressWarnings("fallthrough") 678 @SuppressFBWarnings(value = "SF_SWITCH_FALLTHROUGH") 679 protected synchronized String getRunningMessage() { 680 if (_delayStart) { 681 return Bundle.getMessage("waitForDelayStart", _trainName, getBlockAt(0).getDisplayName()); 682 } 683 switch (_runMode) { 684 case Warrant.MODE_NONE: 685 _message = null; 686 case Warrant.MODE_ABORT: 687 if (getBlockOrders().isEmpty()) { 688 return Bundle.getMessage("BlankWarrant"); 689 } 690 if (_speedUtil.getAddress() == null) { 691 return Bundle.getMessage("NoLoco"); 692 } 693 if (!(this instanceof SCWarrant) && _commands.size() <= _orders.size()) { 694 return Bundle.getMessage("NoCommands", getDisplayName()); 695 } 696 if (_message != null) { 697 if (_lost) { 698 return Bundle.getMessage("locationUnknown", _trainName, getCurrentBlockName()) + _message; 699 } else { 700 return Bundle.getMessage("Idle", _message); 701 } 702 } 703 return Bundle.getMessage("Idle"); 704 case Warrant.MODE_LEARN: 705 return Bundle.getMessage("Learning", getCurrentBlockName()); 706 case Warrant.MODE_RUN: 707 if (_engineer == null) { 708 return Bundle.getMessage("engineerGone", getCurrentBlockName()); 709 } 710 String speedMsg = getSpeedMessage(_engineer.getSpeedType(true)); // current or pending 711 int runState = _engineer.getRunState(); 712 713 int cmdIdx = _engineer.getCurrentCommandIndex(); 714 if (cmdIdx >= _commands.size()) { 715 cmdIdx = _commands.size() - 1; 716 } 717 cmdIdx++; // display is 1-based 718 OBlock block = getBlockAt(_idxCurrentOrder); 719 if ((block.getState() & (Block.OCCUPIED | Block.UNDETECTED)) == 0) { 720 return Bundle.getMessage("TrackerNoCurrentBlock", _trainName, block.getDisplayName()); 721 } 722 String blockName = block.getDisplayName(); 723 724 switch (runState) { 725 case Warrant.ABORT: 726 if (cmdIdx == _commands.size() - 1) { 727 return Bundle.getMessage("endOfScript", _trainName); 728 } 729 return Bundle.getMessage("Aborted", blockName, cmdIdx); 730 731 case Warrant.HALT: 732 return Bundle.getMessage("RampHalt", getTrainName(), blockName); 733 case Warrant.WAIT_FOR_CLEAR: 734 SpeedState ss = _engineer.getSpeedState(); 735 if (ss.equals(SpeedState.STEADY_SPEED)) { 736 return makeWaitMessage(blockName, cmdIdx); 737 } else { 738 return Bundle.getMessage("Ramping", ss.toString(), speedMsg, blockName); 739 } 740 case Warrant.WAIT_FOR_TRAIN: 741 if (_engineer.getSpeedSetting() <= 0) { 742 return makeWaitMessage(blockName, cmdIdx); 743 } else { 744 return Bundle.getMessage("WaitForTrain", cmdIdx, 745 _engineer.getSynchBlock().getDisplayName(), speedMsg); 746 } 747 case Warrant.WAIT_FOR_SENSOR: 748 return Bundle.getMessage("WaitForSensor", 749 cmdIdx, _engineer.getWaitSensor().getDisplayName(), 750 blockName, speedMsg); 751 752 case Warrant.RUNNING: 753 return Bundle.getMessage("WhereRunning", blockName, cmdIdx, speedMsg); 754 case Warrant.SPEED_RESTRICTED: 755 return Bundle.getMessage("changeSpeed", blockName, cmdIdx, speedMsg); 756 757 case Warrant.RAMP_HALT: 758 return Bundle.getMessage("HaltPending", speedMsg, blockName); 759 760 case Warrant.STOP_PENDING: 761 return Bundle.getMessage("StopPending", speedMsg, blockName, (_waitForSignal 762 ? Bundle.getMessage("Signal") : (_waitForWarrant 763 ? Bundle.getMessage("Warrant") :Bundle.getMessage("Occupancy")))); 764 765 default: 766 return _message; 767 } 768 769 case Warrant.MODE_MANUAL: 770 BlockOrder bo = getCurrentBlockOrder(); 771 if (bo != null) { 772 return Bundle.getMessage("ManualRunning", bo.getBlock().getDisplayName()); 773 } 774 return Bundle.getMessage("ManualRun"); 775 776 default: 777 } 778 return "ERROR mode= " + MODES[_runMode]; 779 } 780 781 /** 782 * Calculates the scale speed of the current throttle setting for display 783 * @param speedType name of current speed 784 * @return text message 785 */ 786 private String getSpeedMessage(String speedType) { 787 float speed = 0; 788 String units; 789 SignalSpeedMap speedMap = InstanceManager.getDefault(SignalSpeedMap.class); 790 switch (speedMap.getInterpretation()) { 791 case SignalSpeedMap.PERCENT_NORMAL: 792 speed = _engineer.getSpeedSetting() * 100; 793 float scriptSpeed = _engineer.getScriptSpeed(); 794 scriptSpeed = (scriptSpeed > 0 ? (speed/scriptSpeed) : 0); 795 units = Bundle.getMessage("percentNormalScript", Math.round(scriptSpeed)); 796 break; 797 case SignalSpeedMap.PERCENT_THROTTLE: 798 units = Bundle.getMessage("percentThrottle"); 799 speed = _engineer.getSpeedSetting() * 100; 800 break; 801 case SignalSpeedMap.SPEED_MPH: 802 units = Bundle.getMessage("mph"); 803 speed = _speedUtil.getTrackSpeed(_engineer.getSpeedSetting()) * speedMap.getLayoutScale(); 804 speed *= 2.2369363f; 805 break; 806 case SignalSpeedMap.SPEED_KMPH: 807 units = Bundle.getMessage("kph"); 808 speed = _speedUtil.getTrackSpeed(_engineer.getSpeedSetting()) * speedMap.getLayoutScale(); 809 speed *= 3.6f; 810 break; 811 default: 812 log.error("{} Unknown speed interpretation {}", getDisplayName(), speedMap.getInterpretation()); 813 throw new java.lang.IllegalArgumentException("Unknown speed interpretation " + speedMap.getInterpretation()); 814 } 815 return Bundle.getMessage("atSpeed", speedType, Math.round(speed), units); 816 } 817 818 private String makeWaitMessage(String blockName, int cmdIdx) { 819 String which = null; 820 String where = null; 821 if (_waitForSignal) { 822 which = Bundle.getMessage("Signal"); 823 OBlock protectedBlock = getBlockAt(_idxProtectSignal); 824 if (protectedBlock != null) { 825 where = protectedBlock.getDisplayName(); 826 } 827 } else if (_waitForWarrant) { 828 Warrant w = getBlockingWarrant(); 829 which = Bundle.getMessage("WarrantWait", 830 w==null ? "Unknown" : w.getDisplayName()); 831 if (_stoppingBlock != null) { 832 where = _stoppingBlock.getDisplayName(); 833 } 834 } else if (_waitForBlock) { 835 which = Bundle.getMessage("Occupancy"); 836 if (_stoppingBlock != null) { 837 where = _stoppingBlock.getDisplayName(); 838 } 839 } 840 int runState = _engineer.getRunState(); 841 if (which == null && (runState == HALT || runState == RAMP_HALT)) { 842 which = Bundle.getMessage("Halt"); 843 where = blockName; 844 } 845 if (_engineer.isRamping() && runState != RAMP_HALT) { 846 String speedMsg = getSpeedMessage(_engineer.getSpeedType(true)); 847 return Bundle.getMessage("changeSpeed", blockName, cmdIdx, speedMsg); 848 } 849 850 if (where == null) { 851 // flags can't identify cause. 852 if (_message == null) { 853 _message = Bundle.getMessage(RUN_STATE[runState], blockName); 854 } 855 return Bundle.getMessage("trainWaiting", getTrainName(), _message, blockName); 856 } 857 return Bundle.getMessage("WaitForClear", blockName, which, where); 858 } 859 860 @InvokeOnLayoutThread 861 private void startTracker() { 862 ThreadingUtil.runOnGUIEventually(() -> { 863 new Tracker(getCurrentBlockOrder().getBlock(), _trainName, 864 null, InstanceManager.getDefault(TrackerTableAction.class)); 865 }); 866 } 867 868 // Get engineer thread to TERMINATED state - didn't answer CI test problem, but let it be. 869 private void killEngineer(Engineer engineer, boolean abort, boolean functionFlag) { 870 engineer.stopRun(abort, functionFlag); // releases throttle 871 engineer.interrupt(); 872 if (!engineer.getState().equals(Thread.State.TERMINATED)) { 873 Thread curThread = Thread.currentThread(); 874 if (!curThread.equals(_engineer)) { 875 kill( engineer, abort, functionFlag, curThread); 876 } else { // can't join yourself if called by _engineer 877 class Killer implements Runnable { 878 Engineer victim; 879 boolean abortFlag; 880 boolean functionFlag; 881 Killer (Engineer v, boolean a, boolean f) { 882 victim = v; 883 abortFlag = a; 884 functionFlag = f; 885 } 886 @Override 887 public void run() { 888 kill(victim, abortFlag, functionFlag, victim); 889 } 890 } 891 final Runnable killer = new Killer(engineer, abort, functionFlag); 892 synchronized (killer) { 893 Thread hit = ThreadingUtil.newThread(killer, 894 getDisplayName()+" Killer"); 895 hit.start(); 896 } 897 } 898 } 899 } 900 901 private void kill(Engineer eng, boolean a, boolean f, Thread monitor) { 902 long time = 0; 903 while (!eng.getState().equals(Thread.State.TERMINATED) && time < 100) { 904 try { 905 eng.stopRun(a, f); // releases throttle 906 monitor.join(10); 907 } catch (InterruptedException ex) { 908 log.info("victim.join() interrupted. warrant {}", getDisplayName()); 909 } 910 time += 10; 911 } 912 _engineer = null; 913 log.debug("{}: engineer state {} after {}ms", getDisplayName(), eng.getState().toString(), time); 914 } 915 916 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 917 public void stopWarrant(boolean abort, boolean turnOffFunctions) { 918 _delayStart = false; 919 clearWaitFlags(true); 920 if (_student != null) { 921 _student.dispose(); // releases throttle 922 _student = null; 923 } 924 _curSignalAspect = null; 925 cancelDelayRamp(); 926 927 if (_engineer != null) { 928 if (!_engineer.getState().equals(Thread.State.TERMINATED)) { 929 killEngineer(_engineer, abort, turnOffFunctions); 930 } 931 if (_trace || log.isDebugEnabled()) { 932 if (abort) { 933 log.info("{} at block {}", Bundle.getMessage("warrantAbort", getTrainName(), getDisplayName()), 934 getBlockAt(_idxCurrentOrder).getDisplayName()); 935 } else { 936 log.info(Bundle.getMessage("warrantComplete", 937 getTrainName(), getDisplayName(), getBlockAt(_idxCurrentOrder).getDisplayName())); 938 } 939 } 940 } else { 941 _runMode = MODE_NONE; 942 } 943 944 if (_addTracker && _idxCurrentOrder == _orders.size()-1) { // run was complete to end 945 startTracker(); 946 } 947 _addTracker = false; 948 949 // capture before runOnGUI — deAllocate is async; CheckForTermination may reset _idxCurrentOrder on the GUI thread 950 final int capturedIdx = _idxCurrentOrder; 951 final String capturedBlockName = abort ? null : getCurrentBlockName(); 952 953 // insulate possible non-GUI thread making this call (e.g. Engineer) 954 ThreadingUtil.runOnGUI(this::deAllocate); 955 956 String bundleKey; 957 if (abort) { 958 bundleKey = capturedIdx <= 0 ? "warrantAnnull" : "warrantAbort"; 959 } else { 960 bundleKey = (capturedIdx == _orders.size() - 1) ? "warrantComplete" : "warrantEnd"; 961 } 962 fireRunStatus(PROPERTY_STOP_WARRANT, capturedBlockName, bundleKey); 963 } 964 965 /** 966 * Sets up recording and playing back throttle commands - also cleans up 967 * afterwards. MODE_LEARN and MODE_RUN sessions must end by calling again 968 * with MODE_NONE. It is important that the route be deAllocated (remove 969 * listeners). 970 * <p> 971 * Rule for (auto) MODE_RUN: 1. At least the Origin block must be owned 972 * (allocated) by this warrant. (block._warrant == this) and path set for 973 * Run Mode Rule for (auto) LEARN_RUN: 2. Entire Route must be allocated and 974 * Route Set for Learn Mode. i.e. this warrant has listeners on all block 975 * sensors in the route. Rule for MODE_MANUAL The Origin block must be 976 * allocated to this warrant and path set for the route 977 * 978 * @param mode run mode 979 * @param address DCC loco address 980 * @param student throttle frame for learn mode parameters 981 * @param commands list of throttle commands 982 * @param runBlind true if occupancy should be ignored 983 * @return error message, if any 984 */ 985 public String setRunMode(int mode, DccLocoAddress address, 986 LearnThrottleFrame student, 987 List<ThrottleSetting> commands, boolean runBlind) { 988 if (log.isDebugEnabled()) { 989 log.debug("{}: setRunMode({}) ({}) called with _runMode= {}.", 990 getDisplayName(), mode, MODES[mode], MODES[_runMode]); 991 } 992 _message = null; 993 if (_runMode != MODE_NONE) { 994 _message = getRunModeMessage(); 995 log.error("{} called setRunMode when mode= {}. {}", getDisplayName(), MODES[_runMode], _message); 996 return _message; 997 } 998 _delayStart = false; 999 _lost = false; 1000 _overrun = false; 1001 clearWaitFlags(true); 1002 if (address != null) { 1003 _speedUtil.setDccAddress(address); 1004 } 1005 _message = setPathAt(0); 1006 if (_message != null) { 1007 return _message; 1008 } 1009 1010 if (mode == MODE_LEARN) { 1011 // Cannot record if block 0 is not occupied or not dark. If dark, user is responsible for occupation 1012 if (student == null) { 1013 _message = Bundle.getMessage("noLearnThrottle", getDisplayName()); 1014 log.error("{} called setRunMode for mode= {}. {}", getDisplayName(), MODES[mode], _message); 1015 return _message; 1016 } 1017 synchronized (this) { 1018 _student = student; 1019 } 1020 // set mode before notifyThrottleFound is called 1021 _runMode = mode; 1022 } else if (mode == MODE_RUN) { 1023 if (commands != null && commands.size() > 1) { 1024 _commands = commands; 1025 } 1026 // set mode before setStoppingBlock and callback to notifyThrottleFound are called 1027 _idxCurrentOrder = 0; 1028 _runMode = mode; 1029 OBlock b = getBlockAt(0); 1030 if (b.isDark()) { 1031 _haltStart = true; 1032 } else if (!b.isOccupied()) { 1033 // continuing with no occupation of starting block 1034 _idxCurrentOrder = -1; 1035 setStoppingBlock(0); 1036 _delayStart = true; 1037 } 1038 } else if (mode == MODE_MANUAL) { 1039 if (commands != null) { 1040 _commands = commands; 1041 } 1042 } else { 1043 deAllocate(); 1044 return _message; 1045 } 1046 getBlockAt(0)._entryTime = System.currentTimeMillis(); 1047 _tempRunBlind = runBlind; 1048 if (!_delayStart) { 1049 if (mode != MODE_MANUAL) { 1050 _message = acquireThrottle(); 1051 } else { 1052 startupWarrant(); // assuming manual operator will go to start block 1053 } 1054 } 1055 return _message; 1056 } // end setRunMode 1057 1058 /////////////// start warrant run - end of create/edit/setup methods ////////////////// 1059 1060 /** 1061 * @return error message if any 1062 */ 1063 @CheckForNull 1064 protected String acquireThrottle() { 1065 String msg = null; 1066 DccLocoAddress dccAddress = _speedUtil.getDccAddress(); 1067 if (log.isDebugEnabled()) { 1068 log.debug("{}: acquireThrottle request at {}", 1069 getDisplayName(), dccAddress); 1070 } 1071 if (dccAddress == null) { 1072 msg = Bundle.getMessage("NoAddress", getDisplayName()); 1073 } else { 1074 if (tm == null) { 1075 msg = Bundle.getMessage("noThrottle", _speedUtil.getDccAddress().getNumber()); 1076 } else { 1077 if (!tm.requestThrottle(dccAddress, this, false)) { 1078 msg = Bundle.getMessage("trainInUse", dccAddress.getNumber()); 1079 } 1080 } 1081 } 1082 if (msg != null) { 1083 fireRunStatus(PROPERTY_THROTTLE_FAIL, null, msg); 1084 abortWarrant(msg); 1085 return msg; 1086 } 1087 return null; 1088 } 1089 1090 @Override 1091 public void notifyThrottleFound(DccThrottle throttle) { 1092 if (throttle == null) { 1093 _message = Bundle.getMessage("noThrottle", getDisplayName()); 1094 fireRunStatus(PROPERTY_THROTTLE_FAIL, null, _message); 1095 abortWarrant(_message); 1096 return; 1097 } 1098 if (log.isDebugEnabled()) { 1099 log.debug("{}: notifyThrottleFound for address= {}, class= {},", 1100 getDisplayName(), throttle.getLocoAddress(), throttle.getClass().getName()); 1101 } 1102 _speedUtil.setThrottle(throttle); 1103 startupWarrant(); 1104 runWarrant(throttle); 1105 } //end notifyThrottleFound 1106 1107 @Override 1108 public void notifyFailedThrottleRequest(LocoAddress address, String reason) { 1109 _message = Bundle.getMessage("noThrottle", 1110 (reason + " " + (address != null ? address.getNumber() : getDisplayName()))); 1111 fireRunStatus(PROPERTY_THROTTLE_FAIL, null, reason); 1112 abortWarrant(_message); 1113 } 1114 1115 /** 1116 * No steal or share decisions made locally 1117 * <p> 1118 * {@inheritDoc} 1119 */ 1120 @Override 1121 public void notifyDecisionRequired(LocoAddress address, DecisionType question) { 1122 } 1123 1124 protected void releaseThrottle(DccThrottle throttle) { 1125 if (throttle != null) { 1126 if (tm != null) { 1127 tm.releaseThrottle(throttle, this); 1128 } else { 1129 log.error("{} releaseThrottle. {} on thread {}", 1130 getDisplayName(), Bundle.getMessage("noThrottle", throttle.getLocoAddress()), 1131 Thread.currentThread().getName()); 1132 } 1133 _runMode = MODE_NONE; 1134 } 1135 } 1136 1137 protected void abortWarrant(String msg) { 1138 log.error("Abort warrant \"{}\" - {} ", getDisplayName(), msg); 1139 stopWarrant(true, true); 1140 } 1141 1142 /** 1143 * Pause and resume auto-running train or abort any allocation state User 1144 * issued overriding commands during run time of warrant _engineer.abort() 1145 * calls setRunMode(MODE_NONE,...) which calls deallocate all. 1146 * 1147 * @param idx index of control command 1148 * @return false if command cannot be given 1149 */ 1150 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 1151 public boolean controlRunTrain(int idx) { 1152 if (idx < 0) { 1153 return false; 1154 } 1155 boolean ret = false; 1156 if (_engineer == null) { 1157 if (log.isDebugEnabled()) { 1158 log.debug("{}: controlRunTrain({})= \"{}\" for train {} runMode= {}", 1159 getDisplayName(), idx, CNTRL_CMDS[idx], getTrainName(), MODES[_runMode]); 1160 } 1161 switch (idx) { 1162 case HALT: 1163 case RESUME: 1164 case RETRY_FWD: 1165 case RETRY_BKWD: 1166 case SPEED_UP: 1167 break; 1168 case STOP: 1169 case ABORT: 1170 if (_runMode == Warrant.MODE_LEARN) { 1171 // let WarrantFrame do the abort. (WarrantFrame listens for "abortLearn") 1172 fireRunStatus(PROPERTY_ABORT_LEARN, -MODE_LEARN, _idxCurrentOrder); 1173 } else { 1174 stopWarrant(true, true); 1175 } 1176 break; 1177 case DEBUG: 1178 debugInfo(); 1179 break; 1180 default: 1181 } 1182 return true; 1183 } 1184 int runState = _engineer.getRunState(); 1185 if (_trace || log.isDebugEnabled()) { 1186 log.info(Bundle.getMessage("controlChange", 1187 getTrainName(), Bundle.getMessage(Warrant.CNTRL_CMDS[idx]), 1188 getCurrentBlockName())); 1189 } 1190 synchronized (this) { 1191 switch (idx) { 1192 case HALT: 1193 rampSpeedTo(Warrant.Stop, -1); // ramp down 1194 _engineer.setHalt(true); 1195 ret = true; 1196 break; 1197 case RESUME: 1198 BlockOrder bo = getBlockOrderAt(_idxCurrentOrder); 1199 OBlock block = bo.getBlock(); 1200 String msg = null; 1201 if (checkBlockForRunning(_idxCurrentOrder)) { 1202 if (_waitForSignal || _waitForBlock || _waitForWarrant) { 1203 msg = makeWaitMessage(block.getDisplayName(), _idxCurrentOrder); 1204 } else { 1205 if (runState == WAIT_FOR_CLEAR) { 1206 TrainOrder to = bo.allocatePaths(this, true); 1207 if (to._cause == null) { 1208 _engineer.setWaitforClear(false); 1209 } else { 1210 msg = to._message; 1211 } 1212 } 1213 String train = (String)block.getValue(); 1214 if (train == null) { 1215 train = Bundle.getMessage("unknownTrain"); 1216 } 1217 if (block.isOccupied() && !_trainName.equals(train)) { 1218 msg = Bundle.getMessage("blockInUse", train, block.getDisplayName()); 1219 } 1220 } 1221 } 1222 if (msg != null) { 1223 ret = askResumeQuestion(block, msg); 1224 if (ret) { 1225 ret = reStartTrain(); 1226 } 1227 } else { 1228 ret = reStartTrain(); 1229 } 1230 if (!ret) { 1231// _engineer.setHalt(true); 1232 if (_message.equals(Bundle.getMessage("blockUnoccupied", block.getDisplayName()))) { 1233 ret = askResumeQuestion(block, _message); 1234 if (ret) { 1235 ret = reStartTrain(); 1236 } 1237 } 1238 } 1239 break; 1240 case SPEED_UP: 1241 // user wants to increase throttle of stalled train slowly 1242 if (checkBlockForRunning(_idxCurrentOrder)) { 1243 if ((_waitForSignal || _waitForBlock || _waitForWarrant) || 1244 (runState != RUNNING && runState != SPEED_RESTRICTED)) { 1245 block = getBlockAt(_idxCurrentOrder); 1246 msg = makeWaitMessage(block.getDisplayName(), _idxCurrentOrder); 1247 ret = askResumeQuestion(block, msg); 1248 if (ret) { 1249 ret = bumpSpeed(); 1250 } 1251 } else { 1252 ret = bumpSpeed(); 1253 } 1254 } 1255 break; 1256 case RETRY_FWD: // Force move into next block 1257 if (checkBlockForRunning(_idxCurrentOrder + 1)) { 1258 bo = getBlockOrderAt(_idxCurrentOrder + 1); 1259 block = bo.getBlock(); 1260 if ((_waitForSignal || _waitForBlock || _waitForWarrant) || 1261 (runState != RUNNING && runState != SPEED_RESTRICTED)) { 1262 msg = makeWaitMessage(block.getDisplayName(), _idxCurrentOrder); 1263 ret = askResumeQuestion(block, msg); 1264 if (ret) { 1265 ret = moveToBlock(bo, _idxCurrentOrder + 1); 1266 } 1267 } else { 1268 ret = moveToBlock(bo, _idxCurrentOrder + 1); 1269 } 1270 } 1271 break; 1272 case RETRY_BKWD: // Force move into previous block - Not enabled. 1273 if (checkBlockForRunning(_idxCurrentOrder - 1)) { 1274 bo = getBlockOrderAt(_idxCurrentOrder - 1); 1275 block = bo.getBlock(); 1276 if ((_waitForSignal || _waitForBlock || _waitForWarrant) || 1277 (runState != RUNNING && runState != SPEED_RESTRICTED)) { 1278 msg = makeWaitMessage(block.getDisplayName(), _idxCurrentOrder); 1279 ret = askResumeQuestion(block, msg); 1280 if (ret) { 1281 ret = moveToBlock(bo, _idxCurrentOrder - 1); 1282 } 1283 } else { 1284 ret = moveToBlock(bo, _idxCurrentOrder - 1); 1285 } 1286 } 1287 break; 1288 case ABORT: 1289 stopWarrant(true, true); 1290 ret = true; 1291 break; 1292// case HALT: 1293 case STOP: 1294 setSpeedToType(Stop); // sets _halt 1295 _engineer.setHalt(true); 1296 ret = true; 1297 break; 1298 case ESTOP: 1299 setSpeedToType(EStop); // E-stop & halt 1300 _engineer.setHalt(true); 1301 ret = true; 1302 break; 1303 case DEBUG: 1304 ret = debugInfo(); 1305 break; 1306 default: 1307 } 1308 } 1309 if (ret) { 1310 fireRunStatus(PROPERTY_CONTROL_CHANGE, runState, idx); 1311 } else { 1312 if (_trace || log.isDebugEnabled()) { 1313 log.info(Bundle.getMessage("controlFailed", 1314 getTrainName(), _message, 1315 Bundle.getMessage(Warrant.CNTRL_CMDS[idx]))); 1316 } 1317 fireRunStatus(PROPERTY_CONTROL_FAILED, _message, idx); 1318 } 1319 return ret; 1320 } 1321 1322 private boolean askResumeQuestion(OBlock block, String reason) { 1323 String msg = Bundle.getMessage("ResumeQuestion", reason); 1324 return ThreadingUtil.runOnGUIwithReturn(() -> { 1325 int result = JmriJOptionPane.showConfirmDialog(WarrantTableFrame.getDefault(), 1326 msg, Bundle.getMessage("ResumeTitle"), 1327 JmriJOptionPane.YES_NO_OPTION, JmriJOptionPane.QUESTION_MESSAGE); 1328 return result==JmriJOptionPane.YES_OPTION; 1329 }); 1330 } 1331 1332 // User insists to run train 1333 private boolean reStartTrain() { 1334 BlockOrder bo = getBlockOrderAt(_idxCurrentOrder); 1335 OBlock block = bo.getBlock(); 1336 if (!block.isOccupied() && !block.isDark()) { 1337 _message = Bundle.getMessage("blockUnoccupied", block.getDisplayName()); 1338 return false; 1339 } 1340 // OK, will do it as it long as you own it, and you are where you think you are there. 1341 block.setValue(_trainName); // indicate position 1342 block.setState(block.getState()); 1343 _engineer.setHalt(false); 1344 clearWaitFlags(false); 1345 _overrun = true; // allows doRestoreRunning to run at an OCCUPY state 1346 return restoreRunning(_engineer.getSpeedType(false)); 1347 } 1348 1349 // returns true if block is owned and occupied by this warrant 1350 private boolean checkBlockForRunning(int idxBlockOrder) { 1351 BlockOrder bo = getBlockOrderAt(idxBlockOrder); 1352 if (bo == null) { 1353 _message = Bundle.getMessage("BlockNotInRoute", "?"); 1354 return false; 1355 } 1356 OBlock block = bo.getBlock(); 1357 if (!block.isOccupied()) { 1358 _message = Bundle.getMessage("blockUnoccupied", block.getDisplayName()); 1359 return false; 1360 } 1361 return true; 1362 } 1363 1364 // User increases speed 1365 private boolean bumpSpeed() { 1366 // OK, will do as it long as you own it, and you are where you think you are. 1367 _engineer.setHalt(false); 1368 clearWaitFlags(false); 1369 float speedSetting = _engineer.getSpeedSetting(); 1370 if (speedSetting < 0) { // may have done E-Stop 1371 speedSetting = 0.0f; 1372 } 1373 float bumpSpeed = Math.max(WarrantPreferences.getDefault().getSpeedAssistance(), _speedUtil.getRampThrottleIncrement()); 1374 _engineer.setSpeed(speedSetting + bumpSpeed); 1375 return true; 1376 } 1377 1378 private boolean moveToBlock(BlockOrder bo, int idx) { 1379 _idxCurrentOrder = idx; 1380 _message = setPathAt(idx); // no checks. Force path set and allocation 1381 if (_message != null) { 1382 return false; 1383 } 1384 OBlock block = bo.getBlock(); 1385 if (block.equals(_stoppingBlock)) { 1386 clearStoppingBlock(); 1387 _engineer.setHalt(false); 1388 } 1389 goingActive(block); 1390 return true; 1391 } 1392 1393 protected boolean debugInfo() { 1394 if ( !log.isInfoEnabled() ) { 1395 return true; 1396 } 1397 StringBuilder info = new StringBuilder("\""); info.append(getDisplayName()); 1398 info.append("\" Train \""); info.append(getTrainName()); info.append("\" - Current Block \""); 1399 info.append(getBlockAt(_idxCurrentOrder).getDisplayName()); 1400 info.append("\" BlockOrder idx= "); info.append(_idxCurrentOrder); 1401 info.append("\n\tWait flags: _waitForSignal= "); info.append(_waitForSignal); 1402 info.append(", _waitForBlock= "); info.append(_waitForBlock); 1403 info.append(", _waitForWarrant= "); info.append(_waitForWarrant); 1404 info.append("\n\tStatus flags: _overrun= "); info.append(_overrun); info.append(", _rampBlkOccupied= "); 1405 info.append(_rampBlkOccupied);info.append(", _lost= "); info.append(_lost); 1406 if (_protectSignal != null) { 1407 info.append("\n\tWait for Signal \"");info.append(_protectSignal.getDisplayName());info.append("\" protects block "); 1408 info.append(getBlockAt(_idxProtectSignal).getDisplayName()); info.append("\" from approch block \""); 1409 info.append(getBlockAt(_idxProtectSignal - 1).getDisplayName()); info.append("\". Shows aspect \""); 1410 info.append(getSignalSpeedType(_protectSignal)); info.append("\"."); 1411 } else { 1412 info.append("\n\tNo signals ahead with speed restrictions"); 1413 } 1414 if(_stoppingBlock != null) { 1415 if (_waitForWarrant) { 1416 info.append("\n\tWait for Warrant \""); 1417 Warrant w = getBlockingWarrant(); info.append((w != null?w.getDisplayName():"Unknown")); 1418 info.append("\" owns block \"");info.append(_stoppingBlock.getDisplayName()); info.append("\""); 1419 } else { 1420 Object what = _stoppingBlock.getValue(); 1421 String who; 1422 if (what != null) { 1423 who = what.toString(); 1424 } else { 1425 who = "Unknown Train"; 1426 } 1427 info.append("\n\tWait for \""); info.append(who); info.append("\" occupying Block \""); 1428 info.append(_stoppingBlock.getDisplayName()); info.append("\""); 1429 } 1430 } else { 1431 info.append("\n\tNo occupied blocks ahead"); 1432 } 1433 if (_message != null) { 1434 info.append("\n\tLast message = ");info.append(_message); 1435 } else { 1436 info.append("\n\tNo messages."); 1437 } 1438 1439 if (_engineer != null) { 1440 info.append("\""); info.append("\n\tEngineer Stack trace:"); 1441 for (StackTraceElement elem : _engineer.getStackTrace()) { 1442 info.append("\n\t\t"); 1443 info.append(elem.getClassName()); info.append("."); info.append(elem.getMethodName()); 1444 info.append(", line "); info.append(elem.getLineNumber()); 1445 } 1446 info.append(_engineer.debugInfo()); 1447 } else { 1448 info.append("No engineer."); 1449 } 1450 log.info("\n Warrant: {}", info.toString()); 1451 return true; 1452 } 1453 1454 protected void startupWarrant() { 1455 _idxCurrentOrder = 0; 1456 // set block state to show our train occupies the block 1457 BlockOrder bo = getBlockOrderAt(0); 1458 OBlock b = bo.getBlock(); 1459 b.setValue(_trainName); 1460 b.setState(b.getState() | OBlock.RUNNING); 1461 firePropertyChange(PROPERTY_WARRANT_START, MODE_NONE, _runMode); 1462 } 1463 1464 private void runWarrant(DccThrottle throttle) { 1465 if (_runMode == MODE_LEARN) { 1466 synchronized (this) { 1467 // No Engineer. LearnControlPanel does throttle settings 1468 _student.notifyThrottleFound(throttle); 1469 } 1470 } else { 1471 if (_engineer != null) { // should not happen 1472 killEngineer(_engineer, true, true); 1473 } 1474 _engineer = new Engineer(this, throttle); 1475 1476 _speedUtil.getBlockSpeedTimes(_commands, _orders); // initialize SpeedUtil 1477 if (_tempRunBlind) { 1478 _engineer.setRunOnET(true); 1479 } 1480 if (_delayStart || _haltStart) { 1481 _engineer.setHalt(true); // throttle already at 0 1482 // user must explicitly start train (resume) in a dark block 1483 fireRunStatus(PROPERTY_READY_TO_RUN, -1, 0); // ready to start msg 1484 } 1485 _delayStart = false; 1486 _engineer.start(); 1487 1488 int runState = _engineer.getRunState(); 1489 if (_trace || log.isDebugEnabled()) { 1490 log.info("Train \"{}\" on warrant \"{}\" launched. runState= {}", getTrainName(), getDisplayName(), RUN_STATE[runState]); 1491 } 1492 if (runState != HALT && runState != RAMP_HALT) { 1493 setMovement(); 1494 } 1495 } 1496 } 1497 1498 private String setPathAt(int idx) { 1499 BlockOrder bo = _orders.get(idx); 1500 OBlock b = bo.getBlock(); 1501 String msg = b.allocate(this); 1502 if (msg == null) { 1503 OPath path1 = bo.getPath(); 1504 Portal exit = bo.getExitPortal(); 1505 OBlock block = getBlockAt(idx+1); 1506 if (block != null) { 1507 Warrant w = block.getWarrant(); 1508 if ((w != null && !w.equals(this)) || (w == null && block.isOccupied())) { 1509 msg = bo.pathsConnect(path1, exit, block); 1510 if (msg == null) { 1511 msg = bo.setPath(this); 1512 } 1513 } 1514 } 1515 b.showAllocated(this, bo.getPathName()); 1516 } 1517 return msg; 1518 } 1519 1520 /** 1521 * Allocate as many blocks as possible from the start of the warrant. 1522 * The first block must be allocated and all blocks of the route must 1523 * be in service. Otherwise partial success is OK. 1524 * Installs listeners for the entire route. 1525 * If occupation by another train is detected, a message will be 1526 * posted to the Warrant List Window. Note that warrants sharing their 1527 * clearance only allocate and set paths one block in advance. 1528 * 1529 * @param orders list of block orders 1530 * @param show _message for use ONLY to display a temporary route) continues to 1531 * allocate skipping over blocks occupied or owned by another warrant. 1532 * @return error message, if unable to allocate first block or if any block 1533 * is OUT_OF_SERVICE 1534 */ 1535 public String allocateRoute(boolean show, List<BlockOrder> orders) { 1536 if (_totalAllocated && _runMode != MODE_NONE && _runMode != MODE_ABORT) { 1537 return null; 1538 } 1539 if (orders != null) { 1540 _orders = orders; 1541 } 1542 _allocated = false; 1543 _message = null; 1544 1545 int idxSpeedChange = 0; // idxBlockOrder where speed changes 1546 do { 1547 TrainOrder to = getBlockOrderAt(idxSpeedChange).allocatePaths(this, true); 1548 switch (to._cause) { 1549 case NONE: 1550 break; 1551 case WARRANT: 1552 _waitForWarrant = true; 1553 if (_message == null) { 1554 _message = to._message; 1555 } 1556 if (!show && to._idxContrlBlock == 0) { 1557 return _message; 1558 } 1559 break; 1560 case OCCUPY: 1561 _waitForBlock = true; 1562 if (_message == null) { 1563 _message = to._message; 1564 } 1565 break; 1566 case SIGNAL: 1567 if (Stop.equals(to._speedType)) { 1568 _waitForSignal = true; 1569 if (_message == null) { 1570 _message = to._message; 1571 } 1572 } 1573 break; 1574 default: 1575 log.error("{}: allocateRoute at block \"{}\" setPath returns: {}", 1576 getDisplayName(), getBlockAt(idxSpeedChange).getDisplayName(), to.toString()); 1577 if (_message == null) { 1578 _message = to._message; 1579 } 1580 } 1581 if (!show) { 1582 if (_message != null || (_shareRoute && idxSpeedChange > 1)) { 1583 break; 1584 } 1585 } 1586 idxSpeedChange++; 1587 } while (idxSpeedChange < _orders.size()); 1588 1589 if (log.isDebugEnabled()) { 1590 log.debug("{}: allocateRoute() _shareRoute= {} show= {}. Break at {} of {}. msg= {}", 1591 getDisplayName(), _shareRoute, show, idxSpeedChange, _orders.size(), _message); 1592 } 1593 _allocated = true; // start block allocated 1594 if (_message == null) { 1595 _totalAllocated = true; 1596 if (show && _shareRoute) { 1597 _message = Bundle.getMessage("sharedRoute"); 1598 } 1599 } 1600 if (show) { 1601 return _message; 1602 } 1603 return null; 1604 } 1605 1606 /** 1607 * Deallocates blocks from the current BlockOrder list 1608 */ 1609 public void deAllocate() { 1610 if (_runMode == MODE_NONE || _runMode == MODE_ABORT) { 1611 _allocated = false; 1612 _totalAllocated = false; 1613 _routeSet = false; 1614 for (int i = 0; i < _orders.size(); i++) { 1615 deAllocateBlock(_orders.get(i).getBlock()); 1616 } 1617 } 1618 } 1619 1620 private boolean deAllocateBlock(OBlock block) { 1621 if (block.isAllocatedTo(this)) { 1622 block.deAllocate(this); 1623 if (block.equals(_stoppingBlock)){ 1624 doStoppingBlockClear(); 1625 } 1626 return true; 1627 } 1628 return false; 1629 } 1630 1631 /** 1632 * Convenience routine to use from Python to start a warrant. 1633 * 1634 * @param mode run mode 1635 */ 1636 public void runWarrant(int mode) { 1637 setRunMode(mode, null, null, null, false); 1638 } 1639 1640 /** 1641 * Set the route paths and turnouts for the warrant. Only the first block 1642 * must be allocated and have its path set. Partial success is OK. 1643 * A message of the first subsequent block that fails allocation 1644 * or path setting is written to a field that is 1645 * displayed in the Warrant List window. When running with block 1646 * detection, occupation by another train or block 'not in use' or 1647 * Signals denying movement are reasons 1648 * for such a message, otherwise only allocation to another warrant 1649 * prevents total success. Note that warrants sharing their clearance 1650 * only allocate and set paths one block in advance. 1651 * 1652 * @param show If true allocateRoute returns messages for display. 1653 * @param orders BlockOrder list of route. If null, use permanent warrant 1654 * copy. 1655 * @return message if the first block fails allocation, otherwise null 1656 */ 1657 public String setRoute(boolean show, List<BlockOrder> orders) { 1658 if (_shareRoute) { // full route of a shared warrant may be displayed 1659 deAllocate(); // clear route to allow sharing with another warrant 1660 } 1661 1662 // allocateRoute may set _message for status info, but return null msg 1663 _message = allocateRoute(show, orders); 1664 if (_message != null) { 1665 log.debug("{}: setRoute: {}", getDisplayName(), _message); 1666 return _message; 1667 } 1668 _routeSet = true; 1669 return null; 1670 } // setRoute 1671 1672 /** 1673 * Check start block for occupied for start of run 1674 * 1675 * @return error message, if any 1676 */ 1677 public String checkStartBlock() { 1678 log.debug("{}: checkStartBlock.", getDisplayName()); 1679 BlockOrder bo = _orders.get(0); 1680 OBlock block = bo.getBlock(); 1681 String msg = block.allocate(this); 1682 if (msg != null) { 1683 return msg; 1684 } 1685 if (block.isDark() || _tempRunBlind) { 1686 msg = "BlockDark"; 1687 } else if (!block.isOccupied()) { 1688 msg = "warnStart"; 1689 } 1690 return msg; 1691 } 1692 1693 protected String checkforTrackers() { 1694 BlockOrder bo = _orders.get(0); 1695 OBlock block = bo.getBlock(); 1696 log.debug("{}: checkforTrackers at block {}", getDisplayName(), block.getDisplayName()); 1697 Tracker t = InstanceManager.getDefault(TrackerTableAction.class).findTrackerIn(block); 1698 if (t != null) { 1699 return Bundle.getMessage("blockInUse", t.getTrainName(), block.getDisplayName()); 1700 } 1701 return null; 1702 } 1703 1704 /** 1705 * Report any occupied blocks in the route 1706 * 1707 * @return String 1708 */ 1709 public String checkRoute() { 1710 log.debug("{}: checkRoute.", getDisplayName()); 1711 if (_orders==null || _orders.isEmpty()) { 1712 return Bundle.getMessage("noBlockOrders"); 1713 } 1714 OBlock startBlock = _orders.get(0).getBlock(); 1715 for (int i = 1; i < _orders.size(); i++) { 1716 OBlock block = _orders.get(i).getBlock(); 1717 if (block.isOccupied() && !startBlock.equals(block)) { 1718 return Bundle.getMessage("BlockRougeOccupied", block.getDisplayName()); 1719 } 1720 Warrant w = block.getWarrant(); 1721 if (w !=null && !this.equals(w)) { 1722 return Bundle.getMessage("AllocatedToWarrant", 1723 w.getDisplayName(), block.getDisplayName(), w.getTrainName()); 1724 } 1725 } 1726 return null; 1727 } 1728 1729 @Override 1730 public void propertyChange(java.beans.PropertyChangeEvent evt) { 1731 if (!(evt.getSource() instanceof NamedBean)) { 1732 return; 1733 } 1734 String property = evt.getPropertyName(); 1735 if (log.isDebugEnabled()) { 1736 log.debug("{}: propertyChange \"{}\" new= {} source= {}", getDisplayName(), 1737 property, evt.getNewValue(), ((NamedBean) evt.getSource()).getDisplayName()); 1738 } 1739 1740 if (_protectSignal != null && _protectSignal == evt.getSource()) { 1741 if (property.equals("Aspect") || property.equals("Appearance")) { 1742 // signal controlling warrant has changed. 1743 readStoppingSignal(); 1744 } 1745 } else if (property.equals("state")) { 1746 if (_stoppingBlock != null && _stoppingBlock.equals(evt.getSource())) { 1747 // starting block is allocated but not occupied 1748 int newState = ((Number) evt.getNewValue()).intValue(); 1749 if ((newState & OBlock.OCCUPIED) != 0) { 1750 if (_delayStart) { // wait for arrival of train to begin the run 1751 // train arrived at starting block or last known block of lost train is found 1752 clearStoppingBlock(); 1753 OBlock block = getBlockAt(0); 1754 _idxCurrentOrder = 0; 1755 if (_runMode == MODE_RUN && _engineer == null) { 1756 _message = acquireThrottle(); 1757 } else if (_runMode == MODE_MANUAL) { 1758 fireRunStatus(PROPERTY_READY_TO_RUN, -1, 0); // ready to start msg 1759 _delayStart = false; 1760 } 1761 block._entryTime = System.currentTimeMillis(); 1762 block.setValue(_trainName); 1763 block.setState(block.getState() | OBlock.RUNNING); 1764 } else if ((((Number) evt.getNewValue()).intValue() & OBlock.ALLOCATED) == 0) { 1765 // blocking warrant has released allocation but train still occupies the block 1766 clearStoppingBlock(); 1767 log.debug("\"{}\" cleared its wait. but block \"{}\" remains occupied", getDisplayName(), 1768 (((Block)evt.getSource()).getDisplayName())); 1769 } 1770 } else if ((((Number) evt.getNewValue()).intValue() & OBlock.UNOCCUPIED) != 0) { 1771 // blocking occupation has left the stopping block 1772 clearStoppingBlock(); 1773 } 1774 } 1775 } 1776 } //end propertyChange 1777 1778 private String getSignalSpeedType(@Nonnull NamedBean signal) { 1779 String speedType; 1780 if (signal instanceof SignalHead) { 1781 SignalHead head = (SignalHead) signal; 1782 int appearance = head.getAppearance(); 1783 speedType = InstanceManager.getDefault(SignalSpeedMap.class) 1784 .getAppearanceSpeed(head.getAppearanceName(appearance)); 1785 if (log.isDebugEnabled()) { 1786 log.debug("{}: SignalHead {} sets appearance speed to {}", 1787 getDisplayName(), signal.getDisplayName(), speedType); 1788 } 1789 } else { 1790 SignalMast mast = (SignalMast) signal; 1791 String aspect = mast.getAspect(); 1792 speedType = InstanceManager.getDefault(SignalSpeedMap.class).getAspectSpeed( 1793 (aspect== null ? "" : aspect), mast.getSignalSystem()); 1794 if (log.isDebugEnabled()) { 1795 log.debug("{}: SignalMast {} sets aspect speed to {}", 1796 getDisplayName(), signal.getDisplayName(), speedType); 1797 } 1798 } 1799 return speedType; 1800 } 1801 1802 /** 1803 * _protectSignal made an aspect change 1804 */ 1805 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 1806 private void readStoppingSignal() { 1807 if (_idxProtectSignal < _idxCurrentOrder) { // signal is behind train. ignore 1808 changeSignalListener(null, _idxCurrentOrder); // remove signal 1809 return; 1810 } 1811 // Signals may change after entry and while the train in the block. 1812 // Normally these changes are ignored. 1813 // However for the case of an overrun stop aspect, the train is waiting. 1814 if (_idxProtectSignal == _idxCurrentOrder && !_waitForSignal) { // not waiting 1815 changeSignalListener(null, _idxCurrentOrder); // remove signal 1816 return; // normal case 1817 }// else Train previously overran stop aspect. Continue and respond to signal. 1818 1819 String speedType = getSignalSpeedType(_protectSignal); 1820 String curSpeedType; 1821 if (_waitForSignal) { 1822 curSpeedType = Stop; 1823 } else { 1824 curSpeedType = _engineer.getSpeedType(true); // current or pending ramp completion 1825 } 1826 if (log.isDebugEnabled()) { 1827 log.debug("{}: Signal \"{}\" changed to aspect \"{}\" {} blocks ahead. curSpeedType= {}", 1828 getDisplayName(), _protectSignal.getDisplayName(), speedType, _idxProtectSignal-_idxCurrentOrder, curSpeedType); 1829 } 1830 1831 if (curSpeedType.equals(speedType)) { 1832 return; 1833 } 1834 if (_idxProtectSignal > _idxCurrentOrder) { 1835 if (_speedUtil.secondGreaterThanFirst(speedType, curSpeedType)) { 1836 // change to slower speed. Check if speed change should occur now 1837 float availDist = getAvailableDistance(_idxProtectSignal); 1838 float changeDist = getChangeSpeedDistance(_idxProtectSignal, speedType); 1839 if (changeDist > availDist) { 1840 // Not enough room in blocks ahead. start ramp in current block 1841 availDist += getAvailableDistanceAt(_idxCurrentOrder); 1842 if (speedType.equals(Warrant.Stop)) { 1843 _waitForSignal = true; 1844 } 1845 int cmdStartIdx = _engineer.getCurrentCommandIndex(); // blkSpeedInfo.getFirstIndex(); 1846 if (!doDelayRamp(availDist, changeDist, _idxProtectSignal, speedType, cmdStartIdx)) { 1847 log.info("No room for train {} to ramp to \"{}\" from \"{}\" for signal \"{}\"!. availDist={}, changeDist={} on warrant {}", 1848 getTrainName(), speedType, curSpeedType, _protectSignal.getDisplayName(), 1849 availDist, changeDist, getDisplayName()); 1850 } // otherwise will do ramp when entering a block ahead 1851 } 1852 return; 1853 } 1854 } 1855 if (!speedType.equals(Warrant.Stop)) { // a moving aspect clears a signal wait 1856 if (_waitForSignal) { 1857 // signal protecting next block just released its hold 1858 _curSignalAspect = speedType; 1859 _waitForSignal = false; 1860 if (_trace || log.isDebugEnabled()) { 1861 log.info(Bundle.getMessage("SignalCleared", _protectSignal.getDisplayName(), speedType, _trainName)); 1862 } 1863 ThreadingUtil.runOnGUIDelayed(() -> { 1864 restoreRunning(speedType); 1865 }, 2000); 1866 } 1867 } 1868 } 1869 1870 1871 /* 1872 * return distance from the exit of the current block "_idxCurrentOrder" 1873 * to the entrance of the "idxChange" block. 1874 */ 1875 private float getAvailableDistance(int idxChange) { 1876 float availDist = 0; 1877 int idxBlockOrder = _idxCurrentOrder + 1; 1878 if (idxBlockOrder < _orders.size() - 1) { 1879 while (idxBlockOrder < idxChange) { 1880 availDist += getAvailableDistanceAt(idxBlockOrder++); // distance to next block 1881 } 1882 } 1883 return availDist; 1884 } 1885 1886 /* 1887 * Get distance needed to ramp so the speed into the next block satisfies the speedType 1888 * @param idxBlockOrder blockOrder index of entrance block 1889 */ 1890 private float getChangeSpeedDistance(int idxBlockOrder, String speedType) { 1891 float speedSetting = _engineer.getSpeedSetting(); // current speed 1892 // Estimate speed at start of ramp 1893 float enterSpeed; // speed at start of ramp 1894 if (speedSetting > 0.1f && (_idxCurrentOrder == idxBlockOrder - 1)) { 1895 // if in the block immediately before the entrance block, use current speed 1896 enterSpeed = speedSetting; 1897 } else { // else use entrance speed of previous block 1898 String currentSpeedType = _engineer.getSpeedType(false); // current speed type 1899 float scriptSpeed = _speedUtil.getBlockSpeedInfo(idxBlockOrder - 1).getEntranceSpeed(); 1900 enterSpeed = _speedUtil.modifySpeed(scriptSpeed, currentSpeedType); 1901 } 1902 float scriptSpeed = _speedUtil.getBlockSpeedInfo(idxBlockOrder).getEntranceSpeed(); 1903 float endSpeed = _speedUtil.modifySpeed(scriptSpeed, speedType); 1904 // compare distance needed for script throttle at entrance to entrance speed, 1905 // to the distance needed for current throttle to entrance speed. 1906 float enterLen = _speedUtil.getRampLengthForEntry(enterSpeed, endSpeed); 1907 // add buffers for signal and safety clearance 1908 float bufDist = getEntranceBufferDist(idxBlockOrder); 1909// log.debug("{}: getChangeSpeedDistance curSpeed= {} enterSpeed= {} endSpeed= {}", getDisplayName(), speedSetting, enterSpeed, endSpeed); 1910 return enterLen + bufDist; 1911 } 1912 1913 private void doStoppingBlockClear() { 1914 if (_stoppingBlock == null) { 1915 return; 1916 } 1917 _stoppingBlock.removePropertyChangeListener(this); 1918 _stoppingBlock = null; 1919 _idxStoppingBlock = -1; 1920 } 1921 1922 /** 1923 * Called when a rogue or warranted train has left a block. 1924 * Also called from propertyChange() to allow warrant to acquire a throttle 1925 * and launch an engineer. Also called by retry control command to help user 1926 * work out of an error condition. 1927 */ 1928 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 1929 synchronized private void clearStoppingBlock() { 1930 if (_stoppingBlock == null) { 1931 return; 1932 } 1933 String name = _stoppingBlock.getDisplayName(); 1934 doStoppingBlockClear(); 1935 1936 if (_delayStart) { 1937 return; // don't start. Let user resume start 1938 } 1939 if (_trace || log.isDebugEnabled()) { 1940 String reason; 1941 if (_waitForBlock) { 1942 reason = Bundle.getMessage("Occupancy"); 1943 } else { 1944 reason = Bundle.getMessage("Warrant"); 1945 } 1946 log.info(Bundle.getMessage("StopBlockCleared", 1947 getTrainName(), getDisplayName(), reason, name)); 1948 } 1949 cancelDelayRamp(); 1950 int time = 1000; 1951 if (_waitForBlock) { 1952 _waitForBlock = false; 1953 time = 4000; 1954 } 1955 if (_waitForWarrant) { 1956 _waitForWarrant = false; 1957 time = 3000; 1958 } 1959 String speedType; 1960 if (_curSignalAspect != null) { 1961 speedType = _curSignalAspect; 1962 } else { 1963 speedType = _engineer.getSpeedType(false); // current speed type 1964 } 1965 ThreadingUtil.runOnGUIDelayed(() -> { 1966 restoreRunning(speedType); 1967 }, time); 1968 } 1969 1970 private String okToRun() { 1971 boolean cannot = false; 1972 StringBuilder sb = new StringBuilder(); 1973 if (_waitForSignal) { 1974 sb.append(Bundle.getMessage("Signal")); 1975 cannot = true; 1976 } 1977 if (_waitForWarrant) { 1978 if (cannot) { 1979 sb.append(", "); 1980 } else { 1981 cannot = true; 1982 } 1983 Warrant w = getBlockingWarrant(); 1984 if (w != null) { 1985 sb.append(Bundle.getMessage("WarrantWait", w.getDisplayName())); 1986 } else { 1987 sb.append(Bundle.getMessage("WarrantWait", "Unknown")); 1988 } 1989 } 1990 if (_waitForBlock) { 1991 if (cannot) { 1992 sb.append(", "); 1993 } else { 1994 cannot = true; 1995 } 1996 sb.append(Bundle.getMessage("Occupancy")); 1997 } 1998 1999 if (_engineer != null) { 2000 int runState = _engineer.getRunState(); 2001 if (runState == HALT || runState == RAMP_HALT) { 2002 if (cannot) { 2003 sb.append(", "); 2004 } else { 2005 cannot = true; 2006 } 2007 sb.append(Bundle.getMessage("userHalt")); 2008 } 2009 } 2010 if (cannot) { 2011 return sb.toString(); 2012 } 2013 return null; 2014 } 2015 2016 /** 2017 * A layout condition that has restricted or stopped a train has been cleared. 2018 * i.e. Signal aspect, rogue occupied block, contesting warrant or user halt. 2019 * This may or may not be all the conditions restricting speed. 2020 * @return true if automatic restart is done 2021 */ 2022 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 2023 private boolean restoreRunning(String speedType) { 2024 _message = okToRun(); 2025 boolean returnOK; 2026 if (_message == null) { 2027 BlockOrder bo = getBlockOrderAt(_idxCurrentOrder); 2028 TrainOrder to = bo.allocatePaths(this, true); 2029 OBlock block = bo.getBlock(); 2030 if (log.isDebugEnabled()) { 2031 log.debug("{}: restoreRunning {}", getDisplayName(), to.toString()); 2032 } 2033 switch (to._cause) { // to._cause - precedence of checks is WARRANT, OCCUPY, SIGNAL 2034 case NONE: 2035 returnOK = doRestoreRunning(block, speedType); 2036 break; 2037 case WARRANT: 2038 _waitForWarrant = true; 2039 _message = to._message; 2040 setStoppingBlock(to._idxContrlBlock); 2041 returnOK = false; 2042 break; 2043 case OCCUPY: 2044 if (_overrun || _lost) { 2045 _message = setPathAt(_idxCurrentOrder); 2046 if (_message == null) { 2047 returnOK = doRestoreRunning(block, speedType); 2048 } else { 2049 returnOK = false; 2050 } 2051 if (_lost && returnOK) { 2052 _lost = false; 2053 } 2054 break; 2055 } 2056 returnOK = false; 2057 _waitForBlock = true; 2058 _message = to._message; 2059 setStoppingBlock(to._idxContrlBlock); 2060 break; 2061 case SIGNAL: 2062 if (to._idxContrlBlock == _idxCurrentOrder) { 2063 returnOK = doRestoreRunning(block, speedType); 2064 } else { 2065 returnOK = false; 2066 } 2067 if (returnOK && Stop.equals(to._speedType)) { 2068 _waitForSignal = true; 2069 _message = to._message; 2070 setProtectingSignal(to._idxContrlBlock); 2071 returnOK = false; 2072 break; 2073 } 2074 speedType = to._speedType; 2075 returnOK = doRestoreRunning(block, speedType); 2076 break; 2077 default: 2078 log.error("restoreRunning TrainOrder {}", to.toString()); 2079 _message = to._message; 2080 returnOK = false; 2081 } 2082 } else { 2083 returnOK = false; 2084 } 2085 if (!returnOK) { 2086 String blockName = getBlockAt(_idxCurrentOrder).getDisplayName(); 2087 if (_trace || log.isDebugEnabled()) { 2088 log.info(Bundle.getMessage("trainWaiting", getTrainName(), _message, blockName)); 2089 } 2090 fireRunStatus(PROPERTY_CANNOT_RUN, blockName, _message); 2091 } 2092 return returnOK; 2093 } 2094 2095 private boolean doRestoreRunning(OBlock block, String speedType) { 2096 _overrun = false; 2097 _curSignalAspect = null; 2098 setPathAt(_idxCurrentOrder); // show ownership and train Id 2099 2100 // It is highly likely an event to restart a speed increase occurs when the train 2101 // position is in the middle or end of the block. Since 'lookAheadforSpeedChange' 2102 // assumes the train is at the start of a block, don't ramp up if the 2103 // train may not enter the next block. No room for both ramp up and ramp down 2104 BlockOrder bo = getBlockOrderAt(_idxCurrentOrder+1); 2105 if (bo != null) { 2106 TrainOrder to = bo.allocatePaths(this, true); 2107 if (Warrant.Stop.equals(to._speedType)) { 2108 _message = to._message; 2109 switch (to._cause) { 2110 case NONE: 2111 break; 2112 case WARRANT: 2113 _waitForWarrant = true; 2114 setStoppingBlock(to._idxContrlBlock); 2115 break; 2116 case OCCUPY: 2117 _waitForBlock = true; 2118 setStoppingBlock(to._idxContrlBlock); 2119 break; 2120 case SIGNAL: 2121 _waitForSignal = true; 2122 setProtectingSignal(to._idxContrlBlock); 2123 break; 2124 default: 2125 } 2126 return false; 2127 } 2128 } 2129 _engineer.clearWaitForSync(block); 2130 if (log.isDebugEnabled()) { 2131 log.debug("{}: restoreRunning(): rampSpeedTo to \"{}\"", 2132 getDisplayName(), speedType); 2133 } 2134 rampSpeedTo(speedType, -1); 2135 // continue, there may be blocks ahead that need a speed decrease before entering them 2136 if (!_overrun && _idxCurrentOrder < _orders.size() - 1) { 2137 lookAheadforSpeedChange(speedType, speedType); 2138 } // else at last block, forget about speed changes 2139 return true; 2140 } 2141 2142 /** 2143 * Stopping block only used in MODE_RUN _stoppingBlock is an occupied OBlock 2144 * preventing the train from continuing the route OR another warrant 2145 * is preventing this warrant from allocating the block to continue. 2146 * <p> 2147 */ 2148 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 2149 private void setStoppingBlock(int idxBlock) { 2150 OBlock block = getBlockAt(idxBlock); 2151 if (block == null) { 2152 return; 2153 } 2154 // _idxCurrentOrder == 0 may be a delayed start waiting for loco. 2155 // Otherwise don't set _stoppingBlock for a block occupied by train 2156 if (idxBlock < 0 || (_idxCurrentOrder == idxBlock && !_lost)) { 2157 return; 2158 } 2159 OBlock prevBlk = _stoppingBlock; 2160 if (_stoppingBlock != null) { 2161 if (_stoppingBlock.equals(block)) { 2162 return; 2163 } 2164 2165 int idxStop = getIndexOfBlockAfter(_stoppingBlock, _idxCurrentOrder); 2166 if ((idxBlock < idxStop) || idxStop < 0) { 2167 prevBlk.removePropertyChangeListener(this); 2168 } else { 2169 if (idxStop < _idxCurrentOrder) { 2170 log.error("{}: _stoppingBlock \"{}\" index {} < _idxCurrentOrder {}", 2171 getDisplayName(), _stoppingBlock.getDisplayName(), idxStop, _idxCurrentOrder); 2172 } 2173 return; 2174 } 2175 } 2176 _stoppingBlock = block; 2177 _idxStoppingBlock = idxBlock; 2178 _stoppingBlock.addPropertyChangeListener(this); 2179 if ((_trace || log.isDebugEnabled()) && (_waitForBlock || _waitForWarrant)) { 2180 String reason; 2181 String cause; 2182 if (_waitForWarrant) { 2183 reason = Bundle.getMessage("Warrant"); 2184 Warrant w = block.getWarrant(); 2185 if (w != null) { 2186 cause = w.getDisplayName(); 2187 } else { 2188 cause = Bundle.getMessage("Unknown"); 2189 } 2190 } else if (_waitForBlock) { 2191 reason = Bundle.getMessage("Occupancy"); 2192 cause = (String)block.getValue(); 2193 if (cause == null) { 2194 cause = Bundle.getMessage("unknownTrain"); 2195 } 2196 } else if (_lost) { 2197 reason = Bundle.getMessage("Lost"); 2198 cause = Bundle.getMessage("Occupancy"); 2199 } else { 2200 reason = Bundle.getMessage("Start"); 2201 cause = ""; 2202 } 2203 log.info(Bundle.getMessage("StopBlockSet", _stoppingBlock.getDisplayName(), getTrainName(), reason, cause)); 2204 } 2205 } 2206 2207 /** 2208 * set signal listening for aspect change for block at index. 2209 * return true if signal is set. 2210 */ 2211 private boolean setProtectingSignal(int idx) { 2212 if (_idxProtectSignal == idx) { 2213 return true; 2214 } 2215 BlockOrder blkOrder = getBlockOrderAt(idx); 2216 NamedBean signal = blkOrder.getSignal(); 2217 2218 if (_protectSignal != null && _protectSignal.equals(signal)) { 2219 // Must be the route coming back to the same block. Same signal, move index only. 2220 if (_idxProtectSignal < idx && idx >= 0) { 2221 _idxProtectSignal = idx; 2222 } 2223 return true; 2224 } 2225 2226 if (_protectSignal != null) { 2227 if (idx > _idxProtectSignal && _idxProtectSignal > _idxCurrentOrder) { 2228 return true; 2229 } 2230 } 2231 2232 return changeSignalListener(signal, idx); 2233 } 2234 2235 /** 2236 * if current listening signal is not at signalIndex, remove listener and 2237 * set new listening signal 2238 */ 2239 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 2240 private boolean changeSignalListener(NamedBean signal, int signalIndex) { 2241 if (signalIndex == _idxProtectSignal) { 2242 return true; 2243 } 2244// StringBuilder sb = new StringBuilder(getDisplayName()); 2245 if (_protectSignal != null) { 2246 _protectSignal.removePropertyChangeListener(this); 2247/* if (log.isDebugEnabled()) { 2248 sb.append("Removes \""); 2249 sb.append(_protectSignal.getDisplayName()); 2250 sb.append("\" at \""); 2251 sb.append(getBlockAt(_idxProtectSignal).getDisplayName()); 2252 sb.append("\""); 2253 }*/ 2254 _protectSignal = null; 2255 _idxProtectSignal = -1; 2256 } 2257 boolean ret = false; 2258 if (signal != null) { 2259 _protectSignal = signal; 2260 _idxProtectSignal = signalIndex; 2261 _protectSignal.addPropertyChangeListener(this); 2262 if (_trace || log.isDebugEnabled()) { 2263 log.info(Bundle.getMessage("ProtectSignalSet", getTrainName(), 2264 _protectSignal.getDisplayName(), getBlockAt(_idxProtectSignal).getDisplayName())); 2265 } 2266 ret = true; 2267 } 2268 return ret; 2269 } 2270 2271 /** 2272 * Check if this is the next block of the train moving under the warrant 2273 * Learn mode assumes route is set and clear. Run mode update conditions. 2274 * <p> 2275 * Must be called on Layout thread. 2276 * 2277 * @param block Block in the route is going active. 2278 */ 2279 @InvokeOnLayoutThread 2280 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 2281 protected void goingActive(OBlock block) { 2282 if (log.isDebugEnabled()) { 2283 if (!ThreadingUtil.isLayoutThread()) { 2284 log.error("{} invoked on wrong thread", getDisplayName(), new Exception("traceback")); 2285 stopWarrant(true, true); 2286 return; 2287 } 2288 } 2289 2290 if (_runMode == MODE_NONE) { 2291 return; 2292 } 2293 int activeIdx = getIndexOfBlockAfter(block, _idxCurrentOrder); 2294 if (log.isDebugEnabled()) { 2295 log.debug("{}: **Block \"{}\" goingActive. activeIdx= {}, _idxCurrentOrder= {}.", 2296 getDisplayName(), block.getDisplayName(), activeIdx, _idxCurrentOrder); 2297 } 2298 Warrant w = block.getWarrant(); 2299 if (w == null || !this.equals(w)) { 2300 if (log.isDebugEnabled()) { 2301 log.debug("{}: **Block \"{}\" owned by {}!", 2302 getDisplayName(), block.getDisplayName(), (w==null?"NO One":w.getDisplayName())); 2303 } 2304 return; 2305 } 2306 if (_lost && !getBlockAt(_idxCurrentOrder).isOccupied()) { 2307 _idxCurrentOrder = activeIdx; 2308 log.info("Train \"{}\" found at block \"{}\" of warrant {}.", 2309 getTrainName(), block.getDisplayName(), getDisplayName()); 2310 _lost = false; 2311 rampSpeedTo(_engineer.getSpeedType(false), - 1); // current speed type 2312 setMovement(); 2313 return; 2314 } 2315 if (activeIdx <= 0) { 2316 // if _idxCurrentOrder == 0, (i.e. starting block) case 0 is handled as the _stoppingBlock 2317 return; 2318 } 2319 if (activeIdx == _idxCurrentOrder) { 2320 // unusual occurrence. dirty track? sensor glitch? 2321 if (_trace || log.isDebugEnabled()) { 2322 log.info(Bundle.getMessage("RegainDetection", getTrainName(), block.getDisplayName())); 2323 } 2324 } else if (activeIdx == _idxCurrentOrder + 1) { 2325 if (_delayStart) { 2326 log.warn("{}: Rogue entered Block \"{}\" ahead of {}.", 2327 getDisplayName(), block.getDisplayName(), getTrainName()); 2328 _message = Bundle.getMessage("BlockRougeOccupied", block.getDisplayName()); 2329 return; 2330 } 2331 // Since we are moving at speed we assume it is our train that entered the block 2332 // continue on. 2333 _idxCurrentOrder = activeIdx; 2334 } else if (activeIdx > _idxCurrentOrder + 1) { 2335 // if previous blocks are dark, this could be for our train 2336 // check from current (last known) block to this just activated block 2337 for (int idx = _idxCurrentOrder + 1; idx < activeIdx; idx++) { 2338 OBlock preBlock = getBlockAt(idx); 2339 if (!preBlock.isDark()) { 2340 // not dark, therefore not our train 2341 if (log.isDebugEnabled()) { 2342 OBlock curBlock = getBlockAt(_idxCurrentOrder); 2343 log.debug("Rogue train entered block \"{}\" ahead of train {} currently in block \"{}\"!", 2344 block.getDisplayName(), _trainName, curBlock.getDisplayName()); 2345 } 2346 return; 2347 } 2348 // we assume this is our train entering block 2349 _idxCurrentOrder = activeIdx; 2350 } 2351 // previous blocks were checked as UNDETECTED above 2352 // Indicate the previous dark block was entered 2353 OBlock prevBlock = getBlockAt(activeIdx - 1); 2354 prevBlock._entryTime = System.currentTimeMillis() - 5000; // arbitrary. Just say 5 seconds 2355 prevBlock.setValue(_trainName); 2356 prevBlock.setState(prevBlock.getState() | OBlock.RUNNING); 2357 if (log.isDebugEnabled()) { 2358 log.debug("{}: Train moving from UNDETECTED block \"{}\" now entering block\"{}\"", 2359 getDisplayName(), prevBlock.getDisplayName(), block.getDisplayName()); 2360 } 2361 } else if (_idxCurrentOrder > activeIdx) { 2362 // unusual occurrence. dirty track, sensor glitch, too fast for goingInactive() for complete? 2363 log.info("Tail of Train {} regained detection behind Block= {} at block= {}", 2364 getTrainName(), block.getDisplayName(), getBlockAt(activeIdx).getDisplayName()); 2365 return; 2366 } 2367 // Since we are moving we assume it is our train entering the block 2368 // continue on. 2369 setHeadOfTrain(block); 2370 if (_engineer != null) { 2371 _engineer.clearWaitForSync(block); // Sync commands if train is faster than ET 2372 } 2373 if (_trace) { 2374 log.info(Bundle.getMessage("TrackerBlockEnter", getTrainName(), block.getDisplayName())); 2375 } 2376 fireRunStatus("blockChange", getBlockAt(activeIdx - 1), block); 2377 if (_runMode == MODE_LEARN) { 2378 return; 2379 } 2380 // _idxCurrentOrder has been incremented. Warranted train has entered this block. 2381 // Do signals, speed etc. 2382 if (_idxCurrentOrder < _orders.size() - 1) { 2383 if (_engineer != null) { 2384 BlockOrder bo = _orders.get(_idxCurrentOrder + 1); 2385 if (bo.getBlock().isDark()) { 2386 // can't detect next block, use ET 2387 _engineer.setRunOnET(true); 2388 } else if (!_tempRunBlind) { 2389 _engineer.setRunOnET(false); 2390 } 2391 } 2392 } 2393 if (log.isTraceEnabled()) { 2394 log.debug("{}: end of goingActive. leaving \"{}\" entered \"{}\"", 2395 getDisplayName(), getBlockAt(activeIdx - 1).getDisplayName(), block.getDisplayName()); 2396 } 2397 setMovement(); 2398 } //end goingActive 2399 2400 private void setHeadOfTrain(OBlock block ) { 2401 block.setValue(_trainName); 2402 block.setState(block.getState() | OBlock.RUNNING); 2403 if (_runMode == MODE_RUN && _idxCurrentOrder > 0 && _idxCurrentOrder < _orders.size()) { 2404 _speedUtil.leavingBlock(_idxCurrentOrder - 1); 2405 } 2406 } 2407 2408 /** 2409 * @param block Block in the route is going Inactive 2410 */ 2411 @InvokeOnLayoutThread 2412 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 2413 protected void goingInactive(OBlock block) { 2414 if (log.isDebugEnabled()) { 2415 if (!ThreadingUtil.isLayoutThread()) { 2416 log.error("{} invoked on wrong thread", getDisplayName(), new Exception("traceback")); 2417 } 2418 } 2419 if (_runMode == MODE_NONE) { 2420 return; 2421 } 2422 2423 int idx = getIndexOfBlockBefore(_idxCurrentOrder, block); // if idx >= 0, it is in this warrant 2424 if (log.isDebugEnabled()) { 2425 log.debug("{}: *Block \"{}\" goingInactive. idx= {}, _idxCurrentOrder= {}.", 2426 getDisplayName(), block.getDisplayName(), idx, _idxCurrentOrder); 2427 } 2428 if (idx > _idxCurrentOrder) { 2429 return; 2430 } 2431 releaseBlock(block, idx); 2432 block.setValue(null); 2433 if (idx == _idxCurrentOrder) { 2434 // Train not visible if current block goes inactive. This is OK if the next block is Dark. 2435 if (_idxCurrentOrder + 1 < _orders.size()) { 2436 OBlock nextBlock = getBlockAt(_idxCurrentOrder + 1); 2437 if (nextBlock.isDark()) { 2438 goingActive(nextBlock); // fake occupancy for dark block 2439 return; 2440 } 2441 if (checkForOverrun(nextBlock)) { 2442 return; 2443 } 2444 } 2445 _lost = true; 2446 if (_engineer != null) { 2447 setSpeedToType(Stop); // set 0 throttle 2448 setStoppingBlock(_idxCurrentOrder); 2449 } 2450 if (_trace) { 2451 log.info(Bundle.getMessage("ChangedRoute", _trainName, block.getDisplayName(), getDisplayName())); 2452 } 2453 fireRunStatus("blockChange", block, null); // train is lost 2454 } 2455 } // end goingInactive 2456 2457 /** 2458 * Deallocates all blocks prior to and including block at index idx 2459 * of _orders, if not needed again. 2460 * Comes from goingInactive, i.e. warrant has a listener on the block. 2461 * @param block warrant is releasing 2462 * @param idx index in BlockOrder list 2463 */ 2464 private void releaseBlock(OBlock block, int idx) { 2465 /* 2466 * Deallocate block if train will not use the block again. Warrant 2467 * could loop back and re-enter blocks previously traversed. That is, 2468 * they will need to re-allocation of blocks ahead. 2469 * Previous Dark blocks also need deallocation and other trains or cars 2470 * dropped may have prevented previous blocks from going inactive. 2471 * Thus we must deallocate backward until we reach inactive detectable blocks 2472 * or blocks we no longer own. 2473 */ 2474 for (int i = idx; i > -1; i--) { 2475 boolean neededLater = false; 2476 OBlock curBlock = getBlockAt(i); 2477 for (int j = i + 1; j < _orders.size(); j++) { 2478 if (curBlock.equals(getBlockAt(j))) { 2479 neededLater = true; 2480 } 2481 } 2482 if (!neededLater) { 2483 if (deAllocateBlock(curBlock)) { 2484 curBlock.setValue(null); 2485 _totalAllocated = false; 2486 } 2487 } else { 2488 if (curBlock.isAllocatedTo(this)) { 2489 // Can't deallocate, but must listen for followers 2490 // who may be occupying the block 2491 if (_idxCurrentOrder != idx + 1) { 2492 curBlock.setValue(null); 2493 } 2494 if (curBlock.equals(_stoppingBlock)){ 2495 doStoppingBlockClear(); 2496 } 2497 } 2498 if (_shareRoute) { // don't deallocate if closer than 2 blocks, otherwise deallocate 2499 int k = Math.min(3, _orders.size()); 2500 while (k > _idxCurrentOrder) { 2501 if (!curBlock.equals(getBlockAt(k))) { 2502 if (deAllocateBlock(curBlock)) { 2503 curBlock.setValue(null); 2504 _totalAllocated = false; 2505 } 2506 } 2507 k--; 2508 } 2509 } 2510 } 2511 } 2512 } 2513 2514 /* 2515 * This block is a possible overrun. If permitted, we may claim ownership. 2516 * BlockOrder index of block is _idxCurrentOrder + 1 2517 * return true, if warrant can claim occupation and ownership 2518 */ 2519 private boolean checkForOverrun(OBlock block) { 2520 if (block.isOccupied() && (System.currentTimeMillis() - block._entryTime < 5000)) { 2521 // Went active within the last 5 seconds. Likely an overrun 2522 _overrun = true; 2523 _message = setPathAt(_idxCurrentOrder + 1); // no TrainOrder checks. allocates and sets path 2524 if (_message == null) { // OK we own the block now. 2525 _idxCurrentOrder++; 2526 // insulate possible non-GUI thread making this call (e.g. Engineer) 2527 ThreadingUtil.runOnGUI(()-> goingActive(block)); 2528 return true ; 2529 } 2530 } 2531 return false; 2532 } 2533 2534 @Override 2535 public void dispose() { 2536 if (_runMode != MODE_NONE) { 2537 stopWarrant(true, true); 2538 } 2539 super.dispose(); 2540 } 2541 2542 @Override 2543 public String getBeanType() { 2544 return Bundle.getMessage("BeanNameWarrant"); 2545 } 2546 2547 private class CommandDelay extends Thread { 2548 2549 String _speedType; 2550// long _startTime = 0; 2551 long _waitTime = 0; 2552 float _waitSpeed; 2553 boolean quit = false; 2554 int _endBlockIdx; 2555 2556 CommandDelay(@Nonnull String speedType, long startWait, float waitSpeed, int endBlockIdx) { 2557 _speedType = speedType; 2558 _waitTime = startWait; 2559 _waitSpeed = waitSpeed; 2560 _endBlockIdx = endBlockIdx; 2561 setName("CommandDelay(" + getTrainName() + "-" + speedType +")"); 2562 } 2563 2564 // check if request for a duplicate CommandDelay can be cancelled 2565 boolean isDuplicate(String speedType, long startWait, int endBlockIdx) { 2566 if (endBlockIdx == _endBlockIdx && speedType.equals(_speedType) ) { // && 2567// (_waitTime - (System.currentTimeMillis() - _startTime)) < startWait) { 2568 return true; // keeps this thread 2569 } 2570 return false; // not a duplicate or does not shorten time wait. this thread will be cancelled 2571 } 2572 2573 @Override 2574 @SuppressFBWarnings(value = "WA_NOT_IN_LOOP", justification = "notify never called on this thread") 2575 public void run() { 2576 synchronized (this) { 2577// _startTime = System.currentTimeMillis(); 2578 boolean ramping = _engineer.isRamping(); 2579 if (ramping) { 2580 long time = 0; 2581 while (time <= _waitTime) { 2582 if (_engineer.getSpeedSetting() >= _waitSpeed) { 2583 break; // stop ramping beyond this speed 2584 } 2585 try { 2586 wait(100); 2587 } catch (InterruptedException ie) { 2588 if (log.isDebugEnabled() && quit) { 2589 log.debug("CommandDelay interrupt. Ramp to {} not done. warrant {}", 2590 _speedType, getDisplayName()); 2591 } 2592 } 2593 time += 50; 2594 } 2595 } else { 2596 try { 2597 wait(_waitTime); 2598 } catch (InterruptedException ie) { 2599 if (log.isDebugEnabled() && quit) { 2600 log.debug("CommandDelay interrupt. Ramp to {} not done. warrant {}", 2601 _speedType, getDisplayName()); 2602 } 2603 } 2604 } 2605 2606 if (!quit && _engineer != null) { 2607 if (_noRamp) { 2608 setSpeedToType(_speedType); 2609 } else { 2610 _engineer.rampSpeedTo(_speedType, _endBlockIdx); 2611 } 2612 } 2613 } 2614 endDelayCommand(); 2615 } 2616 } 2617 2618 synchronized private void cancelDelayRamp() { 2619 if (_delayCommand != null) { 2620 log.debug("{}: cancelDelayRamp() called. _speedType= {}", getDisplayName(), _delayCommand._speedType); 2621 _delayCommand.quit = true; 2622 _delayCommand.interrupt(); 2623 _delayCommand = null; 2624 } 2625 } 2626 2627 synchronized private void endDelayCommand() { 2628 _delayCommand = null; 2629 } 2630 2631 private void rampSpeedTo(String speedType, int idx) { 2632 cancelDelayRamp(); 2633 if (_noRamp) { 2634 _engineer.setSpeedToType(speedType); 2635 _engineer.setWaitforClear(speedType.equals(Stop) || speedType.equals(EStop)); 2636 if (log.isDebugEnabled()) { 2637 log.debug("{}: No Ramp to \"{}\" from block \"{}\"", getDisplayName(), speedType, getCurrentBlockName()); 2638 } 2639 return; 2640 } 2641 if (log.isDebugEnabled()) { 2642 if (idx < 0) { 2643 log.debug("{}: Ramp up to \"{}\" from block \"{}\"", getDisplayName(), speedType, getCurrentBlockName()); 2644 } else { 2645 log.debug("{}: Ramp down to \"{}\" before block \"{}\"", getDisplayName(), speedType, getBlockAt(idx).getDisplayName()); 2646 } 2647 } 2648 if (_engineer != null) { 2649 _engineer.rampSpeedTo(speedType, idx); 2650 } else { 2651 log.error("{}: No Engineer!", getDisplayName()); 2652 } 2653 } 2654 2655 private void setSpeedToType(String speedType) { 2656 cancelDelayRamp(); 2657 _engineer.setSpeedToType(speedType); 2658 } 2659 2660 private void clearWaitFlags(boolean removeListeners) { 2661 if (log.isTraceEnabled()) { 2662 log.trace("{}: Flags cleared {}.", getDisplayName(), removeListeners?"and removed Listeners":"only"); 2663 } 2664 _waitForBlock = false; 2665 _waitForSignal = false; 2666 _waitForWarrant = false; 2667 if (removeListeners) { 2668 if (_protectSignal != null) { 2669 _protectSignal.removePropertyChangeListener(this); 2670 _protectSignal = null; 2671 _idxProtectSignal = -1; 2672 } 2673 if (_stoppingBlock != null) { 2674 _stoppingBlock.removePropertyChangeListener(this); 2675 _stoppingBlock = null; 2676 _idxStoppingBlock = -1; 2677 } 2678 } 2679 } 2680 2681 /* 2682 * Return pathLength of the block. 2683 */ 2684 private float getAvailableDistanceAt(int idxBlockOrder) { 2685 BlockOrder blkOrder = getBlockOrderAt(idxBlockOrder); 2686 float pathLength = blkOrder.getPathLength(); 2687 if (idxBlockOrder == 0 || pathLength <= 20.0f) { 2688 // Position in block is unknown. use calculated distances instead 2689 float blkDist = _speedUtil.getBlockSpeedInfo(idxBlockOrder).getCalcLen(); 2690 if (log.isDebugEnabled()) { 2691 log.debug("{}: getAvailableDistanceAt: block \"{}\" using calculated blkDist= {}, pathLength= {}", 2692 getDisplayName(), blkOrder.getBlock().getDisplayName(), blkDist, pathLength); 2693 } 2694 return blkDist; 2695 } else { 2696 return pathLength; 2697 } 2698 } 2699 2700 private float getEntranceBufferDist(int idxBlockOrder) { 2701 float bufDist = BUFFER_DISTANCE; 2702 if (_waitForSignal) { // signal restricting speed 2703 bufDist+= getBlockOrderAt(idxBlockOrder).getEntranceSpace(); // signal's adjustment 2704 } 2705 return bufDist; 2706 } 2707 2708 /** 2709 * Called to set the correct speed for the train when the scripted speed 2710 * must be modified due to a track condition (signaled speed or rogue 2711 * occupation). Also called to return to the scripted speed after the 2712 * condition is cleared. Assumes the train occupies the block of the current 2713 * block order. 2714 * <p> 2715 * Looks for speed requirements of this block and takes immediate action if 2716 * found. Otherwise looks ahead for future speed change needs. If speed 2717 * restriction changes are required to begin in this block, but the change 2718 * is not immediate, then determine the proper time delay to start the speed 2719 * change. 2720 */ 2721 private void setMovement() { 2722 BlockOrder curBlkOrder = getBlockOrderAt(_idxCurrentOrder); 2723 OBlock curBlock = curBlkOrder.getBlock(); 2724 String currentSpeedType = _engineer.getSpeedType(false); // current speed type 2725 String entrySpeedType = BlockOrder.getPermissibleSpeedAt(curBlkOrder); // expected speed type for this block 2726 if (entrySpeedType == null) { 2727 entrySpeedType = currentSpeedType; 2728 } 2729 curBlkOrder.setPath(this); // restore running 2730 2731 if (log.isDebugEnabled()) { 2732 SpeedState speedState = _engineer.getSpeedState(); 2733 int runState = _engineer.getRunState(); 2734 log.debug("{}: SET MOVEMENT Block \"{}\" runState= {}, speedState= {} for currentSpeedType= {}. entrySpeedType= {}.", 2735 getDisplayName(), curBlock.getDisplayName(), RUN_STATE[runState], speedState.toString(), 2736 currentSpeedType, entrySpeedType); 2737 log.debug("{}: Flags: _waitForBlock={}, _waitForSignal={}, _waitForWarrant={} curThrottle= {}.", 2738 getDisplayName(), _waitForBlock, _waitForSignal, _waitForWarrant, _engineer.getSpeedSetting()); 2739 if (_message != null) { 2740 log.debug("{}: _message ({}) ", getDisplayName(), _message); 2741 } 2742 } 2743 2744 // Check that flags and states agree with expected speed and position 2745 // A signal drop down can appear to be a speed violation, but only when a violation when expected 2746 if (_idxCurrentOrder > 0) { 2747 if (_waitForSignal) { 2748 if (_idxProtectSignal == _idxCurrentOrder) { 2749 makeOverrunMessage(curBlkOrder); 2750 setSpeedToType(Stop); // immediate decrease 2751 return; 2752 } 2753 } 2754 if (_idxStoppingBlock == _idxCurrentOrder) { 2755 if (_waitForBlock || _waitForWarrant) { 2756 makeOverrunMessage(curBlkOrder); 2757 setSpeedToType(Stop); // immediate decrease 2758 return; 2759 } 2760 } 2761 2762 if (_speedUtil.secondGreaterThanFirst(entrySpeedType, currentSpeedType)) { 2763 // signal or block speed entrySpeedType is less than currentSpeedType. 2764 // Speed for this block is violated so set end speed immediately 2765 NamedBean signal = curBlkOrder.getSignal(); 2766 if (signal != null) { 2767 log.info("Train {} moved past required {} speed for signal \"{}\" at block \"{}\" on warrant {}!", 2768 getTrainName(), entrySpeedType, signal.getDisplayName(), curBlock.getDisplayName(), getDisplayName()); 2769 } else { 2770 log.info("Train {} moved past required \"{}\" speed at block \"{}\" on warrant {}!", 2771 getTrainName(), entrySpeedType, curBlock.getDisplayName(), getDisplayName()); 2772 } 2773 fireRunStatus("SignalOverrun", (signal!=null?signal.getDisplayName():curBlock.getDisplayName()), 2774 entrySpeedType); // message of speed violation 2775 setSpeedToType(entrySpeedType); // immediate decrease 2776 currentSpeedType = entrySpeedType; 2777 } 2778 } else { // at origin block and train has arrived,. ready to move 2779 if (Stop.equals(currentSpeedType)) { 2780 currentSpeedType = Normal; 2781 } 2782 } 2783 2784 if (_idxCurrentOrder < _orders.size() - 1) { 2785 lookAheadforSpeedChange(currentSpeedType, entrySpeedType); 2786 } // else at last block, forget about speed changes, return; 2787 } 2788 2789 /* 2790 * Looks for the need to reduce speed ahead. If one is found, mkes an estimate of the 2791 *distance needed to change speeds. Find the available distance available, including 2792 * the full length of the current path. If the ramp to reduce speed should begin in the 2793 * current block, calls methods to calculate the time lapse before the ramp should begin. 2794 * entrySpeedType (expected type) will be either equal to or greater than currentSpeedType 2795 * for all blocks except rhe first. 2796 */ 2797 private void lookAheadforSpeedChange(String currentSpeedType, String entrySpeedType) { 2798 clearWaitFlags(false); 2799 // look ahead for speed type slower than current type, refresh flags 2800 // entrySpeedType is the expected speed to be reached, if no speed change ahead 2801 2802 String speedType = currentSpeedType; // first slower speedType ahead 2803 int idx = _idxCurrentOrder + 1; 2804 int idxSpeedChange = -1; // idxBlockOrder where speed changes 2805 int idxContrlBlock = -1; 2806 int limit; 2807 if (_shareRoute) { 2808 limit = Math.min(_orders.size(), _idxCurrentOrder + 3); 2809 } else { 2810 limit = _orders.size(); 2811 } 2812 boolean allocate = true; 2813 int numAllocated = 0; 2814 do { 2815 TrainOrder to = getBlockOrderAt(idx).allocatePaths(this, allocate); 2816 if (log.isDebugEnabled()) { 2817 log.debug("{}: lookAheadforSpeedChange {}", getDisplayName(), to.toString()); 2818 } 2819 switch (to._cause) { 2820 case NONE: 2821 break; 2822 case WARRANT: 2823 _waitForWarrant = true; 2824 _message = to._message; 2825 idxContrlBlock = to._idxContrlBlock; 2826 idxSpeedChange = to._idxEnterBlock; 2827 speedType = Stop; 2828 break; 2829 case OCCUPY: 2830 _waitForBlock = true; 2831 _message = to._message; 2832 idxContrlBlock = to._idxContrlBlock; 2833 idxSpeedChange = to._idxEnterBlock; 2834 speedType = Stop; 2835 break; 2836 case SIGNAL: 2837 speedType = to._speedType; 2838 if (Stop.equals(speedType)) { 2839 _waitForSignal = true; 2840 } 2841 idxContrlBlock = to._idxContrlBlock; 2842 idxSpeedChange = to._idxEnterBlock; 2843 _message = to._message; 2844 break; 2845 default: 2846 log.error("{}: lookAheadforSpeedChange at block \"{}\" setPath returns: {}", 2847 getDisplayName(), getBlockAt(_idxCurrentOrder).getDisplayName(), to.toString()); 2848 _message = to._message; 2849 setSpeedToType(Stop); 2850 return; 2851 } 2852 numAllocated++; 2853 if (Stop.equals(speedType)) { 2854 break; 2855 } 2856 if (_shareRoute && numAllocated > 1 ) { 2857 allocate = false; 2858 } 2859 idx++; 2860 2861 } while ((idxSpeedChange < 0) && (idx < limit) && 2862 !_speedUtil.secondGreaterThanFirst(speedType, currentSpeedType)); 2863 2864 if (!Stop.equals(speedType)) { 2865 while ((idx < limit)) { // allocate and set paths beyond speed change 2866 TrainOrder to = getBlockOrderAt(idx).allocatePaths(this, false); 2867 if (Stop.equals(to._speedType)) { 2868 break; 2869 } 2870 idx++; 2871 } 2872 } 2873 if (idxSpeedChange < 0) { 2874 idxSpeedChange = _orders.size() - 1; 2875 } 2876 2877 float availDist = getAvailableDistance(idxSpeedChange); // distance ahead (excluding current block 2878 float changeDist = getChangeSpeedDistance(idxSpeedChange, speedType); // distance needed to change speed for speedType 2879 2880 if (_speedUtil.secondGreaterThanFirst(currentSpeedType, speedType)) { 2881 // speedType is greater than currentSpeedType. i.e. increase speed. 2882 rampSpeedTo(speedType, -1); 2883 return; 2884 } 2885 if (!currentSpeedType.equals(entrySpeedType)) { 2886 // entrySpeedType is greater than currentSpeedType. i.e. increase speed. 2887 rampSpeedTo(entrySpeedType, -1); 2888 // continue to interrupt ramp up with ramp down 2889 } 2890 2891 // set next signal after current block for aspect speed change 2892 for (int i = _idxCurrentOrder + 1; i < _orders.size(); i++) { 2893 if (setProtectingSignal(i)) { 2894 break; 2895 } 2896 } 2897 2898 OBlock block = getBlockAt(idxSpeedChange); 2899 if (log.isDebugEnabled()) { 2900 log.debug("{}: Speed \"{}\" at block \"{}\" until speed \"{}\" at block \"{}\", availDist={}, changeDist={}", 2901 getDisplayName(), currentSpeedType, getBlockAt(_idxCurrentOrder).getDisplayName(), speedType, 2902 block.getDisplayName(), availDist, changeDist); 2903 } 2904 2905 if (changeDist <= availDist) { 2906 cancelDelayRamp(); // interrupts down ramping 2907 clearWaitFlags(false); 2908 return; 2909 } 2910 2911 // Now set stopping condition of flags, if any. Not, if current block is also ahead. 2912 if (_waitForBlock) { 2913 if (!getBlockAt(_idxCurrentOrder).equals(block)) { 2914 setStoppingBlock(idxContrlBlock); 2915 } 2916 } else if (_waitForWarrant) { 2917 // if block is allocated and unoccupied, but cannot set path exit. 2918 if (_stoppingBlock == null) { 2919 setStoppingBlock(idxContrlBlock); 2920 } 2921 } 2922 2923 // Begin a ramp for speed change in this block. If due to a signal, watch that one 2924 if(_waitForSignal) { 2925 // Watch this signal. Should be the previous set signal above. 2926 // If not, then user has not configured signal system to allow room for speed changes. 2927 setProtectingSignal(idxContrlBlock); 2928 } 2929 2930 // either ramp in progress or no changes needed. Stopping conditions set, so move on. 2931 if (!_speedUtil.secondGreaterThanFirst(speedType, currentSpeedType)) { 2932 return; 2933 } 2934 2935 availDist += getAvailableDistanceAt(_idxCurrentOrder); // Add available length in this block 2936 2937 int cmdStartIdx = _speedUtil.getBlockSpeedInfo(_idxCurrentOrder).getFirstIndex(); 2938 if (!doDelayRamp(availDist, changeDist, idxSpeedChange, speedType, cmdStartIdx)) { 2939 log.warn("No room for train {} to ramp to \"{}\" from \"{}\" in block \"{}\"!. availDist={}, changeDist={} on warrant {}", 2940 getTrainName(), speedType, currentSpeedType, getBlockAt(_idxCurrentOrder).getDisplayName(), 2941 availDist, changeDist, getDisplayName()); 2942 } 2943 } 2944 2945 /* 2946 * if there is sufficient room calculate a wait time, otherwise ramp immediately. 2947 */ 2948 synchronized private boolean doDelayRamp(float availDist, float changeDist, int idxSpeedChange, String speedType, int cmdStartIdx) { 2949 String pendingSpeedType = _engineer.getSpeedType(true); // current or pending speed type 2950 if (pendingSpeedType.equals(speedType)) { 2951 return true; 2952 } 2953 if (availDist < 10) { 2954 setSpeedToType(speedType); 2955 return false; 2956 } else { 2957 SpeedState speedState = _engineer.getSpeedState(); 2958 switch (speedState) { 2959 case RAMPING_UP: 2960 makeRampWait(availDist, idxSpeedChange, speedType); 2961 break; 2962 case RAMPING_DOWN: 2963 log.error("Already ramping to \"{}\" making ramp for \"{}\".", _engineer.getSpeedType(true), speedType); 2964 //$FALL-THROUGH$ 2965 case STEADY_SPEED: 2966 //$FALL-THROUGH$ 2967 default: 2968 makeScriptWait(availDist, changeDist, idxSpeedChange, speedType, cmdStartIdx); 2969 } 2970 } 2971 return true; 2972 } 2973 2974 private void makeRampWait(float availDist, int idxSpeedChange, @Nonnull String speedType) { 2975 BlockSpeedInfo info = _speedUtil.getBlockSpeedInfo(idxSpeedChange - 1); 2976 float speedSetting = info.getExitSpeed(); 2977 float endSpeed = _speedUtil.modifySpeed(speedSetting, speedType); 2978 2979 speedSetting = _engineer.getSpeedSetting(); // current speed 2980 float prevSetting = speedSetting; 2981 String currentSpeedType = _engineer.getSpeedType(false); // current speed type 2982 2983 float changeDist = 0; 2984 if (log.isDebugEnabled()) { 2985 log.debug("{}: makeRampWait for speed change \"{}\" to \"{}\". Throttle from={}, to={}, availDist={}", 2986 getDisplayName(), currentSpeedType, speedType, speedSetting, endSpeed, availDist); 2987 // command index numbers biased by 1 2988 } 2989 float bufDist = getEntranceBufferDist(idxSpeedChange); 2990 float accumTime = 0; // accumulated time of commands up to ramp start 2991 float accumDist = 0; 2992 RampData ramp = _speedUtil.getRampForSpeedChange(speedSetting, 1.0f); 2993 int time = ramp.getRampTimeIncrement(); 2994 ListIterator<Float> iter = ramp.speedIterator(true); 2995 2996 while (iter.hasNext()) { 2997 changeDist = _speedUtil.getRampLengthForEntry(speedSetting, endSpeed) + bufDist; 2998 accumDist += _speedUtil.getDistanceOfSpeedChange(prevSetting, speedSetting, time); 2999 accumTime += time; 3000 prevSetting = speedSetting; 3001 speedSetting = iter.next(); 3002 3003 if (changeDist + accumDist >= availDist) { 3004 float curTrackSpeed = _speedUtil.getTrackSpeed(speedSetting); 3005 float remDist = changeDist + accumDist - availDist; 3006 if (curTrackSpeed > 0) { 3007 accumTime -= remDist / curTrackSpeed; 3008 } else { 3009 log.warn("{}: Cannot compute wait time for \"{}\" ramp to block \"{}\". trackSpeed= {}", getDisplayName(), 3010 speedType, getBlockAt(idxSpeedChange).getDisplayName(), curTrackSpeed); 3011 } 3012 break; 3013 } 3014 } 3015 if (changeDist < accumDist) { 3016 float curTrackSpeed = _speedUtil.getTrackSpeed(speedSetting); 3017 if (curTrackSpeed > 0) { 3018 accumTime += (availDist - changeDist) / curTrackSpeed; 3019 } else { 3020 log.warn("{}: Cannot compute wait time for \"{}\" ramp to block \"{}\". trackSpeed= {}", getDisplayName(), 3021 speedType, getBlockAt(idxSpeedChange).getDisplayName(), curTrackSpeed); 3022 } 3023 } 3024 3025 int waitTime = Math.round(accumTime); 3026 3027 if (log.isDebugEnabled()) { 3028 log.debug("{}: RAMP: Wait {}ms and travel {}mm until {}mm before entering block \"{}\" at throttle= {}. availDist={}mm", 3029 getDisplayName(), waitTime, Math.round(accumDist), Math.round(changeDist), 3030 getBlockAt(idxSpeedChange).getDisplayName(), speedSetting, Math.round(availDist)); 3031 } 3032 rampSpeedDelay(waitTime, speedType, speedSetting, idxSpeedChange); 3033 } 3034 3035 /** 3036 * Must start the ramp in current block. ( at _idxCurrentOrder) 3037 * find the time when ramp should start in this block, then use thread CommandDelay to start the ramp. 3038 * Train must travel a deltaDist for a deltaTime to the start of the ramp. 3039 * It travels at throttle settings of scriptSpeed(s) modified by currentSpeedType. 3040 * trackSpeed(s) of these modified scriptSettings are computed from SpeedProfile 3041 * waitThrottle is throttleSpeed when ramp is started. This may not be the scriptSpeed now 3042 * Start with waitThrottle (modSetting) being at the entrance to the block. 3043 * modSetting gives the current trackSpeed. 3044 * accumulate the time and distance and determine the distance (changeDist) needed for entrance into 3045 * block (at idxSpeedChange) requiring speed change to speedType 3046 * final ramp should modify waitSpeed to endSpeed and must end at the exit of end block (endBlockIdx) 3047 * 3048 * @param availDist distance available to make the ramp 3049 * @param changeDist distance needed for the rmp 3050 * @param idxSpeedChange block order index of block to complete change before entry 3051 * @param speedType speed aspect of speed change 3052 * @param cmdStartIdx command index of delay 3053 */ 3054 private void makeScriptWait(float availDist, float changeDist, int idxSpeedChange, @Nonnull String speedType, int cmdStartIdx) { 3055 BlockSpeedInfo info = _speedUtil.getBlockSpeedInfo(idxSpeedChange - 1); 3056 int cmdEndIdx = info.getLastIndex(); 3057 float scriptSpeed = info.getExitSpeed(); 3058 float endSpeed = _speedUtil.modifySpeed(scriptSpeed, speedType); 3059 3060 scriptSpeed = _engineer.getScriptSpeed(); // script throttle setting 3061 float speedSetting = _engineer.getSpeedSetting(); // current speed 3062 String currentSpeedType = _engineer.getSpeedType(false); // current speed type 3063 3064 float modSetting = speedSetting; // _speedUtil.modifySpeed(scriptSpeed, currentSpeedType); 3065 float beginTrackSpeed = _speedUtil.getTrackSpeed(modSetting); // mm/sec track speed at modSetting 3066 float curTrackSpeed = beginTrackSpeed; 3067 float prevTrackSpeed = beginTrackSpeed; 3068 if (_idxCurrentOrder == 0 && availDist > BUFFER_DISTANCE) { 3069 changeDist = 0; 3070 } 3071 if (log.isDebugEnabled()) { 3072 log.debug("{}: makespeedChange cmdIdx #{} to #{} at speedType \"{}\" to \"{}\". speedSetting={}, changeDist={}, availDist={}", 3073 getDisplayName(), cmdStartIdx+1, cmdEndIdx+1, currentSpeedType, speedType, speedSetting, changeDist, availDist); 3074 // command index numbers biased by 1 3075 } 3076 float accumTime = 0; // accumulated time of commands up to ramp start 3077 float accumDist = 0; 3078 Command cmd = _commands.get(cmdStartIdx).getCommand(); 3079 3080 if (cmd.equals(Command.NOOP) && beginTrackSpeed > 0) { 3081 accumTime = (availDist - changeDist) / beginTrackSpeed; 3082 } else { 3083 float timeRatio; // time adjustment for current speed type 3084 if (curTrackSpeed > _speedUtil.getRampThrottleIncrement()) { 3085 timeRatio = _speedUtil.getTrackSpeed(scriptSpeed) / curTrackSpeed; 3086 } else { 3087 timeRatio = 1; 3088 } 3089 float bufDist = getEntranceBufferDist(idxSpeedChange); 3090 3091 for (int i = cmdStartIdx; i <= cmdEndIdx; i++) { 3092 ThrottleSetting ts = _commands.get(i); 3093 long time = ts.getTime(); 3094 accumDist += _speedUtil.getDistanceOfSpeedChange(prevTrackSpeed, curTrackSpeed, (int)(time * timeRatio)); 3095 accumTime += time * timeRatio; 3096 cmd = ts.getCommand(); 3097 if (cmd.equals(Command.SPEED)) { 3098 prevTrackSpeed = curTrackSpeed; 3099 CommandValue cmdVal = ts.getValue(); 3100 scriptSpeed = cmdVal.getFloat(); 3101 modSetting = _speedUtil.modifySpeed(scriptSpeed, currentSpeedType); 3102 curTrackSpeed = _speedUtil.getTrackSpeed(modSetting); 3103 changeDist = _speedUtil.getRampLengthForEntry(modSetting, endSpeed) + bufDist; 3104 timeRatio = _speedUtil.getTrackSpeed(scriptSpeed) / curTrackSpeed; 3105 } 3106 3107 if (log.isDebugEnabled()) { 3108 log.debug("{}: cmd#{} accumTime= {} accumDist= {} changeDist= {}, throttle= {}", 3109 getDisplayName(), i+1, accumTime, accumDist, changeDist, modSetting); 3110 } 3111 if (changeDist + accumDist >= availDist) { 3112 float remDist = changeDist + accumDist - availDist; 3113 if (curTrackSpeed > 0) { 3114 accumTime -= remDist / curTrackSpeed; 3115 } else { 3116 log.warn("{}: script unfit cmd#{}. Cannot compute wait time for \"{}\" ramp to block \"{}\". trackSpeed= {}", getDisplayName(), 3117 i+1, speedType, getBlockAt(idxSpeedChange).getDisplayName(), curTrackSpeed); 3118 if (prevTrackSpeed > 0) { 3119 accumTime -= remDist / prevTrackSpeed; 3120 } 3121 } 3122 break; 3123 } 3124 if (cmd.equals(Command.NOOP)) { 3125 // speed change is supposed to start in current block 3126 // start ramp in next block? 3127 float remDist = availDist - changeDist - accumDist; 3128 log.warn("{}: script unfit cmd#{}. Cannot compute wait time for \"{}\" ramp to block \"{}\". remDist= {}", 3129 getDisplayName(), i+1, speedType, getBlockAt(idxSpeedChange).getDisplayName(), remDist); 3130 accumTime -= _speedUtil.getTimeForDistance(modSetting, bufDist); 3131 break; 3132 } 3133 } 3134 } 3135 3136 int waitTime = Math.round(accumTime); 3137 3138 if (log.isDebugEnabled()) { 3139 log.debug("{}: RAMP: Wait {}ms and travel {}mm until {}mm before entering block \"{}\" at throttle= {}. availDist={}mm", 3140 getDisplayName(), waitTime, Math.round(accumDist), Math.round(changeDist), 3141 getBlockAt(idxSpeedChange).getDisplayName(), modSetting, Math.round(availDist)); 3142 } 3143 3144 rampSpeedDelay(waitTime, speedType, modSetting, idxSpeedChange); 3145 } 3146 3147 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 3148 synchronized private void rampSpeedDelay (long waitTime, String speedType, float waitSpeed, int idxSpeedChange) { 3149 int endBlockIdx = idxSpeedChange - 1; 3150 waitTime -= 50; // Subtract a bit 3151 if( waitTime < 0) { 3152 rampSpeedTo(speedType, endBlockIdx); // do it now on this thread. 3153 return; 3154 } 3155 String reason; 3156 if(_waitForSignal) { 3157 reason = Bundle.getMessage("Signal"); 3158 } else if (_waitForWarrant) { 3159 reason = Bundle.getMessage("Warrant"); 3160 } else if (_waitForBlock) { 3161 reason = Bundle.getMessage("Occupancy"); 3162 } else { 3163 reason = Bundle.getMessage("Signal"); 3164 } 3165 3166 if (_trace || log.isDebugEnabled()) { 3167 if (log.isDebugEnabled()) { 3168 log.info("Train \"{}\" needs speed decrease to \"{}\" from \"{}\" for {} before entering block \"{}\"", 3169 getTrainName(), speedType, _engineer.getSpeedType(true), reason, getBlockAt(idxSpeedChange).getDisplayName()); 3170 } 3171 } 3172 if (_delayCommand != null) { 3173 if (_delayCommand.isDuplicate(speedType, waitTime, endBlockIdx)) { 3174 return; 3175 } 3176 cancelDelayRamp(); 3177 } 3178 _delayCommand = new CommandDelay(speedType, waitTime, waitSpeed, endBlockIdx); 3179 _delayCommand.start(); 3180 if (log.isDebugEnabled()) { 3181 log.debug("{}: CommandDelay: will wait {}ms, then Ramp to {} in block {}.", 3182 getDisplayName(), waitTime, speedType, getBlockAt(endBlockIdx).getDisplayName()); 3183 } 3184 String blkName = getBlockAt(endBlockIdx).getDisplayName(); 3185 if (_trace || log.isDebugEnabled()) { 3186 log.info(Bundle.getMessage("RampBegin", getTrainName(), reason, blkName, speedType, waitTime)); 3187 } 3188 } 3189 3190 protected void downRampBegun(int endBlockIdx) { 3191 OBlock block = getBlockAt(endBlockIdx + 1); 3192 if (block != null) { 3193 _rampBlkOccupied = block.isOccupied(); 3194 } else { 3195 _rampBlkOccupied = true; 3196 } 3197 } 3198 3199 protected void downRampDone(boolean stop, boolean halted, String speedType, int endBlockIdx) { 3200 if (_idxCurrentOrder < endBlockIdx) { 3201 return; // overrun not possible. 3202 } 3203 // look for overruns 3204 int nextIdx = endBlockIdx + 1; 3205 if (nextIdx > 0 && nextIdx < _orders.size()) { 3206 BlockOrder bo = getBlockOrderAt(nextIdx); 3207 OBlock block = bo.getBlock(); 3208 if (block.isOccupied() && !_rampBlkOccupied) { 3209 // Occupied now, but not occupied by another train at start of ramp. 3210 if (!checkForOverrun(block) ) { // Not us. check if something should have us wait 3211 Warrant w = block.getWarrant(); 3212 _overrun = true; // endBlock occupied during ramp down. Speed overrun! 3213 if (w != null && !w.equals(this)) { // probably redundant 3214 _waitForWarrant = true; 3215 setStoppingBlock(nextIdx); 3216 } else if (Stop.equals(BlockOrder.getPermissibleSpeedAt(bo))) { // probably redundant 3217 _waitForSignal = true; 3218 setProtectingSignal(nextIdx); 3219 } else { 3220 _waitForBlock = true; 3221 } 3222 } 3223 makeOverrunMessage(bo); 3224 } // case where occupied at start of ramp is indeterminate 3225 } 3226 } 3227 3228 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 3229 private void makeOverrunMessage(BlockOrder curBlkOrder) { 3230 OBlock curBlock = curBlkOrder.getBlock(); 3231 String name = null; 3232 if (_waitForSignal) { 3233 NamedBean signal = curBlkOrder.getSignal(); 3234 if (signal!=null) { 3235 name = signal.getDisplayName(); 3236 } else { 3237 name = curBlock.getDisplayName(); 3238 } 3239 _overrun = true; 3240 String entrySpeedType = BlockOrder.getPermissibleSpeedAt(curBlkOrder); // expected speed type for this block 3241 log.info(Bundle.getMessage("SignalOverrun", getTrainName(), entrySpeedType, name)); 3242 fireRunStatus("SignalOverrun", name, entrySpeedType); // message of speed violation 3243 return; 3244 } 3245 String bundleKey = null; 3246 if (_waitForWarrant) { 3247 bundleKey = PROPERTY_WARRANT_OVERRUN; 3248 Warrant w = curBlock.getWarrant(); 3249 if (w != null) { 3250 name = w.getDisplayName(); 3251 } 3252 } else if (_waitForBlock){ 3253 bundleKey = PROPERTY_OCCUPY_OVERRUN; 3254 name = (String)curBlock.getValue(); 3255 } 3256 if (name == null) { 3257 name = Bundle.getMessage("unknownTrain"); 3258 } 3259 if (bundleKey != null) { 3260 _overrun = true; 3261 log.info(Bundle.getMessage(bundleKey, getTrainName(), curBlock.getDisplayName(), name)); 3262 fireRunStatus(bundleKey, curBlock.getDisplayName(), name); // message of speed violation 3263 } else { 3264 log.error("Train \"{}\" entered stopping block \"{}\" for unknown reason on warrant {}!", 3265 getTrainName(), curBlock.getDisplayName(), getDisplayName()); 3266 } 3267 } 3268 3269 /** 3270 * {@inheritDoc} 3271 * <p> 3272 * This implementation tests that 3273 * {@link jmri.NamedBean#getSystemName()} 3274 * is equal for this and obj. 3275 * To allow a warrant to run with sections, DccLocoAddress is included to test equality 3276 * 3277 * @param obj the reference object with which to compare. 3278 * @return {@code true} if this object is the same as the obj argument; 3279 * {@code false} otherwise. 3280 */ 3281 @Override 3282 public boolean equals(Object obj) { 3283 if (obj == null) return false; // by contract 3284 3285 if (obj instanceof Warrant) { // NamedBeans are not equal to things of other types 3286 Warrant b = (Warrant) obj; 3287 DccLocoAddress addr = this._speedUtil.getDccAddress(); 3288 if (addr == null) { 3289 if (b._speedUtil.getDccAddress() != null) { 3290 return false; 3291 } 3292 return (this.getSystemName().equals(b.getSystemName())); 3293 } 3294 return (this.getSystemName().equals(b.getSystemName()) && addr.equals(b._speedUtil.getDccAddress())); 3295 } 3296 return false; 3297 } 3298 3299 /** 3300 * {@inheritDoc} 3301 * 3302 * @return hash code value is based on the system name and DccLocoAddress. 3303 */ 3304 @Override 3305 public int hashCode() { 3306 return (getSystemName().concat(_speedUtil.getDccAddress().toString())).hashCode(); 3307 } 3308 3309 @Override 3310 public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) { 3311 List<NamedBeanUsageReport> report = new ArrayList<>(); 3312 if (bean != null) { 3313 if (bean.equals(getBlockingWarrant())) { 3314 report.add(new NamedBeanUsageReport("WarrantBlocking")); 3315 } 3316 getBlockOrders().forEach((blockOrder) -> { 3317 if (bean.equals(blockOrder.getBlock())) { 3318 report.add(new NamedBeanUsageReport("WarrantBlock")); 3319 } 3320 if (bean.equals(blockOrder.getSignal())) { 3321 report.add(new NamedBeanUsageReport("WarrantSignal")); 3322 } 3323 }); 3324 } 3325 return report; 3326 } 3327 3328 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Warrant.class); 3329}