001package jmri.jmrit.dispatcher; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import java.beans.PropertyChangeEvent; 005import java.beans.PropertyChangeListener; 006import java.util.LinkedList; 007 008import javax.annotation.CheckForNull; 009 010import jmri.*; 011import jmri.implementation.SignalSpeedMap; 012import jmri.jmrit.dispatcher.ActiveTrain.TrainDetection; 013import jmri.jmrit.roster.RosterEntry; 014import jmri.util.swing.JmriJOptionPane; 015 016/** 017 * This class holds information and options for an ActiveTrain when it is 018 * running in AUTOMATIC mode. It is an extension to Active Train for automatic 019 * running. 020 * <p> 021 * This class implements logic that follows a train around a layout. Train 022 * follows signals, provided the next Section is allocated to it, and its 023 * ActiveTrain's status is RUNNING. 024 * <p> 025 * This class is linked via its parent ActiveTrain object. 026 * <p> 027 * This file is part of JMRI. 028 * <p> 029 * JMRI is open source software; you can redistribute it and/or modify it under 030 * the terms of version 2 of the GNU General Public License as published by the 031 * Free Software Foundation. See the "COPYING" file for a copy of this license. 032 * <p> 033 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 034 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 035 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 036 * <p> 037 * The AutoEngineer sub class is based in part on code by Pete Cressman 038 * contained in Warrants.java 039 * 040 * @author Dave Duchamp Copyright (C) 2010-2011 041 */ 042public class AutoActiveTrain implements ThrottleListener { 043 044 /** 045 * Create an AutoActiveTrain. 046 * 047 * @param at the train to automate 048 */ 049 public AutoActiveTrain(ActiveTrain at) { 050 _activeTrain = at; 051 at.setAutoActiveTrain(this); 052 _autoTrainAction = new AutoTrainAction(this); 053 _lbManager = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class); 054 // listen for additions in our allocated section table 055 at.addPropertyChangeListener("sectionallocated",this::handleAnotherSectionAllocatedChange); 056 } 057 058 /* Speed aspects as defined by Douglas A. Kerr - "Rail Signal Aspects and Indications" 059 * http://dougkerr.net/Pumpkin/articles/Rail_signal_aspects.pdf (from Pete Cressman) 060 */ 061// public static final int SPEED_MASK = 0x07; // least significant 3 bits 062 public static final int STOP_SPEED = 0x01; // No Speed 063 public static final int RESTRICTED_SPEED = 0x02; // Train able to stop within 1/2 visual range (10mph) 064 public static final int SLOW_SPEED = 0x03; // Typically 15 mph (25% of NORMAL) 065 public static final int MEDIUM_SPEED = 0x04; // Typically 30 mph (40% of NORMAL) 066 public static final int LIMITED_SPEED = 0x05; // Typically 40-45 mph (65% of NORMAL) 067 public static final int NORMAL_SPEED = 0x06; // Varies with road and location 068 public static final int MAXIMUM_SPEED = 0x07; // "full" throttle 069 070 private final Float[] _speedRatio = {-1.0F, 0.0F, 0.25F, 0.35F, 0.50F, 0.65F, 0.8F, 1.15F}; 071 072 /* The ramp rates below are in addition to what the decoder itself does 073 */ 074 public static final int RAMP_NONE = 0x00; // No ramping - set speed immediately 075 public static final int RAMP_FAST = 0x01; // Fast ramping 076 public static final int RAMP_MEDIUM = 0x02; // Medium ramping 077 public static final int RAMP_MED_SLOW = 0x03; // Medium/slow ramping 078 public static final int RAMP_SLOW = 0x04; // Slow ramping 079 public static final int RAMP_SPEEDPROFILE = 0x05; // use speed profile and section distance 080 081 /* Stop tasks codes 082 */ 083 public static final int NO_TASK = 0x00; // No task at stop 084 public static final int END_REVERSAL = 0x01; // Handle reversing direction at end for back and forth running 085 public static final int BEGINNING_RESET = 0x02; // Handle reseting beginning for back and forth running 086 public static final int END_TRAIN = 0x04; // Ending Transit. 087 088 // operational instance variables 089 private static final NamedBean.DisplayOptions USERSYS = NamedBean.DisplayOptions.USERNAME_SYSTEMNAME; 090 private ActiveTrain _activeTrain = null; 091 private AutoTrainAction _autoTrainAction = null; 092 private DccThrottle _throttle = null; 093 private AutoEngineer _autoEngineer = null; 094 private int _address = -1; 095 private int _savedStatus = ActiveTrain.RUNNING; 096 private int _currentRampRate = RAMP_NONE; // current Ramp Rate 097 private boolean _pausingActive = false; // true if train pausing thread is active 098 private DispatcherFrame _dispatcher; 099 100 // persistent instance variables (saved with train info) 101 private int _rampRate = RAMP_NONE; // default Ramp Rate 102 private float _speedFactor = 1.0f; // default speed factor 103 private float _maxSpeed = 1.0f; // default maximum train speed 104 private float _minReliableOperatingSpeed = 0.0f; 105 private boolean _runInReverse = false; // true if the locomotive should run through Transit in reverse 106 private boolean _soundDecoder = false; // true if locomotive has a sound decoder 107 private long _MaxTrainLength = 600; // default train length mm. 108 private float _stopBySpeedProfileAdjust = 1.0f; 109 private boolean _stopBySpeedProfile = false; 110 private boolean _useSpeedProfileRequested = true; 111 private int _functionLight = 0; 112 private int _functionBell = 1; 113 private int _functionHorn = 2; 114 115 // accessor functions 116 public ActiveTrain getActiveTrain() { 117 return _activeTrain; 118 } 119 120 public AutoEngineer getAutoEngineer() { 121 return _autoEngineer; 122 } 123 124 public AutoTrainAction getAutoTrainAction() { 125 return _autoTrainAction; 126 } 127 128 public RosterEntry getRosterEntry() { 129 return re; 130 } 131 132 public boolean getForward() { 133 return _autoEngineer.getIsForward(); 134 } 135 136 public void setForward(boolean set) { 137 _autoEngineer.setIsForward(set); 138 } 139 140 public synchronized float getTargetSpeed() { 141 return _autoEngineer.getTargetSpeed(); 142 } 143 144 public synchronized void setTargetSpeedByPass(float speed) { 145 _autoEngineer.setTargetSpeed(-1.0f, speed); 146 } 147 148 public synchronized void setTargetSpeedByPass(float distance, float speed) { 149 if (distance < 0.0f) { 150 _autoEngineer.setTargetSpeed(speed); 151 } else { 152 _autoEngineer.setTargetSpeed(distance, speed); 153 } 154 } 155 156 public synchronized void setTargetSpeed(float speed) { 157 if (_autoEngineer.isStopped() && getTargetSpeed() == 0.0f && speed > 0.0f) { 158 if (_autoTrainAction.isDelayedStart(-1.0f, speed)) { 159 return; 160 } 161 } 162 _autoEngineer.setTargetSpeed(speed); 163 } 164 165 public synchronized void setTargetSpeed(float distance, float speed) { 166 if (_autoEngineer.isStopped() && getTargetSpeed() == 0.0f && speed > 0.0f) { 167 if (_autoTrainAction.isDelayedStart(distance, speed)) { 168 return; 169 } 170 } 171 _autoEngineer.setTargetSpeed(distance, speed); 172 } 173 174 public int getSavedStatus() { 175 return _savedStatus; 176 } 177 178 public void setSavedStatus(int status) { 179 _savedStatus = status; 180 } 181 182 public synchronized void setCurrentRampRate(int rate) { 183 _currentRampRate = rate; 184 } 185 186 public int getRampRate() { 187 return _rampRate; 188 } 189 190 public void setRampRate(int rate) { 191 _rampRate = rate; 192 _currentRampRate = rate; 193 } 194 195 public float getSpeedFactor() { 196 return _speedFactor; 197 } 198 199 public void setSpeedFactor(float factor) { 200 _speedFactor = factor; 201 } 202 203 public float getMaxSpeed() { 204 return _maxSpeed; 205 } 206 207 public void setMaxSpeed(float speed) { 208 _maxSpeed = speed; 209 } 210 211 /** 212 * gets the lowest speed as a percentage of throttle that the loco reliably operates. 213 * @return percentage throttle 214 */ 215 public float getMinReliableOperatingSpeed() { 216 return _minReliableOperatingSpeed; 217 } 218 219 /** 220 * Sets the lowest speed as a percentage of throttle that the loco reliably operates. 221 * @param speed percentage of throttle. 222 */ 223 public void setMinReliableOperatingSpeed(float speed) { 224 _minReliableOperatingSpeed = speed; 225 } 226 227/** 228 * @deprecated Use {@code ActiveTrain.setTrainDetection(TrainDetection value } insteadUse 229 * @param set True if entire train is detectable 230 */ 231 @Deprecated (since="5.7.6",forRemoval=true) 232 public void setResistanceWheels(boolean set) { 233 if (set) { 234 _activeTrain.setTrainDetection(TrainDetection.TRAINDETECTION_WHOLETRAIN); 235 } else { 236 _activeTrain.setTrainDetection(TrainDetection.TRAINDETECTION_HEADONLY); 237 } 238 } 239 240 public boolean getRunInReverse() { 241 return _runInReverse; 242 } 243 244 public void setRunInReverse(boolean set) { 245 _runInReverse = set; 246 } 247 248 public boolean getSoundDecoder() { 249 return _soundDecoder; 250 } 251 252 public void setSoundDecoder(boolean set) { 253 _soundDecoder = set; 254 } 255 256 /** 257 * 258 * @return train length in MM. 259 */ 260 public long getMaxTrainLengthMM() { 261 return _MaxTrainLength; 262 } 263 264 /** 265 * Set Train length in Scale Meters 266 * @param length length of train in meterd 267 * @param scaleFactor as supplied by scale object 268 */ 269 public void setMaxTrainLength(double length, double scaleFactor) { 270 _MaxTrainLength = (long) (length * 1000.0 * scaleFactor); 271 log.trace("setMaxTrainLength[{}]",_MaxTrainLength); 272 } 273 274 public void setUseSpeedProfile(boolean tf) { 275 _useSpeedProfileRequested = tf; 276 } 277 278 public boolean getUseSpeedProfile() { 279 return _useSpeedProfileRequested; 280 } 281 282 public void setStopBySpeedProfile(boolean tf) { 283 _stopBySpeedProfile = tf; 284 } 285 286 public void setStopBySpeedProfileAdjust(float adjust) { 287 _stopBySpeedProfileAdjust = adjust; 288 } 289 290 public boolean getStopBySpeedProfile() { 291 return _stopBySpeedProfile; 292 } 293 294 public float getStopBySpeedProfileAdjust() { 295 return _stopBySpeedProfileAdjust; 296 } 297 /** 298 * Set the F-Number for the light 299 * @param value F-Number 300 */ 301 public void setFunctionLight(int value) { 302 _functionLight = value; 303 } 304 /** 305 * Returns the F-Number for the light. 306 * @return F-Number 307 */ 308 public int getFunctionLight() { 309 return _functionLight; 310 } 311 /** 312 * Set the F-Number for the Bell 313 * @param value F-Number 314 */ 315 public void setFunctionBell(int value) { 316 _functionBell = value; 317 } 318 /** 319 * Returns the F-Number for the Bell. 320 * @return F-Number 321 */ 322 public int getFunctionBell() { 323 return _functionBell; 324 } 325 /** 326 * Set the F-Number for the Horn 327 * @param value F-Number 328 */ 329 public void setFunctionHorn(int value) { 330 _functionHorn = value; 331 } 332 /** 333 * Returns the F-Number for the Horn. 334 * @return F-Number 335 */ 336 public int getFunctionHorn() { 337 return _functionHorn; 338 } 339 340 /** 341 * Get current Signal DisplayName. 342 * @return empty String if no signal, otherwise Display Name. 343 */ 344 public String getCurrentSignal() { 345 if (_activeTrain.getSignalType() == DispatcherFrame.SIGNALHEAD) { 346 return (_controllingSignal == null ) ? "" : _controllingSignal.getDisplayName() ; 347 } else { 348 return (_controllingSignalMast == null ) ? "" : _controllingSignalMast.getDisplayName(); 349 } 350 } 351 352 /** 353 * Get current Signal UserName. 354 * @return empty String if no signal, otherwise UserName. 355 */ 356 public String getCurrentSignalUserName() { 357 if (_activeTrain.getSignalType() == DispatcherFrame.SIGNALHEAD) { 358 return ( _controllingSignal == null || _controllingSignal.getUserName() == null) ? "" : _controllingSignal.getUserName(); 359 } else { 360 return ( _controllingSignalMast == null || _controllingSignalMast.getUserName() == null) ? "" : _controllingSignalMast.getUserName(); } 361 } 362 363 private RosterEntry re = null; 364 boolean useSpeedProfile = false; 365 366 /** 367 * Initialize new Auto Active Train or get a new throttle after WORKING Sets 368 * up the DCC address and initiates creation of a throttle to run the train. 369 * 370 * @return true if initialized; false otherwise 371 */ 372 public boolean initialize() { 373 //clear all flags 374 _pausingActive = false; 375 _stoppingBySensor = false; 376 _stoppingByBlockOccupancy = false; 377 _stoppingUsingSpeedProfile = false; 378 // get the dispatcher 379 _dispatcher = InstanceManager.getDefault(DispatcherFrame.class); 380 381 // get decoder address 382 try { 383 _address = Integer.parseInt(_activeTrain.getDccAddress()); 384 } catch (NumberFormatException ex) { 385 log.warn("invalid dcc address '{}' for {}", _activeTrain.getDccAddress(), _activeTrain.getTrainName()); 386 return false; 387 } 388 if ((_address < 1) || (_address > 9999)) { 389 log.warn("invalid dcc address '{}' for {}", _activeTrain.getDccAddress(), _activeTrain.getTrainName()); 390 return false; 391 } 392 // request a throttle for automatic operation, throttle returned via callback below 393 useSpeedProfile = false; 394 boolean ok; 395 DccLocoAddress addressForRequest = new DccLocoAddress( 396 _address,!InstanceManager.throttleManagerInstance().canBeShortAddress(_address)); 397 if (_activeTrain.getTrainSource() == ActiveTrain.ROSTER) { 398 if (_activeTrain.getRosterEntry() != null) { 399 re = _activeTrain.getRosterEntry(); 400 ok = InstanceManager.throttleManagerInstance().requestThrottle(re, this, false); 401 if (_useSpeedProfileRequested) { 402 if (re.getSpeedProfile() != null && re.getSpeedProfile().getProfileSize() > 0) { 403 useSpeedProfile = true; 404 } 405 } 406 log.debug("{}: requested roster entry '{}', address={}, use speed profile requested={} usespeedprofile set={}", 407 _activeTrain.getTrainName(), re.getId(), _address, _useSpeedProfileRequested, useSpeedProfile); 408 } else { 409 ok = InstanceManager.throttleManagerInstance().requestThrottle(addressForRequest, this, false); 410 log.debug("{}: requested throttle address={}, roster entry not found", _activeTrain.getTrainName(), _address); 411 } 412 } else { 413 ok = InstanceManager.throttleManagerInstance().requestThrottle(addressForRequest, this, false); 414 log.debug("{}: requested throttle address={}", _activeTrain.getTrainName(), _address); 415 } 416 if (!ok) { 417 log.warn("Throttle for locomotive address {} could not be setup.", _address); 418 _activeTrain.setMode(ActiveTrain.DISPATCHED); 419 return false; 420 } 421 return true; 422 } 423 424 // Throttle feedback method - Initiates running AutoEngineer with the new throttle 425 @Override 426 public void notifyThrottleFound(DccThrottle t) { 427 _throttle = t; 428 if (_throttle == null) { 429 JmriJOptionPane.showMessageDialog(null, java.text.MessageFormat.format(Bundle.getMessage( 430 "Error28"), new Object[]{_activeTrain.getTrainName()}), Bundle.getMessage("MessageTitle"), 431 JmriJOptionPane.INFORMATION_MESSAGE); 432 log.warn("null throttle returned for train '{}' during automatic initialization.", _activeTrain.getTrainName()); 433 _activeTrain.setMode(ActiveTrain.DISPATCHED); 434 return; 435 } 436 log.debug("{}: New AutoEngineer, address={}, length (mm)={}, factor={}, useSpeedProfile={}", 437 _activeTrain.getTrainName(), 438 _throttle.getLocoAddress(), 439 getMaxTrainLengthMM(), _speedFactor, useSpeedProfile); 440 // get off this thread ASAP, some throttles does not completely initialize 441 // until this thread finishes 442 jmri.util.ThreadingUtil.runOnLayoutDelayed(() -> { 443 if (_autoEngineer != null) { 444 log.error("Second Trottle for same loco[{}] - ignoring", _address); 445 // at least make sure its going the right way... 446 setEngineDirection(); 447 } else { 448 _autoEngineer = new AutoEngineer(t, re); 449 _activeTrain.setMode(ActiveTrain.AUTOMATIC); 450 // set initial direction 451 setEngineDirection(); 452 _autoEngineer.setRamping(_currentRampRate, _dispatcher.getFullRampTime(), 453 _dispatcher.getMinThrottleInterval(), _currentRampRate); 454 _autoEngineer.setSpeedLimits(_minReliableOperatingSpeed, _maxSpeed, _speedFactor); 455 } 456 if (_resumingAutomatic) { 457 _resumingAutomatic = false; 458 _activeTrain.setStatus(ActiveTrain.RUNNING); 459 setupNewCurrentSignal(null, true); 460 // if no current signal use saved. 461 if (!isCurrentSignal()) { 462 restoreSavedSpeedAndDirection(); 463 } else { 464 setSpeedBySignal(); 465 } 466 } else if (_dispatcher.getAutoAllocate()) { 467 // starting for the first time with automatic allocation of 468 // Sections 469 // the last of 2 threads must call setSpeedBySignal 470 // if the other thread is incomplete _currentAllocated Section 471 // will be null 472 if (_currentAllocatedSection != null) { 473 setSpeedBySignal(); 474 } 475 } 476 }, 500); 477 } 478 479 protected DccThrottle getThrottle() { 480 return _throttle; 481 } 482 483 @Override 484 public void notifyFailedThrottleRequest(jmri.LocoAddress address, String reason) { 485 log.error("Throttle request failed for {} because {}", address, reason); 486 } 487 488 /** 489 * No steal or share decisions made locally 490 * <p> 491 * {@inheritDoc} 492 */ 493 @Override 494 public void notifyDecisionRequired(jmri.LocoAddress address, DecisionType question) { 495 } 496 497 // more operational variables 498 // private final ArrayList<AllocatedSection> _allocatedSectionList = new ArrayList<>(); 499 private jmri.jmrit.display.layoutEditor.LayoutBlockManager _lbManager = null; 500 private AllocatedSection _lastAllocatedSection = null; 501 502 protected Section getLastAllocatedSection() { 503 Section as = _activeTrain.getLastAllocatedSection(); 504 return as; 505 } 506 507 private boolean _initialized = false; 508 private Section _nextSection = null; // train has not reached this Section yet 509 private volatile AllocatedSection _currentAllocatedSection = null; // head of the train is in this Section 510 private volatile AllocatedSection _previousAllocatedSection = null; // previous Section - part of train could still be in this section 511 private SignalHead _controllingSignal = null; 512 private SignalMast _controllingSignalMast = null; 513 private SignalHead _controllingSignalPrev = null; 514 private SignalMast _controllingSignalMastPrev = null; 515 private PropertyChangeListener _conSignalListener = null; 516 private PropertyChangeListener _conSignalMastListener = null; 517 private Block _conSignalProtectedBlock = null; 518 private volatile Block _currentBlock = null; 519 private Block _nextBlock = null; 520 private volatile Block _previousBlock = null; 521 private boolean _stoppingBySensor = false; 522 private Sensor _stopSensor = null; 523 private PropertyChangeListener _stopSensorListener = null; 524 private PropertyChangeListener _turnoutStateListener = null; 525 private boolean _stoppingByBlockOccupancy = false; // if true, stop when _stoppingBlock goes UNOCCUPIED 526 private boolean _stoppingUsingSpeedProfile = false; // if true, using the speed profile against the roster entry to bring the loco to a stop in a specific distance 527 private volatile Block _stoppingBlock = null; 528 private boolean _resumingAutomatic = false; // if true, resuming automatic mode after WORKING session 529 private boolean _needSetSpeed = false; // if true, train will set speed according to signal instead of stopping 530 private boolean waitingOnAllocation = false; //if true the train was stopped due to next section not allocated 531 // keeps track of and restores previous speed 532 private float _savedSpeed = 0.0f; 533 private boolean _savedForward = true; 534 535 public void set_useStopSensor(boolean _useStopSensor) { 536 this._useStopSensor = _useStopSensor; 537 } 538 539 private boolean _useStopSensor = true; //used by DispatcherSystem to override use of stop sensor 540 541 542 protected void saveSpeedAndDirection() { 543 _savedSpeed = _autoEngineer.getTargetSpeed(); 544 _savedForward = _autoEngineer.getIsForward(); 545 } 546 547 protected void restoreSavedSpeedAndDirection() { 548 _autoEngineer.setTargetSpeed(_savedSpeed); 549 _autoEngineer.setIsForward(_savedForward); 550 } 551 552 // keeps track of number of horn execution threads that are active 553 private int _activeHornThreads = 0; 554 555 protected void decrementHornExecution() { 556 _activeHornThreads--; 557 } 558 559 protected void incrementHornExecution() { 560 _activeHornThreads++; 561 } 562 563 // 564 // Notification methods 565 // 566 /** 567 * Handle notification of changes in section state. 568 * 569 * @param as the allocated that changed 570 */ 571 protected void handleSectionStateChange(AllocatedSection as) { 572 if (!_activeTrain.isInAllocatedList(as)) { 573 addAllocatedSection(as); 574 } 575 } 576 577 /** 578 * Handle notification of allocation added to the ActiveTrain allocatedsections table. 579 * Subtly different from change in a sections status. 580 * 581 * @param evt the allocation that changed 582 */ 583 private void handleAnotherSectionAllocatedChange( PropertyChangeEvent evt) { 584 if (waitingOnAllocation || _activeTrain.getSignalType() == DispatcherFrame.SECTIONSALLOCATED) { 585 waitingOnAllocation = false; 586 setSpeedBySignal(); 587 } 588 } 589 590 /** 591 * Handle notification of changes in section occupancy. 592 * 593 * @param as the section that changed 594 */ 595 protected void handleSectionOccupancyChange(AllocatedSection as) { 596 if (!_activeTrain.isInAllocatedList(as)) { 597 log.debug("Unexpected occupancy change notification - Section {}", as.getSection().getDisplayName(USERSYS)); 598 return; 599 } 600 if (as.getSection().getOccupancy() == Section.OCCUPIED) { 601 // Section changed to OCCUPIED - process if expected next Section 602 if (as.getSection() == _nextSection) { 603 setNewCurrentSection(as); 604 } 605 } else if (as.getSection().getOccupancy() == Section.UNOCCUPIED) { 606 jmri.TransitSection ts = as.getTransitSection(); 607 if (ts != null) { 608 _autoTrainAction.removeTransitSection(ts); 609 } 610 } 611 } 612 613 @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC", 614 justification = "OK to not sync here, no conflict expected") 615 protected void handleBlockStateChange(AllocatedSection as, Block b) { 616 //Block oldPreviousBlock = _previousBlock; 617 if (b.getState() == Block.OCCUPIED) { 618 // Block changed to OCCUPIED - train has entered this block 619 log.debug("{}: handleBlockStateChange to OCCUPIED section {}, block {}, length {}", _activeTrain.getTrainName(), 620 as.getSection().getDisplayName(USERSYS), 621 b.getDisplayName(USERSYS), getBlockLength(b)); 622 if (b == _nextBlock || _nextBlock == null) { 623 _currentBlock = b; 624 // defer setting the next/previous blocks until we know if its required and in what fashion 625 // for stopping blocks that action happens after the train has stopped. 626 // first check for entering the end point 627 if (!_activeTrain.isTransitReversed() && as.getSequence() == _activeTrain.getEndBlockSectionSequenceNumber()) { 628 // are we going to reverse at end 629 if ( _activeTrain.getReverseAtEnd() ) { 630 removeCurrentSignal(); 631 stopInCurrentSection(END_REVERSAL); 632 } 633 // are we going continuously without delay 634 else if ( _activeTrain.getResetWhenDone() && _activeTrain.getDelayedRestart() == ActiveTrain.NODELAY) { 635 _activeTrain.setRestart(_activeTrain.getDelayedRestart(),_activeTrain.getRestartDelay(), 636 _activeTrain.getRestartSensor(),_activeTrain.getResetRestartSensor()); 637 _activeTrain.setTransitReversed(false); 638 _activeTrain.resetAllAllocatedSections(); 639 _previousBlock = null; 640 _nextBlock = getNextBlock(_currentBlock, _currentAllocatedSection); 641 setEngineDirection(); 642 if ((_nextSection != null) && !_activeTrain.isInAllocatedList(_nextSection)) { 643 // we need to get a next section 644 _dispatcher.queueScanOfAllocationRequests(); 645 // and then set the signal 646 } 647 // can be mid block 648 setupNewCurrentSignal(null, true); 649 setSpeedBySignal(); 650 } 651 // are we restarting later 652 else if ( _activeTrain.getResetWhenDone()) { 653 // We enter this code for each block in the section. 654 // If we stop in the farthest block eg Block 3 in a 3 Block Section 655 // nothing special is required when starting. 656 // If we stop in Block 1 of a 3 block section, and enter this code 657 // when starting off again, so its just an advance of the _nextBlock. 658 // we can tell which situation it is by looking 659 // whether the _nextSection is not null and allocated to us. 660 if ( _nextSection == null || !_activeTrain.isInAllocatedList(_nextSection)) { 661 removeCurrentSignal(); 662 _nextBlock = getNextBlock(_currentBlock, _currentAllocatedSection); 663 stopInCurrentSection(BEGINNING_RESET); 664 } else { 665 _nextBlock = getNextBlock(_currentBlock, _currentAllocatedSection); 666 } 667 } 668 // else we are ending here 669 else { 670 log.debug("{}: Trip end, stop in Current Section, Block= {}", _activeTrain.getTrainName(), b.getDisplayName(USERSYS)); 671 removeCurrentSignal(); 672 stopInCurrentSection(END_TRAIN); 673 } 674 } 675 // are we entering the start point 676 else if (_activeTrain.isTransitReversed() && as.getSequence() == _activeTrain.getStartBlockSectionSequenceNumber()) { 677 // are we coming back from a reverse and running continiuosly 678 if ( _activeTrain.getResetWhenDone() && _activeTrain.isTransitReversed() ) { 679 removeCurrentSignal(); 680 stopInCurrentSection(BEGINNING_RESET); 681 } 682 // else we are ending here 683 else { 684 log.debug("{}: Trip end, stop in Current Section, Block= {}", _activeTrain.getTrainName(), b.getDisplayName(USERSYS)); 685 removeCurrentSignal(); 686 stopInCurrentSection(END_TRAIN); 687 } 688 } else { 689 // if we are not in first and not in last get the next block 690 //_previousBlock = oldPreviousBlock; 691 _nextBlock = getNextBlock(b, as); 692 if (_nextBlock != null) { 693 // this is a normal block/block change 694 // set the blocks as normal 695 _previousBlock = _currentBlock; 696 _nextBlock = getNextBlock(b, as); 697 //if (_nextBlock.getState() == Block.OCCUPIED) { 698 // handleBlockStateChange(as, _nextBlock); 699 //} 700 setupNewCurrentSignal(as, false); 701 } else { 702 // assume we have reached last block in this transit, for safety sake. 703 log.warn("{}: No next Block from Block= {} Section= {}", _activeTrain.getTrainName(), 704 b.getDisplayName(USERSYS), as.getSection().getDisplayName(USERSYS)); 705 removeCurrentSignal(); 706 stopInCurrentSection(NO_TASK); 707 } 708 } 709 } else if (b != _currentBlock) { 710 log.trace("{}: block going occupied {} is not _nextBlock or _currentBlock - ignored.", 711 _activeTrain.getTrainName(), b.getDisplayName(USERSYS)); 712 return; 713 } 714 } else if (b.getState() == Block.UNOCCUPIED) { 715 log.debug("{}: handleBlockStateChange to UNOCCUPIED - Section {}, Block {}, speed {}", _activeTrain.getTrainName(), 716 as.getSection().getDisplayName(USERSYS), b.getDisplayName(USERSYS), 717 _autoEngineer == null ? "" : getTargetSpeed()); 718 if (_stoppingByBlockOccupancy && (b == _stoppingBlock)) { 719 log.trace("{}: setStopNow by block occupancy from Block unoccupied, Block= {}", _activeTrain.getTrainName(), b.getDisplayName(USERSYS)); 720 _stoppingByBlockOccupancy = false; 721 _stoppingBlock = null; 722 if (_needSetSpeed) { 723 _needSetSpeed = false; 724 setSpeedBySignal(); 725 } else { 726 setStopNow(); 727 } 728 } 729 } 730 _autoTrainAction.handleBlockStateChange(as, b); 731 } 732 733 /** 734 * support methods 735 */ 736 protected void setEngineDirection() { 737 boolean oldFwd = getForward(); 738 if (_runInReverse) { 739 setForward(_activeTrain.isTransitReversed()); 740 } else { 741 setForward(!_activeTrain.isTransitReversed()); 742 } 743 log.debug("[{}]flipping direction was [{}] now [{}]",_activeTrain.getActiveTrainName() ,oldFwd, getForward()); 744 } 745 746 protected AllocatedSection getCurrentAllocatedSection() { 747 return _currentAllocatedSection; 748 } 749 750 /* 751 * Reverse lookup for allocated section. 752 */ 753 protected AllocatedSection getAllocatedSectionForSection(Section s) { 754 for (AllocatedSection allocatedSection : _activeTrain.getAllocatedSectionList()) { 755 if (allocatedSection.getSection() == s) { 756 return allocatedSection; 757 } 758 } 759 return null; 760 } 761 762 protected void allocateAFresh() { 763 //Reset initialized flag 764 _initialized = false; 765 // set direction 766 _currentAllocatedSection=null; 767 _currentBlock=null; 768 setForward(!getRunInReverse()); 769 } 770 771 private void addAllocatedSection(AllocatedSection as) { 772 if (!_initialized) { 773 // this is first allocated section, get things started 774 _initialized = true; 775 _nextSection = as.getSection(); 776 _currentBlock = _activeTrain.getStartBlock(); 777 if (as.getSection().containsBlock(_currentBlock)) { 778 // starting Block is in this allocated section - find next Block 779 setNewCurrentSection(as); 780 _nextBlock = getNextBlock(_currentBlock, as); 781 } else if (as.getSection().connectsToBlock(_currentBlock)) { 782 // starting Block is connected to a Block in this allocated section 783 EntryPoint ep = as.getSection().getEntryPointFromBlock(_currentBlock, as.getDirection()); 784 if (ep != null) { 785 _nextBlock = ep.getBlock(); 786 } else { 787 log.error("failure to get entry point to Transit from Block {}", _currentBlock.getDisplayName(USERSYS)); 788 } 789 } 790 if (_nextBlock != null) { 791 // set up new current signal, as this a beginning we allow a signal not at end of block 792 // to control the speed. 793 setupNewCurrentSignal(as,true); 794 } 795 } 796 // if train is stopping for lack of an allocation, set flag to restart it 797 if (!_pausingActive && (_lastAllocatedSection == _currentAllocatedSection) 798 && isStopping() && (_activeTrain.getStatus() == ActiveTrain.RUNNING)) { 799 _needSetSpeed = true; 800 } 801 802 // request next allocation if appropriate--Dispatcher must decide whether to allocate it and when 803 if ((!_dispatcher.getAutoAllocate()) && ((_lastAllocatedSection == null) 804 || (_lastAllocatedSection.getNextSection() == as.getSection()))) { 805 // if AutoAllocate, this is now done in DispatcherFrame.java for all trains 806 _lastAllocatedSection = as; 807 if (as.getNextSection() != null) { 808 Section nSection = as.getNextSection(); 809 int nextSeq = as.getNextSectionSequence(); 810 int nextDir = _activeTrain.getAllocationDirectionFromSectionAndSeq(nSection, nextSeq); 811 _dispatcher.requestAllocation(_activeTrain, nSection, nextDir, nextSeq, true, null); 812 } 813 } 814 } 815 816 private boolean isStopping() { 817 // here add indicator for new stopping methods, if any are added 818 return (_stoppingBySensor || _stoppingByBlockOccupancy || _stoppingUsingSpeedProfile); 819 } 820 821 private void removeCurrentSignal() { 822 if (_conSignalListener != null) { 823 _controllingSignal.removePropertyChangeListener(_conSignalListener); 824 _conSignalListener = null; 825 } 826 _controllingSignalPrev = _controllingSignal; 827 _controllingSignal = null; 828 if (_conSignalMastListener != null) { 829 _controllingSignalMast.removePropertyChangeListener(_conSignalMastListener); 830 _conSignalMastListener = null; 831 } 832 _controllingSignalMastPrev = _controllingSignalMast; 833 _controllingSignalMast = null; 834 _needSetSpeed = false; 835 } 836 837 /** 838 * checks for a controlling signal 839 * @return true if there is one 840 */ 841 protected boolean isCurrentSignal() { 842 if (_activeTrain.getSignalType() == DispatcherFrame.SIGNALHEAD) { 843 return _controllingSignal != null; 844 } else { 845 // SignalMast 846 return _controllingSignalMast != null; 847 } 848 } 849 850 /** 851 * 852 * @param as current section the train is in, can be null 853 * @param forceSpeedChange if true, the speed will be set using the signal mast 854 * even if it is not on the immediate block boundary 855 */ 856 protected synchronized void setupNewCurrentSignal(AllocatedSection as, boolean forceSpeedChange) { 857 log.trace("setupNewCurrentSignal Called Section[{}] forceSpeedChange[{}]", as != null ? as.getSectionName() : "null",forceSpeedChange); 858 removeCurrentSignal(); 859 if (_activeTrain.getSignalType() == DispatcherFrame.SIGNALHEAD) { 860 SignalHead sh = _lbManager.getFacingSignalHead(_currentBlock, _nextBlock); 861 if (sh != null) { 862 _controllingSignal = sh; 863 _conSignalProtectedBlock = _nextBlock; 864 sh.addPropertyChangeListener(_conSignalListener = (PropertyChangeEvent e) -> { 865 if (e.getPropertyName().equals("Appearance")) { 866 // controlling signal has changed appearance 867 setSpeedBySignal(); 868 } 869 }); 870 _activeTrain.setControlingSignal(_controllingSignal, _controllingSignalPrev); 871 log.debug("new current signal = {}", sh.getDisplayName(USERSYS)); 872 } else { 873 // Note: null signal head will result when exiting throat-to-throat blocks. 874 log.warn("new current signal is null - sometimes OK"); 875 } 876 setSpeedBySignal(); 877 } else if (_activeTrain.getSignalType() == DispatcherFrame.SIGNALMAST) { 878 //SignalMast 879 SignalMast sm = null; 880 Block cB = _currentBlock; 881 Block nB = _nextBlock; 882 if (as == null) { 883 as = _currentAllocatedSection; 884 } 885 // get signal mast at current block change, if there is no signal mast we will proceed with no change in speed 886 // unless forceSpeedChange is true, such as beginning, resets of transit. 887 // previous signal mast speed unless the mast is held. 888 boolean weAreAtSpeedChangingMast=forceSpeedChange; 889 if ( !forceSpeedChange && nB != null ) { 890 sm = _lbManager.getFacingSignalMast(cB, nB); 891 if (sm != null) {weAreAtSpeedChangingMast=true;} 892 } 893 894 while (sm == null && nB != null) { 895 sm = _lbManager.getFacingSignalMast(cB, nB); 896 if (sm == null) { 897 cB = nB; 898 nB = getNextBlock(nB, as); 899 } 900 } 901 if (sm != null) { 902 _controllingSignalMast = sm; 903 _conSignalProtectedBlock = nB; 904 sm.addPropertyChangeListener(_conSignalMastListener = (PropertyChangeEvent e) -> { 905 if (e.getPropertyName().equals("Aspect") || e.getPropertyName().equals("Held")) { 906 // controlling signal has changed appearance or a hold has been released 907 // even if its a hold we still have to use target speed etc else we override pauses and other stop events. 908 setSpeedBySignal(); 909 } 910 }); 911 _activeTrain.setControlingSignal(_controllingSignalMast, _controllingSignalMastPrev); 912 log.debug("{}: new current signalmast {}({}) for section {}", _activeTrain.getTrainName(), sm.getDisplayName(USERSYS), 913 sm.getAspect(), as.getSection().getDisplayName(USERSYS)); 914 if ( weAreAtSpeedChangingMast ) { 915 setSpeedBySignal(); 916 } else { 917 checkForGhost(); 918 } 919 } else { 920 // There is a missing signal mast at a block boundary. 921 // If the next block is allocated to this train we can continue. 922 // If the train was stopped here we can try and restart it. Either way we use 923 // setting setSpeedBySectionsAllocated as a way out of the dilemma. 924 log.debug("{}: new current signalmast is null for section {} - sometimes OK", _activeTrain.getTrainName(), 925 as == null ? "Null" : as.getSection().getDisplayName(USERSYS)); 926 if (_nextBlock == null || ! _activeTrain.getBlockList().contains(_nextBlock) || _autoEngineer.isStopped()) { 927 log.warn("{}: new current signalmast is null for section {} and next block is not this trains. Temporarily continuing by allocations", _activeTrain.getTrainName(), 928 as == null ? "Null" : as.getSection().getDisplayName(USERSYS)); 929 setSpeedBySectionsAllocated(); 930 } 931 checkForGhost(); 932 } 933 } else { 934 setSpeedBySignal(); 935 } 936 } 937 938 @CheckForNull 939 private Block getNextBlock(Block b, AllocatedSection as) { 940 //if (((_currentBlock == _activeTrain.getEndBlock()) && _activeTrain.getReverseAtEnd() 941 // && (as.getSequence() == _activeTrain.getEndBlockSectionSequenceNumber()))) { 942 // return _previousBlock; 943 //} 944 if ((_currentBlock == _activeTrain.getStartBlock()) 945 && _activeTrain.getResetWhenDone() && _activeTrain.isTransitReversed() 946 && (as.getSequence() == _activeTrain.getStartBlockSectionSequenceNumber())) { 947 return _previousBlock; 948 } 949 if (as.getNextSection() != null) { 950 EntryPoint ep = as.getSection().getExitPointToSection(_nextSection, as.getDirection()); 951 if ((ep != null) && (ep.getBlock() == b)) { 952 // this block is connected to a block in the next section 953 return ep.getFromBlock(); 954 } 955 } 956 // this allocated section has multiple blocks _or_ there is no next Section 957 Block blk = as.getSection().getEntryBlock(); 958 while (blk != null) { 959 if (b == blk) { 960 return as.getSection().getNextBlock(); 961 } 962 blk = as.getSection().getNextBlock(); 963 } 964 return null; 965 } 966 967 private void setNewCurrentSection(AllocatedSection as) { 968 if (as.getSection() == _nextSection) { 969 _previousAllocatedSection = _currentAllocatedSection; 970 _currentAllocatedSection = as; 971 _nextSection = as.getNextSection(); 972 TransitSection ts = as.getTransitSection(); 973 if (ts != null) { 974 _autoTrainAction.addTransitSection(ts); 975 } 976 // written the long way for readability 977 boolean nextSectionExpected = true; 978 if (ts != null && 979 ts.isSafe() && 980 _activeTrain.getAllocateMethod() == ActiveTrain.ALLOCATE_BY_SAFE_SECTIONS) { 981 nextSectionExpected = false; 982 } else if (!_activeTrain.isAllocationReversed() && 983 _activeTrain.getEndBlockSection() == _currentAllocatedSection.getSection()) { 984 nextSectionExpected = false; 985 } else if (_activeTrain.isAllocationReversed() && 986 _activeTrain.getStartBlockSectionSequenceNumber() == _currentAllocatedSection.getSequence()) { 987 nextSectionExpected = false; 988 } 989 log.debug("{}:Next Section Expected[{}]",_activeTrain.getActiveTrainName(), nextSectionExpected); 990 // NOw handled in SetSpeedBySignal() 991 // check if new next Section exists but is not allocated to this train excepting above circumstances 992 //if ( nextSectionExpected &&_nextSection != null && !_activeTrain.isInAllocatedList(_nextSection)) { 993 // // next section is not allocated to this train, must not enter it, even if signal is OK. 994 // log.warn("Stopping train [{}] in section [{}], as next section [{}] is not allocated", 995 // _activeTrain.getActiveTrainName(),_currentAllocatedSection.getSection().getDisplayName(USERSYS),_nextSection.getDisplayName(USERSYS)); 996 // stopInCurrentSection(NO_TASK); 997 // _needSetSpeed = false; 998 //} 999 // see if we need to rescan as entering safe section. 1000 if (ts != null && 1001 ts.isSafe() && 1002 _activeTrain.getAllocateMethod() == ActiveTrain.ALLOCATE_BY_SAFE_SECTIONS) { 1003 _dispatcher.queueScanOfAllocationRequests(); 1004 } 1005 1006 } 1007 } 1008 1009 // called by above or when resuming after stopped action 1010 protected synchronized void setSpeedBySignal() { 1011 log.trace("Set Speed by Signal"); 1012 if (_pausingActive 1013 || ((_activeTrain.getStatus() != ActiveTrain.RUNNING) 1014 && (_activeTrain.getStatus() != ActiveTrain.WAITING) 1015 && !_activeTrain.getStarted()) 1016 || (_activeTrain.getMode() != ActiveTrain.AUTOMATIC)) { 1017 // train is pausing or not RUNNING or WAITING or started and in AUTOMATIC mode 1018 // don't set speed based on controlling signal 1019 log.trace("Skip Set Speed By Signal"); 1020 return; 1021 } 1022 // only bother to check signal if the next allocation is ours. 1023 // and the turnouts have been set 1024 if (checkAllocationsAhead() && checkTurn(getAllocatedSectionForSection(_nextSection))) { 1025 if (_activeTrain.getSignalType() == DispatcherFrame.SIGNALHEAD 1026 && _controllingSignal != null) { 1027 setSpeedBySignalHead(); 1028 } else if (_activeTrain.getSignalType() == DispatcherFrame.SIGNALMAST 1029 && _controllingSignalMast != null) { 1030 setSpeedBySignalMast(); 1031 } else { 1032 log.trace("{}:Set Speed by BlocksAllocated",_activeTrain.getActiveTrainName()); 1033 setSpeedBySectionsAllocated(); 1034 } 1035 checkForGhost(); 1036 } else { 1037 // This might be the last section.... 1038 if (_currentAllocatedSection != null && _currentAllocatedSection.getNextSection() == null) { 1039 stopInCurrentSection(END_TRAIN); 1040 } else { 1041 // This will stop it. 1042 stopInCurrentSection(NO_TASK); 1043 log.debug("{}:Set Stop",_activeTrain.getActiveTrainName()); 1044 waitingOnAllocation = true; // flag setSpeedBySignal required when another allocation made. 1045 } 1046 } 1047 } 1048 1049 private void checkForGhost() { 1050 if ( !(getTargetSpeed() == 0.0f || isStopping()) 1051 && _nextBlock != null 1052 && _currentBlock != null 1053 && _nextBlock.getSensor() != null 1054 && _nextBlock.getIsGhost()) { 1055 if ( _currentBlock.getIsGhost()) { 1056 log.error("Stopping due to two consecutive no sensor blocks [{}], [{}]", 1057 _currentBlock.getDisplayName(), _nextBlock.getDisplayName()); 1058 } else { 1059 try { 1060 _currentBlock.addPropertyChangeListener(new DarkTerritoryListener(_nextBlock.getSensor())); 1061 _nextBlock.getSensor().setKnownState(Sensor.ACTIVE); 1062 } catch (jmri.JmriException ex) { 1063 log.error("Error entering darkterratory"); 1064 } 1065 } 1066 } 1067 } 1068 1069 /* 1070 * Check at least the next section is allocated 1071 */ 1072 private boolean checkAllocationsAhead() { 1073 if (_nextSection != null) { 1074 // Check that next section is allocated... 1075 for (AllocatedSection allocatedSection : _activeTrain.getAllocatedSectionList()) { 1076 if (allocatedSection.getSection() == _nextSection) { 1077 return true; 1078 } 1079 } 1080 } 1081 return false; 1082 } 1083 1084 private void setSpeedBySectionsAllocated() { 1085 if (_stoppingByBlockOccupancy && (_stoppingBlock != null && _stoppingBlock.getState() == Block.UNOCCUPIED)) { 1086 // we are awaiting a delayed stop 1087 return; 1088 } 1089 int sectionsAhead = 0; 1090 for (AllocatedSection allocatedSection : _activeTrain.getAllocatedSectionList()) { 1091 if (!allocatedSection.getEntered()) { 1092 sectionsAhead++; 1093 } 1094 } 1095 float newSpeed = 0.0f; 1096 log.debug("[{}:SectionsAhead[{}]",_activeTrain.getActiveTrainName() ,sectionsAhead); 1097 switch (sectionsAhead) { 1098 case 0: 1099 newSpeed = 0.0f; 1100 break; 1101 case 1: 1102 newSpeed = InstanceManager.getDefault(SignalSpeedMap.class) 1103 .getSpeed("Medium"); 1104 // .getSpeed(_dispatcher.getStoppingSpeedName()); 1105 _activeTrain.setStatus(ActiveTrain.RUNNING); 1106 break; 1107 default: 1108 newSpeed = InstanceManager.getDefault(SignalSpeedMap.class) 1109 .getSpeed("Normal"); 1110 // .getSpeed(_dispatcher.getStoppingSpeedName()); 1111 _activeTrain.setStatus(ActiveTrain.RUNNING); 1112 } 1113 // get slowest speed of any entered and not released section. 1114 // This then covers off HEADONLY. 1115 for (AllocatedSection asE : _activeTrain.getAllocatedSectionList()) { 1116 if (asE.getEntered()) { 1117 for (Block b : asE.getSection().getBlockList()) { 1118 if (getSpeedFromBlock(b) < newSpeed) { 1119 newSpeed = getSpeedFromBlock(b); 1120 } 1121 } 1122 } 1123 } 1124 // see if needs to slow for next block. 1125 if (newSpeed > 0 && _nextBlock != null) { 1126 float speed = getSpeedFromBlock(_nextBlock); 1127 if (speed < newSpeed) { 1128 // slow for next block 1129 newSpeed = speed; 1130 } 1131 } 1132 if (newSpeed > 0) { 1133 log.trace("setSpeedBySectionsAllocated isStopping[{}]",isStopping()); 1134 cancelStopInCurrentSection(); 1135 setTargetSpeed(getThrottleSettingFromSpeed(newSpeed)); 1136 } else { 1137 waitingOnAllocation = true; 1138 stopInCurrentSection(NO_TASK); 1139 } 1140 } 1141 1142 /** 1143 * Check that all turnouts in a section have finished setting 1144 * for passage. If not listens on first bad turnout 1145 * and rechecks when set. 1146 * @param as Allocated section whose turnouts need to be checked. 1147 * @return true if no errors else false 1148 */ 1149 private boolean checkTurn(AllocatedSection as) { 1150 if (as != null && as.getAutoTurnoutsResponse() != null) { 1151 Turnout to = _dispatcher.getAutoTurnoutsHelper().checkStateAgainstList(as.getAutoTurnoutsResponse()); 1152 if (to != null) { 1153 // at least one turnout isnt correctly set 1154 to.addPropertyChangeListener(_turnoutStateListener = (PropertyChangeEvent e) -> { 1155 if (e.getPropertyName().equals("KnownState")) { 1156 ((Turnout) e.getSource()).removePropertyChangeListener(_turnoutStateListener); 1157 setSpeedBySignal(); 1158 } 1159 }); 1160 return false; 1161 } 1162 } 1163 return true; 1164 } 1165 1166 private void setSpeedBySignalMast() { 1167 //Set speed using SignalMasts; 1168 if (_controllingSignalMast == null) { 1169 // temporarily revert to by sections allocated 1170 setSpeedBySectionsAllocated(); 1171 return; 1172 } 1173 String displayedAspect = _controllingSignalMast.getAspect(); 1174 if (log.isTraceEnabled()) { 1175 log.trace("{}: Controlling mast {} ({})", _activeTrain.getTrainName(), _controllingSignalMast.getDisplayName(USERSYS), displayedAspect); 1176 if (_conSignalProtectedBlock == null) { 1177 log.trace("{}: Protected block is null", _activeTrain.getTrainName()); 1178 } else { 1179 log.trace("{}: Protected block: {} state: {} speed: {}", _activeTrain.getTrainName(), 1180 _conSignalProtectedBlock.getSensor().getDisplayName(USERSYS), 1181 (_conSignalProtectedBlock.getSensor().getState() == Block.OCCUPIED ? "OCCUPIED" : "NOT OCCUPIED"), 1182 _conSignalProtectedBlock.getBlockSpeed()); 1183 } 1184 } 1185 1186 if ((_controllingSignalMast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER).equals(displayedAspect)) 1187 || !_controllingSignalMast.getLit() || _controllingSignalMast.getHeld()) { 1188 checkForSignalPassedOrStop(_controllingSignalMast.getDisplayName(USERSYS)); 1189 } else if (_controllingSignalMast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.PERMISSIVE) != null 1190 && _controllingSignalMast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.PERMISSIVE).equals(displayedAspect)) { 1191 setTargetSpeedState(RESTRICTED_SPEED); 1192 _activeTrain.setStatus(ActiveTrain.RUNNING); 1193 } else { 1194 1195 //if using signalmasts, set speed to lesser of aspect speed and signalmastlogic speed 1196 // (minimum speed on the path to next signal, using turnout and block speeds) 1197 String aspectSpeedStr = (String) _controllingSignalMast.getSignalSystem().getProperty(displayedAspect, "speed"); 1198 log.trace("{}: Signal {} speed {} for aspect {}", _activeTrain.getTrainName(), _controllingSignalMast.getDisplayName(USERSYS), aspectSpeedStr, displayedAspect); 1199 float speed = -1.0f; 1200 if (aspectSpeedStr != null) { 1201 try { 1202 speed = Float.parseFloat(aspectSpeedStr); 1203 } catch (NumberFormatException nx) { 1204 try { 1205 speed = InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(aspectSpeedStr); 1206 log.trace("{}: Signal {} speed from map for {} is {}", _activeTrain.getTrainName(), _controllingSignalMast.getDisplayName(USERSYS), aspectSpeedStr, speed); 1207 } catch (IllegalArgumentException ex) { 1208 //Considered Normal if the speed does not appear in the map 1209 log.trace("{}: Speed not found {}", _activeTrain.getTrainName(), aspectSpeedStr); 1210 } 1211 } 1212 } 1213 int aspectSpeed = (int) speed; //save for debug message 1214 1215 //get maximum speed for the route between current and next signalmasts 1216 float smLogicSpeed = -1.0f; 1217 String smDestinationName = "unknown"; 1218 SignalMastLogic smLogic = InstanceManager.getDefault(SignalMastLogicManager.class).getSignalMastLogic(_controllingSignalMast); 1219 if (smLogic != null) { 1220 SignalMast smDestination = smLogic.getActiveDestination(); 1221 if (smDestination != null) { 1222 smDestinationName = smDestination.getDisplayName(USERSYS); 1223 smLogicSpeed = (int) smLogic.getMaximumSpeed(smDestination); 1224 } 1225 } 1226 1227 //use the smaller of aspect speed or route speed 1228 if (smLogicSpeed > -1.0f && smLogicSpeed < speed) { 1229 speed = smLogicSpeed; 1230 } 1231 1232 log.debug("{}: {}({}) {}({}), Dest: {}, path max: {}", 1233 _activeTrain.getTrainName(), 1234 _controllingSignalMast.getDisplayName(USERSYS), displayedAspect, aspectSpeedStr, aspectSpeed, 1235 smDestinationName, (int) smLogicSpeed); 1236 1237 if (speed > -1.0f) { 1238 /* We should work on the basis that the speed required in the current block/section is governed by the signalmast 1239 that we have passed and not the one we are approaching when we are accelerating. 1240 However when we are decelerating we should be aiming to meet the speed required by the approaching signalmast 1241 whether that is to slow down or come to a complete stand still. 1242 */ 1243 if (prevSpeed == -1 || speed < prevSpeed) { 1244 log.debug("{}: Signal {} setting speed to {} for next", _activeTrain.getTrainName(), 1245 _controllingSignalMast.getDisplayName(USERSYS), speed); 1246 setTargetSpeedValue(speed); 1247 } else { 1248 log.debug("{}: Signal {} setting speed to {} for previous", _activeTrain.getTrainName(), 1249 _controllingSignalMast.getDisplayName(USERSYS), speed); 1250 setTargetSpeedValue(prevSpeed); 1251 } 1252 prevSpeed = speed; 1253 _activeTrain.setStatus(ActiveTrain.RUNNING); 1254 1255 } else { 1256 log.warn("{}: No specific speeds found so will use the default", _activeTrain.getTrainName()); 1257 setTargetSpeedState(NORMAL_SPEED); 1258 _activeTrain.setStatus(ActiveTrain.RUNNING); 1259 } 1260 } 1261 } 1262 1263 private void setSpeedBySignalHead() { 1264 // a held signal always stop 1265 if ( _controllingSignal != null && _controllingSignal.getAppearance() == SignalHead.HELD ) { 1266 // Held - Stop 1267 stopInCurrentSection(NO_TASK); 1268 return; 1269 } 1270 1271 1272 if (useSpeedProfile) { 1273 // find speed from signal. 1274 // find speed from block 1275 // use least 1276 float blockSpeed = getSpeedFromBlock(_conSignalProtectedBlock); 1277 1278 float signalSpeed; 1279 String signalSpeedName; 1280 String displayedAspect = _controllingSignal.getAppearanceName(); 1281 try { 1282 signalSpeedName = 1283 InstanceManager.getDefault(SignalSpeedMap.class).getAppearanceSpeed(displayedAspect); 1284 signalSpeed = InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(signalSpeedName); 1285 } catch (Throwable ex) { // if _anything_ goes wrong, contain it 1286 signalSpeed = -1.0f; 1287 log.warn("{}: Block {} AppearanceSpeed {} not found in SignalSpeedMap", 1288 _activeTrain.getTrainName(), _conSignalProtectedBlock.getDisplayName(USERSYS), displayedAspect); 1289 } 1290 float useSpeed; 1291 if (blockSpeed < signalSpeed) { 1292 useSpeed = blockSpeed; 1293 } else { 1294 useSpeed = signalSpeed; 1295 } 1296 1297 log.trace("BlockSpeed[{}] SignalSpeed[{}]", blockSpeed, signalSpeed); 1298 if (useSpeed < 0.01f) { 1299 checkForSignalPassedOrStop(_controllingSignal.getDisplayName(USERSYS)); 1300 } else { 1301 setTargetSpeedByProfile(useSpeed,_stopBySpeedProfileAdjust,true); 1302 } 1303 } else { 1304 switch (_controllingSignal.getAppearance()) { 1305 case SignalHead.DARK: 1306 case SignalHead.RED: 1307 case SignalHead.FLASHRED: 1308 // May get here from signal changing before Block knows it is occupied, so must 1309 // check Block occupancy sensor, which must change before signal. 1310 // check to to see if its allocated to us!!! 1311 // check Block occupancy sensor if it is in an allocated block, which must change before signal 1312 // If the train has no _currentAllocatedSection it is in a first block outside transit. 1313 checkForSignalPassedOrStop(_controllingSignal.getDisplayName(USERSYS)); 1314 break; 1315 case SignalHead.YELLOW: 1316 case SignalHead.FLASHYELLOW: 1317 setTargetSpeedState(SLOW_SPEED); 1318 _activeTrain.setStatus(ActiveTrain.RUNNING); 1319 break; 1320 case SignalHead.GREEN: 1321 case SignalHead.FLASHGREEN: 1322 setTargetSpeedState(NORMAL_SPEED); 1323 _activeTrain.setStatus(ActiveTrain.RUNNING); 1324 break; 1325 case SignalHead.LUNAR: 1326 case SignalHead.FLASHLUNAR: 1327 setTargetSpeedState(RESTRICTED_SPEED); 1328 _activeTrain.setStatus(ActiveTrain.RUNNING); 1329 break; 1330 default: 1331 log.warn("Signal Head[{}] has invalid Appearence - using stop",_controllingSignal.getAppearance()); 1332 stopInCurrentSection(NO_TASK); 1333 } 1334 1335 } 1336 } 1337 1338 /** 1339 * Check to see if a stop is really required, or if this is the 1340 * signal head that was just passed, in which case ignore as the signal goes red before a 1341 * new signal exists. 1342 * 1343 * @param displayName name of signal for debug messages. 1344 */ 1345 private void checkForSignalPassedOrStop(String displayName) { 1346 // if current section is null we are in a pre transit block. 1347 if (_currentAllocatedSection != null) { 1348 if ((_currentAllocatedSection.isInActiveBlockList(_conSignalProtectedBlock) || 1349 (_nextSection != null && _activeTrain.isInAllocatedList(_nextSection) && _nextSection.containsBlock(_conSignalProtectedBlock))) 1350 && _conSignalProtectedBlock.getSensor().getState() == Block.OCCUPIED) { 1351 // Train has just passed this signal - ignore this signal 1352 log.debug("{}: _conSignalProtectedBlock [{}] for signal [{}] is the block just past so ignore.", _activeTrain.getTrainName(), 1353 _conSignalProtectedBlock.getDisplayName(USERSYS), displayName); 1354 } else { 1355 log.debug("{}: stopping for signal [{}] ", _activeTrain.getTrainName(), 1356 displayName); 1357 stopInCurrentSection(NO_TASK); 1358 } 1359 } 1360 } 1361 1362 protected float getSpeedFromBlock(Block block) { 1363 String blockSpeedName = block.getBlockSpeed(); 1364 if (blockSpeedName.contains("Global")) { 1365 blockSpeedName = InstanceManager.getDefault(BlockManager.class).getDefaultSpeed(); 1366 } 1367 float blockSpeed = -1.0f; 1368 if (!blockSpeedName.isEmpty()) { 1369 try { 1370 blockSpeed = Float.parseFloat(blockSpeedName); 1371 } catch (NumberFormatException nx) { 1372 try { 1373 blockSpeed = InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(blockSpeedName); 1374 log.debug("{} {}: block speed from map for {} is {}", 1375 _activeTrain.getTrainName(), block.getDisplayName(USERSYS), blockSpeedName, 1376 blockSpeed); 1377 } catch (Throwable ex) { // if _anything_ goes wrong, contain it 1378 //Considered Normal if the speed does not appear in the map 1379 log.warn("{}: Block {} Speed {} not found in SignalSpeedMap", 1380 _activeTrain.getTrainName(), block.getDisplayName(USERSYS), blockSpeed); 1381 } 1382 } 1383 } 1384 return blockSpeed; 1385 } 1386 1387 float prevSpeed = -1.0f; 1388 1389 // called to cancel a stopping action that is in progress 1390 private synchronized void cancelStopInCurrentSection() { 1391 log.trace("[{}]:Cancel Stopping", _activeTrain.getTrainName()); 1392 cancelStoppingBySensor(); 1393 _stoppingByBlockOccupancy = false; 1394 _stoppingBlock = null; 1395 _stoppingUsingSpeedProfile = false; 1396 _stoppingBlock = null; 1397 _autoEngineer.slowToStop(false); 1398 } 1399 1400 private synchronized void stopInCurrentSection(int task) { 1401 if (_currentAllocatedSection == null) { 1402 log.error("{}: Current allocated section null on entry to stopInCurrentSection", _activeTrain.getTrainName()); 1403 setStopNow(); 1404 return; 1405 } 1406 log.debug("{}: StopInCurrentSection called for {} task[{}] targetspeed[{}]", _activeTrain.getTrainName(), _currentAllocatedSection.getSection().getDisplayName(USERSYS),task,getTargetSpeed()); 1407 if (getTargetSpeed() == 0.0f || isStopping()) { 1408 log.debug("{}: train is already stopped or stopping.", _activeTrain.getTrainName()); 1409 // ignore if train is already stopped or if stopping is in progress 1410 return; 1411 } 1412 // if Section has stopping sensors, use them 1413 if (_currentAllocatedSection.getSection().getState() == Section.FORWARD) { 1414 _stopSensor = _currentAllocatedSection.getSection().getForwardStoppingSensor(); 1415 } else { 1416 _stopSensor = _currentAllocatedSection.getSection().getReverseStoppingSensor(); 1417 } 1418 if (_stopSensor != null && _useStopSensor) { 1419 if (_stopSensor.getKnownState() == Sensor.ACTIVE) { 1420 // stop sensor is already active, stop now 1421 setStopNow(); 1422 } else { 1423 setDecreasedSpeedBeforeStop(); 1424 _stopSensor.addPropertyChangeListener(_stopSensorListener = (java.beans.PropertyChangeEvent e) -> { 1425 handleStopSensorChange(e); 1426 }); 1427 _stoppingBySensor = true; 1428 } 1429 } else if (useSpeedProfile && _stopBySpeedProfile) { 1430 log.debug("{}: Section [{}] Section Length[{}] Max Train Length [{}] StopBySpeedProfile [{}]. setStopNow", _activeTrain.getTrainName(), 1431 _currentAllocatedSection.getSection().getDisplayName(USERSYS), _currentAllocatedSection.getActualLength(), getMaxTrainLengthMM(), _stopBySpeedProfile); 1432 // stopping by speed profile uses section length to stop 1433 setTargetSpeedState(STOP_SPEED,useSpeedProfile); 1434 } else if (_currentAllocatedSection.getActualLength() < getMaxTrainLengthMM()) { 1435 log.debug("{}: Section [{}] Section Length[{}] Max Train Length [{}]. setStopNow({})", 1436 _activeTrain.getTrainName(), 1437 _currentAllocatedSection.getSection().getDisplayName(USERSYS), 1438 _currentAllocatedSection.getActualLength(), 1439 getMaxTrainLengthMM(), _stopBySpeedProfile); 1440 // train will not fit comfortably in the Section, stop it immediately 1441 setStopNow(); 1442 } else if (_activeTrain.getTrainDetection() == TrainDetection.TRAINDETECTION_WHOLETRAIN) { 1443 log.debug("{}: train will fit in [{}] ({}>={}), stop when prev block clears.", _activeTrain.getTrainName(), 1444 _currentAllocatedSection.getSection().getDisplayName(USERSYS), _currentAllocatedSection.getActualLength(), getMaxTrainLengthMM()); 1445 // train will fit in current allocated Section and has resistance wheels 1446 // try to stop by watching Section Block occupancy 1447 if (_currentAllocatedSection.getSection().getNumBlocks() == 1) { 1448 if (_previousAllocatedSection != null) { 1449 Block tBlock; 1450 // just because current section has one block does not mean the previous one did. 1451 if (_previousAllocatedSection.getSection().getNumBlocks() == 1) { 1452 tBlock = _previousAllocatedSection.getSection().getLastBlock(); 1453 } else { 1454 tBlock = _previousAllocatedSection.getSection().getExitBlock(); 1455 } 1456 if ((tBlock != null) && (tBlock.getState() == Block.OCCUPIED)) { 1457 _stoppingBlock = tBlock; 1458 setStopByBlockOccupancy(false); 1459 } else { 1460 setStopNow(); 1461 } 1462 } else { 1463 setStopNow(); 1464 } 1465 } else { 1466 // Section has multiple blocks 1467 Block exitBlock = _currentAllocatedSection.getExitBlock(); 1468 Block enterBlock = _currentAllocatedSection.getEnterBlock(_previousAllocatedSection); 1469 if (enterBlock == null) { 1470 // this is the first Section of the Transit, with train starting in this Section 1471 setStopNow(); 1472 } else if (exitBlock == enterBlock) { 1473 // entry and exit are from the same Block 1474 if ((_previousBlock != null) && (_previousBlock.getState() == Block.OCCUPIED) 1475 && (getBlockLength(exitBlock) > getMaxTrainLengthMM())) { 1476 _stoppingBlock = _previousBlock; 1477 setStopByBlockOccupancy(false); 1478 } else { 1479 setStopNow(); 1480 } 1481 } else { 1482 // try to move train as far into the Section as it will comfortably fit 1483 Block tstBlock = exitBlock; 1484 if (tstBlock == null) { 1485 if (_currentAllocatedSection.getDirection() == Section.REVERSE) { 1486 tstBlock = _currentAllocatedSection.getSection().getBlockBySequenceNumber(0); 1487 } else { 1488 tstBlock = _currentAllocatedSection.getSection().getBlockBySequenceNumber( 1489 _currentAllocatedSection.getSection().getNumBlocks() - 1); 1490 } 1491 } 1492 int tstLength = getBlockLength(tstBlock); 1493 int tstBlockSeq = _currentAllocatedSection.getSection().getBlockSequenceNumber(tstBlock); 1494 while ((tstLength < getMaxTrainLengthMM()) && (tstBlock != enterBlock)) { 1495 int newSeqNumber; 1496 if (_currentAllocatedSection.getDirection() == Section.REVERSE) { 1497 newSeqNumber = tstBlockSeq + 1; 1498 } else { 1499 newSeqNumber = tstBlockSeq - 1; 1500 } 1501 tstBlock = _currentAllocatedSection.getSection().getBlockBySequenceNumber(newSeqNumber); 1502 tstBlockSeq = newSeqNumber; 1503 tstLength += getBlockLength(tstBlock); 1504 } 1505 if (getMaxTrainLengthMM() > tstLength) { 1506 setStopNow(); 1507 } else if (tstBlock == enterBlock) { 1508 // train fits, but needs all available Blocks 1509 Block previousSectionExitBlock = _previousAllocatedSection.getExitBlock(); 1510 if ((previousSectionExitBlock != null) && (previousSectionExitBlock.getState() == Block.OCCUPIED)) { 1511 _stoppingBlock = previousSectionExitBlock; 1512 setStopByBlockOccupancy(true); 1513 } else { 1514 setStopNow(); 1515 } 1516 } else { 1517 // train fits, and doesn't need all available Blocks 1518 int xSeqNumber = tstBlockSeq + 1; 1519 if (_currentAllocatedSection.getDirection() == Section.FORWARD ) { 1520 xSeqNumber = tstBlockSeq - 1; 1521 } 1522 _stoppingBlock = _currentAllocatedSection.getSection(). 1523 getBlockBySequenceNumber(xSeqNumber); 1524 setStopByBlockOccupancy(true); 1525 } 1526 } 1527 } 1528 } else { 1529 // train will fit, but no way to stop it reliably 1530 setStopNow(); 1531 } 1532 // even if no task is required it must be run 1533 // as cleanup happens after train stops. 1534 Runnable waitForStop = new WaitForTrainToStop(task); 1535 Thread tWait = jmri.util.ThreadingUtil.newThread(waitForStop, "Wait for stop " + getActiveTrain().getActiveTrainName()); 1536 tWait.start(); 1537 } 1538 1539 protected synchronized void executeStopTasks(int task) { 1540 // clean up stopping 1541 cancelStopInCurrentSection(); 1542 _dispatcher.queueReleaseOfCompletedAllocations(); 1543 log.trace("exec[{}]",task); 1544 switch (task) { 1545 case END_TRAIN: 1546 _activeTrain.setStatus(ActiveTrain.DONE); 1547 break; 1548 case NO_TASK: 1549 // clean up stop 1550 break; 1551 case END_REVERSAL: 1552 /* Reset _previousBlock to be the _currentBlock if we do a continious reverse otherwise the stop in block method fails 1553 to stop the loco in the correct block 1554 if the first block we come to has a stopped or held signal */ 1555 _activeTrain.setRestart(_activeTrain.getDelayReverseRestart(),_activeTrain.getReverseRestartDelay(), 1556 _activeTrain.getReverseRestartSensor(),_activeTrain.getResetReverseRestartSensor()); 1557 _activeTrain.setTransitReversed(true); 1558 _activeTrain.reverseAllAllocatedSections(); 1559 setEngineDirection(); 1560 _previousBlock = null; 1561 _nextBlock = getNextBlock(_currentBlock,_currentAllocatedSection); 1562 if (_activeTrain.getDelayReverseRestart() == ActiveTrain.NODELAY) { 1563 _activeTrain.holdAllocation(false); 1564 // a reversal can happen in mid section 1565 setupNewCurrentSignal(_currentAllocatedSection, true); 1566 setSpeedBySignal(); 1567 if ((_nextSection != null) && !_activeTrain.isInAllocatedList(_nextSection)) { 1568 _dispatcher.queueScanOfAllocationRequests(); 1569 break; 1570 } 1571 } 1572 break; 1573 case BEGINNING_RESET: 1574 _activeTrain.setRestart(_activeTrain.getDelayedRestart(),_activeTrain.getRestartDelay(), 1575 _activeTrain.getRestartSensor(),_activeTrain.getResetRestartSensor()); 1576 if (_activeTrain.getResetWhenDone()) { 1577 if (_activeTrain.getDelayedRestart() == ActiveTrain.NODELAY && !_activeTrain.getReverseAtEnd()) { 1578 log.error("[{}]: train is continueing without pause, should have been handled in handleBlockStateChange.",_activeTrain.getTrainName()); 1579 } else { 1580 // then active train is delayed 1581 _activeTrain.setTransitReversed(false); 1582 _activeTrain.resetAllAllocatedSections(); 1583 _previousBlock = null; 1584 _nextBlock = getNextBlock(_currentBlock,_currentAllocatedSection); 1585 setEngineDirection(); 1586 _activeTrain.setRestart(_activeTrain.getDelayedRestart(),_activeTrain.getRestartDelay(), 1587 _activeTrain.getRestartSensor(), _activeTrain.getResetRestartSensor()); 1588 if ((_nextSection != null) && !_activeTrain.isInAllocatedList(_nextSection)) { 1589 _dispatcher.queueScanOfAllocationRequests(); 1590 } 1591 // can be mid block 1592 setupNewCurrentSignal(null, true); 1593 setSpeedBySignal(); 1594 1595 } 1596 } else { 1597 // dispatcher cancelled auto restart while train was stopping? 1598 log.warn("[{}]resetWhenDone flag reset, likely user cancelling while processing stop", 1599 _activeTrain.getActiveTrainName()); 1600 } 1601 break; 1602 default: 1603 log.debug("[{}]Invalid action [{}] in executeStopTasksRequest to execute BEGINNING_RESET cancelled", _activeTrain.getActiveTrainName(),task); 1604 break; 1605 } 1606 } 1607 1608 /** 1609 * Remove the stopping sensor 1610 */ 1611 private void cancelStoppingBySensor() { 1612 if (_stopSensor != null) { 1613 _stopSensor.removePropertyChangeListener(_stopSensorListener); 1614 _stoppingBySensor = false; 1615 _stopSensorListener = null; 1616 _stopSensor = null; 1617 } 1618 } 1619 1620 /** 1621 * When the stopping sensor we are waiting on goes active 1622 * stop the train or set a new speed and destroy itself 1623 * @param e - the property change event 1624 */ 1625 private synchronized void handleStopSensorChange(java.beans.PropertyChangeEvent e) { 1626 if (e.getPropertyName().equals("KnownState") && (int) e.getNewValue() == Sensor.ACTIVE) { 1627 _stopSensor.removePropertyChangeListener(_stopSensorListener); 1628 _stoppingBySensor = false; 1629 _stopSensorListener = null; 1630 _stopSensor = null; 1631 if (_needSetSpeed) { 1632 _needSetSpeed = false; 1633 setSpeedBySignal(); 1634 } else { 1635 setStopNow(); 1636 } 1637 } 1638 } 1639 1640 private synchronized void setStopNow() { 1641 setStopNow(false); 1642 } 1643 1644 private synchronized void setStopNow(boolean useSpeedProfile) { 1645 setTargetSpeedState(STOP_SPEED,useSpeedProfile); 1646 if (_currentAllocatedSection == null) { // this may occur if the train is not in the selected block when initially created and the signal is held. 1647 _activeTrain.setStatus(ActiveTrain.WAITING); 1648 } else if (_currentAllocatedSection.getNextSection() == null) { 1649 // wait for train to stop - this lets action items complete in a timely fashion 1650 waitUntilStopped(); 1651 _activeTrain.setStatus(ActiveTrain.DONE); 1652 } else { 1653 _activeTrain.setStatus(ActiveTrain.WAITING); 1654 } 1655 } 1656 1657 /* 1658 * When multi block stopping, the stopping block may not be occupied yet. 1659 */ 1660 private void setStopByBlockOccupancy(boolean ignoreNotOccupied) { 1661 // note: _stoppingBlock must be set before invoking this method 1662 // verify that _stoppingBlock is actually occupied, if not stop immed 1663 if (_stoppingBlock.getState() == Block.OCCUPIED || ignoreNotOccupied) { 1664 setDecreasedSpeedBeforeStop(); 1665 _stoppingByBlockOccupancy = true; 1666 } else { 1667 setStopNow(); 1668 } 1669 } 1670 1671 /** 1672 * Before stopping by sensor alone, or by clearing previous block, 1673 * set the speed to the user defined preference. 1674 */ 1675 private void setDecreasedSpeedBeforeStop() { 1676 float signalSpeed = 25; 1677 try { 1678 signalSpeed = InstanceManager.getDefault(SignalSpeedMap.class) 1679 .getSpeed(_dispatcher.getStoppingSpeedName()); 1680 } catch (IllegalArgumentException ex) { 1681 log.error("Missing [{}] from Speed table - defaulting to 25", 1682 _dispatcher.getStoppingSpeedName()); 1683 } 1684 if (getThrottleSettingFromSpeed(signalSpeed) < getTargetSpeed()) { 1685 if (useSpeedProfile) { 1686 // use 75 percent or normal amount, dont clear isstopping for ramping. 1687 setTargetSpeedByProfile(signalSpeed,_stopBySpeedProfileAdjust*0.75f,false); 1688 } else { 1689 setTargetSpeed(signalSpeed/100.0f); 1690 } 1691 } 1692 } 1693 1694 ///** 1695 // * Sets the throttle percent unless it is already less than the new setting 1696 // * @param throttleSetting Max ThrottleSetting required. 1697 // */ 1698 //private synchronized void setToAMaximumThrottle(float throttleSetting) { 1699 // if (throttleSetting < getTargetSpeed()) { 1700 // setTargetSpeed(throttleSetting); 1701 // } 1702 //} 1703 1704 /** 1705 * Calculates the throttle setting for a given speed. 1706 * @param speed the unadjusted speed. 1707 * @return - throttle setting (a percentage) 1708 */ 1709 private synchronized float getThrottleSettingFromSpeed(float speed) { 1710 if (useSpeedProfile) { 1711 float throttleSetting = _activeTrain.getRosterEntry().getSpeedProfile() 1712 .getThrottleSettingFromSignalMapSpeed(speed, getForward()); 1713 return throttleSetting; 1714 } 1715 if (_activeTrain.getSignalType() == DispatcherFrame.SIGNALMAST) { 1716 float mls; 1717 if (_controllingSignalMast != null) { 1718 mls = _controllingSignalMast.getSignalSystem().getMaximumLineSpeed(); 1719 } else { 1720 //plan B 1721 mls = _dispatcher.getMaximumLineSpeed(); 1722 } 1723 float throttleSetting = (speed / mls); 1724 return throttleSetting; 1725 } else { 1726 return speed/100.0f; 1727 } 1728 } 1729 1730 1731 /** 1732 * sets the throttle based on an index number into _speedRatio array 1733 * @param speedState Index value 1734 */ 1735 private synchronized void setTargetSpeedState(int speedState) { 1736 setTargetSpeedState(speedState,false); 1737 } 1738 1739 /** 1740 * sets the throttle based on an index number into _speedRatio array 1741 * @param speedState Index value 1742 * @param stopBySpeedProfile if true use speed profile 1743 */ 1744 private synchronized void setTargetSpeedState(int speedState,boolean stopBySpeedProfile) { 1745 log.trace("{}: setTargetSpeedState:({})",_activeTrain.getTrainName(),speedState); 1746 _autoEngineer.slowToStop(false); 1747 if (speedState > STOP_SPEED) { 1748 cancelStopInCurrentSection(); 1749 if (_currentRampRate == RAMP_SPEEDPROFILE && useSpeedProfile) { 1750 // we are going to ramp up / down using section length and speed profile 1751 _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock) * _stopBySpeedProfileAdjust, speedState); 1752 } else { 1753 setTargetSpeed(_speedRatio[speedState]); 1754 } 1755 } else if (stopBySpeedProfile) { 1756 // we are going to stop by profile 1757 _stoppingUsingSpeedProfile = true; 1758 _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock) * _stopBySpeedProfileAdjust, 0.0f); 1759 } else { 1760 _autoEngineer.setHalt(true); 1761 setTargetSpeed(0.0f); 1762 } 1763 } 1764 1765 private synchronized void setTargetSpeedByProfile(float speedState, float stopBySpeedProfileAdjust, boolean cancelStopping) { 1766 // the speed comes in as units of warrents (mph, kph, mm/s etc) 1767 try { 1768 float throttleSetting = _activeTrain.getRosterEntry().getSpeedProfile().getThrottleSettingFromSignalMapSpeed(speedState, getForward()); 1769 log.debug("{}: setTargetSpeedByProfile: {} SpeedState[{}]", 1770 _activeTrain.getTrainName(), 1771 throttleSetting, 1772 speedState); 1773 if (throttleSetting > 0.009 && _currentRampRate != RAMP_SPEEDPROFILE && useSpeedProfile) { 1774 if (cancelStopping) {cancelStopInCurrentSection();} 1775 setTargetSpeed(throttleSetting); // apply speed factor and max 1776 } else if (throttleSetting > 0.009) { 1777 if (cancelStopping) {cancelStopInCurrentSection();} 1778 setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock) * stopBySpeedProfileAdjust , throttleSetting); 1779 } else if (useSpeedProfile && _stopBySpeedProfile) { 1780 setTargetSpeed(0.0f); 1781 _stoppingUsingSpeedProfile = true; 1782 _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock) * stopBySpeedProfileAdjust, 0.0f); 1783 } else { 1784 _autoEngineer.slowToStop(false); 1785 setTargetSpeed(0.0f); 1786 _autoEngineer.setHalt(true); 1787 } 1788 } catch (Exception ex) { 1789 log.error("setTargetSpeedByProfile crashed - Emergency Stop: ", ex ); 1790 _autoEngineer.slowToStop(false); 1791 setTargetSpeed(-1.0f); 1792 _autoEngineer.setHalt(true); 1793 } 1794 } 1795 1796 /** 1797 * Pass in speed as shown on dialogs, and convert to decimal speed needed by 1798 * throttle. 1799 */ 1800 private synchronized void setTargetSpeedValue(float speed) { 1801 log.debug("{}: setTargetSpeedValue: Speed[{}]",_activeTrain.getTrainName(),speed); 1802 if (useSpeedProfile) { 1803 setTargetSpeedByProfile(speed,_stopBySpeedProfileAdjust,true); 1804 return; 1805 } 1806 _autoEngineer.slowToStop(false); 1807 float mls; 1808 if (_controllingSignalMast != null) { 1809 mls = _controllingSignalMast.getSignalSystem().getMaximumLineSpeed(); 1810 } else { 1811 mls = _dispatcher.getMaximumLineSpeed(); 1812 } 1813 float decSpeed = (speed / mls); 1814 if (decSpeed > 0.0f) { 1815 cancelStopInCurrentSection(); 1816 setTargetSpeed(decSpeed); 1817 } else { 1818 setTargetSpeed(0.0f); 1819 _autoEngineer.setHalt(true); 1820 } 1821 } 1822 1823 private int getBlockLength(Block b) { 1824 if (b == null) { 1825 return (0); 1826 } 1827 return (int) b.getLengthMm(); 1828// float fLength = b.getLengthMm() / (float) _dispatcher.getScale().getScaleFactor(); 1829// if (_dispatcher.getUseScaleMeters()) { 1830// return (int) (fLength * 0.001f); 1831// } 1832// return (int) (fLength * 0.00328084f); 1833 } 1834 1835 /** 1836 * Initiates running in manual mode with external throttle. 1837 * <p> 1838 * This method is triggered by an action in the Transit. The throttle in use 1839 * for automatic operation is dispatched. 1840 */ 1841 protected void initiateWorking() { 1842 if (_activeTrain.getStatus() != ActiveTrain.WORKING) { 1843 _activeTrain.setMode(ActiveTrain.DISPATCHED); 1844 _activeTrain.setStatus(ActiveTrain.WORKING); 1845 saveSpeedAndDirection(); 1846 if (_autoEngineer != null) { 1847 _autoEngineer.setHalt(true); 1848 waitUntilStopped(); 1849 _autoEngineer.abort(); 1850 InstanceManager.throttleManagerInstance().releaseThrottle(_throttle, this); 1851 _autoEngineer = null; 1852 _throttle = null; 1853 } 1854 } 1855 } 1856 1857 /** 1858 * Returns when train is stopped. 1859 * <p> 1860 * Note: Provides for _autoEngineer becoming null during wait Ties up the 1861 * current autoActiveTrain thread. 1862 */ 1863 protected void waitUntilStopped() { 1864 boolean doneWaiting = false; 1865 while (!doneWaiting) { 1866 if (_autoEngineer != null) { 1867 doneWaiting = _autoEngineer.isStopped(); 1868 } else { 1869 doneWaiting = true; 1870 } 1871 if (!doneWaiting) { 1872 try { 1873 Thread.sleep(50); 1874 } catch (InterruptedException e) { 1875 // ignore this exception 1876 } 1877 } 1878 } 1879 } 1880 1881 /** 1882 * Resumes automatic running after a working session using an external 1883 * throttle This method is triggered by the dispatcher hitting the "Resume 1884 * Auto Running" button A new throttle is acquired to allow automatic 1885 * running to resume 1886 */ 1887 protected void resumeAutomaticRunning() { 1888 if ((_activeTrain.getStatus() == ActiveTrain.WORKING) 1889 || (_activeTrain.getStatus() == ActiveTrain.READY)) { 1890 _autoTrainAction.cancelDoneSensor(); 1891 if (initialize()) { 1892 _resumingAutomatic = true; 1893 } else { 1894 log.error("Failed to initialize throttle when resuming automatic mode."); 1895 } 1896 } 1897 } 1898 1899 /** 1900 * Pause the auto active train for a specified number of fast clock minutes. 1901 * 1902 * @param fastMinutes the number of minutes to pause the train 1903 * @return the thread waiting on the pause or null if already paused 1904 */ 1905 public Thread pauseTrain(int fastMinutes) { 1906 if (_pausingActive) { 1907 // if a pause train thread is currently active, ignore this call 1908 return (null); 1909 } 1910 Runnable pauseTrain = new PauseTrain(fastMinutes); 1911 Thread tPause = jmri.util.ThreadingUtil.newThread(pauseTrain, "pause train " + _activeTrain.getTrainName()); 1912 tPause.start(); 1913 return tPause; 1914 } 1915 1916 public void terminate() { 1917 // here add code to stop the train and release its throttle if it is in autoRun 1918 while (_activeHornThreads > 0) { 1919 try { 1920 Thread.sleep(50); 1921 } catch (InterruptedException e) { 1922 // ignore this exception 1923 } 1924 } 1925 _autoTrainAction.clearRemainingActions(); 1926 if (_autoEngineer != null) { 1927 _autoEngineer.setHalt(true); 1928 try { 1929 Thread.sleep(50); 1930 } catch (InterruptedException e) { 1931 // ignore this exception 1932 } 1933 waitUntilStopped(); 1934 _autoEngineer.abort(); 1935 InstanceManager.throttleManagerInstance().releaseThrottle(_throttle, this); 1936 } 1937 } 1938 1939 public void dispose() { 1940 if (_controllingSignalMast != null && _conSignalMastListener != null) { 1941 _controllingSignalMast.removePropertyChangeListener(_conSignalMastListener); 1942 } 1943 _controllingSignalMast = null; 1944 _conSignalMastListener = null; 1945 } 1946 1947// _________________________________________________________________________________________ 1948 // This class waits for train stop in a separate thread 1949 class WaitForTrainToStop implements Runnable { 1950 1951 public WaitForTrainToStop(int task) { 1952 _task = task; 1953 } 1954 1955 @Override 1956 public void run() { 1957 boolean waitingOnTrain = true; 1958 try { 1959 while (waitingOnTrain) { 1960 if ((getAutoEngineer() != null) && (getAutoEngineer().isStopped())) { 1961 waitingOnTrain = false; 1962 } else { 1963 Thread.sleep(_delay); 1964 } 1965 } 1966 log.trace("executing task[{}]",_task); 1967 executeStopTasks(_task); 1968 } catch (InterruptedException e) { 1969 log.warn("Waiting for train to stop interrupted - stop tasks not executing"); 1970 } catch (Exception e) { 1971 log.error("Waiting for train to stop crashed - stop tasks not executing.", e); 1972 } 1973 } 1974 1975 private final int _delay = 91; 1976 private int _task = 0; 1977 } 1978 1979 /** 1980 * Pause the train in a separate thread. Train is stopped, then restarted 1981 * after specified number of fast Minutes have elapsed. 1982 */ 1983 class PauseTrain implements Runnable { 1984 /** 1985 * Create a PauseTrain 1986 * 1987 * @param fastMinutes the number of fast clock minutes to pause the 1988 * train 1989 */ 1990 public PauseTrain(int fastMinutes) { 1991 _fastMinutes = fastMinutes; 1992 } 1993 1994 @Override 1995 public void run() { 1996 // set to pause at a fast ramp rate 1997 _pausingActive = true; 1998 // TODO: use stop in section or block? 1999 _savedRampRate = getRampRate(); 2000 setCurrentRampRate(RAMP_FAST); 2001 stopInCurrentSection(NO_TASK); 2002 // wait for train to stop 2003 boolean waitNow = true; 2004 boolean keepGoing = true; 2005 while (waitNow) { 2006 try { 2007 Thread.sleep(101); 2008 if (_autoEngineer != null) { 2009 if (_autoEngineer.isStopped()) { 2010 waitNow = false; 2011 } 2012 } else { 2013 waitNow = false; 2014 } 2015 } catch (InterruptedException e) { 2016 log.trace("InterruptedException while waiting to stop for pause-indicates action cancelled.", e); 2017 waitNow = false; 2018 keepGoing = false; 2019 } 2020 } 2021 _activeTrain.setStatus(ActiveTrain.PAUSED); 2022 if (keepGoing) { 2023 // wait for specified fast clock time 2024 Timebase _clock = InstanceManager.getDefault(jmri.Timebase.class); 2025 java.beans.PropertyChangeListener _clockListener = (java.beans.PropertyChangeEvent e) -> { 2026 _fastMinutes--; 2027 }; 2028 _clock.addMinuteChangeListener(_clockListener); 2029 // wait for fast minutes to tick away 2030 waitNow = true; 2031 while (waitNow) { 2032 try { 2033 Thread.sleep(501); 2034 if (_fastMinutes <= 0) { 2035 waitNow = false; 2036 } 2037 } catch (InterruptedException e) { 2038 log.trace("InterruptedException indicates action cancelled.", e); 2039 keepGoing = false; 2040 } 2041 } 2042 _clock.removeMinuteChangeListener(_clockListener); 2043 } 2044 _pausingActive = false; 2045 if (keepGoing) { 2046 // this thread was not interrupted 2047 // resume running - restore speed, status, and ramp rate 2048 setCurrentRampRate(_savedRampRate); 2049 // Set speed by signal also works if signal missing 2050 // so we dont need to restore a previous value. 2051 _activeTrain.setStatus(ActiveTrain.RUNNING); 2052 setSpeedBySignal(); 2053 } 2054 } 2055 private int _fastMinutes = 0; 2056 private int _savedRampRate = RAMP_NONE; 2057 } 2058 2059 // _________________________________________________________________________________________ 2060 // this class handles the interface with the throttle 2061 // (This class started from code by Pete Cressman contained in Warrant.java.) 2062 class AutoEngineer { 2063 2064 AutoEngineer(DccThrottle throttle, RosterEntry rosterEntry) { 2065 this.throttle = throttle; 2066 this.rosterEntry = rosterEntry; 2067 } 2068 2069 private DccThrottle throttle; 2070 private int ramping; 2071 private boolean speedProfileStoppingIsRunning = false; 2072 private float speedIncrement = 0.0f; //will be recalculated 2073 private float targetSpeed; 2074 private RosterEntry rosterEntry; 2075 private int throttleInterval; 2076 private float minReliableOperatingSpeed; 2077 private float maxSpeed; 2078 private float speedFactor; 2079 2080 public void setRamping(int ramping, int fullRampTime, int minThrottleInterval, int rampRate) { 2081 this.ramping = ramping; 2082 this.throttleInterval = minThrottleInterval; 2083 //calculate speed increment to use in each minInterval time 2084 speedIncrement = (100.0f / ((float) fullRampTime / minThrottleInterval) 2085 / rampRate) / 100.0f; 2086 log.debug("{}: _speedIncrement={}", throttle.getLocoAddress(), speedIncrement); 2087 } 2088 2089 public void setIsForward(boolean isForward) { 2090 throttle.setIsForward(isForward); 2091 } 2092 2093 public boolean getIsForward() { 2094 return(throttle.getIsForward()); 2095 } 2096 2097 public void setTargetSpeed(float speed) { 2098 stopAllTimers(); 2099 targetSpeed = applyMaxThrottleAndFactor(speed); 2100 log.debug("setTargetSpeed: Set Speed[{}] adjusted to TargetSpeed[{}] ",speed,targetSpeed); 2101 if (ramping == RAMP_NONE || ramping == RAMP_SPEEDPROFILE ) { 2102 throttle.setSpeedSetting(targetSpeed); 2103 } else { 2104 rampToTarget(); 2105 } 2106 } 2107 2108 public float getTargetSpeed(){ 2109 return(targetSpeed); 2110 } 2111 2112 /** 2113 * 2114 * @param throttleSetting the throttle setting that would normally be set 2115 * @return the adjusted throttle setting after applying Max Throttle and Percentage throttle settings 2116 */ 2117 private float applyMaxThrottleAndFactor(float throttleSetting) { 2118 if (throttleSetting > 0.0f) { 2119 if ((throttleSetting * speedFactor) > maxSpeed) { 2120 return maxSpeed; 2121 } 2122 if ((throttleSetting * speedFactor) < minReliableOperatingSpeed) { 2123 return minReliableOperatingSpeed; 2124 } 2125 return (throttleSetting * speedFactor); //adjust for train's Speed Factor 2126 } else { 2127 return throttleSetting; 2128 } 2129 } 2130 2131 /** 2132 * Flag from user's control. 2133 * 2134 * @param halt true to immediately stop the train; false otherwise 2135 */ 2136 public void setHalt(boolean halt) { 2137 if (halt) { 2138 this.setSpeedImmediate(0.0f); 2139 } 2140 } 2141 2142 /** 2143 * Set the limits and adjustment factore for train speed. 2144 * Active train will calculate the required setting and it will be adjusted if not 0.0f 2145 * required setting * speed Factor then test for less than max and greater than min. 2146 * @param minReliableOperatingSpeed lowest throttle % train will reliably move. 2147 * @param maxSpeed max throttle % for train. 2148 * @param speedFactor multiplier 2149 */ 2150 public void setSpeedLimits(float minReliableOperatingSpeed, float maxSpeed, float speedFactor) { 2151 this.minReliableOperatingSpeed = minReliableOperatingSpeed; 2152 this.maxSpeed = maxSpeed; 2153 this.speedFactor = speedFactor; 2154 } 2155 2156 public void setTargetSpeed(float distance, float speed) { 2157 log.debug("Set Target Speed[{}] with distance{{}] from speed[{}]",speed,distance,throttle.getSpeedSetting()); 2158 stopAllTimers(); 2159 if (rosterEntry != null) { 2160 rosterEntry.getSpeedProfile().setExtraInitialDelay(1500f); 2161 rosterEntry.getSpeedProfile().setMinMaxLimits(minReliableOperatingSpeed, maxSpeed); 2162 rosterEntry.getSpeedProfile().changeLocoSpeed(_throttle, distance, speed); 2163 speedProfileStoppingIsRunning = true; 2164 targetSpeed = speed; 2165 } else { 2166 setTargetSpeed((0.0f)); 2167 } 2168 } 2169 2170 public void slowToStop(boolean on) { 2171 stopAllTimers(); 2172 if (on) { 2173 log.debug("SlowToStopOn"); 2174 setTargetSpeed((0.0f)); 2175 } 2176 } 2177 2178 public void stopAllTimers() { 2179 if (speedProfileStoppingIsRunning) { 2180 re.getSpeedProfile().cancelSpeedChange(); 2181 speedProfileStoppingIsRunning = false; 2182 } 2183 if (rampingTimer != null) { 2184 rampingTimer.stop(); 2185 rampingTimer = null; 2186 } 2187 } 2188 2189 LinkedList<SpeedSetting> stepQueue; 2190 private javax.swing.Timer rampingTimer; 2191 2192 private void rampToTarget() { 2193 // target already adjusted. 2194 log.debug("RampToTarget[{}]current[{}]", getTargetSpeed(), throttle.getSpeedSetting()); 2195 stepQueue = new LinkedList<>(); 2196 if (throttle.getSpeedSetting() == getTargetSpeed()) { 2197 return; 2198 } else if (throttle.getSpeedSetting() < getTargetSpeed()) { 2199 // Up 2200 float newSpeed = throttle.getSpeedSetting(); 2201 if (newSpeed < minReliableOperatingSpeed) { 2202 stepQueue.add(new SpeedSetting(minReliableOperatingSpeed, throttleInterval)); 2203 newSpeed = minReliableOperatingSpeed; 2204 } 2205 while (newSpeed < getTargetSpeed()) { 2206 newSpeed += speedIncrement; 2207 if (newSpeed > getTargetSpeed()) { 2208 newSpeed = getTargetSpeed(); 2209 } 2210 log.trace("NewSpeedUp[{}]", newSpeed); 2211 stepQueue.add(new SpeedSetting(newSpeed, throttleInterval)); 2212 } 2213 } else { 2214 // Down 2215 boolean andStop = false; 2216 if (getTargetSpeed() <= 0.0f) { 2217 andStop = true; 2218 } 2219 float newSpeed = throttle.getSpeedSetting(); 2220 while (newSpeed > getTargetSpeed()) { 2221 newSpeed -= speedIncrement; 2222 if (newSpeed < getTargetSpeed()) { 2223 newSpeed = getTargetSpeed(); 2224 } 2225 log.trace("NewSpeedDown[{}]", newSpeed); 2226 stepQueue.add(new SpeedSetting(newSpeed, throttleInterval)); 2227 } 2228 if (andStop) { 2229 stepQueue.add(new SpeedSetting(0.0f, throttleInterval)); 2230 } 2231 } 2232 if (rampingTimer == null) { //If this is the first time round then kick off the speed change 2233 setNextStep(); 2234 } 2235 } 2236 2237 private void finishChange() { 2238 if (rampingTimer != null) { 2239 rampingTimer.stop(); 2240 } 2241 rampingTimer = null; 2242 stepQueue.clear(); 2243 stepQueue = null; 2244 } 2245 2246 synchronized void setNextStep() { 2247 if (stepQueue.isEmpty()) { 2248 log.trace("Empty"); 2249 finishChange(); 2250 return; 2251 } 2252 SpeedSetting ss = stepQueue.getFirst(); 2253 if (ss.getDuration() == 0) { 2254 log.trace("Duratiom Zero"); 2255 finishChange(); 2256 return; 2257 } 2258 stepQueue.removeFirst(); 2259 log.trace("Set New Speed[{}]",ss.getSpeedStep()); 2260 throttle.setSpeedSetting(ss.getSpeedStep()); 2261 rampingTimer = new javax.swing.Timer(ss.getDuration(), (java.awt.event.ActionEvent e) -> { 2262 setNextStep(); 2263 }); 2264 rampingTimer.setRepeats(false); 2265 rampingTimer.start(); 2266 } 2267 2268 private class SpeedSetting { 2269 2270 float step = 0.0f; 2271 int duration = 0; 2272 2273 SpeedSetting(float step, int duration) { 2274 this.step = step; 2275 this.duration = duration; 2276 } 2277 2278 float getSpeedStep() { 2279 return step; 2280 } 2281 2282 int getDuration() { 2283 return duration; 2284 } 2285 } 2286 2287 /** 2288 * Set the train speed directly, bypassing ramping. 2289 * 2290 * @param speed 0.0 (stop) to 1.0 (full) 2291 */ 2292 public synchronized void setSpeedImmediate(float speed) { 2293 log.trace("{}: setting speed directly to {}%", _activeTrain.getTrainName(), (int) (speed * 100)); 2294 stopAllTimers(); 2295 targetSpeed = applyMaxThrottleAndFactor(speed); 2296 throttle.setSpeedSetting(targetSpeed); 2297 } 2298 2299 /** 2300 * Check if train is moving or stopped. 2301 * 2302 * @return true if stopped; false otherwise 2303 */ 2304 public synchronized boolean isStopped() { 2305 // when stopping by speed profile you must refresh the throttle speed. 2306 return throttle.getSpeedSetting() <= 0.0004f; 2307 } 2308 2309 /** 2310 * Check if train is moving at its current requested speed. 2311 * 2312 * @return true if at requested speed; false otherwise 2313 */ 2314 public synchronized boolean isAtSpeed() { 2315 return java.lang.Math.abs(throttle.getSpeedSetting() - targetSpeed) <= 0.01; 2316 } 2317 2318 /** 2319 * Flag from user to end run. 2320 */ 2321 public void abort() { 2322 stopAllTimers(); 2323 } 2324 2325 protected void setFunction(int cmdNum, boolean isSet) { 2326 throttle.setFunction(cmdNum, isSet); 2327 } 2328 } 2329 2330 /** 2331 * Convert ramp rate name, stored as a string into the constant value 2332 * assigned. 2333 * 2334 * @param rampRate name of ramp rate, such as "RAMP_FAST" 2335 * @return integer representing a ramprate constant value 2336 */ 2337 public static int getRampRateFromName(String rampRate) { 2338 if (rampRate.equals(Bundle.getMessage("RAMP_FAST"))) { 2339 return RAMP_FAST; 2340 } else if (rampRate.equals(Bundle.getMessage("RAMP_MEDIUM"))) { 2341 return RAMP_MEDIUM; 2342 } else if (rampRate.equals(Bundle.getMessage("RAMP_MED_SLOW"))) { 2343 return RAMP_MED_SLOW; 2344 } else if (rampRate.equals(Bundle.getMessage("RAMP_SLOW"))) { 2345 return RAMP_SLOW; 2346 } else if (rampRate.equals(Bundle.getMessage("RAMP_SPEEDPROFILE"))) { 2347 return RAMP_SPEEDPROFILE; 2348 } 2349 return RAMP_NONE; 2350 } 2351 2352 /* 2353 * Listener for switching Ghost blocks to unoccupied 2354 */ 2355 static class DarkTerritoryListener implements PropertyChangeListener { 2356 private Sensor sensor; 2357 2358 public DarkTerritoryListener(Sensor sensor) { 2359 this.sensor = sensor; 2360 log.trace("Sensor[{}]",sensor.getDisplayName()); 2361 } 2362 2363 @Override 2364 public void propertyChange(PropertyChangeEvent e) { 2365 if (e.getPropertyName().equals("state")) { 2366 ((Block) e.getSource()).removePropertyChangeListener(this); 2367 if (e.getNewValue().equals(Block.UNOCCUPIED)) { 2368 try { 2369 log.trace("Sensor INACTIVE[{}]", sensor.getDisplayName()); 2370 sensor.setKnownState(Sensor.INACTIVE); 2371 } catch (jmri.JmriException ex) { 2372 log.error("Error leaving darkterratory"); 2373 } 2374 } 2375 } 2376 } 2377 } 2378 2379 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AutoActiveTrain.class); 2380}