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