001package jmri.implementation;
002
003import jmri.InstanceManager;
004import jmri.NamedBeanHandle;
005import jmri.Turnout;
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009/**
010 * Extend jmri.SignalHead for signals implemented by an SE8C.
011 * <p>
012 * This implementation writes out to the physical signal when it's commanded to
013 * change appearance, and updates its internal state when it hears commands from
014 * other places.
015 * <p>
016 * To get a complete set of aspects, we assume that the SE8C board has been
017 * configured such that the 4th aspect is "dark". We then do flashing aspects by
018 * commanding the lit appearance to change.
019 * <p>
020 * We can't assume any form of numbering for Turnouts to address the digits, so
021 * we take two turnout names as arguments. As a convenience, we manage the user
022 * names if they're not already set.
023 * <p>
024 * Only the DARK, RED, GREEN and YELLOW appearances will be properly tracked
025 * when they occur on the LocoNet. The FLASHING aspects won't be, nor will the
026 * Held or Lit states.
027 * <p>
028 * The algorithms in this class are a collaborative effort of Digitrax, Inc and
029 * Bob Jacobsen.
030 *
031 * @author Bob Jacobsen Copyright (C) 2002, 2010, 2014
032 */
033public class SE8cSignalHead extends DefaultSignalHead {
034
035    /**
036     * Primary ctor.
037     *
038     * @param lowTO    lower-numbered Turnout reference
039     * @param highTO   higher-numbered Turnout reference
040     * @param userName user name for mast
041     */
042    public SE8cSignalHead(NamedBeanHandle<Turnout> lowTO,
043            NamedBeanHandle<Turnout> highTO,
044            String userName) {
045        // create systemname
046        super(makeSystemName(lowTO, highTO), userName);
047        this.lowTurnout = lowTO;
048        this.highTurnout = highTO;
049        init();
050    }
051
052    /**
053     * Primary ctor without user name.
054     *
055     * @param lowTO  lower-numbered Turnout reference
056     * @param highTO higher-numbered Turnout reference
057     */
058    public SE8cSignalHead(NamedBeanHandle<Turnout> lowTO,
059            NamedBeanHandle<Turnout> highTO) {
060        // create systemname
061        super(makeSystemName(lowTO, highTO));
062        this.lowTurnout = lowTO;
063        this.highTurnout = highTO;
064        init();
065    }
066
067    /**
068     * Ctor specifying system name and user name.
069     *
070     * @param sname    system name for mast
071     * @param lowTO    lower-numbered Turnout reference
072     * @param highTO   higher-numbered Turnout reference
073     * @param userName user name for mast
074     */
075    public SE8cSignalHead(String sname, NamedBeanHandle<Turnout> lowTO,
076            NamedBeanHandle<Turnout> highTO,
077            String userName) {
078        // create systemname
079        super(sname, userName);
080        this.lowTurnout = lowTO;
081        this.highTurnout = highTO;
082        init();
083    }
084
085    /**
086     * Ctor specifying system name.
087     *
088     * @param sname  system name for mast
089     * @param lowTO  lower-numbered Turnout reference
090     * @param highTO higher-numbered Turnout reference
091     */
092    public SE8cSignalHead(String sname, NamedBeanHandle<Turnout> lowTO,
093            NamedBeanHandle<Turnout> highTO) {
094        // create systemname
095        super(sname);
096        this.lowTurnout = lowTO;
097        this.highTurnout = highTO;
098        init();
099    }
100
101    /**
102     * Compatibility ctor.
103     *
104     * @param pNumber  number (address) of low turnout
105     * @param userName name to use for this signal head
106     */
107    public SE8cSignalHead(int pNumber, String userName) {
108        super("LH" + pNumber, userName);
109        this.lowTurnout = makeHandle(pNumber);
110        this.highTurnout = makeHandle(pNumber + 1);
111        init();
112    }
113
114    /**
115     * Implement convention for making a system name.
116     * <p>
117     * Must pass arguments, as it is used before object is complete.
118     *
119     * @param lowTO  lower-numbered Turnout reference
120     * @param highTO higher-numbered Turnout reference
121     * @return system name with fixed elements, i.e. IH:SE8c:to1\to2
122     */
123    static String makeSystemName(NamedBeanHandle<Turnout> lowTO,
124            NamedBeanHandle<Turnout> highTO) {
125        return ("IH:SE8c:\"" + lowTO.getName() + "\";\"" + highTO.getName() + "\"");
126    }
127
128    /**
129     * Create a handle from a raw number.
130     * <p>
131     * Static, so can be referenced before ctor complete.
132     *
133     * @param i index number (address) of a turnout on the signal head
134     * @return NamedBeanHandle&lt;Turnout&gt; object to use as output for head
135     * @throws IllegalArgumentException when creation from i fails
136     */
137    static NamedBeanHandle<Turnout> makeHandle(int i) throws IllegalArgumentException {
138        String number = "" + i;
139        return jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(
140                number,
141                InstanceManager.turnoutManagerInstance().provideTurnout(number)
142        );
143    }
144
145    /**
146     * Compatibility ctor
147     *
148     * @param pNumber number (address) of low turnout
149     */
150    public SE8cSignalHead(int pNumber) {
151        super("LH" + pNumber);
152        this.lowTurnout = makeHandle(pNumber);
153        this.highTurnout = makeHandle(pNumber + 1);
154        init();
155    }
156
157    NamedBeanHandle<Turnout> lowTurnout;
158    NamedBeanHandle<Turnout> highTurnout;
159
160    void init() {
161        // basic operation, nothing but ON messages needed
162        lowTurnout.getBean().setBinaryOutput(true);
163        highTurnout.getBean().setBinaryOutput(true);
164
165        // ensure default appearance
166        mAppearance = DARK;  // start turned off
167        updateOutput();
168    }
169
170    /**
171     * Type-specific routine to handle output to the layout hardware.
172     * Implemented to handle a request to change state by sending a LocoNet
173     * command.
174     * <p>
175     * Does not notify listeners of changes; that's done elsewhere. Should
176     * consider the following variables to determine what to send:
177     * <ul>
178     * <li>mAppearance
179     * <li>mLit
180     * <li>mFlashOn
181     * </ul>
182     */
183    @Override
184    protected void updateOutput() {
185        if (!mLit) {
186            highTurnout.getBean().setCommandedState(Turnout.CLOSED);
187        } else if (!mFlashOn
188                && ((mAppearance == FLASHGREEN)
189                || (mAppearance == FLASHYELLOW)
190                || (mAppearance == FLASHRED))) {
191            // flash says to make output dark;
192            // flashing-but-lit is handled below
193            highTurnout.getBean().setCommandedState(Turnout.CLOSED);
194        } else {
195            // which of the four states?
196            switch (mAppearance) {
197                case FLASHRED:
198                case RED:
199                    lowTurnout.getBean().setCommandedState(Turnout.THROWN);
200                    break;
201                case FLASHYELLOW:
202                case YELLOW:
203                    highTurnout.getBean().setCommandedState(Turnout.THROWN);
204                    break;
205                case FLASHGREEN:
206                case GREEN:
207                    lowTurnout.getBean().setCommandedState(Turnout.CLOSED);
208                    break;
209                case DARK:
210                    highTurnout.getBean().setCommandedState(Turnout.CLOSED);
211                    break;
212                default:
213                    log.error("Invalid state request: {}", mAppearance);
214            }
215        }
216    }
217
218    public NamedBeanHandle<Turnout> getLow() {
219        return lowTurnout;
220    }
221
222    public NamedBeanHandle<Turnout> getHigh() {
223        return highTurnout;
224    }
225
226    @Override
227    public void dispose() {
228    }
229
230    @Override
231    boolean isTurnoutUsed(Turnout t) {
232        return (getLow() != null && t.equals(getLow().getBean()))
233                || (getHigh() != null && t.equals(getHigh().getBean()));
234    }
235
236    private final static Logger log = LoggerFactory.getLogger(SE8cSignalHead.class);
237}