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