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