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