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