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