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 if (useSpeedProfile) { 1272 // find speed from signal. 1273 // find speed from block 1274 // use least 1275 float blockSpeed = getSpeedFromBlock(_conSignalProtectedBlock); 1276 1277 float signalSpeed; 1278 String signalSpeedName; 1279 String displayedAspect = _controllingSignal.getAppearanceName(); 1280 try { 1281 signalSpeedName = 1282 InstanceManager.getDefault(SignalSpeedMap.class).getAppearanceSpeed(displayedAspect); 1283 signalSpeed = InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(signalSpeedName); 1284 } catch (Throwable ex) { // if _anything_ goes wrong, contain it 1285 signalSpeed = -1.0f; 1286 log.warn("{}: Block {} AppearanceSpeed {} not found in SignalSpeedMap", 1287 _activeTrain.getTrainName(), _conSignalProtectedBlock.getDisplayName(USERSYS), displayedAspect); 1288 } 1289 float useSpeed; 1290 if (blockSpeed < signalSpeed) { 1291 useSpeed = blockSpeed; 1292 } else { 1293 useSpeed = signalSpeed; 1294 } 1295 1296 log.trace("BlockSpeed[{}] SignalSpeed[{}]", blockSpeed, signalSpeed); 1297 if (useSpeed < 0.01f) { 1298 checkForSignalPassedOrStop(_controllingSignal.getDisplayName(USERSYS)); 1299 } else { 1300 setTargetSpeedByProfile(useSpeed,_stopBySpeedProfileAdjust,true); 1301 } 1302 } else { 1303 switch (_controllingSignal.getAppearance()) { 1304 case SignalHead.DARK: 1305 case SignalHead.RED: 1306 case SignalHead.FLASHRED: 1307 // May get here from signal changing before Block knows it is occupied, so must 1308 // check Block occupancy sensor, which must change before signal. 1309 // check to to see if its allocated to us!!! 1310 // check Block occupancy sensor if it is in an allocated block, which must change before signal 1311 // If the train has no _currentAllocatedSection it is in a first block outside transit. 1312 checkForSignalPassedOrStop(_controllingSignal.getDisplayName(USERSYS)); 1313 break; 1314 case SignalHead.YELLOW: 1315 case SignalHead.FLASHYELLOW: 1316 setTargetSpeedState(SLOW_SPEED); 1317 _activeTrain.setStatus(ActiveTrain.RUNNING); 1318 break; 1319 case SignalHead.GREEN: 1320 case SignalHead.FLASHGREEN: 1321 setTargetSpeedState(NORMAL_SPEED); 1322 _activeTrain.setStatus(ActiveTrain.RUNNING); 1323 break; 1324 case SignalHead.LUNAR: 1325 case SignalHead.FLASHLUNAR: 1326 setTargetSpeedState(RESTRICTED_SPEED); 1327 _activeTrain.setStatus(ActiveTrain.RUNNING); 1328 break; 1329 default: 1330 log.warn("Signal Head[{}] has invalid Appearence - using stop",_controllingSignal.getAppearance()); 1331 stopInCurrentSection(NO_TASK); 1332 } 1333 1334 } 1335 } 1336 1337 /** 1338 * Check to see if a stop is really required, or if this is the 1339 * signal head that was just passed, in which case ignore as the signal goes red before a 1340 * new signal exists. 1341 * 1342 * @param displayName name of signal for debug messages. 1343 */ 1344 private void checkForSignalPassedOrStop(String displayName) { 1345 // if current section is null we are in a pre transit block. 1346 if (_currentAllocatedSection != null) { 1347 if ((_currentAllocatedSection.isInActiveBlockList(_conSignalProtectedBlock) || 1348 (_nextSection != null && _activeTrain.isInAllocatedList(_nextSection) && _nextSection.containsBlock(_conSignalProtectedBlock))) 1349 && _conSignalProtectedBlock.getSensor().getState() == Block.OCCUPIED) { 1350 // Train has just passed this signal - ignore this signal 1351 log.debug("{}: _conSignalProtectedBlock [{}] for signal [{}] is the block just past so ignore.", _activeTrain.getTrainName(), 1352 _conSignalProtectedBlock.getDisplayName(USERSYS), displayName); 1353 } else { 1354 log.debug("{}: stopping for signal [{}] ", _activeTrain.getTrainName(), 1355 displayName); 1356 stopInCurrentSection(NO_TASK); 1357 } 1358 } 1359 } 1360 1361 protected float getSpeedFromBlock(Block block) { 1362 String blockSpeedName = block.getBlockSpeed(); 1363 if (blockSpeedName.contains("Global")) { 1364 blockSpeedName = InstanceManager.getDefault(BlockManager.class).getDefaultSpeed(); 1365 } 1366 float blockSpeed = -1.0f; 1367 if (!blockSpeedName.isEmpty()) { 1368 try { 1369 blockSpeed = Float.parseFloat(blockSpeedName); 1370 } catch (NumberFormatException nx) { 1371 try { 1372 blockSpeed = InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(blockSpeedName); 1373 log.debug("{} {}: block speed from map for {} is {}", 1374 _activeTrain.getTrainName(), block.getDisplayName(USERSYS), blockSpeedName, 1375 blockSpeed); 1376 } catch (Throwable ex) { // if _anything_ goes wrong, contain it 1377 //Considered Normal if the speed does not appear in the map 1378 log.warn("{}: Block {} Speed {} not found in SignalSpeedMap", 1379 _activeTrain.getTrainName(), block.getDisplayName(USERSYS), blockSpeed); 1380 } 1381 } 1382 } 1383 return blockSpeed; 1384 } 1385 1386 float prevSpeed = -1.0f; 1387 1388 // called to cancel a stopping action that is in progress 1389 private synchronized void cancelStopInCurrentSection() { 1390 log.trace("[{}]:Cancel Stopping", _activeTrain.getTrainName()); 1391 cancelStoppingBySensor(); 1392 _stoppingByBlockOccupancy = false; 1393 _stoppingBlock = null; 1394 _stoppingUsingSpeedProfile = false; 1395 _stoppingBlock = null; 1396 _autoEngineer.slowToStop(false); 1397 } 1398 1399 private synchronized void stopInCurrentSection(int task) { 1400 if (_currentAllocatedSection == null) { 1401 log.error("{}: Current allocated section null on entry to stopInCurrentSection", _activeTrain.getTrainName()); 1402 setStopNow(); 1403 return; 1404 } 1405 log.debug("{}: StopInCurrentSection called for {} task[{}] targetspeed[{}]", _activeTrain.getTrainName(), _currentAllocatedSection.getSection().getDisplayName(USERSYS),task,getTargetSpeed()); 1406 if (getTargetSpeed() == 0.0f || isStopping()) { 1407 log.debug("{}: train is already stopped or stopping.", _activeTrain.getTrainName()); 1408 // ignore if train is already stopped or if stopping is in progress 1409 return; 1410 } 1411 // if Section has stopping sensors, use them 1412 if (_currentAllocatedSection.getSection().getState() == Section.FORWARD) { 1413 _stopSensor = _currentAllocatedSection.getSection().getForwardStoppingSensor(); 1414 } else { 1415 _stopSensor = _currentAllocatedSection.getSection().getReverseStoppingSensor(); 1416 } 1417 if (_stopSensor != null && _useStopSensor) { 1418 if (_stopSensor.getKnownState() == Sensor.ACTIVE) { 1419 // stop sensor is already active, stop now 1420 setStopNow(); 1421 } else { 1422 setDecreasedSpeedBeforeStop(); 1423 _stopSensor.addPropertyChangeListener(_stopSensorListener = (java.beans.PropertyChangeEvent e) -> { 1424 handleStopSensorChange(e); 1425 }); 1426 _stoppingBySensor = true; 1427 } 1428 } else if (useSpeedProfile && _stopBySpeedProfile) { 1429 log.debug("{}: Section [{}] Section Length[{}] Max Train Length [{}] StopBySpeedProfile [{}]. setStopNow", _activeTrain.getTrainName(), 1430 _currentAllocatedSection.getSection().getDisplayName(USERSYS), _currentAllocatedSection.getActualLength(), getMaxTrainLengthMM(), _stopBySpeedProfile); 1431 // stopping by speed profile uses section length to stop 1432 setTargetSpeedState(STOP_SPEED,useSpeedProfile); 1433 } else if (_currentAllocatedSection.getActualLength() < getMaxTrainLengthMM()) { 1434 log.debug("{}: Section [{}] Section Length[{}] Max Train Length [{}]. setStopNow({})", 1435 _activeTrain.getTrainName(), 1436 _currentAllocatedSection.getSection().getDisplayName(USERSYS), 1437 _currentAllocatedSection.getActualLength(), 1438 getMaxTrainLengthMM(), _stopBySpeedProfile); 1439 // train will not fit comfortably in the Section, stop it immediately 1440 setStopNow(); 1441 } else if (_activeTrain.getTrainDetection() == TrainDetection.TRAINDETECTION_WHOLETRAIN) { 1442 log.debug("{}: train will fit in [{}] ({}>={}), stop when prev block clears.", _activeTrain.getTrainName(), 1443 _currentAllocatedSection.getSection().getDisplayName(USERSYS), _currentAllocatedSection.getActualLength(), getMaxTrainLengthMM()); 1444 // train will fit in current allocated Section and has resistance wheels 1445 // try to stop by watching Section Block occupancy 1446 if (_currentAllocatedSection.getSection().getNumBlocks() == 1) { 1447 if (_previousAllocatedSection != null) { 1448 Block tBlock; 1449 // just because current section has one block does not mean the previous one did. 1450 if (_previousAllocatedSection.getSection().getNumBlocks() == 1) { 1451 tBlock = _previousAllocatedSection.getSection().getLastBlock(); 1452 } else { 1453 tBlock = _previousAllocatedSection.getSection().getExitBlock(); 1454 } 1455 if ((tBlock != null) && (tBlock.getState() == Block.OCCUPIED)) { 1456 _stoppingBlock = tBlock; 1457 setStopByBlockOccupancy(false); 1458 } else { 1459 setStopNow(); 1460 } 1461 } else { 1462 setStopNow(); 1463 } 1464 } else { 1465 // Section has multiple blocks 1466 Block exitBlock = _currentAllocatedSection.getExitBlock(); 1467 Block enterBlock = _currentAllocatedSection.getEnterBlock(_previousAllocatedSection); 1468 if (enterBlock == null) { 1469 // this is the first Section of the Transit, with train starting in this Section 1470 setStopNow(); 1471 } else if (exitBlock == enterBlock) { 1472 // entry and exit are from the same Block 1473 if ((_previousBlock != null) && (_previousBlock.getState() == Block.OCCUPIED) 1474 && (getBlockLength(exitBlock) > getMaxTrainLengthMM())) { 1475 _stoppingBlock = _previousBlock; 1476 setStopByBlockOccupancy(false); 1477 } else { 1478 setStopNow(); 1479 } 1480 } else { 1481 // try to move train as far into the Section as it will comfortably fit 1482 Block tstBlock = exitBlock; 1483 if (tstBlock == null) { 1484 if (_currentAllocatedSection.getDirection() == Section.REVERSE) { 1485 tstBlock = _currentAllocatedSection.getSection().getBlockBySequenceNumber(0); 1486 } else { 1487 tstBlock = _currentAllocatedSection.getSection().getBlockBySequenceNumber( 1488 _currentAllocatedSection.getSection().getNumBlocks() - 1); 1489 } 1490 } 1491 int tstLength = getBlockLength(tstBlock); 1492 int tstBlockSeq = _currentAllocatedSection.getSection().getBlockSequenceNumber(tstBlock); 1493 while ((tstLength < getMaxTrainLengthMM()) && (tstBlock != enterBlock)) { 1494 int newSeqNumber; 1495 if (_currentAllocatedSection.getDirection() == Section.REVERSE) { 1496 newSeqNumber = tstBlockSeq + 1; 1497 } else { 1498 newSeqNumber = tstBlockSeq - 1; 1499 } 1500 tstBlock = _currentAllocatedSection.getSection().getBlockBySequenceNumber(newSeqNumber); 1501 tstBlockSeq = newSeqNumber; 1502 tstLength += getBlockLength(tstBlock); 1503 } 1504 if (getMaxTrainLengthMM() > tstLength) { 1505 setStopNow(); 1506 } else if (tstBlock == enterBlock) { 1507 // train fits, but needs all available Blocks 1508 Block previousSectionExitBlock = _previousAllocatedSection.getExitBlock(); 1509 if ((previousSectionExitBlock != null) && (previousSectionExitBlock.getState() == Block.OCCUPIED)) { 1510 _stoppingBlock = previousSectionExitBlock; 1511 setStopByBlockOccupancy(true); 1512 } else { 1513 setStopNow(); 1514 } 1515 } else { 1516 // train fits, and doesn't need all available Blocks 1517 int xSeqNumber = tstBlockSeq + 1; 1518 if (_currentAllocatedSection.getDirection() == Section.FORWARD ) { 1519 xSeqNumber = tstBlockSeq - 1; 1520 } 1521 _stoppingBlock = _currentAllocatedSection.getSection(). 1522 getBlockBySequenceNumber(xSeqNumber); 1523 setStopByBlockOccupancy(true); 1524 } 1525 } 1526 } 1527 } else { 1528 // train will fit, but no way to stop it reliably 1529 setStopNow(); 1530 } 1531 // even if no task is required it must be run 1532 // as cleanup happens after train stops. 1533 Runnable waitForStop = new WaitForTrainToStop(task); 1534 Thread tWait = jmri.util.ThreadingUtil.newThread(waitForStop, "Wait for stop " + getActiveTrain().getActiveTrainName()); 1535 tWait.start(); 1536 } 1537 1538 protected synchronized void executeStopTasks(int task) { 1539 // clean up stopping 1540 cancelStopInCurrentSection(); 1541 _dispatcher.queueReleaseOfCompletedAllocations(); 1542 log.trace("exec[{}]",task); 1543 switch (task) { 1544 case END_TRAIN: 1545 _activeTrain.setStatus(ActiveTrain.DONE); 1546 break; 1547 case NO_TASK: 1548 // clean up stop 1549 break; 1550 case END_REVERSAL: 1551 /* Reset _previousBlock to be the _currentBlock if we do a continious reverse otherwise the stop in block method fails 1552 to stop the loco in the correct block 1553 if the first block we come to has a stopped or held signal */ 1554 _activeTrain.setRestart(_activeTrain.getDelayReverseRestart(),_activeTrain.getReverseRestartDelay(), 1555 _activeTrain.getReverseRestartSensor(),_activeTrain.getResetReverseRestartSensor()); 1556 _activeTrain.setTransitReversed(true); 1557 _activeTrain.reverseAllAllocatedSections(); 1558 setEngineDirection(); 1559 _previousBlock = null; 1560 _nextBlock = getNextBlock(_currentBlock,_currentAllocatedSection); 1561 if (_activeTrain.getDelayReverseRestart() == ActiveTrain.NODELAY) { 1562 _activeTrain.holdAllocation(false); 1563 // a reversal can happen in mid section 1564 setupNewCurrentSignal(_currentAllocatedSection, true); 1565 setSpeedBySignal(); 1566 if ((_nextSection != null) && !_activeTrain.isInAllocatedList(_nextSection)) { 1567 _dispatcher.queueScanOfAllocationRequests(); 1568 break; 1569 } 1570 } 1571 break; 1572 case BEGINNING_RESET: 1573 _activeTrain.setRestart(_activeTrain.getDelayedRestart(),_activeTrain.getRestartDelay(), 1574 _activeTrain.getRestartSensor(),_activeTrain.getResetRestartSensor()); 1575 if (_activeTrain.getResetWhenDone()) { 1576 if (_activeTrain.getDelayedRestart() == ActiveTrain.NODELAY && !_activeTrain.getReverseAtEnd()) { 1577 log.error("[{}]: train is continueing without pause, should have been handled in handleBlockStateChange.",_activeTrain.getTrainName()); 1578 } else { 1579 // then active train is delayed 1580 _activeTrain.setTransitReversed(false); 1581 _activeTrain.resetAllAllocatedSections(); 1582 _previousBlock = null; 1583 _nextBlock = getNextBlock(_currentBlock,_currentAllocatedSection); 1584 setEngineDirection(); 1585 _activeTrain.setRestart(_activeTrain.getDelayedRestart(),_activeTrain.getRestartDelay(), 1586 _activeTrain.getRestartSensor(), _activeTrain.getResetRestartSensor()); 1587 if ((_nextSection != null) && !_activeTrain.isInAllocatedList(_nextSection)) { 1588 _dispatcher.queueScanOfAllocationRequests(); 1589 } 1590 // can be mid block 1591 setupNewCurrentSignal(null, true); 1592 setSpeedBySignal(); 1593 1594 } 1595 } else { 1596 // dispatcher cancelled auto restart while train was stopping? 1597 log.warn("[{}]resetWhenDone flag reset, likely user cancelling while processing stop", 1598 _activeTrain.getActiveTrainName()); 1599 } 1600 break; 1601 default: 1602 log.debug("[{}]Invalid action [{}] in executeStopTasksRequest to execute BEGINNING_RESET cancelled", _activeTrain.getActiveTrainName(),task); 1603 break; 1604 } 1605 } 1606 1607 /** 1608 * Remove the stopping sensor 1609 */ 1610 private void cancelStoppingBySensor() { 1611 if (_stopSensor != null) { 1612 _stopSensor.removePropertyChangeListener(_stopSensorListener); 1613 _stoppingBySensor = false; 1614 _stopSensorListener = null; 1615 _stopSensor = null; 1616 } 1617 } 1618 1619 /** 1620 * When the stopping sensor we are waiting on goes active 1621 * stop the train or set a new speed and destroy itself 1622 * @param e - the property change event 1623 */ 1624 private synchronized void handleStopSensorChange(java.beans.PropertyChangeEvent e) { 1625 if (e.getPropertyName().equals("KnownState") && (int) e.getNewValue() == Sensor.ACTIVE) { 1626 _stopSensor.removePropertyChangeListener(_stopSensorListener); 1627 _stoppingBySensor = false; 1628 _stopSensorListener = null; 1629 _stopSensor = null; 1630 if (_needSetSpeed) { 1631 _needSetSpeed = false; 1632 setSpeedBySignal(); 1633 } else { 1634 setStopNow(); 1635 } 1636 } 1637 } 1638 1639 private synchronized void setStopNow() { 1640 setStopNow(false); 1641 } 1642 1643 private synchronized void setStopNow(boolean useSpeedProfile) { 1644 setTargetSpeedState(STOP_SPEED,useSpeedProfile); 1645 if (_currentAllocatedSection == null) { // this may occur if the train is not in the selected block when initially created and the signal is held. 1646 _activeTrain.setStatus(ActiveTrain.WAITING); 1647 } else if (_currentAllocatedSection.getNextSection() == null) { 1648 // wait for train to stop - this lets action items complete in a timely fashion 1649 waitUntilStopped(); 1650 _activeTrain.setStatus(ActiveTrain.DONE); 1651 } else { 1652 _activeTrain.setStatus(ActiveTrain.WAITING); 1653 } 1654 } 1655 1656 /* 1657 * When multi block stopping, the stopping block may not be occupied yet. 1658 */ 1659 private void setStopByBlockOccupancy(boolean ignoreNotOccupied) { 1660 // note: _stoppingBlock must be set before invoking this method 1661 // verify that _stoppingBlock is actually occupied, if not stop immed 1662 if (_stoppingBlock.getState() == Block.OCCUPIED || ignoreNotOccupied) { 1663 setDecreasedSpeedBeforeStop(); 1664 _stoppingByBlockOccupancy = true; 1665 } else { 1666 setStopNow(); 1667 } 1668 } 1669 1670 /** 1671 * Before stopping by sensor alone, or by clearing previous block, 1672 * set the speed to the user defined preference. 1673 */ 1674 private void setDecreasedSpeedBeforeStop() { 1675 float signalSpeed = 25; 1676 try { 1677 signalSpeed = InstanceManager.getDefault(SignalSpeedMap.class) 1678 .getSpeed(_dispatcher.getStoppingSpeedName()); 1679 } catch (IllegalArgumentException ex) { 1680 log.error("Missing [{}] from Speed table - defaulting to 25", 1681 _dispatcher.getStoppingSpeedName()); 1682 } 1683 if (getThrottleSettingFromSpeed(signalSpeed) < getTargetSpeed()) { 1684 if (useSpeedProfile) { 1685 // use 75 percent or normal amount, dont clear isstopping for ramping. 1686 setTargetSpeedByProfile(signalSpeed,_stopBySpeedProfileAdjust*0.75f,false); 1687 } else { 1688 setTargetSpeed(signalSpeed/100.0f); 1689 } 1690 } 1691 } 1692 1693 ///** 1694 // * Sets the throttle percent unless it is already less than the new setting 1695 // * @param throttleSetting Max ThrottleSetting required. 1696 // */ 1697 //private synchronized void setToAMaximumThrottle(float throttleSetting) { 1698 // if (throttleSetting < getTargetSpeed()) { 1699 // setTargetSpeed(throttleSetting); 1700 // } 1701 //} 1702 1703 /** 1704 * Calculates the throttle setting for a given speed. 1705 * @param speed the unadjusted speed. 1706 * @return - throttle setting (a percentage) 1707 */ 1708 private synchronized float getThrottleSettingFromSpeed(float speed) { 1709 if (useSpeedProfile) { 1710 float throttleSetting = _activeTrain.getRosterEntry().getSpeedProfile() 1711 .getThrottleSettingFromSignalMapSpeed(speed, getForward()); 1712 return throttleSetting; 1713 } 1714 if (_activeTrain.getSignalType() == DispatcherFrame.SIGNALMAST) { 1715 float mls; 1716 if (_controllingSignalMast != null) { 1717 mls = _controllingSignalMast.getSignalSystem().getMaximumLineSpeed(); 1718 } else { 1719 //plan B 1720 mls = _dispatcher.getMaximumLineSpeed(); 1721 } 1722 float throttleSetting = (speed / mls); 1723 return throttleSetting; 1724 } else { 1725 return speed/100.0f; 1726 } 1727 } 1728 1729 1730 /** 1731 * sets the throttle based on an index number into _speedRatio array 1732 * @param speedState Index value 1733 */ 1734 private synchronized void setTargetSpeedState(int speedState) { 1735 setTargetSpeedState(speedState,false); 1736 } 1737 1738 /** 1739 * sets the throttle based on an index number into _speedRatio array 1740 * @param speedState Index value 1741 * @param stopBySpeedProfile if true use speed profile 1742 */ 1743 private synchronized void setTargetSpeedState(int speedState,boolean stopBySpeedProfile) { 1744 log.trace("{}: setTargetSpeedState:({})",_activeTrain.getTrainName(),speedState); 1745 _autoEngineer.slowToStop(false); 1746 float stoppingDistanceAdjust = _stopBySpeedProfileAdjust * 1747 ( _activeTrain.isTransitReversed() ? 1748 _currentAllocatedSection.getTransitSection().getRevStopPerCent() : 1749 _currentAllocatedSection.getTransitSection().getFwdStopPerCent()) ; 1750 log.debug("stoppingDistanceAdjust[{}] isReversed[{}] stopBySpeedProfileAdjust[{}]",stoppingDistanceAdjust, 1751 _activeTrain.isTransitReversed(),_stopBySpeedProfileAdjust ); 1752 if (speedState > STOP_SPEED) { 1753 cancelStopInCurrentSection(); 1754 if (_currentRampRate == RAMP_SPEEDPROFILE && useSpeedProfile) { 1755 // we are going to ramp up / down using section length and speed profile 1756 _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock) 1757 * stoppingDistanceAdjust, speedState); 1758 } else { 1759 setTargetSpeed(_speedRatio[speedState]); 1760 } 1761 } else if (stopBySpeedProfile) { 1762 // we are going to stop by profile 1763 _stoppingUsingSpeedProfile = true; 1764 _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock) 1765 * stoppingDistanceAdjust, 0.0f); 1766 } else { 1767 _autoEngineer.setHalt(true); 1768 setTargetSpeed(0.0f); 1769 } 1770 } 1771 1772 private synchronized void setTargetSpeedByProfile(float speedState, float stopBySpeedProfileAdjust, boolean cancelStopping) { 1773 // the speed comes in as units of warrents (mph, kph, mm/s etc) 1774 try { 1775 float throttleSetting = _activeTrain.getRosterEntry().getSpeedProfile().getThrottleSettingFromSignalMapSpeed(speedState, getForward()); 1776 log.debug("{}: setTargetSpeedByProfile: {} SpeedState[{}]", 1777 _activeTrain.getTrainName(), 1778 throttleSetting, 1779 speedState); 1780 if (throttleSetting > 0.009 && _currentRampRate != RAMP_SPEEDPROFILE && useSpeedProfile) { 1781 if (cancelStopping) {cancelStopInCurrentSection();} 1782 setTargetSpeed(throttleSetting); // apply speed factor and max 1783 } else if (throttleSetting > 0.009) { 1784 if (cancelStopping) {cancelStopInCurrentSection();} 1785 setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock) * stopBySpeedProfileAdjust , throttleSetting); 1786 } else if (useSpeedProfile && _stopBySpeedProfile) { 1787 setTargetSpeed(0.0f); 1788 _stoppingUsingSpeedProfile = true; 1789 _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock) * stopBySpeedProfileAdjust, 0.0f); 1790 } else { 1791 _autoEngineer.slowToStop(false); 1792 setTargetSpeed(0.0f); 1793 _autoEngineer.setHalt(true); 1794 } 1795 } catch (Exception ex) { 1796 log.error("setTargetSpeedByProfile crashed - Emergency Stop: ", ex ); 1797 _autoEngineer.slowToStop(false); 1798 setTargetSpeed(-1.0f); 1799 _autoEngineer.setHalt(true); 1800 } 1801 } 1802 1803 /** 1804 * Pass in speed as shown on dialogs, and convert to decimal speed needed by 1805 * throttle. 1806 */ 1807 private synchronized void setTargetSpeedValue(float speed) { 1808 log.debug("{}: setTargetSpeedValue: Speed[{}]",_activeTrain.getTrainName(),speed); 1809 if (useSpeedProfile) { 1810 setTargetSpeedByProfile(speed,_stopBySpeedProfileAdjust,true); 1811 return; 1812 } 1813 _autoEngineer.slowToStop(false); 1814 float mls; 1815 if (_controllingSignalMast != null) { 1816 mls = _controllingSignalMast.getSignalSystem().getMaximumLineSpeed(); 1817 } else { 1818 mls = _dispatcher.getMaximumLineSpeed(); 1819 } 1820 float decSpeed = (speed / mls); 1821 if (decSpeed > 0.0f) { 1822 cancelStopInCurrentSection(); 1823 setTargetSpeed(decSpeed); 1824 } else { 1825 setTargetSpeed(0.0f); 1826 _autoEngineer.setHalt(true); 1827 } 1828 } 1829 1830 private int getBlockLength(Block b) { 1831 if (b == null) { 1832 return (0); 1833 } 1834 return (int) b.getLengthMm(); 1835// float fLength = b.getLengthMm() / (float) _dispatcher.getScale().getScaleFactor(); 1836// if (_dispatcher.getUseScaleMeters()) { 1837// return (int) (fLength * 0.001f); 1838// } 1839// return (int) (fLength * 0.00328084f); 1840 } 1841 1842 /** 1843 * Initiates running in manual mode with external throttle. 1844 * <p> 1845 * This method is triggered by an action in the Transit. The throttle in use 1846 * for automatic operation is dispatched. 1847 */ 1848 protected void initiateWorking() { 1849 if (_activeTrain.getStatus() != ActiveTrain.WORKING) { 1850 _activeTrain.setMode(ActiveTrain.DISPATCHED); 1851 _activeTrain.setStatus(ActiveTrain.WORKING); 1852 saveSpeedAndDirection(); 1853 if (_autoEngineer != null) { 1854 _autoEngineer.setHalt(true); 1855 waitUntilStopped(); 1856 _autoEngineer.abort(); 1857 InstanceManager.throttleManagerInstance().releaseThrottle(_throttle, this); 1858 _autoEngineer = null; 1859 _throttle = null; 1860 } 1861 } 1862 } 1863 1864 /** 1865 * Returns when train is stopped. 1866 * <p> 1867 * Note: Provides for _autoEngineer becoming null during wait Ties up the 1868 * current autoActiveTrain thread. 1869 */ 1870 protected void waitUntilStopped() { 1871 boolean doneWaiting = false; 1872 while (!doneWaiting) { 1873 if (_autoEngineer != null) { 1874 doneWaiting = _autoEngineer.isStopped(); 1875 } else { 1876 doneWaiting = true; 1877 } 1878 if (!doneWaiting) { 1879 try { 1880 Thread.sleep(50); 1881 } catch (InterruptedException e) { 1882 // ignore this exception 1883 } 1884 } 1885 } 1886 } 1887 1888 /** 1889 * Resumes automatic running after a working session using an external 1890 * throttle This method is triggered by the dispatcher hitting the "Resume 1891 * Auto Running" button A new throttle is acquired to allow automatic 1892 * running to resume 1893 */ 1894 protected void resumeAutomaticRunning() { 1895 if ((_activeTrain.getStatus() == ActiveTrain.WORKING) 1896 || (_activeTrain.getStatus() == ActiveTrain.READY)) { 1897 _autoTrainAction.cancelDoneSensor(); 1898 if (initialize()) { 1899 _resumingAutomatic = true; 1900 } else { 1901 log.error("Failed to initialize throttle when resuming automatic mode."); 1902 } 1903 } 1904 } 1905 1906 /** 1907 * Pause the auto active train for a specified number of fast clock minutes. 1908 * 1909 * @param fastMinutes the number of minutes to pause the train 1910 * @return the thread waiting on the pause or null if already paused 1911 */ 1912 public Thread pauseTrain(int fastMinutes) { 1913 if (_pausingActive) { 1914 // if a pause train thread is currently active, ignore this call 1915 return (null); 1916 } 1917 Runnable pauseTrain = new PauseTrain(fastMinutes); 1918 Thread tPause = jmri.util.ThreadingUtil.newThread(pauseTrain, "pause train " + _activeTrain.getTrainName()); 1919 tPause.start(); 1920 return tPause; 1921 } 1922 1923 public void terminate() { 1924 // here add code to stop the train and release its throttle if it is in autoRun 1925 while (_activeHornThreads > 0) { 1926 try { 1927 Thread.sleep(50); 1928 } catch (InterruptedException e) { 1929 // ignore this exception 1930 } 1931 } 1932 _autoTrainAction.clearRemainingActions(); 1933 if (_autoEngineer != null) { 1934 _autoEngineer.setHalt(true); 1935 try { 1936 Thread.sleep(50); 1937 } catch (InterruptedException e) { 1938 // ignore this exception 1939 } 1940 waitUntilStopped(); 1941 _autoEngineer.abort(); 1942 InstanceManager.throttleManagerInstance().releaseThrottle(_throttle, this); 1943 } 1944 } 1945 1946 public void dispose() { 1947 if (_controllingSignalMast != null && _conSignalMastListener != null) { 1948 _controllingSignalMast.removePropertyChangeListener(_conSignalMastListener); 1949 } 1950 _controllingSignalMast = null; 1951 _conSignalMastListener = null; 1952 } 1953 1954// _________________________________________________________________________________________ 1955 // This class waits for train stop in a separate thread 1956 class WaitForTrainToStop implements Runnable { 1957 1958 public WaitForTrainToStop(int task) { 1959 _task = task; 1960 } 1961 1962 @Override 1963 public void run() { 1964 boolean waitingOnTrain = true; 1965 try { 1966 while (waitingOnTrain) { 1967 if ((getAutoEngineer() != null) && (getAutoEngineer().isStopped())) { 1968 waitingOnTrain = false; 1969 } else { 1970 Thread.sleep(_delay); 1971 } 1972 } 1973 log.trace("executing task[{}]",_task); 1974 executeStopTasks(_task); 1975 } catch (InterruptedException e) { 1976 log.warn("Waiting for train to stop interrupted - stop tasks not executing"); 1977 } catch (Exception e) { 1978 log.error("Waiting for train to stop crashed - stop tasks not executing.", e); 1979 } 1980 } 1981 1982 private final int _delay = 91; 1983 private int _task = 0; 1984 } 1985 1986 /** 1987 * Pause the train in a separate thread. Train is stopped, then restarted 1988 * after specified number of fast Minutes have elapsed. 1989 */ 1990 class PauseTrain implements Runnable { 1991 /** 1992 * Create a PauseTrain 1993 * 1994 * @param fastMinutes the number of fast clock minutes to pause the 1995 * train 1996 */ 1997 public PauseTrain(int fastMinutes) { 1998 _fastMinutes = fastMinutes; 1999 } 2000 2001 @Override 2002 public void run() { 2003 // set to pause at a fast ramp rate 2004 _pausingActive = true; 2005 // TODO: use stop in section or block? 2006 _savedRampRate = getRampRate(); 2007 setCurrentRampRate(RAMP_FAST); 2008 stopInCurrentSection(NO_TASK); 2009 // wait for train to stop 2010 boolean waitNow = true; 2011 boolean keepGoing = true; 2012 while (waitNow) { 2013 try { 2014 Thread.sleep(101); 2015 if (_autoEngineer != null) { 2016 if (_autoEngineer.isStopped()) { 2017 waitNow = false; 2018 } 2019 } else { 2020 waitNow = false; 2021 } 2022 } catch (InterruptedException e) { 2023 log.trace("InterruptedException while waiting to stop for pause-indicates action cancelled.", e); 2024 waitNow = false; 2025 keepGoing = false; 2026 } 2027 } 2028 _activeTrain.setStatus(ActiveTrain.PAUSED); 2029 if (keepGoing) { 2030 // wait for specified fast clock time 2031 Timebase _clock = InstanceManager.getDefault(jmri.Timebase.class); 2032 java.beans.PropertyChangeListener _clockListener = (java.beans.PropertyChangeEvent e) -> { 2033 _fastMinutes--; 2034 }; 2035 _clock.addMinuteChangeListener(_clockListener); 2036 // wait for fast minutes to tick away 2037 waitNow = true; 2038 while (waitNow) { 2039 try { 2040 Thread.sleep(501); 2041 if (_fastMinutes <= 0) { 2042 waitNow = false; 2043 } 2044 } catch (InterruptedException e) { 2045 log.trace("InterruptedException indicates action cancelled.", e); 2046 keepGoing = false; 2047 } 2048 } 2049 _clock.removeMinuteChangeListener(_clockListener); 2050 } 2051 _pausingActive = false; 2052 if (keepGoing) { 2053 // this thread was not interrupted 2054 // resume running - restore speed, status, and ramp rate 2055 setCurrentRampRate(_savedRampRate); 2056 // Set speed by signal also works if signal missing 2057 // so we dont need to restore a previous value. 2058 _activeTrain.setStatus(ActiveTrain.RUNNING); 2059 setSpeedBySignal(); 2060 } 2061 } 2062 private int _fastMinutes = 0; 2063 private int _savedRampRate = RAMP_NONE; 2064 } 2065 2066 // _________________________________________________________________________________________ 2067 // this class handles the interface with the throttle 2068 // (This class started from code by Pete Cressman contained in Warrant.java.) 2069 class AutoEngineer { 2070 2071 AutoEngineer(DccThrottle throttle, RosterEntry rosterEntry) { 2072 this.throttle = throttle; 2073 this.rosterEntry = rosterEntry; 2074 } 2075 2076 private DccThrottle throttle; 2077 private int ramping; 2078 private boolean speedProfileStoppingIsRunning = false; 2079 private float speedIncrement = 0.0f; //will be recalculated 2080 private float targetSpeed; 2081 private RosterEntry rosterEntry; 2082 private int throttleInterval; 2083 private float minReliableOperatingSpeed; 2084 private float maxSpeed; 2085 private float speedFactor; 2086 2087 public void setRamping(int ramping, int fullRampTime, int minThrottleInterval, int rampRate) { 2088 this.ramping = ramping; 2089 this.throttleInterval = minThrottleInterval; 2090 //calculate speed increment to use in each minInterval time 2091 speedIncrement = (100.0f / ((float) fullRampTime / minThrottleInterval) 2092 / rampRate) / 100.0f; 2093 log.debug("{}: _speedIncrement={}", throttle.getLocoAddress(), speedIncrement); 2094 } 2095 2096 public void setIsForward(boolean isForward) { 2097 throttle.setIsForward(isForward); 2098 } 2099 2100 public boolean getIsForward() { 2101 return(throttle.getIsForward()); 2102 } 2103 2104 public void setTargetSpeed(float speed) { 2105 stopAllTimers(); 2106 targetSpeed = applyMaxThrottleAndFactor(speed); 2107 log.debug("setTargetSpeed: Set Speed[{}] adjusted to TargetSpeed[{}] ",speed,targetSpeed); 2108 if (ramping == RAMP_NONE || ramping == RAMP_SPEEDPROFILE ) { 2109 throttle.setSpeedSetting(targetSpeed); 2110 } else { 2111 rampToTarget(); 2112 } 2113 } 2114 2115 public float getTargetSpeed(){ 2116 return(targetSpeed); 2117 } 2118 2119 /** 2120 * 2121 * @param throttleSetting the throttle setting that would normally be set 2122 * @return the adjusted throttle setting after applying Max Throttle and Percentage throttle settings 2123 */ 2124 private float applyMaxThrottleAndFactor(float throttleSetting) { 2125 if (throttleSetting > 0.0f) { 2126 if ((throttleSetting * speedFactor) > maxSpeed) { 2127 return maxSpeed; 2128 } 2129 if ((throttleSetting * speedFactor) < minReliableOperatingSpeed) { 2130 return minReliableOperatingSpeed; 2131 } 2132 return (throttleSetting * speedFactor); //adjust for train's Speed Factor 2133 } else { 2134 return throttleSetting; 2135 } 2136 } 2137 2138 /** 2139 * Flag from user's control. 2140 * 2141 * @param halt true to immediately stop the train; false otherwise 2142 */ 2143 public void setHalt(boolean halt) { 2144 if (halt) { 2145 this.setSpeedImmediate(0.0f); 2146 } 2147 } 2148 2149 /** 2150 * Set the limits and adjustment factore for train speed. 2151 * Active train will calculate the required setting and it will be adjusted if not 0.0f 2152 * required setting * speed Factor then test for less than max and greater than min. 2153 * @param minReliableOperatingSpeed lowest throttle % train will reliably move. 2154 * @param maxSpeed max throttle % for train. 2155 * @param speedFactor multiplier 2156 */ 2157 public void setSpeedLimits(float minReliableOperatingSpeed, float maxSpeed, float speedFactor) { 2158 this.minReliableOperatingSpeed = minReliableOperatingSpeed; 2159 this.maxSpeed = maxSpeed; 2160 this.speedFactor = speedFactor; 2161 } 2162 2163 public void setTargetSpeed(float distance, float speed) { 2164 log.debug("Set Target Speed[{}] with distance{{}] from speed[{}]",speed,distance,throttle.getSpeedSetting()); 2165 stopAllTimers(); 2166 if (rosterEntry != null) { 2167 rosterEntry.getSpeedProfile().setExtraInitialDelay(1500f); 2168 rosterEntry.getSpeedProfile().setMinMaxLimits(minReliableOperatingSpeed, maxSpeed); 2169 rosterEntry.getSpeedProfile().changeLocoSpeed(_throttle, distance, speed); 2170 speedProfileStoppingIsRunning = true; 2171 targetSpeed = speed; 2172 } else { 2173 setTargetSpeed((0.0f)); 2174 } 2175 } 2176 2177 public void slowToStop(boolean on) { 2178 stopAllTimers(); 2179 if (on) { 2180 log.debug("SlowToStopOn"); 2181 setTargetSpeed((0.0f)); 2182 } 2183 } 2184 2185 public void stopAllTimers() { 2186 if (speedProfileStoppingIsRunning) { 2187 re.getSpeedProfile().cancelSpeedChange(); 2188 speedProfileStoppingIsRunning = false; 2189 } 2190 if (rampingTimer != null) { 2191 rampingTimer.stop(); 2192 rampingTimer = null; 2193 } 2194 } 2195 2196 LinkedList<SpeedSetting> stepQueue; 2197 private javax.swing.Timer rampingTimer; 2198 2199 private void rampToTarget() { 2200 // target already adjusted. 2201 log.debug("RampToTarget[{}]current[{}]", getTargetSpeed(), throttle.getSpeedSetting()); 2202 stepQueue = new LinkedList<>(); 2203 if (throttle.getSpeedSetting() == getTargetSpeed()) { 2204 return; 2205 } else if (throttle.getSpeedSetting() < getTargetSpeed()) { 2206 // Up 2207 float newSpeed = throttle.getSpeedSetting(); 2208 if (newSpeed < minReliableOperatingSpeed) { 2209 stepQueue.add(new SpeedSetting(minReliableOperatingSpeed, throttleInterval)); 2210 newSpeed = minReliableOperatingSpeed; 2211 } 2212 while (newSpeed < getTargetSpeed()) { 2213 newSpeed += speedIncrement; 2214 if (newSpeed > getTargetSpeed()) { 2215 newSpeed = getTargetSpeed(); 2216 } 2217 log.trace("NewSpeedUp[{}]", newSpeed); 2218 stepQueue.add(new SpeedSetting(newSpeed, throttleInterval)); 2219 } 2220 } else { 2221 // Down 2222 boolean andStop = false; 2223 if (getTargetSpeed() <= 0.0f) { 2224 andStop = true; 2225 } 2226 float newSpeed = throttle.getSpeedSetting(); 2227 while (newSpeed > getTargetSpeed()) { 2228 newSpeed -= speedIncrement; 2229 if (newSpeed < getTargetSpeed()) { 2230 newSpeed = getTargetSpeed(); 2231 } 2232 log.trace("NewSpeedDown[{}]", newSpeed); 2233 stepQueue.add(new SpeedSetting(newSpeed, throttleInterval)); 2234 } 2235 if (andStop) { 2236 stepQueue.add(new SpeedSetting(0.0f, throttleInterval)); 2237 } 2238 } 2239 if (rampingTimer == null) { //If this is the first time round then kick off the speed change 2240 setNextStep(); 2241 } 2242 } 2243 2244 private void finishChange() { 2245 if (rampingTimer != null) { 2246 rampingTimer.stop(); 2247 } 2248 rampingTimer = null; 2249 stepQueue.clear(); 2250 stepQueue = null; 2251 } 2252 2253 synchronized void setNextStep() { 2254 if (stepQueue.isEmpty()) { 2255 log.trace("Empty"); 2256 finishChange(); 2257 return; 2258 } 2259 SpeedSetting ss = stepQueue.getFirst(); 2260 if (ss.getDuration() == 0) { 2261 log.trace("Duratiom Zero"); 2262 finishChange(); 2263 return; 2264 } 2265 stepQueue.removeFirst(); 2266 log.trace("Set New Speed[{}]",ss.getSpeedStep()); 2267 throttle.setSpeedSetting(ss.getSpeedStep()); 2268 rampingTimer = new javax.swing.Timer(ss.getDuration(), (java.awt.event.ActionEvent e) -> { 2269 setNextStep(); 2270 }); 2271 rampingTimer.setRepeats(false); 2272 rampingTimer.start(); 2273 } 2274 2275 private class SpeedSetting { 2276 2277 float step = 0.0f; 2278 int duration = 0; 2279 2280 SpeedSetting(float step, int duration) { 2281 this.step = step; 2282 this.duration = duration; 2283 } 2284 2285 float getSpeedStep() { 2286 return step; 2287 } 2288 2289 int getDuration() { 2290 return duration; 2291 } 2292 } 2293 2294 /** 2295 * Set the train speed directly, bypassing ramping. 2296 * 2297 * @param speed 0.0 (stop) to 1.0 (full) 2298 */ 2299 public synchronized void setSpeedImmediate(float speed) { 2300 log.trace("{}: setting speed directly to {}%", _activeTrain.getTrainName(), (int) (speed * 100)); 2301 stopAllTimers(); 2302 targetSpeed = applyMaxThrottleAndFactor(speed); 2303 throttle.setSpeedSetting(targetSpeed); 2304 } 2305 2306 /** 2307 * Check if train is moving or stopped. 2308 * 2309 * @return true if stopped; false otherwise 2310 */ 2311 public synchronized boolean isStopped() { 2312 // when stopping by speed profile you must refresh the throttle speed. 2313 return throttle.getSpeedSetting() <= 0.0004f; 2314 } 2315 2316 /** 2317 * Check if train is moving at its current requested speed. 2318 * 2319 * @return true if at requested speed; false otherwise 2320 */ 2321 public synchronized boolean isAtSpeed() { 2322 return java.lang.Math.abs(throttle.getSpeedSetting() - targetSpeed) <= 0.01; 2323 } 2324 2325 /** 2326 * Flag from user to end run. 2327 */ 2328 public void abort() { 2329 stopAllTimers(); 2330 } 2331 2332 protected void setFunction(int cmdNum, boolean isSet) { 2333 throttle.setFunction(cmdNum, isSet); 2334 } 2335 } 2336 2337 /** 2338 * Convert ramp rate name, stored as a string into the constant value 2339 * assigned. 2340 * 2341 * @param rampRate name of ramp rate, such as "RAMP_FAST" 2342 * @return integer representing a ramprate constant value 2343 */ 2344 public static int getRampRateFromName(String rampRate) { 2345 if (rampRate.equals(Bundle.getMessage("RAMP_FAST"))) { 2346 return RAMP_FAST; 2347 } else if (rampRate.equals(Bundle.getMessage("RAMP_MEDIUM"))) { 2348 return RAMP_MEDIUM; 2349 } else if (rampRate.equals(Bundle.getMessage("RAMP_MED_SLOW"))) { 2350 return RAMP_MED_SLOW; 2351 } else if (rampRate.equals(Bundle.getMessage("RAMP_SLOW"))) { 2352 return RAMP_SLOW; 2353 } else if (rampRate.equals(Bundle.getMessage("RAMP_SPEEDPROFILE"))) { 2354 return RAMP_SPEEDPROFILE; 2355 } 2356 return RAMP_NONE; 2357 } 2358 2359 /* 2360 * Listener for switching Ghost blocks to unoccupied 2361 */ 2362 static class DarkTerritoryListener implements PropertyChangeListener { 2363 private Sensor sensor; 2364 2365 public DarkTerritoryListener(Sensor sensor) { 2366 this.sensor = sensor; 2367 log.trace("Sensor[{}]",sensor.getDisplayName()); 2368 } 2369 2370 @Override 2371 public void propertyChange(PropertyChangeEvent e) { 2372 if (e.getPropertyName().equals("state")) { 2373 ((Block) e.getSource()).removePropertyChangeListener(this); 2374 if (e.getNewValue().equals(Block.UNOCCUPIED)) { 2375 try { 2376 log.trace("Sensor INACTIVE[{}]", sensor.getDisplayName()); 2377 sensor.setKnownState(Sensor.INACTIVE); 2378 } catch (jmri.JmriException ex) { 2379 log.error("Error leaving darkterratory"); 2380 } 2381 } 2382 } 2383 } 2384 } 2385 2386 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AutoActiveTrain.class); 2387}