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