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