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