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