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}