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