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