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