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