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