001package jmri.jmrit.dispatcher;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.util.ArrayList;
005import jmri.Block;
006import jmri.InstanceManager;
007import jmri.Sensor;
008import jmri.SignalHead;
009import jmri.SignalHeadManager;
010import jmri.SignalMast;
011import jmri.SignalMastManager;
012import jmri.TransitSection;
013import jmri.TransitSectionAction;
014import org.slf4j.Logger;
015import org.slf4j.LoggerFactory;
016
017/**
018 * This class sets up and executes TransitSectionAction's specified for Sections
019 * traversed by one automatically running train. It is an extension to
020 * AutoActiveTrain that handles special actions while its train is running
021 * automatically.
022 * <p>
023 * This class is linked via its parent AutoActiveTrain object.
024 * <p>
025 * When an AutoActiveTrain enters a Section, it passes the TransitSection of the
026 * entered Section to this class.
027 * <p>
028 * Similarly when an AutoActiveTrain leaves a Section, it passes the
029 * TransitSection of the just vacated Section to this class.
030 * <p>
031 *
032 * This file is part of JMRI.
033 * <p>
034 * JMRI is open source software; you can redistribute it and/or modify it under
035 * the terms of version 2 of the GNU General Public License as published by the
036 * Free Software Foundation. See the "COPYING" file for a copy of this license.
037 * <p>
038 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
039 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
040 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
041 *
042 * @author Dave Duchamp Copyright (C) 2010-2011
043 */
044public class AutoTrainAction {
045
046    /**
047     * Create an AutoTrainAction.
048     *
049     * @param aat the associated train
050     */
051    public AutoTrainAction(AutoActiveTrain aat) {
052        _autoActiveTrain = aat;
053        _activeTrain = aat.getActiveTrain();
054    }
055
056    // operational instance variables
057    private AutoActiveTrain _autoActiveTrain = null;
058    private ActiveTrain _activeTrain = null;
059    private ArrayList<TransitSection> _activeTransitSectionList = new ArrayList<TransitSection>();
060    private ArrayList<TransitSectionAction> _activeActionList = new ArrayList<TransitSectionAction>();
061
062    // this method is called when an AutoActiveTrain enters a Section 
063    protected synchronized void addTransitSection(TransitSection ts) {
064        _activeTransitSectionList.add(ts);
065        ArrayList<TransitSectionAction> tsaList = ts.getTransitSectionActionList();
066        // set up / execute Transit Section Actions if there are any
067        if (tsaList.size() > 0) {
068            for (int i = 0; i < tsaList.size(); i++) {
069                TransitSectionAction tsa = tsaList.get(i);
070                // add to list if not already there
071                boolean found = false;
072                for (int j = 0; j < _activeActionList.size(); j++) {
073                    if (_activeActionList.get(j) == tsa) {
074                        found = true;
075                    }
076                }
077                if (!found) {
078                    _activeActionList.add(tsa);
079                    tsa.initialize();
080                }
081                switch (tsa.getWhenCode()) {
082                    case TransitSectionAction.ENTRY:
083                        // on entry to Section - if here Section was entered
084                        checkDelay(tsa);
085                        break;
086                    case TransitSectionAction.EXIT:
087                        // on exit from Section
088                        tsa.setWaitingForSectionExit(true);
089                        tsa.setTargetTransitSection(ts);
090                        break;
091                    case TransitSectionAction.BLOCKENTRY:
092                        // on entry to specified Block in the Section
093                    case TransitSectionAction.BLOCKEXIT:
094                        // on exit from specified Block in the Section
095                        tsa.setWaitingForBlock(true);
096                        break;
097                    case TransitSectionAction.TRAINSTOP:
098                    // when train stops - monitor in separate thread
099                    case TransitSectionAction.TRAINSTART:
100                        // when train starts - monitor in separate thread
101                        Runnable monTrain = new MonitorTrain(tsa);
102                        Thread tMonTrain = jmri.util.ThreadingUtil.newThread(monTrain, "Monitor Train Transit Action " + _activeTrain.getDccAddress());
103                        tsa.setWaitingThread(tMonTrain);
104                        tMonTrain.start();
105                        break;
106                    case TransitSectionAction.SENSORACTIVE:
107                    // when specified Sensor changes to Active
108                    case TransitSectionAction.SENSORINACTIVE:
109                        // when specified Sensor changes to Inactive
110                        if (!waitOnSensor(tsa)) {
111                            // execute operation immediately -
112                            //  no sensor found, or sensor already in requested state
113                            executeAction(tsa);
114                        } else {
115                            tsa.setWaitingForSensor(true);
116                        }
117                        break;
118                    default:
119                        break;
120                }
121            }
122        }
123    }
124
125    /**
126     * Sets up waiting on Sensor before executing an action If Sensor does not
127     * exist, or Sensor is already in requested state, returns false. If waiting
128     * for Sensor to change, returns true.
129     */
130    private boolean waitOnSensor(TransitSectionAction tsa) {
131        if (tsa.getWaitingForSensor()) {
132            return true;
133        }
134        Sensor s = InstanceManager.sensorManagerInstance().getSensor(tsa.getStringWhen());
135        if (s == null) {
136            log.error("Sensor with name - {} - was not found.", tsa.getStringWhen());
137            return false;
138        }
139        int now = s.getKnownState();
140        if (((now == Sensor.ACTIVE) && (tsa.getWhenCode() == TransitSectionAction.SENSORACTIVE))
141                || ((now == Sensor.INACTIVE) && (tsa.getWhenCode() == TransitSectionAction.SENSORINACTIVE))) {
142            // Sensor is already in the requested state, so execute action immediately
143            return false;
144        }
145        // set up listener
146        tsa.setTriggerSensor(s);
147        tsa.setWaitingForSensor(true);
148        final String sensorName = tsa.getStringWhen();
149        java.beans.PropertyChangeListener sensorListener = null;
150        s.addPropertyChangeListener(sensorListener
151                = new java.beans.PropertyChangeListener() {
152            @Override
153            public void propertyChange(java.beans.PropertyChangeEvent e) {
154                if (e.getPropertyName().equals("KnownState")) {
155                    handleSensorChange(sensorName);
156                }
157            }
158        });
159        tsa.setSensorListener(sensorListener);
160        return true;
161    }
162
163    public void handleSensorChange(String sName) {
164        // find waiting Transit Section Action
165        for (int i = 0; i < _activeActionList.size(); i++) {
166            if (_activeActionList.get(i).getWaitingForSensor()) {
167                TransitSectionAction tsa = _activeActionList.get(i);
168                if (tsa.getStringWhen().equals(sName)) {
169                    // have the waiting action
170                    tsa.setWaitingForSensor(false);
171                    if (tsa.getSensorListener() != null) {
172                        tsa.getTriggerSensor().removePropertyChangeListener(tsa.getSensorListener());
173                        tsa.setSensorListener(null);
174                    }
175                    executeAction(tsa);
176                    return;
177                }
178            }
179        }
180    }
181
182    // this method is called when the state of a Block in an Allocated Section changes
183    protected synchronized void handleBlockStateChange(AllocatedSection as, Block b) {
184        // Ignore call if not waiting on Block state change
185        for (int i = 0; i < _activeActionList.size(); i++) {
186            if (_activeActionList.get(i).getWaitingForBlock()) {
187                TransitSectionAction tsa = _activeActionList.get(i);
188                Block target = InstanceManager.getDefault(jmri.BlockManager.class).getBlock(tsa.getStringWhen());
189                if (b == target) {
190                    // waiting on state change for this block
191                    if (((b.getState() == Block.OCCUPIED) && (tsa.getWhenCode() == TransitSectionAction.BLOCKENTRY))
192                            || ((b.getState() == Block.UNOCCUPIED) && (tsa.getWhenCode() == TransitSectionAction.BLOCKEXIT))) {
193                        checkDelay(tsa);
194                    }
195                }
196            }
197        }
198    }
199
200    // this method is called when an AutoActiveTrain exits a section
201    protected synchronized void removeTransitSection(TransitSection ts) {
202        for (int i = _activeTransitSectionList.size() - 1; i >= 0; i--) {
203            if (_activeTransitSectionList.get(i) == ts) {
204                _activeTransitSectionList.remove(i);
205            }
206        }
207        // perform any actions triggered by leaving Section
208        for (int i = 0; i < _activeActionList.size(); i++) {
209            if (_activeActionList.get(i).getWaitingForSectionExit()
210                    && (_activeActionList.get(i).getTargetTransitSection() == ts)) {
211                // this action is waiting for this Section to exit
212                checkDelay(_activeActionList.get(i));
213            }
214        }
215    }
216
217    // this method is called when an action has been completed
218    private synchronized void completedAction(TransitSectionAction tsa) {
219        // action has been performed, clear, and delete it from the active list
220        if (tsa.getWaitingForSensor()) {
221            tsa.disposeSensorListener();
222        }
223        tsa.initialize();
224        // remove from active list if not continuous running
225        if (!_activeTrain.getResetWhenDone()) {
226            for (int i = _activeActionList.size() - 1; i >= 0; i--) {
227                if (_activeActionList.get(i) == tsa) {
228                    _activeActionList.remove(i);
229                    return;
230                }
231            }
232        }
233    }
234
235    /**
236     * This method is called to clear any actions that have not been completed
237     */
238    protected synchronized void clearRemainingActions() {
239        for (int i = _activeActionList.size() - 1; i >= 0; i--) {
240            TransitSectionAction tsa = _activeActionList.get(i);
241            Thread t = tsa.getWaitingThread();
242            if (t != null) {
243                // interrupting an Action thread will cause it to terminate
244                t.interrupt();
245            }
246            if (tsa.getWaitingForSensor()) {
247                // remove a sensor listener if one is present
248                tsa.disposeSensorListener();
249            }
250            tsa.initialize();
251            _activeActionList.remove(i);
252        }
253    }
254
255    // this method is called when an event has occurred, to check if action should be delayed.
256    private synchronized void checkDelay(TransitSectionAction tsa) {
257        int delay = tsa.getDataWhen();
258        if (delay <= 0) {
259            // no delay, execute action immediately
260            executeAction(tsa);
261        } else {
262            // start thread to trigger delayed action execution
263            Runnable r = new TSActionDelay(tsa, delay);
264            Thread t = jmri.util.ThreadingUtil.newThread( r, "Check Delay on Action");
265            tsa.setWaitingThread(t);
266            t.start();
267        }
268    }
269
270    // this method is called to listen to a Done Sensor if one was provided
271    // if Status is WORKING, and sensor goes Active, Status is set to READY
272    private jmri.Sensor _doneSensor = null;
273    private java.beans.PropertyChangeListener _doneSensorListener = null;
274
275    private synchronized void listenToDoneSensor(TransitSectionAction tsa) {
276        jmri.Sensor s = InstanceManager.sensorManagerInstance().getSensor(tsa.getStringWhat());
277        if (s == null) {
278            log.error("Done Sensor with name - {} - was not found.", tsa.getStringWhat());
279            return;
280        }
281        _doneSensor = s;
282        // set up listener
283        s.addPropertyChangeListener(_doneSensorListener
284                = new java.beans.PropertyChangeListener() {
285            @Override
286            public void propertyChange(java.beans.PropertyChangeEvent e) {
287                if (e.getPropertyName().equals("KnownState")) {
288                    int state = _doneSensor.getKnownState();
289                    if (state == Sensor.ACTIVE) {
290                        if (_activeTrain.getStatus() == ActiveTrain.WORKING) {
291                            _activeTrain.getAutoActiveTrain().resumeAutomaticRunning();
292                        }
293                    }
294                }
295            }
296        });
297    }
298
299    protected synchronized void cancelDoneSensor() {
300        if (_doneSensor != null) {
301            if (_doneSensorListener != null) {
302                _doneSensor.removePropertyChangeListener(_doneSensorListener);
303            }
304            _doneSensorListener = null;
305            _doneSensor = null;
306        }
307    }
308
309    // this method is called to execute the action, when the "When" event has happened.
310    // it is "public" because it may be called from a TransitSectionAction.
311// djd debugging - need to check this out - probably useless, but harmless
312    @SuppressFBWarnings(value = "SWL_SLEEP_WITH_LOCK_HELD",
313            justification = "used only by thread that can be stopped, no conflict with other threads expected")
314    public synchronized void executeAction(TransitSectionAction tsa) {
315        if (tsa == null) {
316            log.error("executeAction called with null TransitSectionAction");
317            return;
318        }
319        Sensor s = null;
320        float temp = 0.0f;
321        switch (tsa.getWhatCode()) {
322            case TransitSectionAction.PAUSE:
323                // pause for a number of fast minutes--e.g. station stop
324                if (_autoActiveTrain.getCurrentAllocatedSection().getNextSection() != null) {
325                    // pause train if this is not the last Section
326                    Thread tPause = _autoActiveTrain.pauseTrain(tsa.getDataWhat1());
327                    tsa.setWaitingThread(tPause);
328                }
329                break;
330            case TransitSectionAction.SETMAXSPEED:
331                // set maximum train speed to value
332                temp = tsa.getDataWhat1();
333                _autoActiveTrain.setMaxSpeed(temp * 0.01f);
334                completedAction(tsa);
335                break;
336            case TransitSectionAction.SETCURRENTSPEED:
337                // set current speed either higher or lower than current value
338                temp = tsa.getDataWhat1();
339                float spd = temp * 0.01f;
340                if (spd > _autoActiveTrain.getMaxSpeed()) {
341                    spd = _autoActiveTrain.getMaxSpeed();
342                }
343                _autoActiveTrain.getAutoEngineer().setSpeedImmediate(spd * _autoActiveTrain.getSpeedFactor());
344                completedAction(tsa);
345                break;
346            case TransitSectionAction.RAMPTRAINSPEED:
347                // set current speed to target using specified ramp rate
348                temp = tsa.getDataWhat1();
349                float spdx = temp * 0.01f;
350                if (spdx > _autoActiveTrain.getMaxSpeed()) {
351                    spdx = _autoActiveTrain.getMaxSpeed();
352                }
353                _autoActiveTrain.setTargetSpeed(spdx * _autoActiveTrain.getSpeedFactor());
354                completedAction(tsa);
355                break;
356            case TransitSectionAction.TOMANUALMODE:
357                // drop out of automated mode and allow manual throttle control
358                _autoActiveTrain.initiateWorking();
359                if ((tsa.getStringWhat() != null) && (!tsa.getStringWhat().equals(""))) {
360                    // optional Done sensor was provided, listen to it
361                    listenToDoneSensor(tsa);
362                }
363                completedAction(tsa);
364                break;
365            case TransitSectionAction.SETLIGHT:
366                // set light on or off
367                if (_autoActiveTrain.getAutoEngineer() != null) {
368                    log.debug("{}: setting light (F0) to {}", _activeTrain.getTrainName(), tsa.getStringWhat());
369                    if (tsa.getStringWhat().equals("On")) {
370                        _autoActiveTrain.getAutoEngineer().setFunction(0, true);
371                    } else if (tsa.getStringWhat().equals("Off")) {
372                        _autoActiveTrain.getAutoEngineer().setFunction(0, false);
373                    } else {
374                        log.error("Incorrect Light ON/OFF setting *{}*", tsa.getStringWhat());
375                    }
376                }
377                completedAction(tsa);
378                break;
379            case TransitSectionAction.STARTBELL:
380                // start bell (only works with sound decoder)
381                if (_autoActiveTrain.getSoundDecoder() && (_autoActiveTrain.getAutoEngineer() != null)) {
382                    log.debug("{}: starting bell (F1)", _activeTrain.getTrainName());
383                    _autoActiveTrain.getAutoEngineer().setFunction(1, true);
384                }
385                completedAction(tsa);
386                break;
387            case TransitSectionAction.STOPBELL:
388                // stop bell (only works with sound decoder)
389                if (_autoActiveTrain.getSoundDecoder() && (_autoActiveTrain.getAutoEngineer() != null)) {
390                    log.debug("{}: stopping bell (F1)", _activeTrain.getTrainName());
391                    _autoActiveTrain.getAutoEngineer().setFunction(1, false);
392                }
393                completedAction(tsa);
394                break;
395            case TransitSectionAction.SOUNDHORN:
396            // sound horn for specified number of milliseconds - done in separate thread
397            case TransitSectionAction.SOUNDHORNPATTERN:
398                // sound horn according to specified pattern - done in separate thread
399                if (_autoActiveTrain.getSoundDecoder()) {
400                    log.debug("{}: sounding horn as specified in action", _activeTrain.getTrainName());
401                    Runnable rHorn = new HornExecution(tsa);
402                    Thread tHorn = jmri.util.ThreadingUtil.newThread(rHorn);
403                    tsa.setWaitingThread(tHorn);
404                    tHorn.start();
405                } else {
406                    completedAction(tsa);
407                }
408                break;
409            case TransitSectionAction.LOCOFUNCTION:
410                // execute the specified decoder function
411                if (_autoActiveTrain.getAutoEngineer() != null) {
412                    log.debug("{}: setting function {} to {}", _activeTrain.getTrainName(),
413                            tsa.getDataWhat1(), tsa.getStringWhat());
414                    int fun = tsa.getDataWhat1();
415                    if (tsa.getStringWhat().equals("On")) {
416                        _autoActiveTrain.getAutoEngineer().setFunction(fun, true);
417                    } else if (tsa.getStringWhat().equals("Off")) {
418                        _autoActiveTrain.getAutoEngineer().setFunction(fun, false);
419                    }
420                }
421                completedAction(tsa);
422                break;
423            case TransitSectionAction.SETSENSORACTIVE:
424                // set specified sensor active
425                s = InstanceManager.sensorManagerInstance().getSensor(tsa.getStringWhat());
426                if (s != null) {
427                    // if sensor is already active, set it to inactive first
428                    if (s.getKnownState() == Sensor.ACTIVE) {
429                        try {
430                            s.setState(Sensor.INACTIVE);
431                        } catch (jmri.JmriException reason) {
432                            log.error("Exception when toggling Sensor {} Inactive - {}", tsa.getStringWhat(), reason);
433                        }
434                    }
435                    try {
436                        s.setState(Sensor.ACTIVE);
437                    } catch (jmri.JmriException reason) {
438                        log.error("Exception when setting Sensor {} Active - {}", tsa.getStringWhat(), reason);
439                    }
440                } else if ((tsa.getStringWhat() != null) && (!tsa.getStringWhat().equals(""))) {
441                    log.error("Could not find Sensor {}", tsa.getStringWhat());
442                } else {
443                    log.error("Sensor not specified for Action");
444                }
445                break;
446            case TransitSectionAction.SETSENSORINACTIVE:
447                // set specified sensor inactive
448                s = InstanceManager.sensorManagerInstance().getSensor(tsa.getStringWhat());
449                if (s != null) {
450                    if (s.getKnownState() == Sensor.ACTIVE) {
451                        try {
452                            s.setState(Sensor.ACTIVE);
453                        } catch (jmri.JmriException reason) {
454                            log.error("Exception when toggling Sensor {} Active - {}", tsa.getStringWhat(), reason);
455                        }
456                    }
457                    try {
458                        s.setState(Sensor.INACTIVE);
459                    } catch (jmri.JmriException reason) {
460                        log.error("Exception when setting Sensor {} Inactive - {}", tsa.getStringWhat(), reason);
461                    }
462                } else if ((tsa.getStringWhat() != null) && (!tsa.getStringWhat().equals(""))) {
463                    log.error("Could not find Sensor {}", tsa.getStringWhat());
464                } else {
465                    log.error("Sensor not specified for Action");
466                }
467                break;
468            case TransitSectionAction.HOLDSIGNAL:
469                // set specified signalhead or signalmast to HELD
470                SignalMast sm = null;
471                SignalHead sh = null;
472                String sName = tsa.getStringWhat();
473                sm = InstanceManager.getDefault(SignalMastManager.class).getSignalMast(sName);
474                if (sm == null) {
475                    sh = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(sName);
476                    if (sh == null) {
477                        log.error("{}: Could not find SignalMast or SignalHead named '{}'", _activeTrain.getTrainName(), sName);
478                    } else {
479                        log.debug("{}: setting signalHead '{}' to HELD", _activeTrain.getTrainName(), sName);
480                        sh.setHeld(true);
481                    }
482                } else {
483                    log.debug("{}: setting signalMast '{}' to HELD", _activeTrain.getTrainName(), sName);
484                    sm.setHeld(true);
485                }
486                break;
487            case TransitSectionAction.RELEASESIGNAL:
488                // set specified signalhead or signalmast to NOT HELD
489                sm = null;
490                sh = null;
491                sName = tsa.getStringWhat();
492                sm = InstanceManager.getDefault(SignalMastManager.class).getSignalMast(sName);
493                if (sm == null) {
494                    sh = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(sName);
495                    if (sh == null) {
496                        log.error("{}: Could not find SignalMast or SignalHead named '{}'", _activeTrain.getTrainName(), sName);
497                    } else {
498                        log.debug("{}: setting signalHead '{}' to NOT HELD", _activeTrain.getTrainName(), sName);
499                        sh.setHeld(false);
500                    }
501                } else {
502                    log.debug("{}: setting signalMast '{}' to NOT HELD", _activeTrain.getTrainName(), sName);
503                    sm.setHeld(false);
504                }
505                break;
506            default:
507                log.error("illegal What code - {} - in call to executeAction", tsa.getWhatCode());
508                break;
509        }
510    }
511
512    /**
513     * A runnable that implements delayed execution of a TransitSectionAction.
514     */
515    class TSActionDelay implements Runnable {
516
517        public TSActionDelay(TransitSectionAction tsa, int delay) {
518            _tsa = tsa;
519            _delay = delay;
520        }
521
522        @Override
523        public void run() {
524            try {
525                Thread.sleep(_delay);
526                executeAction(_tsa);
527            } catch (InterruptedException e) {
528                // interrupting this thread will cause it to terminate without executing the action.
529            }
530        }
531        private TransitSectionAction _tsa = null;
532        private int _delay = 0;
533    }
534
535    class HornExecution implements Runnable {
536
537        /**
538         * Create a HornExecution.
539         *
540         * @param tsa the associated action
541         */
542        public HornExecution(TransitSectionAction tsa) {
543            _tsa = tsa;
544        }
545
546        @Override
547        public void run() {
548            _autoActiveTrain.incrementHornExecution();
549            if (_tsa.getWhatCode() == TransitSectionAction.SOUNDHORN) {
550                if (_autoActiveTrain.getAutoEngineer() != null) {
551                    try {
552                        _autoActiveTrain.getAutoEngineer().setFunction(2, true);
553                        Thread.sleep(_tsa.getDataWhat1());
554                    } catch (InterruptedException e) {
555                        // interrupting will cause termination after turning horn off
556                    }
557                }
558                if (_autoActiveTrain.getAutoEngineer() != null) {
559                    _autoActiveTrain.getAutoEngineer().setFunction(2, false);
560                }
561            } else if (_tsa.getWhatCode() == TransitSectionAction.SOUNDHORNPATTERN) {
562                String pattern = _tsa.getStringWhat();
563                int index = 0;
564                int sleepTime = ((_tsa.getDataWhat1()) * 12) / 10;
565                boolean keepGoing = true;
566                while (keepGoing && (index < pattern.length())) {
567                    // sound horn 
568                    if (_autoActiveTrain.getAutoEngineer() != null) {
569                        _autoActiveTrain.getAutoEngineer().setFunction(2, true);
570                        try {
571                            if (pattern.charAt(index) == 's') {
572                                Thread.sleep(_tsa.getDataWhat1());
573                            } else if (pattern.charAt(index) == 'l') {
574                                Thread.sleep(_tsa.getDataWhat2());
575                            }
576                        } catch (InterruptedException e) {
577                            // interrupting will cause termination after turning horn off
578                            keepGoing = false;
579                        }
580                    } else {
581                        // loss of an autoEngineer will cause termination
582                        keepGoing = false;
583                    }
584                    if (_autoActiveTrain.getAutoEngineer() != null) {
585                        _autoActiveTrain.getAutoEngineer().setFunction(2, false);
586                    } else {
587                        keepGoing = false;
588                    }
589                    index++;
590                    if (keepGoing && (index < pattern.length())) {
591                        try {
592                            Thread.sleep(sleepTime);
593                        } catch (InterruptedException e) {
594                            keepGoing = false;
595                        }
596                    }
597                }
598            }
599            _autoActiveTrain.decrementHornExecution();
600            completedAction(_tsa);
601        }
602        private TransitSectionAction _tsa = null;
603    }
604
605    /**
606     * A runnable to monitor whether the autoActiveTrain is moving or stopped.
607     * Note: If train stops to do work with a manual throttle, this thread will
608     * continue to wait until auto operation is resumed.
609     */
610    class MonitorTrain implements Runnable {
611
612        public MonitorTrain(TransitSectionAction tsa) {
613            _tsa = tsa;
614        }
615
616        @Override
617        public void run() {
618            if (_tsa != null) {
619                boolean waitingOnTrain = true;
620                if (_tsa.getWhenCode() == TransitSectionAction.TRAINSTOP) {
621                    try {
622                        while (waitingOnTrain) {
623                            if ((_autoActiveTrain.getAutoEngineer() != null)
624                                    && (_autoActiveTrain.getAutoEngineer().isStopped())) {
625                                waitingOnTrain = false;
626                            } else {
627                                Thread.sleep(_delay);
628                            }
629                        }
630                        executeAction(_tsa);
631                    } catch (InterruptedException e) {
632                        // interrupting will cause termination without executing the action      
633                    }
634                } else if (_tsa.getWhenCode() == TransitSectionAction.TRAINSTART) {
635                    if ((_autoActiveTrain.getAutoEngineer() != null)
636                            && (!_autoActiveTrain.getAutoEngineer().isStopped())) {
637                        // if train is not currently stopped, wait for it to stop
638                        boolean waitingForStop = true;
639                        try {
640                            while (waitingForStop) {
641                                if ((_autoActiveTrain.getAutoEngineer() != null)
642                                        && (_autoActiveTrain.getAutoEngineer().isStopped())) {
643                                    waitingForStop = false;
644                                } else {
645                                    Thread.sleep(_delay);
646                                }
647                            }
648                        } catch (InterruptedException e) {
649                            // interrupting will cause termination without executing the action      
650                        }
651                    }
652                    // train is stopped, wait for it to start 
653                    try {
654                        while (waitingOnTrain) {
655                            if ((_autoActiveTrain.getAutoEngineer() != null)
656                                    && (!_autoActiveTrain.getAutoEngineer().isStopped())) {
657                                waitingOnTrain = false;
658                            } else {
659                                Thread.sleep(_delay);
660                            }
661                        }
662                        executeAction(_tsa);
663                    } catch (InterruptedException e) {
664                        // interrupting will cause termination without executing the action      
665                    }
666                }
667            }
668        }
669        private int _delay = 50;
670        private TransitSectionAction _tsa = null;
671    }
672
673    /**
674     * A runnable to monitor the autoActiveTrain speed.
675     */
676    class MonitorTrainSpeed implements Runnable {
677
678        public MonitorTrainSpeed(TransitSectionAction tsa) {
679            _tsa = tsa;
680        }
681
682        @Override
683        public void run() {
684            while ((_autoActiveTrain.getAutoEngineer() != null)
685                    && (!_autoActiveTrain.getAutoEngineer().isAtSpeed())) {
686                try {
687                    Thread.sleep(_delay);
688                } catch (InterruptedException e) {
689                    log.error("unexpected interruption of wait for speed");
690                }
691            }
692            _autoActiveTrain.setCurrentRampRate(_autoActiveTrain.getRampRate());
693            if (_tsa != null) {
694                completedAction(_tsa);
695            }
696        }
697        private int _delay = 51;
698        private TransitSectionAction _tsa = null;
699    }
700
701    private final static Logger log = LoggerFactory.getLogger(AutoTrainAction.class);
702}