001package jmri.jmrit.logix;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import javax.annotation.Nonnull;
005
006import java.awt.Color;
007import java.util.List;
008import java.util.ListIterator;
009import jmri.DccThrottle;
010import jmri.NamedBean;
011import jmri.NamedBeanHandle;
012import jmri.Sensor;
013import jmri.util.ThreadingUtil;
014import jmri.jmrit.logix.ThrottleSetting.Command;
015import jmri.jmrit.logix.ThrottleSetting.CommandValue;
016import jmri.jmrit.logix.ThrottleSetting.ValueType;
017import org.slf4j.Logger;
018import org.slf4j.LoggerFactory;
019
020
021/**
022 * Execute a throttle command script for a warrant.
023 * <p>
024 * This generally operates on its own thread, but calls the warrant
025 * thread via Warrant.fireRunStatus to show status. fireRunStatus uses
026 * ThreadingUtil.runOnGUIEventually to display on the layout thread.
027 *
028 * @author Pete Cressman Copyright (C) 2009, 2010, 2020
029 */
030/*
031 * ************************ Thread running the train ****************
032 */
033class Engineer extends Thread implements java.beans.PropertyChangeListener {
034
035    private int _idxCurrentCommand;     // current throttle command
036    private ThrottleSetting _currentCommand;
037    private long _commandTime = 0;      // system time when command was executed.
038    private int _idxSkipToSpeedCommand;   // skip to this index to reset script when ramping
039    private float _normalSpeed = 0;       // current commanded throttle setting from script (unmodified)
040    // speed name of current motion. When train stopped, is the speed that will be restored when movement is permitted
041    private String _speedType = Warrant.Normal; // is never Stop or EStop
042    private float _timeRatio = 1.0f;     // ratio to extend scripted time when speed is modified
043    private boolean _abort = false;
044    private boolean _halt = false;  // halt/resume from user's control
045    private boolean _stopPending = false;   // ramp slow down in progress
046    private boolean _waitForClear = false;  // waits for signals/occupancy/allocation to clear
047    private boolean _waitForSensor = false; // wait for sensor event
048    private boolean _runOnET = false;   // Execute commands on ET only - do not synch
049    private boolean _setRunOnET = false; // Need to delay _runOnET from the block that set it
050    protected DccThrottle _throttle;
051    private final Warrant _warrant;
052    private final List<ThrottleSetting> _commands;
053    private Sensor _waitSensor;
054    private int _sensorWaitState;
055    private Object _rampLockObject = new Object();
056    private Object _synchLockObject = new Object();
057    private Object _clearLockObject = new Object();
058    private boolean _atHalt = false;
059    private boolean _atClear = false;
060    private final SpeedUtil _speedUtil;
061    private OBlock _synchBlock = null;
062    private Thread _checker = null;
063
064    private ThrottleRamp _ramp;
065    private boolean _holdRamp = false;
066    private boolean _isRamping = false;
067
068    Engineer(Warrant warrant, DccThrottle throttle) {
069        _warrant = warrant;
070        _throttle = throttle;
071        _speedUtil = warrant.getSpeedUtil();
072        _commands = _warrant.getThrottleCommands();
073        _idxCurrentCommand = 0;
074        _currentCommand = _commands.get(_idxCurrentCommand);
075        _idxSkipToSpeedCommand = 0;
076        _waitForSensor = false;
077        setName("Engineer(" + _warrant.getTrainName() +")");
078    }
079
080    @Override
081    @SuppressFBWarnings(value="UW_UNCOND_WAIT", justification="waits may be indefinite until satisfied or thread aborted")
082    public void run() {
083        if (log.isDebugEnabled())
084            log.debug("Engineer started warrant {} _throttle= {}", _warrant.getDisplayName(), _throttle.getClass().getName());
085
086        int cmdBlockIdx = 0;
087        while (_idxCurrentCommand < _commands.size()) {
088            while (_idxSkipToSpeedCommand > _idxCurrentCommand) {
089                if (log.isDebugEnabled()) {
090                    ThrottleSetting ts = _commands.get(_idxCurrentCommand);
091                    log.debug("{}: Skip Cmd #{}: {} Warrant", _warrant.getDisplayName(), _idxCurrentCommand+1, ts);
092                    // Note: command indexes biased from 0 to 1 to match Warrant display of commands.
093                }
094                _idxCurrentCommand++;
095            }
096            if (_idxCurrentCommand == _commands.size()) {
097                // skip commands on last block may advance too far. Due to looking for a NOOP
098                break;
099            }
100            _currentCommand = _commands.get(_idxCurrentCommand);
101            long cmdWaitTime = _currentCommand.getTime();    // time to wait before executing command
102            ThrottleSetting.Command command = _currentCommand.getCommand();
103            _runOnET = _setRunOnET;     // OK to set here
104            if (command.hasBlockName()) {
105                int idx = _warrant.getIndexOfBlockAfter((OBlock)_currentCommand.getBean(), cmdBlockIdx);
106                if (idx >= 0) {
107                    cmdBlockIdx = idx;
108                }
109            }
110            if (cmdBlockIdx < _warrant.getCurrentOrderIndex() ||
111                    (command.equals(Command.NOOP) && (cmdBlockIdx <= _warrant.getCurrentOrderIndex()))) {
112                // Train advancing too fast, need to process commands more quickly,
113                // allow some time for whistle toots etc.
114                cmdWaitTime = Math.min(cmdWaitTime, 200); // 200ms per command should be enough for toots etc.
115                if (log.isDebugEnabled())
116                    log.debug("{}: Train reached block \"{}\" before script et={}ms",
117                            _warrant.getDisplayName(), _warrant.getCurrentBlockName(), _currentCommand.getTime());
118            }
119            if (_abort) {
120                break;
121            }
122
123            long cmdStart = System.currentTimeMillis();
124            if (log.isDebugEnabled())
125                log.debug("{}: Start Cmd #{} for block \"{}\" currently in \"{}\". wait {}ms to do cmd {}",
126                    _warrant.getDisplayName(), _idxCurrentCommand+1, _currentCommand.getBeanDisplayName(),
127                    _warrant.getCurrentBlockName(), cmdWaitTime, command.toString());
128                    // Note: command indexes biased from 0 to 1 to match Warrant display of commands.
129            synchronized (this) {
130                if (!Warrant.Normal.equals(_speedType)) {
131                    cmdWaitTime = (long)(cmdWaitTime*_timeRatio); // extend et when speed has been modified from scripted speed
132                }
133                try {
134                    if (cmdWaitTime > 0) {
135                        wait(cmdWaitTime);
136                    }
137                } catch (InterruptedException ie) {
138                    log.debug("InterruptedException during time wait", ie);
139                    _warrant.debugInfo();
140                    Thread.currentThread().interrupt();
141                    _abort = true;
142                } catch (java.lang.IllegalArgumentException iae) {
143                    log.error("At time wait", iae);
144                }
145            }
146            if (_abort) {
147                break;
148            }
149
150            // Having waited, time=ts.getTime(), so blocks should agree.  if not,
151            // wait for train to arrive at block and send sync notification.
152            // note, blind runs cannot detect entrance.
153            if (!_runOnET && cmdBlockIdx > _warrant.getCurrentOrderIndex()) {
154                // commands are ahead of current train position
155                // When the next block goes active or a control command is made, a clear sync call
156                // will test these indexes again and can trigger a notify() to free the wait
157
158                synchronized (_synchLockObject) {
159                    _synchBlock = _warrant.getBlockAt(cmdBlockIdx);
160                    _warrant.fireRunStatus("WaitForSync", _idxCurrentCommand - 1, _idxCurrentCommand);
161                    if (log.isDebugEnabled()) {
162                        log.debug("{}: Wait for train to enter \"{}\".",
163                                _warrant.getDisplayName(), _synchBlock.getDisplayName());
164                    }
165                    try {
166                        _synchLockObject.wait();
167                        _synchBlock = null;
168                    } catch (InterruptedException ie) {
169                        log.debug("InterruptedException during _waitForSync", ie);
170                        _warrant.debugInfo();
171                        Thread.currentThread().interrupt();
172                        _abort = true;
173                    }
174                }
175                if (_abort) {
176                    break;
177                }
178            }
179
180            synchronized (_clearLockObject) {
181                // block position and elapsed time are as expected, but track conditions
182                // such as signals or rogue occupancy requires waiting
183                if (_waitForClear) {
184                    try {
185                        _atClear = true;
186                        if (log.isDebugEnabled())
187                            log.debug("{}: Waiting for clearance. _waitForClear= {} _halt= {} at block \"{}\" Cmd#{}.",
188                                _warrant.getDisplayName(), _waitForClear, _halt,
189                                _warrant.getBlockAt(cmdBlockIdx).getDisplayName(), _idxCurrentCommand+1);
190                        _clearLockObject.wait();
191                        _waitForClear = false;
192                        _atClear = false;
193                    } catch (InterruptedException ie) {
194                        log.debug("InterruptedException during _atClear", ie);
195                        _warrant.debugInfo();
196                        Thread.currentThread().interrupt();
197                        _abort = true;
198                    }
199                }
200            }
201            if (_abort) {
202                break;
203            }
204
205            synchronized (this) {
206                // user's command to halt requires waiting
207                if (_halt) {
208                    try {
209                        _atHalt = true;
210                        if (log.isDebugEnabled())
211                            log.debug("{}: Waiting to Resume. _halt= {}, _waitForClear= {}, Block \"{}\".",
212                                _warrant.getDisplayName(), _halt, _waitForClear,
213                                _warrant.getBlockAt(cmdBlockIdx).getDisplayName());
214                        wait();
215                        _halt = false;
216                        _atHalt = false;
217                    } catch (InterruptedException ie) {
218                        log.debug("InterruptedException during _atHalt", ie);
219                        _warrant.debugInfo();
220                        Thread.currentThread().interrupt();
221                        _abort = true;
222                    }
223                }
224            }
225            if (_abort) {
226                break;
227            }
228
229            synchronized (this) {
230                while (_isRamping || _holdRamp) {
231                    int idx = _idxCurrentCommand;
232                    try {
233                        if (log.isDebugEnabled())
234                            log.debug("{}: Waiting for ramp to finish at Cmd #{}.",
235                                  _warrant.getDisplayName(), _idxCurrentCommand+1);
236                        wait();
237                    } catch (InterruptedException ie) {
238                        _warrant.debugInfo();
239                        Thread.currentThread().interrupt();
240                        _abort = true;
241                    }
242                    // ramp will decide whether to skip or execute _currentCommand
243                    if (log.isDebugEnabled()) {
244                        log.debug("{}: Cmd #{} held for {}ms. {}", _warrant.getDisplayName(),
245                                idx+1, System.currentTimeMillis() - cmdStart, _currentCommand);
246                    }
247                }
248                if (_idxSkipToSpeedCommand <= _idxCurrentCommand) {
249                    executeComand(_currentCommand, System.currentTimeMillis() - cmdStart);
250                    _idxCurrentCommand++;
251                }
252            }
253        }
254        // shut down
255        setSpeed(0.0f); // for safety to be sure train stops
256        _warrant.stopWarrant(_abort, true);
257    }
258
259    private void executeComand(ThrottleSetting ts, long et) {
260        Command command = ts.getCommand();
261        CommandValue cmdVal = ts.getValue();
262        switch (command) {
263            case SPEED:
264                _normalSpeed = cmdVal.getFloat();
265                float speedMod = _speedUtil.modifySpeed(_normalSpeed, _speedType);
266                if (_normalSpeed > speedMod) {
267                    float trackSpeed = _speedUtil.getTrackSpeed(speedMod);
268                    _timeRatio = _speedUtil.getTrackSpeed(_normalSpeed) / trackSpeed;
269                    _speedUtil.speedChange(speedMod);  // call before this setting to compute travel of last setting
270                    setSpeed(speedMod);
271                } else {
272                    _timeRatio = 1.0f;
273                    _speedUtil.speedChange(_normalSpeed);  // call before this setting to compute travel of last setting
274                    setSpeed(_normalSpeed);
275                }
276                break;
277            case NOOP:
278                break;
279            case SET_SENSOR:
280                ThreadingUtil.runOnGUIEventually(() ->
281                    setSensor(ts.getNamedBeanHandle(), cmdVal));
282                break;
283            case FKEY:
284                setFunction(ts.getKeyNum(), cmdVal.getType());
285                break;
286            case FORWARD:
287                setForward(cmdVal.getType());
288                break;
289            case LATCHF:
290                setFunctionMomentary(ts.getKeyNum(), cmdVal.getType());
291                break;
292            case WAIT_SENSOR:
293                waitForSensor(ts.getNamedBeanHandle(), cmdVal);
294                break;
295            case RUN_WARRANT:
296                ThreadingUtil.runOnGUIEventually(() ->
297                    runWarrant(ts.getNamedBeanHandle(), cmdVal));
298                break;
299            case SPEEDSTEP:
300                break;
301            default:
302        }
303        _commandTime = System.currentTimeMillis();
304        if (log.isDebugEnabled()) {
305            log.debug("{}: Cmd #{} done. et={}. {}",
306                   _warrant.getDisplayName(), _idxCurrentCommand + 1, et, ts);
307        }
308    }
309
310    protected int getCurrentCommandIndex() {
311        return _idxCurrentCommand;
312    }
313
314    /**
315     * Delayed ramp has started.
316     * Currently informational only
317     * Do non-speed commands only until idx is reached?  maybe not.
318     * @param idx index
319     */
320    private void advanceToCommandIndex(int idx) {
321        _idxSkipToSpeedCommand = idx;
322      if (log.isTraceEnabled())
323            log.debug("advanceToCommandIndex to {} - {}", _idxSkipToSpeedCommand+1, _commands.get(idx));
324            // Note: command indexes biased from 0 to 1 to match Warrant display of commands, which are 1-based.
325    }
326
327    /**
328     * Cannot set _runOnET to true until current NOOP command completes
329     * so there is the intermediate flag _setRunOnET
330     * @param set true to run on elapsed time calculations only, false to
331     *            consider other inputs
332     */
333    protected void setRunOnET(boolean set) {
334        if (log.isDebugEnabled() && _setRunOnET != set) {
335            log.debug("{}: setRunOnET {} command #{}", _warrant.getDisplayName(),
336                    set, _idxCurrentCommand+1);
337            // Note: command indexes biased from 0 to 1 to match Warrant display of commands.
338        }
339        _setRunOnET = set;
340        if (!set) { // OK to be set false immediately
341            _runOnET = false;
342        }
343    }
344
345    protected boolean getRunOnET() {
346        return _setRunOnET;
347    }
348
349    protected OBlock getSynchBlock() {
350        return _synchBlock;
351    }
352    /**
353     * Called by the warrant when a the block ahead of a moving train goes occupied.
354     * typically when this thread is on a timed wait. The call will free the wait.
355     * @param block going active.
356     */
357    protected void clearWaitForSync(OBlock block) {
358        // block went active. if waiting on sync, clear it
359        if (_synchBlock != null) {
360            synchronized (_synchLockObject) {
361                if (block.equals(_synchBlock)) {
362                    _synchLockObject.notifyAll();
363                    if (log.isDebugEnabled()) {
364                        log.debug("{}: clearWaitForSync from block \"{}\". notifyAll() called.  isRamping()={}",
365                                _warrant.getDisplayName(), block.getDisplayName(), isRamping());
366                    }
367                    return;
368                }
369            }
370        }
371        if (log.isDebugEnabled()) {
372            log.debug("{}: clearWaitForSync from block \"{}\". _synchBlock= {} SpeedState={} _atClear={} _atHalt={}",
373                    _warrant.getDisplayName(), block.getDisplayName(),
374                    (_synchBlock==null?"null":_synchBlock.getDisplayName()), getSpeedState(), _atClear, _atHalt);
375        }
376    }
377
378    private static void setFrameStatusText(String m, Color c, boolean save) {
379        ThreadingUtil.runOnGUIEventually(()-> WarrantTableFrame.getDefault().setStatusText(m, c, true));
380    }
381
382    /**
383     * Occupancy of blocks, user halts and aspects of Portal signals will modify
384     * normal scripted train speeds.
385     * Ramp speed change for smooth prototypical look.
386     *
387     * @param endSpeedType signal aspect speed name
388     * @param endBlockIdx BlockOrder index of the block where ramp is to end.
389     *        -1 if an end block is not specified.
390     */
391    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
392    protected synchronized void rampSpeedTo(@Nonnull String endSpeedType, int endBlockIdx) {
393        float speed = _speedUtil.modifySpeed(_normalSpeed, endSpeedType);
394        if (log.isDebugEnabled()) {
395            log.debug("{}: rampSpeedTo: type= {}, throttle from {} to {}.",
396                _warrant.getDisplayName(), endSpeedType, getSpeedSetting(),
397                speed);
398        }
399        _speedUtil.speedChange(-1); // Notify not to measure speed for speedProfile
400        if (endSpeedType.equals(Warrant.EStop)) {
401            setStop(true);
402            return;
403        }
404        if (endSpeedType.equals(Warrant.Stop) && getSpeedSetting() <= 0) {
405            setStop(false);
406            return; // already stopped, do nothing
407        }
408        if (_isRamping) {
409            if (endSpeedType.equals(_ramp._endSpeedType)) {
410                return; // already ramping to speedType
411            }
412        } else if (speed == getSpeedSetting()){
413            // to be sure flags and notification is done
414            rampDone(false, endSpeedType, endBlockIdx);
415            return; // already at speedType speed
416        }
417        if (_ramp == null) {
418            _ramp = new ThrottleRamp();
419            _ramp.start();
420        } else if (_isRamping) {
421            // for repeated command already ramping
422            if (_ramp.duplicate(endSpeedType, endBlockIdx)) {
423                return;
424            }
425            // stop the ramp and replace it
426            _holdRamp = true;
427            _ramp.quit(false);
428        }
429        long time = 0;
430        int pause = 2 *_speedUtil.getRampTimeIncrement();
431        do {
432            // may need a bit of time for quit() or start() to get ready
433            try {
434                wait(40);
435                time += 40;
436                _ramp.quit(false);
437            }
438            catch (InterruptedException ie) { // ignore
439            }
440        } while (time <= pause && _isRamping);
441
442        if (!_isRamping) {
443            if (Warrant._trace || log.isDebugEnabled()) {
444                log.info(Bundle.getMessage("RampStart", _warrant.getTrainName(),
445                        endSpeedType, _warrant.getCurrentBlockName()));
446            }
447            _ramp.setParameters(endSpeedType, endBlockIdx);
448            synchronized (_rampLockObject) {
449                _ramp._rampDown = (endBlockIdx >= 0) || endSpeedType.equals(Warrant.Stop);
450//                setIsRamping(true);
451                _holdRamp = false;
452                setWaitforClear(true);
453                _rampLockObject.notifyAll(); // free wait at ThrottleRamp.run()
454                log.debug("{}: rampSpeedTo calls notify _rampLockObject", _warrant.getDisplayName());
455            }
456        } else {
457            log.error("Can't launch ramp for speed {}! _ramp Thread.State= {}. Waited {}ms",
458                    endSpeedType, _ramp.getState(), time);
459            _warrant.debugInfo();
460            setSpeedToType(endSpeedType);
461            _ramp.quit(true);
462            _ramp.interrupt();
463            _ramp = null;
464        }
465    }
466
467    protected boolean isRamping() {
468        return _isRamping;
469    }
470    private void setIsRamping(boolean set) {
471        _isRamping = set;
472    }
473
474    /**
475     * Get the Speed type name. _speedType is the type when moving. Used to restore
476     * speeds aspects of signals when halts or other conditions have stopped the train.
477     * If 'absolute' is true return the absolute speed of the train, i.e. 'Stop' if
478     * train is not moving.
479     * @param absolute  which speed type, absolute or allowed movement
480     * @return speed type
481     */
482    protected String getSpeedType(boolean absolute) {
483        if (absolute) {
484            if (isRamping()) {   // return pending type
485                return _ramp._endSpeedType;
486            }
487            if (_waitForClear || _halt) {
488                return Warrant.Stop;
489            }
490        }
491        return _speedType;
492    }
493
494    /*
495     * warrant.cancelDelayRamp()  called for immediate Stop commands
496     * When die==true for ending the warrant run.
497     */
498    synchronized protected boolean cancelRamp(boolean die) {
499        // _ramp.quit sets "stop" and notifies "waits"
500        if (_ramp != null) {
501            if (die) {
502                _ramp.quit(true);
503                _ramp.interrupt();
504            } else {
505                if(_isRamping) {
506                    _ramp.quit(false);
507                    return true;
508                }
509            }
510        }
511        return false;
512    }
513
514    /**
515     * do throttle setting
516     * @param speed throttle setting about to be set. Modified to sType if from script.
517     * UnModified if from ThrottleRamp or stop speeds.
518     */
519     protected void setSpeed(float speed) {
520        _throttle.setSpeedSetting(speed);
521        // Late update to GUI is OK, this is just an informational status display
522        if (!_abort) {
523            _warrant.fireRunStatus("SpeedChange", null, null);
524        }
525        if (log.isDebugEnabled())
526            log.debug("{}: _throttle.setSpeedSetting({}) called, ({}).",
527                    _warrant.getDisplayName(), speed, _speedType);
528    }
529
530    protected float getSpeedSetting() {
531        float speed = _throttle.getSpeedSetting();
532        if (speed < 0.0f) {
533            _throttle.setSpeedSetting(0.0f);
534            speed = _throttle.getSpeedSetting();
535        }
536        return speed;
537    }
538
539    protected float getScriptSpeed() {
540        return _normalSpeed;
541    }
542
543    /**
544     * Utility for unscripted speed changes.
545     * Records current type and sets time ratio.
546     * @param speedType name of speed change type
547     */
548    private void setSpeedRatio(String speedType) {
549        if (speedType.equals(Warrant.Normal)) {
550            _timeRatio = 1.0f;
551        } else if (_normalSpeed > 0.0f) {
552            float speedMod = _speedUtil.modifySpeed(_normalSpeed, _speedType);
553            if (_normalSpeed > speedMod) {
554                float trackSpeed = _speedUtil.getTrackSpeed(speedMod);
555                _timeRatio = _speedUtil.getTrackSpeed(_normalSpeed) / trackSpeed;
556            } else {
557                _timeRatio = 1.0f;
558            }
559        } else {
560            _timeRatio = 1.0f;
561        }
562    }
563
564    /*
565     * Do immediate speed change.
566     */
567    protected synchronized void setSpeedToType(String speedType) {
568        float speed = getSpeedSetting();
569        if (log.isDebugEnabled())  {
570            log.debug("{}: setSpeedToType({}) speed={} scriptSpeed={}", _warrant.getDisplayName(), speedType, speed, _normalSpeed);
571        }
572        if (speedType.equals(Warrant.Stop)) {
573            setStop(false);
574            advanceToCommandIndex(_idxCurrentCommand + 1);  // skip current command
575        } else if (speedType.equals(Warrant.EStop)) {
576            setStop(true);
577            advanceToCommandIndex(_idxCurrentCommand + 1);  // skip current command
578        } else if (speedType.equals(getSpeedType(true))) {
579            return;
580        } else {
581            _speedType = speedType;     // set speedType regardless
582            setSpeedRatio(speedType);
583            float speedMod = _speedUtil.modifySpeed(_normalSpeed, _speedType);
584            _speedUtil.speedChange(speedMod);  // call before this setting to compute travel of last setting
585            setSpeed(speedMod);
586        }
587    }
588
589    /**
590     * Command to stop (or resume speed) of train from Warrant.controlRunTrain()
591     * of user's override of throttle script.  Also from error conditions
592     * such as losing detection of train's location.
593     * @param halt true if train should halt
594     */
595    protected synchronized void setHalt(boolean halt) {
596        if (log.isDebugEnabled())
597            log.debug("{}: setHalt({}): _atHalt= {}, _waitForClear= {}",
598                  _warrant.getDisplayName(), halt, _atHalt, _waitForClear);
599        if (!halt) {    // resume normal running
600            _halt = false;
601            if (!_atClear) {
602                log.debug("setHalt calls notify()");
603                notifyAll();   // free wait at _atHalt
604            }
605        } else {
606            _halt = true;
607        }
608    }
609
610    private long getTimeToNextCommand() {
611        if (_commandTime > 0) {
612            // millisecs already moving on pending command's time.
613            long elapsedTime = System.currentTimeMillis() - _commandTime;
614            return Math.max(0, (_currentCommand.getTime() - elapsedTime));
615        }
616        return 0;
617    }
618
619    /**
620     * Command to stop or smoothly resume speed. Stop due to
621     * signal or occupation stopping condition ahead.  Caller
622     * follows with call for type of stop to make.
623     * Track condition override of throttle script.
624     * @param wait true if train should stop
625     */
626    protected void setWaitforClear(boolean wait) {
627        if (log.isDebugEnabled())
628            log.debug("{}: setWaitforClear({}): _atClear= {}, throttle speed= {}, _halt= {}",
629                   _warrant.getDisplayName(), wait, _atClear,  getSpeedSetting(), _halt);
630        if (!wait) {    // resume normal running
631            synchronized (_clearLockObject) {
632                log.debug("setWaitforClear calls notify");
633                _waitForClear = false;
634                _clearLockObject.notifyAll();   // free wait at _atClear
635            }
636        } else {
637            _waitForClear = true;
638        }
639    }
640
641    String debugInfo() {
642        StringBuffer info = new StringBuffer("\n");
643        info.append(getName()); info.append(" on warrant= "); info.append(_warrant.getDisplayName());
644        info.append("\nThread.State= "); info.append(getState());
645        info.append(", isAlive= "); info.append(isAlive());
646        info.append(", isInterrupted= "); info.append(isInterrupted());
647        info.append("\n\tThrottle setting= "); info.append(getSpeedSetting());
648        info.append(", scriptSpeed= "); info.append(getScriptSpeed());
649        info.append(". runstate= "); info.append(Warrant.RUN_STATE[getRunState()]);
650        int cmdIdx = getCurrentCommandIndex();
651
652        if (cmdIdx < _commands.size()) {
653            info.append("\n\tCommand #"); info.append(cmdIdx + 1);
654            info.append(": "); info.append(_commands.get(cmdIdx).toString());
655        } else {
656            info.append("\n\t\tAt last command.");
657        }
658        // Note: command indexes biased from 0 to 1 to match Warrant's 1-based display of commands.
659        info.append("\n\tEngineer flags: _waitForClear= "); info.append(_waitForClear);
660        info.append(", _atclear= "); info.append(_atClear);
661        info.append(", _halt= "); info.append(_halt);
662        info.append(", _atHalt= "); info.append(_atHalt);
663        if (_synchBlock != null) {
664            info.append("\n\t\tWaiting for Sync at \"");info.append(_synchBlock.getDisplayName());
665        }
666        info.append("\"\n\t\t_setRunOnET= "); info.append(_setRunOnET);
667        info.append(", _runOnET= "); info.append(_runOnET);
668        info.append("\n\t\t_stopPending= "); info.append(_stopPending);
669        info.append(", _abort= "); info.append(_abort);
670        info.append("\n\t_speedType= \""); info.append(_speedType); info.append("\" SpeedState= ");
671        info.append(getSpeedState().toString()); info.append("\n\tStack trace:");
672        for (StackTraceElement elem : getStackTrace()) {
673            info.append("\n\t\t");
674            info.append(elem.getClassName()); info.append("."); info.append(elem.getMethodName());
675            info.append(", line "); info.append(elem.getLineNumber());
676        }
677        if (_ramp != null) {
678            info.append("\n\tRamp Thread.State= "); info.append(_ramp.getState());
679            info.append(", isAlive= "); info.append(_ramp.isAlive());
680            info.append(", isInterrupted= "); info.append(_ramp.isInterrupted());
681            info.append("\n\tRamp flags: _isRamping= "); info.append(_isRamping);
682            info.append(", stop= "); info.append(_ramp.stop);
683            info.append(", _die= "); info.append(_ramp._die);
684            info.append("\n\tRamp Type: "); info.append(_ramp._rampDown ? "DOWN" : "UP");info.append(" ramp");
685            info.append("\n\t\tEndSpeedType= \""); info.append(_ramp._endSpeedType);
686            int endIdx = _ramp.getEndBlockIndex();
687            info.append("\"\n\t\tEndBlockIdx= "); info.append(endIdx);
688            if (endIdx >= 0) {
689                info.append(" EndBlock= \"");
690                info.append(_warrant.getBlockAt(endIdx).getDisplayName());
691            }
692            info.append("\""); info.append("\n\tStack trace:");
693            for (StackTraceElement elem : _ramp.getStackTrace()) {
694                info.append("\n\t\t");
695                info.append(elem.getClassName()); info.append("."); info.append(elem.getMethodName());
696                info.append(", line "); info.append(elem.getLineNumber());
697            }
698        } else {
699            info.append("\n\tNo ramp.");
700        }
701        return info.toString();
702    }
703
704    /**
705     * Immediate stop command from Warrant.controlRunTrain()-user
706     * or from Warrant.goingInactive()-train lost
707     * or from setMovement()-overrun, possible collision risk.
708     * Do not ramp.
709     * @param eStop true for emergency stop
710     */
711    private synchronized void setStop(boolean eStop) {
712        float speed = _throttle.getSpeedSetting();
713        if (speed <= 0.0f && (_waitForClear || _halt)) {
714            return;
715        }
716        cancelRamp(false);
717        if (eStop) {
718            setHalt(true);
719            setSpeed(-0.1f);
720            setSpeed(0.0f);
721        } else {
722            setSpeed(0.0f);
723            setWaitforClear(true);
724        }
725        if (log.isDebugEnabled())
726            log.debug("{}: setStop({}) from speed={} scriptSpeed={}", _warrant.getDisplayName(), eStop, speed, _normalSpeed);
727    }
728
729    protected Warrant.SpeedState getSpeedState() {
730        if (isRamping()) {
731            if (_ramp._rampDown) {
732                return Warrant.SpeedState.RAMPING_DOWN;
733            } else {
734                return Warrant.SpeedState.RAMPING_UP;
735            }
736        }
737        return Warrant.SpeedState.STEADY_SPEED;
738    }
739
740    protected int getRunState() {
741        if (_stopPending) {
742            if (_halt) {
743                return Warrant.RAMP_HALT;
744            }
745            return Warrant.STOP_PENDING;
746        } else if (_halt) {
747            return Warrant.HALT;
748        } else if (_waitForClear) {
749            return Warrant.WAIT_FOR_CLEAR;
750        } else if (_waitForSensor) {
751            return Warrant.WAIT_FOR_SENSOR;
752        } else if (_abort) {
753            return Warrant.ABORT;
754        } else if (_synchBlock != null) {
755            return Warrant.WAIT_FOR_TRAIN;
756        } else if (isRamping()) {
757            return Warrant.SPEED_RESTRICTED;
758        }
759        return Warrant.RUNNING;
760    }
761
762    @SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification="quit is called another thread to clear all ramp waits")
763    public void stopRun(boolean abort, boolean turnOffFunctions) {
764        if (abort) {
765            _abort =true;
766        }
767
768        synchronized (_synchLockObject) {
769            _synchLockObject.notifyAll();
770        }
771        synchronized (_clearLockObject) {
772            _clearLockObject.notifyAll();
773        }
774        synchronized (this) {
775            notifyAll();
776        }
777
778        cancelRamp(true);
779        if (_waitSensor != null) {
780            _waitSensor.removePropertyChangeListener(this);
781        }
782
783        if (_throttle != null) {
784            if (_throttle.getSpeedSetting() > 0.0f) {
785                if (abort) {
786                    _throttle.setSpeedSetting(-1.0f);
787                }
788                setSpeed(0.0f);
789                if (turnOffFunctions) {
790                    _throttle.setFunction(0, false);
791                    _throttle.setFunction(1, false);
792                    _throttle.setFunction(2, false);
793                    _throttle.setFunction(3, false);
794                }
795            }
796            _warrant.releaseThrottle(_throttle);
797        }
798    }
799
800    private void setForward(ValueType type) {
801        if (type == ValueType.VAL_TRUE) {
802            _throttle.setIsForward(true);
803        } else if (type == ValueType.VAL_FALSE) {
804            _throttle.setIsForward(false);
805        } else {
806            throw new java.lang.IllegalArgumentException("setForward type " + type + " wrong");
807        }
808    }
809
810    private void setFunction(int cmdNum, ValueType type) {
811        if ( cmdNum < 0 || cmdNum > 28 ) {
812            throw new java.lang.IllegalArgumentException("setFunction " + cmdNum + " out of range");
813        }
814        if (type == ValueType.VAL_ON) {
815            _throttle.setFunction(cmdNum, true);
816        } else if (type == ValueType.VAL_OFF) {
817            _throttle.setFunction(cmdNum,false);
818        } else {
819            throw new java.lang.IllegalArgumentException("setFunction type " + type + " wrong");
820        }
821    }
822
823    private void setFunctionMomentary(int cmdNum, ValueType type) {
824        if ( cmdNum < 0 || cmdNum > 28 ) {
825            log.error("Function value {} out of range",cmdNum);
826            throw new java.lang.IllegalArgumentException("setFunctionMomentary " + cmdNum + " out of range");
827        }
828        if (type == ValueType.VAL_ON) {
829            _throttle.setFunctionMomentary(cmdNum, true);
830        } else if (type == ValueType.VAL_OFF) {
831            _throttle.setFunctionMomentary(cmdNum,false);
832        } else {
833            throw new java.lang.IllegalArgumentException("setFunctionMomentary type " + type + " wrong");
834        }
835    }
836
837    /**
838     * Set Sensor state
839     */
840    private void setSensor(NamedBeanHandle<?> handle, CommandValue cmdVal) {
841        NamedBean bean = handle.getBean();
842        if (!(bean instanceof Sensor)) {
843            log.error("setSensor: {} not a Sensor!", bean );
844            return;
845        }
846        jmri.Sensor s = (Sensor)bean;
847        ValueType type = cmdVal.getType();
848        try {
849            if (Warrant._trace || log.isDebugEnabled()) {
850                log.info("{} : Set Sensor", Bundle.getMessage("setSensor",
851                            _warrant.getTrainName(), s.getDisplayName(), type.toString()));
852            }
853            _warrant.fireRunStatus("SensorSetCommand", type.toString(), s.getDisplayName());
854            if (type == ValueType.VAL_ACTIVE) {
855                s.setKnownState(jmri.Sensor.ACTIVE);
856            } else if (type == ValueType.VAL_INACTIVE) {
857                s.setKnownState(jmri.Sensor.INACTIVE);
858            } else {
859                throw new java.lang.IllegalArgumentException("setSensor type " + type + " wrong");
860            }
861        } catch (jmri.JmriException e) {
862            log.warn("Exception setting sensor {} in action", handle.toString());
863        }
864    }
865
866    /**
867     * Wait for Sensor state event
868     */
869    private void waitForSensor(NamedBeanHandle<?> handle, CommandValue cmdVal) {
870        if (_waitSensor != null) {
871            _waitSensor.removePropertyChangeListener(this);
872        }
873        NamedBean bean = handle.getBean();
874        if (!(bean instanceof Sensor)) {
875            log.error("setSensor: {} not a Sensor!", bean );
876            return;
877        }
878        _waitSensor = (Sensor)bean;
879        ThrottleSetting.ValueType type = cmdVal.getType();
880        if (type == ValueType.VAL_ACTIVE) {
881            _sensorWaitState = Sensor.ACTIVE;
882        } else if (type == ValueType.VAL_INACTIVE) {
883            _sensorWaitState = Sensor.INACTIVE;
884        } else {
885            throw new java.lang.IllegalArgumentException("waitForSensor type " + type + " wrong");
886        }
887        int state = _waitSensor.getKnownState();
888        if (state == _sensorWaitState) {
889            log.info("Engineer: state of event sensor {} already at state {}", _waitSensor.getDisplayName(), type.toString());
890            return;
891        }
892        _waitSensor.addPropertyChangeListener(this);
893        if (log.isDebugEnabled())
894            log.debug("Listen for propertyChange of {}, wait for State= {}", _waitSensor.getDisplayName(), _sensorWaitState);
895        // suspend commands until sensor changes state
896        synchronized (this) {   // DO NOT USE _waitForSensor for synch
897            _waitForSensor = true;
898            while (_waitForSensor) {
899                try {
900                    if (Warrant._trace || log.isDebugEnabled()) {
901                        log.info("{} : waitSensor", Bundle.getMessage("waitSensor",
902                            _warrant.getTrainName(), _waitSensor.getDisplayName(), type.toString()));
903                    }
904                    _warrant.fireRunStatus("SensorWaitCommand", type.toString(), _waitSensor.getDisplayName());
905                    wait();
906                    if (!_abort ) {
907                        String name =  _waitSensor.getDisplayName();    // save name, _waitSensor will be null 'eventually'
908                        if (Warrant._trace || log.isDebugEnabled()) {
909                            log.info("{} : wait Sensor Change", Bundle.getMessage("waitSensorChange",
910                                    _warrant.getTrainName(), name));
911                        }
912                        _warrant.fireRunStatus("SensorWaitCommand", null, name);
913                    }
914                } catch (InterruptedException ie) {
915                    log.error("Engineer interrupted at waitForSensor \"{}\"", _waitSensor.getDisplayName(), ie);
916                    _warrant.debugInfo();
917                    Thread.currentThread().interrupt();
918                } finally {
919                    clearSensor();
920                }
921            }
922        }
923    }
924
925    private void clearSensor() {
926        if (_waitSensor != null) {
927            _waitSensor.removePropertyChangeListener(this);
928        }
929        _sensorWaitState = 0;
930        _waitForSensor = false;
931        _waitSensor = null;
932    }
933
934    protected Sensor getWaitSensor() {
935        return _waitSensor;
936    }
937
938    @Override
939    @SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification="Sensor change on another thread is expected even when Engineer (this) has not done any modifing")
940    public void propertyChange(java.beans.PropertyChangeEvent evt) {
941        if (log.isDebugEnabled())
942            log.debug("propertyChange {} new value= {}", evt.getPropertyName(), evt.getNewValue());
943        if ((evt.getPropertyName().equals("KnownState")
944                && ((Number) evt.getNewValue()).intValue() == _sensorWaitState)) {
945            synchronized (this) {
946                    notifyAll();  // free sensor wait
947            }
948        }
949    }
950
951    private void runWarrant(NamedBeanHandle<?> handle, CommandValue cmdVal) {
952        NamedBean bean = handle.getBean();
953        if (!(bean instanceof Warrant)) {
954            log.error("runWarrant: {} not a warrant!", bean );
955            return;
956        }
957        Warrant warrant =  (Warrant)bean;
958
959        int num = Math.round(cmdVal.getFloat());    // repeated loops
960        if (num == 0) {
961            return;
962        }
963        if (num > 0) { // do the countdown for all linked warrants.
964            num--;  // decrement loop count
965            cmdVal.setFloat(num);
966        }
967
968        if (_warrant.getSpeedUtil().getDccAddress().equals(warrant.getSpeedUtil().getDccAddress())) {
969            // Same loco, perhaps different warrant
970            if (log.isDebugEnabled()) {
971                log.debug("Loco address {} finishes warrant {} and starts warrant {}",
972                        warrant.getSpeedUtil().getDccAddress(), _warrant.getDisplayName(), warrant.getDisplayName());
973            }
974            long time =  0;
975            for (int i = _idxCurrentCommand+1; i < _commands.size(); i++) {
976                ThrottleSetting cmd = _commands.get(i);
977                time += cmd.getTime();
978            }
979            // same address so this warrant (_warrant) must release the throttle before (warrant) can acquire it
980            _checker = new CheckForTermination(_warrant, warrant, num, time);
981            _checker.start();
982            log.debug("Exit runWarrant");
983            return;
984        } else {
985            java.awt.Color color = java.awt.Color.red;
986            String msg = WarrantTableFrame.getDefault().runTrain(warrant, Warrant.MODE_RUN);
987            if (msg == null) {
988                msg = Bundle.getMessage("linkedLaunch",
989                        warrant.getDisplayName(), _warrant.getDisplayName(),
990                        warrant.getfirstOrder().getBlock().getDisplayName(),
991                        _warrant.getfirstOrder().getBlock().getDisplayName());
992                color = WarrantTableModel.myGreen;
993            }
994            if (Warrant._trace || log.isDebugEnabled()) {
995                log.info("{} : Warrant Status", msg);
996            }
997            Engineer.setFrameStatusText(msg, color, true);
998        }
999    }
1000
1001    // send the messages on success of linked launch completion
1002    private void checkerDone(Warrant oldWarrant, Warrant newWarrant) {
1003        OBlock endBlock = oldWarrant.getLastOrder().getBlock();
1004        if (oldWarrant.getRunMode() != Warrant.MODE_NONE) {
1005            log.error("{} : Cannot Launch", Bundle.getMessage("cannotLaunch",
1006                    newWarrant.getDisplayName(), oldWarrant.getDisplayName(), endBlock.getDisplayName()));
1007            return;
1008        }
1009
1010        String msg = WarrantTableFrame.getDefault().runTrain(newWarrant, Warrant.MODE_RUN);
1011        java.awt.Color color = java.awt.Color.red;
1012        if (msg == null) {
1013            CommandValue cmdVal = _currentCommand.getValue();
1014            int num = Math.round(cmdVal.getFloat());
1015            if (oldWarrant.equals(newWarrant)) {
1016                msg = Bundle.getMessage("reLaunch", oldWarrant.getDisplayName(), (num<0 ? "unlimited" : num));
1017            } else {
1018                msg = Bundle.getMessage("linkedLaunch",
1019                        newWarrant.getDisplayName(), oldWarrant.getDisplayName(),
1020                        newWarrant.getfirstOrder().getBlock().getDisplayName(),
1021                        endBlock.getDisplayName());
1022            }
1023            color = WarrantTableModel.myGreen;
1024        }
1025        if (Warrant._trace || log.isDebugEnabled()) {
1026            log.info("{} : Launch", msg);
1027        }
1028        Engineer.setFrameStatusText(msg, color, true);
1029        _checker = null;
1030    }
1031
1032    private class CheckForTermination extends Thread {
1033        Warrant oldWarrant;
1034        Warrant newWarrant;
1035        long waitTime; // time to finish remaining commands
1036
1037        CheckForTermination(Warrant oldWar, Warrant newWar, int num, long limit) {
1038            oldWarrant = oldWar;
1039            newWarrant = newWar;
1040            waitTime = limit;
1041            if (log.isDebugEnabled()) log.debug("checkForTermination of \"{}\", before launching \"{}\". waitTime= {})",
1042                    oldWarrant.getDisplayName(), newWarrant.getDisplayName(), waitTime);
1043         }
1044
1045        @Override
1046        public void run() {
1047            long time = 0;
1048            synchronized (this) {
1049                while (time <= waitTime || oldWarrant.getRunMode() != Warrant.MODE_NONE) {
1050                    try {
1051                        wait(100);
1052                        time += 100;
1053                    } catch (InterruptedException ie) {
1054                        log.error("Engineer interrupted at CheckForTermination of \"{}\"", oldWarrant.getDisplayName(), ie);
1055                        _warrant.debugInfo();
1056                        Thread.currentThread().interrupt();
1057                        time = waitTime;
1058                    } finally {
1059                    }
1060                }
1061            }
1062            if (time > waitTime || log.isDebugEnabled()) {
1063                log.info("Waited {}ms for warrant \"{}\" to terminate. runMode={}",
1064                        time, oldWarrant.getDisplayName(), oldWarrant.getRunMode());
1065            }
1066            checkerDone(oldWarrant, newWarrant);
1067        }
1068    }
1069
1070    @SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification="rampDone is called by ramp thread to clear Engineer waiting for it to finish")
1071    private void rampDone(boolean stop, String speedType, int endBlockIdx) {
1072        setIsRamping(false);
1073        if (!stop && !speedType.equals(Warrant.Stop)) {
1074            _speedType = speedType;
1075            setSpeedRatio(speedType);
1076            setWaitforClear(false);
1077            setHalt(false);
1078        }
1079        _stopPending = false;
1080        if (!_waitForClear && !_atHalt && !_atClear && !_holdRamp) {
1081            synchronized (this) {
1082                notifyAll();
1083            }
1084            log.debug("{}: rampDone called notify.", _warrant.getDisplayName());
1085            if (_currentCommand != null && _currentCommand.getCommand().equals(Command.NOOP)) {
1086                _idxCurrentCommand--;   // notify advances command.  Repeat wait for entry to next block
1087            }
1088        }
1089        if (log.isDebugEnabled()) {
1090            log.debug("{}: ThrottleRamp {} for speedType \"{}\". Thread.State= {}}", _warrant.getDisplayName(),
1091                    (stop?"stopped":"completed"), speedType, (_ramp != null?_ramp.getState():"_ramp is null!"));
1092        }
1093    }
1094
1095    /*
1096     * *************************************************************************************
1097     */
1098
1099     class ThrottleRamp extends Thread {
1100
1101         private String _endSpeedType;
1102         private int _endBlockIdx = -1;     // index of block where down ramp ends.
1103         private boolean stop = false;      // aborts ramping
1104         private boolean _rampDown = true;
1105         private boolean _die = false;      // kills ramp for good
1106         RampData rampData;
1107
1108         ThrottleRamp() {
1109            setName("Ramp(" + _warrant.getTrainName() +")");
1110            _endBlockIdx = -1;
1111         }
1112
1113         @SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification="quit is called by another thread to clear all ramp waits")
1114         void quit(boolean die) {
1115             stop = true;
1116             synchronized (this) {
1117                 notifyAll();    // free waits at the ramping time intervals
1118             }
1119             if (die) { // once set to true, do not allow resetting to false
1120                 _die = die;    // permanent shutdown, warrant running ending
1121                 synchronized (_rampLockObject) {
1122                     _rampLockObject.notifyAll(); // free wait at ramp run
1123                 }
1124             }
1125             log.debug("{}: ThrottleRamp clears _ramp waits", _warrant.getDisplayName());
1126         }
1127
1128        void setParameters(String endSpeedType, int endBlockIdx) {
1129            _endSpeedType = endSpeedType;
1130            _endBlockIdx = endBlockIdx;
1131            _stopPending = endSpeedType.equals(Warrant.Stop);
1132        }
1133
1134        boolean duplicate(String endSpeedType, int endBlockIdx) {
1135            if (endBlockIdx != _endBlockIdx ||
1136                    !endSpeedType.equals(_endSpeedType)) {
1137                return false;
1138            }
1139            return true;
1140        }
1141
1142        int getEndBlockIndex() {
1143            return _endBlockIdx;
1144        }
1145
1146        /**
1147         * @param blockIdx  index of block order where ramp finishes
1148         * @param cmdIdx   current command index
1149         * @return command index of block where commands should not be executed
1150         */
1151        int getCommandIndexLimit(int blockIdx, int cmdIdx) {
1152            // get next block
1153            int limit = _commands.size();
1154            String curBlkName = _warrant.getCurrentBlockName();
1155            String endBlkName = _warrant.getBlockAt(blockIdx).getDisplayName();
1156            if (!curBlkName.contentEquals(endBlkName)) {
1157                for (int cmd = cmdIdx; cmd < _commands.size(); cmd++) {
1158                    ThrottleSetting ts = _commands.get(cmd);
1159                    if (ts.getBeanDisplayName().equals(endBlkName) ) {
1160                        cmdIdx = cmd;
1161                        break;
1162                    }
1163                }
1164            }
1165            endBlkName = _warrant.getBlockAt(blockIdx+1).getDisplayName();
1166            for (int cmd = cmdIdx; cmd < _commands.size(); cmd++) {
1167                ThrottleSetting ts = _commands.get(cmd);
1168                if (ts.getBeanDisplayName().equals(endBlkName) &&
1169                        ts.getValue().getType().equals(ValueType.VAL_NOOP)) {
1170                    limit = cmd;
1171                    break;
1172                }
1173            }
1174            log.debug("getCommandIndexLimit: in end block {}, limitIdx = {} in block {}",
1175                    curBlkName, limit+1, _warrant.getBlockAt(blockIdx).getDisplayName());
1176            return limit;
1177        }
1178
1179        @Override
1180        @SuppressFBWarnings(value="UW_UNCOND_WAIT", justification="waits may be indefinite until satisfied or thread aborted")
1181        public void run() {
1182            while (!_die) {
1183                setIsRamping(false);
1184                synchronized (_rampLockObject) {
1185                    try {
1186                        _rampLockObject.wait(); // wait until notified by rampSpeedTo() calls quit()
1187                        setIsRamping(true);
1188                    } catch (InterruptedException ie) {
1189                        log.debug("As expected", ie);
1190                    }
1191                }
1192                if (_die) {
1193                    break;
1194                }
1195                stop = false;
1196                doRamp();
1197            }
1198        }
1199
1200//        @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "DLS_DEAD_LOCAL_STORE", justification = "Engineer needs _normalSpeed to be updated")
1201        @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
1202        public void doRamp() {
1203            // At the the time 'right now' is the command indexed by _idxCurrentCommand-1"
1204            // is done. The main thread (Engineer) is waiting to do the _idxCurrentCommand.
1205            // A non-scripted speed change is to begin now.
1206            // If moving, the current speed is _normalSpeed modified by the current _speedType,
1207            // that is, the actual throttle setting.
1208            // If _endBlockIdx >= 0, this indexes the block where the the speed change must be
1209            // completed. the final speed change should occur just before entry into the next
1210            // block. This final speed change must be the exit speed of block '_endBlockIdx'
1211            // modified by _endSpeedType.
1212            // If _endBlockIdx < 0, for down ramps this should be a user initiated stop (Halt)
1213            // the endSpeed should be 0.
1214            // For up ramps, the _endBlockIdx and endSpeed are unknown. The script may have
1215            // speed changes scheduled during the time needed to up ramp. Note the code below
1216            // to negotiate and modify the RampData so that the end speed of the ramp makes a
1217            // smooth transition to the speed of the script (modified by _endSpeedType)
1218            // when the script resumes.
1219            // Non-speed commands are executed at their proper times during ramps.
1220            // Ramp calculations are based on the fact that the distance traveled during the
1221            // ramp is the same as the distance the unmodified script would travel, albeit
1222            // the times of travel are quite different.
1223            // Note on ramp up endSpeed should match scripted speed modified by endSpeedType
1224            float speed = getSpeedSetting();  // current speed setting
1225            float endSpeed;   // requested end speed
1226            int commandIndexLimit;
1227            if (_endBlockIdx >= 0) {
1228                commandIndexLimit = getCommandIndexLimit(_endBlockIdx, _idxCurrentCommand);
1229                endSpeed = _speedUtil.getBlockSpeedInfo(_endBlockIdx).getExitSpeed();
1230                endSpeed = _speedUtil.modifySpeed(endSpeed, _endSpeedType);
1231            } else {
1232                commandIndexLimit = _commands.size();
1233                endSpeed = _speedUtil.modifySpeed(_normalSpeed, _endSpeedType);
1234            }
1235            CommandValue cmdVal = _currentCommand.getValue();
1236            long timeToSpeedCmd = getTimeToNextCommand();
1237            _rampDown = endSpeed <= speed;
1238
1239            if (log.isDebugEnabled()) {
1240                log.debug("RAMP {} \"{}\" speed from {}, to {}, at block \"{}\" at Cmd#{} to Cmd#{}. timeToNextCmd= {}",
1241                       (_rampDown ? "DOWN" : "UP"), _endSpeedType, speed, endSpeed,
1242                       (_endBlockIdx>=0?_warrant.getBlockAt(_endBlockIdx).getDisplayName():"ahead"),
1243                       _idxCurrentCommand+1, commandIndexLimit, timeToSpeedCmd);
1244                       // Note: command indexes biased from 0 to 1 to match Warrant display of commands.
1245            }
1246            float scriptTrackSpeed = _speedUtil.getTrackSpeed(_normalSpeed);
1247
1248            int warBlockIdx = _warrant.getCurrentOrderIndex();  // block of current train position
1249            int cmdBlockIdx = -1;    // block of script commnd's train position
1250            int cmdIdx = _idxCurrentCommand;
1251            while (cmdIdx >= 0) {
1252                ThrottleSetting cmd  = _commands.get(--cmdIdx);
1253                if (cmd.getCommand().hasBlockName()) {
1254                    OBlock blk = (OBlock)cmd.getBean();
1255                    int idx = _warrant.getIndexOfBlockBefore(warBlockIdx, blk);
1256                    if (idx >= 0) {
1257                        cmdBlockIdx = idx;
1258                    } else {
1259                        cmdBlockIdx = _warrant.getIndexOfBlockAfter(blk, warBlockIdx);
1260                    }
1261                    break;
1262                }
1263            }
1264            if (cmdBlockIdx < 0) {
1265                cmdBlockIdx = warBlockIdx;
1266           }
1267
1268            synchronized (this) {
1269                try {
1270                    if (!_rampDown) {
1271                        // Up ramp may advance the train beyond the point where the script is interrupted.
1272                        // The ramp up will take time and the script may have other speed commands while
1273                        // ramping up. So the actual script speed may not match the endSpeed when the ramp
1274                        // up distance is traveled.  We must compare 'endSpeed' to 'scriptSpeed' at each
1275                        // step and skip scriptSpeeds to insure that endSpeed matches scriptSpeed when
1276                        // the ramp ends.
1277                        rampData = _speedUtil.getRampForSpeedChange(speed, 1.0f);
1278                        int timeIncrement = rampData.getRampTimeIncrement();
1279                        ListIterator<Float> iter = rampData.speedIterator(true);
1280                        speed = iter.next().floatValue();   // skip repeat of current speed
1281
1282                        float rampDist = 0;
1283                        float cmdDist = timeToSpeedCmd * scriptTrackSpeed;
1284
1285                        while (!stop && iter.hasNext()) {
1286                            speed = iter.next().floatValue();
1287                            float s = _speedUtil.modifySpeed(_normalSpeed, _endSpeedType);
1288                            if (speed > s) {
1289                                setSpeed(s);
1290                                break;
1291                            }
1292                            setSpeed(speed);
1293
1294                            // during ramp down the script may have non-speed commands that should be executed.
1295                            if (!stop && rampDist >= cmdDist && _idxCurrentCommand < commandIndexLimit) {
1296                                warBlockIdx = _warrant.getCurrentOrderIndex();  // current train position
1297                                if (_currentCommand.getCommand().hasBlockName()) {
1298                                    int idx = _warrant.getIndexOfBlockBefore(warBlockIdx, (OBlock)_currentCommand.getBean());
1299                                    if (idx >= 0) {
1300                                        cmdBlockIdx = idx;
1301                                    }
1302                                }
1303                                if (cmdBlockIdx <= warBlockIdx) {
1304                                    Command cmd = _currentCommand.getCommand();
1305                                    if (cmd.equals(Command.SPEED)) {
1306                                        cmdVal = _currentCommand.getValue();
1307                                        _normalSpeed = cmdVal.getFloat();
1308                                        scriptTrackSpeed = _speedUtil.getTrackSpeed(_normalSpeed);
1309                                        if (log.isDebugEnabled()) {
1310                                            log.debug("Cmd #{} for speed= {} skipped.",
1311                                                    _idxCurrentCommand+1, _normalSpeed);
1312                                        }
1313                                        cmdDist = 0;
1314                                    } else {
1315                                        executeComand(_currentCommand, timeIncrement);
1316                                    }
1317                                    if (_idxCurrentCommand < _commands.size() - 1) {
1318                                        _currentCommand = _commands.get(++_idxCurrentCommand);
1319                                        cmdDist = scriptTrackSpeed * _currentCommand.getTime();
1320                                    } else {
1321                                        cmdDist = 0;
1322                                    }
1323                                    rampDist = 0;
1324                                    advanceToCommandIndex(_idxCurrentCommand); // skip up to this command
1325                                }   // else Do not advance script commands of block ahead of train position
1326                            }
1327
1328                            try {
1329                                wait(timeIncrement);
1330                            } catch (InterruptedException ie) {
1331                                stop = true;
1332                            }
1333
1334                            rampDist += _speedUtil.getDistanceTraveled(speed, _speedType, timeIncrement);
1335                       }
1336
1337                    } else {     // decreasing, ramp down to a modified speed
1338                        // Down ramp may advance the train beyond the point where the script is interrupted.
1339                        // Any down ramp requested with _endBlockIdx >= 0 is expected to end at the end of
1340                        // a block i.e. the block of BlockOrder indexed by _endBlockIdx.
1341                        // Therefore script should resume at the exit to this block.
1342                        // During ramp down the script may have other Non speed commands that should be executed.
1343                        _warrant.downRampBegun(_endBlockIdx);
1344
1345                        rampData = _speedUtil.getRampForSpeedChange(speed, endSpeed);
1346                        int timeIncrement = rampData.getRampTimeIncrement();
1347                        ListIterator<Float> iter = rampData.speedIterator(false);
1348                        speed = iter.previous().floatValue();   // skip repeat of current throttle setting
1349
1350                        float rampDist = 0;
1351                        float cmdDist = timeToSpeedCmd * scriptTrackSpeed;
1352
1353                        while (!stop && iter.hasPrevious()) {
1354                            speed = iter.previous().floatValue();
1355                            setSpeed(speed);
1356
1357                            if (_endBlockIdx >= 0) {    // correction code for ramps that are too long or too short
1358                                int curIdx = _warrant.getCurrentOrderIndex();
1359                                if (curIdx > _endBlockIdx) {
1360                                    // loco overran end block.  Set end speed and leave ramp
1361                                    setSpeed(endSpeed);
1362                                    stop = true;
1363                                    log.warn("\"{}\" Ramp to speed \"{}\" ended due to overrun into block \"{}\". throttle {} set to {}.\"{}\"",
1364                                            _warrant.getTrainName(), _endSpeedType, _warrant.getBlockAt(curIdx).getDisplayName(),
1365                                            speed, endSpeed, _warrant.getDisplayName());
1366                                } else if ( curIdx < _endBlockIdx &&
1367                                        _endSpeedType.equals(Warrant.Stop) && Math.abs(speed - endSpeed) <.001f) {
1368                                    // At last speed change to set throttle was endSpeed, but train has not
1369                                    // reached the last block. Let loco creep to end block at current setting.
1370                                    if (log.isDebugEnabled())
1371                                        log.debug("Extending ramp to reach block {}. speed= {}",
1372                                                _warrant.getBlockAt(_endBlockIdx).getDisplayName(), speed);
1373                                    int waittime = 0;
1374                                    float throttleIncrement = _speedUtil.getRampThrottleIncrement();
1375                                    while (_endBlockIdx > _warrant.getCurrentOrderIndex() && waittime <= 60*timeIncrement && getSpeedSetting() > 0) {
1376                                        // Until loco reaches end block, continue current speed.
1377                                        if (waittime == 5*timeIncrement || waittime == 10*timeIncrement ||
1378                                                waittime == 15*timeIncrement || waittime == 20*timeIncrement) {
1379                                            // maybe train stalled on previous speed step. Bump speed up a notch at 3s, another at 9
1380                                            setSpeed(getSpeedSetting() + throttleIncrement);
1381                                        }
1382                                        try {
1383                                            wait(timeIncrement);
1384                                            waittime += timeIncrement;
1385                                        } catch (InterruptedException ie) {
1386                                            stop = true;
1387                                        }
1388                                    }
1389                                    try {
1390                                        wait(timeIncrement);
1391                                    } catch (InterruptedException ie) {
1392                                        stop = true;
1393                                    }
1394                                }
1395                            }
1396
1397                            // during ramp down the script may have non-speed commands that should be executed.
1398                            if (!stop && rampDist >= cmdDist && _idxCurrentCommand < commandIndexLimit) {
1399                                warBlockIdx = _warrant.getCurrentOrderIndex();  // current train position
1400                                if (_currentCommand.getCommand().hasBlockName()) {
1401                                    int idx = _warrant.getIndexOfBlockBefore(warBlockIdx, (OBlock)_currentCommand.getBean());
1402                                    if (idx >= 0) {
1403                                        cmdBlockIdx = idx;
1404                                    }
1405                                }
1406                                if (cmdBlockIdx <= warBlockIdx) {
1407                                    Command cmd = _currentCommand.getCommand();
1408                                    if (cmd.equals(Command.SPEED)) {
1409                                        cmdVal = _currentCommand.getValue();
1410                                        _normalSpeed = cmdVal.getFloat();
1411                                        scriptTrackSpeed = _speedUtil.getTrackSpeed(_normalSpeed);
1412                                        if (log.isDebugEnabled()) {
1413                                            log.debug("Cmd #{} for speed= {} skipped.",
1414                                                    _idxCurrentCommand+1, _normalSpeed);
1415                                        }
1416                                        cmdDist = 0;
1417                                    } else {
1418                                        executeComand(_currentCommand, timeIncrement);
1419                                    }
1420                                    if (_idxCurrentCommand < _commands.size() - 1) {
1421                                        _currentCommand = _commands.get(++_idxCurrentCommand);
1422                                        cmdDist = scriptTrackSpeed * _currentCommand.getTime();
1423                                    } else {
1424                                        cmdDist = 0;
1425                                    }
1426                                    rampDist = 0;
1427                                    advanceToCommandIndex(_idxCurrentCommand); // skip up to this command
1428                                }   // else Do not advance script commands of block ahead of train position
1429                            }
1430
1431                            try {
1432                                wait(timeIncrement);
1433                            } catch (InterruptedException ie) {
1434                                stop = true;
1435                            }
1436
1437                            rampDist += _speedUtil.getDistanceTraveled(speed, _speedType, timeIncrement);   // _speedType or Warrant.Normal??
1438                            //rampDist += getTrackSpeed(speed) * timeIncrement;
1439                       }
1440
1441                        // Ramp done, still in endBlock. Execute any remaining non-speed commands.
1442                       if (_endBlockIdx >= 0 && commandIndexLimit < _commands.size()) {
1443                            long cmdStart = System.currentTimeMillis();
1444                            while (_idxCurrentCommand < commandIndexLimit) {
1445                                NamedBean bean = _currentCommand.getBean();
1446                                if (bean instanceof OBlock) {
1447                                    if (_endBlockIdx < _warrant.getIndexOfBlockAfter((OBlock)bean, _endBlockIdx)) {
1448                                        // script is past end point, command should be NOOP.
1449                                        // regardless, don't execute any more commands.
1450                                        break;
1451                                    }
1452                                }
1453                                Command cmd = _currentCommand.getCommand();
1454                                if (cmd.equals(Command.SPEED)) {
1455                                    cmdVal = _currentCommand.getValue();
1456                                    _normalSpeed = cmdVal.getFloat();
1457                                    if (log.isDebugEnabled()) {
1458                                        log.debug("Cmd #{} for speed {} skipped. warrant {}",
1459                                                _idxCurrentCommand+1, _normalSpeed, _warrant.getDisplayName());
1460                                    }
1461                                } else {
1462                                    executeComand(_currentCommand, System.currentTimeMillis() - cmdStart);
1463                                }
1464                                _currentCommand = _commands.get(++_idxCurrentCommand);
1465                                advanceToCommandIndex(_idxCurrentCommand); // skip up to this command
1466                            }
1467                        }
1468                    }
1469
1470                } finally {
1471                    if (log.isDebugEnabled()) {
1472                        log.debug("Ramp Done. End Blk= {}, _idxCurrentCommand={} resumeIdx={}, commandIndexLimit={}. warrant {}",
1473                                (_endBlockIdx>=0?_warrant.getBlockAt(_endBlockIdx).getDisplayName():"not required"),
1474                                _idxCurrentCommand+1, _idxSkipToSpeedCommand, commandIndexLimit, _warrant.getDisplayName());
1475                    }
1476                }
1477            }
1478            rampDone(stop, _endSpeedType, _endBlockIdx);
1479            if (!stop) {
1480                _warrant.fireRunStatus("RampDone", _halt, _endSpeedType);   // normal completion of ramp
1481                if (Warrant._trace || log.isDebugEnabled()) {
1482                    log.info(Bundle.getMessage("RampSpeed", _warrant.getTrainName(),
1483                        _endSpeedType, _warrant.getCurrentBlockName()));
1484                }
1485            } else {
1486                if (Warrant._trace || log.isDebugEnabled()) {
1487                    log.info(Bundle.getMessage("RampSpeed", _warrant.getTrainName(),
1488                            _endSpeedType, _warrant.getCurrentBlockName()) + "-Interrupted!");
1489                }
1490
1491            }
1492            stop = false;
1493
1494            if (_rampDown) {    // check for overrun status last
1495                _warrant.downRampDone(stop, _halt, _endSpeedType, _endBlockIdx);
1496            }
1497        }
1498    }
1499
1500    private static final Logger log = LoggerFactory.getLogger(Engineer.class);
1501}