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 // get slowest speed of any entered and not released section. 998 // This then covers off HEADONLY. 999 for (AllocatedSection asE : _activeTrain.getAllocatedSectionList()) { 1000 if (asE.getEntered()) { 1001 for (Block b : asE.getSection().getBlockList()) { 1002 if (getSpeedFromBlock(b) < newSpeed) { 1003 newSpeed = getSpeedFromBlock(b); 1004 } 1005 } 1006 } 1007 } 1008 // see if needs to slow for next block. 1009 if (newSpeed > 0 && _nextBlock != null) { 1010 float speed = getSpeedFromBlock(_nextBlock); 1011 if (speed < newSpeed) { 1012 // slow for next block 1013 newSpeed = speed; 1014 } 1015 } 1016 } 1017 if (newSpeed > 0) { 1018 log.trace("setSpeedBySectionsAllocated isStopping[{}]",isStopping()); 1019 cancelStopInCurrentSection(); 1020 setTargetSpeed(getThrottleSettingFromSpeed(newSpeed)); 1021 } else { 1022 stopInCurrentSection(NO_TASK); 1023 } 1024 } 1025 1026 /** 1027 * Check that all turnouts in a section have finished setting 1028 * for passage. If not listens on first bad turnout 1029 * and rechecks when set. 1030 * @param as Allocated section whose turnouts need to be checked. 1031 * @return true if no errors else false 1032 */ 1033 private boolean checkTurn(AllocatedSection as) { 1034 if (as != null && as.getAutoTurnoutsResponse() != null) { 1035 Turnout to = InstanceManager.getDefault(DispatcherFrame.class).getAutoTurnoutsHelper().checkStateAgainstList(as.getAutoTurnoutsResponse()); 1036 if (to != null) { 1037 // at least one turnout isnt correctly set 1038 to.addPropertyChangeListener(_turnoutStateListener = (PropertyChangeEvent e) -> { 1039 if (e.getPropertyName().equals("KnownState")) { 1040 ((Turnout) e.getSource()).removePropertyChangeListener(_turnoutStateListener); 1041 setSpeedBySignal(); 1042 } 1043 }); 1044 return false; 1045 } 1046 } 1047 return true; 1048 } 1049 1050 private void setSpeedBySignalMast() { 1051 //Set speed using SignalMasts; 1052 String displayedAspect = _controllingSignalMast.getAspect(); 1053 if (log.isTraceEnabled()) { 1054 log.trace("{}: Controlling mast {} ({})", _activeTrain.getTrainName(), _controllingSignalMast.getDisplayName(USERSYS), displayedAspect); 1055 if (_conSignalProtectedBlock == null) { 1056 log.trace("{}: Protected block is null", _activeTrain.getTrainName()); 1057 } else { 1058 log.trace("{}: Protected block: {} state: {} speed: {}", _activeTrain.getTrainName(), 1059 _conSignalProtectedBlock.getSensor().getDisplayName(USERSYS), 1060 (_conSignalProtectedBlock.getSensor().getState() == Block.OCCUPIED ? "OCCUPIED" : "NOT OCCUPIED"), 1061 _conSignalProtectedBlock.getBlockSpeed()); 1062 } 1063 } 1064 1065 if ((_controllingSignalMast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER).equals(displayedAspect)) 1066 || !_controllingSignalMast.getLit() || _controllingSignalMast.getHeld()) { 1067 checkForSignalPassedOrStop(_controllingSignalMast.getDisplayName(USERSYS)); 1068 } else if (_controllingSignalMast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.PERMISSIVE) != null 1069 && _controllingSignalMast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.PERMISSIVE).equals(displayedAspect)) { 1070 setTargetSpeedState(RESTRICTED_SPEED); 1071 _activeTrain.setStatus(ActiveTrain.RUNNING); 1072 } else { 1073 1074 //if using signalmasts, set speed to lesser of aspect speed and signalmastlogic speed 1075 // (minimum speed on the path to next signal, using turnout and block speeds) 1076 String aspectSpeedStr = (String) _controllingSignalMast.getSignalSystem().getProperty(displayedAspect, "speed"); 1077 log.trace("{}: Signal {} speed {} for aspect {}", _activeTrain.getTrainName(), _controllingSignalMast.getDisplayName(USERSYS), aspectSpeedStr, displayedAspect); 1078 float speed = -1.0f; 1079 if (aspectSpeedStr != null) { 1080 try { 1081 speed = Float.parseFloat(aspectSpeedStr); 1082 } catch (NumberFormatException nx) { 1083 try { 1084 speed = InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(aspectSpeedStr); 1085 log.trace("{}: Signal {} speed from map for {} is {}", _activeTrain.getTrainName(), _controllingSignalMast.getDisplayName(USERSYS), aspectSpeedStr, speed); 1086 } catch (IllegalArgumentException ex) { 1087 //Considered Normal if the speed does not appear in the map 1088 log.trace("{}: Speed not found {}", _activeTrain.getTrainName(), aspectSpeedStr); 1089 } 1090 } 1091 } 1092 int aspectSpeed = (int) speed; //save for debug message 1093 1094 //get maximum speed for the route between current and next signalmasts 1095 float smLogicSpeed = -1.0f; 1096 String smDestinationName = "unknown"; 1097 SignalMastLogic smLogic = InstanceManager.getDefault(SignalMastLogicManager.class).getSignalMastLogic(_controllingSignalMast); 1098 if (smLogic != null) { 1099 SignalMast smDestination = smLogic.getActiveDestination(); 1100 if (smDestination != null) { 1101 smDestinationName = smDestination.getDisplayName(USERSYS); 1102 smLogicSpeed = (int) smLogic.getMaximumSpeed(smDestination); 1103 } 1104 } 1105 1106 //use the smaller of aspect speed or route speed 1107 if (smLogicSpeed > -1.0f && smLogicSpeed < speed) { 1108 speed = smLogicSpeed; 1109 } 1110 1111 log.debug("{}: {}({}) {}({}), Dest: {}, path max: {}", 1112 _activeTrain.getTrainName(), 1113 _controllingSignalMast.getDisplayName(USERSYS), displayedAspect, aspectSpeedStr, aspectSpeed, 1114 smDestinationName, (int) smLogicSpeed); 1115 1116 if (speed > -1.0f) { 1117 /* We should work on the basis that the speed required in the current block/section is governed by the signalmast 1118 that we have passed and not the one we are approaching when we are accelerating. 1119 However when we are decelerating we should be aiming to meet the speed required by the approaching signalmast 1120 whether that is to slow down or come to a complete stand still. 1121 */ 1122 if (prevSpeed == -1 || speed < prevSpeed) { 1123 log.debug("{}: Signal {} setting speed to {} for next", _activeTrain.getTrainName(), 1124 _controllingSignalMast.getDisplayName(USERSYS), speed); 1125 setTargetSpeedValue(speed); 1126 } else { 1127 log.debug("{}: Signal {} setting speed to {} for previous", _activeTrain.getTrainName(), 1128 _controllingSignalMast.getDisplayName(USERSYS), speed); 1129 setTargetSpeedValue(prevSpeed); 1130 } 1131 prevSpeed = speed; 1132 _activeTrain.setStatus(ActiveTrain.RUNNING); 1133 1134 } else { 1135 log.warn("{}: No specific speeds found so will use the default", _activeTrain.getTrainName()); 1136 setTargetSpeedState(NORMAL_SPEED); 1137 _activeTrain.setStatus(ActiveTrain.RUNNING); 1138 } 1139 } 1140 } 1141 1142 private void setSpeedBySignalHead() { 1143 // a held signal always stop 1144 if ( _controllingSignal != null && _controllingSignal.getAppearance() == SignalHead.HELD ) { 1145 // Held - Stop 1146 stopInCurrentSection(NO_TASK); 1147 return; 1148 } 1149 1150 if (useSpeedProfile) { 1151 // find speed from signal. 1152 // find speed from block 1153 // use least 1154 float blockSpeed = getSpeedFromBlock(_conSignalProtectedBlock); 1155 1156 float signalSpeed; 1157 String signalSpeedName; 1158 String displayedAspect = _controllingSignal.getAppearanceName(); 1159 try { 1160 signalSpeedName = 1161 InstanceManager.getDefault(SignalSpeedMap.class).getAppearanceSpeed(displayedAspect); 1162 signalSpeed = InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(signalSpeedName); 1163 } catch (Throwable ex) { // if _anything_ goes wrong, contain it 1164 signalSpeed = -1.0f; 1165 log.warn("{}: Block {} AppearanceSpeed {} not found in SignalSpeedMap", 1166 _activeTrain.getTrainName(), _conSignalProtectedBlock.getDisplayName(USERSYS), displayedAspect); 1167 } 1168 float useSpeed; 1169 if (blockSpeed < signalSpeed) { 1170 useSpeed = blockSpeed; 1171 } else { 1172 useSpeed = signalSpeed; 1173 } 1174 1175 log.trace("BlockSpeed[{}] SignalSpeed[{}]", blockSpeed, signalSpeed); 1176 if (useSpeed < 0.01f) { 1177 checkForSignalPassedOrStop(_controllingSignal.getDisplayName(USERSYS)); 1178 } else { 1179 setTargetSpeedByProfile(useSpeed); 1180 } 1181 } else { 1182 switch (_controllingSignal.getAppearance()) { 1183 case SignalHead.DARK: 1184 case SignalHead.RED: 1185 case SignalHead.FLASHRED: 1186 // May get here from signal changing before Block knows it is occupied, so must 1187 // check Block occupancy sensor, which must change before signal. 1188 // check to to see if its allocated to us!!! 1189 // check Block occupancy sensor if it is in an allocated block, which must change before signal 1190 // If the train has no _currentAllocatedSection it is in a first block outside transit. 1191 checkForSignalPassedOrStop(_controllingSignal.getDisplayName(USERSYS)); 1192 break; 1193 case SignalHead.YELLOW: 1194 case SignalHead.FLASHYELLOW: 1195 setTargetSpeedState(SLOW_SPEED); 1196 _activeTrain.setStatus(ActiveTrain.RUNNING); 1197 break; 1198 case SignalHead.GREEN: 1199 case SignalHead.FLASHGREEN: 1200 setTargetSpeedState(NORMAL_SPEED); 1201 _activeTrain.setStatus(ActiveTrain.RUNNING); 1202 break; 1203 case SignalHead.LUNAR: 1204 case SignalHead.FLASHLUNAR: 1205 setTargetSpeedState(RESTRICTED_SPEED); 1206 _activeTrain.setStatus(ActiveTrain.RUNNING); 1207 break; 1208 default: 1209 log.warn("Signal Head[{}] has invalid Appearence - using stop",_controllingSignal.getAppearance()); 1210 stopInCurrentSection(NO_TASK); 1211 } 1212 1213 } 1214 } 1215 1216 /** 1217 * Check to see if a stop is really required, or if this is the 1218 * signal head that was just passed, in which case ignore as the signal goes red before a 1219 * new signal exists. 1220 * 1221 * @param displayName name of signal for debug messages. 1222 */ 1223 private void checkForSignalPassedOrStop(String displayName) { 1224 // if current section is null we are in a pre transit block. 1225 if (_currentAllocatedSection != null) { 1226 if ((_currentAllocatedSection.isInActiveBlockList(_conSignalProtectedBlock) || 1227 (_nextSection != null && _activeTrain.isInAllocatedList(_nextSection) && _nextSection.containsBlock(_conSignalProtectedBlock))) 1228 && _conSignalProtectedBlock.getSensor().getState() == Block.OCCUPIED) { 1229 // Train has just passed this signal - ignore this signal 1230 log.debug("{}: _conSignalProtectedBlock [{}] for signal [{}] is the block just past so ignore.", _activeTrain.getTrainName(), 1231 _conSignalProtectedBlock.getDisplayName(USERSYS), displayName); 1232 } else { 1233 log.debug("{}: stopping for signal [{}] ", _activeTrain.getTrainName(), 1234 displayName); 1235 stopInCurrentSection(NO_TASK); 1236 } 1237 } 1238 } 1239 1240 protected float getSpeedFromBlock(Block block) { 1241 String blockSpeedName = block.getBlockSpeed(); 1242 if (blockSpeedName.contains("Global")) { 1243 blockSpeedName = InstanceManager.getDefault(BlockManager.class).getDefaultSpeed(); 1244 } 1245 float blockSpeed = -1.0f; 1246 if (!blockSpeedName.isEmpty()) { 1247 try { 1248 blockSpeed = Float.parseFloat(blockSpeedName); 1249 } catch (NumberFormatException nx) { 1250 try { 1251 blockSpeed = InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(blockSpeedName); 1252 log.debug("{} {}: block speed from map for {} is {}", 1253 _activeTrain.getTrainName(), block.getDisplayName(USERSYS), blockSpeedName, 1254 blockSpeed); 1255 } catch (Throwable ex) { // if _anything_ goes wrong, contain it 1256 //Considered Normal if the speed does not appear in the map 1257 log.warn("{}: Block {} Speed {} not found in SignalSpeedMap", 1258 _activeTrain.getTrainName(), block.getDisplayName(USERSYS), blockSpeed); 1259 } 1260 } 1261 } 1262 return blockSpeed; 1263 } 1264 1265 float prevSpeed = -1.0f; 1266 1267 // called to cancel a stopping action that is in progress 1268 private synchronized void cancelStopInCurrentSection() { 1269 log.trace("[{}]:Cancel Stopping", _activeTrain.getTrainName()); 1270 cancelStoppingBySensor(); 1271 _stoppingByBlockOccupancy = false; 1272 _stoppingBlock = null; 1273 _stoppingUsingSpeedProfile = false; 1274 _stoppingBlock = null; 1275 _autoEngineer.slowToStop(false); 1276 } 1277 1278 private synchronized void stopInCurrentSection(int task) { 1279 if (_currentAllocatedSection == null) { 1280 log.error("{}: Current allocated section null on entry to stopInCurrentSection", _activeTrain.getTrainName()); 1281 setStopNow(); 1282 return; 1283 } 1284 log.debug("{}: StopInCurrentSection called for {} task[{}] targetspeed[{}]", _activeTrain.getTrainName(), _currentAllocatedSection.getSection().getDisplayName(USERSYS),task,getTargetSpeed()); 1285 if (getTargetSpeed() == 0.0f || isStopping()) { 1286 log.debug("{}: train is already stopped or stopping.", _activeTrain.getTrainName()); 1287 // ignore if train is already stopped or if stopping is in progress 1288 return; 1289 } 1290 // if Section has stopping sensors, use them 1291 if (_currentAllocatedSection.getSection().getState() == Section.FORWARD) { 1292 _stopSensor = _currentAllocatedSection.getSection().getForwardStoppingSensor(); 1293 } else { 1294 _stopSensor = _currentAllocatedSection.getSection().getReverseStoppingSensor(); 1295 } 1296 if (_stopSensor != null && _useStopSensor) { 1297 if (_stopSensor.getKnownState() == Sensor.ACTIVE) { 1298 // stop sensor is already active, stop now 1299 setStopNow(); 1300 } else { 1301 setDecreasedSpeedBeforeStop(); 1302 _stopSensor.addPropertyChangeListener(_stopSensorListener = (java.beans.PropertyChangeEvent e) -> { 1303 handleStopSensorChange(e); 1304 }); 1305 _stoppingBySensor = true; 1306 } 1307 } else if (_useSpeedProfile && _stopBySpeedProfile) { 1308 log.debug("{}: Section [{}] Section Length[{}] Max Train Length [{}] StopBySpeedProfile [{}]. setStopNow", _activeTrain.getTrainName(), 1309 _currentAllocatedSection.getSection().getDisplayName(USERSYS), _currentAllocatedSection.getActualLength(), getMaxTrainLengthMM(), _stopBySpeedProfile); 1310 // stopping by speed profile uses section length to stop 1311 setTargetSpeedState(STOP_SPEED,useSpeedProfile); 1312 } else if (_currentAllocatedSection.getActualLength() < getMaxTrainLengthMM()) { 1313 log.debug("{}: Section [{}] Section Length[{}] Max Train Length [{}]. setStopNow({})", 1314 _activeTrain.getTrainName(), 1315 _currentAllocatedSection.getSection().getDisplayName(USERSYS), 1316 _currentAllocatedSection.getActualLength(), 1317 getMaxTrainLengthMM(), _stopBySpeedProfile); 1318 // train will not fit comfortably in the Section, stop it immediately 1319 setStopNow(); 1320 } else if (_activeTrain.getTrainDetection() == TrainDetection.TRAINDETECTION_WHOLETRAIN) { 1321 log.debug("{}: train will fit in [{}] ({}>={}), stop when prev block clears.", _activeTrain.getTrainName(), 1322 _currentAllocatedSection.getSection().getDisplayName(USERSYS), _currentAllocatedSection.getActualLength(), getMaxTrainLengthMM()); 1323 // train will fit in current allocated Section and has resistance wheels 1324 // try to stop by watching Section Block occupancy 1325 if (_currentAllocatedSection.getSection().getNumBlocks() == 1) { 1326 if (_previousAllocatedSection != null) { 1327 Block tBlock; 1328 // just because current section has one block does not mean the previous one did. 1329 if (_previousAllocatedSection.getSection().getNumBlocks() == 1) { 1330 tBlock = _previousAllocatedSection.getSection().getLastBlock(); 1331 } else { 1332 tBlock = _previousAllocatedSection.getSection().getExitBlock(); 1333 } 1334 if ((tBlock != null) && (tBlock.getState() == Block.OCCUPIED)) { 1335 _stoppingBlock = tBlock; 1336 setStopByBlockOccupancy(false); 1337 } else { 1338 setStopNow(); 1339 } 1340 } else { 1341 setStopNow(); 1342 } 1343 } else { 1344 // Section has multiple blocks 1345 Block exitBlock = _currentAllocatedSection.getExitBlock(); 1346 Block enterBlock = _currentAllocatedSection.getEnterBlock(_previousAllocatedSection); 1347 if (enterBlock == null) { 1348 // this is the first Section of the Transit, with train starting in this Section 1349 setStopNow(); 1350 } else if (exitBlock == enterBlock) { 1351 // entry and exit are from the same Block 1352 if ((_previousBlock != null) && (_previousBlock.getState() == Block.OCCUPIED) 1353 && (getBlockLength(exitBlock) > getMaxTrainLengthMM())) { 1354 _stoppingBlock = _previousBlock; 1355 setStopByBlockOccupancy(false); 1356 } else { 1357 setStopNow(); 1358 } 1359 } else { 1360 // try to move train as far into the Section as it will comfortably fit 1361 Block tstBlock = exitBlock; 1362 if (tstBlock == null) { 1363 if (_currentAllocatedSection.getDirection() == Section.REVERSE) { 1364 tstBlock = _currentAllocatedSection.getSection().getBlockBySequenceNumber(0); 1365 } else { 1366 tstBlock = _currentAllocatedSection.getSection().getBlockBySequenceNumber( 1367 _currentAllocatedSection.getSection().getNumBlocks() - 1); 1368 } 1369 } 1370 int tstLength = getBlockLength(tstBlock); 1371 int tstBlockSeq = _currentAllocatedSection.getSection().getBlockSequenceNumber(tstBlock); 1372 while ((tstLength < getMaxTrainLengthMM()) && (tstBlock != enterBlock)) { 1373 int newSeqNumber; 1374 if (_currentAllocatedSection.getDirection() == Section.REVERSE) { 1375 newSeqNumber = tstBlockSeq + 1; 1376 } else { 1377 newSeqNumber = tstBlockSeq - 1; 1378 } 1379 tstBlock = _currentAllocatedSection.getSection().getBlockBySequenceNumber(newSeqNumber); 1380 tstBlockSeq = newSeqNumber; 1381 tstLength += getBlockLength(tstBlock); 1382 } 1383 if (getMaxTrainLengthMM() > tstLength) { 1384 setStopNow(); 1385 } else if (tstBlock == enterBlock) { 1386 // train fits, but needs all available Blocks 1387 Block previousSectionExitBlock = _previousAllocatedSection.getExitBlock(); 1388 if ((previousSectionExitBlock != null) && (previousSectionExitBlock.getState() == Block.OCCUPIED)) { 1389 _stoppingBlock = previousSectionExitBlock; 1390 setStopByBlockOccupancy(true); 1391 } else { 1392 setStopNow(); 1393 } 1394 } else { 1395 // train fits, and doesn't need all available Blocks 1396 int xSeqNumber = tstBlockSeq + 1; 1397 if (_currentAllocatedSection.getDirection() == Section.FORWARD ) { 1398 xSeqNumber = tstBlockSeq - 1; 1399 } 1400 _stoppingBlock = _currentAllocatedSection.getSection(). 1401 getBlockBySequenceNumber(xSeqNumber); 1402 setStopByBlockOccupancy(true); 1403 } 1404 } 1405 } 1406 } else { 1407 // train will fit, but no way to stop it reliably 1408 setStopNow(); 1409 } 1410 // even if no task is required it must be run 1411 // as cleanup happens after train stops. 1412 Runnable waitForStop = new WaitForTrainToStop(task); 1413 Thread tWait = jmri.util.ThreadingUtil.newThread(waitForStop, "Wait for stop " + getActiveTrain().getActiveTrainName()); 1414 tWait.start(); 1415 } 1416 1417 protected synchronized void executeStopTasks(int task) { 1418 // clean up stopping 1419 cancelStopInCurrentSection(); 1420 dispatcher.queueReleaseOfCompletedAllocations(); 1421 log.trace("exec[{}]",task); 1422 switch (task) { 1423 case END_TRAIN: 1424 _activeTrain.setStatus(ActiveTrain.DONE); 1425 break; 1426 case NO_TASK: 1427 // clean up stop 1428 break; 1429 case END_REVERSAL: 1430 /* Reset _previousBlock to be the _currentBlock if we do a continious reverse otherwise the stop in block method fails 1431 to stop the loco in the correct block 1432 if the first block we come to has a stopped or held signal */ 1433 _activeTrain.setRestart(_activeTrain.getDelayReverseRestart(),_activeTrain.getReverseRestartDelay(), 1434 _activeTrain.getReverseRestartSensor(),_activeTrain.getResetReverseRestartSensor()); 1435 _activeTrain.setTransitReversed(true); 1436 _activeTrain.reverseAllAllocatedSections(); 1437 setEngineDirection(); 1438 _previousBlock = null; 1439 _nextBlock = getNextBlock(_currentBlock,_currentAllocatedSection); 1440 if (_activeTrain.getDelayReverseRestart() == ActiveTrain.NODELAY) { 1441 _activeTrain.holdAllocation(false); 1442 // a reversal can happen in mid section 1443 setupNewCurrentSignal(_currentAllocatedSection, true); 1444 setSpeedBySignal(); 1445 if ((_nextSection != null) && !_activeTrain.isInAllocatedList(_nextSection)) { 1446 InstanceManager.getDefault(DispatcherFrame.class).queueScanOfAllocationRequests(); 1447 break; 1448 } 1449 } 1450 break; 1451 case BEGINNING_RESET: 1452 _activeTrain.setRestart(_activeTrain.getDelayedRestart(),_activeTrain.getRestartDelay(), 1453 _activeTrain.getRestartSensor(),_activeTrain.getResetRestartSensor()); 1454 if (_activeTrain.getResetWhenDone()) { 1455 if (_activeTrain.getDelayedRestart() == ActiveTrain.NODELAY && !_activeTrain.getReverseAtEnd()) { 1456 log.error("[{}]: train is continueing without pause, should have been handled in handleBlockStateChange.",_activeTrain.getTrainName()); 1457 } else { 1458 // then active train is delayed 1459 _activeTrain.setTransitReversed(false); 1460 _activeTrain.resetAllAllocatedSections(); 1461 _previousBlock = null; 1462 _nextBlock = getNextBlock(_currentBlock,_currentAllocatedSection); 1463 setEngineDirection(); 1464 _activeTrain.setRestart(_activeTrain.getDelayedRestart(),_activeTrain.getRestartDelay(), 1465 _activeTrain.getRestartSensor(), _activeTrain.getResetRestartSensor()); 1466 if ((_nextSection != null) && !_activeTrain.isInAllocatedList(_nextSection)) { 1467 InstanceManager.getDefault(DispatcherFrame.class).queueScanOfAllocationRequests(); 1468 } 1469 // can be mid block 1470 setupNewCurrentSignal(null, true); 1471 setSpeedBySignal(); 1472 1473 } 1474 } else { 1475 // dispatcher cancelled auto restart while train was stopping? 1476 log.warn("[{}]resetWhenDone flag reset, likely user cancelling while processing stop", 1477 _activeTrain.getActiveTrainName()); 1478 } 1479 break; 1480 default: 1481 log.debug("[{}]Invalid action [{}] in executeStopTasksRequest to execute BEGINNING_RESET cancelled", _activeTrain.getActiveTrainName(),task); 1482 break; 1483 } 1484 } 1485 1486 /** 1487 * Remove the stopping sensor 1488 */ 1489 private void cancelStoppingBySensor() { 1490 if (_stopSensor != null) { 1491 _stopSensor.removePropertyChangeListener(_stopSensorListener); 1492 _stoppingBySensor = false; 1493 _stopSensorListener = null; 1494 _stopSensor = null; 1495 } 1496 } 1497 1498 /** 1499 * When the stopping sensor we are waiting on goes active 1500 * stop the train or set a new speed and destroy itself 1501 * @param e - the property change event 1502 */ 1503 private synchronized void handleStopSensorChange(java.beans.PropertyChangeEvent e) { 1504 if (e.getPropertyName().equals("KnownState") && (int) e.getNewValue() == Sensor.ACTIVE) { 1505 _stopSensor.removePropertyChangeListener(_stopSensorListener); 1506 _stoppingBySensor = false; 1507 _stopSensorListener = null; 1508 _stopSensor = null; 1509 if (_needSetSpeed) { 1510 _needSetSpeed = false; 1511 setSpeedBySignal(); 1512 } else { 1513 setStopNow(); 1514 } 1515 } 1516 } 1517 1518 private synchronized void setStopNow() { 1519 setStopNow(false); 1520 } 1521 1522 private synchronized void setStopNow(boolean useSpeedProfile) { 1523 setTargetSpeedState(STOP_SPEED,useSpeedProfile); 1524 if (_currentAllocatedSection == null) { // this may occur if the train is not in the selected block when initially created and the signal is held. 1525 _activeTrain.setStatus(ActiveTrain.WAITING); 1526 } else if (_currentAllocatedSection.getNextSection() == null) { 1527 // wait for train to stop - this lets action items complete in a timely fashion 1528 waitUntilStopped(); 1529 _activeTrain.setStatus(ActiveTrain.DONE); 1530 } else { 1531 _activeTrain.setStatus(ActiveTrain.WAITING); 1532 } 1533 } 1534 1535 /* 1536 * When multi block stopping, the stopping block may not be occupied yet. 1537 */ 1538 private void setStopByBlockOccupancy(boolean ignoreNotOccupied) { 1539 // note: _stoppingBlock must be set before invoking this method 1540 // verify that _stoppingBlock is actually occupied, if not stop immed 1541 if (_stoppingBlock.getState() == Block.OCCUPIED || ignoreNotOccupied) { 1542 setDecreasedSpeedBeforeStop(); 1543 _stoppingByBlockOccupancy = true; 1544 } else { 1545 setStopNow(); 1546 } 1547 } 1548 1549 /** 1550 * Before stopping by sensor alone, or by clearing previous block, 1551 * set the speed to the user defined preference. 1552 */ 1553 private void setDecreasedSpeedBeforeStop() { 1554 float signalSpeed = 25; 1555 try { 1556 signalSpeed = InstanceManager.getDefault(SignalSpeedMap.class) 1557 .getSpeed(InstanceManager.getDefault(DispatcherFrame.class).getStoppingSpeedName()); 1558 } catch (IllegalArgumentException ex) { 1559 log.error("Missing [{}] from Speed table - defaulting to 25", 1560 InstanceManager.getDefault(DispatcherFrame.class).getStoppingSpeedName()); 1561 } 1562 setToAMaximumThrottle(getThrottleSettingFromSpeed(signalSpeed)); 1563 } 1564 1565 /** 1566 * Sets the throttle percent unless it is already less than the new setting 1567 * @param throttleSetting Max ThrottleSetting required. 1568 */ 1569 private synchronized void setToAMaximumThrottle(float throttleSetting) { 1570 if (throttleSetting < getTargetSpeed()) { 1571 setTargetSpeed(throttleSetting); 1572 } 1573 } 1574 1575 /** 1576 * Calculates the throttle setting for a given speed. 1577 * @param speed the unadjusted speed. 1578 * @return - throttle setting (a percentage) 1579 */ 1580 private synchronized float getThrottleSettingFromSpeed(float speed) { 1581 if (useSpeedProfile) { 1582 float throttleSetting = _activeTrain.getRosterEntry().getSpeedProfile() 1583 .getThrottleSettingFromSignalMapSpeed(speed, getForward()); 1584 return throttleSetting; 1585 } 1586 if (InstanceManager.getDefault(DispatcherFrame.class).getSignalType() == DispatcherFrame.SIGNALMAST) { 1587 float mls; 1588 if (_controllingSignalMast != null) { 1589 mls = _controllingSignalMast.getSignalSystem().getMaximumLineSpeed(); 1590 } else { 1591 //plan B 1592 mls = InstanceManager.getDefault(DispatcherFrame.class).getMaximumLineSpeed(); 1593 } 1594 float throttleSetting = (speed / mls); 1595 return throttleSetting; 1596 } else { 1597 return speed/100.0f; 1598 } 1599 } 1600 1601 1602 /** 1603 * sets the throttle based on an index number into _speedRatio array 1604 * @param speedState Index value 1605 */ 1606 private synchronized void setTargetSpeedState(int speedState) { 1607 setTargetSpeedState(speedState,false); 1608 } 1609 1610 /** 1611 * sets the throttle based on an index number into _speedRatio array 1612 * @param speedState Index value 1613 * @param stopBySpeedProfile if true use speed profile 1614 */ 1615 private synchronized void setTargetSpeedState(int speedState,boolean stopBySpeedProfile) { 1616 log.trace("{}: setTargetSpeedState:({})",_activeTrain.getTrainName(),speedState); 1617 _autoEngineer.slowToStop(false); 1618 if (speedState > STOP_SPEED) { 1619 cancelStopInCurrentSection(); 1620 if (_currentRampRate == RAMP_SPEEDPROFILE && _useSpeedProfile) { 1621 // we are going to ramp up / down using section length and speed profile 1622 _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock) * _stopBySpeedProfileAdjust, speedState); 1623 } else { 1624 setTargetSpeed(_speedRatio[speedState]); 1625 } 1626 } else if (stopBySpeedProfile) { 1627 // we are going to stop by profile 1628 _stoppingUsingSpeedProfile = true; 1629 _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock) * _stopBySpeedProfileAdjust, 0.0f); 1630 } else { 1631 _autoEngineer.setHalt(true); 1632 setTargetSpeed(0.0f); 1633 } 1634 } 1635 1636 private synchronized void setTargetSpeedByProfile(float speedState) { 1637 // the speed comes in as units of warrents (mph, kph, mm/s etc) 1638 try { 1639 float throttleSetting = _activeTrain.getRosterEntry().getSpeedProfile().getThrottleSettingFromSignalMapSpeed(speedState, getForward()); 1640 log.debug("{}: setTargetSpeedByProfile: {} SpeedState[{}]", 1641 _activeTrain.getTrainName(), 1642 throttleSetting, 1643 speedState); 1644 if (throttleSetting > 0.009 && _currentRampRate != RAMP_SPEEDPROFILE && _useSpeedProfile) { 1645 cancelStopInCurrentSection(); 1646 setTargetSpeed(throttleSetting); // apply speed factor and max 1647 } else if (throttleSetting > 0.009) { 1648 cancelStopInCurrentSection(); 1649 _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock) * _stopBySpeedProfileAdjust , throttleSetting); 1650 } else if (useSpeedProfile && _stopBySpeedProfile) { 1651 setTargetSpeed(0.0f); 1652 _stoppingUsingSpeedProfile = true; 1653 _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock) * _stopBySpeedProfileAdjust, 0.0f); 1654 } else { 1655 _autoEngineer.slowToStop(false); 1656 setTargetSpeed(0.0f); 1657 _autoEngineer.setHalt(true); 1658 } 1659 } catch (Exception ex) { 1660 log.error("setTargetSpeedByProfile crashed - Emergency Stop: ", ex ); 1661 _autoEngineer.slowToStop(false); 1662 setTargetSpeed(-1.0f); 1663 _autoEngineer.setHalt(true); 1664 } 1665 } 1666 1667 /** 1668 * Pass in speed as shown on dialogs, and convert to decimal speed needed by 1669 * throttle. 1670 */ 1671 private synchronized void setTargetSpeedValue(float speed) { 1672 log.debug("{}: setTargetSpeedValue: Speed[{}]",_activeTrain.getTrainName(),speed); 1673 if (useSpeedProfile) { 1674 setTargetSpeedByProfile(speed); 1675 return; 1676 } 1677 _autoEngineer.slowToStop(false); 1678 float mls; 1679 if (_controllingSignalMast != null) { 1680 mls = _controllingSignalMast.getSignalSystem().getMaximumLineSpeed(); 1681 } else { 1682 mls = InstanceManager.getDefault(DispatcherFrame.class).getMaximumLineSpeed(); 1683 } 1684 float decSpeed = (speed / mls); 1685 if (decSpeed > 0.0f) { 1686 cancelStopInCurrentSection(); 1687 setTargetSpeed(decSpeed); 1688 } else { 1689 setTargetSpeed(0.0f); 1690 _autoEngineer.setHalt(true); 1691 } 1692 } 1693 1694 private int getBlockLength(Block b) { 1695 if (b == null) { 1696 return (0); 1697 } 1698 return (int) b.getLengthMm(); 1699// float fLength = b.getLengthMm() / (float) InstanceManager.getDefault(DispatcherFrame.class).getScale().getScaleFactor(); 1700// if (InstanceManager.getDefault(DispatcherFrame.class).getUseScaleMeters()) { 1701// return (int) (fLength * 0.001f); 1702// } 1703// return (int) (fLength * 0.00328084f); 1704 } 1705 1706 /** 1707 * Initiates running in manual mode with external throttle. 1708 * <p> 1709 * This method is triggered by an action in the Transit. The throttle in use 1710 * for automatic operation is dispatched. 1711 */ 1712 protected void initiateWorking() { 1713 if (_activeTrain.getStatus() != ActiveTrain.WORKING) { 1714 _activeTrain.setMode(ActiveTrain.DISPATCHED); 1715 _activeTrain.setStatus(ActiveTrain.WORKING); 1716 saveSpeedAndDirection(); 1717 if (_autoEngineer != null) { 1718 _autoEngineer.setHalt(true); 1719 waitUntilStopped(); 1720 _autoEngineer.abort(); 1721 InstanceManager.throttleManagerInstance().releaseThrottle(_throttle, this); 1722 _autoEngineer = null; 1723 _throttle = null; 1724 } 1725 } 1726 } 1727 1728 /** 1729 * Returns when train is stopped. 1730 * <p> 1731 * Note: Provides for _autoEngineer becoming null during wait Ties up the 1732 * current autoActiveTrain thread. 1733 */ 1734 protected void waitUntilStopped() { 1735 boolean doneWaiting = false; 1736 while (!doneWaiting) { 1737 if (_autoEngineer != null) { 1738 doneWaiting = _autoEngineer.isStopped(); 1739 } else { 1740 doneWaiting = true; 1741 } 1742 if (!doneWaiting) { 1743 try { 1744 Thread.sleep(50); 1745 } catch (InterruptedException e) { 1746 // ignore this exception 1747 } 1748 } 1749 } 1750 } 1751 1752 /** 1753 * Resumes automatic running after a working session using an external 1754 * throttle This method is triggered by the dispatcher hitting the "Resume 1755 * Auto Running" button A new throttle is acquired to allow automatic 1756 * running to resume 1757 */ 1758 protected void resumeAutomaticRunning() { 1759 if ((_activeTrain.getStatus() == ActiveTrain.WORKING) 1760 || (_activeTrain.getStatus() == ActiveTrain.READY)) { 1761 _autoTrainAction.cancelDoneSensor(); 1762 if (initialize()) { 1763 _resumingAutomatic = true; 1764 } else { 1765 log.error("Failed to initialize throttle when resuming automatic mode."); 1766 } 1767 } 1768 } 1769 1770 /** 1771 * Pause the auto active train for a specified number of fast clock minutes. 1772 * 1773 * @param fastMinutes the number of minutes to pause the train 1774 * @return the thread waiting on the pause or null if already paused 1775 */ 1776 public Thread pauseTrain(int fastMinutes) { 1777 if (_pausingActive) { 1778 // if a pause train thread is currently active, ignore this call 1779 return (null); 1780 } 1781 Runnable pauseTrain = new PauseTrain(fastMinutes); 1782 Thread tPause = jmri.util.ThreadingUtil.newThread(pauseTrain, "pause train " + _activeTrain.getTrainName()); 1783 tPause.start(); 1784 return tPause; 1785 } 1786 1787 public void terminate() { 1788 // here add code to stop the train and release its throttle if it is in autoRun 1789 while (_activeHornThreads > 0) { 1790 try { 1791 Thread.sleep(50); 1792 } catch (InterruptedException e) { 1793 // ignore this exception 1794 } 1795 } 1796 _autoTrainAction.clearRemainingActions(); 1797 if (_autoEngineer != null) { 1798 _autoEngineer.setHalt(true); 1799 try { 1800 Thread.sleep(50); 1801 } catch (InterruptedException e) { 1802 // ignore this exception 1803 } 1804 waitUntilStopped(); 1805 _autoEngineer.abort(); 1806 InstanceManager.throttleManagerInstance().releaseThrottle(_throttle, this); 1807 } 1808 } 1809 1810 public void dispose() { 1811 if (_controllingSignalMast != null && _conSignalMastListener != null) { 1812 _controllingSignalMast.removePropertyChangeListener(_conSignalMastListener); 1813 } 1814 _controllingSignalMast = null; 1815 _conSignalMastListener = null; 1816 } 1817 1818// _________________________________________________________________________________________ 1819 // This class waits for train stop in a separate thread 1820 class WaitForTrainToStop implements Runnable { 1821 1822 public WaitForTrainToStop(int task) { 1823 _task = task; 1824 } 1825 1826 @Override 1827 public void run() { 1828 boolean waitingOnTrain = true; 1829 try { 1830 while (waitingOnTrain) { 1831 if ((getAutoEngineer() != null) && (getAutoEngineer().isStopped())) { 1832 waitingOnTrain = false; 1833 } else { 1834 Thread.sleep(_delay); 1835 } 1836 } 1837 log.trace("executing task[{}]",_task); 1838 executeStopTasks(_task); 1839 } catch (InterruptedException e) { 1840 log.warn("Waiting for train to stop interrupted - stop tasks not executing"); 1841 } catch (Exception e) { 1842 log.error("Waiting for train to stop crashed - stop tasks not executing.", e); 1843 } 1844 } 1845 1846 private final int _delay = 91; 1847 private int _task = 0; 1848 } 1849 1850 /** 1851 * Pause the train in a separate thread. Train is stopped, then restarted 1852 * after specified number of fast Minutes have elapsed. 1853 */ 1854 class PauseTrain implements Runnable { 1855 /** 1856 * Create a PauseTrain 1857 * 1858 * @param fastMinutes the number of fast clock minutes to pause the 1859 * train 1860 */ 1861 public PauseTrain(int fastMinutes) { 1862 _fastMinutes = fastMinutes; 1863 } 1864 1865 @Override 1866 public void run() { 1867 // set to pause at a fast ramp rate 1868 _pausingActive = true; 1869 _savedTargetSpeed = getTargetSpeed(); 1870 _savedRampRate = getRampRate(); 1871 setCurrentRampRate(RAMP_FAST); 1872 stopInCurrentSection(NO_TASK); 1873 // wait for train to stop 1874 boolean waitNow = true; 1875 boolean keepGoing = true; 1876 while (waitNow) { 1877 try { 1878 Thread.sleep(101); 1879 if (_autoEngineer != null) { 1880 if (_autoEngineer.isStopped()) { 1881 waitNow = false; 1882 } 1883 } else { 1884 waitNow = false; 1885 } 1886 } catch (InterruptedException e) { 1887 log.trace("InterruptedException while waiting to stop for pause-indicates action cancelled.", e); 1888 waitNow = false; 1889 keepGoing = false; 1890 } 1891 } 1892 _activeTrain.setStatus(ActiveTrain.PAUSED); 1893 if (keepGoing) { 1894 // wait for specified fast clock time 1895 Timebase _clock = InstanceManager.getDefault(jmri.Timebase.class); 1896 java.beans.PropertyChangeListener _clockListener = (java.beans.PropertyChangeEvent e) -> { 1897 _fastMinutes--; 1898 }; 1899 _clock.addMinuteChangeListener(_clockListener); 1900 // wait for fast minutes to tick away 1901 waitNow = true; 1902 while (waitNow) { 1903 try { 1904 Thread.sleep(501); 1905 if (_fastMinutes <= 0) { 1906 waitNow = false; 1907 } 1908 } catch (InterruptedException e) { 1909 log.trace("InterruptedException indicates action cancelled.", e); 1910 keepGoing = false; 1911 } 1912 } 1913 _clock.removeMinuteChangeListener(_clockListener); 1914 } 1915 _pausingActive = false; 1916 if (keepGoing) { 1917 // this thread was not interrupted 1918 // resume running - restore speed, status, and ramp rate 1919 setCurrentRampRate(_savedRampRate); 1920 setTargetSpeed(_savedTargetSpeed); 1921 _activeTrain.setStatus(ActiveTrain.RUNNING); 1922 setSpeedBySignal(); 1923 } 1924 } 1925 private int _fastMinutes = 0; 1926 private float _savedTargetSpeed = 0.0f; 1927 private int _savedRampRate = RAMP_NONE; 1928 } 1929 1930 // _________________________________________________________________________________________ 1931 // this class handles the interface with the throttle 1932 // (This class started from code by Pete Cressman contained in Warrant.java.) 1933 class AutoEngineer { 1934 1935 AutoEngineer(DccThrottle throttle, RosterEntry rosterEntry) { 1936 this.throttle = throttle; 1937 this.rosterEntry = rosterEntry; 1938 } 1939 1940 private DccThrottle throttle; 1941 private int ramping; 1942 private boolean speedProfileStoppingIsRunning = false; 1943 private float speedIncrement = 0.0f; //will be recalculated 1944 private float targetSpeed; 1945 private RosterEntry rosterEntry; 1946 private int throttleInterval; 1947 private float minReliableOperatingSpeed; 1948 private float maxSpeed; 1949 private float speedFactor; 1950 1951 public void setRamping(int ramping, int fullRampTime, int minThrottleInterval, int rampRate) { 1952 this.ramping = ramping; 1953 this.throttleInterval = minThrottleInterval; 1954 //calculate speed increment to use in each minInterval time 1955 speedIncrement = (100.0f / ((float) fullRampTime / minThrottleInterval) 1956 / rampRate) / 100.0f; 1957 log.debug("{}: _speedIncrement={}", throttle.getLocoAddress(), speedIncrement); 1958 } 1959 1960 public void setIsForward(boolean isForward) { 1961 throttle.setIsForward(isForward); 1962 } 1963 1964 public boolean getIsForward() { 1965 return(throttle.getIsForward()); 1966 } 1967 1968 public void setTargetSpeed(float speed) { 1969 stopAllTimers(); 1970 targetSpeed = applyMaxThrottleAndFactor(speed); 1971 log.debug("setTargetSpeed: Set Speed[{}] adjusted to TargetSpeed[{}] ",speed,targetSpeed); 1972 if (ramping == RAMP_NONE || ramping == RAMP_SPEEDPROFILE ) { 1973 throttle.setSpeedSetting(targetSpeed); 1974 } else { 1975 rampToTarget(); 1976 } 1977 } 1978 1979 public float getTargetSpeed(){ 1980 return(targetSpeed); 1981 } 1982 1983 /** 1984 * 1985 * @param throttleSetting the throttle setting that would normally be set 1986 * @return the adjusted throttle setting after applying Max Throttle and Percentage throttle settings 1987 */ 1988 private float applyMaxThrottleAndFactor(float throttleSetting) { 1989 if (throttleSetting > 0.0f) { 1990 if ((throttleSetting * speedFactor) > maxSpeed) { 1991 return maxSpeed; 1992 } 1993 if ((throttleSetting * speedFactor) < minReliableOperatingSpeed) { 1994 return minReliableOperatingSpeed; 1995 } 1996 return (throttleSetting * speedFactor); //adjust for train's Speed Factor 1997 } else { 1998 return throttleSetting; 1999 } 2000 } 2001 2002 /** 2003 * Flag from user's control. 2004 * 2005 * @param halt true to immediately stop the train; false otherwise 2006 */ 2007 public void setHalt(boolean halt) { 2008 if (halt) { 2009 this.setSpeedImmediate(0.0f); 2010 } 2011 } 2012 2013 /** 2014 * Set the limits and adjustment factore for train speed. 2015 * Active train will calculate the required setting and it will be adjusted if not 0.0f 2016 * required setting * speed Factor then test for less than max and greater than min. 2017 * @param minReliableOperatingSpeed lowest throttle % train will reliably move. 2018 * @param maxSpeed max throttle % for train. 2019 * @param speedFactor multiplier 2020 */ 2021 public void setSpeedLimits(float minReliableOperatingSpeed, float maxSpeed, float speedFactor) { 2022 this.minReliableOperatingSpeed = minReliableOperatingSpeed; 2023 this.maxSpeed = maxSpeed; 2024 this.speedFactor = speedFactor; 2025 } 2026 2027 public void setTargetSpeed(float distance, float speed) { 2028 log.debug("Set Target Speed[{}] with distance{{}] from speed[{}]",speed,distance,throttle.getSpeedSetting()); 2029 stopAllTimers(); 2030 if (rosterEntry != null) { 2031 rosterEntry.getSpeedProfile().setExtraInitialDelay(1500f); 2032 rosterEntry.getSpeedProfile().setMinMaxLimits(minReliableOperatingSpeed, maxSpeed); 2033 rosterEntry.getSpeedProfile().changeLocoSpeed(_throttle, distance, speed); 2034 speedProfileStoppingIsRunning = true; 2035 targetSpeed = speed; 2036 } else { 2037 setTargetSpeed((0.0f)); 2038 } 2039 } 2040 2041 public void slowToStop(boolean on) { 2042 stopAllTimers(); 2043 if (on) { 2044 log.debug("SlowToStopOn"); 2045 setTargetSpeed((0.0f)); 2046 } 2047 } 2048 2049 public void stopAllTimers() { 2050 if (speedProfileStoppingIsRunning) { 2051 re.getSpeedProfile().cancelSpeedChange(); 2052 speedProfileStoppingIsRunning = false; 2053 } 2054 if (rampingTimer != null) { 2055 rampingTimer.stop(); 2056 rampingTimer = null; 2057 } 2058 } 2059 2060 LinkedList<SpeedSetting> stepQueue; 2061 private javax.swing.Timer rampingTimer; 2062 2063 private void rampToTarget() { 2064 // target already adjusted. 2065 log.debug("RampToTarget[{}]current[{}]", getTargetSpeed(), throttle.getSpeedSetting()); 2066 stepQueue = new LinkedList<>(); 2067 if (throttle.getSpeedSetting() <= getTargetSpeed()) { 2068 // Up 2069 float newSpeed = throttle.getSpeedSetting(); 2070 if (newSpeed < minReliableOperatingSpeed) { 2071 stepQueue.add(new SpeedSetting(minReliableOperatingSpeed, throttleInterval)); 2072 newSpeed = minReliableOperatingSpeed; 2073 } 2074 while (newSpeed < getTargetSpeed()) { 2075 newSpeed += speedIncrement; 2076 if (newSpeed > getTargetSpeed()) { 2077 newSpeed = getTargetSpeed(); 2078 } 2079 log.trace("NewSpeedUp[{}]", newSpeed); 2080 stepQueue.add(new SpeedSetting(newSpeed, throttleInterval)); 2081 } 2082 } else { 2083 // Down 2084 boolean andStop = false; 2085 if (getTargetSpeed() <= 0.0f) { 2086 andStop = true; 2087 } 2088 float newSpeed = throttle.getSpeedSetting(); 2089 while (newSpeed > getTargetSpeed()) { 2090 newSpeed -= speedIncrement; 2091 if (newSpeed < getTargetSpeed()) { 2092 newSpeed = getTargetSpeed(); 2093 } 2094 log.trace("NewSpeedDown[{}]", newSpeed); 2095 stepQueue.add(new SpeedSetting(newSpeed, throttleInterval)); 2096 } 2097 if (andStop) { 2098 stepQueue.add(new SpeedSetting(0.0f, throttleInterval)); 2099 } 2100 } 2101 if (rampingTimer == null) { //If this is the first time round then kick off the speed change 2102 setNextStep(); 2103 } 2104 } 2105 2106 private void finishChange() { 2107 if (rampingTimer != null) { 2108 rampingTimer.stop(); 2109 } 2110 rampingTimer = null; 2111 stepQueue.clear(); 2112 stepQueue = null; 2113 } 2114 2115 synchronized void setNextStep() { 2116 if (stepQueue.isEmpty()) { 2117 log.trace("Empty"); 2118 finishChange(); 2119 return; 2120 } 2121 SpeedSetting ss = stepQueue.getFirst(); 2122 if (ss.getDuration() == 0) { 2123 log.trace("Duratiom Zero"); 2124 finishChange(); 2125 return; 2126 } 2127 stepQueue.removeFirst(); 2128 log.trace("Set New Speed[{}]",ss.getSpeedStep()); 2129 throttle.setSpeedSetting(ss.getSpeedStep()); 2130 rampingTimer = new javax.swing.Timer(ss.getDuration(), (java.awt.event.ActionEvent e) -> { 2131 setNextStep(); 2132 }); 2133 rampingTimer.setRepeats(false); 2134 rampingTimer.start(); 2135 } 2136 2137 private class SpeedSetting { 2138 2139 float step = 0.0f; 2140 int duration = 0; 2141 2142 SpeedSetting(float step, int duration) { 2143 this.step = step; 2144 this.duration = duration; 2145 } 2146 2147 float getSpeedStep() { 2148 return step; 2149 } 2150 2151 int getDuration() { 2152 return duration; 2153 } 2154 } 2155 2156 /** 2157 * Set the train speed directly, bypassing ramping. 2158 * 2159 * @param speed 0.0 (stop) to 1.0 (full) 2160 */ 2161 public synchronized void setSpeedImmediate(float speed) { 2162 log.trace("{}: setting speed directly to {}%", _activeTrain.getTrainName(), (int) (speed * 100)); 2163 stopAllTimers(); 2164 targetSpeed = applyMaxThrottleAndFactor(speed); 2165 throttle.setSpeedSetting(targetSpeed); 2166 } 2167 2168 /** 2169 * Check if train is moving or stopped. 2170 * 2171 * @return true if stopped; false otherwise 2172 */ 2173 public synchronized boolean isStopped() { 2174 // when stopping by speed profile you must refresh the throttle speed. 2175 return throttle.getSpeedSetting() <= 0.0004f; 2176 } 2177 2178 /** 2179 * Check if train is moving at its current requested speed. 2180 * 2181 * @return true if at requested speed; false otherwise 2182 */ 2183 public synchronized boolean isAtSpeed() { 2184 return java.lang.Math.abs(throttle.getSpeedSetting() - targetSpeed) <= 0.01; 2185 } 2186 2187 /** 2188 * Flag from user to end run. 2189 */ 2190 public void abort() { 2191 stopAllTimers(); 2192 } 2193 2194 protected void setFunction(int cmdNum, boolean isSet) { 2195 throttle.setFunction(cmdNum, isSet); 2196 } 2197 } 2198 2199 /** 2200 * Convert ramp rate name, stored as a string into the constant value 2201 * assigned. 2202 * 2203 * @param rampRate name of ramp rate, such as "RAMP_FAST" 2204 * @return integer representing a ramprate constant value 2205 */ 2206 public static int getRampRateFromName(String rampRate) { 2207 if (rampRate.equals(Bundle.getMessage("RAMP_FAST"))) { 2208 return RAMP_FAST; 2209 } else if (rampRate.equals(Bundle.getMessage("RAMP_MEDIUM"))) { 2210 return RAMP_MEDIUM; 2211 } else if (rampRate.equals(Bundle.getMessage("RAMP_MED_SLOW"))) { 2212 return RAMP_MED_SLOW; 2213 } else if (rampRate.equals(Bundle.getMessage("RAMP_SLOW"))) { 2214 return RAMP_SLOW; 2215 } else if (rampRate.equals(Bundle.getMessage("RAMP_SPEEDPROFILE"))) { 2216 return RAMP_SPEEDPROFILE; 2217 } 2218 return RAMP_NONE; 2219 } 2220 2221 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AutoActiveTrain.class); 2222}