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