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