001package jmri.jmrit.logix;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.util.List;
006import java.util.ListIterator;
007import java.util.concurrent.locks.ReentrantLock;
008import jmri.DccThrottle;
009import jmri.NamedBean;
010import jmri.NamedBeanHandle;
011import jmri.Sensor;
012import jmri.util.ThreadingUtil;
013import jmri.jmrit.logix.ThrottleSetting.Command;
014import jmri.jmrit.logix.ThrottleSetting.CommandValue;
015import jmri.jmrit.logix.ThrottleSetting.ValueType;
016import org.slf4j.Logger;
017import org.slf4j.LoggerFactory;
018
019
020/**
021 * Execute a throttle command script for a warrant.
022 * <p>
023 * This generally operates on its own thread, but calls the warrant
024 * thread via Warrant.fireRunStatus to show status. fireRunStatus uses
025 * ThreadingUtil.runOnLayoutEventually to display on the layout thread.
026 *
027 * @author Pete Cressman Copyright (C) 2009, 2010, 2020
028 */
029/*
030 * ************************ Thread running the train ****************
031 */
032public class Engineer extends Thread implements java.beans.PropertyChangeListener {
033
034    private int _idxCurrentCommand;     // current throttle command
035    private ThrottleSetting _currentCommand;
036    private int _idxSkipToSpeedCommand;   // skip to this index to reset script when ramping
037    private float _normalSpeed = 0;       // current commanded throttle setting (unmodified)
038    private String _speedType = Warrant.Normal;    // current speed name
039    private float _timeRatio = 1.0f;     // ratio to extend scripted time when speed is modified
040    private boolean _abort = false;
041    private boolean _halt = false;  // halt/resume from user's control
042    private boolean _stopPending = false;   // ramp slow down in progress
043    private boolean _resumePending = false;   // ramp up to clear flags in progress
044    private boolean _waitForClear = false;  // waits for signals/occupancy/allocation to clear
045    private boolean _waitForSync = false;  // waits for train to catch up to commands
046    private boolean _waitForSensor = false; // wait for sensor event
047    private boolean _runOnET = false;   // Execute commands on ET only - do not synch
048    private boolean _setRunOnET = false; // Need to delay _runOnET from the block that set it
049    private int _syncIdx;           // block order index of current command
050    protected DccThrottle _throttle;
051    private final Warrant _warrant;
052    private final List<ThrottleSetting> _commands;
053    private Sensor _waitSensor;
054    private int _sensorWaitState;
055    final ReentrantLock _lock = new ReentrantLock(true);    // Ramp uses to block script speeds
056    private Object _rampLockObject = new Object(); // used for synchronizing threads for _ramp
057    private ThrottleRamp _ramp;
058    private boolean _atHalt = false;
059    private boolean _atClear = false;
060    private final SpeedUtil _speedUtil;
061
062    Engineer(Warrant warrant, DccThrottle throttle) {
063        _warrant = warrant;
064        _throttle = throttle;
065        _speedUtil = warrant.getSpeedUtil();
066        _commands = _warrant.getThrottleCommands();
067        _idxCurrentCommand = 0;
068        _idxSkipToSpeedCommand = 0;
069        _syncIdx = -1;
070        _waitForSensor = false;
071        setName("Engineer(" + _warrant.getTrainName() +")");
072    }
073
074    @Override
075    @SuppressFBWarnings(value="UW_UNCOND_WAIT", justification="waits may be indefinite until satisfied or thread aborted")
076    public void run() {
077        if (log.isDebugEnabled()) 
078            log.debug("Engineer started warrant {} _throttle= {}", _warrant.getDisplayName(), _throttle.getClass().getName());
079
080        int cmdBlockIdx = 0;
081        while (_idxCurrentCommand < _commands.size()) {
082            while (_idxSkipToSpeedCommand > _idxCurrentCommand) {
083                if (log.isDebugEnabled()) {
084                    ThrottleSetting ts = _commands.get(_idxCurrentCommand);
085                    log.debug("Skip Cmd #{}: {} Warrant {}", _idxCurrentCommand+1, ts, _warrant.getDisplayName());
086                    // Note: command indexes biased from 0 to 1 to match Warrant display of commands.
087                }
088                _idxCurrentCommand++;
089            }
090            _currentCommand = _commands.get(_idxCurrentCommand);
091            long cmdWaitTime = _currentCommand.getTime();    // time to wait before executing command
092            ThrottleSetting.Command command = _currentCommand.getCommand();
093            _runOnET = _setRunOnET;     // OK to set here
094            if (command.hasBlockName()) {
095                int idx = _warrant.getIndexOfBlock(_currentCommand.getBeanDisplayName(), cmdBlockIdx);
096                if (idx >= 0) {
097                    cmdBlockIdx = idx;
098                }
099            }
100            if (cmdBlockIdx < _warrant.getCurrentOrderIndex() || 
101                    (command.equals(Command.NOOP) && (cmdBlockIdx <= _warrant.getCurrentOrderIndex()))) {
102                // Train advancing too fast, need to process commands more quickly,
103                // allow some time for whistle toots etc.
104                cmdWaitTime = Math.min(cmdWaitTime, 200); // 200ms per command should be enough for toots etc.
105                if (log.isDebugEnabled())
106                    log.debug("Train reached block \"{}\" before script et={}ms . Warrant {}",
107                            _warrant.getCurrentBlockName(), _currentCommand.getTime(), _warrant.getDisplayName());
108            }
109            if (_abort) {
110                break;
111            }
112
113            long cmdStart = System.currentTimeMillis();
114            if (log.isDebugEnabled()) 
115                log.debug("Start Cmd #{} for block \"{}\" currently in \"{}\". wait {}ms to do cmd {}. Warrant {}",
116                    _idxCurrentCommand+1, _currentCommand.getBeanDisplayName(), _warrant.getCurrentBlockName(), 
117                    cmdWaitTime, command.toString(), _warrant.getDisplayName());
118                    // Note: command indexes biased from 0 to 1 to match Warrant display of commands.
119            synchronized (this) {
120                if (!Warrant.Normal.equals(_speedType)) {
121                    cmdWaitTime = (long)(cmdWaitTime*_timeRatio); // extend et when speed has been modified from scripted speed
122                }                
123                try {
124                    if (cmdWaitTime > 0) {
125                        wait(cmdWaitTime);
126                    }
127                    if (_abort) {
128                        break;
129                    }
130                } catch (InterruptedException ie) {
131                    log.error("At time wait {}", ie);
132                    _warrant.debugInfo();
133                    Thread.currentThread().interrupt();
134                } catch (java.lang.IllegalArgumentException iae) {
135                    log.error("At time wait {}", iae);
136                }
137            }
138
139            _syncIdx = cmdBlockIdx;
140            // Having waited, time=ts.getTime(), so blocks should agree.  if not,
141            // wait for train to arrive at block and send sync notification.
142            // note, blind runs cannot detect entrance.
143            if (!_runOnET && _syncIdx > _warrant.getCurrentOrderIndex()) {
144                // commands are ahead of current train position
145                // When the next block goes active or a control command is made, a clear sync call
146                // will test these indexes again and can trigger a notify() to free the wait
147                synchronized (this) {
148                    try {
149                        _waitForSync = true;
150                        if (log.isDebugEnabled()) 
151                            log.debug("Wait for train to enter \"{}\". Warrant {}",
152                                _warrant.getBlockAt(_syncIdx).getDisplayName(), _warrant.getDisplayName());
153                        _warrant.fireRunStatus("WaitForSync", _idxCurrentCommand - 1, _idxCurrentCommand);
154                        wait();
155                    } catch (InterruptedException ie) {
156                        log.error("At _waitForSync {}", ie);
157                        _warrant.debugInfo();
158                        Thread.currentThread().interrupt();
159                    }
160                    finally {
161                        _waitForSync = false;
162                    }
163                }
164            }
165            if (_abort) {
166                break;
167            }
168
169            synchronized (this) {
170                // block position and elapsed time are as expected, but track conditions
171                // such as signals or rogue occupancy requires waiting
172                if (_waitForClear) {
173                    try {
174                        _atClear = true;
175                        if (log.isDebugEnabled()) 
176                            log.debug("Waiting for clearance. _waitForClear= {} _halt= {} \"{}\".  Warrant {}",
177                                _waitForClear, _halt, _warrant.getBlockAt(cmdBlockIdx).getDisplayName(), _warrant.getDisplayName());
178                        wait();
179                    } catch (InterruptedException ie) {
180                        log.error("At _atClear {}", ie);
181                        _warrant.debugInfo();
182                        Thread.currentThread().interrupt();
183                    }
184                    finally {
185                        _waitForClear = false;
186                        _atClear = false;
187                    }
188                }
189            }
190            if (_abort) {
191                break;
192            }
193
194            synchronized (this) {
195                // user's command to halt requires waiting
196                if (_halt) {
197                    try {
198                        _atHalt = true;
199                        if (log.isDebugEnabled()) 
200                            log.debug("Waiting to Resume. _halt= {}, _waitForClear= {}, Block \"{}\".  Warrant {}",
201                                _halt, _waitForClear, _warrant.getBlockAt(cmdBlockIdx).getDisplayName(), _warrant.getDisplayName());
202                        wait();
203                    } catch (InterruptedException ie) {
204                        log.error("At _atHalt {}", ie);
205                        _warrant.debugInfo();
206                        Thread.currentThread().interrupt();
207                    }
208                    finally {
209                        _halt = false;
210                        _atHalt = false;
211                    }
212                }
213            }
214            if (_abort) {
215                break;
216            }
217
218            synchronized (this) {
219                long et;
220                if (_ramp != null && !_ramp.ready) {
221                    int idx = _idxCurrentCommand;
222                    try {
223                        if (log.isDebugEnabled()) 
224                            log.debug("Waiting for ramp to finish at Cmd #{}.  Warrant {}", 
225                                    _idxCurrentCommand+1, _warrant.getDisplayName());
226                        wait();
227                    } catch (InterruptedException ie) {
228                        _warrant.debugInfo();
229                        Thread.currentThread().interrupt();
230                    }
231                    // ramp will decide whether to skip or execute _currentCommand
232                    et = System.currentTimeMillis() - cmdStart;
233                    if (log.isDebugEnabled()) {
234                        log.debug("Cmd #{} held for {}ms. {} warrant {}",
235                                idx+1, et, _currentCommand, _warrant.getDisplayName());
236                    }
237                } else {
238                    executeComand(_currentCommand);
239                    et = System.currentTimeMillis() - cmdStart;
240                    _idxCurrentCommand++;
241                    if (log.isDebugEnabled()) {
242                        log.debug("Cmd #{} done. et={}. {} warrant {}",
243                                _idxCurrentCommand, et, _currentCommand, _warrant.getDisplayName());
244                    }
245                }
246            }
247
248        }
249        // shut down
250        setSpeed(0.0f); // for safety to be sure train stops                               
251        _warrant.stopWarrant(_abort, true);
252    }
253
254    private void executeComand(ThrottleSetting ts) {
255        Command command = ts.getCommand();
256        CommandValue cmdVal = ts.getValue();
257        switch (command) {
258            case SPEED:
259                float throttle = cmdVal.getFloat();
260                _normalSpeed = throttle;
261                float speedMod = _speedUtil.modifySpeed(throttle, _speedType);
262                if (Math.abs(throttle - speedMod) > .0001f) {
263                    _timeRatio = throttle / speedMod;
264                } else {
265                    _timeRatio = 1.0f;
266                }
267                setSpeed(speedMod);                                
268                break;
269            case NOOP:
270                break;
271            case SET_SENSOR:
272                ThreadingUtil.runOnLayoutEventually(() ->
273                    setSensor(ts.getNamedBeanHandle(), cmdVal));
274                break;
275            case FKEY:
276                setFunction(ts.getKeyNum(), cmdVal.getType());
277                break;
278            case FORWARD:
279                setForward(cmdVal.getType());
280                break;
281            case LATCHF:
282                setFunctionMomentary(ts.getKeyNum(), cmdVal.getType());
283                break;
284            case WAIT_SENSOR:
285                waitForSensor(ts.getNamedBeanHandle(), cmdVal);
286                break;
287            case RUN_WARRANT:
288                runWarrant(ts.getNamedBeanHandle(), cmdVal);
289                break;
290            case SPEEDSTEP:
291                break;
292            default:
293        }
294    }
295
296    protected int getCurrentCommandIndex() {
297        return _idxCurrentCommand;
298    }
299
300    /**
301     * Delayed ramp has started.
302     * Currently informational only
303     * Do non-speed commands only until idx is reached?  maybe not.
304     * @param idx index
305     */
306    private void advanceToCommandIndex(int idx) {
307        _idxSkipToSpeedCommand = idx;
308//        if (log.isTraceEnabled()) 
309            log.debug("advanceToCommandIndex to {} - {}", _idxSkipToSpeedCommand+1, _commands.get(idx));
310            // Note: command indexes biased from 0 to 1 to match Warrant display of commands, which are 1-based.
311    }
312
313    /**
314     * Cannot set _runOnET to true until current NOOP command completes
315     * so there is the intermediate flag _setRunOnET
316     * @param set true to run on elapsed time calculations only, false to
317     *            consider other inputs
318     */
319    protected void setRunOnET(boolean set) {
320        if (log.isDebugEnabled() && _setRunOnET != set) {
321            log.debug("setRunOnET {} command #{} warrant {}", set, _idxCurrentCommand+1, _warrant.getDisplayName());
322            // Note: command indexes biased from 0 to 1 to match Warrant display of commands.
323        }
324        _setRunOnET = set;
325        if (!set) { // OK to be set false immediately
326            _runOnET = false;
327        }
328    }
329
330    protected boolean getRunOnET() {
331        return _setRunOnET;
332    }
333
334    /**
335     * If waiting to sync entrance to a block boundary with recorded wait time,
336     * or waiting for clearance ahead for rogue occupancy, stop aspect or
337     * sharing of turnouts, this call will free the wait.
338     */
339    protected synchronized void clearWaitForSync() {
340        if (_waitForSync) {
341            if (log.isDebugEnabled()) 
342                log.debug("_waitForSync={} clearWaitForSync() calls notify()", _waitForSync);
343            notifyAll();   // if wait is cleared, this sets _waitForSync= false
344        } else {
345            ThrottleSetting ts = _commands.get(_idxCurrentCommand);
346            OBlock block = _warrant.getCurrentBlockOrder().getBlock();
347            // block went active. if waiting on cmdWaitTime, clear it
348            if (ts.getCommand().equals(Command.NOOP) && ts.getBeanDisplayName().equals(block.getDisplayName())) {
349                if (log.isDebugEnabled()) 
350                    log.debug("_waitForSync={} clearWaitForSync() calls notify()", _waitForSync);
351                notifyAll();
352            }
353        }
354    }
355
356    /**
357     * Occupancy of blocks, user halts and aspects of Portal signals will modify
358     * normal scripted train speeds.
359     * Ramp speed change for smooth prototypical look.
360     *
361     * @param endSpeedType signal aspect speed name
362     * @param endBlockIdx BlockOrder index of where ramp is to end.
363     * @param useIndex false if endBlockIdx should not be considered 
364     */
365    protected void rampSpeedTo(String endSpeedType, int endBlockIdx, boolean useIndex) {
366        if (!setSpeedRatio(endSpeedType)) {
367            if (!endSpeedType.equals(Warrant.Stop) && !endSpeedType.equals(Warrant.EStop)) {
368                setWaitforClear(false);
369            }
370            return;
371        }
372        synchronized (this) {
373            if (log.isDebugEnabled()) 
374                log.debug("rampSpeedTo type= {}, throttle from {} to {}. warrant {}",
375                    endSpeedType, getSpeedSetting(), 
376                    _speedUtil.modifySpeed(_normalSpeed, endSpeedType), 
377                    _warrant.getDisplayName());
378
379            if (_ramp == null) {
380                _ramp = new ThrottleRamp();
381                _ramp.start();
382            } else if (!_ramp.ready) {
383                // for repeated command
384                if (_ramp.duplicate(endSpeedType, endBlockIdx, useIndex)) {
385                    return;
386                }
387                _ramp.quit(false);
388            }
389            long time = 0;
390            int waitTime = _speedUtil.getRampTimeIncrement() + 20;
391            while (time < waitTime && !_ramp.ready) {
392                // may need a bit of time for quit() or start() to get ready
393                try {
394                    wait(20);
395                    time += 20;
396                }
397                catch (InterruptedException ie) { // ignore
398                }
399            }
400            if (_ramp.ready) {
401                _ramp.setParameters(endSpeedType, endBlockIdx, useIndex);
402                synchronized (_rampLockObject) {
403                    _rampLockObject.notifyAll(); // free wait at ThrottleRamp.run()
404                    log.debug("rampSpeedTo called notify _ramp.ready={}", _ramp.ready);
405                }
406            } else {
407                log.error("Can't launch ramp for speed {}! _ramp Thread.State= {}. Waited {}ms",
408                        endSpeedType, _ramp.getState(), time-20);
409                _warrant.debugInfo();
410            }
411        }
412    }
413
414    protected boolean isRamping() {
415        if (_ramp == null || _ramp.ready) {
416            return false;
417        }
418        return true;
419    }
420
421    private void cancelRamp(boolean die) {
422        if (_ramp != null && !_ramp.ready) {
423            _ramp.quit(die);
424        }
425    }
426
427    /**
428     * do throttle setting
429     * @param s throttle setting
430     */
431     protected void setSpeed(float s) {
432        float speed = s;
433        _speedUtil.speedChange();   // call before changing throttle setting
434        _throttle.setSpeedSetting(speed);       // CAN MISS SETTING SPEED! (as done when runOnLayoutEventually used) ??
435        // Late update to GUI is OK, this is just an informational status display
436        _warrant.fireRunStatus("SpeedChange", null, _speedType);
437        if (log.isDebugEnabled()) 
438            log.debug("_throttle.setSpeedSetting({}) called, ({}).  warrant {}",
439                    speed, _speedType, _warrant.getDisplayName());
440    }
441
442    protected float getSpeedSetting() {
443        return _throttle.getSpeedSetting();
444    }
445
446    protected float getScriptSpeed() {
447        return _normalSpeed;
448    }
449    /**
450     * Utility for unscripted speed changes.
451     * Records current type and sets time ratio.
452     * @param speedType name of speed change type
453     * @return true to continue, false to skip setting a speed
454     */
455    private boolean setSpeedRatio(String speedType) {
456        float newSpeed = _speedUtil.modifySpeed(_normalSpeed, speedType);
457        if (log.isTraceEnabled()) {
458            float scriptSpeed = _speedUtil.modifySpeed(_normalSpeed, _speedType);
459            log.debug("setSpeedRatio: \"{}\" speed setting= {}, calculated current speed = {},  newSpeed= {}. - {}",
460                    speedType, getSpeedSetting(), scriptSpeed, newSpeed, _warrant.getDisplayName());
461        }
462
463        if (!speedType.equals(Warrant.Stop) && !speedType.equals(Warrant.EStop)) {
464            _speedType = speedType;     // set type regardless of return
465            synchronized (this) {
466                float speedMod = _speedUtil.modifySpeed(1.0f, _speedType);
467                if (Math.abs(1.0f - speedMod) > .0001f) {
468                    _timeRatio = 1.0f / speedMod;
469                } else {
470                    _timeRatio = 1.0f;
471                }
472            }
473        }
474        if (Math.abs(getSpeedSetting() - newSpeed) < .002f) {
475            setHalt(false);
476            return false;
477        }
478        return true;
479    }
480
481    /*
482     * Do immediate speed change.
483     */
484    protected void setSpeedToType(String speedType) {
485        cancelRamp(false);
486        if (speedType.equals(Warrant.EStop)) {
487            setSpeed(-0.1f);        // always do immediate EStop
488            _waitForClear = true;
489            advanceToCommandIndex(_idxCurrentCommand + 1);  // skip current command
490        } else if (speedType.equals(Warrant.Stop)) {
491            setSpeed(0.0f);
492            _waitForClear = true;
493            advanceToCommandIndex(_idxCurrentCommand + 1);  // skip current command
494        } else {
495            if (setSpeedRatio(speedType)) {
496                setSpeed(_speedUtil.modifySpeed(_normalSpeed, speedType));
497            }
498        }
499        if (log.isDebugEnabled()) 
500            log.debug("setSpeedToType({}) scriptSpeed= {}", speedType, _normalSpeed);
501    }
502
503    /**
504     * Command to stop (or resume speed) of train from Warrant.controlRunTrain()
505     * of user's override of throttle script.  Also from error conditions
506     * such as losing detection of train's location.
507     * @param halt true if train should halt
508     */
509    public synchronized void setHalt(boolean halt) {
510        if (log.isDebugEnabled()) 
511            log.debug("setHalt({}): _atHalt= {}, _waitForClear= {}, _waitForSync= {}, warrant {}",
512                halt, _atHalt, _waitForClear, _waitForSync, _warrant.getDisplayName());
513        if (!halt) {    // resume normal running
514            _halt = false;
515            if (_atHalt) {
516                if (log.isDebugEnabled()) 
517                    log.debug("setHalt calls notify()");
518                notifyAll();   // free wait at _atHalt
519            }
520        } else {
521            _halt = true;
522        }
523    }
524
525    /**
526     * Command to stop or smoothly resume speed. Stop due to
527     * signal or occupation stopping condition ahead.  Caller
528     * follows with call for type of stop to make.
529     * Track condition override of throttle script.
530     * @param stop true if train should stop
531     */
532    protected synchronized void setWaitforClear(boolean stop) {
533        if (log.isDebugEnabled()) 
534            log.debug("setWaitforClear({}): _atClear= {}, throttle speed= {}, _halt= {}, _waitForSync= {}, warrant {}",
535                stop, _atClear,  _throttle.getSpeedSetting(), _halt, _waitForSync, _warrant.getDisplayName());
536        if (!stop) {    // resume normal running
537            _waitForClear = false;
538            if (_atClear) {
539                if (log.isDebugEnabled()) 
540                    log.debug("setWaitforClear calls notify");
541                notifyAll();   // free wait at _atClear
542            }
543        } else {
544            _waitForClear = true;
545        }
546    }
547
548    String getFlags() {
549        StringBuilder buf = new StringBuilder("Engineer flags: _waitForClear= ");
550        buf.append(_waitForClear);
551        buf.append(", _atclear= "); buf.append(_atClear);
552        buf.append(", _halt= "); buf.append(_halt);
553        buf.append(", _atHalt= "); buf.append(_atHalt);
554        buf.append(", _waitForSync= "); buf.append(_waitForSync);
555        return buf.toString();
556    }
557
558    ThrottleRamp getRamp() {
559        return _ramp;
560    }
561    /**
562     * Immediate stop command from Warrant.controlRunTrain()-user
563     * or from Warrant.goingInactive()-train lost
564     * or from setMovement()-overrun, possible collision risk.
565     * Do not ramp.
566     * @param eStop true for emergency stop
567     * @param setHalt for user restart needed, otherwise some kind of clear
568     */
569    public synchronized void setStop(boolean eStop, boolean setHalt) {
570        cancelRamp(false);
571        if (setHalt) {
572            _halt = true;
573        } else {
574            _waitForClear = true;
575        }
576        if (eStop) {
577            setSpeed(-0.1f);
578        } else {
579            setSpeed(0.0f);
580        }
581    }
582
583    public synchronized int getRunState() {
584        if (_stopPending) {
585            if (_halt) {
586                return Warrant.RAMP_HALT;
587            }
588            return Warrant.STOP_PENDING;
589        } else if (_resumePending) {
590            return Warrant.RAMPING_UP;            
591        } else if (_waitForClear) {
592            return Warrant.WAIT_FOR_CLEAR;
593        } else if (_waitForSensor) {
594            return Warrant.WAIT_FOR_SENSOR;
595        } else if (_halt) {
596            return Warrant.HALT;
597        } else if (_abort) {
598            return Warrant.ABORT;
599        } else if (_waitForSync) {
600            return Warrant.WAIT_FOR_TRAIN;
601        } else if (!_speedType.equals(Warrant.Normal)) {
602            return Warrant.SPEED_RESTRICTED;
603        } else if (_idxCurrentCommand < 0) {
604            return Warrant.STOP;
605        }
606        return Warrant.RUNNING;
607    }
608
609    public void stopRun(boolean abort, boolean turnOffFunctions) {
610        if (abort) {
611            _abort =true;            
612        }
613        if (_waitSensor != null) {
614            _waitSensor.removePropertyChangeListener(this);
615        }
616        cancelRamp(true);
617
618        if (_throttle != null) {
619            if (_throttle.getSpeedSetting() > 0.0f) {
620                _throttle.setSpeedSetting(-1.0f);
621                setSpeed(0.0f);     // prevent creep after EStop - according to Jim Betz
622            }
623            if (abort && turnOffFunctions) {
624                _throttle.setF0(false);
625                _throttle.setF1(false);
626                _throttle.setF2(false);
627                _throttle.setF3(false);
628            }
629            _warrant.releaseThrottle(_throttle);
630        }
631    }
632
633    private void setForward(ValueType type) {
634        if (type == ValueType.VAL_TRUE) {
635            _throttle.setIsForward(true);
636        } else if (type == ValueType.VAL_FALSE) {
637            _throttle.setIsForward(false);
638        } else {
639            throw new java.lang.IllegalArgumentException("setForward type " + type + " wrong");
640        }
641    }
642
643    private void setFunction(int cmdNum, ValueType type) {
644        if ( cmdNum < 0 || cmdNum > 28 ) {       
645            throw new java.lang.IllegalArgumentException("setFunction " + cmdNum + " out of range");
646        }
647        if (type == ValueType.VAL_ON) {
648            _throttle.setFunction(cmdNum, true);
649        } else if (type == ValueType.VAL_OFF) {
650            _throttle.setFunction(cmdNum,false);
651        } else {
652            throw new java.lang.IllegalArgumentException("setFunction type " + type + " wrong");
653        }
654    }
655
656    private void setFunctionMomentary(int cmdNum, ValueType type) {
657        if ( cmdNum < 0 || cmdNum > 28 ) {       
658            log.error("Function value {} out of range",cmdNum);
659            throw new java.lang.IllegalArgumentException("setFunctionMomentary " + cmdNum + " out of range");
660        }
661        if (type == ValueType.VAL_ON) {
662            _throttle.setFunctionMomentary(cmdNum, true);
663        } else if (type == ValueType.VAL_OFF) {
664            _throttle.setFunctionMomentary(cmdNum,false);
665        } else {
666            throw new java.lang.IllegalArgumentException("setFunctionMomentary type " + type + " wrong");
667        }
668    }
669
670    /**
671     * Set Sensor state
672     */
673    private void setSensor(NamedBeanHandle<?> handle, CommandValue cmdVal) {
674        NamedBean bean = handle.getBean();
675        if (!(bean instanceof Sensor)) {
676            log.error("setSensor: {} not a Sensor!", bean.getDisplayName());
677            return;
678        }
679        jmri.Sensor s = (Sensor)bean;
680        ValueType type = cmdVal.getType();
681        try {
682            if (type == ValueType.VAL_ACTIVE) {
683                s.setKnownState(jmri.Sensor.ACTIVE);
684            } else if (type == ValueType.VAL_INACTIVE) {
685                s.setKnownState(jmri.Sensor.INACTIVE);
686            } else {
687                throw new java.lang.IllegalArgumentException("setSensor type " + type + " wrong");
688            }
689            _warrant.fireRunStatus("SensorSetCommand", type.toString(), s.getDisplayName());
690        } catch (jmri.JmriException e) {
691            log.warn("Exception setting sensor {} in action", handle.toString());
692        }
693    }
694
695    /**
696     * Wait for Sensor state event
697     */
698    private void waitForSensor(NamedBeanHandle<?> handle, CommandValue cmdVal) {
699        if (_waitSensor != null) {
700            _waitSensor.removePropertyChangeListener(this);
701        }
702        NamedBean bean = handle.getBean();
703        if (!(bean instanceof Sensor)) {
704            log.error("setSensor: {} not a Sensor!", bean.getDisplayName());
705            return;
706        }
707        _waitSensor = (Sensor)bean;
708        ThrottleSetting.ValueType type = cmdVal.getType();
709        if (type == ValueType.VAL_ACTIVE) {
710            _sensorWaitState = Sensor.ACTIVE;
711        } else if (type == ValueType.VAL_INACTIVE) {
712            _sensorWaitState = Sensor.INACTIVE;
713        } else {
714            throw new java.lang.IllegalArgumentException("waitForSensor type " + type + " wrong");
715        }
716        int state = _waitSensor.getKnownState();
717        if (state == _sensorWaitState) {
718            log.info("Engineer: state of event sensor {} already at state {}", _waitSensor.getDisplayName(), type.toString());
719            return;
720        }
721        _waitSensor.addPropertyChangeListener(this);
722        if (log.isDebugEnabled()) 
723            log.debug("Listen for propertyChange of {}, wait for State= {}", _waitSensor.getDisplayName(), _sensorWaitState);
724        // suspend commands until sensor changes state
725        synchronized (this) {   // DO NOT USE _waitForSensor for synch
726            _waitForSensor = true;
727            while (_waitForSensor) {
728                try {
729                    _warrant.fireRunStatus("SensorWaitCommand", type.toString(), _waitSensor.getDisplayName());
730                    wait();
731                    String name =  _waitSensor.getDisplayName();    // save name, _waitSensor will be null 'eventually' 
732                    _warrant.fireRunStatus("SensorWaitCommand", null, name);
733                } catch (InterruptedException ie) {
734                    log.error("Engineer interrupted at _waitForSensor ",ie);
735                    _warrant.debugInfo();
736                    Thread.currentThread().interrupt();
737                } finally {
738                    clearSensor();
739                }
740            }
741        }
742    }
743
744    private void clearSensor() {
745        if (_waitSensor != null) {
746            _waitSensor.removePropertyChangeListener(this);
747        }
748        _sensorWaitState = 0;
749        _waitForSensor = false;
750        _waitSensor = null;
751    }
752
753    protected Sensor getWaitSensor() {
754        return _waitSensor;
755    }
756
757    @Override
758    @SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification="Sensor change on another thread is expected even when Engineer (this) has not done any modifing")
759    public void propertyChange(java.beans.PropertyChangeEvent evt) {
760        if (log.isDebugEnabled()) 
761            log.debug("propertyChange {} new value= {}", evt.getPropertyName(), evt.getNewValue());
762        if ((evt.getPropertyName().equals("KnownState")
763                && ((Number) evt.getNewValue()).intValue() == _sensorWaitState)) {
764            synchronized (this) {
765                    notifyAll();  // free sensor wait
766            }
767        }
768    }
769
770
771    private void runWarrant(NamedBeanHandle<?> handle, CommandValue cmdVal) {
772        NamedBean bean = handle.getBean();
773        if (!(bean instanceof Warrant)) {
774            log.error("runWarrant: {} not a warrant!", bean.getDisplayName());
775            return;
776        }
777        Warrant warrant =  (Warrant)bean;
778        
779        int num = Math.round(cmdVal.getFloat());
780        if (num <= 0) {
781            return;
782        }
783        num--;
784        cmdVal.setFloat(num);
785        java.awt.Color color = java.awt.Color.red;
786
787        String msg = null;
788        if (_warrant.getSpeedUtil().getDccAddress().equals(warrant.getSpeedUtil().getDccAddress())) {
789            // Same loco, perhaps different warrant
790            log.debug("Loco address {} finishes warrant {} and starts warrant {}",
791                    warrant.getSpeedUtil().getDccAddress(), _warrant.getDisplayName(), warrant.getDisplayName());
792            Thread checker = new CheckForTermination(_warrant, warrant, num);
793            checker.start();
794            if (log.isDebugEnabled()) log.debug("Exit runWarrant");
795            return;
796        } else {
797            log.debug("Loco address {} on warrant {} and starts loco {} on warrant {}",
798                    _warrant.getSpeedUtil().getDccAddress(), _warrant.getDisplayName(),
799                    warrant.getSpeedUtil().getDccAddress(), warrant.getDisplayName());
800            msg = WarrantTableFrame.getDefault().runTrain(warrant, Warrant.MODE_RUN);
801            if (msg != null) {
802                msg = Bundle.getMessage("CannotRun", warrant.getDisplayName(), msg);
803            } else {
804                msg = Bundle.getMessage("linkedLaunch",
805                        warrant.getDisplayName(), _warrant.getDisplayName(),
806                        warrant.getfirstOrder().getBlock().getDisplayName(),
807                        _warrant.getfirstOrder().getBlock().getDisplayName());
808                color = WarrantTableModel.myGreen;
809           }
810        }
811        final String m = msg;
812        java.awt.Color c = color;
813        ThreadingUtil.runOnLayoutEventually(()-> WarrantTableFrame.getDefault().setStatusText(m, c, true));
814        log.debug("Exit runWarrant - {}",msg);
815    }
816
817    private static class CheckForTermination extends Thread {
818        Warrant oldWarrant;
819        Warrant newWarrant;
820        int num;
821
822        CheckForTermination(Warrant oldWar, Warrant newWar, int n) {
823            oldWarrant = oldWar;
824            newWarrant = newWar;
825            num = n;
826            if (log.isDebugEnabled()) log.debug("checkForTermination({}, {}, {})",
827                    oldWarrant.getDisplayName(), newWarrant.getDisplayName(), num);
828         }
829
830        @Override
831        public void run() {
832            OBlock endBlock = oldWarrant.getLastOrder().getBlock();
833            long time = 0;
834            String msg = null;
835            while (time < 10000) {
836                if (oldWarrant.getRunMode() == Warrant.MODE_NONE) {
837                    break;
838                }
839                int priority = Thread.currentThread().getPriority();
840                try {
841                    Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
842                    Thread.sleep(100);
843                    time += 100;
844                } catch (InterruptedException ie) {
845                    time = 10000;
846                    msg = Bundle.getMessage("CannotRun", newWarrant.getDisplayName(), ie);
847                } finally {
848                    Thread.currentThread().setPriority(priority);
849                }
850                if (time >= 10000) {
851                    msg = Bundle.getMessage("cannotLaunch",
852                            newWarrant.getDisplayName(), oldWarrant.getDisplayName(), endBlock.getDisplayName());
853                }
854            }
855            if (log.isDebugEnabled()) log.debug("CheckForTermination waited {}ms. runMode={} ", time, oldWarrant.getRunMode());
856
857            java.awt.Color color;
858            msg = WarrantTableFrame.getDefault().runTrain(newWarrant, Warrant.MODE_RUN);
859            if (msg != null) {
860                msg = Bundle.getMessage("CannotRun", newWarrant.getDisplayName(), msg);
861                color = java.awt.Color.red;
862            } else {
863                if (oldWarrant.equals(newWarrant)) {
864                    msg = Bundle.getMessage("reLaunch", oldWarrant.getDisplayName(), (num<0 ? "unlimited" : num));
865                } else {
866                    msg = Bundle.getMessage("linkedLaunch",
867                            newWarrant.getDisplayName(), oldWarrant.getDisplayName(),
868                            newWarrant.getfirstOrder().getBlock().getDisplayName(),
869                            endBlock.getDisplayName());
870                }
871                color = WarrantTableModel.myGreen;
872            }
873            String m = msg;
874            java.awt.Color c = color;
875            ThreadingUtil.runOnLayoutEventually(() -> // delay until current warrant can complete
876                WarrantTableFrame.getDefault().setStatusText(m, c, true));
877        }
878    }
879
880    private void rampDone(boolean stop, String type) {
881        if (!stop) {
882            _warrant.fireRunStatus("RampDone", _halt, type);
883        }
884        if (!_atHalt && !_atClear) {
885            synchronized (this) {
886                notifyAll();  // let engineer run script
887                log.debug("rampDone called notify");
888            }
889            if (_currentCommand.getCommand().equals(Command.NOOP)) {
890                _idxCurrentCommand--;   // notify advances command.  Repeat wait for entry to next block
891            }
892        }
893        if (log.isDebugEnabled())
894            log.debug("ThrottleRamp done: {} for \"{}\" at speed= {}. _normalScript={}, Thread.State= {} resume index= {}, current Index= {} on warrant {}",
895                    (stop?"stopped":"completed"), type, getSpeedSetting(), _normalSpeed, (_ramp != null?_ramp.getState():"_ramp is null!"),
896                    _idxSkipToSpeedCommand+1, _idxCurrentCommand+1, _warrant.getDisplayName());
897        // Note: command indexes biased from 0 to 1 to match Warrant display of commands.
898    }
899
900    /*
901     * *************************************************************************************
902     */
903
904     class ThrottleRamp extends Thread {
905
906         private RampData _rampData;
907         private String _endSpeedType;
908         private int _endBlockIdx;   // index of block where down ramp ends - not used for up ramps.
909         private boolean _useIndex;  // false if endBlockIdx should not be considered
910         private boolean stop = false;   // aborts ramping
911         boolean ready = false;      // ready for call doRamp
912         private boolean _die = false;    // kills ramp for good
913
914         ThrottleRamp() {
915            setName("Ramp(" + _warrant.getTrainName() +")");
916         }
917
918         void quit(boolean die) {
919             log.debug("ThrottleRamp.quit({}) _die= {}", die, _die);
920             stop = true;
921             if (die) { // once set to true, do not allow resetting to false
922                 _die = die;    // permanent shutdown, warrant running ending
923             }
924             synchronized (_rampLockObject) {
925                 log.debug("ThrottleRamp.quit calls notify()");
926                 _rampLockObject.notifyAll(); // free waits at ramp time interval
927             }
928         }
929
930        void setParameters(String endSpeedType, int endBlockIdx, boolean useIndex) {
931            _endSpeedType = endSpeedType;
932            _endBlockIdx = endBlockIdx;
933            _useIndex = useIndex;
934            _stopPending = endSpeedType.equals(Warrant.Stop);                    
935        }
936
937        boolean duplicate(String endSpeedType, int endBlockIdx, boolean useIndex) {
938            if (endBlockIdx != _endBlockIdx || 
939                    !endSpeedType.equals(_endSpeedType) || useIndex != _useIndex) {
940                return false;
941            }
942            return true;                    
943        }
944
945        RampData getRampData () {
946            return _rampData;
947        }
948
949        /**
950         * 
951         * @param blockIdx  index of block order where ramp finishes
952         * @param cmdIdx   current command index
953         * @return command index of block where commands should not be executed 
954         */
955        int getCommandIndexLimit(int blockIdx, int cmdIdx) {
956            // get next block
957            int limit = _commands.size();
958            String curBlkName = _warrant.getCurrentBlockName();
959            String endBlkName = _warrant.getBlockAt(blockIdx).getDisplayName();
960            if (_useIndex) {
961                if (!curBlkName.contentEquals(endBlkName)) {
962                    for (int cmd = cmdIdx; cmd < _commands.size(); cmd++) {
963                        ThrottleSetting ts = _commands.get(cmd);
964                        if (ts.getBeanDisplayName().equals(endBlkName) ) {
965                            cmdIdx = cmd;
966                            break;
967                        }
968                    }
969                }
970                endBlkName = _warrant.getBlockAt(blockIdx+1).getDisplayName();
971                for (int cmd = cmdIdx; cmd < _commands.size(); cmd++) {
972                    ThrottleSetting ts = _commands.get(cmd);
973                    if (ts.getBeanDisplayName().equals(endBlkName) && 
974                            ts.getValue().getType().equals(ValueType.VAL_NOOP)) {
975                        limit = cmd;
976                        break;
977                    }
978                }
979            }
980            log.debug("getCommandIndexLimit: in current block {}, limitIdx = {} in block {}", curBlkName, limit+1, endBlkName);
981            return limit;
982        }
983
984        @Override
985        @SuppressFBWarnings(value="UW_UNCOND_WAIT", justification="waits may be indefinite until satisfied or thread aborted")
986        public void run() {
987            ready = true;
988            while (!_die) {
989                synchronized (_rampLockObject) {
990                    try {
991                        _rampLockObject.wait(); // wait until notified by rampSpeedTo() calls quit()
992                    } catch (InterruptedException ie) {
993                        log.debug("As expected {}", ie);
994                    }
995                }
996                doRamp();
997            }
998        }
999
1000        public void doRamp() {
1001            // the time 'right now' is at having done _idxCurrentCommand-1 and is waiting
1002            // to do the _idxCurrentCommand.  A non-scripted speed change is to begin now.
1003            // current speed at _idxCurrentCommand is (should be) _normalSpeed modified by _speedType
1004            // Note on ramp down the _normalSpeed value may be modified. 
1005            // "idxSkipToSpeedCommand" may be used rather than "_idxCurrentCommand".
1006            // Note on ramp up endSpeed should match scripted speed modified by endSpeedType
1007            ready = false;
1008            stop = false;
1009            float endSpeed = _speedUtil.modifySpeed(_normalSpeed, _endSpeedType);   // requested end speed
1010            float speed = _throttle.getSpeedSetting();  // current speed setting
1011            if (speed < 0.0f) {
1012                speed = 0.0f;
1013            }
1014            _rampData = _speedUtil.getRampForSpeedChange(speed, endSpeed);
1015            int timeIncrement = _rampData.getRampTimeIncrement();
1016            long rampTime = 0;      // accumulating time doing the ramp
1017            float rampDist = 0;     // accumulating distance of ramp
1018            float rampLen = _rampData.getRampLength();
1019            float scriptSpeed = _normalSpeed;
1020            float distToCmd = _currentCommand.getTrackSpeed() * _currentCommand.getTime();   // distance to next command
1021
1022            int commandIndexLimit = getCommandIndexLimit(_endBlockIdx, _idxCurrentCommand);
1023            if (log.isDebugEnabled()) 
1024                log.debug("ThrottleRamp for \"{}\". At Cmd#{} limit#{}. rampLen= {} distToCmd= {}. useIndex= {}. on warrant {}",
1025                   _endSpeedType, _idxCurrentCommand+1, commandIndexLimit+1, rampLen, distToCmd, _useIndex, _warrant.getDisplayName());
1026
1027            synchronized (this) {
1028                try {
1029                     _lock.lock();
1030                     // _normalSpeed typically is the last setThrottleSetting done. However it also
1031                     // may be reset after a down ramp to be the setting expected to be resumed at the
1032                     // point skipped to by the down ramp.
1033
1034                    if (_rampData.isUpRamp()) {
1035                        _resumePending = true;
1036                        // The ramp up will take time and the script may have other speed commands while
1037                        // ramping up. So the actual script speed may not match the endSpeed when ramp up distance
1038                        // is traveled.  Adjust 'endSpeed' to match that 'scriptSpeed'.
1039                        // Up rampLen is distance from current throttle speed to endSpeed of ramp.
1040                        if (log.isDebugEnabled()) {
1041                            log.debug("RAMP UP \"{}\" speed from {}, to {}. distToCmd= {}, Ramp: {}mm {}steps {}ms, Currentdx= {}, SkipToIdx= {}",
1042                                    _endSpeedType, speed, endSpeed, distToCmd, rampLen, _rampData.getNumSteps(), _rampData.getRamptime(),
1043                                    _idxCurrentCommand+1, _idxSkipToSpeedCommand+1);
1044                                // Note: command indexes biased from 0 to 1 to match Warrant display of commands.
1045                        }
1046                        // during ramp up the script may have non-speed commands that should be executed.
1047                        ListIterator<Float> iter = _rampData.speedIterator(true);
1048                        float prevSpeed = iter.next().floatValue();   // skip repeat of current speed
1049                        float prevScriptSpeed;
1050
1051                        while (iter.hasNext()) { // do ramp up
1052                            if (stop) {
1053                                break;
1054                            }
1055                            speed = iter.next().floatValue();
1056
1057                            setSpeed(speed);
1058                            rampDist += _speedUtil.getDistanceOfSpeedChange(prevSpeed, speed, timeIncrement);
1059                            prevSpeed = speed;
1060
1061                            try {
1062                                wait(timeIncrement);
1063                            } catch (InterruptedException ie) {
1064                                _lock.unlock();
1065                                stop = true;
1066                            }
1067                            rampTime += timeIncrement;
1068                            rampDist += _speedUtil.getDistanceOfSpeedChange(prevSpeed, speed, timeIncrement);
1069                            prevSpeed = speed;
1070
1071                            // Execute the non-Speed commands during the ramp
1072                            if (distToCmd < rampDist && _idxCurrentCommand < commandIndexLimit) {
1073                                CommandValue cmdVal = _currentCommand.getValue();
1074                                if (!cmdVal.getType().equals(ThrottleSetting.ValueType.VAL_FLOAT)) {
1075                                    executeComand(_currentCommand);
1076                                    if (log.isDebugEnabled()) {
1077                                        log.debug("Cmd #{} done.  rampTime={}. distToCmd={} rampDist={}",
1078                                                _idxCurrentCommand+1, rampTime, distToCmd, rampDist);
1079                                    }
1080                                    distToCmd += scriptSpeed * _currentCommand.getTime();
1081                                } else {
1082                                    if (log.isDebugEnabled()) {
1083                                        log.debug("Cmd #{} skipped. rampTime={}. distToCmd={} rampDist={}",
1084                                                _idxCurrentCommand+1, rampTime, distToCmd, rampDist);
1085                                    }
1086                                    prevScriptSpeed = scriptSpeed;
1087                                    scriptSpeed = _currentCommand.getValue().getFloat();
1088                                    distToCmd += _speedUtil.getDistanceOfSpeedChange(prevScriptSpeed, scriptSpeed, timeIncrement);
1089                                    if (_speedUtil.modifySpeed(scriptSpeed, _endSpeedType) < speed) {
1090                                        if (log.isDebugEnabled()) {
1091                                            log.debug("Ramp stopped at speed {}. Cmd #{} rampTime={}. distToCmd={} rampDist={}",
1092                                                    speed, _idxCurrentCommand+1, rampTime, distToCmd, rampDist);
1093                                        }
1094                                        executeComand(_currentCommand);
1095                                        stop = true;    // let script take over from here.
1096                                    }
1097                                 }
1098                                _currentCommand = _commands.get(++_idxCurrentCommand);
1099                            }
1100                            advanceToCommandIndex(_idxCurrentCommand); // skip up to this command
1101                        }
1102                    } else {     // decreasing, ramp down to a modified speed
1103                        // Down ramp may advance the train beyond the point where the script is paused.
1104                        // Any down ramp requested with _useIndex==true is expected to end at the end of
1105                        // a block i.e. the block of BlockOrder indexed by _endBlockIdx.
1106                        // Therefore script should resume at the exit to this block.
1107                        // During ramp down the script may have other Non speed commands that should be executed.
1108                        if (log.isDebugEnabled()) {
1109                            // Note: command indexes biased from 0 to 1 to match Warrant display of commands.
1110                            log.debug("RAMP DOWN to \"{}\". curSpeed= {}, endSpeed= {}, endBlock= {}",
1111                                    _endSpeedType, speed, endSpeed, _warrant.getBlockAt(_endBlockIdx).getDisplayName());
1112                        }
1113                        ListIterator<Float> iter = _rampData.speedIterator(false);
1114                        float prevSpeed = iter.previous().floatValue();   // skip repeat of current throttle setting
1115                        float prevScriptSpeed;
1116 
1117                        while (iter.hasPrevious()) {
1118                            if (stop) {
1119                                break;
1120                            }
1121                            speed = iter.previous().floatValue();
1122
1123                            if (_useIndex) {    // correction code for ramps that are too long or too short
1124                                if ( _warrant._idxCurrentOrder > _endBlockIdx) {
1125                                    // loco overran end block.  Set end speed and leave ramp
1126                                    speed = endSpeed;
1127                                    stop = true;
1128                                } else if ( _warrant._idxCurrentOrder < _endBlockIdx && 
1129                                        _endSpeedType.equals(Warrant.Stop) && Math.abs(speed - endSpeed) <.001f) {
1130                                    // At last speed change to set throttle to 0.0, but train has not 
1131                                    // reached the last block. Let loco creep to end block at current setting.
1132                                    if (log.isDebugEnabled()) 
1133                                        log.debug("Extending ramp to reach block {}. speed= {}",
1134                                                _warrant.getBlockAt(_endBlockIdx).getDisplayName(), speed);
1135                                    while (_endBlockIdx - _warrant._idxCurrentOrder > 0) {
1136                                        // Until loco reaches end block, continue current speed
1137                                        try {
1138                                            wait(timeIncrement);
1139                                        } catch (InterruptedException ie) {
1140                                            _lock.unlock();
1141                                            stop = true;
1142                                        }   
1143                                    }
1144                                }
1145                            }
1146
1147                            setSpeed(speed);
1148                            try {
1149                                wait(timeIncrement);
1150                            } catch (InterruptedException ie) {
1151                                _lock.unlock();
1152                                stop = true;
1153                            }
1154
1155                            rampTime += timeIncrement;
1156                            rampDist += _speedUtil.getDistanceOfSpeedChange(prevSpeed, speed, timeIncrement);
1157                            prevSpeed = speed;
1158
1159                            // Execute the non-Speed commands during the ramp
1160                            if (distToCmd < rampDist && _idxCurrentCommand < commandIndexLimit) {
1161                                CommandValue cmdVal = _currentCommand.getValue();
1162                                if (!cmdVal.getType().equals(ThrottleSetting.ValueType.VAL_FLOAT)) {
1163                                    executeComand(_currentCommand);
1164                                    if (log.isDebugEnabled()) {
1165                                        log.debug("Cmd #{} done. rampTime={}. distToCmd={} rampDist={}",
1166                                                _idxCurrentCommand+1, rampTime, distToCmd, rampDist);
1167                                    }
1168                                    distToCmd += scriptSpeed * _currentCommand.getTime();
1169                                } else {
1170                                    if (log.isDebugEnabled()) {
1171                                        log.debug("Cmd #{} skipped. rampTime={}.  distToCmd={} rampDist={}",
1172                                                _idxCurrentCommand+1, rampTime, distToCmd, rampDist);
1173                                    }
1174                                    prevScriptSpeed = scriptSpeed;
1175                                    scriptSpeed = _currentCommand.getValue().getFloat();
1176                                    distToCmd += _speedUtil.getDistanceOfSpeedChange(prevScriptSpeed, scriptSpeed, timeIncrement);
1177                                 }
1178                                _currentCommand = _commands.get(++_idxCurrentCommand);
1179                            }
1180                        }
1181                        // ramp done.
1182                        if (log.isDebugEnabled()) {
1183                            log.debug("Ramp Down done. _idxCurrentCommand={} commandIndexLimit={}. warrant {}",
1184                                    _idxCurrentCommand+1, commandIndexLimit, _warrant.getDisplayName());
1185                        }
1186                        if (_useIndex) {
1187                            while (_idxCurrentCommand < commandIndexLimit) {
1188                                NamedBean bean = _currentCommand.getNamedBeanHandle().getBean();
1189                                if (bean instanceof OBlock) {
1190                                    OBlock blk = (OBlock)bean;
1191                                    if (_endBlockIdx < _warrant.getIndexOfBlock(blk, _endBlockIdx)) {
1192                                        // script is past end point, command should be NOOP
1193                                        break;
1194                                    }
1195                                }
1196                                CommandValue cmdVal = _currentCommand.getValue();
1197                                if (!cmdVal.getType().equals(ThrottleSetting.ValueType.VAL_FLOAT)) {
1198                                    executeComand(_currentCommand);
1199                                    if (log.isDebugEnabled()) {
1200                                        log.debug("Cmd #{} command=\"{}\' executed. warrant {}",
1201                                                _idxCurrentCommand+1, _currentCommand.getCommand(), _warrant.getDisplayName());
1202                                    }
1203                                } else {
1204                                    _normalSpeed = cmdVal.getFloat();
1205                                    if (log.isDebugEnabled()) {
1206                                        log.debug("Cmd #{} command=\"{}\' skipped. warrant {}",
1207                                                _idxCurrentCommand+1, _currentCommand.getCommand(), _warrant.getDisplayName());
1208                                    }
1209                                }
1210                                _currentCommand = _commands.get(++_idxCurrentCommand);
1211                            }
1212                            advanceToCommandIndex(_idxCurrentCommand); // skip up to this command
1213
1214                            if (log.isDebugEnabled()) 
1215                                log.debug("End Blk= {}, Cmd Blk= {}, idxCurrentCommand={}, normalSpeed= {}",
1216                                        _warrant.getBlockAt(_endBlockIdx).getDisplayName(),
1217                                        _commands.get(_idxCurrentCommand).getNamedBeanHandle().getBean().getDisplayName(),
1218                                        _normalSpeed); // Note: command indexes biased from 0 to 1 to match Warrant display of commands.
1219                        }
1220                        
1221                        _stopPending = false;
1222                    }
1223                    
1224                } finally {
1225                    _lock.unlock();
1226                    if (!_endSpeedType.equals(Warrant.Stop) &&
1227                            !_endSpeedType.equals(Warrant.EStop)) {
1228                        // speed restored, clear any stop waits
1229                        // If flags already off, OK to repeat setting false
1230                        setWaitforClear(false);
1231                        setHalt(false);
1232                    }
1233                    _resumePending = false;
1234                }
1235            }
1236            ready = true;
1237            rampDone(stop, _endSpeedType);
1238            stop = false;
1239        }
1240    }
1241
1242    private static final Logger log = LoggerFactory.getLogger(Engineer.class);
1243}