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