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