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