001package jmri.jmrix.tams;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import jmri.Turnout;
005import jmri.implementation.AbstractTurnout;
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009/**
010 * Implement a Turnout via Tams communications.
011 * <p>
012 * This object doesn't listen to the Tams communications. This is because it
013 * should be the only object that is sending messages for this turnout; more
014 * than one Turnout object pointing to a single device is not allowed.
015 * <p>
016 * Based on work by Bob Jacobsen and Kevin Dickerson Copyright
017 *
018 * @author Jan Boen
019 */
020public class TamsTurnout extends AbstractTurnout
021        implements TamsListener {
022
023    /**
024     * Tams turnouts use the NMRA number (0-2040) as their numerical
025     * identification in the system name.
026     *
027     * @param number DCC address of the turnout
028     * @param prefix system prefix
029     * @param etc Tams system connection traffic controller
030     */
031    public TamsTurnout(int number, String prefix, TamsTrafficController etc) {
032        super(prefix + "T" + number);
033        _number = number;
034        tc = etc;
035        // Request status of turnout
036        TamsMessage m = new TamsMessage("xT " + _number + ",,0");
037        m.setBinary(false);
038        m.setReplyType('T');
039        tc.sendTamsMessage(m, this);
040
041        _validFeedbackTypes |= MONITORING;
042        _activeFeedbackType = MONITORING;
043
044        // if needed, create the list of feedback mode
045        // names with additional LocoNet-specific modes
046        if (modeNames == null) {
047            initFeedbackModes();
048        }
049        _validFeedbackNames = modeNames;
050        _validFeedbackModes = modeValues;
051    }
052
053    @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD",
054            justification = "Only used during creation of 1st turnout")
055    private void initFeedbackModes() {
056        log.debug("*** initFeedbackModes ***");
057        if (_validFeedbackNames.length != _validFeedbackModes.length) {
058            log.error("int and string feedback arrays different length");
059        }
060        String[] tempModeNames = new String[_validFeedbackNames.length + 1];
061        int[] tempModeValues = new int[_validFeedbackNames.length + 1];
062        for (int i = 0; i < _validFeedbackNames.length; i++) {
063            tempModeNames[i] = _validFeedbackNames[i];
064            tempModeValues[i] = _validFeedbackModes[i];
065        }
066        tempModeNames[_validFeedbackNames.length] = "MONITORING";
067        tempModeValues[_validFeedbackNames.length] = MONITORING;
068
069        modeNames = tempModeNames;
070        modeValues = tempModeValues;
071    }
072
073    static String[] modeNames = null;
074    static int[] modeValues = null;
075
076    private final TamsTrafficController tc;
077
078    /**
079     * {@inheritDoc}
080     */
081    @Override
082    protected void forwardCommandChangeToLayout(int newState) {
083        log.debug("*** forwardCommandChangeToLayout ***");
084        // sort out states
085        if ((newState & Turnout.CLOSED) != 0) {
086            // first look for the double case, which we can't handle
087            if ((newState & Turnout.THROWN) != 0) {
088                // this is the disaster case!
089                log.error("Cannot command both CLOSED and THROWN {}", newState);
090            } else {
091                // send a CLOSED command
092                sendMessage(!getInverted());
093            }
094        } else {
095            // send a THROWN command
096            sendMessage(getInverted());
097        }
098    }
099
100    // data members
101    int _number; // turnout number
102
103    /**
104     * Set the turnout known state to reflect what's been observed from the
105     * command station messages. A change there means that somebody commanded a
106     * state change (by using a throttle), and that command has
107     * already taken effect. Hence we use "newCommandedState" to indicate it's
108     * taken place. Must be followed by "newKnownState" to complete the turnout
109     * action.
110     *
111     * @param state Observed state, updated state from command station
112     */
113    synchronized void setCommandedStateFromCS(int state) {
114        log.debug("*** setCommandedStateFromCS ***");
115        if ((getFeedbackMode() != MONITORING)) {
116            log.debug("Returning");
117            return;
118        }
119        log.debug("Setting to state {}", state);
120        newCommandedState(state);
121    }
122
123    /**
124     * Set the turnout known state to reflect what's been observed from the
125     * command station messages. A change there means that somebody commanded a
126     * state change (by using a throttle), and that command has
127     * already taken effect. Hence we use "newKnownState" to indicate it's taken
128     * place.
129     *
130     * @param state Observed state, updated state from command station
131     */
132    synchronized void setKnownStateFromCS(int state) {
133        log.debug("*** setKnownStateFromCS ***");
134        if ((getFeedbackMode() != MONITORING)) {
135            return;
136        }
137        newKnownState(state);
138    }
139
140    @Override
141    public void turnoutPushbuttonLockout(boolean b) {
142    }
143
144    /**
145     * Tams turnouts can be inverted.
146     */
147    @Override
148    public boolean canInvert() {
149        return true;
150    }
151
152    /**
153     * Tell the layout to go to new state.
154     *
155     * @param closed State of the turnout to be sent to the command station
156     */
157    protected void sendMessage(boolean closed) {
158        log.debug("*** sendMessage ***");
159        // get control
160        TamsMessage m = new TamsMessage("xT " + _number + "," + (closed ? "r" : "g") + ",1");
161        tc.sendTamsMessage(m, this);
162    }
163
164    // Listen for status changes from Tams system.
165    @Override
166    public void reply(TamsReply m) {
167        log.debug("*** TamsReply ***");
168        log.debug("m.match(\"T\") = {}", Integer.toString(m.match("T")));
169        String msg = m.toString();
170        log.debug("Turnout Reply = {}", msg);
171        if (m.match("T") == 0) {
172            String[] lines = msg.split(" ");
173            if (lines[1].equals("" + _number)) {
174                //updateReceived = true; // uncomment when pollForStatus() works
175                if (lines[2].equals("r") || lines[2].equals("0")) {
176                    log.debug("Turnout {} = CLOSED", _number);
177                    setCommandedStateFromCS(Turnout.CLOSED);
178                    setKnownStateFromCS(Turnout.CLOSED);
179                } else {
180                    log.debug("Turnout {} = THROWN", _number);
181                    setCommandedStateFromCS(Turnout.THROWN);
182                    setKnownStateFromCS(Turnout.THROWN);
183                }
184            }
185        }
186    }
187
188    //boolean updateReceived = false;
189
190    /*protected void pollForStatus() {
191        if (_activeFeedbackType == MONITORING) {
192            log.debug("*** pollForStatus ***");
193            //if we received an update last time we send a request again, but if we did not we shall skip it once and try again next time.
194            if (updateReceived) {
195                updateReceived = false;
196                TamsMessage m = new TamsMessage("xT " + _number + ",,1");
197                m.setTimeout(TamsMessage.POLLTIMEOUT);
198                tc.sendTamsMessage(m, this);
199            } else {
200                updateReceived = true;
201            }
202        }
203    }*/
204
205    @Override
206    public void setFeedbackMode(int mode) throws IllegalArgumentException {
207        log.debug("*** setFeedbackMode ***");
208        TamsMessage m = new TamsMessage("xT " + _number + ",,1");
209        if (mode == MONITORING) {
210            tc.sendTamsMessage(m, this);//Only send a message once
211            //The rest gets done via polling from TamsTurnoutManager
212        }
213        super.setFeedbackMode(mode);
214    }
215
216    @Override
217    public void message(TamsMessage m) {
218        log.debug("*** message ***");
219        // messages are ignored
220    }
221
222    @Override
223    public void dispose() {
224        log.debug("*** dispose ***");
225        TamsMessage m = new TamsMessage("xT " + _number + ",,1");
226        tc.removePollMessage(m, this);
227        super.dispose();
228    }
229
230    private final static Logger log = LoggerFactory.getLogger(TamsTurnout.class);
231
232}