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); 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); 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) { 927 long time = 0; 928 while (!eng.getState().equals(Thread.State.TERMINATED) && time < 100) { 929 try { 930 eng.stopRun(a, f); // releases throttle 931 eng.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 _engineer.releaseSensorWait(); 1370 clearWaitFlags(false); 1371 _overrun = true; // allows doRestoreRunning to run at an OCCUPY state 1372 return restoreRunning(_engineer.getSpeedType(false)); 1373 } 1374 1375 // returns true if block is owned and occupied by this warrant 1376 private boolean checkBlockForRunning(int idxBlockOrder) { 1377 BlockOrder bo = getBlockOrderAt(idxBlockOrder); 1378 if (bo == null) { 1379 _message = Bundle.getMessage("BlockNotInRoute", "?"); 1380 return false; 1381 } 1382 OBlock block = bo.getBlock(); 1383 if (!block.isOccupied()) { 1384 _message = Bundle.getMessage("blockUnoccupied", block.getDisplayName()); 1385 return false; 1386 } 1387 return true; 1388 } 1389 1390 // User increases speed 1391 private boolean bumpSpeed() { 1392 // OK, will do as it long as you own it, and you are where you think you are. 1393 _engineer.setHalt(false); 1394 _engineer.releaseSensorWait(); 1395 clearWaitFlags(false); 1396 float speedSetting = _engineer.getSpeedSetting(); 1397 if (speedSetting < 0) { // may have done E-Stop 1398 speedSetting = 0.0f; 1399 } 1400 float bumpSpeed = Math.max(WarrantPreferences.getDefault().getSpeedAssistance(), _speedUtil.getRampThrottleIncrement()); 1401 _engineer.setSpeed(speedSetting + bumpSpeed); 1402 return true; 1403 } 1404 1405 private boolean moveToBlock(BlockOrder bo, int idx) { 1406 _idxCurrentOrder = idx; 1407 _message = setPathAt(idx); // no checks. Force path set and allocation 1408 if (_message != null) { 1409 return false; 1410 } 1411 OBlock block = bo.getBlock(); 1412 if (block.equals(_stoppingBlock)) { 1413 clearStoppingBlock(); 1414 _engineer.setHalt(false); 1415 _engineer.releaseSensorWait(); 1416 } 1417 goingActive(block); 1418 return true; 1419 } 1420 1421 protected boolean debugInfo() { 1422 if ( !log.isInfoEnabled() ) { 1423 return true; 1424 } 1425 StringBuilder info = new StringBuilder("\""); info.append(getDisplayName()); 1426 info.append("\" Train \""); info.append(getTrainName()); info.append("\" - Current Block \""); 1427 info.append(getBlockAt(_idxCurrentOrder).getDisplayName()); 1428 info.append("\" BlockOrder idx= "); info.append(_idxCurrentOrder); 1429 info.append("\n\tWait flags: _waitForSignal= "); info.append(_waitForSignal); 1430 info.append(", _waitForBlock= "); info.append(_waitForBlock); 1431 info.append(", _waitForWarrant= "); info.append(_waitForWarrant); 1432 info.append("\n\tStatus flags: _overrun= "); info.append(_overrun); info.append(", _rampBlkOccupied= "); 1433 info.append(_rampBlkOccupied);info.append(", _lost= "); info.append(_lost); 1434 if (_protectSignal != null) { 1435 info.append("\n\tWait for Signal \"");info.append(_protectSignal.getDisplayName());info.append("\" protects block "); 1436 info.append(getBlockAt(_idxProtectSignal).getDisplayName()); info.append("\" from approch block \""); 1437 info.append(getBlockAt(_idxProtectSignal - 1).getDisplayName()); info.append("\". Shows aspect \""); 1438 info.append(getSignalSpeedType(_protectSignal)); info.append("\"."); 1439 } else { 1440 info.append("\n\tNo signals ahead with speed restrictions"); 1441 } 1442 if(_stoppingBlock != null) { 1443 if (_waitForWarrant) { 1444 info.append("\n\tWait for Warrant \""); 1445 Warrant w = getBlockingWarrant(); info.append((w != null?w.getDisplayName():"Unknown")); 1446 info.append("\" owns block \"");info.append(_stoppingBlock.getDisplayName()); info.append("\""); 1447 } else { 1448 Object what = _stoppingBlock.getValue(); 1449 String who; 1450 if (what != null) { 1451 who = what.toString(); 1452 } else { 1453 who = "Unknown Train"; 1454 } 1455 info.append("\n\tWait for \""); info.append(who); info.append("\" occupying Block \""); 1456 info.append(_stoppingBlock.getDisplayName()); info.append("\""); 1457 } 1458 } else { 1459 info.append("\n\tNo occupied blocks ahead"); 1460 } 1461 if (_message != null) { 1462 info.append("\n\tLast message = ");info.append(_message); 1463 } else { 1464 info.append("\n\tNo messages."); 1465 } 1466 1467 if (_engineer != null) { 1468 info.append("\""); info.append("\n\tEngineer Stack trace:"); 1469 for (StackTraceElement elem : _engineer.getStackTrace()) { 1470 info.append("\n\t\t"); 1471 info.append(elem.getClassName()); info.append("."); info.append(elem.getMethodName()); 1472 info.append(", line "); info.append(elem.getLineNumber()); 1473 } 1474 info.append(_engineer.debugInfo()); 1475 } else { 1476 info.append("No engineer."); 1477 } 1478 log.info("\n Warrant: {}", info.toString()); 1479 return true; 1480 } 1481 1482 protected void startupWarrant() { 1483 _idxCurrentOrder = 0; 1484 // set block state to show our train occupies the block 1485 BlockOrder bo = getBlockOrderAt(0); 1486 OBlock b = bo.getBlock(); 1487 b.setValue(_trainName); 1488 b.setState(b.getState() | OBlock.RUNNING); 1489 firePropertyChange(PROPERTY_WARRANT_START, MODE_NONE, _runMode); 1490 } 1491 1492 private void runWarrant(DccThrottle throttle) { 1493 if (_runMode == MODE_LEARN) { 1494 synchronized (this) { 1495 // No Engineer. LearnControlPanel does throttle settings 1496 _student.notifyThrottleFound(throttle); 1497 } 1498 } else { 1499 if (_engineer != null) { // should not happen 1500 killEngineer(_engineer, true, true); 1501 } 1502 _engineer = new Engineer(this, throttle); 1503 1504 _speedUtil.getBlockSpeedTimes(_commands, _orders); // initialize SpeedUtil 1505 if (_tempRunBlind) { 1506 _engineer.setRunOnET(true); 1507 } 1508 if (_delayStart || _haltStart) { 1509 _engineer.setHalt(true); // throttle already at 0 1510 // user must explicitly start train (resume) in a dark block 1511 fireRunStatus(PROPERTY_READY_TO_RUN, -1, 0); // ready to start msg 1512 } 1513 _delayStart = false; 1514 _engineer.start(); 1515 1516 int runState = _engineer.getRunState(); 1517 if (_trace || log.isDebugEnabled()) { 1518 log.info("Train \"{}\" on warrant \"{}\" launched. runState= {}", getTrainName(), getDisplayName(), RUN_STATE[runState]); 1519 } 1520 if (runState != HALT && runState != RAMP_HALT) { 1521 setMovement(); 1522 } 1523 } 1524 } 1525 1526 private String setPathAt(int idx) { 1527 BlockOrder bo = _orders.get(idx); 1528 OBlock b = bo.getBlock(); 1529 String msg = b.allocate(this); 1530 if (msg == null) { 1531 OPath path1 = bo.getPath(); 1532 Portal exit = bo.getExitPortal(); 1533 OBlock block = getBlockAt(idx+1); 1534 if (block != null) { 1535 Warrant w = block.getWarrant(); 1536 if ((w != null && !w.equals(this)) || (w == null && block.isOccupied())) { 1537 msg = bo.pathsConnect(path1, exit, block); 1538 if (msg == null) { 1539 msg = bo.setPath(this); 1540 } 1541 } 1542 } 1543 b.showAllocated(this, bo.getPathName()); 1544 } 1545 return msg; 1546 } 1547 1548 /** 1549 * Allocate as many blocks as possible from the start of the warrant. 1550 * The first block must be allocated and all blocks of the route must 1551 * be in service. Otherwise partial success is OK. 1552 * Installs listeners for the entire route. 1553 * If occupation by another train is detected, a message will be 1554 * posted to the Warrant List Window. Note that warrants sharing their 1555 * clearance only allocate and set paths one block in advance. 1556 * 1557 * @param orders list of block orders 1558 * @param show _message for use ONLY to display a temporary route) continues to 1559 * allocate skipping over blocks occupied or owned by another warrant. 1560 * @return error message, if unable to allocate first block or if any block 1561 * is OUT_OF_SERVICE 1562 */ 1563 public String allocateRoute(boolean show, List<BlockOrder> orders) { 1564 if (_totalAllocated && _runMode != MODE_NONE && _runMode != MODE_ABORT) { 1565 return null; 1566 } 1567 if (orders != null) { 1568 _orders = orders; 1569 } 1570 _allocated = false; 1571 _message = null; 1572 1573 int idxSpeedChange = 0; // idxBlockOrder where speed changes 1574 do { 1575 TrainOrder to = getBlockOrderAt(idxSpeedChange).allocatePaths(this, true); 1576 switch (to._cause) { 1577 case NONE: 1578 break; 1579 case WARRANT: 1580 _waitForWarrant = true; 1581 if (_message == null) { 1582 _message = to._message; 1583 } 1584 if (!show && to._idxContrlBlock == 0) { 1585 return _message; 1586 } 1587 break; 1588 case OCCUPY: 1589 _waitForBlock = true; 1590 if (_message == null) { 1591 _message = to._message; 1592 } 1593 break; 1594 case SIGNAL: 1595 if (Stop.equals(to._speedType)) { 1596 _waitForSignal = true; 1597 if (_message == null) { 1598 _message = to._message; 1599 } 1600 } 1601 break; 1602 default: 1603 log.error("{}: allocateRoute at block \"{}\" setPath returns: {}", 1604 getDisplayName(), getBlockAt(idxSpeedChange).getDisplayName(), to.toString()); 1605 if (_message == null) { 1606 _message = to._message; 1607 } 1608 } 1609 if (!show) { 1610 if (_message != null || (_shareRoute && idxSpeedChange > 1)) { 1611 break; 1612 } 1613 } 1614 idxSpeedChange++; 1615 } while (idxSpeedChange < _orders.size()); 1616 1617 if (log.isDebugEnabled()) { 1618 log.debug("{}: allocateRoute() _shareRoute= {} show= {}. Break at {} of {}. msg= {}", 1619 getDisplayName(), _shareRoute, show, idxSpeedChange, _orders.size(), _message); 1620 } 1621 _allocated = true; // start block allocated 1622 if (_message == null) { 1623 _totalAllocated = true; 1624 if (show && _shareRoute) { 1625 _message = Bundle.getMessage("sharedRoute"); 1626 } 1627 } 1628 if (show) { 1629 return _message; 1630 } 1631 return null; 1632 } 1633 1634 /** 1635 * Deallocates blocks from the current BlockOrder list 1636 */ 1637 public void deAllocate() { 1638 if (_runMode == MODE_NONE || _runMode == MODE_ABORT) { 1639 _allocated = false; 1640 _totalAllocated = false; 1641 _routeSet = false; 1642 for (int i = 0; i < _orders.size(); i++) { 1643 deAllocateBlock(_orders.get(i).getBlock()); 1644 } 1645 } 1646 } 1647 1648 private boolean deAllocateBlock(OBlock block) { 1649 if (block.isAllocatedTo(this)) { 1650 block.deAllocate(this); 1651 if (block.equals(_stoppingBlock)){ 1652 doStoppingBlockClear(); 1653 } 1654 return true; 1655 } 1656 return false; 1657 } 1658 1659 /** 1660 * Convenience routine to use from Python to start a warrant. 1661 * 1662 * @param mode run mode 1663 */ 1664 public void runWarrant(int mode) { 1665 setRunMode(mode, null, null, null, false); 1666 } 1667 1668 /** 1669 * Set the route paths and turnouts for the warrant. Only the first block 1670 * must be allocated and have its path set. Partial success is OK. 1671 * A message of the first subsequent block that fails allocation 1672 * or path setting is written to a field that is 1673 * displayed in the Warrant List window. When running with block 1674 * detection, occupation by another train or block 'not in use' or 1675 * Signals denying movement are reasons 1676 * for such a message, otherwise only allocation to another warrant 1677 * prevents total success. Note that warrants sharing their clearance 1678 * only allocate and set paths one block in advance. 1679 * 1680 * @param show If true allocateRoute returns messages for display. 1681 * @param orders BlockOrder list of route. If null, use permanent warrant 1682 * copy. 1683 * @return message if the first block fails allocation, otherwise null 1684 */ 1685 public String setRoute(boolean show, List<BlockOrder> orders) { 1686 if (_shareRoute) { // full route of a shared warrant may be displayed 1687 deAllocate(); // clear route to allow sharing with another warrant 1688 } 1689 1690 // allocateRoute may set _message for status info, but return null msg 1691 _message = allocateRoute(show, orders); 1692 if (_message != null) { 1693 log.debug("{}: setRoute: {}", getDisplayName(), _message); 1694 return _message; 1695 } 1696 _routeSet = true; 1697 return null; 1698 } // setRoute 1699 1700 /** 1701 * Check start block for occupied for start of run 1702 * 1703 * @return error message, if any 1704 */ 1705 public String checkStartBlock() { 1706 log.debug("{}: checkStartBlock.", getDisplayName()); 1707 BlockOrder bo = _orders.get(0); 1708 OBlock block = bo.getBlock(); 1709 String msg = block.allocate(this); 1710 if (msg != null) { 1711 return msg; 1712 } 1713 if (block.isDark() || _tempRunBlind) { 1714 msg = "BlockDark"; 1715 } else if (!block.isOccupied()) { 1716 msg = "warnStart"; 1717 } 1718 return msg; 1719 } 1720 1721 protected String checkforTrackers() { 1722 BlockOrder bo = _orders.get(0); 1723 OBlock block = bo.getBlock(); 1724 log.debug("{}: checkforTrackers at block {}", getDisplayName(), block.getDisplayName()); 1725 Tracker t = InstanceManager.getDefault(TrackerTableAction.class).findTrackerIn(block); 1726 if (t != null) { 1727 return Bundle.getMessage("blockInUse", t.getTrainName(), block.getDisplayName()); 1728 } 1729 return null; 1730 } 1731 1732 /** 1733 * Report any occupied blocks in the route 1734 * 1735 * @return String 1736 */ 1737 public String checkRoute() { 1738 log.debug("{}: checkRoute.", getDisplayName()); 1739 if (_orders==null || _orders.isEmpty()) { 1740 return Bundle.getMessage("noBlockOrders"); 1741 } 1742 OBlock startBlock = _orders.get(0).getBlock(); 1743 for (int i = 1; i < _orders.size(); i++) { 1744 OBlock block = _orders.get(i).getBlock(); 1745 if (block.isOccupied() && !startBlock.equals(block)) { 1746 return Bundle.getMessage("BlockRougeOccupied", block.getDisplayName()); 1747 } 1748 Warrant w = block.getWarrant(); 1749 if (w !=null && !this.equals(w)) { 1750 return Bundle.getMessage("AllocatedToWarrant", 1751 w.getDisplayName(), block.getDisplayName(), w.getTrainName()); 1752 } 1753 } 1754 return null; 1755 } 1756 1757 @Override 1758 public void propertyChange(java.beans.PropertyChangeEvent evt) { 1759 if (!(evt.getSource() instanceof NamedBean)) { 1760 return; 1761 } 1762 String property = evt.getPropertyName(); 1763 if (log.isDebugEnabled()) { 1764 log.debug("{}: propertyChange \"{}\" new= {} source= {}", getDisplayName(), 1765 property, evt.getNewValue(), ((NamedBean) evt.getSource()).getDisplayName()); 1766 } 1767 1768 if (_protectSignal != null && _protectSignal == evt.getSource()) { 1769 if (property.equals("Aspect") || property.equals("Appearance")) { 1770 // signal controlling warrant has changed. 1771 readStoppingSignal(); 1772 } 1773 } else if (property.equals("state")) { 1774 if (_stoppingBlock != null && _stoppingBlock.equals(evt.getSource())) { 1775 // starting block is allocated but not occupied 1776 int newState = ((Number) evt.getNewValue()).intValue(); 1777 if ((newState & OBlock.OCCUPIED) != 0) { 1778 if (_delayStart) { // wait for arrival of train to begin the run 1779 // train arrived at starting block or last known block of lost train is found 1780 clearStoppingBlock(); 1781 OBlock block = getBlockAt(0); 1782 _idxCurrentOrder = 0; 1783 if (_runMode == MODE_RUN && _engineer == null) { 1784 _message = acquireThrottle(); 1785 } else if (_runMode == MODE_MANUAL) { 1786 fireRunStatus(PROPERTY_READY_TO_RUN, -1, 0); // ready to start msg 1787 _delayStart = false; 1788 } 1789 block._entryTime = System.currentTimeMillis(); 1790 block.setValue(_trainName); 1791 block.setState(block.getState() | OBlock.RUNNING); 1792 } else if ((((Number) evt.getNewValue()).intValue() & OBlock.ALLOCATED) == 0) { 1793 // blocking warrant has released allocation but train still occupies the block 1794 clearStoppingBlock(); 1795 log.debug("\"{}\" cleared its wait. but block \"{}\" remains occupied", getDisplayName(), 1796 (((Block)evt.getSource()).getDisplayName())); 1797 } 1798 } else if ((((Number) evt.getNewValue()).intValue() & OBlock.UNOCCUPIED) != 0) { 1799 // blocking occupation has left the stopping block 1800 clearStoppingBlock(); 1801 } 1802 } 1803 } 1804 } //end propertyChange 1805 1806 private String getSignalSpeedType(@Nonnull NamedBean signal) { 1807 String speedType; 1808 if (signal instanceof SignalHead) { 1809 SignalHead head = (SignalHead) signal; 1810 int appearance = head.getAppearance(); 1811 speedType = InstanceManager.getDefault(SignalSpeedMap.class) 1812 .getAppearanceSpeed(head.getAppearanceName(appearance)); 1813 if (log.isDebugEnabled()) { 1814 log.debug("{}: SignalHead {} sets appearance speed to {}", 1815 getDisplayName(), signal.getDisplayName(), speedType); 1816 } 1817 } else { 1818 SignalMast mast = (SignalMast) signal; 1819 String aspect = mast.getAspect(); 1820 speedType = InstanceManager.getDefault(SignalSpeedMap.class).getAspectSpeed( 1821 (aspect== null ? "" : aspect), mast.getSignalSystem()); 1822 if (log.isDebugEnabled()) { 1823 log.debug("{}: SignalMast {} sets aspect speed to {}", 1824 getDisplayName(), signal.getDisplayName(), speedType); 1825 } 1826 } 1827 return speedType; 1828 } 1829 1830 /** 1831 * _protectSignal made an aspect change 1832 */ 1833 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 1834 private void readStoppingSignal() { 1835 if (_idxProtectSignal < _idxCurrentOrder) { // signal is behind train. ignore 1836 changeSignalListener(null, _idxCurrentOrder); // remove signal 1837 return; 1838 } 1839 // Signals may change after entry and while the train in the block. 1840 // Normally these changes are ignored. 1841 // However for the case of an overrun stop aspect, the train is waiting. 1842 if (_idxProtectSignal == _idxCurrentOrder && !_waitForSignal) { // not waiting 1843 changeSignalListener(null, _idxCurrentOrder); // remove signal 1844 return; // normal case 1845 }// else Train previously overran stop aspect. Continue and respond to signal. 1846 1847 String speedType = getSignalSpeedType(_protectSignal); 1848 String curSpeedType; 1849 if (_waitForSignal) { 1850 curSpeedType = Stop; 1851 } else { 1852 curSpeedType = _engineer.getSpeedType(true); // current or pending ramp completion 1853 } 1854 if (log.isDebugEnabled()) { 1855 log.debug("{}: Signal \"{}\" changed to aspect \"{}\" {} blocks ahead. curSpeedType= {}", 1856 getDisplayName(), _protectSignal.getDisplayName(), speedType, _idxProtectSignal-_idxCurrentOrder, curSpeedType); 1857 } 1858 1859 if (curSpeedType.equals(speedType)) { 1860 return; 1861 } 1862 if (_idxProtectSignal > _idxCurrentOrder) { 1863 if (_speedUtil.secondGreaterThanFirst(speedType, curSpeedType)) { 1864 // change to slower speed. Check if speed change should occur now 1865 float availDist = getAvailableDistance(_idxProtectSignal); 1866 float changeDist = getChangeSpeedDistance(_idxProtectSignal, speedType); 1867 if (changeDist > availDist) { 1868 // Not enough room in blocks ahead. start ramp in current block 1869 availDist += getAvailableDistanceAt(_idxCurrentOrder); 1870 if (speedType.equals(Warrant.Stop)) { 1871 _waitForSignal = true; 1872 } 1873 int cmdStartIdx = _engineer.getCurrentCommandIndex(); // blkSpeedInfo.getFirstIndex(); 1874 if (!doDelayRamp(availDist, changeDist, _idxProtectSignal, speedType, cmdStartIdx)) { 1875 log.info("No room for train {} to ramp to \"{}\" from \"{}\" for signal \"{}\"!. availDist={}, changeDist={} on warrant {}", 1876 getTrainName(), speedType, curSpeedType, _protectSignal.getDisplayName(), 1877 availDist, changeDist, getDisplayName()); 1878 } // otherwise will do ramp when entering a block ahead 1879 } 1880 return; 1881 } 1882 } 1883 if (!speedType.equals(Warrant.Stop)) { // a moving aspect clears a signal wait 1884 if (_waitForSignal) { 1885 // signal protecting next block just released its hold 1886 _curSignalAspect = speedType; 1887 _waitForSignal = false; 1888 if (_trace || log.isDebugEnabled()) { 1889 log.info(Bundle.getMessage("SignalCleared", _protectSignal.getDisplayName(), speedType, _trainName)); 1890 } 1891 ThreadingUtil.runOnGUIDelayed(() -> { 1892 restoreRunning(speedType); 1893 }, 2000); 1894 } 1895 } 1896 } 1897 1898 1899 /* 1900 * return distance from the exit of the current block "_idxCurrentOrder" 1901 * to the entrance of the "idxChange" block. 1902 */ 1903 private float getAvailableDistance(int idxChange) { 1904 float availDist = 0; 1905 int idxBlockOrder = _idxCurrentOrder + 1; 1906 if (idxBlockOrder < _orders.size() - 1) { 1907 while (idxBlockOrder < idxChange) { 1908 availDist += getAvailableDistanceAt(idxBlockOrder++); // distance to next block 1909 } 1910 } 1911 return availDist; 1912 } 1913 1914 /* 1915 * Get distance needed to ramp so the speed into the next block satisfies the speedType 1916 * @param idxBlockOrder blockOrder index of entrance block 1917 */ 1918 private float getChangeSpeedDistance(int idxBlockOrder, String speedType) { 1919 float speedSetting = _engineer.getSpeedSetting(); // current speed 1920 // Estimate speed at start of ramp 1921 float enterSpeed; // speed at start of ramp 1922 if (speedSetting > 0.1f && (_idxCurrentOrder == idxBlockOrder - 1)) { 1923 // if in the block immediately before the entrance block, use current speed 1924 enterSpeed = speedSetting; 1925 } else { // else use entrance speed of previous block 1926 String currentSpeedType = _engineer.getSpeedType(false); // current speed type 1927 float scriptSpeed = _speedUtil.getBlockSpeedInfo(idxBlockOrder - 1).getEntranceSpeed(); 1928 enterSpeed = _speedUtil.modifySpeed(scriptSpeed, currentSpeedType); 1929 } 1930 float scriptSpeed = _speedUtil.getBlockSpeedInfo(idxBlockOrder).getEntranceSpeed(); 1931 float endSpeed = _speedUtil.modifySpeed(scriptSpeed, speedType); 1932 // compare distance needed for script throttle at entrance to entrance speed, 1933 // to the distance needed for current throttle to entrance speed. 1934 float enterLen = _speedUtil.getRampLengthForEntry(enterSpeed, endSpeed); 1935 // add buffers for signal and safety clearance 1936 float bufDist = getEntranceBufferDist(idxBlockOrder); 1937// log.debug("{}: getChangeSpeedDistance curSpeed= {} enterSpeed= {} endSpeed= {}", getDisplayName(), speedSetting, enterSpeed, endSpeed); 1938 return enterLen + bufDist; 1939 } 1940 1941 private void doStoppingBlockClear() { 1942 if (_stoppingBlock == null) { 1943 return; 1944 } 1945 _stoppingBlock.removePropertyChangeListener(this); 1946 _stoppingBlock = null; 1947 _idxStoppingBlock = -1; 1948 } 1949 1950 /** 1951 * Called when a rogue or warranted train has left a block. 1952 * Also called from propertyChange() to allow warrant to acquire a throttle 1953 * and launch an engineer. Also called by retry control command to help user 1954 * work out of an error condition. 1955 */ 1956 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 1957 synchronized private void clearStoppingBlock() { 1958 if (_stoppingBlock == null) { 1959 return; 1960 } 1961 String name = _stoppingBlock.getDisplayName(); 1962 doStoppingBlockClear(); 1963 1964 if (_delayStart) { 1965 return; // don't start. Let user resume start 1966 } 1967 if (_trace || log.isDebugEnabled()) { 1968 String reason; 1969 if (_waitForBlock) { 1970 reason = Bundle.getMessage("Occupancy"); 1971 } else { 1972 reason = Bundle.getMessage("Warrant"); 1973 } 1974 log.info(Bundle.getMessage("StopBlockCleared", 1975 getTrainName(), getDisplayName(), reason, name)); 1976 } 1977 cancelDelayRamp(); 1978 int time = 1000; 1979 if (_waitForBlock) { 1980 _waitForBlock = false; 1981 time = 4000; 1982 } 1983 if (_waitForWarrant) { 1984 _waitForWarrant = false; 1985 time = 3000; 1986 } 1987 String speedType; 1988 if (_curSignalAspect != null) { 1989 speedType = _curSignalAspect; 1990 } else { 1991 speedType = _engineer.getSpeedType(false); // current speed type 1992 } 1993 ThreadingUtil.runOnGUIDelayed(() -> { 1994 restoreRunning(speedType); 1995 }, time); 1996 } 1997 1998 private String okToRun() { 1999 boolean cannot = false; 2000 StringBuilder sb = new StringBuilder(); 2001 if (_waitForSignal) { 2002 sb.append(Bundle.getMessage("Signal")); 2003 cannot = true; 2004 } 2005 if (_waitForWarrant) { 2006 if (cannot) { 2007 sb.append(", "); 2008 } else { 2009 cannot = true; 2010 } 2011 Warrant w = getBlockingWarrant(); 2012 if (w != null) { 2013 sb.append(Bundle.getMessage("WarrantWait", w.getDisplayName())); 2014 } else { 2015 sb.append(Bundle.getMessage("WarrantWait", "Unknown")); 2016 } 2017 } 2018 if (_waitForBlock) { 2019 if (cannot) { 2020 sb.append(", "); 2021 } else { 2022 cannot = true; 2023 } 2024 sb.append(Bundle.getMessage("Occupancy")); 2025 } 2026 2027 if (_engineer != null) { 2028 int runState = _engineer.getRunState(); 2029 if (runState == HALT || runState == RAMP_HALT) { 2030 if (cannot) { 2031 sb.append(", "); 2032 } else { 2033 cannot = true; 2034 } 2035 sb.append(Bundle.getMessage("userHalt")); 2036 } 2037 } 2038 if (cannot) { 2039 return sb.toString(); 2040 } 2041 return null; 2042 } 2043 2044 /** 2045 * A layout condition that has restricted or stopped a train has been cleared. 2046 * i.e. Signal aspect, rogue occupied block, contesting warrant or user halt. 2047 * This may or may not be all the conditions restricting speed. 2048 * @return true if automatic restart is done 2049 */ 2050 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 2051 private boolean restoreRunning(String speedType) { 2052 _message = okToRun(); 2053 boolean returnOK; 2054 if (_message == null) { 2055 BlockOrder bo = getBlockOrderAt(_idxCurrentOrder); 2056 TrainOrder to = bo.allocatePaths(this, true); 2057 OBlock block = bo.getBlock(); 2058 if (log.isDebugEnabled()) { 2059 log.debug("{}: restoreRunning {}", getDisplayName(), to.toString()); 2060 } 2061 switch (to._cause) { // to._cause - precedence of checks is WARRANT, OCCUPY, SIGNAL 2062 case NONE: 2063 returnOK = doRestoreRunning(block, speedType); 2064 break; 2065 case WARRANT: 2066 _waitForWarrant = true; 2067 _message = to._message; 2068 setStoppingBlock(to._idxContrlBlock); 2069 returnOK = false; 2070 break; 2071 case OCCUPY: 2072 if (_overrun || _lost) { 2073 _message = setPathAt(_idxCurrentOrder); 2074 if (_message == null) { 2075 returnOK = doRestoreRunning(block, speedType); 2076 } else { 2077 returnOK = false; 2078 } 2079 if (_lost && returnOK) { 2080 _lost = false; 2081 } 2082 break; 2083 } 2084 returnOK = false; 2085 _waitForBlock = true; 2086 _message = to._message; 2087 setStoppingBlock(to._idxContrlBlock); 2088 break; 2089 case SIGNAL: 2090 if (to._idxContrlBlock == _idxCurrentOrder) { 2091 returnOK = doRestoreRunning(block, speedType); 2092 } else { 2093 returnOK = false; 2094 } 2095 if (returnOK && Stop.equals(to._speedType)) { 2096 _waitForSignal = true; 2097 _message = to._message; 2098 setProtectingSignal(to._idxContrlBlock); 2099 returnOK = false; 2100 break; 2101 } 2102 speedType = to._speedType; 2103 returnOK = doRestoreRunning(block, speedType); 2104 break; 2105 default: 2106 log.error("restoreRunning TrainOrder {}", to.toString()); 2107 _message = to._message; 2108 returnOK = false; 2109 } 2110 } else { 2111 returnOK = false; 2112 } 2113 if (!returnOK) { 2114 String blockName = getBlockAt(_idxCurrentOrder).getDisplayName(); 2115 if (_trace || log.isDebugEnabled()) { 2116 log.info(Bundle.getMessage("trainWaiting", getTrainName(), _message, blockName)); 2117 } 2118 fireRunStatus(PROPERTY_CANNOT_RUN, blockName, _message); 2119 } 2120 return returnOK; 2121 } 2122 2123 private boolean doRestoreRunning(OBlock block, String speedType) { 2124 _overrun = false; 2125 _curSignalAspect = null; 2126 setPathAt(_idxCurrentOrder); // show ownership and train Id 2127 2128 // It is highly likely an event to restart a speed increase occurs when the train 2129 // position is in the middle or end of the block. Since 'lookAheadforSpeedChange' 2130 // assumes the train is at the start of a block, don't ramp up if the 2131 // train may not enter the next block. No room for both ramp up and ramp down 2132 BlockOrder bo = getBlockOrderAt(_idxCurrentOrder+1); 2133 if (bo != null) { 2134 TrainOrder to = bo.allocatePaths(this, true); 2135 if (Warrant.Stop.equals(to._speedType)) { 2136 _message = to._message; 2137 switch (to._cause) { 2138 case NONE: 2139 break; 2140 case WARRANT: 2141 _waitForWarrant = true; 2142 setStoppingBlock(to._idxContrlBlock); 2143 break; 2144 case OCCUPY: 2145 _waitForBlock = true; 2146 setStoppingBlock(to._idxContrlBlock); 2147 break; 2148 case SIGNAL: 2149 _waitForSignal = true; 2150 setProtectingSignal(to._idxContrlBlock); 2151 break; 2152 default: 2153 } 2154 return false; 2155 } 2156 } 2157 _engineer.clearWaitForSync(block); 2158 if (log.isDebugEnabled()) { 2159 log.debug("{}: restoreRunning(): rampSpeedTo to \"{}\"", 2160 getDisplayName(), speedType); 2161 } 2162 rampSpeedTo(speedType, -1); 2163 // continue, there may be blocks ahead that need a speed decrease before entering them 2164 if (!_overrun && _idxCurrentOrder < _orders.size() - 1) { 2165 lookAheadforSpeedChange(speedType, speedType); 2166 } // else at last block, forget about speed changes 2167 return true; 2168 } 2169 2170 /** 2171 * Stopping block only used in MODE_RUN _stoppingBlock is an occupied OBlock 2172 * preventing the train from continuing the route OR another warrant 2173 * is preventing this warrant from allocating the block to continue. 2174 * <p> 2175 */ 2176 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 2177 private void setStoppingBlock(int idxBlock) { 2178 OBlock block = getBlockAt(idxBlock); 2179 if (block == null) { 2180 return; 2181 } 2182 // _idxCurrentOrder == 0 may be a delayed start waiting for loco. 2183 // Otherwise don't set _stoppingBlock for a block occupied by train 2184 if (idxBlock < 0 || (_idxCurrentOrder == idxBlock && !_lost)) { 2185 return; 2186 } 2187 OBlock prevBlk = _stoppingBlock; 2188 if (_stoppingBlock != null) { 2189 if (_stoppingBlock.equals(block)) { 2190 return; 2191 } 2192 2193 int idxStop = getIndexOfBlockAfter(_stoppingBlock, _idxCurrentOrder); 2194 if ((idxBlock < idxStop) || idxStop < 0) { 2195 prevBlk.removePropertyChangeListener(this); 2196 } else { 2197 if (idxStop < _idxCurrentOrder) { 2198 log.error("{}: _stoppingBlock \"{}\" index {} < _idxCurrentOrder {}", 2199 getDisplayName(), _stoppingBlock.getDisplayName(), idxStop, _idxCurrentOrder); 2200 } 2201 return; 2202 } 2203 } 2204 _stoppingBlock = block; 2205 _idxStoppingBlock = idxBlock; 2206 _stoppingBlock.addPropertyChangeListener(this); 2207 if ((_trace || log.isDebugEnabled()) && (_waitForBlock || _waitForWarrant)) { 2208 String reason; 2209 String cause; 2210 if (_waitForWarrant) { 2211 reason = Bundle.getMessage("Warrant"); 2212 Warrant w = block.getWarrant(); 2213 if (w != null) { 2214 cause = w.getDisplayName(); 2215 } else { 2216 cause = Bundle.getMessage("Unknown"); 2217 } 2218 } else if (_waitForBlock) { 2219 reason = Bundle.getMessage("Occupancy"); 2220 cause = (String)block.getValue(); 2221 if (cause == null) { 2222 cause = Bundle.getMessage("unknownTrain"); 2223 } 2224 } else if (_lost) { 2225 reason = Bundle.getMessage("Lost"); 2226 cause = Bundle.getMessage("Occupancy"); 2227 } else { 2228 reason = Bundle.getMessage("Start"); 2229 cause = ""; 2230 } 2231 log.info(Bundle.getMessage("StopBlockSet", _stoppingBlock.getDisplayName(), getTrainName(), reason, cause)); 2232 } 2233 } 2234 2235 /** 2236 * set signal listening for aspect change for block at index. 2237 * return true if signal is set. 2238 */ 2239 private boolean setProtectingSignal(int idx) { 2240 if (_idxProtectSignal == idx) { 2241 return true; 2242 } 2243 BlockOrder blkOrder = getBlockOrderAt(idx); 2244 NamedBean signal = blkOrder.getSignal(); 2245 2246 if (_protectSignal != null && _protectSignal.equals(signal)) { 2247 // Must be the route coming back to the same block. Same signal, move index only. 2248 if (_idxProtectSignal < idx && idx >= 0) { 2249 _idxProtectSignal = idx; 2250 } 2251 return true; 2252 } 2253 2254 if (_protectSignal != null) { 2255 if (idx > _idxProtectSignal && _idxProtectSignal > _idxCurrentOrder) { 2256 return true; 2257 } 2258 } 2259 2260 return changeSignalListener(signal, idx); 2261 } 2262 2263 /** 2264 * if current listening signal is not at signalIndex, remove listener and 2265 * set new listening signal 2266 */ 2267 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 2268 private boolean changeSignalListener(NamedBean signal, int signalIndex) { 2269 if (signalIndex == _idxProtectSignal) { 2270 return true; 2271 } 2272// StringBuilder sb = new StringBuilder(getDisplayName()); 2273 if (_protectSignal != null) { 2274 _protectSignal.removePropertyChangeListener(this); 2275/* if (log.isDebugEnabled()) { 2276 sb.append("Removes \""); 2277 sb.append(_protectSignal.getDisplayName()); 2278 sb.append("\" at \""); 2279 sb.append(getBlockAt(_idxProtectSignal).getDisplayName()); 2280 sb.append("\""); 2281 }*/ 2282 _protectSignal = null; 2283 _idxProtectSignal = -1; 2284 } 2285 boolean ret = false; 2286 if (signal != null) { 2287 _protectSignal = signal; 2288 _idxProtectSignal = signalIndex; 2289 _protectSignal.addPropertyChangeListener(this); 2290 if (_trace || log.isDebugEnabled()) { 2291 log.info(Bundle.getMessage("ProtectSignalSet", getTrainName(), 2292 _protectSignal.getDisplayName(), getBlockAt(_idxProtectSignal).getDisplayName())); 2293 } 2294 ret = true; 2295 } 2296 return ret; 2297 } 2298 2299 /** 2300 * Check if this is the next block of the train moving under the warrant 2301 * Learn mode assumes route is set and clear. Run mode update conditions. 2302 * <p> 2303 * Must be called on Layout thread. 2304 * 2305 * @param block Block in the route is going active. 2306 */ 2307 @InvokeOnLayoutThread 2308 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 2309 protected void goingActive(OBlock block) { 2310 if (log.isDebugEnabled()) { 2311 if (!ThreadingUtil.isLayoutThread()) { 2312 log.error("{} invoked on wrong thread", getDisplayName(), new Exception("traceback")); 2313 stopWarrant(true, true); 2314 return; 2315 } 2316 } 2317 2318 if (_runMode == MODE_NONE) { 2319 return; 2320 } 2321 int activeIdx = getIndexOfBlockAfter(block, _idxCurrentOrder); 2322 if (log.isDebugEnabled()) { 2323 log.debug("{}: **Block \"{}\" goingActive. activeIdx= {}, _idxCurrentOrder= {}.", 2324 getDisplayName(), block.getDisplayName(), activeIdx, _idxCurrentOrder); 2325 } 2326 Warrant w = block.getWarrant(); 2327 if (w == null || !this.equals(w)) { 2328 if (log.isDebugEnabled()) { 2329 log.debug("{}: **Block \"{}\" owned by {}!", 2330 getDisplayName(), block.getDisplayName(), (w==null?"NO One":w.getDisplayName())); 2331 } 2332 return; 2333 } 2334 if (_lost && !getBlockAt(_idxCurrentOrder).isOccupied()) { 2335 _idxCurrentOrder = activeIdx; 2336 log.info("Train \"{}\" found at block \"{}\" of warrant {}.", 2337 getTrainName(), block.getDisplayName(), getDisplayName()); 2338 _lost = false; 2339 rampSpeedTo(_engineer.getSpeedType(false), - 1); // current speed type 2340 setMovement(); 2341 return; 2342 } 2343 if (activeIdx <= 0) { 2344 // if _idxCurrentOrder == 0, (i.e. starting block) case 0 is handled as the _stoppingBlock 2345 return; 2346 } 2347 if (activeIdx == _idxCurrentOrder) { 2348 // unusual occurrence. dirty track? sensor glitch? 2349 if (_trace || log.isDebugEnabled()) { 2350 log.info(Bundle.getMessage("RegainDetection", getTrainName(), block.getDisplayName())); 2351 } 2352 } else if (activeIdx == _idxCurrentOrder + 1) { 2353 if (_delayStart) { 2354 log.warn("{}: Rogue entered Block \"{}\" ahead of {}.", 2355 getDisplayName(), block.getDisplayName(), getTrainName()); 2356 _message = Bundle.getMessage("BlockRougeOccupied", block.getDisplayName()); 2357 return; 2358 } 2359 // Since we are moving at speed we assume it is our train that entered the block 2360 // continue on. 2361 _idxCurrentOrder = activeIdx; 2362 } else if (activeIdx > _idxCurrentOrder + 1) { 2363 // if previous blocks are dark, this could be for our train 2364 // check from current (last known) block to this just activated block 2365 for (int idx = _idxCurrentOrder + 1; idx < activeIdx; idx++) { 2366 OBlock preBlock = getBlockAt(idx); 2367 if (!preBlock.isDark()) { 2368 // not dark, therefore not our train 2369 if (log.isDebugEnabled()) { 2370 OBlock curBlock = getBlockAt(_idxCurrentOrder); 2371 log.debug("Rogue train entered block \"{}\" ahead of train {} currently in block \"{}\"!", 2372 block.getDisplayName(), _trainName, curBlock.getDisplayName()); 2373 } 2374 return; 2375 } 2376 } 2377 // we assume this is our train entering block 2378 _idxCurrentOrder = activeIdx; 2379 // previous blocks were checked as UNDETECTED above 2380 // Indicate the previous dark block was entered 2381 OBlock prevBlock = getBlockAt(activeIdx - 1); 2382 prevBlock._entryTime = System.currentTimeMillis() - 5000; // arbitrary. Just say 5 seconds 2383 prevBlock.setValue(_trainName); 2384 prevBlock.setState(prevBlock.getState() | OBlock.RUNNING); 2385 if (log.isDebugEnabled()) { 2386 log.debug("{}: Train moving from UNDETECTED block \"{}\" now entering block\"{}\"", 2387 getDisplayName(), prevBlock.getDisplayName(), block.getDisplayName()); 2388 } 2389 } else if (_idxCurrentOrder > activeIdx) { 2390 // unusual occurrence. dirty track, sensor glitch, too fast for goingInactive() for complete? 2391 log.info("Tail of Train {} regained detection behind Block= {} at block= {}", 2392 getTrainName(), block.getDisplayName(), getBlockAt(activeIdx).getDisplayName()); 2393 return; 2394 } 2395 // Since we are moving we assume it is our train entering the block 2396 // continue on. 2397 setHeadOfTrain(block); 2398 if (_engineer != null) { 2399 _engineer.clearWaitForSync(block); // Sync commands if train is faster than ET 2400 } 2401 if (_trace) { 2402 log.info(Bundle.getMessage("TrackerBlockEnter", getTrainName(), block.getDisplayName())); 2403 } 2404 fireRunStatus("blockChange", getBlockAt(activeIdx - 1), block); 2405 if (_runMode == MODE_LEARN) { 2406 return; 2407 } 2408 // _idxCurrentOrder has been incremented. Warranted train has entered this block. 2409 // Do signals, speed etc. 2410 if (_idxCurrentOrder < _orders.size() - 1) { 2411 if (_engineer != null) { 2412 BlockOrder bo = _orders.get(_idxCurrentOrder + 1); 2413 if (bo.getBlock().isDark()) { 2414 // can't detect next block, use ET 2415 _engineer.setRunOnET(true); 2416 } else if (!_tempRunBlind) { 2417 _engineer.setRunOnET(false); 2418 } 2419 } 2420 } 2421 if (log.isTraceEnabled()) { 2422 log.debug("{}: end of goingActive. leaving \"{}\" entered \"{}\"", 2423 getDisplayName(), getBlockAt(activeIdx - 1).getDisplayName(), block.getDisplayName()); 2424 } 2425 setMovement(); 2426 } //end goingActive 2427 2428 private void setHeadOfTrain(OBlock block ) { 2429 block.setValue(_trainName); 2430 block.setState(block.getState() | OBlock.RUNNING); 2431 if (_runMode == MODE_RUN && _idxCurrentOrder > 0 && _idxCurrentOrder < _orders.size()) { 2432 _speedUtil.leavingBlock(_idxCurrentOrder - 1); 2433 } 2434 } 2435 2436 /** 2437 * @param block Block in the route is going Inactive 2438 */ 2439 @InvokeOnLayoutThread 2440 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 2441 protected void goingInactive(OBlock block) { 2442 if (log.isDebugEnabled()) { 2443 if (!ThreadingUtil.isLayoutThread()) { 2444 log.error("{} invoked on wrong thread", getDisplayName(), new Exception("traceback")); 2445 } 2446 } 2447 if (_runMode == MODE_NONE) { 2448 return; 2449 } 2450 2451 int idx = getIndexOfBlockBefore(_idxCurrentOrder, block); // if idx >= 0, it is in this warrant 2452 if (log.isDebugEnabled()) { 2453 log.debug("{}: *Block \"{}\" goingInactive. idx= {}, _idxCurrentOrder= {}.", 2454 getDisplayName(), block.getDisplayName(), idx, _idxCurrentOrder); 2455 } 2456 if (idx > _idxCurrentOrder) { 2457 return; 2458 } 2459 releaseBlock(block, idx); 2460 block.setValue(null); 2461 if (idx == _idxCurrentOrder) { 2462 // Train not visible if current block goes inactive. This is OK if the next block is Dark. 2463 if (_idxCurrentOrder + 1 < _orders.size()) { 2464 OBlock nextBlock = getBlockAt(_idxCurrentOrder + 1); 2465 if (nextBlock.isDark()) { 2466 goingActive(nextBlock); // fake occupancy for dark block 2467 return; 2468 } 2469 if (checkForOverrun(nextBlock)) { 2470 return; 2471 } 2472 } 2473 _lost = true; 2474 if (_engineer != null) { 2475 setSpeedToType(Stop); // set 0 throttle 2476 setStoppingBlock(_idxCurrentOrder); 2477 } 2478 if (_trace) { 2479 log.info(Bundle.getMessage("ChangedRoute", _trainName, block.getDisplayName(), getDisplayName())); 2480 } 2481 fireRunStatus("blockChange", block, null); // train is lost 2482 } 2483 } // end goingInactive 2484 2485 /** 2486 * Deallocates all blocks prior to and including block at index idx 2487 * of _orders, if not needed again. 2488 * Comes from goingInactive, i.e. warrant has a listener on the block. 2489 * @param block warrant is releasing 2490 * @param idx index in BlockOrder list 2491 */ 2492 private void releaseBlock(OBlock block, int idx) { 2493 /* 2494 * Deallocate block if train will not use the block again. Warrant 2495 * could loop back and re-enter blocks previously traversed. That is, 2496 * they will need to re-allocation of blocks ahead. 2497 * Previous Dark blocks also need deallocation and other trains or cars 2498 * dropped may have prevented previous blocks from going inactive. 2499 * Thus we must deallocate backward until we reach inactive detectable blocks 2500 * or blocks we no longer own. 2501 */ 2502 for (int i = idx; i > -1; i--) { 2503 boolean neededLater = false; 2504 OBlock curBlock = getBlockAt(i); 2505 for (int j = i + 1; j < _orders.size(); j++) { 2506 if (curBlock.equals(getBlockAt(j))) { 2507 neededLater = true; 2508 } 2509 } 2510 if (!neededLater) { 2511 if (deAllocateBlock(curBlock)) { 2512 curBlock.setValue(null); 2513 _totalAllocated = false; 2514 } 2515 } else { 2516 if (curBlock.isAllocatedTo(this)) { 2517 // Can't deallocate, but must listen for followers 2518 // who may be occupying the block 2519 if (_idxCurrentOrder != idx + 1) { 2520 curBlock.setValue(null); 2521 } 2522 if (curBlock.equals(_stoppingBlock)){ 2523 doStoppingBlockClear(); 2524 } 2525 } 2526 if (_shareRoute) { // don't deallocate if closer than 2 blocks, otherwise deallocate 2527 int k = Math.min(3, _orders.size()); 2528 while (k > _idxCurrentOrder) { 2529 if (!curBlock.equals(getBlockAt(k))) { 2530 if (deAllocateBlock(curBlock)) { 2531 curBlock.setValue(null); 2532 _totalAllocated = false; 2533 } 2534 } 2535 k--; 2536 } 2537 } 2538 } 2539 } 2540 } 2541 2542 /* 2543 * This block is a possible overrun. If permitted, we may claim ownership. 2544 * BlockOrder index of block is _idxCurrentOrder + 1 2545 * return true, if warrant can claim occupation and ownership 2546 */ 2547 private boolean checkForOverrun(OBlock block) { 2548 if (block.isOccupied() && (System.currentTimeMillis() - block._entryTime < 5000)) { 2549 // Went active within the last 5 seconds. Likely an overrun 2550 _overrun = true; 2551 _message = setPathAt(_idxCurrentOrder + 1); // no TrainOrder checks. allocates and sets path 2552 if (_message == null) { // OK we own the block now. 2553 _idxCurrentOrder++; 2554 // insulate possible non-GUI thread making this call (e.g. Engineer) 2555 ThreadingUtil.runOnGUI(()-> goingActive(block)); 2556 return true ; 2557 } 2558 } 2559 return false; 2560 } 2561 2562 @Override 2563 public void dispose() { 2564 if (_runMode != MODE_NONE) { 2565 stopWarrant(true, true); 2566 } 2567 super.dispose(); 2568 } 2569 2570 @Override 2571 public String getBeanType() { 2572 return Bundle.getMessage("BeanNameWarrant"); 2573 } 2574 2575 private class CommandDelay extends Thread { 2576 2577 String _speedType; 2578// long _startTime = 0; 2579 long _waitTime = 0; 2580 float _waitSpeed; 2581 volatile boolean quit = false; 2582 int _endBlockIdx; 2583 2584 CommandDelay(@Nonnull String speedType, long startWait, float waitSpeed, int endBlockIdx) { 2585 _speedType = speedType; 2586 _waitTime = startWait; 2587 _waitSpeed = waitSpeed; 2588 _endBlockIdx = endBlockIdx; 2589 setName("CommandDelay(" + getTrainName() + "-" + speedType +")"); 2590 } 2591 2592 // check if request for a duplicate CommandDelay can be cancelled 2593 boolean isDuplicate(String speedType, long startWait, int endBlockIdx) { 2594 if (endBlockIdx == _endBlockIdx && speedType.equals(_speedType) ) { // && 2595// (_waitTime - (System.currentTimeMillis() - _startTime)) < startWait) { 2596 return true; // keeps this thread 2597 } 2598 return false; // not a duplicate or does not shorten time wait. this thread will be cancelled 2599 } 2600 2601 @Override 2602 @SuppressFBWarnings(value = "WA_NOT_IN_LOOP", justification = "notify never called on this thread") 2603 public void run() { 2604 synchronized (this) { 2605// _startTime = System.currentTimeMillis(); 2606 boolean ramping = _engineer.isRamping(); 2607 if (ramping) { 2608 long startTime = System.currentTimeMillis(); 2609 while (!quit && System.currentTimeMillis() - startTime <= _waitTime) { 2610 if (_engineer.getSpeedSetting() >= _waitSpeed) { 2611 break; // stop ramping beyond this speed 2612 } 2613 try { 2614 wait(100); 2615 } catch (InterruptedException ie) { 2616 if (log.isDebugEnabled() && quit) { 2617 log.debug("CommandDelay interrupt. Ramp to {} not done. warrant {}", 2618 _speedType, getDisplayName()); 2619 } 2620 } 2621 } 2622 } else { 2623 try { 2624 wait(_waitTime); 2625 } catch (InterruptedException ie) { 2626 if (log.isDebugEnabled() && quit) { 2627 log.debug("CommandDelay interrupt. Ramp to {} not done. warrant {}", 2628 _speedType, getDisplayName()); 2629 } 2630 } 2631 } 2632 2633 if (!quit && _engineer != null) { 2634 if (_noRamp) { 2635 setSpeedToType(_speedType); 2636 } else { 2637 _engineer.rampSpeedTo(_speedType, _endBlockIdx); 2638 } 2639 } 2640 } 2641 endDelayCommand(this); 2642 } 2643 } 2644 2645 synchronized private void cancelDelayRamp() { 2646 if (_delayCommand != null) { 2647 log.debug("{}: cancelDelayRamp() called. _speedType= {}", getDisplayName(), _delayCommand._speedType); 2648 _delayCommand.quit = true; 2649 _delayCommand.interrupt(); 2650 _delayCommand = null; 2651 } 2652 } 2653 2654 synchronized private void endDelayCommand(CommandDelay cd) { 2655 if (_delayCommand == cd) { // may have been replaced by a newer one 2656 _delayCommand = null; 2657 } 2658 } 2659 2660 private void rampSpeedTo(String speedType, int idx) { 2661 cancelDelayRamp(); 2662 if (_noRamp) { 2663 _engineer.setSpeedToType(speedType); 2664 _engineer.setWaitforClear(speedType.equals(Stop) || speedType.equals(EStop)); 2665 if (log.isDebugEnabled()) { 2666 log.debug("{}: No Ramp to \"{}\" from block \"{}\"", getDisplayName(), speedType, getCurrentBlockName()); 2667 } 2668 return; 2669 } 2670 if (log.isDebugEnabled()) { 2671 if (idx < 0) { 2672 log.debug("{}: Ramp up to \"{}\" from block \"{}\"", getDisplayName(), speedType, getCurrentBlockName()); 2673 } else { 2674 log.debug("{}: Ramp down to \"{}\" before block \"{}\"", getDisplayName(), speedType, getBlockAt(idx).getDisplayName()); 2675 } 2676 } 2677 if (_engineer != null) { 2678 _engineer.rampSpeedTo(speedType, idx); 2679 } else { 2680 log.error("{}: No Engineer!", getDisplayName()); 2681 } 2682 } 2683 2684 private void setSpeedToType(String speedType) { 2685 cancelDelayRamp(); 2686 _engineer.setSpeedToType(speedType); 2687 } 2688 2689 private void clearWaitFlags(boolean removeListeners) { 2690 if (log.isTraceEnabled()) { 2691 log.trace("{}: Flags cleared {}.", getDisplayName(), removeListeners?"and removed Listeners":"only"); 2692 } 2693 _waitForBlock = false; 2694 _waitForSignal = false; 2695 _waitForWarrant = false; 2696 if (removeListeners) { 2697 if (_protectSignal != null) { 2698 _protectSignal.removePropertyChangeListener(this); 2699 _protectSignal = null; 2700 _idxProtectSignal = -1; 2701 } 2702 if (_stoppingBlock != null) { 2703 _stoppingBlock.removePropertyChangeListener(this); 2704 _stoppingBlock = null; 2705 _idxStoppingBlock = -1; 2706 } 2707 } 2708 } 2709 2710 /* 2711 * Return pathLength of the block. 2712 */ 2713 private float getAvailableDistanceAt(int idxBlockOrder) { 2714 BlockOrder blkOrder = getBlockOrderAt(idxBlockOrder); 2715 float pathLength = blkOrder.getPathLength(); 2716 if (idxBlockOrder == 0 || pathLength <= 20.0f) { 2717 // Position in block is unknown. use calculated distances instead 2718 float blkDist = _speedUtil.getBlockSpeedInfo(idxBlockOrder).getCalcLen(); 2719 if (log.isDebugEnabled()) { 2720 log.debug("{}: getAvailableDistanceAt: block \"{}\" using calculated blkDist= {}, pathLength= {}", 2721 getDisplayName(), blkOrder.getBlock().getDisplayName(), blkDist, pathLength); 2722 } 2723 return blkDist; 2724 } else { 2725 return pathLength; 2726 } 2727 } 2728 2729 private float getEntranceBufferDist(int idxBlockOrder) { 2730 float bufDist = BUFFER_DISTANCE; 2731 if (_waitForSignal) { // signal restricting speed 2732 bufDist+= getBlockOrderAt(idxBlockOrder).getEntranceSpace(); // signal's adjustment 2733 } 2734 return bufDist; 2735 } 2736 2737 /** 2738 * Called to set the correct speed for the train when the scripted speed 2739 * must be modified due to a track condition (signaled speed or rogue 2740 * occupation). Also called to return to the scripted speed after the 2741 * condition is cleared. Assumes the train occupies the block of the current 2742 * block order. 2743 * <p> 2744 * Looks for speed requirements of this block and takes immediate action if 2745 * found. Otherwise looks ahead for future speed change needs. If speed 2746 * restriction changes are required to begin in this block, but the change 2747 * is not immediate, then determine the proper time delay to start the speed 2748 * change. 2749 */ 2750 private void setMovement() { 2751 BlockOrder curBlkOrder = getBlockOrderAt(_idxCurrentOrder); 2752 OBlock curBlock = curBlkOrder.getBlock(); 2753 String currentSpeedType = _engineer.getSpeedType(false); // current speed type 2754 String entrySpeedType = BlockOrder.getPermissibleSpeedAt(curBlkOrder); // expected speed type for this block 2755 if (entrySpeedType == null) { 2756 entrySpeedType = currentSpeedType; 2757 } 2758 curBlkOrder.setPath(this); // restore running 2759 2760 if (log.isDebugEnabled()) { 2761 SpeedState speedState = _engineer.getSpeedState(); 2762 int runState = _engineer.getRunState(); 2763 log.debug("{}: SET MOVEMENT Block \"{}\" runState= {}, speedState= {} for currentSpeedType= {}. entrySpeedType= {}.", 2764 getDisplayName(), curBlock.getDisplayName(), RUN_STATE[runState], speedState.toString(), 2765 currentSpeedType, entrySpeedType); 2766 log.debug("{}: Flags: _waitForBlock={}, _waitForSignal={}, _waitForWarrant={} curThrottle= {}.", 2767 getDisplayName(), _waitForBlock, _waitForSignal, _waitForWarrant, _engineer.getSpeedSetting()); 2768 if (_message != null) { 2769 log.debug("{}: _message ({}) ", getDisplayName(), _message); 2770 } 2771 } 2772 2773 // Check that flags and states agree with expected speed and position 2774 // A signal drop down can appear to be a speed violation, but only when a violation when expected 2775 if (_idxCurrentOrder > 0) { 2776 if (_waitForSignal) { 2777 if (_idxProtectSignal == _idxCurrentOrder) { 2778 makeOverrunMessage(curBlkOrder); 2779 setSpeedToType(Stop); // immediate decrease 2780 return; 2781 } 2782 } 2783 if (_idxStoppingBlock == _idxCurrentOrder) { 2784 if (_waitForBlock || _waitForWarrant) { 2785 makeOverrunMessage(curBlkOrder); 2786 setSpeedToType(Stop); // immediate decrease 2787 return; 2788 } 2789 } 2790 2791 if (_speedUtil.secondGreaterThanFirst(entrySpeedType, currentSpeedType)) { 2792 // signal or block speed entrySpeedType is less than currentSpeedType. 2793 // Speed for this block is violated so set end speed immediately 2794 NamedBean signal = curBlkOrder.getSignal(); 2795 if (signal != null) { 2796 log.info("Train {} moved past required {} speed for signal \"{}\" at block \"{}\" on warrant {}!", 2797 getTrainName(), entrySpeedType, signal.getDisplayName(), curBlock.getDisplayName(), getDisplayName()); 2798 } else { 2799 log.info("Train {} moved past required \"{}\" speed at block \"{}\" on warrant {}!", 2800 getTrainName(), entrySpeedType, curBlock.getDisplayName(), getDisplayName()); 2801 } 2802 fireRunStatus("SignalOverrun", (signal!=null?signal.getDisplayName():curBlock.getDisplayName()), 2803 entrySpeedType); // message of speed violation 2804 setSpeedToType(entrySpeedType); // immediate decrease 2805 currentSpeedType = entrySpeedType; 2806 } 2807 } else { // at origin block and train has arrived,. ready to move 2808 if (Stop.equals(currentSpeedType)) { 2809 currentSpeedType = Normal; 2810 } 2811 } 2812 2813 if (_idxCurrentOrder < _orders.size() - 1) { 2814 lookAheadforSpeedChange(currentSpeedType, entrySpeedType); 2815 } // else at last block, forget about speed changes, return; 2816 } 2817 2818 /* 2819 * Looks for the need to reduce speed ahead. If one is found, mkes an estimate of the 2820 *distance needed to change speeds. Find the available distance available, including 2821 * the full length of the current path. If the ramp to reduce speed should begin in the 2822 * current block, calls methods to calculate the time lapse before the ramp should begin. 2823 * entrySpeedType (expected type) will be either equal to or greater than currentSpeedType 2824 * for all blocks except rhe first. 2825 */ 2826 private void lookAheadforSpeedChange(String currentSpeedType, String entrySpeedType) { 2827 clearWaitFlags(false); 2828 // look ahead for speed type slower than current type, refresh flags 2829 // entrySpeedType is the expected speed to be reached, if no speed change ahead 2830 2831 String speedType = currentSpeedType; // first slower speedType ahead 2832 int idx = _idxCurrentOrder + 1; 2833 int idxSpeedChange = -1; // idxBlockOrder where speed changes 2834 int idxContrlBlock = -1; 2835 int limit; 2836 if (_shareRoute) { 2837 limit = Math.min(_orders.size(), _idxCurrentOrder + 3); 2838 } else { 2839 limit = _orders.size(); 2840 } 2841 boolean allocate = true; 2842 int numAllocated = 0; 2843 do { 2844 TrainOrder to = getBlockOrderAt(idx).allocatePaths(this, allocate); 2845 if (log.isDebugEnabled()) { 2846 log.debug("{}: lookAheadforSpeedChange {}", getDisplayName(), to.toString()); 2847 } 2848 switch (to._cause) { 2849 case NONE: 2850 break; 2851 case WARRANT: 2852 _waitForWarrant = true; 2853 _message = to._message; 2854 idxContrlBlock = to._idxContrlBlock; 2855 idxSpeedChange = to._idxEnterBlock; 2856 speedType = Stop; 2857 break; 2858 case OCCUPY: 2859 _waitForBlock = true; 2860 _message = to._message; 2861 idxContrlBlock = to._idxContrlBlock; 2862 idxSpeedChange = to._idxEnterBlock; 2863 speedType = Stop; 2864 break; 2865 case SIGNAL: 2866 speedType = to._speedType; 2867 if (Stop.equals(speedType)) { 2868 _waitForSignal = true; 2869 } 2870 idxContrlBlock = to._idxContrlBlock; 2871 idxSpeedChange = to._idxEnterBlock; 2872 _message = to._message; 2873 break; 2874 default: 2875 log.error("{}: lookAheadforSpeedChange at block \"{}\" setPath returns: {}", 2876 getDisplayName(), getBlockAt(_idxCurrentOrder).getDisplayName(), to.toString()); 2877 _message = to._message; 2878 setSpeedToType(Stop); 2879 return; 2880 } 2881 numAllocated++; 2882 if (Stop.equals(speedType)) { 2883 break; 2884 } 2885 if (_shareRoute && numAllocated > 1 ) { 2886 allocate = false; 2887 } 2888 idx++; 2889 2890 } while ((idxSpeedChange < 0) && (idx < limit) && 2891 !_speedUtil.secondGreaterThanFirst(speedType, currentSpeedType)); 2892 2893 if (!Stop.equals(speedType)) { 2894 while ((idx < limit)) { // allocate and set paths beyond speed change 2895 TrainOrder to = getBlockOrderAt(idx).allocatePaths(this, false); 2896 if (Stop.equals(to._speedType)) { 2897 break; 2898 } 2899 idx++; 2900 } 2901 } 2902 if (idxSpeedChange < 0) { 2903 idxSpeedChange = _orders.size() - 1; 2904 } 2905 2906 float availDist = getAvailableDistance(idxSpeedChange); // distance ahead (excluding current block 2907 float changeDist = getChangeSpeedDistance(idxSpeedChange, speedType); // distance needed to change speed for speedType 2908 2909 if (_speedUtil.secondGreaterThanFirst(currentSpeedType, speedType)) { 2910 // speedType is greater than currentSpeedType. i.e. increase speed. 2911 rampSpeedTo(speedType, -1); 2912 return; 2913 } 2914 if (!currentSpeedType.equals(entrySpeedType)) { 2915 // entrySpeedType is greater than currentSpeedType. i.e. increase speed. 2916 rampSpeedTo(entrySpeedType, -1); 2917 // continue to interrupt ramp up with ramp down 2918 } 2919 2920 // set next signal after current block for aspect speed change 2921 for (int i = _idxCurrentOrder + 1; i < _orders.size(); i++) { 2922 if (setProtectingSignal(i)) { 2923 break; 2924 } 2925 } 2926 2927 OBlock block = getBlockAt(idxSpeedChange); 2928 if (log.isDebugEnabled()) { 2929 log.debug("{}: Speed \"{}\" at block \"{}\" until speed \"{}\" at block \"{}\", availDist={}, changeDist={}", 2930 getDisplayName(), currentSpeedType, getBlockAt(_idxCurrentOrder).getDisplayName(), speedType, 2931 block.getDisplayName(), availDist, changeDist); 2932 } 2933 2934 if (changeDist <= availDist) { 2935 cancelDelayRamp(); // interrupts down ramping 2936 clearWaitFlags(false); 2937 return; 2938 } 2939 2940 // Now set stopping condition of flags, if any. Not, if current block is also ahead. 2941 if (_waitForBlock) { 2942 if (!getBlockAt(_idxCurrentOrder).equals(block)) { 2943 setStoppingBlock(idxContrlBlock); 2944 } 2945 } else if (_waitForWarrant) { 2946 // if block is allocated and unoccupied, but cannot set path exit. 2947 if (_stoppingBlock == null) { 2948 setStoppingBlock(idxContrlBlock); 2949 } 2950 } 2951 2952 // Begin a ramp for speed change in this block. If due to a signal, watch that one 2953 if(_waitForSignal) { 2954 // Watch this signal. Should be the previous set signal above. 2955 // If not, then user has not configured signal system to allow room for speed changes. 2956 setProtectingSignal(idxContrlBlock); 2957 } 2958 2959 // either ramp in progress or no changes needed. Stopping conditions set, so move on. 2960 if (!_speedUtil.secondGreaterThanFirst(speedType, currentSpeedType)) { 2961 return; 2962 } 2963 2964 availDist += getAvailableDistanceAt(_idxCurrentOrder); // Add available length in this block 2965 2966 int cmdStartIdx = _speedUtil.getBlockSpeedInfo(_idxCurrentOrder).getFirstIndex(); 2967 if (!doDelayRamp(availDist, changeDist, idxSpeedChange, speedType, cmdStartIdx)) { 2968 log.warn("No room for train {} to ramp to \"{}\" from \"{}\" in block \"{}\"!. availDist={}, changeDist={} on warrant {}", 2969 getTrainName(), speedType, currentSpeedType, getBlockAt(_idxCurrentOrder).getDisplayName(), 2970 availDist, changeDist, getDisplayName()); 2971 } 2972 } 2973 2974 /* 2975 * if there is sufficient room calculate a wait time, otherwise ramp immediately. 2976 */ 2977 synchronized private boolean doDelayRamp(float availDist, float changeDist, int idxSpeedChange, String speedType, int cmdStartIdx) { 2978 String pendingSpeedType = _engineer.getSpeedType(true); // current or pending speed type 2979 if (pendingSpeedType.equals(speedType)) { 2980 return true; 2981 } 2982 if (availDist < 10) { 2983 setSpeedToType(speedType); 2984 return false; 2985 } else { 2986 SpeedState speedState = _engineer.getSpeedState(); 2987 switch (speedState) { 2988 case RAMPING_UP: 2989 makeRampWait(availDist, idxSpeedChange, speedType); 2990 break; 2991 case RAMPING_DOWN: 2992 log.error("Already ramping to \"{}\" making ramp for \"{}\".", _engineer.getSpeedType(true), speedType); 2993 //$FALL-THROUGH$ 2994 case STEADY_SPEED: 2995 //$FALL-THROUGH$ 2996 default: 2997 makeScriptWait(availDist, changeDist, idxSpeedChange, speedType, cmdStartIdx); 2998 } 2999 } 3000 return true; 3001 } 3002 3003 private void makeRampWait(float availDist, int idxSpeedChange, @Nonnull String speedType) { 3004 BlockSpeedInfo info = _speedUtil.getBlockSpeedInfo(idxSpeedChange - 1); 3005 float speedSetting = info.getExitSpeed(); 3006 float endSpeed = _speedUtil.modifySpeed(speedSetting, speedType); 3007 3008 speedSetting = _engineer.getSpeedSetting(); // current speed 3009 float prevSetting = speedSetting; 3010 String currentSpeedType = _engineer.getSpeedType(false); // current speed type 3011 3012 float changeDist = 0; 3013 if (log.isDebugEnabled()) { 3014 log.debug("{}: makeRampWait for speed change \"{}\" to \"{}\". Throttle from={}, to={}, availDist={}", 3015 getDisplayName(), currentSpeedType, speedType, speedSetting, endSpeed, availDist); 3016 // command index numbers biased by 1 3017 } 3018 float bufDist = getEntranceBufferDist(idxSpeedChange); 3019 float accumTime = 0; // accumulated time of commands up to ramp start 3020 float accumDist = 0; 3021 RampData ramp = _speedUtil.getRampForSpeedChange(speedSetting, 1.0f); 3022 int time = ramp.getRampTimeIncrement(); 3023 ListIterator<Float> iter = ramp.speedIterator(true); 3024 3025 while (iter.hasNext()) { 3026 changeDist = _speedUtil.getRampLengthForEntry(speedSetting, endSpeed) + bufDist; 3027 accumDist += _speedUtil.getDistanceOfSpeedChange(prevSetting, speedSetting, time); 3028 accumTime += time; 3029 prevSetting = speedSetting; 3030 speedSetting = iter.next(); 3031 3032 if (changeDist + accumDist >= availDist) { 3033 float curTrackSpeed = _speedUtil.getTrackSpeed(speedSetting); 3034 float remDist = changeDist + accumDist - availDist; 3035 if (curTrackSpeed > 0) { 3036 accumTime -= remDist / curTrackSpeed; 3037 } else { 3038 log.warn("{}: Cannot compute wait time for \"{}\" ramp to block \"{}\". trackSpeed= {}", getDisplayName(), 3039 speedType, getBlockAt(idxSpeedChange).getDisplayName(), curTrackSpeed); 3040 } 3041 break; 3042 } 3043 } 3044 if (changeDist < accumDist) { 3045 float curTrackSpeed = _speedUtil.getTrackSpeed(speedSetting); 3046 if (curTrackSpeed > 0) { 3047 accumTime += (availDist - changeDist) / curTrackSpeed; 3048 } else { 3049 log.warn("{}: Cannot compute wait time for \"{}\" ramp to block \"{}\". trackSpeed= {}", getDisplayName(), 3050 speedType, getBlockAt(idxSpeedChange).getDisplayName(), curTrackSpeed); 3051 } 3052 } 3053 3054 int waitTime = Math.round(accumTime); 3055 3056 if (log.isDebugEnabled()) { 3057 log.debug("{}: RAMP: Wait {}ms and travel {}mm until {}mm before entering block \"{}\" at throttle= {}. availDist={}mm", 3058 getDisplayName(), waitTime, Math.round(accumDist), Math.round(changeDist), 3059 getBlockAt(idxSpeedChange).getDisplayName(), speedSetting, Math.round(availDist)); 3060 } 3061 rampSpeedDelay(waitTime, speedType, speedSetting, idxSpeedChange); 3062 } 3063 3064 /** 3065 * Must start the ramp in current block. ( at _idxCurrentOrder) 3066 * find the time when ramp should start in this block, then use thread CommandDelay to start the ramp. 3067 * Train must travel a deltaDist for a deltaTime to the start of the ramp. 3068 * It travels at throttle settings of scriptSpeed(s) modified by currentSpeedType. 3069 * trackSpeed(s) of these modified scriptSettings are computed from SpeedProfile 3070 * waitThrottle is throttleSpeed when ramp is started. This may not be the scriptSpeed now 3071 * Start with waitThrottle (modSetting) being at the entrance to the block. 3072 * modSetting gives the current trackSpeed. 3073 * accumulate the time and distance and determine the distance (changeDist) needed for entrance into 3074 * block (at idxSpeedChange) requiring speed change to speedType 3075 * final ramp should modify waitSpeed to endSpeed and must end at the exit of end block (endBlockIdx) 3076 * 3077 * @param availDist distance available to make the ramp 3078 * @param changeDist distance needed for the rmp 3079 * @param idxSpeedChange block order index of block to complete change before entry 3080 * @param speedType speed aspect of speed change 3081 * @param cmdStartIdx command index of delay 3082 */ 3083 /** 3084 * Ratio by which scripted times are stretched when the train runs at a 3085 * modified speed: the scripted track speed over the actual track speed. 3086 * While the script has the train stopped there is nothing to stretch, so 3087 * the scripted times are used unchanged (as the Engineer does). 3088 */ 3089 private float getScriptTimeRatio(float scriptSpeed, float trackSpeed) { 3090 if (trackSpeed > 0) { 3091 return _speedUtil.getTrackSpeed(scriptSpeed) / trackSpeed; 3092 } 3093 return 1; 3094 } 3095 3096 private void makeScriptWait(float availDist, float changeDist, int idxSpeedChange, @Nonnull String speedType, int cmdStartIdx) { 3097 BlockSpeedInfo info = _speedUtil.getBlockSpeedInfo(idxSpeedChange - 1); 3098 int cmdEndIdx = info.getLastIndex(); 3099 float scriptSpeed = info.getExitSpeed(); 3100 float endSpeed = _speedUtil.modifySpeed(scriptSpeed, speedType); 3101 3102 scriptSpeed = _engineer.getScriptSpeed(); // script throttle setting 3103 float speedSetting = _engineer.getSpeedSetting(); // current speed 3104 String currentSpeedType = _engineer.getSpeedType(false); // current speed type 3105 3106 float modSetting = speedSetting; // _speedUtil.modifySpeed(scriptSpeed, currentSpeedType); 3107 float beginTrackSpeed = _speedUtil.getTrackSpeed(modSetting); // mm/sec track speed at modSetting 3108 float curTrackSpeed = beginTrackSpeed; 3109 if (_idxCurrentOrder == 0 && availDist > BUFFER_DISTANCE) { 3110 changeDist = 0; 3111 } 3112 if (log.isDebugEnabled()) { 3113 log.debug("{}: makespeedChange cmdIdx #{} to #{} at speedType \"{}\" to \"{}\". speedSetting={}, changeDist={}, availDist={}", 3114 getDisplayName(), cmdStartIdx+1, cmdEndIdx+1, currentSpeedType, speedType, speedSetting, changeDist, availDist); 3115 // command index numbers biased by 1 3116 } 3117 float accumTime = 0; // accumulated time of commands up to ramp start 3118 float accumDist = 0; 3119 Command cmd = _commands.get(cmdStartIdx).getCommand(); 3120 3121 if (cmd.equals(Command.NOOP) && beginTrackSpeed > 0) { 3122 accumTime = (availDist - changeDist) / beginTrackSpeed; 3123 } else { 3124 // time adjustment for current speed type; scripted times are used unchanged while stopped 3125 float timeRatio = getScriptTimeRatio(scriptSpeed, curTrackSpeed); 3126 float bufDist = getEntranceBufferDist(idxSpeedChange); 3127 float prevSetting = modSetting; // throttle setting at the start of each interval 3128 3129 // Each command runs its time after the previous one, so the train covers the interval 3130 // before command i at the setting from the previous speed command. 3131 for (int i = cmdStartIdx; i <= cmdEndIdx; i++) { 3132 ThrottleSetting ts = _commands.get(i); 3133 long time = (long)(ts.getTime() * timeRatio); 3134 // getDistanceOfSpeedChange takes throttle settings, not track speeds 3135 float dist = _speedUtil.getDistanceOfSpeedChange(prevSetting, modSetting, time); 3136 prevSetting = modSetting; 3137 if (changeDist + accumDist + dist >= availDist) { 3138 // the ramp must start within this interval 3139 float needDist = Math.max(0, availDist - changeDist - accumDist); 3140 if (dist > 0) { 3141 accumTime += time * needDist / dist; 3142 } 3143 break; 3144 } 3145 accumDist += dist; 3146 accumTime += time; 3147 cmd = ts.getCommand(); 3148 if (cmd.equals(Command.SPEED)) { 3149 CommandValue cmdVal = ts.getValue(); 3150 scriptSpeed = cmdVal.getFloat(); 3151 modSetting = _speedUtil.modifySpeed(scriptSpeed, currentSpeedType); 3152 curTrackSpeed = _speedUtil.getTrackSpeed(modSetting); 3153 changeDist = _speedUtil.getRampLengthForEntry(modSetting, endSpeed) + bufDist; 3154 timeRatio = getScriptTimeRatio(scriptSpeed, curTrackSpeed); 3155 } 3156 3157 if (log.isDebugEnabled()) { 3158 log.debug("{}: cmd#{} accumTime= {} accumDist= {} changeDist= {}, throttle= {}", 3159 getDisplayName(), i+1, accumTime, accumDist, changeDist, modSetting); 3160 } 3161 if (changeDist + accumDist >= availDist) { 3162 // the ramp from the new speed needs more distance: start it as this command runs 3163 break; 3164 } 3165 if (cmd.equals(Command.NOOP)) { 3166 // speed change is supposed to start in current block 3167 // start ramp in next block? 3168 float remDist = availDist - changeDist - accumDist; 3169 log.warn("{}: script unfit cmd#{}. Cannot compute wait time for \"{}\" ramp to block \"{}\". remDist= {}", 3170 getDisplayName(), i+1, speedType, getBlockAt(idxSpeedChange).getDisplayName(), remDist); 3171 accumTime -= _speedUtil.getTimeForDistance(modSetting, bufDist); 3172 break; 3173 } 3174 } 3175 } 3176 3177 int waitTime = Math.round(accumTime); 3178 3179 if (log.isDebugEnabled()) { 3180 log.debug("{}: RAMP: Wait {}ms and travel {}mm until {}mm before entering block \"{}\" at throttle= {}. availDist={}mm", 3181 getDisplayName(), waitTime, Math.round(accumDist), Math.round(changeDist), 3182 getBlockAt(idxSpeedChange).getDisplayName(), modSetting, Math.round(availDist)); 3183 } 3184 3185 rampSpeedDelay(waitTime, speedType, modSetting, idxSpeedChange); 3186 } 3187 3188 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 3189 synchronized private void rampSpeedDelay (long waitTime, String speedType, float waitSpeed, int idxSpeedChange) { 3190 int endBlockIdx = idxSpeedChange - 1; 3191 waitTime -= 50; // Subtract a bit 3192 if( waitTime < 0) { 3193 rampSpeedTo(speedType, endBlockIdx); // do it now on this thread. 3194 return; 3195 } 3196 String reason; 3197 if(_waitForSignal) { 3198 reason = Bundle.getMessage("Signal"); 3199 } else if (_waitForWarrant) { 3200 reason = Bundle.getMessage("Warrant"); 3201 } else if (_waitForBlock) { 3202 reason = Bundle.getMessage("Occupancy"); 3203 } else { 3204 reason = Bundle.getMessage("Signal"); 3205 } 3206 3207 if (_trace || log.isDebugEnabled()) { 3208 if (log.isDebugEnabled()) { 3209 log.info("Train \"{}\" needs speed decrease to \"{}\" from \"{}\" for {} before entering block \"{}\"", 3210 getTrainName(), speedType, _engineer.getSpeedType(true), reason, getBlockAt(idxSpeedChange).getDisplayName()); 3211 } 3212 } 3213 if (_delayCommand != null) { 3214 if (_delayCommand.isDuplicate(speedType, waitTime, endBlockIdx)) { 3215 return; 3216 } 3217 cancelDelayRamp(); 3218 } 3219 _delayCommand = new CommandDelay(speedType, waitTime, waitSpeed, endBlockIdx); 3220 _delayCommand.start(); 3221 if (log.isDebugEnabled()) { 3222 log.debug("{}: CommandDelay: will wait {}ms, then Ramp to {} in block {}.", 3223 getDisplayName(), waitTime, speedType, getBlockAt(endBlockIdx).getDisplayName()); 3224 } 3225 String blkName = getBlockAt(endBlockIdx).getDisplayName(); 3226 if (_trace || log.isDebugEnabled()) { 3227 log.info(Bundle.getMessage("RampBegin", getTrainName(), reason, blkName, speedType, waitTime)); 3228 } 3229 } 3230 3231 protected void downRampBegun(int endBlockIdx) { 3232 OBlock block = getBlockAt(endBlockIdx + 1); 3233 if (block != null) { 3234 _rampBlkOccupied = block.isOccupied(); 3235 } else { 3236 _rampBlkOccupied = true; 3237 } 3238 } 3239 3240 protected void downRampDone(boolean stop, boolean halted, String speedType, int endBlockIdx) { 3241 if (_idxCurrentOrder < endBlockIdx) { 3242 return; // overrun not possible. 3243 } 3244 // look for overruns 3245 int nextIdx = endBlockIdx + 1; 3246 if (nextIdx > 0 && nextIdx < _orders.size()) { 3247 BlockOrder bo = getBlockOrderAt(nextIdx); 3248 OBlock block = bo.getBlock(); 3249 if (block.isOccupied() && !_rampBlkOccupied) { 3250 // Occupied now, but not occupied by another train at start of ramp. 3251 if (!checkForOverrun(block) ) { // Not us. check if something should have us wait 3252 Warrant w = block.getWarrant(); 3253 _overrun = true; // endBlock occupied during ramp down. Speed overrun! 3254 if (w != null && !w.equals(this)) { // probably redundant 3255 _waitForWarrant = true; 3256 setStoppingBlock(nextIdx); 3257 } else if (Stop.equals(BlockOrder.getPermissibleSpeedAt(bo))) { // probably redundant 3258 _waitForSignal = true; 3259 setProtectingSignal(nextIdx); 3260 } else { 3261 _waitForBlock = true; 3262 } 3263 } 3264 makeOverrunMessage(bo); 3265 } // case where occupied at start of ramp is indeterminate 3266 } 3267 } 3268 3269 @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption") 3270 private void makeOverrunMessage(BlockOrder curBlkOrder) { 3271 OBlock curBlock = curBlkOrder.getBlock(); 3272 String name = null; 3273 if (_waitForSignal) { 3274 NamedBean signal = curBlkOrder.getSignal(); 3275 if (signal!=null) { 3276 name = signal.getDisplayName(); 3277 } else { 3278 name = curBlock.getDisplayName(); 3279 } 3280 _overrun = true; 3281 String entrySpeedType = BlockOrder.getPermissibleSpeedAt(curBlkOrder); // expected speed type for this block 3282 log.info(Bundle.getMessage("SignalOverrun", getTrainName(), entrySpeedType, name)); 3283 fireRunStatus("SignalOverrun", name, entrySpeedType); // message of speed violation 3284 return; 3285 } 3286 String bundleKey = null; 3287 if (_waitForWarrant) { 3288 bundleKey = PROPERTY_WARRANT_OVERRUN; 3289 Warrant w = curBlock.getWarrant(); 3290 if (w != null) { 3291 name = w.getDisplayName(); 3292 } 3293 } else if (_waitForBlock){ 3294 bundleKey = PROPERTY_OCCUPY_OVERRUN; 3295 name = (String)curBlock.getValue(); 3296 } 3297 if (name == null) { 3298 name = Bundle.getMessage("unknownTrain"); 3299 } 3300 if (bundleKey != null) { 3301 _overrun = true; 3302 log.info(Bundle.getMessage(bundleKey, getTrainName(), curBlock.getDisplayName(), name)); 3303 fireRunStatus(bundleKey, curBlock.getDisplayName(), name); // message of speed violation 3304 } else { 3305 log.error("Train \"{}\" entered stopping block \"{}\" for unknown reason on warrant {}!", 3306 getTrainName(), curBlock.getDisplayName(), getDisplayName()); 3307 } 3308 } 3309 3310 /** 3311 * {@inheritDoc} 3312 * <p> 3313 * This implementation tests that 3314 * {@link jmri.NamedBean#getSystemName()} 3315 * is equal for this and obj. 3316 * To allow a warrant to run with sections, DccLocoAddress is included to test equality 3317 * 3318 * @param obj the reference object with which to compare. 3319 * @return {@code true} if this object is the same as the obj argument; 3320 * {@code false} otherwise. 3321 */ 3322 @Override 3323 public boolean equals(Object obj) { 3324 if (obj == null) return false; // by contract 3325 3326 if (obj instanceof Warrant) { // NamedBeans are not equal to things of other types 3327 Warrant b = (Warrant) obj; 3328 DccLocoAddress addr = this._speedUtil.getDccAddress(); 3329 if (addr == null) { 3330 if (b._speedUtil.getDccAddress() != null) { 3331 return false; 3332 } 3333 return (this.getSystemName().equals(b.getSystemName())); 3334 } 3335 return (this.getSystemName().equals(b.getSystemName()) && addr.equals(b._speedUtil.getDccAddress())); 3336 } 3337 return false; 3338 } 3339 3340 /** 3341 * {@inheritDoc} 3342 * 3343 * @return hash code value is based on the system name and DccLocoAddress. 3344 */ 3345 @Override 3346 public int hashCode() { 3347 return (getSystemName().concat(_speedUtil.getDccAddress().toString())).hashCode(); 3348 } 3349 3350 @Override 3351 public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) { 3352 List<NamedBeanUsageReport> report = new ArrayList<>(); 3353 if (bean != null) { 3354 if (bean.equals(getBlockingWarrant())) { 3355 report.add(new NamedBeanUsageReport("WarrantBlocking")); 3356 } 3357 getBlockOrders().forEach((blockOrder) -> { 3358 if (bean.equals(blockOrder.getBlock())) { 3359 report.add(new NamedBeanUsageReport("WarrantBlock")); 3360 } 3361 if (bean.equals(blockOrder.getSignal())) { 3362 report.add(new NamedBeanUsageReport("WarrantSignal")); 3363 } 3364 }); 3365 } 3366 return report; 3367 } 3368 3369 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Warrant.class); 3370}