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}