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