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