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}