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
1272        if (useSpeedProfile) {
1273            // find speed from signal.
1274            // find speed from block
1275            // use least
1276            float blockSpeed = getSpeedFromBlock(_conSignalProtectedBlock);
1277
1278            float signalSpeed;
1279            String signalSpeedName;
1280            String displayedAspect = _controllingSignal.getAppearanceName();
1281            try {
1282                signalSpeedName =
1283                        InstanceManager.getDefault(SignalSpeedMap.class).getAppearanceSpeed(displayedAspect);
1284                signalSpeed = InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(signalSpeedName);
1285            } catch (Throwable ex) { // if _anything_ goes wrong, contain it
1286                signalSpeed = -1.0f;
1287                log.warn("{}: Block {} AppearanceSpeed {} not found in SignalSpeedMap",
1288                        _activeTrain.getTrainName(), _conSignalProtectedBlock.getDisplayName(USERSYS), displayedAspect);
1289            }
1290            float useSpeed;
1291            if (blockSpeed < signalSpeed) {
1292                useSpeed = blockSpeed;
1293            } else {
1294                useSpeed = signalSpeed;
1295            }
1296
1297            log.trace("BlockSpeed[{}] SignalSpeed[{}]", blockSpeed, signalSpeed);
1298            if (useSpeed < 0.01f) {
1299                checkForSignalPassedOrStop(_controllingSignal.getDisplayName(USERSYS));
1300            } else {
1301                setTargetSpeedByProfile(useSpeed,_stopBySpeedProfileAdjust,true);
1302            }
1303        } else {
1304            switch (_controllingSignal.getAppearance()) {
1305                case SignalHead.DARK:
1306                case SignalHead.RED:
1307                case SignalHead.FLASHRED:
1308                    // May get here from signal changing before Block knows it is occupied, so must
1309                    //      check Block occupancy sensor, which must change before signal.
1310                    // check to to see if its allocated to us!!!
1311                    //      check Block occupancy sensor if it is in an allocated block, which must change before signal
1312                    // If the train has no _currentAllocatedSection it is in a first block outside transit.
1313                    checkForSignalPassedOrStop(_controllingSignal.getDisplayName(USERSYS));
1314                    break;
1315                case SignalHead.YELLOW:
1316                case SignalHead.FLASHYELLOW:
1317                    setTargetSpeedState(SLOW_SPEED);
1318                    _activeTrain.setStatus(ActiveTrain.RUNNING);
1319                    break;
1320                case SignalHead.GREEN:
1321                case SignalHead.FLASHGREEN:
1322                    setTargetSpeedState(NORMAL_SPEED);
1323                    _activeTrain.setStatus(ActiveTrain.RUNNING);
1324                    break;
1325                case SignalHead.LUNAR:
1326                case SignalHead.FLASHLUNAR:
1327                    setTargetSpeedState(RESTRICTED_SPEED);
1328                    _activeTrain.setStatus(ActiveTrain.RUNNING);
1329                    break;
1330                default:
1331                    log.warn("Signal Head[{}] has invalid Appearence - using stop",_controllingSignal.getAppearance());
1332                    stopInCurrentSection(NO_TASK);
1333            }
1334
1335        }
1336    }
1337
1338    /**
1339     * Check to see if a stop is really required, or if this is the
1340     * signal head that was just passed, in which case ignore as the signal goes red before a
1341     * new signal exists.
1342     *
1343     * @param displayName name of signal for debug messages.
1344     */
1345    private void checkForSignalPassedOrStop(String displayName) {
1346        // if current section is null we are in a pre transit block.
1347        if (_currentAllocatedSection != null) {
1348            if ((_currentAllocatedSection.isInActiveBlockList(_conSignalProtectedBlock) ||
1349                    (_nextSection != null && _activeTrain.isInAllocatedList(_nextSection) && _nextSection.containsBlock(_conSignalProtectedBlock)))
1350                    && _conSignalProtectedBlock.getSensor().getState() == Block.OCCUPIED) {
1351                // Train has just passed this signal - ignore this signal
1352                log.debug("{}: _conSignalProtectedBlock [{}] for signal [{}] is the block just past so ignore.", _activeTrain.getTrainName(),
1353                        _conSignalProtectedBlock.getDisplayName(USERSYS), displayName);
1354            } else {
1355                log.debug("{}: stopping for signal [{}] ", _activeTrain.getTrainName(),
1356                         displayName);
1357                stopInCurrentSection(NO_TASK);
1358            }
1359        }
1360    }
1361
1362    protected float getSpeedFromBlock(Block block) {
1363        String blockSpeedName = block.getBlockSpeed();
1364        if (blockSpeedName.contains("Global")) {
1365            blockSpeedName = InstanceManager.getDefault(BlockManager.class).getDefaultSpeed();
1366        }
1367        float blockSpeed = -1.0f;
1368        if (!blockSpeedName.isEmpty()) {
1369            try {
1370                blockSpeed = Float.parseFloat(blockSpeedName);
1371            } catch (NumberFormatException nx) {
1372                try {
1373                    blockSpeed = InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(blockSpeedName);
1374                    log.debug("{} {}: block speed from map for {} is {}",
1375                            _activeTrain.getTrainName(), block.getDisplayName(USERSYS), blockSpeedName,
1376                            blockSpeed);
1377                } catch (Throwable ex) { // if _anything_ goes wrong, contain it
1378                    //Considered Normal if the speed does not appear in the map
1379                    log.warn("{}: Block {} Speed {} not found in SignalSpeedMap",
1380                            _activeTrain.getTrainName(), block.getDisplayName(USERSYS), blockSpeed);
1381                }
1382            }
1383        }
1384        return blockSpeed;
1385    }
1386
1387    float prevSpeed = -1.0f;
1388
1389    // called to cancel a stopping action that is in progress
1390    private synchronized void cancelStopInCurrentSection() {
1391        log.trace("[{}]:Cancel Stopping", _activeTrain.getTrainName());
1392        cancelStoppingBySensor();
1393        _stoppingByBlockOccupancy = false;
1394        _stoppingBlock = null;
1395        _stoppingUsingSpeedProfile = false;
1396        _stoppingBlock = null;
1397        _autoEngineer.slowToStop(false);
1398    }
1399
1400    private synchronized void stopInCurrentSection(int task) {
1401        if (_currentAllocatedSection == null) {
1402            log.error("{}: Current allocated section null on entry to stopInCurrentSection", _activeTrain.getTrainName());
1403            setStopNow();
1404            return;
1405        }
1406        log.debug("{}: StopInCurrentSection called for {} task[{}] targetspeed[{}]", _activeTrain.getTrainName(), _currentAllocatedSection.getSection().getDisplayName(USERSYS),task,getTargetSpeed());
1407        if (getTargetSpeed() == 0.0f || isStopping()) {
1408            log.debug("{}: train is already stopped or stopping.", _activeTrain.getTrainName());
1409            // ignore if train is already stopped or if stopping is in progress
1410            return;
1411        }
1412        // if Section has stopping sensors, use them
1413        if (_currentAllocatedSection.getSection().getState() == Section.FORWARD) {
1414            _stopSensor = _currentAllocatedSection.getSection().getForwardStoppingSensor();
1415        } else {
1416            _stopSensor = _currentAllocatedSection.getSection().getReverseStoppingSensor();
1417        }
1418        if (_stopSensor != null && _useStopSensor) {
1419            if (_stopSensor.getKnownState() == Sensor.ACTIVE) {
1420                // stop sensor is already active, stop now
1421                setStopNow();
1422            } else {
1423                setDecreasedSpeedBeforeStop();
1424                _stopSensor.addPropertyChangeListener(_stopSensorListener = (java.beans.PropertyChangeEvent e) -> {
1425                    handleStopSensorChange(e);
1426                });
1427                _stoppingBySensor = true;
1428            }
1429        } else if (useSpeedProfile && _stopBySpeedProfile) {
1430            log.debug("{}: Section [{}] Section Length[{}] Max Train Length [{}] StopBySpeedProfile [{}]. setStopNow", _activeTrain.getTrainName(),
1431                    _currentAllocatedSection.getSection().getDisplayName(USERSYS), _currentAllocatedSection.getActualLength(), getMaxTrainLengthMM(), _stopBySpeedProfile);
1432            // stopping by speed profile uses section length to stop
1433            setTargetSpeedState(STOP_SPEED,useSpeedProfile);
1434        } else if (_currentAllocatedSection.getActualLength()  < getMaxTrainLengthMM()) {
1435            log.debug("{}: Section [{}] Section Length[{}] Max Train Length [{}]. setStopNow({})",
1436                    _activeTrain.getTrainName(),
1437                    _currentAllocatedSection.getSection().getDisplayName(USERSYS),
1438                    _currentAllocatedSection.getActualLength(),
1439                    getMaxTrainLengthMM(), _stopBySpeedProfile);
1440            // train will not fit comfortably in the Section, stop it immediately
1441            setStopNow();
1442        } else if (_activeTrain.getTrainDetection() == TrainDetection.TRAINDETECTION_WHOLETRAIN) {
1443            log.debug("{}: train will fit in [{}] ({}>={}), stop when prev block clears.", _activeTrain.getTrainName(),
1444                    _currentAllocatedSection.getSection().getDisplayName(USERSYS), _currentAllocatedSection.getActualLength(), getMaxTrainLengthMM());
1445            // train will fit in current allocated Section and has resistance wheels
1446            // try to stop by watching Section Block occupancy
1447            if (_currentAllocatedSection.getSection().getNumBlocks() == 1) {
1448                if (_previousAllocatedSection != null) {
1449                    Block tBlock;
1450                    // just because current section has one block does not mean the previous one did.
1451                    if (_previousAllocatedSection.getSection().getNumBlocks() == 1) {
1452                       tBlock = _previousAllocatedSection.getSection().getLastBlock();
1453                    } else {
1454                       tBlock = _previousAllocatedSection.getSection().getExitBlock();
1455                    }
1456                    if ((tBlock != null) && (tBlock.getState() == Block.OCCUPIED)) {
1457                        _stoppingBlock = tBlock;
1458                        setStopByBlockOccupancy(false);
1459                    } else {
1460                        setStopNow();
1461                    }
1462                } else {
1463                    setStopNow();
1464                }
1465            } else {
1466                // Section has multiple blocks
1467                Block exitBlock = _currentAllocatedSection.getExitBlock();
1468                Block enterBlock = _currentAllocatedSection.getEnterBlock(_previousAllocatedSection);
1469                if (enterBlock == null) {
1470                    // this is the first Section of the Transit, with train starting in this Section
1471                    setStopNow();
1472                } else if (exitBlock == enterBlock) {
1473                    // entry and exit are from the same Block
1474                    if ((_previousBlock != null) && (_previousBlock.getState() == Block.OCCUPIED)
1475                            && (getBlockLength(exitBlock) > getMaxTrainLengthMM())) {
1476                        _stoppingBlock = _previousBlock;
1477                        setStopByBlockOccupancy(false);
1478                    } else {
1479                        setStopNow();
1480                    }
1481                } else {
1482                    // try to move train as far into the Section as it will comfortably fit
1483                    Block tstBlock = exitBlock;
1484                    if (tstBlock == null) {
1485                        if (_currentAllocatedSection.getDirection() == Section.REVERSE) {
1486                            tstBlock = _currentAllocatedSection.getSection().getBlockBySequenceNumber(0);
1487                        } else {
1488                            tstBlock = _currentAllocatedSection.getSection().getBlockBySequenceNumber(
1489                                    _currentAllocatedSection.getSection().getNumBlocks() - 1);
1490                        }
1491                    }
1492                    int tstLength = getBlockLength(tstBlock);
1493                    int tstBlockSeq = _currentAllocatedSection.getSection().getBlockSequenceNumber(tstBlock);
1494                    while ((tstLength < getMaxTrainLengthMM()) && (tstBlock != enterBlock)) {
1495                        int newSeqNumber;
1496                        if (_currentAllocatedSection.getDirection() == Section.REVERSE) {
1497                            newSeqNumber = tstBlockSeq + 1;
1498                        } else {
1499                            newSeqNumber = tstBlockSeq - 1;
1500                        }
1501                        tstBlock = _currentAllocatedSection.getSection().getBlockBySequenceNumber(newSeqNumber);
1502                        tstBlockSeq = newSeqNumber;
1503                        tstLength += getBlockLength(tstBlock);
1504                    }
1505                    if (getMaxTrainLengthMM() > tstLength) {
1506                        setStopNow();
1507                    } else if (tstBlock == enterBlock) {
1508                        // train fits, but needs all available Blocks
1509                        Block previousSectionExitBlock = _previousAllocatedSection.getExitBlock();
1510                        if ((previousSectionExitBlock != null) && (previousSectionExitBlock.getState() == Block.OCCUPIED)) {
1511                            _stoppingBlock = previousSectionExitBlock;
1512                            setStopByBlockOccupancy(true);
1513                        } else {
1514                            setStopNow();
1515                        }
1516                    } else {
1517                        // train fits, and doesn't need all available Blocks
1518                        int xSeqNumber = tstBlockSeq + 1;
1519                        if (_currentAllocatedSection.getDirection() == Section.FORWARD ) {
1520                            xSeqNumber = tstBlockSeq - 1;
1521                        }
1522                        _stoppingBlock = _currentAllocatedSection.getSection().
1523                                getBlockBySequenceNumber(xSeqNumber);
1524                        setStopByBlockOccupancy(true);
1525                    }
1526                }
1527            }
1528        } else {
1529            // train will fit, but no way to stop it reliably
1530            setStopNow();
1531        }
1532        // even if no task is required it must be run
1533        // as cleanup happens after train stops.
1534        Runnable waitForStop = new WaitForTrainToStop(task);
1535        Thread tWait = jmri.util.ThreadingUtil.newThread(waitForStop, "Wait for stop " + getActiveTrain().getActiveTrainName());
1536        tWait.start();
1537    }
1538
1539    protected synchronized void executeStopTasks(int task) {
1540        // clean up stopping
1541        cancelStopInCurrentSection();
1542        _dispatcher.queueReleaseOfCompletedAllocations();
1543        log.trace("exec[{}]",task);
1544        switch (task) {
1545            case END_TRAIN:
1546                _activeTrain.setStatus(ActiveTrain.DONE);
1547                break;
1548            case NO_TASK:
1549                // clean up stop
1550                break;
1551            case END_REVERSAL:
1552                /* Reset _previousBlock to be the _currentBlock if we do a continious reverse otherwise the stop in block method fails
1553                to stop the loco in the correct block
1554                 if the first block we come to has a stopped or held signal */
1555                _activeTrain.setRestart(_activeTrain.getDelayReverseRestart(),_activeTrain.getReverseRestartDelay(),
1556                        _activeTrain.getReverseRestartSensor(),_activeTrain.getResetReverseRestartSensor());
1557                _activeTrain.setTransitReversed(true);
1558                _activeTrain.reverseAllAllocatedSections();
1559                setEngineDirection();
1560                _previousBlock = null;
1561                _nextBlock = getNextBlock(_currentBlock,_currentAllocatedSection);
1562                if (_activeTrain.getDelayReverseRestart() == ActiveTrain.NODELAY) {
1563                   _activeTrain.holdAllocation(false);
1564                    // a reversal can happen in mid section
1565                    setupNewCurrentSignal(_currentAllocatedSection, true);
1566                    setSpeedBySignal();
1567                    if ((_nextSection != null) && !_activeTrain.isInAllocatedList(_nextSection)) {
1568                        _dispatcher.queueScanOfAllocationRequests();
1569                        break;
1570                    }
1571                }
1572                break;
1573            case BEGINNING_RESET:
1574                _activeTrain.setRestart(_activeTrain.getDelayedRestart(),_activeTrain.getRestartDelay(),
1575                        _activeTrain.getRestartSensor(),_activeTrain.getResetRestartSensor());
1576                if (_activeTrain.getResetWhenDone()) {
1577                    if (_activeTrain.getDelayedRestart() == ActiveTrain.NODELAY && !_activeTrain.getReverseAtEnd()) {
1578                        log.error("[{}]: train is continueing without pause, should have been handled in handleBlockStateChange.",_activeTrain.getTrainName());
1579                    } else {
1580                        // then active train is delayed
1581                        _activeTrain.setTransitReversed(false);
1582                        _activeTrain.resetAllAllocatedSections();
1583                        _previousBlock = null;
1584                        _nextBlock = getNextBlock(_currentBlock,_currentAllocatedSection);
1585                        setEngineDirection();
1586                        _activeTrain.setRestart(_activeTrain.getDelayedRestart(),_activeTrain.getRestartDelay(),
1587                                _activeTrain.getRestartSensor(), _activeTrain.getResetRestartSensor());
1588                        if ((_nextSection != null) && !_activeTrain.isInAllocatedList(_nextSection)) {
1589                            _dispatcher.queueScanOfAllocationRequests();
1590                        }
1591                        // can be mid block
1592                        setupNewCurrentSignal(null, true);
1593                        setSpeedBySignal();
1594
1595                    }
1596                } else {
1597                    // dispatcher cancelled auto restart while train was stopping?
1598                    log.warn("[{}]resetWhenDone flag reset, likely user cancelling while processing stop",
1599                            _activeTrain.getActiveTrainName());
1600                }
1601                break;
1602            default:
1603                log.debug("[{}]Invalid action [{}] in executeStopTasksRequest to execute BEGINNING_RESET cancelled", _activeTrain.getActiveTrainName(),task);
1604                break;
1605        }
1606    }
1607
1608    /**
1609     * Remove the stopping sensor
1610     */
1611    private void cancelStoppingBySensor() {
1612        if (_stopSensor != null) {
1613            _stopSensor.removePropertyChangeListener(_stopSensorListener);
1614            _stoppingBySensor = false;
1615            _stopSensorListener = null;
1616            _stopSensor = null;
1617        }
1618    }
1619
1620    /**
1621     * When the stopping sensor we are waiting on goes active
1622     * stop the train or set a new speed and destroy itself
1623     * @param e  - the property change event
1624     */
1625    private synchronized void handleStopSensorChange(java.beans.PropertyChangeEvent e) {
1626        if (e.getPropertyName().equals("KnownState") && (int) e.getNewValue() == Sensor.ACTIVE) {
1627            _stopSensor.removePropertyChangeListener(_stopSensorListener);
1628            _stoppingBySensor = false;
1629            _stopSensorListener = null;
1630            _stopSensor = null;
1631            if (_needSetSpeed) {
1632                _needSetSpeed = false;
1633                setSpeedBySignal();
1634            } else {
1635                setStopNow();
1636            }
1637        }
1638    }
1639
1640    private synchronized void setStopNow() {
1641        setStopNow(false);
1642        }
1643
1644    private synchronized void setStopNow(boolean useSpeedProfile) {
1645        setTargetSpeedState(STOP_SPEED,useSpeedProfile);
1646        if (_currentAllocatedSection == null) {  // this may occur if the train is not in the selected block when initially created and the signal is held.
1647            _activeTrain.setStatus(ActiveTrain.WAITING);
1648        } else if (_currentAllocatedSection.getNextSection() == null) {
1649            // wait for train to stop - this lets action items complete in a timely fashion
1650            waitUntilStopped();
1651            _activeTrain.setStatus(ActiveTrain.DONE);
1652        } else {
1653            _activeTrain.setStatus(ActiveTrain.WAITING);
1654        }
1655    }
1656
1657    /*
1658     * When multi block stopping, the stopping block may not be occupied yet.
1659     */
1660    private void setStopByBlockOccupancy(boolean ignoreNotOccupied) {
1661        // note: _stoppingBlock must be set before invoking this method
1662        //  verify that _stoppingBlock is actually occupied, if not stop immed
1663        if (_stoppingBlock.getState() == Block.OCCUPIED || ignoreNotOccupied) {
1664            setDecreasedSpeedBeforeStop();
1665            _stoppingByBlockOccupancy = true;
1666        } else {
1667            setStopNow();
1668        }
1669    }
1670
1671    /**
1672     * Before stopping by sensor alone, or by clearing previous block,
1673     * set the speed to the user defined preference.
1674     */
1675    private void setDecreasedSpeedBeforeStop() {
1676        float signalSpeed = 25;
1677        try {
1678            signalSpeed = InstanceManager.getDefault(SignalSpeedMap.class)
1679                    .getSpeed(_dispatcher.getStoppingSpeedName());
1680        } catch (IllegalArgumentException ex) {
1681            log.error("Missing [{}] from Speed table - defaulting to 25",
1682                    _dispatcher.getStoppingSpeedName());
1683        }
1684        if (getThrottleSettingFromSpeed(signalSpeed) < getTargetSpeed()) {
1685            if (useSpeedProfile) {
1686                // use 75 percent or normal amount, dont clear isstopping for ramping.
1687                setTargetSpeedByProfile(signalSpeed,_stopBySpeedProfileAdjust*0.75f,false);
1688            } else {
1689                setTargetSpeed(signalSpeed/100.0f);
1690            }
1691        }
1692    }
1693
1694    ///**
1695    // * Sets the throttle percent unless it is already less than the new setting
1696    // * @param throttleSetting  Max ThrottleSetting required.
1697    // */
1698    //private synchronized void setToAMaximumThrottle(float throttleSetting) {
1699    //    if (throttleSetting < getTargetSpeed()) {
1700    //        setTargetSpeed(throttleSetting);
1701    //    }
1702    //}
1703
1704    /**
1705     * Calculates the throttle setting for a given speed.
1706     * @param speed  the unadjusted speed.
1707     * @return - throttle setting (a percentage)
1708     */
1709    private synchronized float getThrottleSettingFromSpeed(float speed) {
1710        if (useSpeedProfile) {
1711            float throttleSetting = _activeTrain.getRosterEntry().getSpeedProfile()
1712                    .getThrottleSettingFromSignalMapSpeed(speed, getForward());
1713            return throttleSetting;
1714        }
1715        if (_activeTrain.getSignalType() == DispatcherFrame.SIGNALMAST) {
1716            float mls;
1717            if (_controllingSignalMast != null) {
1718                mls = _controllingSignalMast.getSignalSystem().getMaximumLineSpeed();
1719            } else {
1720                //plan B
1721                mls = _dispatcher.getMaximumLineSpeed();
1722            }
1723            float throttleSetting = (speed / mls);
1724            return throttleSetting;
1725        } else {
1726            return speed/100.0f;
1727        }
1728    }
1729
1730
1731    /**
1732     * sets the throttle based on an index number into _speedRatio array
1733     * @param speedState  Index value
1734     */
1735    private synchronized void setTargetSpeedState(int speedState) {
1736        setTargetSpeedState(speedState,false);
1737    }
1738
1739    /**
1740     * sets the throttle based on an index number into _speedRatio array
1741     * @param speedState  Index value
1742     * @param stopBySpeedProfile if true use speed profile
1743     */
1744    private synchronized void setTargetSpeedState(int speedState,boolean stopBySpeedProfile) {
1745        log.trace("{}: setTargetSpeedState:({})",_activeTrain.getTrainName(),speedState);
1746        _autoEngineer.slowToStop(false);
1747        if (speedState > STOP_SPEED) {
1748            cancelStopInCurrentSection();
1749            if (_currentRampRate == RAMP_SPEEDPROFILE && useSpeedProfile) {
1750                // we are going to ramp up  / down using section length and speed profile
1751                _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock) * _stopBySpeedProfileAdjust, speedState);
1752            } else {
1753                setTargetSpeed(_speedRatio[speedState]);
1754            }
1755        } else if (stopBySpeedProfile) {
1756            // we are going to stop by profile
1757            _stoppingUsingSpeedProfile = true;
1758            _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock) * _stopBySpeedProfileAdjust, 0.0f);
1759        } else {
1760            _autoEngineer.setHalt(true);
1761            setTargetSpeed(0.0f);
1762        }
1763    }
1764
1765    private synchronized void setTargetSpeedByProfile(float speedState, float stopBySpeedProfileAdjust, boolean cancelStopping) {
1766        // the speed comes in as units of warrents (mph, kph, mm/s etc)
1767            try {
1768                float throttleSetting = _activeTrain.getRosterEntry().getSpeedProfile().getThrottleSettingFromSignalMapSpeed(speedState, getForward());
1769                log.debug("{}: setTargetSpeedByProfile: {} SpeedState[{}]",
1770                        _activeTrain.getTrainName(),
1771                        throttleSetting,
1772                        speedState);
1773                if (throttleSetting > 0.009 && _currentRampRate != RAMP_SPEEDPROFILE && useSpeedProfile) {
1774                    if (cancelStopping) {cancelStopInCurrentSection();}
1775                    setTargetSpeed(throttleSetting); // apply speed factor and max
1776                } else if (throttleSetting > 0.009) {
1777                    if (cancelStopping) {cancelStopInCurrentSection();}
1778                    setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock)  * stopBySpeedProfileAdjust , throttleSetting);
1779                } else if (useSpeedProfile && _stopBySpeedProfile) {
1780                    setTargetSpeed(0.0f);
1781                    _stoppingUsingSpeedProfile = true;
1782                    _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock)  * stopBySpeedProfileAdjust, 0.0f);
1783                } else {
1784                    _autoEngineer.slowToStop(false);
1785                    setTargetSpeed(0.0f);
1786                    _autoEngineer.setHalt(true);
1787                }
1788            } catch (Exception ex) {
1789                log.error("setTargetSpeedByProfile crashed - Emergency Stop: ", ex );
1790                _autoEngineer.slowToStop(false);
1791                setTargetSpeed(-1.0f);
1792                _autoEngineer.setHalt(true);
1793            }
1794        }
1795
1796    /**
1797     * Pass in speed as shown on dialogs, and convert to decimal speed needed by
1798     * throttle.
1799     */
1800    private synchronized void setTargetSpeedValue(float speed) {
1801        log.debug("{}: setTargetSpeedValue: Speed[{}]",_activeTrain.getTrainName(),speed);
1802        if (useSpeedProfile) {
1803            setTargetSpeedByProfile(speed,_stopBySpeedProfileAdjust,true);
1804            return;
1805        }
1806        _autoEngineer.slowToStop(false);
1807        float mls;
1808        if (_controllingSignalMast != null) {
1809            mls = _controllingSignalMast.getSignalSystem().getMaximumLineSpeed();
1810        } else {
1811            mls = _dispatcher.getMaximumLineSpeed();
1812        }
1813        float decSpeed = (speed / mls);
1814        if (decSpeed > 0.0f) {
1815            cancelStopInCurrentSection();
1816            setTargetSpeed(decSpeed);
1817        } else {
1818            setTargetSpeed(0.0f);
1819            _autoEngineer.setHalt(true);
1820        }
1821    }
1822
1823    private int getBlockLength(Block b) {
1824        if (b == null) {
1825            return (0);
1826        }
1827        return (int) b.getLengthMm();
1828//        float fLength = b.getLengthMm() / (float) _dispatcher.getScale().getScaleFactor();
1829//        if (_dispatcher.getUseScaleMeters()) {
1830//            return (int) (fLength * 0.001f);
1831//        }
1832//        return (int) (fLength * 0.00328084f);
1833    }
1834
1835    /**
1836     * Initiates running in manual mode with external throttle.
1837     * <p>
1838     * This method is triggered by an action in the Transit. The throttle in use
1839     * for automatic operation is dispatched.
1840     */
1841    protected void initiateWorking() {
1842        if (_activeTrain.getStatus() != ActiveTrain.WORKING) {
1843            _activeTrain.setMode(ActiveTrain.DISPATCHED);
1844            _activeTrain.setStatus(ActiveTrain.WORKING);
1845            saveSpeedAndDirection();
1846            if (_autoEngineer != null) {
1847                _autoEngineer.setHalt(true);
1848                waitUntilStopped();
1849                _autoEngineer.abort();
1850                InstanceManager.throttleManagerInstance().releaseThrottle(_throttle, this);
1851                _autoEngineer = null;
1852                _throttle = null;
1853            }
1854        }
1855    }
1856
1857    /**
1858     * Returns when train is stopped.
1859     * <p>
1860     * Note: Provides for _autoEngineer becoming null during wait Ties up the
1861     * current autoActiveTrain thread.
1862     */
1863    protected void waitUntilStopped() {
1864        boolean doneWaiting = false;
1865        while (!doneWaiting) {
1866            if (_autoEngineer != null) {
1867                doneWaiting = _autoEngineer.isStopped();
1868            } else {
1869                doneWaiting = true;
1870            }
1871            if (!doneWaiting) {
1872                try {
1873                    Thread.sleep(50);
1874                } catch (InterruptedException e) {
1875                    // ignore this exception
1876                }
1877            }
1878        }
1879    }
1880
1881    /**
1882     * Resumes automatic running after a working session using an external
1883     * throttle This method is triggered by the dispatcher hitting the "Resume
1884     * Auto Running" button A new throttle is acquired to allow automatic
1885     * running to resume
1886     */
1887    protected void resumeAutomaticRunning() {
1888        if ((_activeTrain.getStatus() == ActiveTrain.WORKING)
1889                || (_activeTrain.getStatus() == ActiveTrain.READY)) {
1890            _autoTrainAction.cancelDoneSensor();
1891            if (initialize()) {
1892                _resumingAutomatic = true;
1893            } else {
1894                log.error("Failed to initialize throttle when resuming automatic mode.");
1895            }
1896        }
1897    }
1898
1899    /**
1900     * Pause the auto active train for a specified number of fast clock minutes.
1901     *
1902     * @param fastMinutes the number of minutes to pause the train
1903     * @return the thread waiting on the pause or null if already paused
1904     */
1905    public Thread pauseTrain(int fastMinutes) {
1906        if (_pausingActive) {
1907            // if a pause train thread is currently active, ignore this call
1908            return (null);
1909        }
1910        Runnable pauseTrain = new PauseTrain(fastMinutes);
1911        Thread tPause = jmri.util.ThreadingUtil.newThread(pauseTrain, "pause train " + _activeTrain.getTrainName());
1912        tPause.start();
1913        return tPause;
1914    }
1915
1916    public void terminate() {
1917        // here add code to stop the train and release its throttle if it is in autoRun
1918        while (_activeHornThreads > 0) {
1919            try {
1920                Thread.sleep(50);
1921            } catch (InterruptedException e) {
1922                // ignore this exception
1923            }
1924        }
1925        _autoTrainAction.clearRemainingActions();
1926        if (_autoEngineer != null) {
1927            _autoEngineer.setHalt(true);
1928            try {
1929                Thread.sleep(50);
1930            } catch (InterruptedException e) {
1931                // ignore this exception
1932            }
1933            waitUntilStopped();
1934            _autoEngineer.abort();
1935            InstanceManager.throttleManagerInstance().releaseThrottle(_throttle, this);
1936        }
1937    }
1938
1939    public void dispose() {
1940        if (_controllingSignalMast != null && _conSignalMastListener != null) {
1941            _controllingSignalMast.removePropertyChangeListener(_conSignalMastListener);
1942        }
1943        _controllingSignalMast = null;
1944        _conSignalMastListener = null;
1945    }
1946
1947// _________________________________________________________________________________________
1948    // This class waits for train stop in a separate thread
1949    class WaitForTrainToStop implements Runnable {
1950
1951        public WaitForTrainToStop(int task) {
1952            _task = task;
1953        }
1954
1955        @Override
1956        public void run() {
1957            boolean waitingOnTrain = true;
1958            try {
1959                while (waitingOnTrain) {
1960                    if ((getAutoEngineer() != null) && (getAutoEngineer().isStopped())) {
1961                        waitingOnTrain = false;
1962                    } else {
1963                        Thread.sleep(_delay);
1964                    }
1965                }
1966                log.trace("executing task[{}]",_task);
1967                executeStopTasks(_task);
1968            } catch (InterruptedException e) {
1969                log.warn("Waiting for train to stop interrupted - stop tasks not executing");
1970            } catch (Exception e) {
1971                log.error("Waiting for train to stop crashed - stop tasks not executing.", e);
1972            }
1973        }
1974
1975        private final int _delay = 91;
1976        private int _task = 0;
1977    }
1978
1979    /**
1980     * Pause the train in a separate thread. Train is stopped, then restarted
1981     * after specified number of fast Minutes have elapsed.
1982     */
1983    class PauseTrain implements Runnable {
1984        /**
1985         * Create a PauseTrain
1986         *
1987         * @param fastMinutes the number of fast clock minutes to pause the
1988         *                    train
1989         */
1990        public PauseTrain(int fastMinutes) {
1991            _fastMinutes = fastMinutes;
1992        }
1993
1994        @Override
1995        public void run() {
1996            // set to pause at a fast ramp rate
1997            _pausingActive = true;
1998            // TODO: use stop in section or block?
1999            _savedRampRate = getRampRate();
2000            setCurrentRampRate(RAMP_FAST);
2001            stopInCurrentSection(NO_TASK);
2002            // wait for train to stop
2003            boolean waitNow = true;
2004            boolean keepGoing = true;
2005            while (waitNow) {
2006                try {
2007                    Thread.sleep(101);
2008                    if (_autoEngineer != null) {
2009                        if (_autoEngineer.isStopped()) {
2010                            waitNow = false;
2011                        }
2012                    } else {
2013                        waitNow = false;
2014                    }
2015                } catch (InterruptedException e) {
2016                    log.trace("InterruptedException while waiting to stop for pause-indicates action cancelled.", e);
2017                    waitNow = false;
2018                    keepGoing = false;
2019                }
2020            }
2021            _activeTrain.setStatus(ActiveTrain.PAUSED);
2022            if (keepGoing) {
2023                // wait for specified fast clock time
2024                Timebase _clock = InstanceManager.getDefault(jmri.Timebase.class);
2025                java.beans.PropertyChangeListener _clockListener = (java.beans.PropertyChangeEvent e) -> {
2026                    _fastMinutes--;
2027                };
2028                _clock.addMinuteChangeListener(_clockListener);
2029                // wait for fast minutes to tick away
2030                waitNow = true;
2031                while (waitNow) {
2032                    try {
2033                        Thread.sleep(501);
2034                        if (_fastMinutes <= 0) {
2035                            waitNow = false;
2036                        }
2037                    } catch (InterruptedException e) {
2038                        log.trace("InterruptedException indicates action cancelled.", e);
2039                        keepGoing = false;
2040                    }
2041                }
2042                _clock.removeMinuteChangeListener(_clockListener);
2043            }
2044            _pausingActive = false;
2045            if (keepGoing) {
2046                // this thread was not interrupted
2047                //   resume running - restore speed, status, and ramp rate
2048                setCurrentRampRate(_savedRampRate);
2049                // Set speed by signal also works if signal missing
2050                // so we dont need to restore a previous value.
2051                _activeTrain.setStatus(ActiveTrain.RUNNING);
2052                setSpeedBySignal();
2053            }
2054        }
2055        private int _fastMinutes = 0;
2056        private int _savedRampRate = RAMP_NONE;
2057    }
2058
2059    // _________________________________________________________________________________________
2060    // this class handles the interface with the throttle
2061    // (This class started from code by Pete Cressman contained in Warrant.java.)
2062    class AutoEngineer  {
2063
2064        AutoEngineer(DccThrottle throttle, RosterEntry rosterEntry) {
2065            this.throttle = throttle;
2066            this.rosterEntry = rosterEntry;
2067        }
2068
2069        private DccThrottle throttle;
2070        private int ramping;
2071        private boolean speedProfileStoppingIsRunning = false;
2072        private float speedIncrement = 0.0f; //will be recalculated
2073        private float targetSpeed;
2074        private RosterEntry rosterEntry;
2075        private int throttleInterval;
2076        private float minReliableOperatingSpeed;
2077        private float maxSpeed;
2078        private float speedFactor;
2079
2080        public void setRamping(int ramping, int fullRampTime, int minThrottleInterval, int rampRate) {
2081            this.ramping = ramping;
2082            this.throttleInterval = minThrottleInterval;
2083            //calculate speed increment to use in each minInterval time
2084            speedIncrement = (100.0f / ((float) fullRampTime / minThrottleInterval)
2085                    / rampRate) / 100.0f;
2086            log.debug("{}: _speedIncrement={}", throttle.getLocoAddress(), speedIncrement);
2087        }
2088
2089        public  void setIsForward(boolean isForward) {
2090            throttle.setIsForward(isForward);
2091        }
2092
2093        public boolean getIsForward() {
2094            return(throttle.getIsForward());
2095        }
2096
2097        public void setTargetSpeed(float speed) {
2098            stopAllTimers();
2099            targetSpeed = applyMaxThrottleAndFactor(speed);
2100            log.debug("setTargetSpeed: Set Speed[{}] adjusted to TargetSpeed[{}] ",speed,targetSpeed);
2101            if (ramping == RAMP_NONE || ramping == RAMP_SPEEDPROFILE ) {
2102                throttle.setSpeedSetting(targetSpeed);
2103            } else {
2104                rampToTarget();
2105            }
2106        }
2107
2108        public float getTargetSpeed(){
2109            return(targetSpeed);
2110        }
2111
2112        /**
2113        *
2114        * @param throttleSetting the throttle setting that would normally be set
2115        * @return the adjusted throttle setting after applying Max Throttle and Percentage throttle settings
2116        */
2117       private float applyMaxThrottleAndFactor(float throttleSetting) {
2118           if (throttleSetting > 0.0f) {
2119               if ((throttleSetting * speedFactor) > maxSpeed) {
2120                   return maxSpeed;
2121               }
2122               if ((throttleSetting * speedFactor) < minReliableOperatingSpeed) {
2123                   return minReliableOperatingSpeed;
2124               }
2125               return (throttleSetting * speedFactor); //adjust for train's Speed Factor
2126           } else {
2127               return throttleSetting;
2128           }
2129       }
2130
2131        /**
2132         * Flag from user's control.
2133         *
2134         * @param halt true to immediately stop the train; false otherwise
2135         */
2136        public void setHalt(boolean halt) {
2137            if (halt) {
2138                this.setSpeedImmediate(0.0f);
2139            }
2140        }
2141
2142        /**
2143         * Set the limits and adjustment factore for train speed.
2144         * Active train will calculate the required setting and it will be adjusted if not 0.0f
2145         * required setting * speed Factor  then test for less than max and greater than min.
2146         * @param minReliableOperatingSpeed lowest throttle % train will reliably move.
2147         * @param maxSpeed max throttle % for train.
2148         * @param speedFactor multiplier
2149         */
2150        public void setSpeedLimits(float minReliableOperatingSpeed, float maxSpeed, float speedFactor) {
2151            this.minReliableOperatingSpeed = minReliableOperatingSpeed;
2152            this.maxSpeed = maxSpeed;
2153            this.speedFactor = speedFactor;
2154        }
2155
2156        public void setTargetSpeed(float distance, float speed) {
2157            log.debug("Set Target Speed[{}] with distance{{}] from speed[{}]",speed,distance,throttle.getSpeedSetting());
2158            stopAllTimers();
2159            if (rosterEntry != null) {
2160                rosterEntry.getSpeedProfile().setExtraInitialDelay(1500f);
2161                rosterEntry.getSpeedProfile().setMinMaxLimits(minReliableOperatingSpeed, maxSpeed);
2162                rosterEntry.getSpeedProfile().changeLocoSpeed(_throttle, distance, speed);
2163                speedProfileStoppingIsRunning = true;
2164                targetSpeed = speed;
2165            } else {
2166                setTargetSpeed((0.0f));
2167            }
2168        }
2169
2170        public void slowToStop(boolean on) {
2171            stopAllTimers();
2172            if (on) {
2173                log.debug("SlowToStopOn");
2174                setTargetSpeed((0.0f));
2175            }
2176        }
2177
2178        public void stopAllTimers() {
2179            if (speedProfileStoppingIsRunning) {
2180                re.getSpeedProfile().cancelSpeedChange();
2181                speedProfileStoppingIsRunning = false;
2182            }
2183            if (rampingTimer != null) {
2184                rampingTimer.stop();
2185                rampingTimer = null;
2186            }
2187        }
2188
2189        LinkedList<SpeedSetting> stepQueue;
2190        private javax.swing.Timer rampingTimer;
2191
2192        private void rampToTarget() {
2193            // target already adjusted.
2194            log.debug("RampToTarget[{}]current[{}]", getTargetSpeed(), throttle.getSpeedSetting());
2195            stepQueue = new LinkedList<>();
2196            if (throttle.getSpeedSetting() == getTargetSpeed()) {
2197                return;
2198            } else if (throttle.getSpeedSetting() < getTargetSpeed()) {
2199                // Up
2200                float newSpeed = throttle.getSpeedSetting();
2201                if (newSpeed < minReliableOperatingSpeed) {
2202                    stepQueue.add(new SpeedSetting(minReliableOperatingSpeed, throttleInterval));
2203                    newSpeed = minReliableOperatingSpeed;
2204                }
2205                while (newSpeed < getTargetSpeed()) {
2206                    newSpeed += speedIncrement;
2207                    if (newSpeed > getTargetSpeed()) {
2208                        newSpeed = getTargetSpeed();
2209                    }
2210                    log.trace("NewSpeedUp[{}]", newSpeed);
2211                    stepQueue.add(new SpeedSetting(newSpeed, throttleInterval));
2212                }
2213            } else {
2214                // Down
2215                boolean andStop = false;
2216                if (getTargetSpeed() <= 0.0f) {
2217                    andStop = true;
2218                }
2219                float newSpeed = throttle.getSpeedSetting();
2220                while (newSpeed > getTargetSpeed()) {
2221                    newSpeed -= speedIncrement;
2222                    if (newSpeed < getTargetSpeed()) {
2223                        newSpeed = getTargetSpeed();
2224                    }
2225                    log.trace("NewSpeedDown[{}]", newSpeed);
2226                    stepQueue.add(new SpeedSetting(newSpeed, throttleInterval));
2227                }
2228                if (andStop) {
2229                    stepQueue.add(new SpeedSetting(0.0f, throttleInterval));
2230                }
2231            }
2232            if (rampingTimer == null) { //If this is the first time round then kick off the speed change
2233                setNextStep();
2234            }
2235        }
2236
2237        private void finishChange() {
2238            if (rampingTimer != null) {
2239                rampingTimer.stop();
2240            }
2241            rampingTimer = null;
2242            stepQueue.clear();
2243            stepQueue = null;
2244        }
2245
2246        synchronized void setNextStep() {
2247                if (stepQueue.isEmpty()) {
2248                    log.trace("Empty");
2249                    finishChange();
2250                    return;
2251                }
2252                SpeedSetting ss = stepQueue.getFirst();
2253                if (ss.getDuration() == 0) {
2254                    log.trace("Duratiom Zero");
2255                    finishChange();
2256                    return;
2257                }
2258                stepQueue.removeFirst();
2259                log.trace("Set New Speed[{}]",ss.getSpeedStep());
2260                throttle.setSpeedSetting(ss.getSpeedStep());
2261                rampingTimer = new javax.swing.Timer(ss.getDuration(), (java.awt.event.ActionEvent e) -> {
2262                    setNextStep();
2263                });
2264                rampingTimer.setRepeats(false);
2265                rampingTimer.start();
2266            }
2267
2268        private class SpeedSetting {
2269
2270            float step = 0.0f;
2271            int duration = 0;
2272
2273            SpeedSetting(float step, int duration) {
2274                this.step = step;
2275                this.duration = duration;
2276            }
2277
2278            float getSpeedStep() {
2279                return step;
2280            }
2281
2282            int getDuration() {
2283                return duration;
2284            }
2285        }
2286
2287        /**
2288         * Set the train speed directly, bypassing ramping.
2289         *
2290         * @param speed 0.0 (stop) to 1.0 (full)
2291         */
2292        public synchronized void setSpeedImmediate(float speed) {
2293            log.trace("{}: setting speed directly to {}%", _activeTrain.getTrainName(), (int) (speed * 100));
2294            stopAllTimers();
2295            targetSpeed = applyMaxThrottleAndFactor(speed);
2296            throttle.setSpeedSetting(targetSpeed);
2297        }
2298
2299        /**
2300         * Check if train is moving or stopped.
2301         *
2302         * @return true if stopped; false otherwise
2303         */
2304        public synchronized boolean isStopped() {
2305            // when stopping by speed profile you must refresh the throttle speed.
2306            return throttle.getSpeedSetting() <= 0.0004f;
2307        }
2308
2309        /**
2310         * Check if train is moving at its current requested speed.
2311         *
2312         * @return true if at requested speed; false otherwise
2313         */
2314        public synchronized boolean isAtSpeed() {
2315            return java.lang.Math.abs(throttle.getSpeedSetting() - targetSpeed) <= 0.01;
2316        }
2317
2318        /**
2319         * Flag from user to end run.
2320         */
2321        public void abort() {
2322            stopAllTimers();
2323        }
2324
2325        protected void setFunction(int cmdNum, boolean isSet) {
2326            throttle.setFunction(cmdNum, isSet);
2327        }
2328    }
2329
2330    /**
2331     * Convert ramp rate name, stored as a string into the constant value
2332     * assigned.
2333     *
2334     * @param rampRate  name of ramp rate, such as "RAMP_FAST"
2335     * @return integer representing a ramprate constant value
2336     */
2337    public static int getRampRateFromName(String rampRate) {
2338        if (rampRate.equals(Bundle.getMessage("RAMP_FAST"))) {
2339            return RAMP_FAST;
2340        } else if (rampRate.equals(Bundle.getMessage("RAMP_MEDIUM"))) {
2341            return RAMP_MEDIUM;
2342        } else if (rampRate.equals(Bundle.getMessage("RAMP_MED_SLOW"))) {
2343            return RAMP_MED_SLOW;
2344        } else if (rampRate.equals(Bundle.getMessage("RAMP_SLOW"))) {
2345            return RAMP_SLOW;
2346        } else if (rampRate.equals(Bundle.getMessage("RAMP_SPEEDPROFILE"))) {
2347            return RAMP_SPEEDPROFILE;
2348        }
2349        return RAMP_NONE;
2350    }
2351
2352    /*
2353     * Listener for switching Ghost blocks to unoccupied
2354     */
2355    static class DarkTerritoryListener implements PropertyChangeListener {
2356        private Sensor sensor;
2357
2358        public DarkTerritoryListener(Sensor sensor) {
2359            this.sensor = sensor;
2360            log.trace("Sensor[{}]",sensor.getDisplayName());
2361        }
2362
2363        @Override
2364        public void propertyChange(PropertyChangeEvent e) {
2365            if (e.getPropertyName().equals("state")) {
2366                ((Block) e.getSource()).removePropertyChangeListener(this);
2367                if (e.getNewValue().equals(Block.UNOCCUPIED)) {
2368                    try {
2369                        log.trace("Sensor INACTIVE[{}]", sensor.getDisplayName());
2370                        sensor.setKnownState(Sensor.INACTIVE);
2371                    } catch (jmri.JmriException ex) {
2372                        log.error("Error leaving darkterratory");
2373                    }
2374                }
2375            }
2376        }
2377    }
2378
2379    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AutoActiveTrain.class);
2380}