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