001package jmri.implementation; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.io.File; 006import java.util.ArrayList; 007import java.util.List; 008 009import javax.annotation.CheckForNull; 010import javax.annotation.Nonnull; 011 012import jmri.InstanceManager; 013import jmri.JmriException; 014import jmri.NamedBean; 015import jmri.NamedBeanHandle; 016import jmri.NamedBeanUsageReport; 017import jmri.Route; 018import jmri.Sensor; 019import jmri.Turnout; 020import jmri.jmrit.Sound; 021import jmri.script.JmriScriptEngineManager; 022import jmri.util.ThreadingUtil; 023 024import org.slf4j.Logger; 025import org.slf4j.LoggerFactory; 026 027/** 028 * Class providing the basic logic of the Route interface. 029 * 030 * @see jmri.Route Route 031 * @author Dave Duchamp Copyright (C) 2004 032 * @author Bob Jacobsen Copyright (C) 2006, 2007 033 * @author Simon Reader Copyright (C) 2008 034 * @author Pete Cressman Copyright (C) 2009 035 */ 036public class DefaultRoute extends AbstractNamedBean implements Route, java.beans.VetoableChangeListener { 037 038 /** 039 * Constructor for a Route instance with a given userName. 040 * 041 * @param systemName suggested system name 042 * @param userName provided user name 043 */ 044 public DefaultRoute(String systemName, String userName) { 045 super(systemName, userName); 046 } 047 048 /** 049 * Constructor for a Route instance. 050 * 051 * @param systemName suggested system name 052 */ 053 public DefaultRoute(String systemName) { 054 super(systemName); 055 log.debug("default Route {} created", systemName); 056 } 057 058 /** {@inheritDoc} */ 059 @Override 060 public String getBeanType() { 061 return Bundle.getMessage("BeanNameRoute"); 062 } 063 064 /** 065 * Persistant instance variables (saved between runs). 066 */ 067 private String mControlTurnout = ""; 068 private NamedBeanHandle<Turnout> mControlNamedTurnout = null; 069 private int mControlTurnoutState = jmri.Turnout.THROWN; 070 private int mDelay = 0; 071 private boolean mTurnoutFeedbackIsCommanded = false; 072 073 private String mLockControlTurnout = ""; 074 private NamedBeanHandle<Turnout> mLockControlNamedTurnout = null; 075 private int mLockControlTurnoutState = Turnout.THROWN; 076 077 private String mTurnoutsAlignedSensor = ""; 078 private NamedBeanHandle<Sensor> mTurnoutsAlignedNamedSensor = null; 079 080 private String soundFilename; 081 private String scriptFilename; 082 083 private final jmri.NamedBeanHandleManager nbhm = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class); 084 085 /** 086 * Operational instance variables (not saved between runs). 087 */ 088 private ArrayList<OutputSensor> _outputSensorList = new ArrayList<>(); 089 090 private class OutputSensor { 091 092 NamedBeanHandle<Sensor> _sensor; 093 int _state = Sensor.ACTIVE; 094 095 OutputSensor(String name) throws IllegalArgumentException { 096 Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(name); 097 _sensor = nbhm.getNamedBeanHandle(name, sensor); 098 } 099 100 String getName() { 101 if (_sensor != null) { 102 return _sensor.getName(); 103 } 104 return null; 105 } 106 107 boolean setState(int state) { 108 if (_sensor == null) { 109 return false; 110 } 111 if ((state != Sensor.ACTIVE) && (state != Sensor.INACTIVE) && (state != Route.TOGGLE)) { 112 log.warn("Illegal Sensor state for Route: {}", getName()); 113 return false; 114 } 115 _state = state; 116 return true; 117 } 118 119 int getState() { 120 return _state; 121 } 122 123 Sensor getSensor() { 124 if (_sensor != null) { 125 return _sensor.getBean(); 126 } 127 return null; 128 } 129 } 130 131 private ArrayList<ControlSensor> _controlSensorList = new ArrayList<>(); 132 133 private class ControlSensor extends OutputSensor implements PropertyChangeListener { 134 135 ControlSensor(String name) { 136 super(name); 137 } 138 139 @Override 140 boolean setState(int state) { 141 if (_sensor == null) { 142 return false; 143 } 144 _state = state; 145 return true; 146 } 147 148 void addListener() { 149 if (_sensor != null) { 150 _sensor.getBean().addPropertyChangeListener(this, getName(), 151 "Route " + getDisplayName() + "Output Sensor"); 152 } 153 } 154 155 void removeListener() { 156 if (_sensor != null) { 157 _sensor.getBean().removePropertyChangeListener(this); 158 } 159 } 160 161 @Override 162 public void propertyChange(PropertyChangeEvent e) { 163 if ( Sensor.PROPERTY_KNOWN_STATE.equals(e.getPropertyName())) { 164 int now = ((Integer) e.getNewValue()); 165 int then = ((Integer) e.getOldValue()); 166 checkSensor(now, then, (Sensor) e.getSource()); 167 } 168 } 169 } 170 171 private transient PropertyChangeListener mTurnoutListener = null; 172 private transient PropertyChangeListener mLockTurnoutListener = null; 173 174 private ArrayList<OutputTurnout> _outputTurnoutList = new ArrayList<>(); 175 176 private class OutputTurnout implements PropertyChangeListener { 177 178 NamedBeanHandle<Turnout> _turnout; 179 int _state; 180 181 OutputTurnout(String name) throws IllegalArgumentException { 182 Turnout turnout = InstanceManager.turnoutManagerInstance().provideTurnout(name); 183 _turnout = nbhm.getNamedBeanHandle(name, turnout); 184 185 } 186 187 String getName() { 188 if (_turnout != null) { 189 return _turnout.getName(); 190 } 191 return null; 192 } 193 194 boolean setState(int state) { 195 if (_turnout == null) { 196 return false; 197 } 198 if ((state != Turnout.THROWN) && (state != Turnout.CLOSED) && (state != Route.TOGGLE)) { 199 log.warn("Illegal Turnout state for Route: {}", getName()); 200 return false; 201 } 202 _state = state; 203 return true; 204 } 205 206 int getState() { 207 return _state; 208 } 209 210 Turnout getTurnout() { 211 return (_turnout != null ? _turnout.getBean() : null); 212 } 213 214 void addListener() { 215 if (_turnout != null) { 216 _turnout.getBean().addPropertyChangeListener(this, getName(), 217 "Route " + getDisplayName() + " Output Turnout"); 218 } 219 } 220 221 void removeListener() { 222 if (_turnout != null) { 223 _turnout.getBean().removePropertyChangeListener(this); 224 } 225 } 226 227 @Override 228 public void propertyChange(PropertyChangeEvent e) { 229 if ( Turnout.PROPERTY_KNOWN_STATE.equals(e.getPropertyName()) 230 || Turnout.PROPERTY_COMMANDED_STATE.equals(e.getPropertyName())) { 231 //check alignement of all turnouts in route 232 checkTurnoutAlignment(); 233 } 234 } 235 } 236 private boolean busy = false; 237 private boolean _enabled = true; 238 239 /** {@inheritDoc} */ 240 @Override 241 public boolean getEnabled() { 242 return _enabled; 243 } 244 245 /** {@inheritDoc} */ 246 @Override 247 public void setEnabled(boolean v) { 248 boolean old = _enabled; 249 _enabled = v; 250 if (old != v) { 251 firePropertyChange(PROPERTY_ENABLED, old, v); 252 } 253 } 254 255 private boolean _locked = false; 256 257 /** {@inheritDoc} */ 258 @Override 259 public boolean getLocked() { 260 return _locked; 261 } 262 263 /** {@inheritDoc} */ 264 @Override 265 public void setLocked(boolean v) { 266 lockTurnouts(v); 267 boolean old = _locked; 268 _locked = v; 269 if (old != v) { 270 firePropertyChange(PROPERTY_ROUTE_LOCKED, old, v); 271 } 272 } 273 274 /** {@inheritDoc} */ 275 @Override 276 public boolean canLock() { 277 for ( OutputTurnout oto : _outputTurnoutList){ 278 Turnout to = oto.getTurnout(); 279 if ( to !=null && to.canLock(Turnout.CABLOCKOUT)) { 280 return true; 281 } 282 } 283 return false; 284 } 285 286 /** {@inheritDoc} */ 287 @Override 288 public boolean addOutputTurnout(String turnoutName, int turnoutState) { 289 OutputTurnout outputTurnout = new OutputTurnout(turnoutName); 290 if (!outputTurnout.setState(turnoutState)) { 291 return false; 292 } 293 _outputTurnoutList.add(outputTurnout); 294 return true; 295 } 296 297 /** {@inheritDoc} */ 298 @Override 299 public void clearOutputTurnouts() { 300 _outputTurnoutList = new ArrayList<>(); 301 } 302 303 /** {@inheritDoc} */ 304 @Override 305 public int getNumOutputTurnouts() { 306 return _outputTurnoutList.size(); 307 } 308 309 /** {@inheritDoc} */ 310 @Override 311 @CheckForNull 312 public String getOutputTurnoutByIndex(int index) { 313 try { 314 return _outputTurnoutList.get(index).getName(); 315 } catch (IndexOutOfBoundsException ioob) { 316 return null; 317 } 318 } 319 320 /** {@inheritDoc} */ 321 @Override 322 public boolean isOutputTurnoutIncluded(String turnoutName) throws IllegalArgumentException { 323 Turnout t1 = InstanceManager.turnoutManagerInstance().provideTurnout(turnoutName); 324 return isOutputTurnoutIncluded(t1); 325 } 326 327 private boolean isOutputTurnoutIncluded(Turnout t1) { 328 for (int i = 0; i < _outputTurnoutList.size(); i++) { 329 if (_outputTurnoutList.get(i).getTurnout() == t1) { 330 // Found turnout 331 return true; 332 } 333 } 334 return false; 335 } 336 337 private void deleteOutputTurnout(Turnout t) { 338 int index = -1; 339 for (int i = 0; i < _outputTurnoutList.size(); i++) { 340 if (_outputTurnoutList.get(i).getTurnout() == t) { 341 index = i; 342 break; 343 } 344 } 345 if (index != -1) { 346 _outputTurnoutList.remove(index); 347 } 348 } 349 350 /** {@inheritDoc} */ 351 @Override 352 public int getOutputTurnoutSetState(String name) throws IllegalArgumentException { 353 Turnout t1 = InstanceManager.turnoutManagerInstance().provideTurnout(name); 354 for (int i = 0; i < _outputTurnoutList.size(); i++) { 355 if (_outputTurnoutList.get(i).getTurnout() == t1) { 356 // Found turnout 357 return _outputTurnoutList.get(i).getState(); 358 } 359 } 360 return -1; 361 } 362 363 /** {@inheritDoc} */ 364 @Override 365 @CheckForNull 366 public Turnout getOutputTurnout(int k) { 367 try { 368 return _outputTurnoutList.get(k).getTurnout(); 369 } catch (IndexOutOfBoundsException ioob) { 370 return null; 371 } 372 } 373 374 /** {@inheritDoc} */ 375 @Override 376 public int getOutputTurnoutState(int k) { 377 try { 378 return _outputTurnoutList.get(k).getState(); 379 } catch (IndexOutOfBoundsException ioob) { 380 return -1; 381 } 382 } 383 384 /** {@inheritDoc} */ 385 @Override 386 public boolean addOutputSensor(String sensorName, int state) { 387 OutputSensor outputSensor = new OutputSensor(sensorName); 388 if (!outputSensor.setState(state)) { 389 return false; 390 } 391 _outputSensorList.add(outputSensor); 392 return true; 393 } 394 395 /** {@inheritDoc} */ 396 @Override 397 public void clearOutputSensors() { 398 _outputSensorList = new ArrayList<>(); 399 } 400 401 @Override 402 public int getNumOutputSensors() { 403 return _outputSensorList.size(); 404 } 405 406 /** {@inheritDoc} */ 407 @Override 408 @CheckForNull 409 public String getOutputSensorByIndex(int index) { 410 try { 411 return _outputSensorList.get(index).getName(); 412 } catch (IndexOutOfBoundsException ioob) { 413 return null; 414 } 415 } 416 417 /** {@inheritDoc} */ 418 @Override 419 public boolean isOutputSensorIncluded(String sensorName) throws IllegalArgumentException { 420 Sensor s1 = InstanceManager.sensorManagerInstance().provideSensor(sensorName); 421 return isOutputSensorIncluded(s1); 422 } 423 424 private boolean isOutputSensorIncluded(Sensor s1) { 425 for (int i = 0; i < _outputSensorList.size(); i++) { 426 if (_outputSensorList.get(i).getSensor() == s1) { 427 // Found turnout 428 return true; 429 } 430 } 431 return false; 432 } 433 434 /** {@inheritDoc} */ 435 @Override 436 public int getOutputSensorSetState(String name) throws IllegalArgumentException { 437 Sensor s1 = InstanceManager.sensorManagerInstance().provideSensor(name); 438 for (int i = 0; i < _outputSensorList.size(); i++) { 439 if (_outputSensorList.get(i).getSensor() == s1) { 440 // Found turnout 441 return _outputSensorList.get(i).getState(); 442 } 443 } 444 return -1; 445 } 446 447 /** {@inheritDoc} */ 448 @Override 449 @CheckForNull 450 public Sensor getOutputSensor(int k) { 451 try { 452 return _outputSensorList.get(k).getSensor(); 453 } catch (IndexOutOfBoundsException ioob) { 454 return null; 455 } 456 } 457 458 /** {@inheritDoc} */ 459 @Override 460 public int getOutputSensorState(int k) { 461 try { 462 return _outputSensorList.get(k).getState(); 463 } catch (IndexOutOfBoundsException ioob) { 464 return -1; 465 } 466 } 467 468 private void removeOutputSensor(Sensor s) { 469 int index = -1; 470 for (int i = 0; i < _outputSensorList.size(); i++) { 471 if (_outputSensorList.get(i).getSensor() == s) { 472 index = i; 473 break; 474 } 475 } 476 if (index != -1) { 477 _outputSensorList.remove(index); 478 } 479 } 480 481 /** {@inheritDoc} */ 482 @Override 483 public void setOutputScriptName(String filename) { 484 scriptFilename = filename; 485 } 486 487 /** {@inheritDoc} */ 488 @Override 489 @CheckForNull 490 public String getOutputScriptName() { 491 return scriptFilename; 492 } 493 494 /** {@inheritDoc} */ 495 @Override 496 public void setOutputSoundName(String filename) { 497 soundFilename = filename; 498 } 499 500 /** {@inheritDoc} */ 501 @Override 502 @CheckForNull 503 public String getOutputSoundName() { 504 return soundFilename; 505 } 506 507 /** {@inheritDoc} */ 508 @Override 509 public void setTurnoutsAlignedSensor(@CheckForNull String sensorName) throws IllegalArgumentException { 510 log.debug("setTurnoutsAlignedSensor {} {}", getSystemName(), sensorName); 511 512 mTurnoutsAlignedSensor = sensorName; 513 if (mTurnoutsAlignedSensor == null || mTurnoutsAlignedSensor.isEmpty()) { 514 mTurnoutsAlignedNamedSensor = null; 515 return; 516 } 517 Sensor s = InstanceManager.sensorManagerInstance().provideSensor(mTurnoutsAlignedSensor); 518 mTurnoutsAlignedNamedSensor = nbhm.getNamedBeanHandle(mTurnoutsAlignedSensor, s); 519 } 520 521 /** {@inheritDoc} */ 522 @Override 523 @CheckForNull 524 public String getTurnoutsAlignedSensor() { 525 if (mTurnoutsAlignedNamedSensor != null) { 526 return mTurnoutsAlignedNamedSensor.getName(); 527 } 528 return mTurnoutsAlignedSensor; 529 } 530 531 /** {@inheritDoc} */ 532 @Override 533 @CheckForNull 534 public Sensor getTurnoutsAlgdSensor() throws IllegalArgumentException { 535 if (mTurnoutsAlignedNamedSensor != null) { 536 return mTurnoutsAlignedNamedSensor.getBean(); 537 } else if (mTurnoutsAlignedSensor != null && !mTurnoutsAlignedSensor.isEmpty()) { 538 Sensor s = InstanceManager.sensorManagerInstance().provideSensor(mTurnoutsAlignedSensor); 539 mTurnoutsAlignedNamedSensor = nbhm.getNamedBeanHandle(mTurnoutsAlignedSensor, s); 540 return s; 541 } 542 return null; 543 } 544 // Inputs ---------------- 545 546 /** {@inheritDoc} */ 547 @Override 548 public void clearRouteSensors() { 549 _controlSensorList = new ArrayList<>(); 550 } 551 552 /** 553 * Method returns true if the sensor provided is already in the list of 554 * control sensors for this route. 555 * 556 * @param sensor the sensor to check for 557 * @return true if the sensor is found, false otherwise 558 */ 559 private boolean isControlSensorIncluded(@Nonnull ControlSensor sensor) { 560 for (int i = 0; i < _controlSensorList.size(); i++) { 561 if (_controlSensorList.get(i).getName().equals(sensor.getName()) 562 && _controlSensorList.get(i).getState() == sensor.getState()) { 563 return true; 564 } 565 } 566 return false; 567 } 568 569 /** {@inheritDoc} */ 570 @Override 571 public boolean addSensorToRoute(String sensorName, int mode) { 572 log.debug("addSensorToRoute({}, {}) as {} in {}", sensorName, mode, _controlSensorList.size(), getSystemName()); 573 574 ControlSensor sensor = new ControlSensor(sensorName); 575 if (!sensor.setState(mode)) { 576 return false; 577 } 578 if (isControlSensorIncluded(sensor)) { 579 // this is a normal condition, but log in case 580 log.debug("Not adding duplicate control sensor {} to route {}", sensorName, getSystemName()); 581 } else { 582 _controlSensorList.add(sensor); 583 } 584 585 if (_controlSensorList.size() > MAX_CONTROL_SENSORS) { 586 // reached maximum 587 log.warn("Sensor {} exceeded maximum number of control Sensors for Route: {}", sensorName, getSystemName()); 588 } 589 590 return true; 591 } 592 593 /** {@inheritDoc} */ 594 @Override 595 @CheckForNull 596 public String getRouteSensorName(int index) { 597 try { 598 return _controlSensorList.get(index).getName(); 599 } catch (IndexOutOfBoundsException ioob) { 600 return null; 601 } 602 } 603 604 /** {@inheritDoc} */ 605 @Override 606 @CheckForNull 607 public Sensor getRouteSensor(int index) { 608 try { 609 return _controlSensorList.get(index).getSensor(); 610 } catch (IndexOutOfBoundsException ioob) { 611 return null; 612 } 613 } 614 615 /** {@inheritDoc} */ 616 @Override 617 public int getRouteSensorMode(int index) { 618 try { 619 return _controlSensorList.get(index).getState(); 620 } catch (IndexOutOfBoundsException ioob) { 621 return 0; 622 } 623 } 624 625 boolean isRouteSensorIncluded(Sensor s) { 626 for (int i = 0; i < _controlSensorList.size(); i++) { 627 if (_controlSensorList.get(i).getSensor() == s) { 628 // Found turnout 629 return true; 630 } 631 } 632 return false; 633 } 634 635 void removeRouteSensor(Sensor s) { 636 int index = -1; 637 for (int i = 0; i < _controlSensorList.size(); i++) { 638 if (_controlSensorList.get(i).getSensor() == s) { 639 index = i; 640 break; 641 } 642 } 643 if (index != -1) { 644 _controlSensorList.remove(index); 645 } 646 } 647 648 /** {@inheritDoc} */ 649 @Override 650 public void setControlTurnout(@CheckForNull String turnoutName) throws IllegalArgumentException { 651 mControlTurnout = turnoutName; 652 if (mControlTurnout == null || mControlTurnout.isEmpty()) { 653 mControlNamedTurnout = null; 654 return; 655 } 656 Turnout t = InstanceManager.turnoutManagerInstance().provideTurnout(mControlTurnout); 657 mControlNamedTurnout = nbhm.getNamedBeanHandle(mControlTurnout, t); 658 } 659 660 /** {@inheritDoc} */ 661 @Override 662 @CheckForNull 663 public String getControlTurnout() { 664 if (mControlNamedTurnout != null) { 665 return mControlNamedTurnout.getName(); 666 } 667 return mControlTurnout; 668 } 669 670 /** {@inheritDoc} */ 671 @Override 672 @CheckForNull 673 public Turnout getCtlTurnout() throws IllegalArgumentException { 674 if (mControlNamedTurnout != null) { 675 return mControlNamedTurnout.getBean(); 676 } else if (mControlTurnout != null && !mControlTurnout.isEmpty()) { 677 Turnout t = InstanceManager.turnoutManagerInstance().provideTurnout(mControlTurnout); 678 mControlNamedTurnout = nbhm.getNamedBeanHandle(mControlTurnout, t); 679 return t; 680 } 681 return null; 682 } 683 684 /** {@inheritDoc} */ 685 @Override 686 public void setLockControlTurnout(@CheckForNull String turnoutName) throws IllegalArgumentException { 687 mLockControlTurnout = turnoutName; 688 if (mLockControlTurnout == null || mLockControlTurnout.isEmpty()) { 689 mLockControlNamedTurnout = null; 690 return; 691 } 692 Turnout t = InstanceManager.turnoutManagerInstance().provideTurnout(mLockControlTurnout); 693 mLockControlNamedTurnout = nbhm.getNamedBeanHandle(mLockControlTurnout, t); 694 } 695 696 /** {@inheritDoc} */ 697 @Override 698 @CheckForNull 699 public String getLockControlTurnout() { 700 if (mLockControlNamedTurnout != null) { 701 return mLockControlNamedTurnout.getName(); 702 } 703 return mLockControlTurnout; 704 } 705 706 /** {@inheritDoc} */ 707 @Override 708 @CheckForNull 709 public Turnout getLockCtlTurnout() throws IllegalArgumentException { 710 if (mLockControlNamedTurnout != null) { 711 return mLockControlNamedTurnout.getBean(); 712 } else if (mLockControlTurnout != null && !mLockControlTurnout.isEmpty()) { 713 Turnout t = InstanceManager.turnoutManagerInstance().provideTurnout(mLockControlTurnout); 714 mLockControlNamedTurnout = nbhm.getNamedBeanHandle(mLockControlTurnout, t); 715 return t; 716 } 717 return null; 718 } 719 720 /** {@inheritDoc} */ 721 @Override 722 public void setRouteCommandDelay(int delay) { 723 if (delay >= 0) { 724 mDelay = delay; 725 } 726 } 727 728 /** {@inheritDoc} */ 729 @Override 730 public int getRouteCommandDelay() { 731 return mDelay; 732 } 733 734 /** {@inheritDoc} */ 735 @Override 736 public void setControlTurnoutState(int turnoutState) { 737 if ((turnoutState == Route.ONTHROWN) 738 || (turnoutState == Route.ONCLOSED) 739 || (turnoutState == Route.ONCHANGE) 740 || (turnoutState == Route.VETOCLOSED) 741 || (turnoutState == Route.VETOTHROWN)) { 742 mControlTurnoutState = turnoutState; 743 } else { 744 log.error("Attempt to set invalid control Turnout state for Route."); 745 } 746 } 747 748 /** {@inheritDoc} */ 749 @Override 750 public int getControlTurnoutState() { 751 return mControlTurnoutState; 752 } 753 754 /** {@inheritDoc} */ 755 @Override 756 public void setControlTurnoutFeedback(boolean turnoutFeedbackIsCommanded) { 757 mTurnoutFeedbackIsCommanded = turnoutFeedbackIsCommanded; 758 } 759 760 /** {@inheritDoc} */ 761 @Override 762 public boolean getControlTurnoutFeedback() { 763 return mTurnoutFeedbackIsCommanded; 764 } 765 766 /** {@inheritDoc} */ 767 @Override 768 public void setLockControlTurnoutState(int turnoutState) { 769 if ((turnoutState == Route.ONTHROWN) 770 || (turnoutState == Route.ONCLOSED) 771 || (turnoutState == Route.ONCHANGE)) { 772 mLockControlTurnoutState = turnoutState; 773 } else { 774 log.error("Attempt to set invalid lock control Turnout state for Route."); 775 } 776 } 777 778 /** {@inheritDoc} */ 779 @Override 780 public int getLockControlTurnoutState() { 781 return mLockControlTurnoutState; 782 } 783 784 /** 785 * Lock or unlock turnouts that are part of a route 786 */ 787 private void lockTurnouts(boolean lock) { 788 // determine if turnout should be locked 789 for (int i = 0; i < _outputTurnoutList.size(); i++) { 790 _outputTurnoutList.get(i).getTurnout().setLocked( 791 Turnout.CABLOCKOUT + Turnout.PUSHBUTTONLOCKOUT, lock); 792 } 793 } 794 795 /** {@inheritDoc} */ 796 @Override 797 public void setRoute() { 798 if ((!_outputTurnoutList.isEmpty()) 799 || (!_outputSensorList.isEmpty()) 800 || (soundFilename != null) 801 || (scriptFilename != null)) { 802 if (!busy) { 803 log.debug("Setting route {}", this.getSystemName()); 804 setRouteBusy(true); 805 SetRouteThread thread = new SetRouteThread(this); 806 thread.setName("Route "+getDisplayName()+" setRoute"); 807 thread.start(); 808 } else { 809 log.debug("Not setting route {} because busy", this.getSystemName()); 810 } 811 } else { 812 log.debug("Unable to set route {} because no turnouts or no sensors", this.getSystemName()); 813 } 814 } 815 816 /** 817 * Handle sensor update event to see if it will set the route. 818 * <p> 819 * Called when a "KnownState" event is received, it assumes that only one 820 * sensor is changing right now, so can use state calls for everything other 821 * than this sensor. 822 * <p> 823 * This will fire the Route if the conditions are correct. 824 * <p> 825 * Returns nothing explicitly, but has the side effect of firing route. 826 * 827 * @param newState new state of control sensor 828 * @param oldState former state 829 * @param sensor Sensor used as Route control sensor 830 */ 831 protected void checkSensor(int newState, int oldState, @Nonnull Sensor sensor) { 832 // check for veto of change 833 if (isVetoed()) { 834 return; // don't fire 835 } 836 String name = sensor.getSystemName(); 837 log.debug("check Sensor {} for {}", name, getSystemName()); 838 boolean fire = false; // dont fire unless we find something 839 for (int i = 0; i < _controlSensorList.size(); i++) { 840 Sensor s = getRouteSensor(i); 841 if (s !=null && s.equals(sensor)) { 842 // here for match, check mode & handle onActive, onInactive 843 int mode = getRouteSensorMode(i); 844 log.debug("match mode: {} new state: {} old state: {}", mode, newState, oldState); 845 846 // if in target mode, note whether to act 847 if (((mode == ONACTIVE) && (newState == Sensor.ACTIVE)) 848 || ((mode == ONINACTIVE) && (newState == Sensor.INACTIVE)) 849 || ((mode == ONCHANGE) && (newState != oldState))) { 850 fire = true; 851 } 852 853 // if any other modes, just skip because 854 // the sensor might be in list more than once 855 } 856 } 857 858 log.debug("check activated"); 859 if (!fire) { 860 return; 861 } 862 863 // and finally set the route 864 log.debug("call setRoute for {}", getSystemName()); 865 setRoute(); 866 } 867 868 /** 869 * Turnout has changed, check to see if this fires. 870 * <p> 871 * Will fire Route if appropriate. 872 * 873 * @param newState new state of control turnout 874 * @param oldState former state 875 * @param t Turnout used as Route control turnout 876 */ 877 void checkTurnout(int newState, int oldState, Turnout t) { 878 if (isVetoed()) { 879 return; // skip setting route 880 } 881 switch (mControlTurnoutState) { 882 case ONCLOSED: 883 if (newState == Turnout.CLOSED) { 884 setRoute(); 885 } 886 break; 887 case ONTHROWN: 888 if (newState == Turnout.THROWN) { 889 setRoute(); 890 } 891 break; 892 case ONCHANGE: 893 if (newState != oldState) { 894 setRoute(); 895 } 896 break; 897 default: 898 break; // not a firing state 899 } 900 } 901 902 /** 903 * Turnout has changed, check to see if this will lock or unlock route. 904 * 905 * @param newState new state of lock turnout 906 * @param oldState former turnout state 907 * @param t Turnout used for locking the Route 908 */ 909 void checkLockTurnout(int newState, int oldState, Turnout t) { 910 switch (mLockControlTurnoutState) { 911 case ONCLOSED: 912 setLocked(newState == Turnout.CLOSED); 913 break; 914 case ONTHROWN: 915 setLocked(newState == Turnout.THROWN); 916 break; 917 case ONCHANGE: 918 if (newState != oldState) { 919 setLocked(!getLocked()); 920 } 921 break; 922 default: // if none, return 923 } 924 } 925 926 /** 927 * Method to check if the turnouts for this route are correctly aligned. 928 * Sets turnouts aligned sensor (if there is one) to active if the turnouts 929 * are aligned. Sets the sensor to inactive if they are not aligned 930 */ 931 public void checkTurnoutAlignment() { 932 933 //check each of the output turnouts in turn 934 //turnouts are deemed not aligned if: 935 // - commanded and known states don't agree 936 // - non-toggle turnouts known state not equal to desired state 937 // turnouts aligned sensor is then set accordingly 938 Sensor sensor = this.getTurnoutsAlgdSensor(); 939 if (sensor != null) { 940 try { 941 // this method can be called multiple times while a route is 942 // still going ACTIVE, so short-circut out as INCONSISTENT if 943 // isRouteBusy() is true; this ensures nothing watching the 944 // route shows it as ACTIVE when it may not really be 945 if (this.isRouteBusy()) { 946 sensor.setKnownState(Sensor.INCONSISTENT); 947 return; 948 } 949 for (OutputTurnout ot : this._outputTurnoutList) { 950 Turnout turnout = ot.getTurnout(); 951 int targetState = ot.getState(); 952 if (!turnout.isConsistentState()) { 953 sensor.setKnownState(Sensor.INCONSISTENT); 954 return; 955 } 956 if (targetState != Route.TOGGLE && targetState != turnout.getKnownState()) { 957 sensor.setKnownState(Sensor.INACTIVE); 958 return; 959 } 960 } 961 sensor.setKnownState(Sensor.ACTIVE); 962 } catch (JmriException ex) { 963 log.warn("Exception setting sensor {} in route", getTurnoutsAlignedSensor()); 964 } 965 } 966 } 967 968 /** {@inheritDoc} */ 969 @Override 970 public void activateRoute() { 971 activatedRoute = true; 972 973 String tas = getTurnoutsAlignedSensor(); 974 //register output turnouts to return Known State if a turnouts aligned sensor is defined 975 if ( tas != null && !tas.isEmpty()) { 976 977 for (int k = 0; k < _outputTurnoutList.size(); k++) { 978 _outputTurnoutList.get(k).addListener(); 979 } 980 } 981 982 for (int k = 0; k < _controlSensorList.size(); k++) { 983 _controlSensorList.get(k).addListener(); 984 } 985 Turnout ctl = getCtlTurnout(); 986 if (ctl != null) { 987 mTurnoutListener = e -> { 988 String name = Turnout.PROPERTY_KNOWN_STATE; 989 if (this.getControlTurnoutFeedback()) { 990 name = Turnout.PROPERTY_COMMANDED_STATE; 991 } 992 if (e.getPropertyName().equals(name)) { 993 int now = ((Integer) e.getNewValue()); 994 int then = ((Integer) e.getOldValue()); 995 checkTurnout(now, then, (Turnout) e.getSource()); 996 } 997 }; 998 ctl.addPropertyChangeListener(mTurnoutListener, getControlTurnout(), "Route " + getDisplayName()); 999 } 1000 Turnout lockCtl = getLockCtlTurnout(); 1001 if (lockCtl != null) { 1002 mLockTurnoutListener = e -> { 1003 if ( Turnout.PROPERTY_KNOWN_STATE.equals(e.getPropertyName())) { 1004 int now = ((Integer) e.getNewValue()); 1005 int then = ((Integer) e.getOldValue()); 1006 checkLockTurnout(now, then, (Turnout) e.getSource()); 1007 } 1008 }; 1009 lockCtl.addPropertyChangeListener(mLockTurnoutListener, 1010 getLockControlTurnout(), "Route " + getDisplayName()); 1011 } 1012 1013 checkTurnoutAlignment(); 1014 // register for updates to the Output Turnouts 1015 } 1016 1017 /** 1018 * Internal method to check whether operation of the route has been vetoed 1019 * by a sensor or turnout setting. 1020 * 1021 * @return true if veto, i.e. don't fire route; false if no veto, OK to fire 1022 */ 1023 boolean isVetoed() { 1024 log.debug("check for veto"); 1025 // check this route not enabled 1026 if (!_enabled) { 1027 return true; 1028 } 1029 1030 // check sensors 1031 for (int i = 0; i < _controlSensorList.size(); i++) { 1032 ControlSensor controlSensor = _controlSensorList.get(i); 1033 int s = controlSensor.getSensor().getKnownState(); 1034 int mode = controlSensor.getState(); 1035 if (((mode == VETOACTIVE) && (s == Sensor.ACTIVE)) 1036 || ((mode == VETOINACTIVE) && (s == Sensor.INACTIVE))) { 1037 return true; // veto set 1038 } 1039 } 1040 // check control turnout 1041 Turnout ctl = getCtlTurnout(); 1042 if (ctl != null) { 1043 int tstate = ctl.getKnownState(); 1044 if (mControlTurnoutState == Route.VETOCLOSED && tstate == Turnout.CLOSED) { 1045 return true; 1046 } 1047 if (mControlTurnoutState == Route.VETOTHROWN && tstate == Turnout.THROWN) { 1048 return true; 1049 } 1050 } 1051 return false; 1052 } 1053 1054 /** {@inheritDoc} */ 1055 @Override 1056 public void deActivateRoute() { 1057 //Check that the route isn't already deactived. 1058 if (!activatedRoute) { 1059 return; 1060 } 1061 1062 activatedRoute = false; 1063 // remove control turnout if there's one 1064 for (int k = 0; k < _controlSensorList.size(); k++) { 1065 _controlSensorList.get(k).removeListener(); 1066 } 1067 if (mTurnoutListener != null) { 1068 Turnout ctl = getCtlTurnout(); 1069 if (ctl != null) { 1070 ctl.removePropertyChangeListener(mTurnoutListener); 1071 } 1072 mTurnoutListener = null; 1073 } 1074 // remove lock control turnout if there's one 1075 if (mLockTurnoutListener != null) { 1076 Turnout lockCtl = getCtlTurnout(); 1077 if (lockCtl != null) { 1078 lockCtl.removePropertyChangeListener(mLockTurnoutListener); 1079 } 1080 mLockTurnoutListener = null; 1081 } 1082 //remove listeners on output turnouts if there are any 1083 if (!mTurnoutsAlignedSensor.isEmpty()) { 1084 for (int k = 0; k < _outputTurnoutList.size(); k++) { 1085 _outputTurnoutList.get(k).removeListener(); 1086 } 1087 } 1088 } 1089 1090 private boolean activatedRoute = false; 1091 1092 /** 1093 * Mark the Route as transitioning to an {@link jmri.Sensor#ACTIVE} state. 1094 * 1095 * @param busy true if Route should be busy. 1096 */ 1097 protected void setRouteBusy(boolean busy) { 1098 this.busy = busy; 1099 this.checkTurnoutAlignment(); 1100 } 1101 1102 /** 1103 * Method to query if Route is busy (returns true if commands are being 1104 * issued to Route turnouts) 1105 * 1106 * @return true if the Route is transitioning to an 1107 * {@link jmri.Sensor#ACTIVE} state, false otherwise. 1108 */ 1109 protected boolean isRouteBusy() { 1110 return busy; 1111 } 1112 1113 /** {@inheritDoc} */ 1114 @Override 1115 public int getState() { 1116 Sensor s = getTurnoutsAlgdSensor(); 1117 if (s != null) { 1118 return s.getKnownState(); 1119 } 1120 return UNKNOWN; 1121 } 1122 1123 /** {@inheritDoc} */ 1124 @Override 1125 public void setState(int state) { 1126 setRoute(); 1127 } 1128 1129 /** {@inheritDoc} */ 1130 @Override 1131 public void vetoableChange(PropertyChangeEvent evt) throws java.beans.PropertyVetoException { 1132 NamedBean nb = (NamedBean) evt.getOldValue(); 1133 if ( jmri.Manager.PROPERTY_CAN_DELETE.equals(evt.getPropertyName())) { 1134 StringBuilder message = new StringBuilder(); 1135 message.append("<b>").append(getDisplayName()).append("</b><ul>"); // NOI18N 1136 boolean found = false; 1137 if (nb instanceof Turnout) { 1138 if (isOutputTurnoutIncluded((Turnout) nb)) { 1139 message.append(Bundle.getMessage("InUseRouteOutputTurnout")); // NOI18N 1140 found = true; 1141 } 1142 if (nb.equals(getCtlTurnout())) { 1143 message.append(Bundle.getMessage("InUseRouteControlTurnout")); // NOI18N 1144 found = true; 1145 } 1146 if (nb.equals(getLockCtlTurnout())) { 1147 message.append(Bundle.getMessage("InUseRouteLockTurnout")); // NOI18N 1148 found = true; 1149 } 1150 } else if (nb instanceof Sensor) { 1151 if (isOutputSensorIncluded((Sensor) nb)) { 1152 message.append(Bundle.getMessage("InUseRouteOutputSensor")); // NOI18N 1153 found = true; 1154 } 1155 if (nb.equals(getTurnoutsAlgdSensor())) { 1156 message.append(Bundle.getMessage("InUseRouteAlignSensor")); // NOI18N 1157 found = true; 1158 } 1159 if (isRouteSensorIncluded((Sensor) nb)) { 1160 message.append(Bundle.getMessage("InUseRouteSensor")); // NOI18N 1161 found = true; 1162 } 1163 1164 } 1165 if (found) { 1166 message.append("</ul>"); 1167 throw new java.beans.PropertyVetoException(message.toString(), evt); 1168 } 1169 } else if (jmri.Manager.PROPERTY_DO_DELETE.equals(evt.getPropertyName())) { 1170 if (nb instanceof Turnout) { 1171 if (isOutputTurnoutIncluded((Turnout) nb)) { 1172 deActivateRoute(); 1173 deleteOutputTurnout((Turnout) evt.getOldValue()); 1174 } 1175 if (nb.equals(getCtlTurnout())) { 1176 deActivateRoute(); 1177 setControlTurnout(null); 1178 } 1179 if (nb.equals(getLockCtlTurnout())) { 1180 deActivateRoute(); 1181 setLockControlTurnout(null); 1182 } 1183 } else if (nb instanceof Sensor) { 1184 if (isOutputSensorIncluded((Sensor) nb)) { 1185 deActivateRoute(); 1186 removeOutputSensor((Sensor) nb); 1187 } 1188 if (nb.equals(getTurnoutsAlgdSensor())) { 1189 deActivateRoute(); 1190 setTurnoutsAlignedSensor(null); 1191 } 1192 if (isRouteSensorIncluded((Sensor) nb)) { 1193 deActivateRoute(); 1194 removeRouteSensor((Sensor) nb); 1195 } 1196 } 1197 activateRoute(); 1198 } 1199 } 1200 1201 @Override 1202 public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) { 1203 List<NamedBeanUsageReport> report = new ArrayList<>(); 1204 if (bean != null) { 1205 for (int i = 0; i < getNumOutputTurnouts(); i++) { 1206 if (bean.equals(getOutputTurnout(i))) { 1207 report.add(new NamedBeanUsageReport("RouteTurnoutOutput")); // NOI18N 1208 } 1209 } 1210 for (int i = 0; i < getNumOutputSensors(); i++) { 1211 if (bean.equals(getOutputSensor(i))) { 1212 report.add(new NamedBeanUsageReport("RouteSensorOutput")); // NOI18N 1213 } 1214 } 1215 for (int i = 0; i < _controlSensorList.size(); i++) { 1216 if (bean.equals(getRouteSensor(i))) { 1217 report.add(new NamedBeanUsageReport("RouteSensorControl")); // NOI18N 1218 } 1219 } 1220 if (bean.equals(getTurnoutsAlgdSensor())) { 1221 report.add(new NamedBeanUsageReport("RouteSensorAligned")); // NOI18N 1222 } 1223 if (bean.equals(getCtlTurnout())) { 1224 report.add(new NamedBeanUsageReport("RouteTurnoutControl")); // NOI18N 1225 } 1226 if (bean.equals(getLockCtlTurnout())) { 1227 report.add(new NamedBeanUsageReport("RouteTurnoutLock")); // NOI18N 1228 } 1229 } 1230 return report; 1231 } 1232 1233 private static final Logger log = LoggerFactory.getLogger(DefaultRoute.class); 1234 1235 /** 1236 * Class providing a thread to set route turnouts. 1237 */ 1238 private static class SetRouteThread extends Thread { 1239 1240 /** 1241 * Constructs the thread. 1242 * 1243 * @param aRoute DefaultRoute to set 1244 */ 1245 private SetRouteThread(DefaultRoute aRoute) { 1246 r = aRoute; 1247 } 1248 1249 /** 1250 * Runs the thread - performs operations in the order: 1251 * <ul> 1252 * <li>Run script (can run in parallel) 1253 * <li>Play Sound (runs in parallel) 1254 * <li>Set Sensors 1255 * <li>Set Turnouts 1256 * </ul> 1257 */ 1258 @Override 1259 public void run() { 1260 runScript(); 1261 playSound(); 1262 setSensors(); 1263 setTurnouts(); 1264 // set route not busy 1265 r.setRouteBusy(false); 1266 } 1267 1268 private void runScript() { 1269 // run script defined for start of route set 1270 String scriptName = r.getOutputScriptName(); 1271 if ((scriptName != null) && (!scriptName.isEmpty())) { 1272 JmriScriptEngineManager.getDefault().runScript( 1273 new File(jmri.util.FileUtil.getExternalFilename(scriptName))); 1274 } 1275 } 1276 1277 private void playSound() { 1278 // play sound defined for start of route set 1279 var outputSoundName = r.getOutputSoundName(); 1280 if (( outputSoundName != null) && (!outputSoundName.isEmpty())) { 1281 try { 1282 (new Sound(outputSoundName)).play(true); 1283 } catch (NullPointerException ex) { 1284 setRouteLog.error("Cannot find Sound file {} for Route {}", outputSoundName, r.getDisplayName()); 1285 } 1286 } 1287 } 1288 1289 private void setSensors() { 1290 // set sensors 1291 for (int k = 0; k < r.getNumOutputSensors(); k++) { 1292 Sensor t = r.getOutputSensor(k); 1293 if ( t==null ) { 1294 setRouteLog.warn("Sensor {} not found for Route {}",k,r.getDisplayName()); 1295 continue; 1296 } 1297 int state = r.getOutputSensorState(k); 1298 if (state == Route.TOGGLE) { 1299 int st = t.getKnownState(); 1300 if (st == Sensor.ACTIVE) { 1301 state = Sensor.INACTIVE; 1302 } else { 1303 state = Sensor.ACTIVE; 1304 } 1305 } 1306 final int toState = state; 1307 final Sensor setSensor = t; 1308 ThreadingUtil.runOnLayoutEventually(() -> { // eventually, even though timing here, should be soon 1309 try { 1310 setSensor.setKnownState(toState); 1311 } catch (JmriException e) { 1312 setRouteLog.warn("Exception setting sensor {} in route {} {}", 1313 setSensor.getSystemName(), r.getDisplayName(), e.getMessage()); 1314 } 1315 }); 1316 threadSleep(50); 1317 } 1318 } 1319 1320 private void threadSleep(long millis) { 1321 try { 1322 Thread.sleep(millis); 1323 } catch (InterruptedException e) { 1324 Thread.currentThread().interrupt(); // retain if needed later 1325 } 1326 } 1327 1328 private void setTurnouts() { 1329 // set turnouts 1330 int delay = r.getRouteCommandDelay(); 1331 1332 for (int k = 0; k < r.getNumOutputTurnouts(); k++) { 1333 Turnout t = r.getOutputTurnout(k); 1334 if ( t == null ) { 1335 log.error("No Turnout {} for Route {}",k,r.getDisplayName()); 1336 continue; 1337 } 1338 int state = r.getOutputTurnoutState(k); 1339 if (state == Route.TOGGLE) { 1340 int st = t.getKnownState(); 1341 if (st == Turnout.CLOSED) { 1342 state = Turnout.THROWN; 1343 } else { 1344 state = Turnout.CLOSED; 1345 } 1346 } 1347 final int toState = state; 1348 final Turnout setTurnout = t; 1349 ThreadingUtil.runOnLayoutEventually(() -> // eventually, even though we have timing here 1350 setTurnout.setCommandedStateAtInterval(toState)); // delayed by connection specific turnoutManager 1351 threadSleep(delay); // only the Route specific user defined delay is applied here 1352 } 1353 } 1354 1355 private final DefaultRoute r; 1356 1357 private static final Logger setRouteLog = LoggerFactory.getLogger(SetRouteThread.class); 1358 } 1359 1360}