001package jmri.jmrix.roco.z21;
002
003import jmri.jmrix.lenz.XNetMessage;
004import jmri.jmrix.lenz.XNetReply;
005import jmri.jmrix.lenz.XNetTrafficController;
006import jmri.jmrix.lenz.XNetTurnout;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010/**
011 * Extend jmri.jmrix.lenz.XNetTurnout for Roco Z21/z21 systems.
012 *
013 * @author Paul Bender Copyright (C) 2016
014 */
015public class Z21XNetTurnout extends XNetTurnout {
016
017    public Z21XNetTurnout(String prefix, int pNumber, XNetTrafficController controller) {  
018        super(prefix,pNumber,controller);
019    }
020
021    /**
022     * {@inheritDoc}
023     * Sends an XpressNet command.
024     */
025    @Override
026    protected synchronized void forwardCommandChangeToLayout(int s) {
027        if (s != _mClosed && s != _mThrown) {
028            log.warn("Turnout {}: state {} not forwarded to layout.", mNumber, s);
029            return;
030        }
031        log.debug("Turnout {}: forwarding state  {} to layout.", mNumber, s);
032        // get the right packet
033        XNetMessage msg = Z21XNetMessage.getZ21SetTurnoutRequestMessage(mNumber,
034                (s & _mThrown) != 0,
035                true, false ); // for now always active and not queued.
036        if (getFeedbackMode() == SIGNAL) {
037            msg.setTimeout(0); // Set the timeout to 0, so the off message can
038            // be sent immediately.
039            // leave the next line commented out for now.
040            // it may be enabled later to allow SIGNAL mode to ignore
041            // directed replies, which lets the traffic controller move on
042            // to the next message without waiting.
043            //msg.setBroadcastReply();
044            tc.sendXNetMessage(msg, null);
045            sendOffMessage(s);
046        } else {
047            queueMessage(msg,COMMANDSENT,this);
048        }
049    }
050
051    /**
052     * Request an update on status by sending an XpressNet message.
053     */
054    @Override
055    public void requestUpdateFromLayout() {
056        log.debug("Turnout {} requesting update from layout",mNumber);
057        // This will handle ONESENSOR and TWOSENSOR feedback modes.
058        super.requestUpdateFromLayout();
059
060        // On the z21, we send a LAN_X_GET_TURNOUT_INFO message
061        // (see section 5.1 of the protocol documenation ).
062        XNetMessage msg = Z21XNetMessage.getZ21TurnoutInfoRequestMessage(mNumber);
063        msg.setBroadcastReply();
064        queueMessage(msg,IDLE,null); //status is returned via the manager.
065    }
066
067    // Handle a timeout notification.
068    @Override
069    public synchronized void notifyTimeout(XNetMessage msg) {
070        log.debug("Notified of timeout on message {}",msg);
071        // If we're in the OFFSENT state, we need to send another OFF message.
072        synchronized (this) {
073            if (internalState == OFFSENT) {
074               sendOffMessage(getCommandedState());
075            }
076        }
077    }
078
079    /**
080     * initmessage is a package proteceted class which allows the Manger to send
081     * a feedback message at initialization without changing the state of the
082     * turnout with respect to whether or not a feedback request was sent.
083     * This is used only when the turnout is created by on layout feedback.
084     * @param l Init message
085     */
086    synchronized void initMessageZ21(XNetReply l) {
087        int oldState = internalState;
088        message(l);
089        internalState = oldState;
090    }
091
092    @Override
093    public synchronized void message(XNetReply l) {
094        log.debug("received message: {}",l);
095        if (l.getElement(0)==Z21Constants.LAN_X_TURNOUT_INFO) {
096          // bytes 2 and 3 are the address.
097          int address = (l.getElement(1) << 8) + l.getElement(2);
098          // the address sent byte the Z21 is one less than what JMRI's 
099          // XpressNet code (and Lenz systems) expect.
100          address = address + 1; 
101          if(log.isDebugEnabled()) {
102               log.debug("message has address: {}",address);
103          }
104          // if this is for this turnout, check the turnout state.
105          if(mNumber==address) {
106              int messageState = decodeZ21FeedbackMessageState(l);
107              if(getFeedbackMode() == MONITORING ) {
108                  newKnownState(messageState);
109              } else if(getFeedbackMode()==DIRECT) {
110                  newKnownState(getCommandedState());
111              }
112              if(internalState == COMMANDSENT) {
113                /* the command was successfully received */
114                sendOffMessage(messageState);  // turn off the repetition on the track.
115                // and check to see if there are any more queued messages.
116                sendQueuedMessage();
117             }
118          }
119        } else {
120          super.message(l); // the XpressNetTurnout code
121                            // handles any other replies.
122        }
123    }
124
125    private int decodeZ21FeedbackMessageState(XNetReply l){
126        int state;
127        switch (l.getElement(3)) {
128            case 0x03:
129                state = INCONSISTENT;
130                break;
131            case 0x02:
132                state = _inverted ? CLOSED : THROWN;
133                break;
134            case 0x01:
135                state = _inverted ? THROWN : CLOSED;
136                break;
137            case 0x00:
138            default:
139                state = UNKNOWN;
140        }
141        return state;
142    }
143
144    @Override
145    protected synchronized void sendOffMessage() {
146       sendOffMessage(getCommandedState());
147    }
148
149    @SuppressWarnings("deprecation")    // The method getId() from the type Thread is deprecated since version 19
150                                        // The replacement Thread.threadId() isn't available before version 19
151    protected synchronized void sendOffMessage(int state) {
152        // We need to tell the turnout to shut off the output.
153        if (log.isDebugEnabled()) {
154            log.debug("Sending off message for turnout {} commanded state={}", mNumber, getCommandedState());
155            log.debug("Current Thread ID: {} Thread Name {}", java.lang.Thread.currentThread().getId(), java.lang.Thread.currentThread().getName());
156        }
157        XNetMessage msg = getOffMessage(state == _mThrown);
158        // Set the known state to the commanded state.
159        newKnownState(getCommandedState());
160        internalState = IDLE;
161        // Then send the message.
162        tc.sendXNetMessage(msg, this);  // reply sent through loconet
163    }
164
165    protected synchronized XNetMessage getOffMessage(boolean state) {
166        XNetMessage msg = Z21XNetMessage.getZ21SetTurnoutRequestMessage(mNumber,
167                 state, false, false );// for now always not active and not queued.
168        msg.setBroadcastReply(); // reply comes through loconet
169        return msg;
170    }
171
172    private static final Logger log = LoggerFactory.getLogger(Z21XNetTurnout.class);
173
174}