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