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