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