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.FORCEALLOCATEPASSSAFESECTION:
378                log.trace("Force pass next safe Section [[{}]",tsa.getTargetTransitSection().getSectionName());
379                _activeTrain.forcePassNextSafeSection();
380                break;
381            case TransitSectionAction.LOADTRAININFO:
382                log.info("Section[[{}] LoadTrain [{}]",tsa.getTargetTransitSection().getSectionName(),tsa.getStringWhat());
383                switch (tsa.getDataWhat2()) {
384                    case TransitSectionAction.LOCOADDRESSTYPEROSTER:
385                        log.debug("Spinning off load of {}, using Roster entry {}",tsa.getStringWhat(),tsa.getStringWhat2());
386                        jmri.util.ThreadingUtil.runOnLayoutDelayed(()-> {
387                            InstanceManager.getDefault(DispatcherFrame.class).loadTrainFromTrainInfo(tsa.getStringWhat(),DispatcherFrame.OVERRIDETYPE_ROSTER,tsa.getStringWhat2());},2000);
388                        break;
389                    case TransitSectionAction.LOCOADDRESSTYPENUMBER:
390                        log.debug("Spinning off load of {}, using USER entered address {}",tsa.getStringWhat(),tsa.getStringWhat2());
391                        jmri.util.ThreadingUtil.runOnLayoutDelayed(()-> {
392                            InstanceManager.getDefault(DispatcherFrame.class).loadTrainFromTrainInfo(tsa.getStringWhat(),DispatcherFrame.OVERRIDETYPE_USER,tsa.getStringWhat2());},2000);
393                        break;
394                    case TransitSectionAction.LOCOADDRESSTYPECURRENT:
395                        if ( _activeTrain.getTrainSource() == ActiveTrain.ROSTER) {
396                            log.debug("Spinning off load of {}, using current Roster {}",tsa.getStringWhat(),_activeTrain.getRosterEntry().getId());
397                            jmri.util.ThreadingUtil.runOnLayoutDelayed(()-> {
398                                InstanceManager.getDefault(DispatcherFrame.class).loadTrainFromTrainInfo(tsa.getStringWhat(),
399                                        DispatcherFrame.OVERRIDETYPE_ROSTER,_activeTrain.getRosterEntry().getId());},2000);
400                        } else {
401                            log.debug("Spinning off load of {}, using current address {}",tsa.getStringWhat(),_activeTrain.getDccAddress());
402                            jmri.util.ThreadingUtil.runOnLayoutDelayed(()-> {
403                                InstanceManager.getDefault(DispatcherFrame.class).loadTrainFromTrainInfo(tsa.getStringWhat(),
404                                        DispatcherFrame.OVERRIDETYPE_USER,_activeTrain.getDccAddress());},2000);
405                        }
406                        break;
407                    case TransitSectionAction.LOCOADDRESSTYPEDEFAULT:
408                    default:
409                        log.debug("Spinning off load of {}, using defaults",tsa.getStringWhat());
410                        jmri.util.ThreadingUtil.runOnLayoutDelayed(()-> {
411                            InstanceManager.getDefault(DispatcherFrame.class).loadTrainFromTrainInfo(tsa.getStringWhat(),DispatcherFrame.OVERRIDETYPE_NONE,null);},2000);
412                }
413                break;
414            case TransitSectionAction.PAUSE:
415                log.trace("Pause Started Section:[{}]",tsa.getTargetTransitSection().getSectionName());
416                // pause for a number of fast minutes--e.g. station stop
417                if (_autoActiveTrain.getCurrentAllocatedSection().getNextSection() != null) {
418                    // pause train if this is not the last Section
419                    Thread tPause = _autoActiveTrain.pauseTrain(tsa.getDataWhat1());
420                    tsa.setWaitingThread(tPause);
421                }
422                break;
423            case TransitSectionAction.SETMAXSPEED:
424                // set maximum train speed to value
425                log.trace("Set Max Speed Section:[{}]",tsa.getTargetTransitSection().getSectionName());
426                temp = tsa.getDataWhat1();
427                _autoActiveTrain.setMaxSpeed(temp * 0.01f);
428                completedAction(tsa);
429                break;
430            case TransitSectionAction.PRESTARTRESUME:
431                // set current speed either higher or lower than current value
432                log.trace("Resume After Prestart Setting Target[{}] Section:[{}]",tsa.getDataWhat1Float(),tsa.getTargetTransitSection().getSectionName());
433                _autoActiveTrain.setTargetSpeedByPass (tsa.getDataWhat1Float());
434                completedAction(tsa);
435                break;
436            case TransitSectionAction.SETCURRENTSPEED:
437                // set current speed either higher or lower than current value
438                log.trace("Set Current Speed Section:[{}]",tsa.getTargetTransitSection().getSectionName());
439                temp = tsa.getDataWhat1();
440                float spd = temp * 0.01f;
441                if (spd > _autoActiveTrain.getMaxSpeed()) {
442                    spd = _autoActiveTrain.getMaxSpeed();
443                }
444                _autoActiveTrain.getAutoEngineer().setSpeedImmediate(spd * _autoActiveTrain.getSpeedFactor());
445                completedAction(tsa);
446                break;
447            case TransitSectionAction.RAMPTRAINSPEED:
448                // set current speed to target using specified ramp rate
449                log.trace("Set Ramp Speed Section:[{}]",tsa.getTargetTransitSection().getSectionName());
450                temp = tsa.getDataWhat1();
451                float spdx = temp * 0.01f;
452                if (spdx > _autoActiveTrain.getMaxSpeed()) {
453                    spdx = _autoActiveTrain.getMaxSpeed();
454                }
455                _autoActiveTrain.setTargetSpeed(spdx * _autoActiveTrain.getSpeedFactor());
456                completedAction(tsa);
457                break;
458            case TransitSectionAction.TOMANUALMODE:
459                // drop out of automated mode and allow manual throttle control
460                log.trace("Goto Manual Section:[{}]",tsa.getTargetTransitSection().getSectionName());
461                _autoActiveTrain.initiateWorking();
462                if ((tsa.getStringWhat() != null) && (!tsa.getStringWhat().equals(""))) {
463                    // optional Done sensor was provided, listen to it
464                    listenToDoneSensor(tsa);
465                }
466                completedAction(tsa);
467                break;
468            case TransitSectionAction.SETLIGHT:
469                // set light on or off
470                log.trace("Set Light Section:[{}]",tsa.getTargetTransitSection().getSectionName());
471                if (_autoActiveTrain.getAutoEngineer() != null) {
472                    log.trace("{}: setting light (F0) to {}", _activeTrain.getTrainName(), tsa.getStringWhat());
473                    if (tsa.getStringWhat().equals("On")) {
474                        _autoActiveTrain.getAutoEngineer().setFunction(0, true);
475                    } else if (tsa.getStringWhat().equals("Off")) {
476                        _autoActiveTrain.getAutoEngineer().setFunction(0, false);
477                    } else {
478                        log.error("Incorrect Light ON/OFF setting *{}*", tsa.getStringWhat());
479                    }
480                }
481                completedAction(tsa);
482                break;
483            case TransitSectionAction.STARTBELL:
484                // start bell (only works with sound decoder)
485                log.trace("Set Start Bell Section:[{}]",tsa.getTargetTransitSection().getSectionName());
486                if (_autoActiveTrain.getSoundDecoder() && (_autoActiveTrain.getAutoEngineer() != null)) {
487                    log.trace("{}: starting bell (F1)", _activeTrain.getTrainName());
488                    _autoActiveTrain.getAutoEngineer().setFunction(1, true);
489                }
490                completedAction(tsa);
491                break;
492            case TransitSectionAction.STOPBELL:
493                // stop bell (only works with sound decoder)
494                log.trace("Set Stop Bell Section:[{}]",tsa.getTargetTransitSection().getSectionName());
495                if (_autoActiveTrain.getSoundDecoder() && (_autoActiveTrain.getAutoEngineer() != null)) {
496                    log.trace("{}: stopping bell (F1)", _activeTrain.getTrainName());
497                    _autoActiveTrain.getAutoEngineer().setFunction(1, false);
498                }
499                completedAction(tsa);
500                break;
501            case TransitSectionAction.SOUNDHORN:
502            // sound horn for specified number of milliseconds - done in separate thread
503            case TransitSectionAction.SOUNDHORNPATTERN:
504                // sound horn according to specified pattern - done in separate thread
505                log.trace("Sound Horn or Pattern Section:[{}]",tsa.getTargetTransitSection().getSectionName());
506                if (_autoActiveTrain.getSoundDecoder()) {
507                    log.trace("{}: sounding horn as specified in action", _activeTrain.getTrainName());
508                    Runnable rHorn = new HornExecution(tsa);
509                    Thread tHorn = jmri.util.ThreadingUtil.newThread(rHorn);
510                    tsa.setWaitingThread(tHorn);
511                    tHorn.start();
512                } else {
513                    completedAction(tsa);
514                }
515                break;
516            case TransitSectionAction.LOCOFUNCTION:
517                // execute the specified decoder function
518                log.trace("Set Loco Function Section:[{}]",tsa.getTargetTransitSection().getSectionName());
519                if (_autoActiveTrain.getAutoEngineer() != null) {
520                    log.trace("{}: setting function {} to {}", _activeTrain.getTrainName(),
521                            tsa.getDataWhat1(), tsa.getStringWhat());
522                    int fun = tsa.getDataWhat1();
523                    if (tsa.getStringWhat().equals("On")) {
524                        _autoActiveTrain.getAutoEngineer().setFunction(fun, true);
525                    } else if (tsa.getStringWhat().equals("Off")) {
526                        _autoActiveTrain.getAutoEngineer().setFunction(fun, false);
527                    }
528                }
529                completedAction(tsa);
530                break;
531            case TransitSectionAction.SETSENSORACTIVE:
532                // set specified sensor active
533                log.trace("Set Sensor Active Section:[{}]",tsa.getTargetTransitSection().getSectionName());
534                s = InstanceManager.sensorManagerInstance().getSensor(tsa.getStringWhat());
535                if (s != null) {
536                    // if sensor is already active, set it to inactive first
537                    if (s.getKnownState() == Sensor.ACTIVE) {
538                        try {
539                            s.setState(Sensor.INACTIVE);
540                        } catch (jmri.JmriException reason) {
541                            log.error("Exception when toggling Sensor {} Inactive", tsa.getStringWhat(), reason);
542                        }
543                    }
544                    try {
545                        s.setState(Sensor.ACTIVE);
546                    } catch (jmri.JmriException reason) {
547                        log.error("Exception when setting Sensor {} Active", tsa.getStringWhat(), reason);
548                    }
549                } else if ((tsa.getStringWhat() != null) && (!tsa.getStringWhat().equals(""))) {
550                    log.error("Could not find Sensor {}", tsa.getStringWhat());
551                } else {
552                    log.error("Sensor not specified for Action");
553                }
554                break;
555            case TransitSectionAction.SETSENSORINACTIVE:
556                // set specified sensor inactive
557                log.trace("Set Sensor Inactive Section:[{}]",tsa.getTargetTransitSection().getSectionName());
558                s = InstanceManager.sensorManagerInstance().getSensor(tsa.getStringWhat());
559                if (s != null) {
560                    if (s.getKnownState() == Sensor.ACTIVE) {
561                        try {
562                            s.setState(Sensor.ACTIVE);
563                        } catch (jmri.JmriException reason) {
564                            log.error("Exception when toggling Sensor {} Active", tsa.getStringWhat(), reason);
565                        }
566                    }
567                    try {
568                        s.setState(Sensor.INACTIVE);
569                    } catch (jmri.JmriException reason) {
570                        log.error("Exception when setting Sensor {} Inactive", tsa.getStringWhat(), reason);
571                    }
572                } else if ((tsa.getStringWhat() != null) && (!tsa.getStringWhat().equals(""))) {
573                    log.error("Could not find Sensor {}", tsa.getStringWhat());
574                } else {
575                    log.error("Sensor not specified for Action");
576                }
577                break;
578            case TransitSectionAction.HOLDSIGNAL:
579                // set specified signalhead or signalmast to HELD
580                log.trace("Set Hold Section:[{}]",tsa.getTargetTransitSection().getSectionName());
581                SignalMast sm = null;
582                SignalHead sh = null;
583                String sName = tsa.getStringWhat();
584                sm = InstanceManager.getDefault(SignalMastManager.class).getSignalMast(sName);
585                if (sm == null) {
586                    sh = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(sName);
587                    if (sh == null) {
588                        log.error("{}: Could not find SignalMast or SignalHead named '{}'", _activeTrain.getTrainName(), sName);
589                    } else {
590                        log.trace("{}: setting signalHead '{}' to HELD", _activeTrain.getTrainName(), sName);
591                        sh.setHeld(true);
592                    }
593                } else {
594                    log.trace("{}: setting signalMast '{}' to HELD", _activeTrain.getTrainName(), sName);
595                    sm.setHeld(true);
596                }
597                break;
598            case TransitSectionAction.RELEASESIGNAL:
599                // set specified signalhead or signalmast to NOT HELD
600                log.trace("Set Release Hold Section:[{}]",tsa.getTargetTransitSection().getSectionName());
601               sm = null;
602                sh = null;
603                sName = tsa.getStringWhat();
604                sm = InstanceManager.getDefault(SignalMastManager.class).getSignalMast(sName);
605                if (sm == null) {
606                    sh = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(sName);
607                    if (sh == null) {
608                        log.error("{}: Could not find SignalMast or SignalHead named '{}'", _activeTrain.getTrainName(), sName);
609                    } else {
610                        log.trace("{}: setting signalHead '{}' to NOT HELD", _activeTrain.getTrainName(), sName);
611                        sh.setHeld(false);
612                    }
613                } else {
614                    log.trace("{}: setting signalMast '{}' to NOT HELD", _activeTrain.getTrainName(), sName);
615                    sm.setHeld(false);
616                }
617                break;
618            case TransitSectionAction.ESTOP:
619                log.trace("EStop Section:[{}]",tsa.getTargetTransitSection().getSectionName());
620                _autoActiveTrain.getAutoEngineer().setSpeedImmediate(-1);
621                break;
622            default:
623                log.error("illegal What code - {} - in call to executeAction  Section:[{}]",
624                        tsa.getWhatCode(),tsa.getTargetTransitSection().getSectionName());
625                break;
626        }
627    }
628
629    /**
630     * A runnable that implements delayed execution of a TransitSectionAction.
631     */
632    class TSActionDelay implements Runnable {
633
634        public TSActionDelay(TransitSectionAction tsa, int delay) {
635            _tsa = tsa;
636            _delay = delay;
637            log.debug("Delay Starting for Code [{}] in Section [{}] for [{}]",
638                    tsa.getWhatCode(),tsa.getTargetTransitSection().getSectionName(),delay);
639        }
640
641        @Override
642        public void run() {
643            try {
644                Thread.sleep(_delay);
645                executeAction(_tsa);
646            } catch (InterruptedException e) {
647                // interrupting this thread will cause it to terminate without executing the action.
648            }
649        }
650        private TransitSectionAction _tsa = null;
651        private int _delay = 0;
652    }
653
654    class HornExecution implements Runnable {
655
656        /**
657         * Create a HornExecution.
658         *
659         * @param tsa the associated action
660         */
661        public HornExecution(TransitSectionAction tsa) {
662            _tsa = tsa;
663        }
664
665        @Override
666        public void run() {
667            _autoActiveTrain.incrementHornExecution();
668            if (_tsa.getWhatCode() == TransitSectionAction.SOUNDHORN) {
669                if (_autoActiveTrain.getAutoEngineer() != null) {
670                    try {
671                        _autoActiveTrain.getAutoEngineer().setFunction(2, true);
672                        Thread.sleep(_tsa.getDataWhat1());
673                    } catch (InterruptedException e) {
674                        // interrupting will cause termination after turning horn off
675                    }
676                }
677                if (_autoActiveTrain.getAutoEngineer() != null) {
678                    _autoActiveTrain.getAutoEngineer().setFunction(2, false);
679                }
680            } else if (_tsa.getWhatCode() == TransitSectionAction.SOUNDHORNPATTERN) {
681                String pattern = _tsa.getStringWhat();
682                int index = 0;
683                int sleepTime = ((_tsa.getDataWhat1()) * 12) / 10;
684                boolean keepGoing = true;
685                while (keepGoing && (index < pattern.length())) {
686                    // sound horn
687                    if (_autoActiveTrain.getAutoEngineer() != null) {
688                        _autoActiveTrain.getAutoEngineer().setFunction(2, true);
689                        try {
690                            if (pattern.charAt(index) == 's') {
691                                Thread.sleep(_tsa.getDataWhat1());
692                            } else if (pattern.charAt(index) == 'l') {
693                                Thread.sleep(_tsa.getDataWhat2());
694                            }
695                        } catch (InterruptedException e) {
696                            // interrupting will cause termination after turning horn off
697                            keepGoing = false;
698                        }
699                    } else {
700                        // loss of an autoEngineer will cause termination
701                        keepGoing = false;
702                    }
703                    if (_autoActiveTrain.getAutoEngineer() != null) {
704                        _autoActiveTrain.getAutoEngineer().setFunction(2, false);
705                    } else {
706                        keepGoing = false;
707                    }
708                    index++;
709                    if (keepGoing && (index < pattern.length())) {
710                        try {
711                            Thread.sleep(sleepTime);
712                        } catch (InterruptedException e) {
713                            keepGoing = false;
714                        }
715                    }
716                }
717            }
718            _autoActiveTrain.decrementHornExecution();
719            completedAction(_tsa);
720        }
721        private TransitSectionAction _tsa = null;
722    }
723
724    /**
725     * A runnable to monitor whether the autoActiveTrain is moving or stopped.
726     * Note: If train stops to do work with a manual throttle, this thread will
727     * continue to wait until auto operation is resumed.
728     */
729    class MonitorTrain implements Runnable {
730
731        public MonitorTrain(TransitSectionAction tsa) {
732            _tsa = tsa;
733        }
734
735        @Override
736        public void run() {
737            if (_tsa != null) {
738                boolean waitingOnTrain = true;
739                if (_tsa.getWhenCode() == TransitSectionAction.TRAINSTOP) {
740                    try {
741                        while (waitingOnTrain) {
742                            if ((_autoActiveTrain.getAutoEngineer() != null)
743                                    && (_autoActiveTrain.getAutoEngineer().isStopped())) {
744                                waitingOnTrain = false;
745                            } else {
746                                Thread.sleep(_delay);
747                            }
748                        }
749                        checkDelay(_tsa);
750                    } catch (InterruptedException e) {
751                        // interrupting will cause termination without executing the action
752                    }
753                } else if (_tsa.getWhenCode() == TransitSectionAction.TRAINSTART) {
754                    if ( _autoActiveTrain.getThrottle() != null
755                            && _autoActiveTrain.getAutoEngineer() != null
756                            && !_autoActiveTrain.getAutoEngineer().isStopped()) {
757                        // if train is not currently stopped, wait for it to stop
758                        boolean waitingForStop = true;
759                        try {
760                            while (waitingForStop) {
761                                if ((_autoActiveTrain.getAutoEngineer() != null)
762                                        && (_autoActiveTrain.getAutoEngineer().isStopped())) {
763                                    waitingForStop = false;
764                                } else {
765                                    Thread.sleep(_delay);
766                                }
767                            }
768                        } catch (InterruptedException e) {
769                            // interrupting will cause termination without executing the action
770                        }
771                    }
772                    // train is stopped, wait for it to start
773                    try {
774                        while (waitingOnTrain) {
775                            if ( _autoActiveTrain.getThrottle() != null
776                                    && _autoActiveTrain.getAutoEngineer() != null
777                                    && !_autoActiveTrain.getAutoEngineer().isStopped()) {
778                                waitingOnTrain = false;
779                            } else {
780                                Thread.sleep(_delay);
781                            }
782                        }
783                        checkDelay(_tsa);
784                    } catch (InterruptedException e) {
785                        // interrupting will cause termination without executing the action
786                    }
787                }
788            }
789        }
790        private int _delay = 50;
791        private TransitSectionAction _tsa = null;
792    }
793
794    /**
795     * A runnable to monitor the autoActiveTrain speed.
796     */
797    class MonitorTrainSpeed implements Runnable {
798
799        public MonitorTrainSpeed(TransitSectionAction tsa) {
800            _tsa = tsa;
801        }
802
803        @Override
804        public void run() {
805            while ((_autoActiveTrain.getAutoEngineer() != null)
806                    && (!_autoActiveTrain.getAutoEngineer().isAtSpeed())) {
807                try {
808                    Thread.sleep(_delay);
809                } catch (InterruptedException e) {
810                    log.error("unexpected interruption of wait for speed");
811                }
812            }
813            _autoActiveTrain.setCurrentRampRate(_autoActiveTrain.getRampRate());
814            if (_tsa != null) {
815                completedAction(_tsa);
816            }
817        }
818        private int _delay = 51;
819        private TransitSectionAction _tsa = null;
820    }
821
822    private final static Logger log = LoggerFactory.getLogger(AutoTrainAction.class);
823}