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