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 "CS VPIN output mode". The command station drives a
153     * VPIN directly via the {@code <z>} command with no pre-definition required
154     * in the CS output table. Differs from {@link #DIRECT} (which sends a DCC
155     * accessory packet); this sends a VPIN pin-control command resolved by the
156     * CS HAL layer at runtime. The known state is updated optimistically on send.
157     */
158    static final int CS_VPIN = 512;
159
160    /**
161     * Constant representing turnout lockout cab commands
162     */
163    static final int CABLOCKOUT = 1;
164
165    /**
166     * Constant representing turnout lockout pushbuttons
167     */
168    static final int PUSHBUTTONLOCKOUT = 2;
169
170    /**
171     * Constant representing a unlocked turnout
172     */
173    static final int UNLOCKED = 0;
174
175    /**
176     * Constant representing a locked turnout
177     */
178    static final int LOCKED = 1;
179
180    /**
181     * String constant for Property Change to set Commanded State.
182     */
183    String PROPERTY_COMMANDED_STATE = "CommandedState";
184
185    /**
186     * String constant for Property Change to set Feedback Mode.
187     */
188    String PROPERTY_FEEDBACK_MODE = "feedbackchange";
189
190    /**
191     * String constant for Property Change to set the Inverted Mode.
192     */
193    String PROPERTY_INVERTED = "inverted";
194
195    /**
196     * String constant for Property Change to set the Locked state.
197     */
198    String PROPERTY_LOCKED = "locked";
199
200    /**
201     * String constant for Property Change to set the Report Locked state.
202     */
203    String PROPERTY_REPORT_LOCKED = "reportlocked";
204
205    /**
206     * String constant for Property Change to set Decoder Name.
207     */
208    String PROPERTY_DECODER_NAME = "decoderNameChange";
209
210    /**
211     * String constant for Property Change to set Turnout Operator.
212     */
213    String PROPERTY_TURNOUT_OPERATION_STATE = "TurnoutOperationState";
214
215    /**
216     * String constant for when changing the First Feedback Sensor in use.
217     */
218    String PROPERTY_TURNOUT_FEEDBACK_FIRST_SENSOR = "turnoutFeedbackFirstSensorChange";
219
220    /**
221     * String constant for when changing the Second Feedback Sensor in use.
222     */
223    String PROPERTY_TURNOUT_FEEDBACK_SECOND_SENSOR = "turnoutFeedbackSecondSensorChange";
224
225    /**
226     * String constant for when the Diverging Speed has changed.
227     */
228    String PROPERTY_TURNOUT_DIVERGING_SPEED = "TurnoutDivergingSpeedChange";
229
230    /**
231     * String constant for when the Straight Speed has changed.
232     */
233    String PROPERTY_TURNOUT_STRAIGHT_SPEED = "TurnoutStraightSpeedChange";
234
235    /**
236     * String constant for when the Leading Turnout is set.
237     */
238    String PROPERTY_LEADING_TURNOUT = "LeadingTurnout";
239
240    /**
241     * Get a list of valid feedback types. The valid types depend on the
242     * implemented system.
243     *
244     * @return array of feedback types
245     */
246    Set<Integer> getValidFeedbackModes();
247
248    /**
249     * Get a representation of the feedback type. This is the OR of possible
250     * values: DIRECT, EXACT, etc. The valid combinations depend on the
251     * implemented system.
252     *
253     * @return the ORed combination of feedback types
254     */
255    int getValidFeedbackTypes();
256
257    /**
258     * Get a human readable representation of the feedback type. The values
259     * depend on the implemented system.
260     *
261     * @return the names of the feedback types or an empty list if no feedback
262     *         is available
263     */
264    @Nonnull
265    String[] getValidFeedbackNames();
266
267    /**
268     * Set the feedback mode from a human readable name. This must be one of the
269     * names defined in a previous {@link #getValidFeedbackNames} call.
270     *
271     * @param mode the feedback type name
272     * @throws IllegalArgumentException if mode is not valid
273     */
274    @InvokeOnLayoutThread
275    void setFeedbackMode(@Nonnull String mode) throws IllegalArgumentException;
276
277    /**
278     * Set the feedback mode from a integer. This must be one of the bit values
279     * defined in a previous {@link #getValidFeedbackTypes} call. Having more
280     * than one bit set is an error.
281     *
282     * @param mode the feedback type to set
283     * @throws IllegalArgumentException if mode is not valid
284     */
285    @InvokeOnLayoutThread
286    void setFeedbackMode(int mode) throws IllegalArgumentException;
287
288    /**
289     * Get the feedback mode in human readable form. This will be one of the
290     * names defined in a {@link #getValidFeedbackNames} call.
291     *
292     * @return the feedback type
293     */
294    @Nonnull
295    String getFeedbackModeName();
296
297    /**
298     * Get the feedback mode in machine readable form. This will be one of the
299     * bits defined in a {@link #getValidFeedbackTypes} call.
300     *
301     * @return the feedback type
302     */
303    int getFeedbackMode();
304
305    /**
306     * Get if automatically retrying an operation is blocked for this turnout.
307     *
308     * @return true if retrying is disabled; false otherwise
309     */
310    boolean getInhibitOperation();
311
312    /**
313     * Set if automatically retrying an operation is blocked for this turnout.
314     *
315     * @param io true if retrying is to be disabled; false otherwise
316     */
317    void setInhibitOperation(boolean io);
318
319    /**
320     * @return current operation automation class
321     */
322    @CheckForNull
323    TurnoutOperation getTurnoutOperation();
324
325    /**
326     * set current automation class
327     *
328     * @param toper TurnoutOperation subclass instance
329     */
330    @InvokeOnLayoutThread
331    void setTurnoutOperation(@CheckForNull TurnoutOperation toper);
332
333    /**
334     * Return the inverted state of the specified state
335     * Does NOT invert INCONSISTENT
336     * @param inState the specified state
337     * @return the inverted state
338     */
339    static int invertTurnoutState(int inState) {
340        int result = UNKNOWN;
341        if (inState == CLOSED) {
342            result = THROWN;
343        } else if (inState == THROWN){
344            result = CLOSED;
345        } else if (inState == INCONSISTENT){
346            result = INCONSISTENT;
347        }
348        return result;
349    }
350
351    /**
352     * Provide Sensor objects needed for some feedback types.
353     *
354     * Since we defined two feedback methods that require monitoring, we provide
355     * these methods to define those sensors to the Turnout.
356     * <p>
357     * The second sensor can be null if needed.
358     * <p>
359     * Sensor-based feedback will not function until these sensors have been
360     * provided.
361     *
362     * @param name the user or system name of the sensor
363     * @param number the feedback number of the sensor, indexed from 0
364     * @throws jmri.JmriException if unable to assign the feedback sensor
365     */
366    default void provideFeedbackSensor(@CheckForNull String name, int number) throws JmriException {
367        switch (number) {
368            case 0:
369                provideFirstFeedbackSensor(name);
370                break;
371            case 1:
372                provideSecondFeedbackSensor(name);
373                break;
374            default:
375                throw new IllegalArgumentException("Turnouts have no more than two sensors");
376        }
377    }
378
379    void provideFirstFeedbackSensor(@CheckForNull String pName) throws JmriException;
380
381    void provideSecondFeedbackSensor(@CheckForNull String pName) throws JmriException;
382
383    /**
384     * Get the first feedback sensor.
385     *
386     * @return the sensor or null if no Sensor set
387     */
388    @CheckForNull
389    Sensor getFirstSensor();
390
391    /**
392     * Get the handle for the first feedback sensor.
393     *
394     * @return the sensor handle or null if no Sensor set
395     */
396    @CheckForNull
397    NamedBeanHandle<Sensor> getFirstNamedSensor();
398
399    /**
400     * Get the second feedback sensor.
401     *
402     * @return the sensor or null if no Sensor set
403     */
404    @CheckForNull
405    Sensor getSecondSensor();
406
407    /**
408     * Get the second feedback sensor handle.
409     *
410     * @return the sensor handle or null if no Sensor set
411     */
412    @CheckForNull
413    NamedBeanHandle<Sensor> getSecondNamedSensor();
414
415    /**
416     * Sets the initial known state (CLOSED,THROWN,UNKNOWN) from feedback
417     * information, if appropriate.
418     * <p>
419     * This method is designed to be called only when Turnouts are loaded and
420     * when a new Turnout is defined in the Turnout table.
421     * <p>
422     * No change to known state is made if feedback information is not
423     * available. If feedback information is inconsistent, or if sensor
424     * definition is missing in ONESENSOR and TWOSENSOR feedback, turnout state
425     * is set to UNKNOWN.
426     */
427    @InvokeOnLayoutThread
428    void setInitialKnownStateFromFeedback();
429
430    /**
431     * Get control type.
432     *
433     * @return 0 for steady state or the number of time units the control pulses
434     */
435    int getControlType();
436
437    /**
438     * Set control type.
439     *
440     * @param num 0 for steady state or the number of time units the control
441     *            pulses
442     */
443    @InvokeOnLayoutThread
444    void setControlType(int num);
445
446    /**
447     * Get turnout inverted. When a turnout is inverted the {@link #CLOSED} and
448     * {@link #THROWN} states are reversed on the layout.
449     *
450     * @return true if inverted; false otherwise
451     */
452    boolean getInverted();
453
454    /**
455     * Get turnout inverted. When a turnout is inverted the {@link #CLOSED} and
456     * {@link #THROWN} states are reversed on the layout.
457     *
458     * @param inverted true if inverted; false otherwise
459     */
460    void setInverted(boolean inverted);
461
462    /**
463     * Determine if turnout can be inverted. When a turnout is inverted the
464     * {@link #CLOSED} and {@link #THROWN} states are inverted on the layout.
465     *
466     * @return true if can be inverted; false otherwise
467     */
468    boolean canInvert();
469
470    /**
471     * Get the locked state of the turnout. A turnout can be locked to prevent
472     * it being thrown from a cab or push button on the layout if supported by
473     * the protocol.
474     *
475     * @param turnoutLockout the type of lock
476     * @return true if turnout is locked using specified lock method
477     */
478    boolean getLocked(int turnoutLockout);
479
480    /**
481     * Enable turnout lock operators. A turnout can be locked to prevent it
482     * being thrown from a cab or push button on the layout if supported by the
483     * protocol.
484     *
485     * @param turnoutLockout the type of lock
486     * @param locked         true if locking is enabled for the given type;
487     *                       false otherwise
488     */
489    @InvokeOnLayoutThread
490    void enableLockOperation(int turnoutLockout, boolean locked);
491
492    /**
493     * Determine if turnout can be locked as currently configured. A turnout can be locked to prevent it
494     * being thrown from a cab or push button on the layout if supported by the
495     * protocol.
496     *
497     * @param turnoutLockout the type of lock, one of CABLOCKOUT, PUSHBUTTONLOCKOUT
498     * or BOTH = CABLOCKOUT | PUSHBUTTONLOCKOUT
499     * @return true if turnout is locked using specified lock method; false
500     *         otherwise
501     */
502    boolean canLock(int turnoutLockout);
503
504    /**
505     * Provide the possible locking modes for a turnout.
506     * These may require additional configuration, e.g.
507     * setting of a decoder definition for PUSHBUTTONLOCKOUT,
508     * before {@link #canLock(int)} will return true.
509     *
510     * @return One of 0 for none, CABLOCKOUT, PUSHBUTTONLOCKOUT
511     * or CABLOCKOUT | PUSHBUTTONLOCKOUT for both
512     */
513    int getPossibleLockModes();
514
515    /**
516     * Lock a turnout. A turnout can be locked to prevent it being thrown from a
517     * cab or push button on the layout if supported by the protocol.
518     *
519     * @param turnoutLockout the type of lock
520     * @param locked         true if turnout is locked using specified lock
521     *                       method; false otherwise
522     */
523    @InvokeOnLayoutThread
524    void setLocked(int turnoutLockout, boolean locked);
525
526    /**
527     * Get reporting of use of locked turnout by a cab or throttle.
528     *
529     * @return true to report; false otherwise
530     */
531    boolean getReportLocked();
532
533    /**
534     * Set reporting of use of locked turnout by a cab or throttle.
535     *
536     * @param reportLocked true to report; false otherwise
537     */
538    @InvokeOnLayoutThread
539    void setReportLocked(boolean reportLocked);
540
541    /**
542     * Get a human readable representation of the decoder types.
543     *
544     * @return a list of known stationary decoders that can be specified for locking
545     */
546    @Nonnull
547    String[] getValidDecoderNames();
548
549    /**
550     * Get a human readable representation of the locking decoder type for this turnout.
551     *
552     * In AbstractTurnout this String defaults to PushbuttonPacket.unknown , ie "None"
553     * @return the name of the decoder type; null indicates none defined
554     */
555    @CheckForNull
556    String getDecoderName();
557
558    /**
559     * Set a human readable representation of the locking decoder type for this turnout.
560     *
561     * @param decoderName the name of the decoder type
562     */
563    void setDecoderName(@CheckForNull String decoderName);
564
565    /**
566     * Use a binary output for sending commands. This appears to expose a
567     * LocoNet-specific feature.
568     *
569     * @param state true if the outputs are binary; false otherwise
570     */
571    @InvokeOnLayoutThread
572    void setBinaryOutput(boolean state);
573
574    float getDivergingLimit();
575
576    String getDivergingSpeed();
577
578    void setDivergingSpeed(String s) throws JmriException;
579
580    float getStraightLimit();
581
582    String getStraightSpeed();
583
584    void setStraightSpeed(String s) throws JmriException;
585
586    /**
587     * Check if this Turnout can follow the state of another Turnout.
588     *
589     * @return true if this Turnout is capable of following; false otherwise
590     */
591    // Note: not `canFollow()` to allow JavaBeans introspection to find
592    // the property "canFollow"
593    boolean isCanFollow();
594
595    /**
596     * Get the Turnout this Turnout is following.
597     *
598     * @return the leading Turnout or null if none; null if
599     *         {@link #isCanFollow()} is false
600     */
601    @CheckForNull
602    Turnout getLeadingTurnout();
603
604    /**
605     * Set the Turnout this Turnout will follow.
606     * <p>
607     * It is valid for two or more turnouts to follow each other in a circular
608     * pattern.
609     * <p>
610     * It is recommended that a following turnout's feedback mode be
611     * {@link #DIRECT}.
612     * <p>
613     * It is recommended to explicitly call
614     * {@link #setFollowingCommandedState(boolean)} after calling this method or
615     * to use {@link #setLeadingTurnout(jmri.Turnout, boolean)} to ensure this
616     * Turnout follows the leading Turnout in the expected manner.
617     *
618     * @param turnout the leading Turnout or null if this Turnout should not
619     *                follow another Turnout; silently ignored if
620     *                {@link #isCanFollow()} is false
621     */
622    void setLeadingTurnout(@CheckForNull Turnout turnout);
623
624    /**
625     * Set both the leading Turnout and if the commanded state of the leading
626     * Turnout is followed. This is a convenience method for calling both
627     * {@link #setLeadingTurnout(jmri.Turnout)} and
628     * {@link #setFollowingCommandedState(boolean)}.
629     *
630     * @param turnout                 the leading Turnout or null if this
631     *                                Turnout should not follow another Turnout;
632     *                                silently ignored if {@link #isCanFollow()}
633     *                                is false
634     * @param followingCommandedState true to have all states match leading
635     *                                turnout; false to only have non-commanded
636     *                                states match
637     */
638    void setLeadingTurnout(@CheckForNull Turnout turnout, boolean followingCommandedState);
639
640    /**
641     * Check if this Turnout is following all states or only the non-commanded
642     * states of the leading Turnout.
643     *
644     * @return true if following all states; false otherwise
645     */
646    boolean isFollowingCommandedState();
647
648    /**
649     * Set if this Turnout follows all states or only the non-commanded states
650     * of the leading Turnout.
651     * <p>
652     * A Turnout can be commanded to be {@link #THROWN} or {@link #CLOSED}, but
653     * can also have additional states {@link #INCONSISTENT} and
654     * {@link #UNKNOWN}. There are some use cases where a following Turnout
655     * should match all states of the leading Turnout, in which case this should
656     * be true, but there are also use cases where the following Turnout should
657     * only match the INCONSISTENT and UNKNOWN states of the leading Turnout,
658     * but should otherwise be independently commanded, in which case this
659     * should be false.
660     *
661     * @param following true to have all states match leading turnout; false to
662     *                  only have non-commanded states match
663     */
664    void setFollowingCommandedState(boolean following);
665
666    /**
667     * Before setting commanded state, if required by manager, apply wait interval until
668     * outputIntervalEnds() to put less pressure on the connection.
669     * <p>
670     * Used to insert a delay before calling {@link #setCommandedState(int)} to spread out a series of
671     * output commands, as in {@link jmri.implementation.MatrixSignalMast#updateOutputs(char[])} and
672     * {@link jmri.implementation.DefaultRoute} class SetRouteThread#run().
673     * Interval value is kept in the Memo per hardware connection.
674     *
675     * @param s turnout state to forward
676     */
677    void setCommandedStateAtInterval(int s);
678
679}