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