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