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            } else {
745                if (!isStopping() && _dispatcher.getUseOccupiedTrackSpeed()) {
746                    setSpeedBySignal();
747                }
748            }
749        }
750        _autoTrainAction.handleBlockStateChange(as, b);
751    }
752
753    /**
754     * support methods
755     */
756    protected void setEngineDirection() {
757        boolean oldFwd = getForward();
758        if (_runInReverse) {
759            setForward(_activeTrain.isTransitReversed());
760        } else {
761            setForward(!_activeTrain.isTransitReversed());
762        }
763        log.debug("[{}]flipping direction was [{}] now [{}]",_activeTrain.getActiveTrainName() ,oldFwd, getForward());
764    }
765
766    protected AllocatedSection getCurrentAllocatedSection() {
767        return _currentAllocatedSection;
768    }
769
770    /*
771     * Reverse lookup for allocated section.
772     */
773    protected AllocatedSection getAllocatedSectionForSection(Section s) {
774        for (AllocatedSection allocatedSection : _activeTrain.getAllocatedSectionList()) {
775            if (allocatedSection.getSection() == s) {
776                return allocatedSection;
777            }
778        }
779        return null;
780    }
781
782    protected void allocateAFresh() {
783        //Reset initialized flag
784        _initialized = false;
785        // set direction
786        _currentAllocatedSection=null;
787        _currentBlock=null;
788        setForward(!getRunInReverse());
789    }
790
791    private void addAllocatedSection(AllocatedSection as) {
792        if (!_initialized) {
793            // this is first allocated section, get things started
794            _initialized = true;
795            _nextSection = as.getSection();
796            _currentBlock = _activeTrain.getStartBlock();
797            if (as.getSection().containsBlock(_currentBlock)) {
798                // starting Block is in this allocated section - find next Block
799                setNewCurrentSection(as);
800                _nextBlock = getNextBlock(_currentBlock, as);
801            } else if (as.getSection().connectsToBlock(_currentBlock)) {
802                // starting Block is connected to a Block in this allocated section
803                EntryPoint ep = as.getSection().getEntryPointFromBlock(_currentBlock, as.getDirection());
804                if (ep != null) {
805                    _nextBlock = ep.getBlock();
806                } else {
807                    log.error("failure to get entry point to Transit from Block {}", _currentBlock.getDisplayName(USERSYS));
808                }
809            }
810            if (_nextBlock != null) {
811                // set up new current signal, as this a beginning we allow a signal not at end of block
812                // to control the speed.
813                setupNewCurrentSignal(as,true);
814            }
815        }
816        // if train is stopping for lack of an allocation, set flag to restart it
817        if (!_pausingActive && (_lastAllocatedSection == _currentAllocatedSection)
818                && isStopping() && (_activeTrain.getStatus() == ActiveTrain.RUNNING)) {
819            _needSetSpeed = true;
820        }
821
822        // request next allocation if appropriate--Dispatcher must decide whether to allocate it and when
823        if ((!_dispatcher.getAutoAllocate()) && ((_lastAllocatedSection == null)
824                || (_lastAllocatedSection.getNextSection() == as.getSection()))) {
825            // if AutoAllocate, this is now done in DispatcherFrame.java for all trains
826            _lastAllocatedSection = as;
827            if (as.getNextSection() != null) {
828                Section nSection = as.getNextSection();
829                int nextSeq = as.getNextSectionSequence();
830                int nextDir = _activeTrain.getAllocationDirectionFromSectionAndSeq(nSection, nextSeq);
831                _dispatcher.requestAllocation(_activeTrain, nSection, nextDir, nextSeq, true, null);
832            }
833        }
834    }
835
836    private boolean isStopping() {
837        // here add indicator for new stopping methods, if any are added
838        return (_stoppingBySensor || _stoppingByBlockOccupancy || _stoppingUsingSpeedProfile);
839    }
840
841    private void removeCurrentSignal() {
842        if (_conSignalListener != null) {
843            _controllingSignal.removePropertyChangeListener(_conSignalListener);
844            _conSignalListener = null;
845        }
846        _controllingSignalPrev = _controllingSignal;
847        _controllingSignal = null;
848        if (_conSignalMastListener != null) {
849            _controllingSignalMast.removePropertyChangeListener(_conSignalMastListener);
850            _conSignalMastListener = null;
851        }
852        _controllingSignalMastPrev = _controllingSignalMast;
853        _controllingSignalMast = null;
854        _needSetSpeed = false;
855    }
856
857    /**
858     * checks for a controlling signal
859     * @return true if there is one
860     */
861    protected boolean isCurrentSignal() {
862        if (_activeTrain.getSignalType() == DispatcherFrame.SIGNALHEAD) {
863            return _controllingSignal != null;
864        } else {
865            // SignalMast
866            return _controllingSignalMast != null;
867        }
868    }
869
870    /**
871     *
872     * @param as current section the train is in, can be null
873     * @param forceSpeedChange if true, the speed will be set using the signal mast
874     *        even if it is not on the immediate block boundary
875     */
876    protected synchronized void setupNewCurrentSignal(AllocatedSection as, boolean forceSpeedChange) {
877        log.trace("setupNewCurrentSignal Called Section[{}] forceSpeedChange[{}]", as != null ? as.getSectionName() : "null",forceSpeedChange);
878        removeCurrentSignal();
879        if (_activeTrain.getSignalType() == DispatcherFrame.SIGNALHEAD) {
880            SignalHead sh = _lbManager.getFacingSignalHead(_currentBlock, _nextBlock);
881            if (sh != null) {
882                _controllingSignal = sh;
883                _conSignalProtectedBlock = _nextBlock;
884                sh.addPropertyChangeListener(_conSignalListener = (PropertyChangeEvent e) -> {
885                    if (e.getPropertyName().equals("Appearance")) {
886                        // controlling signal has changed appearance
887                        setSpeedBySignal();
888                    }
889                });
890                _activeTrain.setControlingSignal(_controllingSignal, _controllingSignalPrev);
891                log.debug("new current signal = {}", sh.getDisplayName(USERSYS));
892            } else {
893                // Note: null signal head will result when exiting throat-to-throat blocks.
894                log.warn("new current signal is null - sometimes OK");
895            }
896            setSpeedBySignal();
897        } else if (_activeTrain.getSignalType() == DispatcherFrame.SIGNALMAST) {
898            //SignalMast
899            SignalMast sm = null;
900            Block cB = _currentBlock;
901            Block nB = _nextBlock;
902            if (as == null) {
903                as = _currentAllocatedSection;
904            }
905            // get signal mast at current block change, if there is no signal mast we will proceed with no change in speed
906            // unless forceSpeedChange is true, such as beginning, resets of transit.
907            // previous signal mast speed unless the mast is held.
908            boolean weAreAtSpeedChangingMast=forceSpeedChange;
909            if ( !forceSpeedChange  && nB != null ) {
910                sm  = _lbManager.getFacingSignalMast(cB, nB);
911                if (sm != null) {weAreAtSpeedChangingMast=true;}
912            }
913
914            while (sm == null && nB != null) {
915                sm = _lbManager.getFacingSignalMast(cB, nB);
916                if (sm == null) {
917                    cB = nB;
918                    nB = getNextBlock(nB, as);
919                }
920            }
921            if (sm != null) {
922                _controllingSignalMast = sm;
923                _conSignalProtectedBlock = nB;
924                sm.addPropertyChangeListener(_conSignalMastListener = (PropertyChangeEvent e) -> {
925                    if (e.getPropertyName().equals("Aspect") || e.getPropertyName().equals("Held")) {
926                        // controlling signal has changed appearance or a hold has been released
927                        // even if its a hold we still have to use target speed etc else we override pauses and other stop events.
928                        setSpeedBySignal();
929                    }
930                });
931                _activeTrain.setControlingSignal(_controllingSignalMast, _controllingSignalMastPrev);
932                log.debug("{}: new current signalmast {}({}) for section {}", _activeTrain.getTrainName(), sm.getDisplayName(USERSYS),
933                        sm.getAspect(), as.getSection().getDisplayName(USERSYS));
934                if ( weAreAtSpeedChangingMast ) {
935                    setSpeedBySignal();
936                } else {
937                    checkForGhost();
938                }
939            } else {
940                // There is a missing signal mast at a block boundary.
941                // If the next block is allocated to this train we can continue.
942                // If the train was stopped here we can try and restart it. Either way we use
943                // setting setSpeedBySectionsAllocated as a way out of the dilemma.
944                log.debug("{}: new current signalmast is null for section {} - sometimes OK", _activeTrain.getTrainName(),
945                        as == null ? "Null" : as.getSection().getDisplayName(USERSYS));
946                if (_nextBlock == null || ! _activeTrain.getBlockList().contains(_nextBlock) ||  _autoEngineer.isStopped()) {
947                    log.warn("{}: new current signalmast is null for section {} and next block is not this trains. Temporarily continuing by allocations", _activeTrain.getTrainName(),
948                            as == null ? "Null" : as.getSection().getDisplayName(USERSYS));
949                    setSpeedBySectionsAllocated();
950                }
951                checkForGhost();
952            }
953        } else {
954            setSpeedBySignal();
955        }
956    }
957
958    @CheckForNull
959    private Block getNextBlock(Block b, AllocatedSection as) {
960        //if (((_currentBlock == _activeTrain.getEndBlock()) && _activeTrain.getReverseAtEnd()
961        //        && (as.getSequence() == _activeTrain.getEndBlockSectionSequenceNumber()))) {
962        //    return _previousBlock;
963        //}
964        if ((_currentBlock == _activeTrain.getStartBlock())
965                && _activeTrain.getResetWhenDone() && _activeTrain.isTransitReversed()
966                && (as.getSequence() == _activeTrain.getStartBlockSectionSequenceNumber())) {
967            return _previousBlock;
968        }
969        if (as.getNextSection() != null) {
970            EntryPoint ep = as.getSection().getExitPointToSection(_nextSection, as.getDirection());
971            if ((ep != null) && (ep.getBlock() == b)) {
972                // this block is connected to a block in the next section
973                return ep.getFromBlock();
974            }
975        }
976        // this allocated section has multiple blocks _or_ there is no next Section
977        Block blk = as.getSection().getEntryBlock();
978        while (blk != null) {
979            if (b == blk) {
980                return as.getSection().getNextBlock();
981            }
982            blk = as.getSection().getNextBlock();
983        }
984        return null;
985    }
986
987    private void setNewCurrentSection(AllocatedSection as) {
988        if (as.getSection() == _nextSection) {
989            _previousAllocatedSection = _currentAllocatedSection;
990            _currentAllocatedSection = as;
991            _nextSection = as.getNextSection();
992            TransitSection ts = as.getTransitSection();
993            if (ts != null) {
994                _autoTrainAction.addTransitSection(ts);
995            }
996            // written the long way for readability
997            boolean nextSectionExpected = true;
998            if (ts != null &&
999                    ts.isSafe() &&
1000                    _activeTrain.getAllocateMethod() == ActiveTrain.ALLOCATE_BY_SAFE_SECTIONS) {
1001                nextSectionExpected = false;
1002            } else if (!_activeTrain.isAllocationReversed() &&
1003                    _activeTrain.getEndBlockSection() == _currentAllocatedSection.getSection()) {
1004                nextSectionExpected = false;
1005            } else if (_activeTrain.isAllocationReversed() &&
1006                    _activeTrain.getStartBlockSectionSequenceNumber() == _currentAllocatedSection.getSequence()) {
1007                nextSectionExpected = false;
1008            }
1009            log.debug("{}:Next Section Expected[{}]",_activeTrain.getActiveTrainName(),  nextSectionExpected);
1010            // NOw handled in SetSpeedBySignal()
1011            // check if new next Section exists but is not allocated to this train excepting above circumstances
1012            //if ( nextSectionExpected &&_nextSection != null && !_activeTrain.isInAllocatedList(_nextSection)) {
1013            //    // next section is not allocated to this train, must not enter it, even if signal is OK.
1014            //    log.warn("Stopping train [{}] in section [{}], as next section [{}] is not allocated",
1015            //            _activeTrain.getActiveTrainName(),_currentAllocatedSection.getSection().getDisplayName(USERSYS),_nextSection.getDisplayName(USERSYS));
1016            //    stopInCurrentSection(NO_TASK);
1017            //    _needSetSpeed = false;
1018            //}
1019            // see if we need to rescan as entering safe section.
1020            if (ts != null &&
1021                    ts.isSafe() &&
1022                    _activeTrain.getAllocateMethod() == ActiveTrain.ALLOCATE_BY_SAFE_SECTIONS) {
1023                _dispatcher.queueScanOfAllocationRequests();
1024            }
1025
1026        }
1027    }
1028
1029    // Criteria for being able to set or get a speed.
1030    protected boolean canSpeedBeSetOrChecked() {
1031        if (_pausingActive || getAutoEngineer() == null ||
1032                ((_activeTrain.getStatus() != ActiveTrain.RUNNING) &&
1033                        (_activeTrain.getStatus() != ActiveTrain.WAITING) &&
1034                        !_activeTrain.getStarted()) ||
1035                (_activeTrain.getMode() != ActiveTrain.AUTOMATIC)) {
1036            log.debug("{}:Train is not currently eligible for settingspeed or checking ghosts",_activeTrain.getActiveTrainName());
1037            return false;
1038        }
1039        return true;
1040    }
1041
1042    // called by above or when resuming after stopped action
1043    protected synchronized void setSpeedBySignal() {
1044        log.trace("Set Speed by Signal");
1045        if (!canSpeedBeSetOrChecked()) {
1046            log.trace("[{}]:cannot set speed.",getActiveTrain().getActiveTrainName());
1047            return;
1048        }
1049        // only bother to check signal if the next allocation is ours.
1050        // and the turnouts have been set
1051        if (checkAllocationsAhead() && checkTurn(getAllocatedSectionForSection(_nextSection))) {
1052            if (_activeTrain.getSignalType() == DispatcherFrame.SIGNALHEAD
1053                    && _controllingSignal != null) {
1054                setSpeedBySignalHead();
1055            } else if (_activeTrain.getSignalType() == DispatcherFrame.SIGNALMAST
1056                    && _controllingSignalMast != null) {
1057                setSpeedBySignalMast();
1058            } else {
1059                log.trace("{}:Set Speed by BlocksAllocated",_activeTrain.getActiveTrainName());
1060                setSpeedBySectionsAllocated();
1061            }
1062            checkForGhost();
1063        } else {
1064            // This might be the last section....
1065            if (_currentAllocatedSection != null && _currentAllocatedSection.getNextSection() == null) {
1066                stopInCurrentSection(END_TRAIN);
1067            } else {
1068                // This will stop it.
1069                stopInCurrentSection(NO_TASK);
1070                log.debug("{}:Set Stop",_activeTrain.getActiveTrainName());
1071                waitingOnAllocation = true;  // flag setSpeedBySignal required when another allocation made.
1072            }
1073        }
1074    }
1075
1076    private void checkForGhost() {
1077        if (!canSpeedBeSetOrChecked()) {
1078            log.trace("[{}]:cannot check for ghost.",getActiveTrain().getActiveTrainName());
1079            return;
1080        }
1081        if ( !(getTargetSpeed() == 0.0f || isStopping())
1082                && _nextBlock != null
1083                && _currentBlock != null
1084                && _nextBlock.getSensor() != null
1085                && _nextBlock.getIsGhost()) {
1086            if ( _currentBlock.getIsGhost()) {
1087                log.error("Stopping due to two consecutive no sensor blocks [{}], [{}]",
1088                        _currentBlock.getDisplayName(), _nextBlock.getDisplayName());
1089            } else {
1090                try {
1091                    _currentBlock.addPropertyChangeListener(new DarkTerritoryListener(_nextBlock.getSensor()));
1092                    _nextBlock.getSensor().setKnownState(Sensor.ACTIVE);
1093                } catch (jmri.JmriException ex) {
1094                    log.error("Error entering darkterratory");
1095                }
1096            }
1097        }
1098    }
1099
1100    /*
1101     * Check at least the next section is allocated
1102     */
1103    private boolean checkAllocationsAhead() {
1104        if (_nextSection != null) {
1105            // Check that next section is allocated...
1106            for (AllocatedSection allocatedSection : _activeTrain.getAllocatedSectionList()) {
1107                if (allocatedSection.getSection() == _nextSection) {
1108                    return true;
1109                }
1110            }
1111        }
1112        return false;
1113    }
1114
1115    private void setSpeedBySectionsAllocated() {
1116        if (!canSpeedBeSetOrChecked()) {
1117            log.trace("[{}]:cannot set speed.",getActiveTrain().getActiveTrainName());
1118            return;
1119        }
1120
1121        if (_stoppingByBlockOccupancy && (_stoppingBlock != null && _stoppingBlock.getState() == Block.UNOCCUPIED)) {
1122            // we are awaiting a delayed stop
1123            return;
1124        }
1125        int sectionsAhead = 0;
1126        for (AllocatedSection allocatedSection : _activeTrain.getAllocatedSectionList()) {
1127            if (!allocatedSection.getEntered()) {
1128                sectionsAhead++;
1129            }
1130        }
1131        float newSpeed = 0.0f;
1132        log.debug("[{}:SectionsAhead[{}]",_activeTrain.getActiveTrainName() ,sectionsAhead);
1133            switch (sectionsAhead) {
1134                case 0:
1135                    newSpeed = 0.0f;
1136                    break;
1137                case 1:
1138                    newSpeed = InstanceManager.getDefault(SignalSpeedMap.class)
1139                            .getSpeed("Medium");
1140                    // .getSpeed(_dispatcher.getStoppingSpeedName());
1141                    _activeTrain.setStatus(ActiveTrain.RUNNING);
1142                    break;
1143                default:
1144                    newSpeed = InstanceManager.getDefault(SignalSpeedMap.class)
1145                            .getSpeed("Normal");
1146                    // .getSpeed(_dispatcher.getStoppingSpeedName());
1147                    _activeTrain.setStatus(ActiveTrain.RUNNING);
1148            }
1149            if (_dispatcher.getUseOccupiedTrackSpeed()) {
1150                newSpeed = getMinSpeedOfOccupiedBlocks(newSpeed);
1151            }
1152            // see if needs to slow for next block.
1153            if (newSpeed > 0 && _nextBlock != null) {
1154                float speed = getSpeedFromBlock(_nextBlock);
1155                if (speed < newSpeed) {
1156                    // slow for next block
1157                    newSpeed = speed;
1158                }
1159            }
1160        if (newSpeed > 0) {
1161            log.trace("setSpeedBySectionsAllocated isStopping[{}]",isStopping());
1162            cancelStopInCurrentSection();
1163            setTargetSpeed(getThrottleSettingFromSpeed(newSpeed));
1164        } else {
1165            waitingOnAllocation = true;
1166            stopInCurrentSection(NO_TASK);
1167        }
1168    }
1169
1170    // Check for speed of incoming blocks.
1171    // in and out speed in is throttle percent.
1172    private float getMinSpeedOfOccupiedBlocks(float speed) {
1173        if (!_dispatcher.getUseOccupiedTrackSpeed()) {
1174            return speed;
1175        }
1176        // get slowest speed of any entered and still occupied
1177        // or entered but not released (HEADONLY / HEADANDTAIL
1178        float newSpeed = speed;
1179        for (AllocatedSection asE : _activeTrain.getAllocatedSectionList()) {
1180            if (asE.getEntered()) {
1181                for (Block b : asE.getSection().getBlockList()) {
1182                    if (b.getState() == Block.OCCUPIED
1183                            || _activeTrain.getTrainDetection() != TrainDetection.TRAINDETECTION_WHOLETRAIN ) {
1184                        if (getSpeedFromBlock(b) < newSpeed) {
1185                            newSpeed = getSpeedFromBlock(b);
1186                        }
1187                    }
1188                }
1189            }
1190        }
1191        log.trace("{}: getMinSpeedOfOccupiedBlocks Org Speed [{}] New [{}]",
1192                _activeTrain.getActiveTrainName(), speed, newSpeed);
1193        return newSpeed;
1194    }
1195
1196    /**
1197     * Check that all turnouts in a section have finished setting
1198     * for passage. If not listens on first bad turnout
1199     * and rechecks when set.
1200     * @param as Allocated section whose turnouts need to be checked.
1201     * @return true if no errors else false
1202     */
1203    private boolean checkTurn(AllocatedSection as) {
1204        if (as != null && as.getAutoTurnoutsResponse() != null) {
1205            Turnout to = _dispatcher.getAutoTurnoutsHelper().checkStateAgainstList(as.getAutoTurnoutsResponse());
1206            if (to != null) {
1207                // at least one turnout isnt correctly set
1208                to.addPropertyChangeListener(_turnoutStateListener = (PropertyChangeEvent e) -> {
1209                    if (e.getPropertyName().equals("KnownState")) {
1210                        ((Turnout) e.getSource()).removePropertyChangeListener(_turnoutStateListener);
1211                        setSpeedBySignal();
1212                    }
1213                });
1214                return false;
1215            }
1216        }
1217        return true;
1218    }
1219
1220    private void setSpeedBySignalMast() {
1221        //Set speed using SignalMasts;
1222        if (_controllingSignalMast == null) {
1223            // temporarily revert to by sections allocated
1224            setSpeedBySectionsAllocated();
1225            return;
1226        }
1227        String displayedAspect = _controllingSignalMast.getAspect();
1228        if (log.isTraceEnabled()) {
1229            log.trace("{}: Controlling mast {} ({})", _activeTrain.getTrainName(), _controllingSignalMast.getDisplayName(USERSYS), displayedAspect);
1230            if (_conSignalProtectedBlock == null) {
1231                log.trace("{}: Protected block is null", _activeTrain.getTrainName());
1232            } else {
1233                log.trace("{}: Protected block: {} state: {} speed: {}", _activeTrain.getTrainName(),
1234                        _conSignalProtectedBlock.getSensor().getDisplayName(USERSYS),
1235                        (_conSignalProtectedBlock.getSensor().getState() == Block.OCCUPIED ? "OCCUPIED" : "NOT OCCUPIED"),
1236                        _conSignalProtectedBlock.getBlockSpeed());
1237            }
1238        }
1239
1240        if ((_controllingSignalMast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER).equals(displayedAspect))
1241                || !_controllingSignalMast.getLit() || _controllingSignalMast.getHeld()) {
1242            checkForSignalPassedOrStop(_controllingSignalMast.getDisplayName(USERSYS));
1243        } else if (_controllingSignalMast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.PERMISSIVE) != null
1244                && _controllingSignalMast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.PERMISSIVE).equals(displayedAspect)) {
1245            setTargetSpeedState(RESTRICTED_SPEED);
1246            _activeTrain.setStatus(ActiveTrain.RUNNING);
1247        } else {
1248
1249            //if using signalmasts, set speed to lesser of aspect speed and signalmastlogic speed
1250            //  (minimum speed on the path to next signal, using turnout and block speeds)
1251            String aspectSpeedStr = (String) _controllingSignalMast.getSignalSystem().getProperty(displayedAspect, "speed");
1252            log.trace("{}: Signal {} speed {} for aspect {}", _activeTrain.getTrainName(), _controllingSignalMast.getDisplayName(USERSYS), aspectSpeedStr, displayedAspect);
1253            float speed = -1.0f;
1254            if (aspectSpeedStr != null) {
1255                try {
1256                    speed = Float.parseFloat(aspectSpeedStr);
1257                } catch (NumberFormatException nx) {
1258                    try {
1259                        speed = InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(aspectSpeedStr);
1260                        log.trace("{}: Signal {} speed from map for {} is {}", _activeTrain.getTrainName(), _controllingSignalMast.getDisplayName(USERSYS), aspectSpeedStr, speed);
1261                    } catch (IllegalArgumentException ex) {
1262                        //Considered Normal if the speed does not appear in the map
1263                        log.trace("{}: Speed not found {}", _activeTrain.getTrainName(), aspectSpeedStr);
1264                    }
1265                }
1266            }
1267            int aspectSpeed = (int) speed; //save for debug message
1268
1269            //get maximum speed for the route between current and next signalmasts
1270            float smLogicSpeed = -1.0f;
1271            String smDestinationName = "unknown";
1272            SignalMastLogic smLogic = InstanceManager.getDefault(SignalMastLogicManager.class).getSignalMastLogic(_controllingSignalMast);
1273            if (smLogic != null) {
1274                SignalMast smDestination = smLogic.getActiveDestination();
1275                if (smDestination != null) {
1276                    smDestinationName = smDestination.getDisplayName(USERSYS);
1277                    smLogicSpeed = (int) smLogic.getMaximumSpeed(smDestination);
1278                }
1279            }
1280
1281            //use the smaller of aspect speed or route speed
1282            if (smLogicSpeed > -1.0f && smLogicSpeed < speed) {
1283                speed = smLogicSpeed;
1284            }
1285
1286            log.debug("{}: {}({}) {}({}), Dest: {}, path max: {}",
1287                    _activeTrain.getTrainName(),
1288                    _controllingSignalMast.getDisplayName(USERSYS), displayedAspect, aspectSpeedStr, aspectSpeed,
1289                    smDestinationName, (int) smLogicSpeed);
1290            // Adjust for occupied blocks.
1291            if (_dispatcher.getUseOccupiedTrackSpeed()) {
1292                speed = getMinSpeedOfOccupiedBlocks(speed);
1293            }
1294            if (speed > -1.0f) {
1295                /* We should work on the basis that the speed required in the current block/section is governed by the signalmast
1296                 that we have passed and not the one we are approaching when we are accelerating.
1297                 However when we are decelerating we should be aiming to meet the speed required by the approaching signalmast
1298                 whether that is to slow down or come to a complete stand still.
1299                 */
1300                if (prevSpeed == -1 || speed < prevSpeed) {
1301                    log.debug("{}: Signal {} setting speed to {} for next", _activeTrain.getTrainName(),
1302                            _controllingSignalMast.getDisplayName(USERSYS), speed);
1303                    setTargetSpeedValue(speed);
1304                } else {
1305                    log.debug("{}: Signal {} setting speed to {} for previous", _activeTrain.getTrainName(),
1306                            _controllingSignalMast.getDisplayName(USERSYS), speed);
1307                    setTargetSpeedValue(prevSpeed);
1308                }
1309                prevSpeed = speed;
1310                _activeTrain.setStatus(ActiveTrain.RUNNING);
1311
1312            } else {
1313                log.warn("{}: No specific speeds found so will use the default", _activeTrain.getTrainName());
1314                setTargetSpeedState(NORMAL_SPEED);
1315                _activeTrain.setStatus(ActiveTrain.RUNNING);
1316            }
1317        }
1318    }
1319
1320    private void setSpeedBySignalHead() {
1321        // a held signal always stop
1322        if ( _controllingSignal != null && _controllingSignal.getAppearance() == SignalHead.HELD ) {
1323            // Held - Stop
1324            stopInCurrentSection(NO_TASK);
1325            return;
1326        }
1327
1328        if (useSpeedProfile) {
1329            // find speed from signal.
1330            // find speed from block
1331            // use least
1332            float blockSpeed = getSpeedFromBlock(_conSignalProtectedBlock);
1333
1334            float signalSpeed;
1335            String signalSpeedName;
1336            String displayedAspect = _controllingSignal.getAppearanceName();
1337            try {
1338                signalSpeedName =
1339                        InstanceManager.getDefault(SignalSpeedMap.class).getAppearanceSpeed(displayedAspect);
1340                signalSpeed = InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(signalSpeedName);
1341            } catch (Throwable ex) { // if _anything_ goes wrong, contain it
1342                signalSpeed = -1.0f;
1343                log.warn("{}: Block {} AppearanceSpeed {} not found in SignalSpeedMap",
1344                        _activeTrain.getTrainName(), _conSignalProtectedBlock.getDisplayName(USERSYS), displayedAspect);
1345            }
1346            float useSpeed;
1347            if (blockSpeed < signalSpeed) {
1348                useSpeed = blockSpeed;
1349            } else {
1350                useSpeed = signalSpeed;
1351            }
1352
1353            log.trace("BlockSpeed[{}] SignalSpeed[{}]", blockSpeed, signalSpeed);
1354            if (useSpeed < 0.01f) {
1355                checkForSignalPassedOrStop(_controllingSignal.getDisplayName(USERSYS));
1356            } else {
1357                setTargetSpeedByProfile(useSpeed,_stopBySpeedProfileAdjust,true);
1358            }
1359        } else {
1360            switch (_controllingSignal.getAppearance()) {
1361                case SignalHead.DARK:
1362                case SignalHead.RED:
1363                case SignalHead.FLASHRED:
1364                    // May get here from signal changing before Block knows it is occupied, so must
1365                    //      check Block occupancy sensor, which must change before signal.
1366                    // check to to see if its allocated to us!!!
1367                    //      check Block occupancy sensor if it is in an allocated block, which must change before signal
1368                    // If the train has no _currentAllocatedSection it is in a first block outside transit.
1369                    checkForSignalPassedOrStop(_controllingSignal.getDisplayName(USERSYS));
1370                    break;
1371                case SignalHead.YELLOW:
1372                case SignalHead.FLASHYELLOW:
1373                    setTargetSpeedState(SLOW_SPEED);
1374                    _activeTrain.setStatus(ActiveTrain.RUNNING);
1375                    break;
1376                case SignalHead.GREEN:
1377                case SignalHead.FLASHGREEN:
1378                    setTargetSpeedState(NORMAL_SPEED);
1379                    _activeTrain.setStatus(ActiveTrain.RUNNING);
1380                    break;
1381                case SignalHead.LUNAR:
1382                case SignalHead.FLASHLUNAR:
1383                    setTargetSpeedState(RESTRICTED_SPEED);
1384                    _activeTrain.setStatus(ActiveTrain.RUNNING);
1385                    break;
1386                default:
1387                    log.warn("Signal Head[{}] has invalid Appearence - using stop",_controllingSignal.getAppearance());
1388                    stopInCurrentSection(NO_TASK);
1389            }
1390
1391        }
1392    }
1393
1394    /**
1395     * Check to see if a stop is really required, or if this is the
1396     * signal head that was just passed, in which case ignore as the signal goes red before a
1397     * new signal exists.
1398     *
1399     * @param displayName name of signal for debug messages.
1400     */
1401    private void checkForSignalPassedOrStop(String displayName) {
1402        // if current section is null we are in a pre transit block.
1403        if (_currentAllocatedSection != null) {
1404            if ((_currentAllocatedSection.isInActiveBlockList(_conSignalProtectedBlock) ||
1405                    (_nextSection != null && _activeTrain.isInAllocatedList(_nextSection) && _nextSection.containsBlock(_conSignalProtectedBlock)))
1406                    && _conSignalProtectedBlock.getSensor().getState() == Block.OCCUPIED) {
1407                // Train has just passed this signal - ignore this signal
1408                log.debug("{}: _conSignalProtectedBlock [{}] for signal [{}] is the block just past so ignore.", _activeTrain.getTrainName(),
1409                        _conSignalProtectedBlock.getDisplayName(USERSYS), displayName);
1410            } else {
1411                log.debug("{}: stopping for signal [{}] ", _activeTrain.getTrainName(),
1412                         displayName);
1413                stopInCurrentSection(NO_TASK);
1414            }
1415        }
1416    }
1417
1418    protected float getSpeedFromBlock(Block block) {
1419        String blockSpeedName = block.getBlockSpeed();
1420        if (blockSpeedName.contains("Global")) {
1421            blockSpeedName = InstanceManager.getDefault(BlockManager.class).getDefaultSpeed();
1422        }
1423        float blockSpeed = -1.0f;
1424        if (!blockSpeedName.isEmpty()) {
1425            try {
1426                blockSpeed = Float.parseFloat(blockSpeedName);
1427            } catch (NumberFormatException nx) {
1428                try {
1429                    blockSpeed = InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(blockSpeedName);
1430                    log.debug("{} {}: block speed from map for {} is {}",
1431                            _activeTrain.getTrainName(), block.getDisplayName(USERSYS), blockSpeedName,
1432                            blockSpeed);
1433                } catch (Throwable ex) { // if _anything_ goes wrong, contain it
1434                    //Considered Normal if the speed does not appear in the map
1435                    log.warn("{}: Block {} Speed {} not found in SignalSpeedMap",
1436                            _activeTrain.getTrainName(), block.getDisplayName(USERSYS), blockSpeed);
1437                }
1438            }
1439        }
1440        return blockSpeed;
1441    }
1442
1443    float prevSpeed = -1.0f;
1444
1445    // called to cancel a stopping action that is in progress
1446    private synchronized void cancelStopInCurrentSection() {
1447        log.trace("[{}]:Cancel Stopping", _activeTrain.getTrainName());
1448        cancelStoppingBySensor();
1449        _stoppingByBlockOccupancy = false;
1450        _stoppingBlock = null;
1451        _stoppingUsingSpeedProfile = false;
1452        _stoppingBlock = null;
1453        _autoEngineer.slowToStop(false);
1454    }
1455
1456    private synchronized void stopInCurrentSection(int task) {
1457        if (_currentAllocatedSection == null) {
1458            log.error("{}: Current allocated section null on entry to stopInCurrentSection", _activeTrain.getTrainName());
1459            setStopNow();
1460            return;
1461        }
1462        log.debug("{}: StopInCurrentSection called for {} task[{}] targetspeed[{}]", _activeTrain.getTrainName(), _currentAllocatedSection.getSection().getDisplayName(USERSYS),task,getTargetSpeed());
1463        if (getTargetSpeed() == 0.0f || isStopping()) {
1464            log.debug("{}: train is already stopped or stopping.", _activeTrain.getTrainName());
1465            // ignore if train is already stopped or if stopping is in progress
1466            return;
1467        }
1468        // if Section has stopping sensors, use them
1469        if (_currentAllocatedSection.getSection().getState() == Section.FORWARD) {
1470            _stopSensor = _currentAllocatedSection.getSection().getForwardStoppingSensor();
1471        } else {
1472            _stopSensor = _currentAllocatedSection.getSection().getReverseStoppingSensor();
1473        }
1474        if (_stopSensor != null && _useStopSensor) {
1475            if (_stopSensor.getKnownState() == Sensor.ACTIVE) {
1476                // stop sensor is already active, stop now
1477                setStopNow();
1478            } else {
1479                setDecreasedSpeedBeforeStop();
1480                _stopSensor.addPropertyChangeListener(_stopSensorListener = (java.beans.PropertyChangeEvent e) -> {
1481                    handleStopSensorChange(e);
1482                });
1483                _stoppingBySensor = true;
1484            }
1485        } else if (useSpeedProfile && _stopBySpeedProfile) {
1486            log.debug("{}: Section [{}] Section Length[{}] Max Train Length [{}] StopBySpeedProfile [{}]. setStopNow", _activeTrain.getTrainName(),
1487                    _currentAllocatedSection.getSection().getDisplayName(USERSYS), _currentAllocatedSection.getActualLength(), getMaxTrainLengthMM(), _stopBySpeedProfile);
1488            // stopping by speed profile uses section length to stop
1489            setTargetSpeedState(STOP_SPEED,useSpeedProfile);
1490        } else if (_currentAllocatedSection.getActualLength()  < getMaxTrainLengthMM()) {
1491            log.debug("{}: Section [{}] Section Length[{}] Max Train Length [{}]. setStopNow({})",
1492                    _activeTrain.getTrainName(),
1493                    _currentAllocatedSection.getSection().getDisplayName(USERSYS),
1494                    _currentAllocatedSection.getActualLength(),
1495                    getMaxTrainLengthMM(), _stopBySpeedProfile);
1496            // train will not fit comfortably in the Section, stop it immediately
1497            setStopNow();
1498        } else if (_activeTrain.getTrainDetection() == TrainDetection.TRAINDETECTION_WHOLETRAIN) {
1499            log.debug("{}: train will fit in [{}] ({}>={}), stop when prev block clears.", _activeTrain.getTrainName(),
1500                    _currentAllocatedSection.getSection().getDisplayName(USERSYS), _currentAllocatedSection.getActualLength(), getMaxTrainLengthMM());
1501            // train will fit in current allocated Section and has resistance wheels
1502            // try to stop by watching Section Block occupancy
1503            if (_currentAllocatedSection.getSection().getNumBlocks() == 1) {
1504                if (_previousAllocatedSection != null) {
1505                    Block tBlock;
1506                    // just because current section has one block does not mean the previous one did.
1507                    if (_previousAllocatedSection.getSection().getNumBlocks() == 1) {
1508                       tBlock = _previousAllocatedSection.getSection().getLastBlock();
1509                    } else {
1510                       tBlock = _previousAllocatedSection.getSection().getExitBlock();
1511                    }
1512                    if ((tBlock != null) && (tBlock.getState() == Block.OCCUPIED)) {
1513                        _stoppingBlock = tBlock;
1514                        setStopByBlockOccupancy(false);
1515                    } else {
1516                        setStopNow();
1517                    }
1518                } else {
1519                    setStopNow();
1520                }
1521            } else {
1522                // Section has multiple blocks
1523                Block exitBlock = _currentAllocatedSection.getExitBlock();
1524                Block enterBlock = _currentAllocatedSection.getEnterBlock(_previousAllocatedSection);
1525                if (enterBlock == null) {
1526                    // this is the first Section of the Transit, with train starting in this Section
1527                    setStopNow();
1528                } else if (exitBlock == enterBlock) {
1529                    // entry and exit are from the same Block
1530                    if ((_previousBlock != null) && (_previousBlock.getState() == Block.OCCUPIED)
1531                            && (getBlockLength(exitBlock) > getMaxTrainLengthMM())) {
1532                        _stoppingBlock = _previousBlock;
1533                        setStopByBlockOccupancy(false);
1534                    } else {
1535                        setStopNow();
1536                    }
1537                } else {
1538                    // try to move train as far into the Section as it will comfortably fit
1539                    Block tstBlock = exitBlock;
1540                    if (tstBlock == null) {
1541                        if (_currentAllocatedSection.getDirection() == Section.REVERSE) {
1542                            tstBlock = _currentAllocatedSection.getSection().getBlockBySequenceNumber(0);
1543                        } else {
1544                            tstBlock = _currentAllocatedSection.getSection().getBlockBySequenceNumber(
1545                                    _currentAllocatedSection.getSection().getNumBlocks() - 1);
1546                        }
1547                    }
1548                    int tstLength = getBlockLength(tstBlock);
1549                    int tstBlockSeq = _currentAllocatedSection.getSection().getBlockSequenceNumber(tstBlock);
1550                    while ((tstLength < getMaxTrainLengthMM()) && (tstBlock != enterBlock)) {
1551                        int newSeqNumber;
1552                        if (_currentAllocatedSection.getDirection() == Section.REVERSE) {
1553                            newSeqNumber = tstBlockSeq + 1;
1554                        } else {
1555                            newSeqNumber = tstBlockSeq - 1;
1556                        }
1557                        tstBlock = _currentAllocatedSection.getSection().getBlockBySequenceNumber(newSeqNumber);
1558                        tstBlockSeq = newSeqNumber;
1559                        tstLength += getBlockLength(tstBlock);
1560                    }
1561                    if (getMaxTrainLengthMM() > tstLength) {
1562                        setStopNow();
1563                    } else if (tstBlock == enterBlock) {
1564                        // train fits, but needs all available Blocks
1565                        Block previousSectionExitBlock = _previousAllocatedSection.getExitBlock();
1566                        if ((previousSectionExitBlock != null) && (previousSectionExitBlock.getState() == Block.OCCUPIED)) {
1567                            _stoppingBlock = previousSectionExitBlock;
1568                            setStopByBlockOccupancy(true);
1569                        } else {
1570                            setStopNow();
1571                        }
1572                    } else {
1573                        // train fits, and doesn't need all available Blocks
1574                        int xSeqNumber = tstBlockSeq + 1;
1575                        if (_currentAllocatedSection.getDirection() == Section.FORWARD ) {
1576                            xSeqNumber = tstBlockSeq - 1;
1577                        }
1578                        _stoppingBlock = _currentAllocatedSection.getSection().
1579                                getBlockBySequenceNumber(xSeqNumber);
1580                        setStopByBlockOccupancy(true);
1581                    }
1582                }
1583            }
1584        } else {
1585            // train will fit, but no way to stop it reliably
1586            setStopNow();
1587        }
1588        // even if no task is required it must be run
1589        // as cleanup happens after train stops.
1590        Runnable waitForStop = new WaitForTrainToStop(task);
1591        Thread tWait = jmri.util.ThreadingUtil.newThread(waitForStop, "Wait for stop " + getActiveTrain().getActiveTrainName());
1592        tWait.start();
1593    }
1594
1595    protected synchronized void executeStopTasks(int task) {
1596        // clean up stopping
1597        cancelStopInCurrentSection();
1598        _dispatcher.queueReleaseOfCompletedAllocations();
1599        log.trace("exec[{}]",task);
1600        switch (task) {
1601            case END_TRAIN:
1602                _activeTrain.setStatus(ActiveTrain.DONE);
1603                break;
1604            case NO_TASK:
1605                // clean up stop
1606                break;
1607            case END_REVERSAL:
1608                /* Reset _previousBlock to be the _currentBlock if we do a continious reverse otherwise the stop in block method fails
1609                to stop the loco in the correct block
1610                 if the first block we come to has a stopped or held signal */
1611                _activeTrain.setRestart(_activeTrain.getDelayReverseRestart(),_activeTrain.getReverseRestartDelay(),
1612                        _activeTrain.getReverseRestartSensor(),_activeTrain.getResetReverseRestartSensor());
1613                _activeTrain.setTransitReversed(true);
1614                _activeTrain.reverseAllAllocatedSections();
1615                setEngineDirection();
1616                _previousBlock = null;
1617                _nextBlock = getNextBlock(_currentBlock,_currentAllocatedSection);
1618                if (_activeTrain.getDelayReverseRestart() == ActiveTrain.NODELAY) {
1619                   _activeTrain.holdAllocation(false);
1620                    // a reversal can happen in mid section
1621                    setupNewCurrentSignal(_currentAllocatedSection, true);
1622                    setSpeedBySignal();
1623                    if ((_nextSection != null) && !_activeTrain.isInAllocatedList(_nextSection)) {
1624                        _dispatcher.queueScanOfAllocationRequests();
1625                        break;
1626                    }
1627                }
1628                break;
1629            case BEGINNING_RESET:
1630                _activeTrain.setRestart(_activeTrain.getDelayedRestart(),_activeTrain.getRestartDelay(),
1631                        _activeTrain.getRestartSensor(),_activeTrain.getResetRestartSensor());
1632                if (_activeTrain.getResetWhenDone()) {
1633                    if (_activeTrain.getDelayedRestart() == ActiveTrain.NODELAY && !_activeTrain.getReverseAtEnd()) {
1634                        log.error("[{}]: train is continueing without pause, should have been handled in handleBlockStateChange.",_activeTrain.getTrainName());
1635                    } else {
1636                        // then active train is delayed
1637                        _activeTrain.setTransitReversed(false);
1638                        _activeTrain.resetAllAllocatedSections();
1639                        _previousBlock = null;
1640                        _nextBlock = getNextBlock(_currentBlock,_currentAllocatedSection);
1641                        setEngineDirection();
1642                        _activeTrain.setRestart(_activeTrain.getDelayedRestart(),_activeTrain.getRestartDelay(),
1643                                _activeTrain.getRestartSensor(), _activeTrain.getResetRestartSensor());
1644                        if ((_nextSection != null) && !_activeTrain.isInAllocatedList(_nextSection)) {
1645                            _dispatcher.queueScanOfAllocationRequests();
1646                        }
1647                        // can be mid block
1648                        setupNewCurrentSignal(null, true);
1649                        setSpeedBySignal();
1650
1651                    }
1652                } else {
1653                    // dispatcher cancelled auto restart while train was stopping?
1654                    log.warn("[{}]resetWhenDone flag reset, likely user cancelling while processing stop",
1655                            _activeTrain.getActiveTrainName());
1656                }
1657                break;
1658            default:
1659                log.debug("[{}]Invalid action [{}] in executeStopTasksRequest to execute BEGINNING_RESET cancelled", _activeTrain.getActiveTrainName(),task);
1660                break;
1661        }
1662    }
1663
1664    /**
1665     * Remove the stopping sensor
1666     */
1667    private void cancelStoppingBySensor() {
1668        if (_stopSensor != null) {
1669            _stopSensor.removePropertyChangeListener(_stopSensorListener);
1670            _stoppingBySensor = false;
1671            _stopSensorListener = null;
1672            _stopSensor = null;
1673        }
1674    }
1675
1676    /**
1677     * When the stopping sensor we are waiting on goes active
1678     * stop the train or set a new speed and destroy itself
1679     * @param e  - the property change event
1680     */
1681    private synchronized void handleStopSensorChange(java.beans.PropertyChangeEvent e) {
1682        if (e.getPropertyName().equals("KnownState") && (int) e.getNewValue() == Sensor.ACTIVE) {
1683            _stopSensor.removePropertyChangeListener(_stopSensorListener);
1684            _stoppingBySensor = false;
1685            _stopSensorListener = null;
1686            _stopSensor = null;
1687            if (_needSetSpeed) {
1688                _needSetSpeed = false;
1689                setSpeedBySignal();
1690            } else {
1691                setStopNow();
1692            }
1693        }
1694    }
1695
1696    private synchronized void setStopNow() {
1697        setStopNow(false);
1698        }
1699
1700    private synchronized void setStopNow(boolean useSpeedProfile) {
1701        setTargetSpeedState(STOP_SPEED,useSpeedProfile);
1702        if (_currentAllocatedSection == null) {  // this may occur if the train is not in the selected block when initially created and the signal is held.
1703            _activeTrain.setStatus(ActiveTrain.WAITING);
1704        } else if (_currentAllocatedSection.getNextSection() == null) {
1705            // wait for train to stop - this lets action items complete in a timely fashion
1706            waitUntilStopped();
1707            _activeTrain.setStatus(ActiveTrain.DONE);
1708        } else {
1709            _activeTrain.setStatus(ActiveTrain.WAITING);
1710        }
1711    }
1712
1713    /*
1714     * When multi block stopping, the stopping block may not be occupied yet.
1715     */
1716    private void setStopByBlockOccupancy(boolean ignoreNotOccupied) {
1717        // note: _stoppingBlock must be set before invoking this method
1718        //  verify that _stoppingBlock is actually occupied, if not stop immed
1719        if (_stoppingBlock.getState() == Block.OCCUPIED || ignoreNotOccupied) {
1720            setDecreasedSpeedBeforeStop();
1721            _stoppingByBlockOccupancy = true;
1722        } else {
1723            setStopNow();
1724        }
1725    }
1726
1727    /**
1728     * Before stopping by sensor alone, or by clearing previous block,
1729     * set the speed to the user defined preference.
1730     */
1731    private void setDecreasedSpeedBeforeStop() {
1732        float signalSpeed = 25;
1733        try {
1734            signalSpeed = InstanceManager.getDefault(SignalSpeedMap.class)
1735                    .getSpeed(_dispatcher.getStoppingSpeedName());
1736        } catch (IllegalArgumentException ex) {
1737            log.error("Missing [{}] from Speed table - defaulting to 25",
1738                    _dispatcher.getStoppingSpeedName());
1739        }
1740        if (getThrottleSettingFromSpeed(signalSpeed) < getTargetSpeed()) {
1741            if (useSpeedProfile) {
1742                // use 75 percent or normal amount, dont clear isstopping for ramping.
1743                setTargetSpeedByProfile(signalSpeed,_stopBySpeedProfileAdjust*0.75f,false);
1744            } else {
1745                setTargetSpeed(signalSpeed/100.0f);
1746            }
1747        }
1748    }
1749
1750    ///**
1751    // * Sets the throttle percent unless it is already less than the new setting
1752    // * @param throttleSetting  Max ThrottleSetting required.
1753    // */
1754    //private synchronized void setToAMaximumThrottle(float throttleSetting) {
1755    //    if (throttleSetting < getTargetSpeed()) {
1756    //        setTargetSpeed(throttleSetting);
1757    //    }
1758    //}
1759
1760    /**
1761     * Calculates the throttle setting for a given speed.
1762     * @param speed  the unadjusted speed.
1763     * @return - throttle setting (a percentage)
1764     */
1765    private synchronized float getThrottleSettingFromSpeed(float speed) {
1766        if (useSpeedProfile) {
1767            float throttleSetting = _activeTrain.getRosterEntry().getSpeedProfile()
1768                    .getThrottleSettingFromSignalMapSpeed(speed, getForward());
1769            return throttleSetting;
1770        }
1771        if (_activeTrain.getSignalType() == DispatcherFrame.SIGNALMAST) {
1772            float mls;
1773            if (_controllingSignalMast != null) {
1774                mls = _controllingSignalMast.getSignalSystem().getMaximumLineSpeed();
1775            } else {
1776                //plan B
1777                mls = _dispatcher.getMaximumLineSpeed();
1778            }
1779            float throttleSetting = (speed / mls);
1780            return throttleSetting;
1781        } else {
1782            return speed/100.0f;
1783        }
1784    }
1785
1786
1787    /**
1788     * sets the throttle based on an index number into _speedRatio array
1789     * @param speedState  Index value
1790     */
1791    private synchronized void setTargetSpeedState(int speedState) {
1792        setTargetSpeedState(speedState,false);
1793    }
1794
1795    /**
1796     * sets the throttle based on an index number into _speedRatio array
1797     * @param speedState  Index value
1798     * @param stopBySpeedProfile if true use speed profile
1799     */
1800    private synchronized void setTargetSpeedState(int speedState,boolean stopBySpeedProfile) {
1801        log.trace("{}: setTargetSpeedState:({})",_activeTrain.getTrainName(),speedState);
1802        _autoEngineer.slowToStop(false);
1803        float stoppingDistanceAdjust =  _stopBySpeedProfileAdjust *
1804                ( _activeTrain.isTransitReversed() ?
1805                _currentAllocatedSection.getTransitSection().getRevStopPerCent() :
1806                    _currentAllocatedSection.getTransitSection().getFwdStopPerCent()) ;
1807        log.debug("stoppingDistanceAdjust[{}] isReversed[{}] stopBySpeedProfileAdjust[{}]",stoppingDistanceAdjust,
1808                _activeTrain.isTransitReversed(),_stopBySpeedProfileAdjust );
1809        if (speedState > STOP_SPEED) {
1810            cancelStopInCurrentSection();
1811            if (_currentRampRate == RAMP_SPEEDPROFILE && useSpeedProfile) {
1812                // we are going to ramp up  / down using section length and speed profile
1813                _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock)
1814                        * stoppingDistanceAdjust, speedState);
1815            } else {
1816                setTargetSpeed(_speedRatio[speedState]);
1817            }
1818        } else if (stopBySpeedProfile) {
1819            // we are going to stop by profile
1820            _stoppingUsingSpeedProfile = true;
1821            _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock)
1822                    * stoppingDistanceAdjust, 0.0f);
1823        } else {
1824            _autoEngineer.setHalt(true);
1825            setTargetSpeed(0.0f);
1826        }
1827    }
1828
1829    private synchronized void setTargetSpeedByProfile(float speedState, float stopBySpeedProfileAdjust, boolean cancelStopping) {
1830        // the speed comes in as units of warrents (mph, kph, mm/s etc)
1831            try {
1832                float throttleSetting = _activeTrain.getRosterEntry().getSpeedProfile().getThrottleSettingFromSignalMapSpeed(speedState, getForward());
1833                log.debug("{}: setTargetSpeedByProfile: {} SpeedState[{}]",
1834                        _activeTrain.getTrainName(),
1835                        throttleSetting,
1836                        speedState);
1837                if (throttleSetting > 0.009 && _currentRampRate != RAMP_SPEEDPROFILE && useSpeedProfile) {
1838                    if (cancelStopping) {cancelStopInCurrentSection();}
1839                    setTargetSpeed(throttleSetting); // apply speed factor and max
1840                } else if (throttleSetting > 0.009) {
1841                    if (cancelStopping) {cancelStopInCurrentSection();}
1842                    setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock)  * stopBySpeedProfileAdjust , throttleSetting);
1843                } else if (useSpeedProfile && _stopBySpeedProfile) {
1844                    setTargetSpeed(0.0f);
1845                    _stoppingUsingSpeedProfile = true;
1846                    _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock)  * stopBySpeedProfileAdjust, 0.0f);
1847                } else {
1848                    _autoEngineer.slowToStop(false);
1849                    setTargetSpeed(0.0f);
1850                    _autoEngineer.setHalt(true);
1851                }
1852            } catch (Exception ex) {
1853                log.error("setTargetSpeedByProfile crashed - Emergency Stop: ", ex );
1854                _autoEngineer.slowToStop(false);
1855                setTargetSpeed(-1.0f);
1856                _autoEngineer.setHalt(true);
1857            }
1858        }
1859
1860    /**
1861     * Pass in speed as shown on dialogs, and convert to decimal speed needed by
1862     * throttle.
1863     */
1864    private synchronized void setTargetSpeedValue(float speed) {
1865        log.debug("{}: setTargetSpeedValue: Speed[{}]",_activeTrain.getTrainName(),speed);
1866        if (useSpeedProfile) {
1867            setTargetSpeedByProfile(speed,_stopBySpeedProfileAdjust,true);
1868            return;
1869        }
1870        _autoEngineer.slowToStop(false);
1871        float mls;
1872        if (_controllingSignalMast != null) {
1873            mls = _controllingSignalMast.getSignalSystem().getMaximumLineSpeed();
1874        } else {
1875            mls = _dispatcher.getMaximumLineSpeed();
1876        }
1877        float decSpeed = (speed / mls);
1878        if (decSpeed > 0.0f) {
1879            cancelStopInCurrentSection();
1880            setTargetSpeed(decSpeed);
1881        } else {
1882            setTargetSpeed(0.0f);
1883            _autoEngineer.setHalt(true);
1884        }
1885    }
1886
1887    private int getBlockLength(Block b) {
1888        if (b == null) {
1889            return (0);
1890        }
1891        return (int) b.getLengthMm();
1892//        float fLength = b.getLengthMm() / (float) _dispatcher.getScale().getScaleFactor();
1893//        if (_dispatcher.getUseScaleMeters()) {
1894//            return (int) (fLength * 0.001f);
1895//        }
1896//        return (int) (fLength * 0.00328084f);
1897    }
1898
1899    /**
1900     * Initiates running in manual mode with external throttle.
1901     * <p>
1902     * This method is triggered by an action in the Transit. The throttle in use
1903     * for automatic operation is dispatched.
1904     */
1905    protected void initiateWorking() {
1906        if (_activeTrain.getStatus() != ActiveTrain.WORKING) {
1907            _activeTrain.setMode(ActiveTrain.DISPATCHED);
1908            _activeTrain.setStatus(ActiveTrain.WORKING);
1909            saveSpeedAndDirection();
1910            if (_autoEngineer != null) {
1911                _autoEngineer.setHalt(true);
1912                waitUntilStopped();
1913                _autoEngineer.abort();
1914                InstanceManager.throttleManagerInstance().releaseThrottle(_throttle, this);
1915                _autoEngineer = null;
1916                _throttle = null;
1917            }
1918        }
1919    }
1920
1921    /**
1922     * Returns when train is stopped.
1923     * <p>
1924     * Note: Provides for _autoEngineer becoming null during wait Ties up the
1925     * current autoActiveTrain thread.
1926     */
1927    protected void waitUntilStopped() {
1928        boolean doneWaiting = false;
1929        while (!doneWaiting) {
1930            if (_autoEngineer != null) {
1931                doneWaiting = _autoEngineer.isStopped();
1932            } else {
1933                doneWaiting = true;
1934            }
1935            if (!doneWaiting) {
1936                try {
1937                    Thread.sleep(50);
1938                } catch (InterruptedException e) {
1939                    // ignore this exception
1940                }
1941            }
1942        }
1943    }
1944
1945    /**
1946     * Resumes automatic running after a working session using an external
1947     * throttle This method is triggered by the dispatcher hitting the "Resume
1948     * Auto Running" button A new throttle is acquired to allow automatic
1949     * running to resume
1950     */
1951    protected void resumeAutomaticRunning() {
1952        if ((_activeTrain.getStatus() == ActiveTrain.WORKING)
1953                || (_activeTrain.getStatus() == ActiveTrain.READY)) {
1954            _autoTrainAction.cancelDoneSensor();
1955            if (initialize()) {
1956                _resumingAutomatic = true;
1957            } else {
1958                log.error("Failed to initialize throttle when resuming automatic mode.");
1959            }
1960        }
1961    }
1962
1963    /**
1964     * Pause the auto active train for a specified number of fast clock minutes.
1965     *
1966     * @param fastMinutes the number of minutes to pause the train
1967     * @return the thread waiting on the pause or null if already paused
1968     */
1969    public Thread pauseTrain(int fastMinutes) {
1970        if (_pausingActive) {
1971            // if a pause train thread is currently active, ignore this call
1972            return (null);
1973        }
1974        Runnable pauseTrain = new PauseTrain(fastMinutes);
1975        Thread tPause = jmri.util.ThreadingUtil.newThread(pauseTrain, "pause train " + _activeTrain.getTrainName());
1976        tPause.start();
1977        return tPause;
1978    }
1979
1980    public void terminate() {
1981        // here add code to stop the train and release its throttle if it is in autoRun
1982        while (_activeHornThreads > 0) {
1983            try {
1984                Thread.sleep(50);
1985            } catch (InterruptedException e) {
1986                // ignore this exception
1987            }
1988        }
1989        _autoTrainAction.clearRemainingActions();
1990        if (_autoEngineer != null) {
1991            _autoEngineer.setHalt(true);
1992            try {
1993                Thread.sleep(50);
1994            } catch (InterruptedException e) {
1995                // ignore this exception
1996            }
1997            waitUntilStopped();
1998            _autoEngineer.abort();
1999            InstanceManager.throttleManagerInstance().releaseThrottle(_throttle, this);
2000        }
2001    }
2002
2003    public void dispose() {
2004        if (_controllingSignalMast != null && _conSignalMastListener != null) {
2005            _controllingSignalMast.removePropertyChangeListener(_conSignalMastListener);
2006        }
2007        _controllingSignalMast = null;
2008        _conSignalMastListener = null;
2009    }
2010
2011// _________________________________________________________________________________________
2012    // This class waits for train stop in a separate thread
2013    class WaitForTrainToStop implements Runnable {
2014
2015        public WaitForTrainToStop(int task) {
2016            _task = task;
2017        }
2018
2019        @Override
2020        public void run() {
2021            boolean waitingOnTrain = true;
2022            try {
2023                while (waitingOnTrain) {
2024                    if ((getAutoEngineer() != null) && (getAutoEngineer().isStopped())) {
2025                        waitingOnTrain = false;
2026                    } else {
2027                        Thread.sleep(_delay);
2028                    }
2029                }
2030                log.trace("executing task[{}]",_task);
2031                executeStopTasks(_task);
2032            } catch (InterruptedException e) {
2033                log.warn("Waiting for train to stop interrupted - stop tasks not executing");
2034            } catch (Exception e) {
2035                log.error("Waiting for train to stop crashed - stop tasks not executing.", e);
2036            }
2037        }
2038
2039        private final int _delay = 91;
2040        private int _task = 0;
2041    }
2042
2043    /**
2044     * Pause the train in a separate thread. Train is stopped, then restarted
2045     * after specified number of fast Minutes have elapsed.
2046     */
2047    class PauseTrain implements Runnable {
2048        /**
2049         * Create a PauseTrain
2050         *
2051         * @param fastMinutes the number of fast clock minutes to pause the
2052         *                    train
2053         */
2054        public PauseTrain(int fastMinutes) {
2055            _fastMinutes = fastMinutes;
2056        }
2057
2058        @Override
2059        public void run() {
2060            // set to pause at a fast ramp rate
2061            _pausingActive = true;
2062            // TODO: use stop in section or block?
2063            _savedRampRate = getRampRate();
2064            setCurrentRampRate(RAMP_FAST);
2065            stopInCurrentSection(NO_TASK);
2066            // wait for train to stop
2067            boolean waitNow = true;
2068            boolean keepGoing = true;
2069            while (waitNow) {
2070                try {
2071                    Thread.sleep(101);
2072                    if (_autoEngineer != null) {
2073                        if (_autoEngineer.isStopped()) {
2074                            waitNow = false;
2075                        }
2076                    } else {
2077                        waitNow = false;
2078                    }
2079                } catch (InterruptedException e) {
2080                    log.trace("InterruptedException while waiting to stop for pause-indicates action cancelled.", e);
2081                    waitNow = false;
2082                    keepGoing = false;
2083                }
2084            }
2085            _activeTrain.setStatus(ActiveTrain.PAUSED);
2086            if (keepGoing) {
2087                // wait for specified fast clock time
2088                Timebase _clock = InstanceManager.getDefault(jmri.Timebase.class);
2089                java.beans.PropertyChangeListener _clockListener = (java.beans.PropertyChangeEvent e) -> {
2090                    _fastMinutes--;
2091                };
2092                _clock.addMinuteChangeListener(_clockListener);
2093                // wait for fast minutes to tick away
2094                waitNow = true;
2095                while (waitNow) {
2096                    try {
2097                        Thread.sleep(501);
2098                        if (_fastMinutes <= 0) {
2099                            waitNow = false;
2100                        }
2101                    } catch (InterruptedException e) {
2102                        log.trace("InterruptedException indicates action cancelled.", e);
2103                        keepGoing = false;
2104                    }
2105                }
2106                _clock.removeMinuteChangeListener(_clockListener);
2107            }
2108            _pausingActive = false;
2109            if (keepGoing) {
2110                // this thread was not interrupted
2111                //   resume running - restore speed, status, and ramp rate
2112                setCurrentRampRate(_savedRampRate);
2113                // Set speed by signal also works if signal missing
2114                // so we dont need to restore a previous value.
2115                _activeTrain.setStatus(ActiveTrain.RUNNING);
2116                setSpeedBySignal();
2117            }
2118        }
2119        private int _fastMinutes = 0;
2120        private int _savedRampRate = RAMP_NONE;
2121    }
2122
2123    // _________________________________________________________________________________________
2124    // this class handles the interface with the throttle
2125    // (This class started from code by Pete Cressman contained in Warrant.java.)
2126    class AutoEngineer  {
2127
2128        AutoEngineer(DccThrottle throttle, RosterEntry rosterEntry) {
2129            this.throttle = throttle;
2130            this.rosterEntry = rosterEntry;
2131        }
2132
2133        private DccThrottle throttle;
2134        private int ramping;
2135        private boolean speedProfileStoppingIsRunning = false;
2136        private float speedIncrement = 0.0f; //will be recalculated
2137        private float targetSpeed;
2138        private RosterEntry rosterEntry;
2139        private int throttleInterval;
2140        private float minReliableOperatingSpeed;
2141        private float maxSpeed;
2142        private float speedFactor;
2143
2144        public void setRamping(int ramping, int fullRampTime, int minThrottleInterval, int rampRate) {
2145            this.ramping = ramping;
2146            this.throttleInterval = minThrottleInterval;
2147            //calculate speed increment to use in each minInterval time
2148            speedIncrement = (100.0f / ((float) fullRampTime / minThrottleInterval)
2149                    / rampRate) / 100.0f;
2150            log.debug("{}: _speedIncrement={}", throttle.getLocoAddress(), speedIncrement);
2151        }
2152
2153        public  void setIsForward(boolean isForward) {
2154            throttle.setIsForward(isForward);
2155        }
2156
2157        public boolean getIsForward() {
2158            return(throttle.getIsForward());
2159        }
2160
2161        public void setTargetSpeed(float speed) {
2162            stopAllTimers();
2163            targetSpeed = applyMaxThrottleAndFactor(speed);
2164            log.debug("setTargetSpeed: Set Speed[{}] adjusted to TargetSpeed[{}] ",speed,targetSpeed);
2165            if (ramping == RAMP_NONE || ramping == RAMP_SPEEDPROFILE ) {
2166                throttle.setSpeedSetting(targetSpeed);
2167            } else {
2168                rampToTarget();
2169            }
2170        }
2171
2172        public float getTargetSpeed(){
2173            return(targetSpeed);
2174        }
2175
2176        /**
2177        *
2178        * @param throttleSetting the throttle setting that would normally be set
2179        * @return the adjusted throttle setting after applying Max Throttle and Percentage throttle settings
2180        */
2181       private float applyMaxThrottleAndFactor(float throttleSetting) {
2182           if (throttleSetting > 0.0f) {
2183               if ((throttleSetting * speedFactor) > maxSpeed) {
2184                   return maxSpeed;
2185               }
2186               if ((throttleSetting * speedFactor) < minReliableOperatingSpeed) {
2187                   return minReliableOperatingSpeed;
2188               }
2189               return (throttleSetting * speedFactor); //adjust for train's Speed Factor
2190           } else {
2191               return throttleSetting;
2192           }
2193       }
2194
2195        /**
2196         * Flag from user's control.
2197         *
2198         * @param halt true to immediately stop the train; false otherwise
2199         */
2200        public void setHalt(boolean halt) {
2201            if (halt) {
2202                this.setSpeedImmediate(0.0f);
2203            }
2204        }
2205
2206        /**
2207         * Set the limits and adjustment factore for train speed.
2208         * Active train will calculate the required setting and it will be adjusted if not 0.0f
2209         * required setting * speed Factor  then test for less than max and greater than min.
2210         * @param minReliableOperatingSpeed lowest throttle % train will reliably move.
2211         * @param maxSpeed max throttle % for train.
2212         * @param speedFactor multiplier
2213         */
2214        public void setSpeedLimits(float minReliableOperatingSpeed, float maxSpeed, float speedFactor) {
2215            this.minReliableOperatingSpeed = minReliableOperatingSpeed;
2216            this.maxSpeed = maxSpeed;
2217            this.speedFactor = speedFactor;
2218        }
2219
2220        public void setTargetSpeed(float distance, float speed) {
2221            log.debug("Set Target Speed[{}] with distance{{}] from speed[{}]",speed,distance,throttle.getSpeedSetting());
2222            stopAllTimers();
2223            if (rosterEntry != null) {
2224                rosterEntry.getSpeedProfile().setExtraInitialDelay(1500f);
2225                rosterEntry.getSpeedProfile().setMinMaxLimits(minReliableOperatingSpeed, maxSpeed);
2226                rosterEntry.getSpeedProfile().changeLocoSpeed(_throttle, distance, speed);
2227                speedProfileStoppingIsRunning = true;
2228                targetSpeed = speed;
2229            } else {
2230                setTargetSpeed((0.0f));
2231            }
2232        }
2233
2234        public void slowToStop(boolean on) {
2235            stopAllTimers();
2236            if (on) {
2237                log.debug("SlowToStopOn");
2238                setTargetSpeed((0.0f));
2239            }
2240        }
2241
2242        public void stopAllTimers() {
2243            if (speedProfileStoppingIsRunning) {
2244                re.getSpeedProfile().cancelSpeedChange();
2245                speedProfileStoppingIsRunning = false;
2246            }
2247            if (rampingTimer != null) {
2248                rampingTimer.stop();
2249                rampingTimer = null;
2250            }
2251        }
2252
2253        LinkedList<SpeedSetting> stepQueue;
2254        private javax.swing.Timer rampingTimer;
2255
2256        private void rampToTarget() {
2257            // target already adjusted.
2258            log.debug("RampToTarget[{}]current[{}]", getTargetSpeed(), throttle.getSpeedSetting());
2259            stepQueue = new LinkedList<>();
2260            if (throttle.getSpeedSetting() == getTargetSpeed()) {
2261                return;
2262            } else if (throttle.getSpeedSetting() < getTargetSpeed()) {
2263                // Up
2264                float newSpeed = throttle.getSpeedSetting();
2265                if (newSpeed < minReliableOperatingSpeed) {
2266                    stepQueue.add(new SpeedSetting(minReliableOperatingSpeed, throttleInterval));
2267                    newSpeed = minReliableOperatingSpeed;
2268                }
2269                while (newSpeed < getTargetSpeed()) {
2270                    newSpeed += speedIncrement;
2271                    if (newSpeed > getTargetSpeed()) {
2272                        newSpeed = getTargetSpeed();
2273                    }
2274                    log.trace("NewSpeedUp[{}]", newSpeed);
2275                    stepQueue.add(new SpeedSetting(newSpeed, throttleInterval));
2276                }
2277            } else {
2278                // Down
2279                boolean andStop = false;
2280                if (getTargetSpeed() <= 0.0f) {
2281                    andStop = true;
2282                }
2283                float newSpeed = throttle.getSpeedSetting();
2284                while (newSpeed > getTargetSpeed()) {
2285                    newSpeed -= speedIncrement;
2286                    if (newSpeed < getTargetSpeed()) {
2287                        newSpeed = getTargetSpeed();
2288                    }
2289                    log.trace("NewSpeedDown[{}]", newSpeed);
2290                    stepQueue.add(new SpeedSetting(newSpeed, throttleInterval));
2291                }
2292                if (andStop) {
2293                    stepQueue.add(new SpeedSetting(0.0f, throttleInterval));
2294                }
2295            }
2296            if (rampingTimer == null) { //If this is the first time round then kick off the speed change
2297                setNextStep();
2298            }
2299        }
2300
2301        private void finishChange() {
2302            if (rampingTimer != null) {
2303                rampingTimer.stop();
2304            }
2305            rampingTimer = null;
2306            stepQueue.clear();
2307            stepQueue = null;
2308        }
2309
2310        synchronized void setNextStep() {
2311                if (stepQueue.isEmpty()) {
2312                    log.trace("Empty");
2313                    finishChange();
2314                    return;
2315                }
2316                SpeedSetting ss = stepQueue.getFirst();
2317                if (ss.getDuration() == 0) {
2318                    log.trace("Duratiom Zero");
2319                    finishChange();
2320                    return;
2321                }
2322                stepQueue.removeFirst();
2323                log.trace("Set New Speed[{}]",ss.getSpeedStep());
2324                throttle.setSpeedSetting(ss.getSpeedStep());
2325                rampingTimer = new javax.swing.Timer(ss.getDuration(), (java.awt.event.ActionEvent e) -> {
2326                    setNextStep();
2327                });
2328                rampingTimer.setRepeats(false);
2329                rampingTimer.start();
2330            }
2331
2332        private class SpeedSetting {
2333
2334            float step = 0.0f;
2335            int duration = 0;
2336
2337            SpeedSetting(float step, int duration) {
2338                this.step = step;
2339                this.duration = duration;
2340            }
2341
2342            float getSpeedStep() {
2343                return step;
2344            }
2345
2346            int getDuration() {
2347                return duration;
2348            }
2349        }
2350
2351        /**
2352         * Set the train speed directly, bypassing ramping.
2353         *
2354         * @param speed 0.0 (stop) to 1.0 (full)
2355         */
2356        public synchronized void setSpeedImmediate(float speed) {
2357            log.trace("{}: setting speed directly to {}%", _activeTrain.getTrainName(), (int) (speed * 100));
2358            stopAllTimers();
2359            targetSpeed = applyMaxThrottleAndFactor(speed);
2360            throttle.setSpeedSetting(targetSpeed);
2361        }
2362
2363        /**
2364         * Check if train is moving or stopped.
2365         *
2366         * @return true if stopped; false otherwise
2367         */
2368        public synchronized boolean isStopped() {
2369            // when stopping by speed profile you must refresh the throttle speed.
2370            return throttle.getSpeedSetting() <= 0.0004f;
2371        }
2372
2373        /**
2374         * Check if train is moving at its current requested speed.
2375         *
2376         * @return true if at requested speed; false otherwise
2377         */
2378        public synchronized boolean isAtSpeed() {
2379            return java.lang.Math.abs(throttle.getSpeedSetting() - targetSpeed) <= 0.01;
2380        }
2381
2382        /**
2383         * Flag from user to end run.
2384         */
2385        public void abort() {
2386            stopAllTimers();
2387        }
2388
2389        protected void setFunction(int cmdNum, boolean isSet) {
2390            throttle.setFunction(cmdNum, isSet);
2391        }
2392    }
2393
2394    /**
2395     * Convert ramp rate name, stored as a string into the constant value
2396     * assigned.
2397     *
2398     * @param rampRate  name of ramp rate, such as "RAMP_FAST"
2399     * @return integer representing a ramprate constant value
2400     */
2401    public static int getRampRateFromName(String rampRate) {
2402        if (rampRate.equals(Bundle.getMessage("RAMP_FAST"))) {
2403            return RAMP_FAST;
2404        } else if (rampRate.equals(Bundle.getMessage("RAMP_MEDIUM"))) {
2405            return RAMP_MEDIUM;
2406        } else if (rampRate.equals(Bundle.getMessage("RAMP_MED_SLOW"))) {
2407            return RAMP_MED_SLOW;
2408        } else if (rampRate.equals(Bundle.getMessage("RAMP_SLOW"))) {
2409            return RAMP_SLOW;
2410        } else if (rampRate.equals(Bundle.getMessage("RAMP_SPEEDPROFILE"))) {
2411            return RAMP_SPEEDPROFILE;
2412        }
2413        return RAMP_NONE;
2414    }
2415
2416    /*
2417     * Listener for switching Ghost blocks to unoccupied
2418     */
2419    static class DarkTerritoryListener implements PropertyChangeListener {
2420        private Sensor sensor;
2421
2422        public DarkTerritoryListener(Sensor sensor) {
2423            this.sensor = sensor;
2424            log.trace("Sensor[{}]",sensor.getDisplayName());
2425        }
2426
2427        @Override
2428        public void propertyChange(PropertyChangeEvent e) {
2429            if (e.getPropertyName().equals("state")) {
2430                ((Block) e.getSource()).removePropertyChangeListener(this);
2431                if (e.getNewValue().equals(Block.UNOCCUPIED)) {
2432                    try {
2433                        log.trace("Sensor INACTIVE[{}]", sensor.getDisplayName());
2434                        sensor.setKnownState(Sensor.INACTIVE);
2435                    } catch (jmri.JmriException ex) {
2436                        log.error("Error leaving darkterratory");
2437                    }
2438                }
2439            }
2440        }
2441    }
2442
2443    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AutoActiveTrain.class);
2444}