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            // If the train has no _currentAllocatedSection it is in a first block outside transit.
998            if (_currentAllocatedSection != null ) {
999                for (Block block : _currentAllocatedSection.getSection().getBlockList()) {
1000                    float speed = getSpeedFromBlock(block);
1001                    if (speed > 0 && speed < newSpeed) {
1002                        newSpeed = speed;
1003                    }
1004                }
1005            }
1006        }
1007        if (newSpeed > 0) {
1008            log.trace("setSpeedBySectionsAllocated isStopping[{}]",isStopping());
1009            cancelStopInCurrentSection();
1010            setTargetSpeed(getThrottleSettingFromSpeed(newSpeed));
1011        } else {
1012            stopInCurrentSection(NO_TASK);
1013        }
1014    }
1015
1016    /**
1017     * Check that all turnouts in a section have finished setting
1018     * for passage. If not listens on first bad turnout
1019     * and rechecks when set.
1020     * @param as Allocated section whose turnouts need to be checked.
1021     * @return true if no errors else false
1022     */
1023    private boolean checkTurn(AllocatedSection as) {
1024        if (as != null && as.getAutoTurnoutsResponse() != null) {
1025            Turnout to = InstanceManager.getDefault(DispatcherFrame.class).getAutoTurnoutsHelper().checkStateAgainstList(as.getAutoTurnoutsResponse());
1026            if (to != null) {
1027                // at least one turnout isnt correctly set
1028                to.addPropertyChangeListener(_turnoutStateListener = (PropertyChangeEvent e) -> {
1029                    if (e.getPropertyName().equals("KnownState")) {
1030                        ((Turnout) e.getSource()).removePropertyChangeListener(_turnoutStateListener);
1031                        setSpeedBySignal();
1032                    }
1033                });
1034                return false;
1035            }
1036        }
1037        return true;
1038    }
1039
1040    private void setSpeedBySignalMast() {
1041        //Set speed using SignalMasts;
1042        String displayedAspect = _controllingSignalMast.getAspect();
1043        if (log.isTraceEnabled()) {
1044            log.trace("{}: Controlling mast {} ({})", _activeTrain.getTrainName(), _controllingSignalMast.getDisplayName(USERSYS), displayedAspect);
1045            if (_conSignalProtectedBlock == null) {
1046                log.trace("{}: Protected block is null", _activeTrain.getTrainName());
1047            } else {
1048                log.trace("{}: Protected block: {} state: {} speed: {}", _activeTrain.getTrainName(),
1049                        _conSignalProtectedBlock.getSensor().getDisplayName(USERSYS),
1050                        (_conSignalProtectedBlock.getSensor().getState() == Block.OCCUPIED ? "OCCUPIED" : "NOT OCCUPIED"),
1051                        _conSignalProtectedBlock.getBlockSpeed());
1052            }
1053        }
1054
1055        if ((_controllingSignalMast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER).equals(displayedAspect))
1056                || !_controllingSignalMast.getLit() || _controllingSignalMast.getHeld()) {
1057            checkForSignalPassedOrStop(_controllingSignalMast.getDisplayName(USERSYS));
1058        } else if (_controllingSignalMast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.PERMISSIVE) != null
1059                && _controllingSignalMast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.PERMISSIVE).equals(displayedAspect)) {
1060            setTargetSpeedState(RESTRICTED_SPEED);
1061            _activeTrain.setStatus(ActiveTrain.RUNNING);
1062        } else {
1063
1064            //if using signalmasts, set speed to lesser of aspect speed and signalmastlogic speed
1065            //  (minimum speed on the path to next signal, using turnout and block speeds)
1066            String aspectSpeedStr = (String) _controllingSignalMast.getSignalSystem().getProperty(displayedAspect, "speed");
1067            log.trace("{}: Signal {} speed {} for aspect {}", _activeTrain.getTrainName(), _controllingSignalMast.getDisplayName(USERSYS), aspectSpeedStr, displayedAspect);
1068            float speed = -1.0f;
1069            if (aspectSpeedStr != null) {
1070                try {
1071                    speed = Float.parseFloat(aspectSpeedStr);
1072                } catch (NumberFormatException nx) {
1073                    try {
1074                        speed = InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(aspectSpeedStr);
1075                        log.trace("{}: Signal {} speed from map for {} is {}", _activeTrain.getTrainName(), _controllingSignalMast.getDisplayName(USERSYS), aspectSpeedStr, speed);
1076                    } catch (IllegalArgumentException ex) {
1077                        //Considered Normal if the speed does not appear in the map
1078                        log.trace("{}: Speed not found {}", _activeTrain.getTrainName(), aspectSpeedStr);
1079                    }
1080                }
1081            }
1082            int aspectSpeed = (int) speed; //save for debug message
1083
1084            //get maximum speed for the route between current and next signalmasts
1085            float smLogicSpeed = -1.0f;
1086            String smDestinationName = "unknown";
1087            SignalMastLogic smLogic = InstanceManager.getDefault(SignalMastLogicManager.class).getSignalMastLogic(_controllingSignalMast);
1088            if (smLogic != null) {
1089                SignalMast smDestination = smLogic.getActiveDestination();
1090                if (smDestination != null) {
1091                    smDestinationName = smDestination.getDisplayName(USERSYS);
1092                    smLogicSpeed = (int) smLogic.getMaximumSpeed(smDestination);
1093                }
1094            }
1095
1096            //use the smaller of aspect speed or route speed
1097            if (smLogicSpeed > -1.0f && smLogicSpeed < speed) {
1098                speed = smLogicSpeed;
1099            }
1100
1101            log.debug("{}: {}({}) {}({}), Dest: {}, path max: {}",
1102                    _activeTrain.getTrainName(),
1103                    _controllingSignalMast.getDisplayName(USERSYS), displayedAspect, aspectSpeedStr, aspectSpeed,
1104                    smDestinationName, (int) smLogicSpeed);
1105
1106            if (speed > -1.0f) {
1107                /* We should work on the basis that the speed required in the current block/section is governed by the signalmast
1108                 that we have passed and not the one we are approaching when we are accelerating.
1109                 However when we are decelerating we should be aiming to meet the speed required by the approaching signalmast
1110                 whether that is to slow down or come to a complete stand still.
1111                 */
1112                if (prevSpeed == -1 || speed < prevSpeed) {
1113                    log.debug("{}: Signal {} setting speed to {} for next", _activeTrain.getTrainName(),
1114                            _controllingSignalMast.getDisplayName(USERSYS), speed);
1115                    setTargetSpeedValue(speed);
1116                } else {
1117                    log.debug("{}: Signal {} setting speed to {} for previous", _activeTrain.getTrainName(),
1118                            _controllingSignalMast.getDisplayName(USERSYS), speed);
1119                    setTargetSpeedValue(prevSpeed);
1120                }
1121                prevSpeed = speed;
1122                _activeTrain.setStatus(ActiveTrain.RUNNING);
1123
1124            } else {
1125                log.warn("{}: No specific speeds found so will use the default", _activeTrain.getTrainName());
1126                setTargetSpeedState(NORMAL_SPEED);
1127                _activeTrain.setStatus(ActiveTrain.RUNNING);
1128            }
1129        }
1130    }
1131
1132    private void setSpeedBySignalHead() {
1133        // a held signal always stop
1134        if ( _controllingSignal != null && _controllingSignal.getAppearance() == SignalHead.HELD ) {
1135            // Held - Stop
1136            stopInCurrentSection(NO_TASK);
1137            return;
1138        }
1139
1140        if (useSpeedProfile) {
1141            // find speed from signal.
1142            // find speed from block
1143            // use least
1144            float blockSpeed = getSpeedFromBlock(_conSignalProtectedBlock);
1145
1146            float signalSpeed;
1147            String signalSpeedName;
1148            String displayedAspect = _controllingSignal.getAppearanceName();
1149            try {
1150                signalSpeedName =
1151                        InstanceManager.getDefault(SignalSpeedMap.class).getAppearanceSpeed(displayedAspect);
1152                signalSpeed = InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(signalSpeedName);
1153            } catch (Throwable ex) { // if _anything_ goes wrong, contain it
1154                signalSpeed = -1.0f;
1155                log.warn("{}: Block {} AppearanceSpeed {} not found in SignalSpeedMap",
1156                        _activeTrain.getTrainName(), _conSignalProtectedBlock.getDisplayName(USERSYS), displayedAspect);
1157            }
1158            float useSpeed;
1159            if (blockSpeed < signalSpeed) {
1160                useSpeed = blockSpeed;
1161            } else {
1162                useSpeed = signalSpeed;
1163            }
1164
1165            log.trace("BlockSpeed[{}] SignalSpeed[{}]", blockSpeed, signalSpeed);
1166            if (useSpeed < 0.01f) {
1167                checkForSignalPassedOrStop(_controllingSignal.getDisplayName(USERSYS));
1168            } else {
1169                setTargetSpeedByProfile(useSpeed);
1170            }
1171        } else {
1172            switch (_controllingSignal.getAppearance()) {
1173                case SignalHead.DARK:
1174                case SignalHead.RED:
1175                case SignalHead.FLASHRED:
1176                    // May get here from signal changing before Block knows it is occupied, so must
1177                    //      check Block occupancy sensor, which must change before signal.
1178                    // check to to see if its allocated to us!!!
1179                    //      check Block occupancy sensor if it is in an allocated block, which must change before signal
1180                    // If the train has no _currentAllocatedSection it is in a first block outside transit.
1181                    checkForSignalPassedOrStop(_controllingSignal.getDisplayName(USERSYS));
1182                    break;
1183                case SignalHead.YELLOW:
1184                case SignalHead.FLASHYELLOW:
1185                    setTargetSpeedState(SLOW_SPEED);
1186                    _activeTrain.setStatus(ActiveTrain.RUNNING);
1187                    break;
1188                case SignalHead.GREEN:
1189                case SignalHead.FLASHGREEN:
1190                    setTargetSpeedState(NORMAL_SPEED);
1191                    _activeTrain.setStatus(ActiveTrain.RUNNING);
1192                    break;
1193                case SignalHead.LUNAR:
1194                case SignalHead.FLASHLUNAR:
1195                    setTargetSpeedState(RESTRICTED_SPEED);
1196                    _activeTrain.setStatus(ActiveTrain.RUNNING);
1197                    break;
1198                default:
1199                    log.warn("Signal Head[{}] has invalid Appearence - using stop",_controllingSignal.getAppearance());
1200                    stopInCurrentSection(NO_TASK);
1201            }
1202
1203        }
1204    }
1205
1206    /**
1207     * Check to see if a stop is really required, or if this is the
1208     * signal head that was just passed, in which case ignore as the signal goes red before a
1209     * new signal exists.
1210     *
1211     * @param displayName name of signal for debug messages.
1212     */
1213    private void checkForSignalPassedOrStop(String displayName) {
1214        // if current section is null we are in a pre transit block.
1215        if (_currentAllocatedSection != null) {
1216            if ((_currentAllocatedSection.isInActiveBlockList(_conSignalProtectedBlock) ||
1217                    (_nextSection != null && _activeTrain.isInAllocatedList(_nextSection) && _nextSection.containsBlock(_conSignalProtectedBlock)))
1218                    && _conSignalProtectedBlock.getSensor().getState() == Block.OCCUPIED) {
1219                // Train has just passed this signal - ignore this signal
1220                log.debug("{}: _conSignalProtectedBlock [{}] for signal [{}] is the block just past so ignore.", _activeTrain.getTrainName(),
1221                        _conSignalProtectedBlock.getDisplayName(USERSYS), displayName);
1222            } else {
1223                log.debug("{}: stopping for signal [{}] ", _activeTrain.getTrainName(),
1224                         displayName);
1225                stopInCurrentSection(NO_TASK);
1226            }
1227        }
1228    }
1229
1230    protected float getSpeedFromBlock(Block block) {
1231        String blockSpeedName = block.getBlockSpeed();
1232        if (blockSpeedName.contains("Global")) {
1233            blockSpeedName = InstanceManager.getDefault(BlockManager.class).getDefaultSpeed();
1234        }
1235        float blockSpeed = -1.0f;
1236        if (!blockSpeedName.isEmpty()) {
1237            try {
1238                blockSpeed = Float.parseFloat(blockSpeedName);
1239            } catch (NumberFormatException nx) {
1240                try {
1241                    blockSpeed = InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(blockSpeedName);
1242                    log.debug("{} {}: block speed from map for {} is {}",
1243                            _activeTrain.getTrainName(), block.getDisplayName(USERSYS), blockSpeedName,
1244                            blockSpeed);
1245                } catch (Throwable ex) { // if _anything_ goes wrong, contain it
1246                    //Considered Normal if the speed does not appear in the map
1247                    log.warn("{}: Block {} Speed {} not found in SignalSpeedMap",
1248                            _activeTrain.getTrainName(), block.getDisplayName(USERSYS), blockSpeed);
1249                }
1250            }
1251        }
1252        return blockSpeed;
1253    }
1254
1255    float prevSpeed = -1.0f;
1256
1257    // called to cancel a stopping action that is in progress
1258    private synchronized void cancelStopInCurrentSection() {
1259        log.trace("[{}]:Cancel Stopping", _activeTrain.getTrainName());
1260        cancelStoppingBySensor();
1261        _stoppingByBlockOccupancy = false;
1262        _stoppingBlock = null;
1263        _stoppingUsingSpeedProfile = false;
1264        _stoppingBlock = null;
1265        _autoEngineer.slowToStop(false);
1266    }
1267
1268    private synchronized void stopInCurrentSection(int task) {
1269        if (_currentAllocatedSection == null) {
1270            log.error("{}: Current allocated section null on entry to stopInCurrentSection", _activeTrain.getTrainName());
1271            setStopNow();
1272            return;
1273        }
1274        log.debug("{}: StopInCurrentSection called for {} task[{}] targetspeed[{}]", _activeTrain.getTrainName(), _currentAllocatedSection.getSection().getDisplayName(USERSYS),task,getTargetSpeed());
1275        if (getTargetSpeed() == 0.0f || isStopping()) {
1276            log.debug("{}: train is already stopped or stopping.", _activeTrain.getTrainName());
1277            // ignore if train is already stopped or if stopping is in progress
1278            return;
1279        }
1280        // if Section has stopping sensors, use them
1281        if (_currentAllocatedSection.getSection().getState() == Section.FORWARD) {
1282            _stopSensor = _currentAllocatedSection.getSection().getForwardStoppingSensor();
1283        } else {
1284            _stopSensor = _currentAllocatedSection.getSection().getReverseStoppingSensor();
1285        }
1286        if (_stopSensor != null && _useStopSensor) {
1287            if (_stopSensor.getKnownState() == Sensor.ACTIVE) {
1288                // stop sensor is already active, stop now
1289                setStopNow();
1290            } else {
1291                setDecreasedSpeedBeforeStop();
1292                _stopSensor.addPropertyChangeListener(_stopSensorListener = (java.beans.PropertyChangeEvent e) -> {
1293                    handleStopSensorChange(e);
1294                });
1295                _stoppingBySensor = true;
1296            }
1297        } else if (_useSpeedProfile && _stopBySpeedProfile) {
1298            log.debug("{}: Section [{}] Section Length[{}] Max Train Length [{}] StopBySpeedProfile [{}]. setStopNow", _activeTrain.getTrainName(),
1299                    _currentAllocatedSection.getSection().getDisplayName(USERSYS), _currentAllocatedSection.getActualLength(), getMaxTrainLengthMM(), _stopBySpeedProfile);
1300            // stopping by speed profile uses section length to stop
1301            setTargetSpeedState(STOP_SPEED,useSpeedProfile);
1302        } else if (_currentAllocatedSection.getActualLength()  < getMaxTrainLengthMM()) {
1303            log.debug("{}: Section [{}] Section Length[{}] Max Train Length [{}]. setStopNow({})",
1304                    _activeTrain.getTrainName(),
1305                    _currentAllocatedSection.getSection().getDisplayName(USERSYS),
1306                    _currentAllocatedSection.getActualLength(),
1307                    getMaxTrainLengthMM(), _stopBySpeedProfile);
1308            // train will not fit comfortably in the Section, stop it immediately
1309            setStopNow();
1310        } else if (_activeTrain.getTrainDetection() == TrainDetection.TRAINDETECTION_WHOLETRAIN) {
1311            log.debug("{}: train will fit in [{}] ({}>={}), stop when prev block clears.", _activeTrain.getTrainName(),
1312                    _currentAllocatedSection.getSection().getDisplayName(USERSYS), _currentAllocatedSection.getActualLength(), getMaxTrainLengthMM());
1313            // train will fit in current allocated Section and has resistance wheels
1314            // try to stop by watching Section Block occupancy
1315            if (_currentAllocatedSection.getSection().getNumBlocks() == 1) {
1316                if (_previousAllocatedSection != null) {
1317                    Block tBlock;
1318                    // just because current section has one block does not mean the previous one did.
1319                    if (_previousAllocatedSection.getSection().getNumBlocks() == 1) {
1320                       tBlock = _previousAllocatedSection.getSection().getLastBlock();
1321                    } else {
1322                       tBlock = _previousAllocatedSection.getSection().getExitBlock();
1323                    }
1324                    if ((tBlock != null) && (tBlock.getState() == Block.OCCUPIED)) {
1325                        _stoppingBlock = tBlock;
1326                        setStopByBlockOccupancy(false);
1327                    } else {
1328                        setStopNow();
1329                    }
1330                } else {
1331                    setStopNow();
1332                }
1333            } else {
1334                // Section has multiple blocks
1335                Block exitBlock = _currentAllocatedSection.getExitBlock();
1336                Block enterBlock = _currentAllocatedSection.getEnterBlock(_previousAllocatedSection);
1337                if (enterBlock == null) {
1338                    // this is the first Section of the Transit, with train starting in this Section
1339                    setStopNow();
1340                } else if (exitBlock == enterBlock) {
1341                    // entry and exit are from the same Block
1342                    if ((_previousBlock != null) && (_previousBlock.getState() == Block.OCCUPIED)
1343                            && (getBlockLength(exitBlock) > getMaxTrainLengthMM())) {
1344                        _stoppingBlock = _previousBlock;
1345                        setStopByBlockOccupancy(false);
1346                    } else {
1347                        setStopNow();
1348                    }
1349                } else {
1350                    // try to move train as far into the Section as it will comfortably fit
1351                    Block tstBlock = exitBlock;
1352                    if (tstBlock == null) {
1353                        if (_currentAllocatedSection.getDirection() == Section.REVERSE) {
1354                            tstBlock = _currentAllocatedSection.getSection().getBlockBySequenceNumber(0);
1355                        } else {
1356                            tstBlock = _currentAllocatedSection.getSection().getBlockBySequenceNumber(
1357                                    _currentAllocatedSection.getSection().getNumBlocks() - 1);
1358                        }
1359                    }
1360                    int tstLength = getBlockLength(tstBlock);
1361                    int tstBlockSeq = _currentAllocatedSection.getSection().getBlockSequenceNumber(tstBlock);
1362                    while ((tstLength < getMaxTrainLengthMM()) && (tstBlock != enterBlock)) {
1363                        int newSeqNumber;
1364                        if (_currentAllocatedSection.getDirection() == Section.REVERSE) {
1365                            newSeqNumber = tstBlockSeq + 1;
1366                        } else {
1367                            newSeqNumber = tstBlockSeq - 1;
1368                        }
1369                        tstBlock = _currentAllocatedSection.getSection().getBlockBySequenceNumber(newSeqNumber);
1370                        tstBlockSeq = newSeqNumber;
1371                        tstLength += getBlockLength(tstBlock);
1372                    }
1373                    if (getMaxTrainLengthMM() > tstLength) {
1374                        setStopNow();
1375                    } else if (tstBlock == enterBlock) {
1376                        // train fits, but needs all available Blocks
1377                        Block previousSectionExitBlock = _previousAllocatedSection.getExitBlock();
1378                        if ((previousSectionExitBlock != null) && (previousSectionExitBlock.getState() == Block.OCCUPIED)) {
1379                            _stoppingBlock = previousSectionExitBlock;
1380                            setStopByBlockOccupancy(true);
1381                        } else {
1382                            setStopNow();
1383                        }
1384                    } else {
1385                        // train fits, and doesn't need all available Blocks
1386                        int xSeqNumber = tstBlockSeq + 1;
1387                        if (_currentAllocatedSection.getDirection() == Section.FORWARD ) {
1388                            xSeqNumber = tstBlockSeq - 1;
1389                        }
1390                        _stoppingBlock = _currentAllocatedSection.getSection().
1391                                getBlockBySequenceNumber(xSeqNumber);
1392                        setStopByBlockOccupancy(true);
1393                    }
1394                }
1395            }
1396        } else {
1397            // train will fit, but no way to stop it reliably
1398            setStopNow();
1399        }
1400        // even if no task is required it must be run
1401        // as cleanup happens after train stops.
1402        Runnable waitForStop = new WaitForTrainToStop(task);
1403        Thread tWait = jmri.util.ThreadingUtil.newThread(waitForStop, "Wait for stop " + getActiveTrain().getActiveTrainName());
1404        tWait.start();
1405    }
1406
1407    protected synchronized void executeStopTasks(int task) {
1408        // clean up stopping
1409        cancelStopInCurrentSection();
1410        dispatcher.queueReleaseOfCompletedAllocations();
1411        log.trace("exec[{}]",task);
1412        switch (task) {
1413            case END_TRAIN:
1414                _activeTrain.setStatus(ActiveTrain.DONE);
1415                break;
1416            case NO_TASK:
1417                // clean up stop
1418                break;
1419            case END_REVERSAL:
1420                /* Reset _previousBlock to be the _currentBlock if we do a continious reverse otherwise the stop in block method fails
1421                to stop the loco in the correct block
1422                 if the first block we come to has a stopped or held signal */
1423                _activeTrain.setRestart(_activeTrain.getDelayReverseRestart(),_activeTrain.getReverseRestartDelay(),
1424                        _activeTrain.getReverseRestartSensor(),_activeTrain.getResetReverseRestartSensor());
1425                _activeTrain.setTransitReversed(true);
1426                _activeTrain.reverseAllAllocatedSections();
1427                setEngineDirection();
1428                _previousBlock = null;
1429                _nextBlock = getNextBlock(_currentBlock,_currentAllocatedSection);
1430                if (_activeTrain.getDelayReverseRestart() == ActiveTrain.NODELAY) {
1431                   _activeTrain.holdAllocation(false);
1432                    // a reversal can happen in mid section
1433                    setupNewCurrentSignal(_currentAllocatedSection, true);
1434                    setSpeedBySignal();
1435                    if ((_nextSection != null) && !_activeTrain.isInAllocatedList(_nextSection)) {
1436                        InstanceManager.getDefault(DispatcherFrame.class).queueScanOfAllocationRequests();
1437                        break;
1438                    }
1439                }
1440                break;
1441            case BEGINNING_RESET:
1442                _activeTrain.setRestart(_activeTrain.getDelayedRestart(),_activeTrain.getRestartDelay(),
1443                        _activeTrain.getRestartSensor(),_activeTrain.getResetRestartSensor());
1444                if (_activeTrain.getResetWhenDone()) {
1445                    if (_activeTrain.getDelayedRestart() == ActiveTrain.NODELAY && !_activeTrain.getReverseAtEnd()) {
1446                        log.error("[{}]: train is continueing without pause, should have been handled in handleBlockStateChange.",_activeTrain.getTrainName());
1447                    } else {
1448                        // then active train is delayed
1449                        _activeTrain.setTransitReversed(false);
1450                        _activeTrain.resetAllAllocatedSections();
1451                        _previousBlock = null;
1452                        _nextBlock = getNextBlock(_currentBlock,_currentAllocatedSection);
1453                        setEngineDirection();
1454                        _activeTrain.setRestart(_activeTrain.getDelayedRestart(),_activeTrain.getRestartDelay(),
1455                                _activeTrain.getRestartSensor(), _activeTrain.getResetRestartSensor());
1456                        if ((_nextSection != null) && !_activeTrain.isInAllocatedList(_nextSection)) {
1457                            InstanceManager.getDefault(DispatcherFrame.class).queueScanOfAllocationRequests();
1458                        }
1459                        // can be mid block
1460                        setupNewCurrentSignal(null, true);
1461                        setSpeedBySignal();
1462
1463                    }
1464                } else {
1465                    // dispatcher cancelled auto restart while train was stopping?
1466                    log.warn("[{}]resetWhenDone flag reset, likely user cancelling while processing stop",
1467                            _activeTrain.getActiveTrainName());
1468                }
1469                break;
1470            default:
1471                log.debug("[{}]Invalid action [{}] in executeStopTasksRequest to execute BEGINNING_RESET cancelled", _activeTrain.getActiveTrainName(),task);
1472                break;
1473        }
1474    }
1475
1476    /**
1477     * Remove the stopping sensor
1478     */
1479    private void cancelStoppingBySensor() {
1480        if (_stopSensor != null) {
1481            _stopSensor.removePropertyChangeListener(_stopSensorListener);
1482            _stoppingBySensor = false;
1483            _stopSensorListener = null;
1484            _stopSensor = null;
1485        }
1486    }
1487
1488    /**
1489     * When the stopping sensor we are waiting on goes active
1490     * stop the train or set a new speed and destroy itself
1491     * @param e  - the property change event
1492     */
1493    private synchronized void handleStopSensorChange(java.beans.PropertyChangeEvent e) {
1494        if (e.getPropertyName().equals("KnownState") && (int) e.getNewValue() == Sensor.ACTIVE) {
1495            _stopSensor.removePropertyChangeListener(_stopSensorListener);
1496            _stoppingBySensor = false;
1497            _stopSensorListener = null;
1498            _stopSensor = null;
1499            if (_needSetSpeed) {
1500                _needSetSpeed = false;
1501                setSpeedBySignal();
1502            } else {
1503                setStopNow();
1504            }
1505        }
1506    }
1507
1508    private synchronized void setStopNow() {
1509        setStopNow(false);
1510        }
1511
1512    private synchronized void setStopNow(boolean useSpeedProfile) {
1513        setTargetSpeedState(STOP_SPEED,useSpeedProfile);
1514        if (_currentAllocatedSection == null) {  // this may occur if the train is not in the selected block when initially created and the signal is held.
1515            _activeTrain.setStatus(ActiveTrain.WAITING);
1516        } else if (_currentAllocatedSection.getNextSection() == null) {
1517            // wait for train to stop - this lets action items complete in a timely fashion
1518            waitUntilStopped();
1519            _activeTrain.setStatus(ActiveTrain.DONE);
1520        } else {
1521            _activeTrain.setStatus(ActiveTrain.WAITING);
1522        }
1523    }
1524
1525    /*
1526     * When multi block stopping, the stopping block may not be occupied yet.
1527     */
1528    private void setStopByBlockOccupancy(boolean ignoreNotOccupied) {
1529        // note: _stoppingBlock must be set before invoking this method
1530        //  verify that _stoppingBlock is actually occupied, if not stop immed
1531        if (_stoppingBlock.getState() == Block.OCCUPIED || ignoreNotOccupied) {
1532            setDecreasedSpeedBeforeStop();
1533            _stoppingByBlockOccupancy = true;
1534        } else {
1535            setStopNow();
1536        }
1537    }
1538
1539    /**
1540     * Before stopping by sensor alone, or by clearing previous block,
1541     * set the speed to the user defined preference.
1542     */
1543    private void setDecreasedSpeedBeforeStop() {
1544        float signalSpeed = 25;
1545        try {
1546            signalSpeed = InstanceManager.getDefault(SignalSpeedMap.class)
1547                    .getSpeed(InstanceManager.getDefault(DispatcherFrame.class).getStoppingSpeedName());
1548        } catch (IllegalArgumentException ex) {
1549            log.error("Missing [{}] from Speed table - defaulting to 25",
1550                    InstanceManager.getDefault(DispatcherFrame.class).getStoppingSpeedName());
1551        }
1552        setToAMaximumThrottle(getThrottleSettingFromSpeed(signalSpeed));
1553    }
1554
1555    /**
1556     * Sets the throttle percent unless it is already less than the new setting
1557     * @param throttleSetting  Max ThrottleSetting required.
1558     */
1559    private synchronized void setToAMaximumThrottle(float throttleSetting) {
1560        if (throttleSetting < getTargetSpeed()) {
1561            setTargetSpeed(throttleSetting);
1562        }
1563    }
1564
1565    /**
1566     * Calculates the throttle setting for a given speed.
1567     * @param speed  the unadjusted speed.
1568     * @return - throttle setting (a percentage)
1569     */
1570    private synchronized float getThrottleSettingFromSpeed(float speed) {
1571        if (useSpeedProfile) {
1572            float throttleSetting = _activeTrain.getRosterEntry().getSpeedProfile()
1573                    .getThrottleSettingFromSignalMapSpeed(speed, getForward());
1574            return throttleSetting;
1575        }
1576        if (InstanceManager.getDefault(DispatcherFrame.class).getSignalType() == DispatcherFrame.SIGNALMAST) {
1577            float mls;
1578            if (_controllingSignalMast != null) {
1579                mls = _controllingSignalMast.getSignalSystem().getMaximumLineSpeed();
1580            } else {
1581                //plan B
1582                mls = InstanceManager.getDefault(DispatcherFrame.class).getMaximumLineSpeed();
1583            }
1584            float throttleSetting = (speed / mls);
1585            return throttleSetting;
1586        } else {
1587            return speed/100.0f;
1588        }
1589    }
1590
1591
1592    /**
1593     * sets the throttle based on an index number into _speedRatio array
1594     * @param speedState  Index value
1595     */
1596    private synchronized void setTargetSpeedState(int speedState) {
1597        setTargetSpeedState(speedState,false);
1598    }
1599
1600    /**
1601     * sets the throttle based on an index number into _speedRatio array
1602     * @param speedState  Index value
1603     * @param stopBySpeedProfile if true use speed profile
1604     */
1605    private synchronized void setTargetSpeedState(int speedState,boolean stopBySpeedProfile) {
1606        log.trace("{}: setTargetSpeedState:({})",_activeTrain.getTrainName(),speedState);
1607        _autoEngineer.slowToStop(false);
1608        if (speedState > STOP_SPEED) {
1609            cancelStopInCurrentSection();
1610            if (_currentRampRate == RAMP_SPEEDPROFILE && _useSpeedProfile) {
1611                // we are going to ramp up  / down using section length and speed profile
1612                _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock) * _stopBySpeedProfileAdjust, speedState);
1613            } else {
1614                setTargetSpeed(_speedRatio[speedState]);
1615            }
1616        } else if (stopBySpeedProfile) {
1617            // we are going to stop by profile
1618            _stoppingUsingSpeedProfile = true;
1619            _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock) * _stopBySpeedProfileAdjust, 0.0f);
1620        } else {
1621            _autoEngineer.setHalt(true);
1622            setTargetSpeed(0.0f);
1623        }
1624    }
1625
1626    private synchronized void setTargetSpeedByProfile(float speedState) {
1627        // the speed comes in as units of warrents (mph, kph, mm/s etc)
1628            try {
1629                float throttleSetting = _activeTrain.getRosterEntry().getSpeedProfile().getThrottleSettingFromSignalMapSpeed(speedState, getForward());
1630                log.debug("{}: setTargetSpeedByProfile: {} SpeedState[{}]",
1631                        _activeTrain.getTrainName(),
1632                        throttleSetting,
1633                        speedState);
1634                if (throttleSetting > 0.009 && _currentRampRate != RAMP_SPEEDPROFILE && _useSpeedProfile) {
1635                    cancelStopInCurrentSection();
1636                    setTargetSpeed(throttleSetting); // apply speed factor and max
1637                } else if (throttleSetting > 0.009) {
1638                    cancelStopInCurrentSection();
1639                    _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock)  * _stopBySpeedProfileAdjust , throttleSetting);
1640                } else if (useSpeedProfile && _stopBySpeedProfile) {
1641                    setTargetSpeed(0.0f);
1642                    _stoppingUsingSpeedProfile = true;
1643                    _autoEngineer.setTargetSpeed(_currentAllocatedSection.getLengthRemaining(_currentBlock)  * _stopBySpeedProfileAdjust, 0.0f);
1644                } else {
1645                    _autoEngineer.slowToStop(false);
1646                    setTargetSpeed(0.0f);
1647                    _autoEngineer.setHalt(true);
1648                }
1649            } catch (Exception ex) {
1650                log.error("setTargetSpeedByProfile crashed - Emergency Stop: ", ex );
1651                _autoEngineer.slowToStop(false);
1652                setTargetSpeed(-1.0f);
1653                _autoEngineer.setHalt(true);
1654            }
1655        }
1656
1657    /**
1658     * Pass in speed as shown on dialogs, and convert to decimal speed needed by
1659     * throttle.
1660     */
1661    private synchronized void setTargetSpeedValue(float speed) {
1662        log.debug("{}: setTargetSpeedValue: Speed[{}]",_activeTrain.getTrainName(),speed);
1663        if (useSpeedProfile) {
1664            setTargetSpeedByProfile(speed);
1665            return;
1666        }
1667        _autoEngineer.slowToStop(false);
1668        float mls;
1669        if (_controllingSignalMast != null) {
1670            mls = _controllingSignalMast.getSignalSystem().getMaximumLineSpeed();
1671        } else {
1672            mls = InstanceManager.getDefault(DispatcherFrame.class).getMaximumLineSpeed();
1673        }
1674        float decSpeed = (speed / mls);
1675        if (decSpeed > 0.0f) {
1676            cancelStopInCurrentSection();
1677            setTargetSpeed(decSpeed);
1678        } else {
1679            setTargetSpeed(0.0f);
1680            _autoEngineer.setHalt(true);
1681        }
1682    }
1683
1684    private int getBlockLength(Block b) {
1685        if (b == null) {
1686            return (0);
1687        }
1688        return (int) b.getLengthMm();
1689//        float fLength = b.getLengthMm() / (float) InstanceManager.getDefault(DispatcherFrame.class).getScale().getScaleFactor();
1690//        if (InstanceManager.getDefault(DispatcherFrame.class).getUseScaleMeters()) {
1691//            return (int) (fLength * 0.001f);
1692//        }
1693//        return (int) (fLength * 0.00328084f);
1694    }
1695
1696    /**
1697     * Initiates running in manual mode with external throttle.
1698     * <p>
1699     * This method is triggered by an action in the Transit. The throttle in use
1700     * for automatic operation is dispatched.
1701     */
1702    protected void initiateWorking() {
1703        if (_activeTrain.getStatus() != ActiveTrain.WORKING) {
1704            _activeTrain.setMode(ActiveTrain.DISPATCHED);
1705            _activeTrain.setStatus(ActiveTrain.WORKING);
1706            saveSpeedAndDirection();
1707            if (_autoEngineer != null) {
1708                _autoEngineer.setHalt(true);
1709                waitUntilStopped();
1710                _autoEngineer.abort();
1711                InstanceManager.throttleManagerInstance().releaseThrottle(_throttle, this);
1712                _autoEngineer = null;
1713                _throttle = null;
1714            }
1715        }
1716    }
1717
1718    /**
1719     * Returns when train is stopped.
1720     * <p>
1721     * Note: Provides for _autoEngineer becoming null during wait Ties up the
1722     * current autoActiveTrain thread.
1723     */
1724    protected void waitUntilStopped() {
1725        boolean doneWaiting = false;
1726        while (!doneWaiting) {
1727            if (_autoEngineer != null) {
1728                doneWaiting = _autoEngineer.isStopped();
1729            } else {
1730                doneWaiting = true;
1731            }
1732            if (!doneWaiting) {
1733                try {
1734                    Thread.sleep(50);
1735                } catch (InterruptedException e) {
1736                    // ignore this exception
1737                }
1738            }
1739        }
1740    }
1741
1742    /**
1743     * Resumes automatic running after a working session using an external
1744     * throttle This method is triggered by the dispatcher hitting the "Resume
1745     * Auto Running" button A new throttle is acquired to allow automatic
1746     * running to resume
1747     */
1748    protected void resumeAutomaticRunning() {
1749        if ((_activeTrain.getStatus() == ActiveTrain.WORKING)
1750                || (_activeTrain.getStatus() == ActiveTrain.READY)) {
1751            _autoTrainAction.cancelDoneSensor();
1752            if (initialize()) {
1753                _resumingAutomatic = true;
1754            } else {
1755                log.error("Failed to initialize throttle when resuming automatic mode.");
1756            }
1757        }
1758    }
1759
1760    /**
1761     * Pause the auto active train for a specified number of fast clock minutes.
1762     *
1763     * @param fastMinutes the number of minutes to pause the train
1764     * @return the thread waiting on the pause or null if already paused
1765     */
1766    public Thread pauseTrain(int fastMinutes) {
1767        if (_pausingActive) {
1768            // if a pause train thread is currently active, ignore this call
1769            return (null);
1770        }
1771        Runnable pauseTrain = new PauseTrain(fastMinutes);
1772        Thread tPause = jmri.util.ThreadingUtil.newThread(pauseTrain, "pause train " + _activeTrain.getTrainName());
1773        tPause.start();
1774        return tPause;
1775    }
1776
1777    public void terminate() {
1778        // here add code to stop the train and release its throttle if it is in autoRun
1779        while (_activeHornThreads > 0) {
1780            try {
1781                Thread.sleep(50);
1782            } catch (InterruptedException e) {
1783                // ignore this exception
1784            }
1785        }
1786        _autoTrainAction.clearRemainingActions();
1787        if (_autoEngineer != null) {
1788            _autoEngineer.setHalt(true);
1789            try {
1790                Thread.sleep(50);
1791            } catch (InterruptedException e) {
1792                // ignore this exception
1793            }
1794            waitUntilStopped();
1795            _autoEngineer.abort();
1796            InstanceManager.throttleManagerInstance().releaseThrottle(_throttle, this);
1797        }
1798    }
1799
1800    public void dispose() {
1801        if (_controllingSignalMast != null && _conSignalMastListener != null) {
1802            _controllingSignalMast.removePropertyChangeListener(_conSignalMastListener);
1803        }
1804        _controllingSignalMast = null;
1805        _conSignalMastListener = null;
1806    }
1807
1808// _________________________________________________________________________________________
1809    // This class waits for train stop in a separate thread
1810    class WaitForTrainToStop implements Runnable {
1811
1812        public WaitForTrainToStop(int task) {
1813            _task = task;
1814        }
1815
1816        @Override
1817        public void run() {
1818            boolean waitingOnTrain = true;
1819            try {
1820                while (waitingOnTrain) {
1821                    if ((getAutoEngineer() != null) && (getAutoEngineer().isStopped())) {
1822                        waitingOnTrain = false;
1823                    } else {
1824                        Thread.sleep(_delay);
1825                    }
1826                }
1827                log.trace("executing task[{}]",_task);
1828                executeStopTasks(_task);
1829            } catch (InterruptedException e) {
1830                log.warn("Waiting for train to stop interrupted - stop tasks not executing");
1831            } catch (Exception e) {
1832                log.error("Waiting for train to stop crashed - stop tasks not executing.", e);
1833            }
1834        }
1835
1836        private final int _delay = 91;
1837        private int _task = 0;
1838    }
1839
1840    /**
1841     * Pause the train in a separate thread. Train is stopped, then restarted
1842     * after specified number of fast Minutes have elapsed.
1843     */
1844    class PauseTrain implements Runnable {
1845        /**
1846         * Create a PauseTrain
1847         *
1848         * @param fastMinutes the number of fast clock minutes to pause the
1849         *                    train
1850         */
1851        public PauseTrain(int fastMinutes) {
1852            _fastMinutes = fastMinutes;
1853        }
1854
1855        @Override
1856        public void run() {
1857            // set to pause at a fast ramp rate
1858            _pausingActive = true;
1859            _savedTargetSpeed = getTargetSpeed();
1860            _savedRampRate = getRampRate();
1861            setCurrentRampRate(RAMP_FAST);
1862            stopInCurrentSection(NO_TASK);
1863            // wait for train to stop
1864            boolean waitNow = true;
1865            boolean keepGoing = true;
1866            while (waitNow) {
1867                try {
1868                    Thread.sleep(101);
1869                    if (_autoEngineer != null) {
1870                        if (_autoEngineer.isStopped()) {
1871                            waitNow = false;
1872                        }
1873                    } else {
1874                        waitNow = false;
1875                    }
1876                } catch (InterruptedException e) {
1877                    log.trace("InterruptedException while waiting to stop for pause-indicates action cancelled.", e);
1878                    waitNow = false;
1879                    keepGoing = false;
1880                }
1881            }
1882            _activeTrain.setStatus(ActiveTrain.PAUSED);
1883            if (keepGoing) {
1884                // wait for specified fast clock time
1885                Timebase _clock = InstanceManager.getDefault(jmri.Timebase.class);
1886                java.beans.PropertyChangeListener _clockListener = (java.beans.PropertyChangeEvent e) -> {
1887                    _fastMinutes--;
1888                };
1889                _clock.addMinuteChangeListener(_clockListener);
1890                // wait for fast minutes to tick away
1891                waitNow = true;
1892                while (waitNow) {
1893                    try {
1894                        Thread.sleep(501);
1895                        if (_fastMinutes <= 0) {
1896                            waitNow = false;
1897                        }
1898                    } catch (InterruptedException e) {
1899                        log.trace("InterruptedException indicates action cancelled.", e);
1900                        keepGoing = false;
1901                    }
1902                }
1903                _clock.removeMinuteChangeListener(_clockListener);
1904            }
1905            _pausingActive = false;
1906            if (keepGoing) {
1907                // this thread was not interrupted
1908                //   resume running - restore speed, status, and ramp rate
1909                setCurrentRampRate(_savedRampRate);
1910                setTargetSpeed(_savedTargetSpeed);
1911                _activeTrain.setStatus(ActiveTrain.RUNNING);
1912                setSpeedBySignal();
1913            }
1914        }
1915        private int _fastMinutes = 0;
1916        private float _savedTargetSpeed = 0.0f;
1917        private int _savedRampRate = RAMP_NONE;
1918    }
1919
1920    // _________________________________________________________________________________________
1921    // this class handles the interface with the throttle
1922    // (This class started from code by Pete Cressman contained in Warrant.java.)
1923    class AutoEngineer  {
1924
1925        AutoEngineer(DccThrottle throttle, RosterEntry rosterEntry) {
1926            this.throttle = throttle;
1927            this.rosterEntry = rosterEntry;
1928        }
1929
1930        private DccThrottle throttle;
1931        private int ramping;
1932        private boolean speedProfileStoppingIsRunning = false;
1933        private float speedIncrement = 0.0f; //will be recalculated
1934        private float targetSpeed;
1935        private RosterEntry rosterEntry;
1936        private int throttleInterval;
1937        private float minReliableOperatingSpeed;
1938        private float maxSpeed;
1939        private float speedFactor;
1940
1941        public void setRamping(int ramping, int fullRampTime, int minThrottleInterval, int rampRate) {
1942            this.ramping = ramping;
1943            this.throttleInterval = minThrottleInterval;
1944            //calculate speed increment to use in each minInterval time
1945            speedIncrement = (100.0f / ((float) fullRampTime / minThrottleInterval)
1946                    / rampRate) / 100.0f;
1947            log.debug("{}: _speedIncrement={}", throttle.getLocoAddress(), speedIncrement);
1948        }
1949
1950        public  void setIsForward(boolean isForward) {
1951            throttle.setIsForward(isForward);
1952        }
1953
1954        public boolean getIsForward() {
1955            return(throttle.getIsForward());
1956        }
1957
1958        public void setTargetSpeed(float speed) {
1959            stopAllTimers();
1960            targetSpeed = applyMaxThrottleAndFactor(speed);
1961            log.debug("setTargetSpeed: Set Speed[{}] adjusted to TargetSpeed[{}] ",speed,targetSpeed);
1962            if (ramping == RAMP_NONE || ramping == RAMP_SPEEDPROFILE ) {
1963                throttle.setSpeedSetting(targetSpeed);
1964            } else {
1965                rampToTarget();
1966            }
1967        }
1968
1969        public float getTargetSpeed(){
1970            return(targetSpeed);
1971        }
1972
1973        /**
1974        *
1975        * @param throttleSetting the throttle setting that would normally be set
1976        * @return the adjusted throttle setting after applying Max Throttle and Percentage throttle settings
1977        */
1978       private float applyMaxThrottleAndFactor(float throttleSetting) {
1979           if (throttleSetting > 0.0f) {
1980               if ((throttleSetting * speedFactor) > maxSpeed) {
1981                   return maxSpeed;
1982               }
1983               if ((throttleSetting * speedFactor) < minReliableOperatingSpeed) {
1984                   return minReliableOperatingSpeed;
1985               }
1986               return (throttleSetting * speedFactor); //adjust for train's Speed Factor
1987           } else {
1988               return throttleSetting;
1989           }
1990       }
1991
1992        /**
1993         * Flag from user's control.
1994         *
1995         * @param halt true to immediately stop the train; false otherwise
1996         */
1997        public void setHalt(boolean halt) {
1998            if (halt) {
1999                this.setSpeedImmediate(0.0f);
2000            }
2001        }
2002
2003        /**
2004         * Set the limits and adjustment factore for train speed.
2005         * Active train will calculate the required setting and it will be adjusted if not 0.0f
2006         * required setting * speed Factor  then test for less than max and greater than min.
2007         * @param minReliableOperatingSpeed lowest throttle % train will reliably move.
2008         * @param maxSpeed max throttle % for train.
2009         * @param speedFactor multiplier
2010         */
2011        public void setSpeedLimits(float minReliableOperatingSpeed, float maxSpeed, float speedFactor) {
2012            this.minReliableOperatingSpeed = minReliableOperatingSpeed;
2013            this.maxSpeed = maxSpeed;
2014            this.speedFactor = speedFactor;
2015        }
2016
2017        public void setTargetSpeed(float distance, float speed) {
2018            log.debug("Set Target Speed[{}] with distance{{}] from speed[{}]",speed,distance,throttle.getSpeedSetting());
2019            stopAllTimers();
2020            if (rosterEntry != null) {
2021                rosterEntry.getSpeedProfile().setExtraInitialDelay(1500f);
2022                rosterEntry.getSpeedProfile().setMinMaxLimits(minReliableOperatingSpeed, maxSpeed);
2023                rosterEntry.getSpeedProfile().changeLocoSpeed(_throttle, distance, speed);
2024                speedProfileStoppingIsRunning = true;
2025                targetSpeed = speed;
2026            } else {
2027                setTargetSpeed((0.0f));
2028            }
2029        }
2030
2031        public void slowToStop(boolean on) {
2032            stopAllTimers();
2033            if (on) {
2034                log.debug("SlowToStopOn");
2035                setTargetSpeed((0.0f));
2036            }
2037        }
2038
2039        public void stopAllTimers() {
2040            if (speedProfileStoppingIsRunning) {
2041                re.getSpeedProfile().cancelSpeedChange();
2042                speedProfileStoppingIsRunning = false;
2043            }
2044            if (rampingTimer != null) {
2045                rampingTimer.stop();
2046                rampingTimer = null;
2047            }
2048        }
2049
2050        LinkedList<SpeedSetting> stepQueue;
2051        private javax.swing.Timer rampingTimer;
2052
2053        private void rampToTarget() {
2054            // target already adjusted.
2055            log.debug("RampToTarget[{}]current[{}]", getTargetSpeed(), throttle.getSpeedSetting());
2056            stepQueue = new LinkedList<>();
2057            if (throttle.getSpeedSetting() <= getTargetSpeed()) {
2058                // Up
2059                float newSpeed = throttle.getSpeedSetting();
2060                if (newSpeed < minReliableOperatingSpeed) {
2061                    stepQueue.add(new SpeedSetting(minReliableOperatingSpeed, throttleInterval));
2062                    newSpeed = minReliableOperatingSpeed;
2063                }
2064                while (newSpeed < getTargetSpeed()) {
2065                    newSpeed += speedIncrement;
2066                    if (newSpeed > getTargetSpeed()) {
2067                        newSpeed = getTargetSpeed();
2068                    }
2069                    log.trace("NewSpeedUp[{}]", newSpeed);
2070                    stepQueue.add(new SpeedSetting(newSpeed, throttleInterval));
2071                }
2072            } else {
2073                // Down
2074                boolean andStop = false;
2075                if (getTargetSpeed() <= 0.0f) {
2076                    andStop = true;
2077                }
2078                float newSpeed = throttle.getSpeedSetting();
2079                while (newSpeed > getTargetSpeed()) {
2080                    newSpeed -= speedIncrement;
2081                    if (newSpeed < getTargetSpeed()) {
2082                        newSpeed = getTargetSpeed();
2083                    }
2084                    log.trace("NewSpeedDown[{}]", newSpeed);
2085                    stepQueue.add(new SpeedSetting(newSpeed, throttleInterval));
2086                }
2087                if (andStop) {
2088                    stepQueue.add(new SpeedSetting(0.0f, throttleInterval));
2089                }
2090            }
2091            if (rampingTimer == null) { //If this is the first time round then kick off the speed change
2092                setNextStep();
2093            }
2094        }
2095
2096        private void finishChange() {
2097            if (rampingTimer != null) {
2098                rampingTimer.stop();
2099            }
2100            rampingTimer = null;
2101            stepQueue.clear();
2102            stepQueue = null;
2103        }
2104
2105        synchronized void setNextStep() {
2106                if (stepQueue.isEmpty()) {
2107                    log.trace("Empty");
2108                    finishChange();
2109                    return;
2110                }
2111                SpeedSetting ss = stepQueue.getFirst();
2112                if (ss.getDuration() == 0) {
2113                    log.trace("Duratiom Zero");
2114                    finishChange();
2115                    return;
2116                }
2117                stepQueue.removeFirst();
2118                log.trace("Set New Speed[{}]",ss.getSpeedStep());
2119                throttle.setSpeedSetting(ss.getSpeedStep());
2120                rampingTimer = new javax.swing.Timer(ss.getDuration(), (java.awt.event.ActionEvent e) -> {
2121                    setNextStep();
2122                });
2123                rampingTimer.setRepeats(false);
2124                rampingTimer.start();
2125            }
2126
2127        private class SpeedSetting {
2128
2129            float step = 0.0f;
2130            int duration = 0;
2131
2132            SpeedSetting(float step, int duration) {
2133                this.step = step;
2134                this.duration = duration;
2135            }
2136
2137            float getSpeedStep() {
2138                return step;
2139            }
2140
2141            int getDuration() {
2142                return duration;
2143            }
2144        }
2145
2146        /**
2147         * Set the train speed directly, bypassing ramping.
2148         *
2149         * @param speed 0.0 (stop) to 1.0 (full)
2150         */
2151        public synchronized void setSpeedImmediate(float speed) {
2152            log.trace("{}: setting speed directly to {}%", _activeTrain.getTrainName(), (int) (speed * 100));
2153            stopAllTimers();
2154            targetSpeed = applyMaxThrottleAndFactor(speed);
2155            throttle.setSpeedSetting(targetSpeed);
2156        }
2157
2158        /**
2159         * Check if train is moving or stopped.
2160         *
2161         * @return true if stopped; false otherwise
2162         */
2163        public synchronized boolean isStopped() {
2164            // when stopping by speed profile you must refresh the throttle speed.
2165            return throttle.getSpeedSetting() <= 0.0004f;
2166        }
2167
2168        /**
2169         * Check if train is moving at its current requested speed.
2170         *
2171         * @return true if at requested speed; false otherwise
2172         */
2173        public synchronized boolean isAtSpeed() {
2174            return java.lang.Math.abs(throttle.getSpeedSetting() - targetSpeed) <= 0.01;
2175        }
2176
2177        /**
2178         * Flag from user to end run.
2179         */
2180        public void abort() {
2181            stopAllTimers();
2182        }
2183
2184        protected void setFunction(int cmdNum, boolean isSet) {
2185            throttle.setFunction(cmdNum, isSet);
2186        }
2187    }
2188
2189    /**
2190     * Convert ramp rate name, stored as a string into the constant value
2191     * assigned.
2192     *
2193     * @param rampRate  name of ramp rate, such as "RAMP_FAST"
2194     * @return integer representing a ramprate constant value
2195     */
2196    public static int getRampRateFromName(String rampRate) {
2197        if (rampRate.equals(Bundle.getMessage("RAMP_FAST"))) {
2198            return RAMP_FAST;
2199        } else if (rampRate.equals(Bundle.getMessage("RAMP_MEDIUM"))) {
2200            return RAMP_MEDIUM;
2201        } else if (rampRate.equals(Bundle.getMessage("RAMP_MED_SLOW"))) {
2202            return RAMP_MED_SLOW;
2203        } else if (rampRate.equals(Bundle.getMessage("RAMP_SLOW"))) {
2204            return RAMP_SLOW;
2205        } else if (rampRate.equals(Bundle.getMessage("RAMP_SPEEDPROFILE"))) {
2206            return RAMP_SPEEDPROFILE;
2207        }
2208        return RAMP_NONE;
2209    }
2210
2211    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AutoActiveTrain.class);
2212}