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