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