001package jmri.jmrix.lenz;
002
003import java.util.Arrays;
004import java.util.LinkedList;
005import java.util.Queue;
006import jmri.implementation.AbstractTurnout;
007import javax.annotation.concurrent.GuardedBy;
008
009/**
010 * Extend jmri.AbstractTurnout for XNet layouts
011 * <p>
012 * Turnout operation on XpressNet based systems goes through the following
013 * sequence:
014 * <ul>
015 * <li> set the commanded state, and, Send request to command station to start
016 * sending DCC operations packet to track</li>
017 * <li> Wait for response message from command station. (valid response list
018 * follows)</li>
019 * <li> Send request to command station to stop sending DCC operations packet to
020 * track</li>
021 * <li> Wait for response from command station
022 * <ul>
023 * <li>If Success Message, set Known State to Commanded State</li>
024 * <li>If error message, repeat previous step</li>
025 * </ul>
026 * </li>
027 * </ul>
028 * <p>
029 * NOTE: Some XpressNet Command stations take no action when the message
030 * generated during the third step is received.
031 * <p>
032 * Valid response messages are command station dependent, but there are 4
033 * possibilities:
034 * <ul>
035 * <li> a "Command Successfully Received..." (aka "OK") message</li>
036 * <li> a "Feedback Response Message" indicating the message is for a turnout
037 * with feedback</li>
038 * <li> a "Feedback Response Message" indicating the message is for a turnout
039 * without feedback</li>
040 * <li> The XpressNet protocol allows for no response. </li>
041 * </ul>
042 * <p>
043 * Response NOTE 1: The "Command Successfully Received..." message is generated
044 * by the lenz LIxxx interfaces when it successfully transfers the command to
045 * the command station. When this happens, the command station generates no
046 * useable response message.
047 * <p>
048 * Response NOTE 2: Currently the only command stations known to generate
049 * Feedback response messages are the Lenz LZ100 and LZV100.
050 * <p>
051 * Response NOTE 3: Software version 3.2 and above LZ100 and LZV100 may send
052 * either a Feedback response or no response at all. All other known command
053 * stations generate no response.
054 * <p>
055 * Response NOTE 4: The Feedback response messages may be generated
056 * asynchronously
057 * <p>
058 * Response NOTE 5: Feedback response messages may contain feedback for more
059 * than one device. The devices included in the response may or may not be
060 * stationary decoders (they can also be feedback encoders see
061 * {@link XNetSensor}).
062 * <p>
063 * Response NOTE 6: The last situation situation is not currently handled. The
064 * supported interfaces garantee at least an "OK" message will be sent to the
065 * computer
066 * <p>
067 * What is done with each of the response messages depends on which feedback
068 * mode is in use. "DIRECT,"MONITORING", and "EXACT" feedback mode are supported
069 * directly by this class.
070 * <p>
071 * "DIRECT" mode instantly triggers step 3 when any valid response message for
072 * this turnout is received from the command station or computer interface.
073 * <p>
074 * "SIGNAL" mode is identical to "DIRECT" mode, except it skips step 2. i.e. it
075 * triggers step 3 without receiving any reply from the command station.
076 * <p>
077 * "MONITORING" mode is an extention to direct mode. In monitoring mode, a
078 * feedback response message (for a turnout with or without feedback) is
079 * interpreted to set the known state of the turnout based on information
080 * provided by the command station.
081 * <p>
082 * "MONITORING" mode will interpret the feedback response messages when they are
083 * generated by external sources (fascia controls or other XpressNet devices)
084 * and that information is received by the computer.
085 * <p>
086 * "EXACT" mode is an extention of "MONITORING" mode. In addition to
087 * interpretting all feedback messages from the command station, "EXACT" mode
088 * will monitor the "motion complete" bit of the feedback response.
089 * <p>
090 * For turnouts without feedback, the motion complete bit is always set, so
091 * "EXACT" mode handles these messages as though the specified feedback mode is
092 * "MONITORING" mode.
093 * <p>
094 * For turnouts with feedback, "EXACT" mode polls the command station until the
095 * motion complete bit is set before triggering step 3 of the turnout operation
096 * sequence.
097 * <p>
098 * "EXACT" mode will interpret the feedback response messages when they are
099 * generated by external sources (fascia controls or other XpressNet devices)
100 * and that information is received by the computer.
101 * <p>
102 * NOTE: For LZ100 and LZV100 command stations prior to version 3.2, it may be
103 * necessary to poll for the feedback response data.
104 *
105 * @author Bob Jacobsen Copyright (C) 2001
106 * @author      Paul Bender Copyright (C) 2003-2010
107 */
108public class XNetTurnout extends AbstractTurnout implements XNetListener {
109
110    /* State information */
111    protected static final int OFFSENT = 1;
112    protected static final int COMMANDSENT = 2;
113    protected static final int STATUSREQUESTSENT = 4;
114    protected static final int QUEUEDMESSAGE = 8;
115    protected static final int IDLE = 0;
116    protected int internalState = IDLE;
117
118    /* Static arrays to hold Lenz specific feedback mode information */
119    static String[] modeNames = null;
120    static int[] modeValues = null;
121
122    @GuardedBy("this")
123    protected int _mThrown = jmri.Turnout.THROWN;
124    @GuardedBy("this")
125    protected int _mClosed = jmri.Turnout.CLOSED;
126
127    protected int mNumber;   // XpressNet turnout number
128    final XNetTurnoutStateListener _stateListener;  // Internal class object
129
130    // A queue to hold outstanding messages
131    @GuardedBy("this")
132    protected final Queue<RequestMessage> requestList;
133
134    @GuardedBy("this")
135    protected RequestMessage lastMsg = null;
136
137    protected final String _prefix; // default
138    protected final XNetTrafficController tc;
139
140    public XNetTurnout(String prefix, int pNumber, XNetTrafficController controller) {  // a human-readable turnout number must be specified!
141        super(prefix + "T" + pNumber);
142        tc = controller;
143        _prefix = prefix;
144        mNumber = pNumber;
145
146        requestList = new LinkedList<>();
147
148        /* Add additional feedback types information */
149        _validFeedbackTypes |= MONITORING | EXACT | SIGNAL;
150
151        // Default feedback mode is MONITORING
152        _activeFeedbackType = MONITORING;
153
154        setModeInformation(_validFeedbackNames, _validFeedbackModes);
155
156        // set the mode names and values based on the static values.
157        _validFeedbackNames = getModeNames();
158        _validFeedbackModes = getModeValues();
159        
160        // Register to get property change information from the superclass
161        _stateListener = new XNetTurnoutStateListener(this);
162        this.addPropertyChangeListener(_stateListener);
163        // Finally, request the current state from the layout.
164        tc.getFeedbackMessageCache().requestCachedStateFromLayout(this);
165    }
166    
167    /**
168     * Set the mode information for XpressNet Turnouts.
169     */
170    private static synchronized void setModeInformation(String[] feedbackNames, int[] feedbackModes) {
171        // if it hasn't been done already, create static arrays to hold
172        // the Lenz specific feedback information.
173        if (modeNames == null) {
174            if (feedbackNames.length != feedbackModes.length) {
175                log.error("int and string feedback arrays different length");
176            }
177            modeNames = Arrays.copyOf(feedbackNames, feedbackNames.length + 3);
178            modeValues = Arrays.copyOf(feedbackModes, feedbackNames.length + 3);
179            modeNames[feedbackNames.length] = "MONITORING";
180            modeValues[feedbackNames.length] = MONITORING;
181            modeNames[feedbackNames.length + 1] = "EXACT";
182            modeValues[feedbackNames.length + 1] = EXACT;
183            modeNames[feedbackNames.length + 2] = "SIGNAL";
184            modeValues[feedbackNames.length + 2] = SIGNAL;
185        }
186    }
187
188    static int[] getModeValues() {
189        return modeValues;
190    }
191
192    static String[] getModeNames() {
193        return modeNames;
194    }
195
196    public int getNumber() {
197        return mNumber;
198    }
199
200    /**
201     * Set the Commanded State.
202     * This method overides {@link jmri.implementation.AbstractTurnout#setCommandedState(int)}.
203     */
204    @Override
205    public void setCommandedState(int s) {
206        if (log.isDebugEnabled()) {
207            log.debug("set commanded state for XNet turnout {} to {}", getSystemName(), s);
208        }
209        synchronized (this) {
210            newCommandedState(s);
211        }
212        myOperator = getTurnoutOperator(); // MUST set myOperator before starting the thread
213        if (myOperator == null) {
214            forwardCommandChangeToLayout(s);
215            synchronized (this) {
216                newKnownState(INCONSISTENT);
217            }
218        } else {
219            myOperator.start();
220        }
221    }
222
223    /**
224     * {@inheritDoc}
225     * Sends an XpressNet command.
226     */
227    @Override
228    protected synchronized void forwardCommandChangeToLayout(int s) {
229        if (s != _mClosed && s != _mThrown) {
230            log.warn("Turnout {}: state {} not forwarded to layout.", mNumber, s);
231            return;
232        }
233        // get the right packet
234        XNetMessage msg = XNetMessage.getTurnoutCommandMsg(mNumber,
235                (s & _mClosed) != 0,
236                (s & _mThrown) != 0,
237                true);
238        if (getFeedbackMode() == SIGNAL) {
239            msg.setTimeout(0); // Set the timeout to 0, so the off message can
240            // be sent immediately.
241            // leave the next line commented out for now.
242            // It may be enabled later to allow SIGNAL mode to ignore
243            // directed replies, which lets the traffic controller move on
244            // to the next message without waiting.
245            //msg.setBroadcastReply();
246            tc.sendXNetMessage(msg, null);
247            sendOffMessage();
248        } else {
249            queueMessage(msg, COMMANDSENT, this);
250        }
251    }
252
253    @Override
254    protected void turnoutPushbuttonLockout(boolean _pushButtonLockout) {
255        log.debug("Send command to {} Pushbutton {}T{}", (_pushButtonLockout ? "Lock" : "Unlock"), _prefix, mNumber);
256    }
257
258    /**
259     * Request an update on status by sending an XpressNet message.
260     */
261    @Override
262    public void requestUpdateFromLayout() {
263        // This will handle ONESENSOR and TWOSENSOR feedback modes.
264        super.requestUpdateFromLayout();
265
266        // To do this, we send an XpressNet Accessory Decoder Information
267        // Request.
268        // The generated message works for Feedback modules and turnouts
269        // with feedback, but the address passed is translated as though it
270        // is a turnout address.  As a result, we substitute our base
271        // address in for the address. after the message is returned.
272        XNetMessage msg = XNetMessage.getFeedbackRequestMsg(mNumber,
273                ((mNumber - 1) % 4) < 2);
274        queueMessage(msg,IDLE,null); //status is returned via the manager.
275
276    }
277
278    /**
279     * {@inheritDoc}
280     */
281    @Override
282    public synchronized void setInverted(boolean inverted) {
283        log.debug("Inverting Turnout State for turnout {}T{}", _prefix, mNumber);
284        if (inverted) {
285            _mThrown = jmri.Turnout.CLOSED;
286            _mClosed = jmri.Turnout.THROWN;
287        } else {
288            _mThrown = jmri.Turnout.THROWN;
289            _mClosed = jmri.Turnout.CLOSED;
290        }
291        super.setInverted(inverted);
292    }
293
294    @Override
295    public boolean canInvert() {
296        return true;
297    }
298
299    /**
300     * Package protected class which allows the Manger to send
301     * a feedback message at initialization without changing the state of the
302     * turnout with respect to whether or not a feedback request was sent. This
303     * is used only when the turnout is created by on layout feedback.
304     * @param l Message to initialize
305     */
306    synchronized void initmessage(XNetReply l) {
307        int oldState = internalState;
308        message(l);
309        internalState = oldState;
310    }
311
312    /**
313     * Handle an incoming message from the XpressNet.
314     */
315    @Override
316    public synchronized void message(XNetReply l) {
317        log.debug("received message: {}", l);
318        if (internalState == OFFSENT) {
319            if (l.isOkMessage() && !l.isUnsolicited()) {
320                /* the command was successfully received */
321                synchronized (this) {
322                    newKnownState(getCommandedState());
323                }
324                sendQueuedMessage();
325                return;
326            } else if (l.isRetransmittableErrorMsg()) {
327                return; // don't do anything, the Traffic
328                // Controller is handling retransmitting
329                // this one.
330            } else {
331                /* Default Behavior: If anything other than an OK message
332                 is received, Send another OFF message. */
333                log.debug("Message is not OK message. Message received was: {}", l);
334                sendOffMessage();
335            }
336        }
337
338        switch (getFeedbackMode()) {
339            case EXACT:
340                handleExactModeFeedback(l);
341                break;
342            case MONITORING:
343                handleMonitoringModeFeedback(l);
344                break;
345            case DIRECT:
346            default:
347                // Default is direct mode
348                handleDirectModeFeedback(l);
349        }
350    }
351
352    /**
353     * Listen for the messages to the LI100/LI101.
354     */
355    @Override
356    public synchronized void message(XNetMessage l) {
357        log.debug("received outgoing message {} for turnout {}",l,getSystemName());
358        // we want to verify this is the last message we sent
359        // so use == not .equals
360        if(lastMsg!=null && l == lastMsg.msg){
361            //if this is the last message we sent, set the state appropriately
362            internalState = lastMsg.getState();
363            // and set lastMsg to null
364            lastMsg = null;
365        }
366    }
367
368    /**
369     * Handle a timeout notification.
370     */
371    @Override
372    public synchronized void notifyTimeout(XNetMessage msg) {
373        log.debug("Notified of timeout on message {}", msg);
374        // If we're in the OFFSENT state, we need to send another OFF message.
375        if (internalState == OFFSENT) {
376            sendOffMessage();
377        }
378    }
379
380    /**
381     *  With Direct Mode feedback, if we see ANY valid response to our
382     *  request, we ask the command station to stop sending information
383     *  to the stationary decoder.
384     *  <p>
385     *  No effort is made to interpret feedback when using direct mode.
386     *
387     *  @param l an {@link XNetReply} message
388     */
389    private synchronized void handleDirectModeFeedback(XNetReply l) {
390        /* If commanded state does not equal known state, we are
391         going to check to see if one of the following conditions
392         applies:
393         1) The received message is a feedback message for a turnout
394         and one of the two addresses to which it applies is our
395         address
396         2) We receive an "OK" message, indicating the command was
397         successfully sent
398
399         If either of these two cases occur, we trigger an off message
400         */
401
402        log.debug("Handle Message for turnout {} in DIRECT feedback mode   ", mNumber);
403        if (getCommandedState() != getKnownState() || internalState == COMMANDSENT) {
404            if (l.isOkMessage()) {
405                // Finally, we may just receive an OK message.
406                log.debug("Turnout {} DIRECT feedback mode - OK message triggering OFF message.", mNumber);
407            } else {
408                // implicitly checks for isFeedbackBroadcastMessage()
409                if (!l.selectTurnoutFeedback(mNumber).isPresent()) {
410                    return;
411                }
412                log.debug("Turnout {} DIRECT feedback mode - directed reply received.", mNumber);
413            }
414            sendOffMessage();
415            // Explicitly send two off messages in Direct Mode
416            sendOffMessage();
417        }
418    }
419
420    /**
421     *  With Monitoring Mode feedback, if we see a feedback message, we
422     *  interpret that message and use it to display our feedback.
423     *  <p>
424     *  After we send a request to operate a turnout, We ask the command
425     *  station to stop sending information to the stationary decoder
426     *  when the either a feedback message or an "OK" message is received.
427     *
428     *  @param l an {@link XNetReply} message
429     */
430    private synchronized void handleMonitoringModeFeedback(XNetReply l) {
431        /* In Monitoring Mode, We have two cases to check if CommandedState
432         does not equal KnownState, otherwise, we only want to check to
433         see if the messages we receive indicate this turnout chagned
434         state
435         */
436        log.debug("Handle Message for turnout {} in MONITORING feedback mode ", mNumber);
437        if (internalState == IDLE || internalState == STATUSREQUESTSENT) {
438            if (l.onTurnoutFeedback(mNumber, this::parseFeedbackMessage)) {
439                log.debug("Turnout {} MONITORING feedback mode - state change from feedback.", mNumber);
440            }
441        } else if (getCommandedState() != getKnownState()
442                || internalState == COMMANDSENT) {
443            if (l.isOkMessage()) {
444                // Finally, we may just receive an OK message.
445                log.debug("Turnout {} MONITORING feedback mode - OK message triggering OFF message.", mNumber);
446                sendOffMessage();
447            } else {
448                // In Monitoring mode, treat both turnouts with feedback
449                // and turnouts without feedback as turnouts without
450                // feedback.  i.e. just interpret the feedback
451                // message, don't check to see if the motion is complete
452                // implicitly checks for isFeedbackBroadcastMessage()
453                if (l.onTurnoutFeedback(mNumber, this::parseFeedbackMessage)) {
454                    // We need to tell the turnout to shut off the output.
455                    log.debug("Turnout {} MONITORING feedback mode - state change from feedback, CommandedState != KnownState.", mNumber);
456                    sendOffMessage();
457                }
458            }
459        }
460    }
461
462    /**
463     *  With Exact Mode feedback, if we see a feedback message, we
464     *  interpret that message and use it to display our feedback.
465     *  <p>
466     *  After we send a request to operate a turnout, We ask the command
467     *  station to stop sending information to the stationary decoder
468     *  when the either a feedback message or an "OK" message is received.
469     *
470     *  @param reply The reply message to process
471     */
472    private synchronized void handleExactModeFeedback(XNetReply reply) {
473        // We have three cases to check if CommandedState does
474        // not equal KnownState, otherwise, we only want to check to
475        // see if the messages we receive indicate this turnout chagned
476        // state
477        log.debug("Handle Message for turnout {} in EXACT feedback mode ", mNumber);
478        if (getCommandedState() == getKnownState()
479                && (internalState == IDLE || internalState == STATUSREQUESTSENT)) {
480            // This is a feedback message, we need to check and see if it
481            // indicates this turnout is to change state or if it is for
482            // another turnout.
483            if (reply.onTurnoutFeedback(mNumber, this::parseFeedbackMessage)) {
484                log.debug("Turnout {} EXACT feedback mode - state change from feedback.", mNumber);
485            }
486        } else if (getCommandedState() != getKnownState()
487                || internalState == COMMANDSENT
488                || internalState == STATUSREQUESTSENT) {
489            if (reply.isOkMessage()) {
490                // Finally, we may just receive an OK message.
491                log.debug("Turnout {} EXACT feedback mode - OK message triggering OFF message.", mNumber);
492                sendOffMessage();
493            } else {
494                // implicitly checks for isFeedbackBroadcastMessage()
495                reply.selectTurnoutFeedback(mNumber).ifPresent(l -> {
496                    int messageType = l.getType();
497                    switch (messageType) {
498                        case 1: {
499                            // The first case is that we receive a message for
500                            // this turnout and this turnout provides feedback.
501                            // In this case, we want to check to see if the
502                            // turnout has completed its movement before doing
503                            // anything else.
504                            if (!l.isMotionComplete()) {
505                                log.debug("Turnout {} EXACT feedback mode - state change from feedback, CommandedState!=KnownState - motion not complete", mNumber);
506                                // If the motion is NOT complete, send a feedback
507                                // request for this nibble
508                                XNetMessage msg = XNetMessage.getFeedbackRequestMsg(
509                                        mNumber, ((mNumber % 4) <= 1));
510                                queueMessage(msg,STATUSREQUESTSENT ,null); //status is returned via the manager.
511                                return;
512                            } else {
513                                log.debug("Turnout {} EXACT feedback mode - state change from feedback, CommandedState!=KnownState - motion complete", mNumber);
514                            }
515                            break;
516                        }
517                        case 0: 
518                            log.debug("Turnout {} EXACT feedback mode - state change from feedback, CommandedState!=KnownState - motion complete", mNumber);
519                            // The second case is that we receive a message about
520                            // this turnout, and this turnout does not provide
521                            // feedback. In this case, we want to check the
522                            // contents of the message and act accordingly.
523                            break;
524                        default: return;
525                    }
526                    parseFeedbackMessage(l);
527                    // We need to tell the turnout to shut off the output.
528                    sendOffMessage();
529                });
530            }
531        }
532    }
533    
534    /**
535     * Send an "Off" message to the decoder for this output. 
536     */
537    protected synchronized void sendOffMessage() {
538        // We need to tell the turnout to shut off the output.
539        if (log.isDebugEnabled()) {
540            log.debug("Sending off message for turnout {} commanded state={}", mNumber, getCommandedState());
541            log.debug("Current Thread ID: {} Thread Name {}", java.lang.Thread.currentThread().getId(), java.lang.Thread.currentThread().getName());
542        }
543        XNetMessage msg = getOffMessage();
544        lastMsg = new RequestMessage(msg,OFFSENT,this);
545        this.internalState = OFFSENT;
546        newKnownState(getCommandedState());
547        // Then send the message.
548        tc.sendHighPriorityXNetMessage(msg, this);
549    }
550
551    protected synchronized XNetMessage getOffMessage(){
552        return ( XNetMessage.getTurnoutCommandMsg(mNumber,
553                getCommandedState() == _mClosed,
554                getCommandedState() == _mThrown,
555                false) );
556    }
557
558    /**
559     * Parse the feedback message, and set the status of the turnout
560     * accordingly.
561     *
562     * @param l  turnout feedback item
563     * 
564     * @return 0 if address matches our turnout -1 otherwise
565     */
566    private synchronized boolean parseFeedbackMessage(FeedbackItem l) {
567        log.debug("Message for turnout {}", mNumber);
568        switch (l.getTurnoutStatus()) {
569            case THROWN:
570                newKnownState(_mThrown);
571                return true;
572            case CLOSED:
573                newKnownState(_mClosed);
574                return true;
575            default:
576                // the state is unknown or inconsistent.  If the command state
577                // does not equal the known state, and the command repeat the
578                // last command
579                if (getCommandedState() != getKnownState()) {
580                    forwardCommandChangeToLayout(getCommandedState());
581                } else {
582                    sendQueuedMessage();
583                }
584                return false;
585        }
586    }
587    
588    @Override
589    public void dispose() {
590        this.removePropertyChangeListener(_stateListener);
591        super.dispose();
592    }
593
594    /**
595     * Internal class to use for listening to state changes.
596     */
597    private static class XNetTurnoutStateListener implements java.beans.PropertyChangeListener {
598
599        final XNetTurnout _turnout;
600
601        XNetTurnoutStateListener(XNetTurnout turnout) {
602            _turnout = turnout;
603        }
604
605        /**
606         * If we're  not using DIRECT feedback mode, we need to listen for
607         * state changes to know when to send an OFF message after we set the
608         * known state.
609         * If we're using DIRECT mode, all of this is handled from the
610         * XpressNet Messages.
611         * @param event The event that causes this operation
612         */
613        @Override
614        public void propertyChange(java.beans.PropertyChangeEvent event) {
615            log.debug("propertyChange called");
616            // If we're using DIRECT feedback mode, we don't care what we see here
617            if (_turnout.getFeedbackMode() != DIRECT) {
618                if (log.isDebugEnabled()) {
619                    log.debug("propertyChange Not Direct Mode property: {} old value {} new value {}", event.getPropertyName(), event.getOldValue(), event.getNewValue());
620                }
621                if (event.getPropertyName().equals("KnownState")) {
622                    // Check to see if this is a change in the status
623                    // triggered by a device on the layout, or a change in
624                    // status we triggered.
625                    int oldKnownState = (Integer) event.getOldValue();
626                    int curKnownState = (Integer) event.getNewValue();
627                    log.debug("propertyChange KnownState - old value {} new value {}", oldKnownState, curKnownState);
628                    if (curKnownState != INCONSISTENT
629                            && _turnout.getCommandedState() == oldKnownState) {
630                        // This was triggered by feedback on the layout, change
631                        // the commanded state to reflect the new Known State
632                        if (log.isDebugEnabled()) {
633                            log.debug("propertyChange CommandedState: {}", _turnout.getCommandedState());
634                        }
635                        _turnout.newCommandedState(curKnownState);
636                    } else {
637                        // Since we always set the KnownState to
638                        // INCONSISTENT when we send a command, If the old
639                        // known state is INCONSISTENT, we just want to send
640                        // an off message
641                        if (oldKnownState == INCONSISTENT) {
642                            if (log.isDebugEnabled()) {
643                                log.debug("propertyChange CommandedState: {}", _turnout.getCommandedState());
644                            }
645                            _turnout.sendOffMessage();
646                        }
647                    }
648                }
649            }
650        }
651
652    }
653
654    /**
655     * Send message from queue.
656     */
657    protected synchronized void sendQueuedMessage() {
658
659        lastMsg = null;
660        // check to see if the queue has a message in it, and if it does,
661        // remove the first message
662        lastMsg = requestList.poll();
663        // if the queue is not empty, remove the first message
664        // from the queue, send the message, and set the state machine
665        // to the required state.
666        if (lastMsg != null) {
667            log.debug("sending message to traffic controller");
668            if(lastMsg.listener!=null) {
669                internalState = QUEUEDMESSAGE;
670            } else {
671                internalState = lastMsg.state;
672            }
673            tc.sendXNetMessage(lastMsg.getMsg(), lastMsg.getListener());
674        } else {
675            log.debug("message queue empty");
676            // if the queue is empty, set the state to idle.
677            internalState = IDLE;
678        }
679    }
680    
681    /**
682     * Queue a message.
683     * @param m Message to send
684     * @param s sequence
685     * @param l Listener to get notification of completion
686     */
687    protected synchronized void queueMessage(XNetMessage m, int s, XNetListener l) {
688        log.debug("adding message {} to message queue.  Current Internal State {}",m,internalState);
689        // put the message in the queue
690        RequestMessage msg = new RequestMessage(m, s, l);
691        // the queue is unbounded; can't throw exceptions 
692        requestList.add(msg);
693        // if the state is idle, trigger the message send
694        if (internalState == IDLE ) {
695            sendQueuedMessage();
696        }
697    }
698
699    /**
700     * Internal class to hold a request message, along with the associated throttle state.
701     */
702    protected static class RequestMessage {
703
704        private final int state;
705        private final XNetMessage msg;
706        private final XNetListener listener;
707
708        RequestMessage(XNetMessage m, int s, XNetListener listener) {
709            state = s;
710            msg = m;
711            this.listener = listener;
712        }
713
714        int getState() {
715            return state;
716        }
717
718        XNetMessage getMsg() {
719            return msg;
720        }
721
722        XNetListener getListener() {
723            return listener;
724        }
725    }
726
727    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(XNetTurnout.class);
728
729}