001package jmri; 002 003import java.util.Set; 004import javax.annotation.Nonnull; 005import javax.annotation.CheckForNull; 006 007/** 008 * Represent a Turnout on the layout. 009 * <p> 010 * A Turnout has two states: 011 * <ul> 012 * <li>The "commandedState" records the state that's been commanded in the 013 * program. It might take some time, perhaps a long time, for that to actually 014 * take effect. 015 * <li>The "knownState" is the program's best idea of the actual state on the 016 * the layout. 017 * </ul> 018 * <p> 019 * There are a number of reasons that commandedState and knownState differ: 020 * <ul> 021 * <li>A change has been commanded, but it hasn't had time to happen yet 022 * <li>Something has gone wrong, and a commanded change isn't actually going to 023 * happen 024 * <li>Although the program hasn't commanded a change, something on the layout 025 * has made the turnout change. This could be a local electrical button, a 026 * mechanical movement of the points, or something else. 027 * <li>For a bus-like system, e.g. LocoNet or XpressNet, some other device might 028 * have sent a command to change the turnout. 029 * </ul> 030 * <p> 031 * Turnout feedback is involved in the connection between these two states; for 032 * more information see the 033 * <a href="http://jmri.org/help/en/html/doc/Technical/TurnoutFeedback.shtml">feedback 034 * page</a>. 035 * <p> 036 * The AbstractTurnout class contains a basic implementation of the state and 037 * messaging code, and forms a useful start for a system-specific 038 * implementation. Specific implementations, e.g. for 039 * LocoNet and NCE, will convert to and from the layout commands. 040 * <p> 041 * The states and names are Java Bean parameters, so that listeners can be 042 * registered to be notified of any changes. 043 * <p> 044 * A sample use of the Turnout interface can be seen in the 045 * jmri.jmrit.simpleturnoutctrl.SimpleTurnoutCtrlFrame class, which provides a 046 * simple GUI for controlling a single turnout. 047 * <p> 048 * Each Turnout object has a two names. The "user" name is entirely free form, 049 * and can be used for any purpose. The "system" name is provided by the 050 * system-specific implementations, and provides a unique mapping to the layout 051 * control system (for example LocoNet or NCE) and address within that system. 052 * <p> 053 * Turnouts exhibit some complex behaviors. At the same time, they are sometimes 054 * used as generic binary outputs where those get in the way. Eventually, we 055 * need to have a separate e.g. Output class, but for now you can defeat much of 056 * the advanced behaviors with the setBinaryOutput(true) method. This is a 057 * configuration property; changing it on the fly may give unexpected results. 058 * It's value is not persisted. 059 * <p> 060 * This file is part of JMRI. 061 * <p> 062 * JMRI is free software; you can redistribute it and/or modify it under the 063 * terms of version 2 of the GNU General Public License as published by the Free 064 * Software Foundation. See the "COPYING" file for a copy of this license. 065 * <p> 066 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 067 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 068 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 069 * 070 * @author Bob Jacobsen Copyright (C) 2001 071 * @see jmri.TurnoutManager 072 * @see jmri.InstanceManager 073 * @see jmri.jmrit.simpleturnoutctrl.SimpleTurnoutCtrlFrame 074 */ 075public interface Turnout extends DigitalIO, VariableControlSpanBean { 076 077 /** 078 * Constant representing a "closed" state, either in readback or as a 079 * commanded state. Note that it's possible to be both CLOSED and THROWN at 080 * the same time on some systems, which should be called INCONSISTENT 081 */ 082 static final int CLOSED = DigitalIO.ON; 083 084 /** 085 * Constant representing a "thrown" state, either in readback or as a 086 * commanded state. Note that it's possible to be both CLOSED and THROWN at 087 * the same time on some systems, which should be called INCONSISTENT 088 */ 089 static final int THROWN = DigitalIO.OFF; 090 091 /** 092 * Constant representing "direct feedback method". In this case, the 093 * commanded state is provided when the known state is requested. The two 094 * states never differ. This mode is always possible! 095 */ 096 static final int DIRECT = 1; 097 098 /** 099 * Constant representing "exact feedback method". In this case, the layout 100 * hardware can sense both positions of the turnout, which is used to set 101 * the known state. 102 */ 103 static final int EXACT = 2; 104 105 /** 106 * Constant representing "indirect feedback". In this case, the layout 107 * hardware can only sense one setting of the turnout. The known state is 108 * inferred from that info. 109 */ 110 static final int INDIRECT = 4; // only one side directly sensed 111 112 /** 113 * Constant representing "feedback by monitoring sent commands". In this 114 * case, the known state tracks commands seen on the rails or bus. 115 */ 116 static final int MONITORING = 8; 117 118 /** 119 * Constant representing "feedback by monitoring one sensor". The sensor 120 * sets the state CLOSED when INACTIVE and THROWN when ACTIVE 121 */ 122 static final int ONESENSOR = 16; 123 124 /** 125 * Constant representing "feedback by monitoring two sensors". The first 126 * sensor sets the state THROWN when ACTIVE; the second sensor sets the 127 * state CLOSED when ACTIVE. 128 */ 129 static final int TWOSENSOR = 32; 130 131 /** 132 * Constant representing "feedback for signals" . This is DIRECT feedback, 133 * with minimal delay (for use with systems that wait for responses returned 134 * by from the command station). 135 */ 136 static final int SIGNAL = 64; 137 138 /** 139 * Constant representing "automatic delayed feedback" . This is DIRECT feedback 140 * with a fixed delay before the feedback (known state) takes effect. 141 */ 142 static final int DELAYED = 128; 143 144 /** 145 * Constant representing "loconet alternate feedback method". In this case, the layout 146 * hardware can sense both positions of the turnout, which is used to set 147 * the known state. Hardware use OPS_SW_REP alternate message. 148 */ 149 static final int LNALTERNATE = 256; 150 151 /** 152 * Constant representing turnout lockout cab commands 153 */ 154 static final int CABLOCKOUT = 1; 155 156 /** 157 * Constant representing turnout lockout pushbuttons 158 */ 159 static final int PUSHBUTTONLOCKOUT = 2; 160 161 /** 162 * Constant representing a unlocked turnout 163 */ 164 static final int UNLOCKED = 0; 165 166 /** 167 * Constant representing a locked turnout 168 */ 169 static final int LOCKED = 1; 170 171 /** 172 * String constant for Property Change to set Commanded State. 173 */ 174 String PROPERTY_COMMANDED_STATE = "CommandedState"; 175 176 /** 177 * String constant for Property Change to set Feedback Mode. 178 */ 179 String PROPERTY_FEEDBACK_MODE = "feedbackchange"; 180 181 /** 182 * String constant for Property Change to set the Inverted Mode. 183 */ 184 String PROPERTY_INVERTED = "inverted"; 185 186 /** 187 * String constant for Property Change to set the Locked state. 188 */ 189 String PROPERTY_LOCKED = "locked"; 190 191 /** 192 * String constant for Property Change to set the Report Locked state. 193 */ 194 String PROPERTY_REPORT_LOCKED = "reportlocked"; 195 196 /** 197 * String constant for Property Change to set Decoder Name. 198 */ 199 String PROPERTY_DECODER_NAME = "decoderNameChange"; 200 201 /** 202 * String constant for Property Change to set Turnout Operator. 203 */ 204 String PROPERTY_TURNOUT_OPERATION_STATE = "TurnoutOperationState"; 205 206 /** 207 * String constant for when changing the First Feedback Sensor in use. 208 */ 209 String PROPERTY_TURNOUT_FEEDBACK_FIRST_SENSOR = "turnoutFeedbackFirstSensorChange"; 210 211 /** 212 * String constant for when changing the Second Feedback Sensor in use. 213 */ 214 String PROPERTY_TURNOUT_FEEDBACK_SECOND_SENSOR = "turnoutFeedbackSecondSensorChange"; 215 216 /** 217 * String constant for when the Diverging Speed has changed. 218 */ 219 String PROPERTY_TURNOUT_DIVERGING_SPEED = "TurnoutDivergingSpeedChange"; 220 221 /** 222 * String constant for when the Straight Speed has changed. 223 */ 224 String PROPERTY_TURNOUT_STRAIGHT_SPEED = "TurnoutStraightSpeedChange"; 225 226 /** 227 * String constant for when the Leading Turnout is set. 228 */ 229 String PROPERTY_LEADING_TURNOUT = "LeadingTurnout"; 230 231 /** 232 * Get a list of valid feedback types. The valid types depend on the 233 * implemented system. 234 * 235 * @return array of feedback types 236 */ 237 Set<Integer> getValidFeedbackModes(); 238 239 /** 240 * Get a representation of the feedback type. This is the OR of possible 241 * values: DIRECT, EXACT, etc. The valid combinations depend on the 242 * implemented system. 243 * 244 * @return the ORed combination of feedback types 245 */ 246 int getValidFeedbackTypes(); 247 248 /** 249 * Get a human readable representation of the feedback type. The values 250 * depend on the implemented system. 251 * 252 * @return the names of the feedback types or an empty list if no feedback 253 * is available 254 */ 255 @Nonnull 256 String[] getValidFeedbackNames(); 257 258 /** 259 * Set the feedback mode from a human readable name. This must be one of the 260 * names defined in a previous {@link #getValidFeedbackNames} call. 261 * 262 * @param mode the feedback type name 263 * @throws IllegalArgumentException if mode is not valid 264 */ 265 @InvokeOnLayoutThread 266 void setFeedbackMode(@Nonnull String mode) throws IllegalArgumentException; 267 268 /** 269 * Set the feedback mode from a integer. This must be one of the bit values 270 * defined in a previous {@link #getValidFeedbackTypes} call. Having more 271 * than one bit set is an error. 272 * 273 * @param mode the feedback type to set 274 * @throws IllegalArgumentException if mode is not valid 275 */ 276 @InvokeOnLayoutThread 277 void setFeedbackMode(int mode) throws IllegalArgumentException; 278 279 /** 280 * Get the feedback mode in human readable form. This will be one of the 281 * names defined in a {@link #getValidFeedbackNames} call. 282 * 283 * @return the feedback type 284 */ 285 @Nonnull 286 String getFeedbackModeName(); 287 288 /** 289 * Get the feedback mode in machine readable form. This will be one of the 290 * bits defined in a {@link #getValidFeedbackTypes} call. 291 * 292 * @return the feedback type 293 */ 294 int getFeedbackMode(); 295 296 /** 297 * Get if automatically retrying an operation is blocked for this turnout. 298 * 299 * @return true if retrying is disabled; false otherwise 300 */ 301 boolean getInhibitOperation(); 302 303 /** 304 * Set if automatically retrying an operation is blocked for this turnout. 305 * 306 * @param io true if retrying is to be disabled; false otherwise 307 */ 308 void setInhibitOperation(boolean io); 309 310 /** 311 * @return current operation automation class 312 */ 313 @CheckForNull 314 TurnoutOperation getTurnoutOperation(); 315 316 /** 317 * set current automation class 318 * 319 * @param toper TurnoutOperation subclass instance 320 */ 321 @InvokeOnLayoutThread 322 void setTurnoutOperation(@CheckForNull TurnoutOperation toper); 323 324 /** 325 * Return the inverted state of the specified state 326 * Does NOT invert INCONSISTENT 327 * @param inState the specified state 328 * @return the inverted state 329 */ 330 static int invertTurnoutState(int inState) { 331 int result = UNKNOWN; 332 if (inState == CLOSED) { 333 result = THROWN; 334 } else if (inState == THROWN){ 335 result = CLOSED; 336 } else if (inState == INCONSISTENT){ 337 result = INCONSISTENT; 338 } 339 return result; 340 } 341 342 /** 343 * Provide Sensor objects needed for some feedback types. 344 * 345 * Since we defined two feedback methods that require monitoring, we provide 346 * these methods to define those sensors to the Turnout. 347 * <p> 348 * The second sensor can be null if needed. 349 * <p> 350 * Sensor-based feedback will not function until these sensors have been 351 * provided. 352 * 353 * @param name the user or system name of the sensor 354 * @param number the feedback number of the sensor, indexed from 0 355 * @throws jmri.JmriException if unable to assign the feedback sensor 356 */ 357 default void provideFeedbackSensor(@CheckForNull String name, int number) throws JmriException { 358 switch (number) { 359 case 0: 360 provideFirstFeedbackSensor(name); 361 break; 362 case 1: 363 provideSecondFeedbackSensor(name); 364 break; 365 default: 366 throw new IllegalArgumentException("Turnouts have no more than two sensors"); 367 } 368 } 369 370 void provideFirstFeedbackSensor(@CheckForNull String pName) throws JmriException; 371 372 void provideSecondFeedbackSensor(@CheckForNull String pName) throws JmriException; 373 374 /** 375 * Get the first feedback sensor. 376 * 377 * @return the sensor or null if no Sensor set 378 */ 379 @CheckForNull 380 Sensor getFirstSensor(); 381 382 /** 383 * Get the handle for the first feedback sensor. 384 * 385 * @return the sensor handle or null if no Sensor set 386 */ 387 @CheckForNull 388 NamedBeanHandle<Sensor> getFirstNamedSensor(); 389 390 /** 391 * Get the second feedback sensor. 392 * 393 * @return the sensor or null if no Sensor set 394 */ 395 @CheckForNull 396 Sensor getSecondSensor(); 397 398 /** 399 * Get the second feedback sensor handle. 400 * 401 * @return the sensor handle or null if no Sensor set 402 */ 403 @CheckForNull 404 NamedBeanHandle<Sensor> getSecondNamedSensor(); 405 406 /** 407 * Sets the initial known state (CLOSED,THROWN,UNKNOWN) from feedback 408 * information, if appropriate. 409 * <p> 410 * This method is designed to be called only when Turnouts are loaded and 411 * when a new Turnout is defined in the Turnout table. 412 * <p> 413 * No change to known state is made if feedback information is not 414 * available. If feedback information is inconsistent, or if sensor 415 * definition is missing in ONESENSOR and TWOSENSOR feedback, turnout state 416 * is set to UNKNOWN. 417 */ 418 @InvokeOnLayoutThread 419 void setInitialKnownStateFromFeedback(); 420 421 /** 422 * Get control type. 423 * 424 * @return 0 for steady state or the number of time units the control pulses 425 */ 426 int getControlType(); 427 428 /** 429 * Set control type. 430 * 431 * @param num 0 for steady state or the number of time units the control 432 * pulses 433 */ 434 @InvokeOnLayoutThread 435 void setControlType(int num); 436 437 /** 438 * Get turnout inverted. When a turnout is inverted the {@link #CLOSED} and 439 * {@link #THROWN} states are reversed on the layout. 440 * 441 * @return true if inverted; false otherwise 442 */ 443 boolean getInverted(); 444 445 /** 446 * Get turnout inverted. When a turnout is inverted the {@link #CLOSED} and 447 * {@link #THROWN} states are reversed on the layout. 448 * 449 * @param inverted true if inverted; false otherwise 450 */ 451 void setInverted(boolean inverted); 452 453 /** 454 * Determine if turnout can be inverted. When a turnout is inverted the 455 * {@link #CLOSED} and {@link #THROWN} states are inverted on the layout. 456 * 457 * @return true if can be inverted; false otherwise 458 */ 459 boolean canInvert(); 460 461 /** 462 * Get the locked state of the turnout. A turnout can be locked to prevent 463 * it being thrown from a cab or push button on the layout if supported by 464 * the protocol. 465 * 466 * @param turnoutLockout the type of lock 467 * @return true if turnout is locked using specified lock method 468 */ 469 boolean getLocked(int turnoutLockout); 470 471 /** 472 * Enable turnout lock operators. A turnout can be locked to prevent it 473 * being thrown from a cab or push button on the layout if supported by the 474 * protocol. 475 * 476 * @param turnoutLockout the type of lock 477 * @param locked true if locking is enabled for the given type; 478 * false otherwise 479 */ 480 @InvokeOnLayoutThread 481 void enableLockOperation(int turnoutLockout, boolean locked); 482 483 /** 484 * Determine if turnout can be locked as currently configured. A turnout can be locked to prevent it 485 * being thrown from a cab or push button on the layout if supported by the 486 * protocol. 487 * 488 * @param turnoutLockout the type of lock, one of CABLOCKOUT, PUSHBUTTONLOCKOUT 489 * or BOTH = CABLOCKOUT | PUSHBUTTONLOCKOUT 490 * @return true if turnout is locked using specified lock method; false 491 * otherwise 492 */ 493 boolean canLock(int turnoutLockout); 494 495 /** 496 * Provide the possible locking modes for a turnout. 497 * These may require additional configuration, e.g. 498 * setting of a decoder definition for PUSHBUTTONLOCKOUT, 499 * before {@link #canLock(int)} will return true. 500 * 501 * @return One of 0 for none, CABLOCKOUT, PUSHBUTTONLOCKOUT 502 * or CABLOCKOUT | PUSHBUTTONLOCKOUT for both 503 */ 504 int getPossibleLockModes(); 505 506 /** 507 * Lock a turnout. A turnout can be locked to prevent it being thrown from a 508 * cab or push button on the layout if supported by the protocol. 509 * 510 * @param turnoutLockout the type of lock 511 * @param locked true if turnout is locked using specified lock 512 * method; false otherwise 513 */ 514 @InvokeOnLayoutThread 515 void setLocked(int turnoutLockout, boolean locked); 516 517 /** 518 * Get reporting of use of locked turnout by a cab or throttle. 519 * 520 * @return true to report; false otherwise 521 */ 522 boolean getReportLocked(); 523 524 /** 525 * Set reporting of use of locked turnout by a cab or throttle. 526 * 527 * @param reportLocked true to report; false otherwise 528 */ 529 @InvokeOnLayoutThread 530 void setReportLocked(boolean reportLocked); 531 532 /** 533 * Get a human readable representation of the decoder types. 534 * 535 * @return a list of known stationary decoders that can be specified for locking 536 */ 537 @Nonnull 538 String[] getValidDecoderNames(); 539 540 /** 541 * Get a human readable representation of the locking decoder type for this turnout. 542 * 543 * In AbstractTurnout this String defaults to PushbuttonPacket.unknown , ie "None" 544 * @return the name of the decoder type; null indicates none defined 545 */ 546 @CheckForNull 547 String getDecoderName(); 548 549 /** 550 * Set a human readable representation of the locking decoder type for this turnout. 551 * 552 * @param decoderName the name of the decoder type 553 */ 554 void setDecoderName(@CheckForNull String decoderName); 555 556 /** 557 * Use a binary output for sending commands. This appears to expose a 558 * LocoNet-specific feature. 559 * 560 * @param state true if the outputs are binary; false otherwise 561 */ 562 @InvokeOnLayoutThread 563 void setBinaryOutput(boolean state); 564 565 float getDivergingLimit(); 566 567 String getDivergingSpeed(); 568 569 void setDivergingSpeed(String s) throws JmriException; 570 571 float getStraightLimit(); 572 573 String getStraightSpeed(); 574 575 void setStraightSpeed(String s) throws JmriException; 576 577 /** 578 * Check if this Turnout can follow the state of another Turnout. 579 * 580 * @return true if this Turnout is capable of following; false otherwise 581 */ 582 // Note: not `canFollow()` to allow JavaBeans introspection to find 583 // the property "canFollow" 584 boolean isCanFollow(); 585 586 /** 587 * Get the Turnout this Turnout is following. 588 * 589 * @return the leading Turnout or null if none; null if 590 * {@link #isCanFollow()} is false 591 */ 592 @CheckForNull 593 Turnout getLeadingTurnout(); 594 595 /** 596 * Set the Turnout this Turnout will follow. 597 * <p> 598 * It is valid for two or more turnouts to follow each other in a circular 599 * pattern. 600 * <p> 601 * It is recommended that a following turnout's feedback mode be 602 * {@link #DIRECT}. 603 * <p> 604 * It is recommended to explicitly call 605 * {@link #setFollowingCommandedState(boolean)} after calling this method or 606 * to use {@link #setLeadingTurnout(jmri.Turnout, boolean)} to ensure this 607 * Turnout follows the leading Turnout in the expected manner. 608 * 609 * @param turnout the leading Turnout or null if this Turnout should not 610 * follow another Turnout; silently ignored if 611 * {@link #isCanFollow()} is false 612 */ 613 void setLeadingTurnout(@CheckForNull Turnout turnout); 614 615 /** 616 * Set both the leading Turnout and if the commanded state of the leading 617 * Turnout is followed. This is a convenience method for calling both 618 * {@link #setLeadingTurnout(jmri.Turnout)} and 619 * {@link #setFollowingCommandedState(boolean)}. 620 * 621 * @param turnout the leading Turnout or null if this 622 * Turnout should not follow another Turnout; 623 * silently ignored if {@link #isCanFollow()} 624 * is false 625 * @param followingCommandedState true to have all states match leading 626 * turnout; false to only have non-commanded 627 * states match 628 */ 629 void setLeadingTurnout(@CheckForNull Turnout turnout, boolean followingCommandedState); 630 631 /** 632 * Check if this Turnout is following all states or only the non-commanded 633 * states of the leading Turnout. 634 * 635 * @return true if following all states; false otherwise 636 */ 637 boolean isFollowingCommandedState(); 638 639 /** 640 * Set if this Turnout follows all states or only the non-commanded states 641 * of the leading Turnout. 642 * <p> 643 * A Turnout can be commanded to be {@link #THROWN} or {@link #CLOSED}, but 644 * can also have additional states {@link #INCONSISTENT} and 645 * {@link #UNKNOWN}. There are some use cases where a following Turnout 646 * should match all states of the leading Turnout, in which case this should 647 * be true, but there are also use cases where the following Turnout should 648 * only match the INCONSISTENT and UNKNOWN states of the leading Turnout, 649 * but should otherwise be independently commanded, in which case this 650 * should be false. 651 * 652 * @param following true to have all states match leading turnout; false to 653 * only have non-commanded states match 654 */ 655 void setFollowingCommandedState(boolean following); 656 657 /** 658 * Before setting commanded state, if required by manager, apply wait interval until 659 * outputIntervalEnds() to put less pressure on the connection. 660 * <p> 661 * Used to insert a delay before calling {@link #setCommandedState(int)} to spread out a series of 662 * output commands, as in {@link jmri.implementation.MatrixSignalMast#updateOutputs(char[])} and 663 * {@link jmri.implementation.DefaultRoute} class SetRouteThread#run(). 664 * Interval value is kept in the Memo per hardware connection, default = 0 665 * 666 * @param s turnout state to forward 667 */ 668 void setCommandedStateAtInterval(int s); 669 670}