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