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