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