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