001package jmri.jmrix.lenz;
002
003import java.util.concurrent.LinkedBlockingQueue;
004
005import jmri.DccLocoAddress;
006import jmri.LocoAddress;
007import jmri.SpeedStepMode;
008import jmri.jmrix.AbstractThrottle;
009
010import org.slf4j.Logger;
011import org.slf4j.LoggerFactory;
012
013import javax.annotation.concurrent.GuardedBy;
014
015/**
016 * An implementation of DccThrottle with code specific to an XpressNet
017 * connection.
018 *
019 * @author Paul Bender (C) 2002-2019
020 * @author Bob Jacobsen (C) 2023
021 */
022public class XNetThrottle extends AbstractThrottle implements XNetListener {
023
024    protected boolean isAvailable;  // Flag  stating if the throttle is in use or not.
025
026    protected java.util.TimerTask statusTask;   // Timer Task used to periodically get current
027    // status of the throttle when throttle not available.
028    protected static final int statTimeoutValue = 1000; // Interval to check the
029
030    /**
031     * Default interval, in milliseconds, after which a throttle still waiting
032     * for the reply to the message it sent gives up and restarts its queue,
033     * provided the traffic controller reports itself at rest.
034     * <p>
035     * When the traffic controller has nothing outstanding and nothing queued
036     * while this throttle is still waiting, the reply it waits for is already
037     * gone - typically delivered to another listener because a broadcast
038     * message was taken for it. No delay can make it arrive, so there is
039     * nothing to gain by waiting longer.
040     * <p>
041     * That state is otherwise absorbing: a throttle which is not idle never
042     * hands another message to the traffic controller, so it never becomes the
043     * current destination again, so it can never receive the reply that would
044     * release it. Every command queued afterwards is held back for the lifetime
045     * of the session.
046     */
047    protected static final int THROTTLE_WATCHDOG_INTERVAL = 5000;
048
049    /**
050     * Unconditional deadline, in milliseconds, expressed as a multiple of
051     * {@link #THROTTLE_WATCHDOG_INTERVAL}.
052     * <p>
053     * While the traffic controller is busy, a reply can legitimately be late:
054     * the message may still be queued behind a message of its own which is
055     * consuming its full timeout. This longer deadline stays clear of that, and
056     * every retransmission re-arms the watchdog, so a message being retried
057     * never trips it.
058     */
059    protected static final int THROTTLE_WATCHDOG_MAX_MULTIPLE = 3;
060
061    @GuardedBy("this")
062    protected XNetTrafficController tc;
063
064    // status of the throttle
065    protected static final int THROTTLEIDLE = 0;  // Idle Throttle
066    protected static final int THROTTLESTATSENT = 1;  // Sent Status request
067    protected static final int THROTTLESPEEDSENT = 2;  // Sent speed/dir command to locomotive
068    protected static final int THROTTLEFUNCSENT = 4;   // Sent a function command to locomotive.
069    protected static final int THROTTLEMOMSTATSENT = 8;  // Sent Momentary Status request for F0-F12
070    protected static final int THROTTLEHIGHSTATSENT = 16;  // Sent Status request for F13-F28
071    protected static final int THROTTLEHIGHMOMSTATSENT = 32;  // Sent Momentary Status request for F13-F28
072
073    // Written by the receive thread in message(), by the GUI thread in
074    // queueMessage() and read by the watchdog timer thread, so it has to be
075    // volatile for the watchdog to observe the current state rather than a
076    // stale one.
077    protected volatile int requestState = THROTTLEIDLE;
078
079    protected int address;
080
081    // Watchdog for the reply to the message currently handed to the traffic
082    // controller.  Null when no message is outstanding.
083    @GuardedBy("this")
084    private java.util.TimerTask watchdogTask;
085
086    @GuardedBy("this")
087    private int watchdogInterval = THROTTLE_WATCHDOG_INTERVAL;
088
089    // When the message the watchdog is guarding was handed to the traffic
090    // controller, so that a re-check can tell how long the wait has lasted.
091    @GuardedBy("this")
092    private long watchdogArmedAt;
093
094    // Cleared by throttleDispose().  A disposed throttle never arms its
095    // watchdog again, not even from a timer which was already running when it
096    // was disposed of.  Read from the timer thread, hence volatile.
097    private volatile boolean watchdogEnabled = true;
098
099    // Set once the watchdog has reported this throttle stuck, cleared by any
100    // reply.  A command station which stopped answering altogether is then
101    // reported once instead of every time the watchdog restarts the queue.
102    @GuardedBy("this")
103    private boolean watchdogReported;
104
105    // Get the number of valid functions from the software version number.
106    // Declared private static so it can be called as an argument to super(..)
107    private static int numberOfFuns(XNetTrafficController controller) {
108        int version = (int) controller.getCommandStation().getCommandStationSoftwareVersionBCD();
109        if (version < 0x40) return 29;  // 0 - 28
110        return 69;                      // 0 - 68
111    }
112    /**
113     * Constructor
114     * @param memo system connection.
115     * @param controller system connection traffic controller.
116     */
117    public XNetThrottle(XNetSystemConnectionMemo memo, XNetTrafficController controller) {
118        super(memo, numberOfFuns(controller));
119        tc = controller;
120        requestList = new LinkedBlockingQueue<>();
121
122        log.info("Throttle created with no address and version {}", controller.getCommandStation().getCommandStationSoftwareVersionBCD());
123    }
124
125    /**
126     * Constructor.
127     * @param memo system connection.
128     * @param address loco address.
129     * @param controller system connection traffic controller.
130     */
131    public XNetThrottle(XNetSystemConnectionMemo memo, LocoAddress address, XNetTrafficController controller) {
132        super(memo, numberOfFuns(controller));
133        this.tc = controller;
134        this.setDccAddress(address.getNumber());
135        this.speedStepMode = jmri.SpeedStepMode.NMRA_DCC_128;
136        setIsAvailable(false);
137
138        requestList = new LinkedBlockingQueue<>();
139        sendStatusInformationRequest();
140        log.info("Throttle created for address {} with version {}",
141            address, controller.getCommandStation().getCommandStationSoftwareVersionBCD());
142    }
143
144    /*
145     * Set the traffic controller used with this throttle.
146     */
147    public synchronized void setXNetTrafficController(XNetTrafficController controller) {
148        tc = controller;
149    }
150
151    protected synchronized boolean csVersionSupportFn13to28() {
152        if (tc.getCommandStation().getCommandStationSoftwareVersionBCD() < 0x36) {
153            log.info("Functions F13-F28 unavailable in CS software version {}",
154                    tc.getCommandStation().getCommandStationSoftwareVersion());
155            return false;
156        }
157        return true;
158    }
159
160    protected synchronized boolean csVersionSupportFn29to68() {
161        if (tc.getCommandStation().getCommandStationSoftwareVersionBCD() < 0x40) {
162            log.info("Functions F29-68 unavailable in CS software version {}",
163                    tc.getCommandStation().getCommandStationSoftwareVersion());
164            return false;
165        }
166        return true;
167    }
168
169    /**
170     * Send the XpressNet message to set the state of locomotive direction and
171     * functions F0, F1, F2, F3, F4.
172     */
173    @Override
174    protected void sendFunctionGroup1() {
175        XNetMessage msg = XNetMessage.getFunctionGroup1OpsMsg(this.getDccAddress(),
176                getFunction(0), getFunction(1), getFunction(2), getFunction(3), getFunction(4));
177        // now, queue the message for sending to the command station
178        queueMessage(msg, THROTTLEFUNCSENT);
179    }
180
181    /**
182     * Send the XpressNet message to set the state of functions F5, F6, F7, F8.
183     */
184    @Override
185    protected void sendFunctionGroup2() {
186        XNetMessage msg = XNetMessage.getFunctionGroup2OpsMsg(this.getDccAddress(),
187                getFunction(5), getFunction(6), getFunction(7), getFunction(8));
188        // now, queue the message for sending to the command station
189        queueMessage(msg, THROTTLEFUNCSENT);
190    }
191
192    /**
193     * Send the XpressNet message to set the state of functions F9, F10, F11,
194     * F12.
195     */
196    @Override
197    protected void sendFunctionGroup3() {
198        XNetMessage msg = XNetMessage.getFunctionGroup3OpsMsg(this.getDccAddress(),
199                getFunction(9), getFunction(10), getFunction(11), getFunction(12));
200        // now, queue the message for sending to the command station
201        queueMessage(msg, THROTTLEFUNCSENT);
202    }
203
204    /**
205     * Send the XpressNet message to set the state of functions F13, F14, F15,
206     * F16, F17, F18, F19, F20.
207     */
208    @Override
209    protected void sendFunctionGroup4() {
210        if (csVersionSupportFn13to28()) {
211            XNetMessage msg = XNetMessage.getFunctionGroup4OpsMsg(this.getDccAddress(),
212                    getFunction(13), getFunction(14), getFunction(15), getFunction(16),
213                    getFunction(17), getFunction(18), getFunction(19), getFunction(20));
214            // now, queue the message for sending to the command station
215            queueMessage(msg, THROTTLEFUNCSENT);
216        }
217    }
218
219    /**
220     * Send the XpressNet message to set the state of functions F21, F22, F23,
221     * F24, F25, F26, F27, F28.
222     */
223    @Override
224    protected void sendFunctionGroup5() {
225        if (csVersionSupportFn13to28()) {
226            XNetMessage msg = XNetMessage.getFunctionGroup5OpsMsg(this.getDccAddress(),
227                    getFunction(21), getFunction(22), getFunction(23), getFunction(24),
228                    getFunction(25), getFunction(26), getFunction(27), getFunction(28));
229            // now, queue the message for sending to the command station
230            queueMessage(msg, THROTTLEFUNCSENT);
231        }
232    }
233
234    /**
235     * Send the XpressNet message to set the state of functions F29-36
236     */
237    @Override
238    protected void sendFunctionGroup6() {
239        if (csVersionSupportFn29to68()) {
240            int i = 29;
241            XNetMessage msg = XNetMessage.getFunctionGroup6OpsMsg(this.getDccAddress(),
242                    getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3),
243                    getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7));
244            // now, queue the message for sending to the command station
245            queueMessage(msg, THROTTLEFUNCSENT);
246        }
247    }
248
249    /**
250     * Send the XpressNet message to set the state of functions F37-44
251     */
252    @Override
253    protected void sendFunctionGroup7() {
254        if (csVersionSupportFn29to68()) {
255            int i = 37;
256            XNetMessage msg = XNetMessage.getFunctionGroup7OpsMsg(this.getDccAddress(),
257                    getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3),
258                    getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7));
259            // now, queue the message for sending to the command station
260            queueMessage(msg, THROTTLEFUNCSENT);
261        }
262    }
263
264    /**
265     * Send the XpressNet message to set the state of functions F45-52
266     */
267    @Override
268    protected void sendFunctionGroup8() {
269        if (csVersionSupportFn29to68()) {
270            int i = 45;
271            XNetMessage msg = XNetMessage.getFunctionGroup8OpsMsg(this.getDccAddress(),
272                    getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3),
273                    getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7));
274            // now, queue the message for sending to the command station
275            queueMessage(msg, THROTTLEFUNCSENT);
276        }
277    }
278
279    /**
280     * Send the XpressNet message to set the state of functions F53-60
281     */
282    @Override
283    protected void sendFunctionGroup9() {
284        if (csVersionSupportFn29to68()) {
285            int i = 53;
286            XNetMessage msg = XNetMessage.getFunctionGroup9OpsMsg(this.getDccAddress(),
287                    getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3),
288                    getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7));
289            // now, queue the message for sending to the command station
290            queueMessage(msg, THROTTLEFUNCSENT);
291        }
292    }
293
294    /**
295     * Send the XpressNet message to set the state of functions F61-68
296     */
297    @Override
298    protected void sendFunctionGroup10() {
299        if (csVersionSupportFn29to68()) {
300            int i = 61;
301            XNetMessage msg = XNetMessage.getFunctionGroup10OpsMsg(this.getDccAddress(),
302                    getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3),
303                    getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7));
304            // now, queue the message for sending to the command station
305            queueMessage(msg, THROTTLEFUNCSENT);
306        }
307    }
308
309    /**
310     * Send the XpressNet message to set the Momentary state of locomotive
311     * functions F0, F1, F2, F3, F4.
312     */
313    @Override
314    protected void sendMomentaryFunctionGroup1() {
315        XNetMessage msg = XNetMessage.getFunctionGroup1SetMomMsg(this.getDccAddress(),
316           getFunctionMomentary(0), getFunctionMomentary(1), getFunctionMomentary(2),
317           getFunctionMomentary(3), getFunctionMomentary(4));
318        // now, queue the message for sending to the command station
319        queueMessage(msg, THROTTLEFUNCSENT);
320    }
321
322    /**
323     * Send the XpressNet message to set the momentary state of functions F5,
324     * F6, F7, F8.
325     */
326    @Override
327    protected void sendMomentaryFunctionGroup2() {
328        XNetMessage msg = XNetMessage.getFunctionGroup2SetMomMsg(this.getDccAddress(),
329            getFunctionMomentary(5), getFunctionMomentary(6),
330            getFunctionMomentary(7), getFunctionMomentary(8));
331        // now, queue the message for sending to the command station
332        queueMessage(msg, THROTTLEFUNCSENT);
333    }
334
335    /**
336     * Send the XpressNet message to set the momentary state of functions F9,
337     * F10, F11, F12.
338     */
339    @Override
340    protected void sendMomentaryFunctionGroup3() {
341        XNetMessage msg = XNetMessage.getFunctionGroup3SetMomMsg(this.getDccAddress(),
342            getFunctionMomentary(9), getFunctionMomentary(10),
343           getFunctionMomentary(11), getFunctionMomentary(12));
344        // now, queue the message for sending to the command station
345        queueMessage(msg, THROTTLEFUNCSENT);
346    }
347
348    /**
349     * Send the XpressNet message to set the momentary state of functions
350     * F13, F14, F15, F16, F17, F18, F19, F20.
351     */
352    @Override
353    protected void sendMomentaryFunctionGroup4() {
354        if (csVersionSupportFn13to28()) {
355            XNetMessage msg = XNetMessage.getFunctionGroup4SetMomMsg(this.getDccAddress(),
356               getFunctionMomentary(13), getFunctionMomentary(14),
357               getFunctionMomentary(15), getFunctionMomentary(16),
358               getFunctionMomentary(17), getFunctionMomentary(18),
359               getFunctionMomentary(19), getFunctionMomentary(20));
360            // now, queue the message for sending to the command station
361            queueMessage(msg, THROTTLEFUNCSENT);
362        }
363    }
364
365    /**
366     * Send the XpressNet message to set the momentary state of functions F21,
367     * F22, F23, F24, F25, F26, F27, F28.
368     */
369    @Override
370    protected void sendMomentaryFunctionGroup5() {
371        if (csVersionSupportFn13to28()) {
372            XNetMessage msg = XNetMessage.getFunctionGroup5SetMomMsg(this.getDccAddress(),
373                getFunctionMomentary(21), getFunctionMomentary(22), getFunctionMomentary(23),
374                getFunctionMomentary(24), getFunctionMomentary(25), getFunctionMomentary(26),
375                getFunctionMomentary(27), getFunctionMomentary(28));
376            // now, queue the message for sending to the command station
377            queueMessage(msg, THROTTLEFUNCSENT);
378        }
379    }
380
381    /**
382     * Send the XpressNet message to set the momentary state of functions F29-36
383     */
384    @Override
385    protected void sendMomentaryFunctionGroup6() {
386        if (csVersionSupportFn29to68()) {
387            int i = 29;
388            XNetMessage msg = XNetMessage.getFunctionGroup6SetMomMsg(this.getDccAddress(),
389                getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2),
390                getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5),
391                getFunctionMomentary(i+6), getFunctionMomentary(i+7));
392            // now, queue the message for sending to the command station
393            queueMessage(msg, THROTTLEFUNCSENT);
394        }
395    }
396
397    /**
398     * Send the XpressNet message to set the momentary state of functions F37-44
399     */
400    @Override
401    protected void sendMomentaryFunctionGroup7() {
402        if (csVersionSupportFn29to68()) {
403            int i = 37;
404            XNetMessage msg = XNetMessage.getFunctionGroup7SetMomMsg(this.getDccAddress(),
405                getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2),
406                getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5),
407                getFunctionMomentary(i+6), getFunctionMomentary(i+7));
408            // now, queue the message for sending to the command station
409            queueMessage(msg, THROTTLEFUNCSENT);
410        }
411    }
412
413    /**
414     * Send the XpressNet message to set the momentary state of functions F45-52
415     */
416    @Override
417    protected void sendMomentaryFunctionGroup8() {
418        if (csVersionSupportFn29to68()) {
419            int i = 45;
420            XNetMessage msg = XNetMessage.getFunctionGroup8SetMomMsg(this.getDccAddress(),
421                getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2),
422                getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5),
423                getFunctionMomentary(i+6), getFunctionMomentary(i+7));
424            // now, queue the message for sending to the command station
425            queueMessage(msg, THROTTLEFUNCSENT);
426        }
427    }
428
429    /**
430     * Send the XpressNet message to set the momentary state of functions F53-60
431     */
432    @Override
433    protected void sendMomentaryFunctionGroup9() {
434        if (csVersionSupportFn29to68()) {
435            int i = 53;
436            XNetMessage msg = XNetMessage.getFunctionGroup9SetMomMsg(this.getDccAddress(),
437                getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2),
438                getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5),
439                getFunctionMomentary(i+6), getFunctionMomentary(i+7));
440            // now, queue the message for sending to the command station
441            queueMessage(msg, THROTTLEFUNCSENT);
442        }
443    }
444
445    /**
446     * Send the XpressNet message to set the momentary state of functions F61-68
447     */
448    @Override
449    protected void sendMomentaryFunctionGroup10() {
450        if (csVersionSupportFn29to68()) {
451            int i = 61;
452            XNetMessage msg = XNetMessage.getFunctionGroup10SetMomMsg(this.getDccAddress(),
453                getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2),
454                getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5),
455                getFunctionMomentary(i+6), getFunctionMomentary(i+7));
456            // now, queue the message for sending to the command station
457            queueMessage(msg, THROTTLEFUNCSENT);
458        }
459    }
460
461    /**
462     * Notify listeners and send the new speed to the command station.
463     */
464    @Override
465    public synchronized void setSpeedSetting(float speed) {
466        log.debug("set Speed to: {} Current step mode is: {}", speed, this.speedStepMode);
467        super.setSpeedSetting(speed);
468        if (speed < 0) {
469            /* we're sending an emergency stop to this locomotive only */
470            sendEmergencyStop();
471        } else {
472            if (speed > 1) {
473                speed = (float) 1.0;
474            }
475            /* we're sending a speed to the locomotive */
476            XNetMessage msg = XNetMessage.getSpeedAndDirectionMsg(getDccAddress(),
477                    this.speedStepMode,
478                    speed,
479                    this.isForward);
480            // now, queue the message for sending to the command station
481            queueMessage(msg, THROTTLESPEEDSENT);
482        }
483    }
484
485    /**
486     * Since XpressNet has a seperate Opcode for emergency stop, we're setting
487     * this up as a seperate protected function.
488     */
489    protected void sendEmergencyStop() {
490        /* Emergency stop sent */
491        XNetMessage msg = XNetMessage.getAddressedEmergencyStop(this.getDccAddress());
492        // now, queue the message for sending to the command station
493        queueMessage(msg, THROTTLESPEEDSENT);
494    }
495
496    /**
497     * When we set the direction, we're going to set the speed to zero as well.
498     */
499    @Override
500    public void setIsForward(boolean forward) {
501        super.setIsForward(forward);
502        synchronized(this) {
503            setSpeedSetting(this.speedSetting);
504        }
505    }
506
507    /**
508     * Set the speed step value and the related speedIncrement value.
509     *
510     * @param Mode the current speed step mode - default should be 128 speed
511     *             step mode in most cases
512     */
513    @Override
514    public void setSpeedStepMode(SpeedStepMode Mode) {
515        super.setSpeedStepMode(Mode);
516        // On a Lenz system, we need to send the speed to make sure the
517        // command station knows about the change.
518        synchronized(this) {
519            setSpeedSetting(this.speedSetting);
520        }
521    }
522
523    /**
524     * Dispose when finished with this object. After this, further usage of this
525     * Throttle object will result in a JmriException.
526     * <p>
527     * This is quite problematic, because a using object doesn't know when it's
528     * the last user.
529     */
530    @Override
531    public void throttleDispose() {
532        active = false;
533        watchdogEnabled = false;
534        stopStatusTimer();
535        cancelWatchdog();
536        finishRecord();
537    }
538
539    public int setDccAddress(int newaddress) {
540        address = newaddress;
541        return address;
542    }
543
544    public int getDccAddress() {
545        return address;
546    }
547
548    protected int getDccAddressHigh() {
549        return LenzCommandStation.getDCCAddressHigh(this.address);
550    }
551
552    protected int getDccAddressLow() {
553        return LenzCommandStation.getDCCAddressLow(this.address);
554    }
555
556    /**
557     * Send a request to get the speed, direction and function status from the
558     * command station.
559     */
560    protected synchronized void sendStatusInformationRequest() {
561        /* Send the request for status */
562        XNetMessage msg = XNetMessage.getLocomotiveInfoRequestMsg(this.address);
563        msg.setRetries(1); // Since we repeat this ourselves, don't ask the
564        // traffic controller to do this for us.
565        // now, we queue the message for sending to the command station
566        queueMessage(msg, THROTTLESTATSENT);
567    }
568
569    /**
570     * Send a request to get the status of functions from the command station.
571     */
572    protected synchronized void sendFunctionStatusInformationRequest() {
573        log.debug("Throttle {} sending request for function momentary status.", address);
574        /* Send the request for Function status */
575        XNetMessage msg = XNetMessage.getLocomotiveFunctionStatusMsg(this.address);
576        queueMessage(msg, (THROTTLEMOMSTATSENT | THROTTLESTATSENT));
577    }
578
579    /**
580     * Send a request to get the on/off status of functions 13-28 from the
581     * command station.
582     */
583    protected synchronized void sendFunctionHighInformationRequest() {
584        if (csVersionSupportFn13to28()) {
585            log.debug("Throttle {} sending request for high function momentary status.", address);
586            /* Send the request for Function status */
587            XNetMessage msg = XNetMessage.getLocomotiveFunctionHighOnStatusMsg(this.address);
588            // now, we send the message to the command station
589            queueMessage(msg, THROTTLEHIGHSTATSENT | THROTTLESTATSENT);
590        }
591    }
592
593    /**
594     * Send a request to get the status of functions from the command station.
595     */
596    protected synchronized void sendFunctionHighMomentaryStatusRequest() {
597        if (csVersionSupportFn13to28()) {
598            log.debug("Throttle {} sending request for function momentary status.", address);
599            /* Send the request for Function status */
600            XNetMessage msg = XNetMessage.getLocomotiveFunctionHighMomStatusMsg(this.address);
601            // now, we send the message to the command station
602            queueMessage(msg, (THROTTLEHIGHMOMSTATSENT | THROTTLESTATSENT));
603        }
604    }
605
606    // Handle incoming messages for This throttle.
607    @Override
608    public void message(XNetReply l) {
609        // First, we want to see if this throttle is waiting for a message
610        //or not.
611        log.debug("Throttle {} - received message {}", getDccAddress(), l);
612        synchronized (this) {
613            // the command station is answering again, a later silence is worth
614            // reporting anew.
615            watchdogReported = false;
616        }
617        if (requestState == THROTTLEIDLE) {
618            log.trace("Current throttle status is THROTTLEIDLE");
619            // We haven't sent anything, but we might be told someone else
620            // has taken over this address
621            if (l.getElement(0) == XNetConstants.LOCO_INFO_RESPONSE) {
622                log.trace("Throttle - message is LOCO_INFO_RESPONSE ");
623                if (l.getElement(1) == XNetConstants.LOCO_NOT_AVAILABLE
624                        && getDccAddressHigh() == l.getElement(2)
625                        && getDccAddressLow() == l.getElement(3)) {
626                    locoInUse();
627                }
628            }
629        } else if ((requestState & THROTTLESPEEDSENT) == THROTTLESPEEDSENT
630                || (requestState & THROTTLEFUNCSENT) == THROTTLEFUNCSENT) {
631            log.trace("Current throttle status is THROTTLESPEEDSENT");
632            // For a Throttle Command, we're just looking for a return
633            // acknowledgment, Either a Success or Failure message.
634            if (l.isOkMessage()) {
635                log.trace("Last Command processed successfully.");
636                // Since we received an "ok",  we want to make sure
637                // "isAvailable reflects we are in control
638                setIsAvailable(true);
639                requestState = THROTTLEIDLE;
640                sendQueuedMessage();
641            } else if (l.isRetransmittableErrorMsg()) {
642                /* this is a communications error */
643                log.trace("Communications error occurred - message received was: {}", l);
644            } else if (l.isUnsupportedError()) {
645                /* The Command Station does not support this command */
646                log.error("Unsupported Command Sent to command station");
647                requestState = THROTTLEIDLE;
648                sendQueuedMessage();
649            } else {
650                /* this is an unknown error */
651                requestState = THROTTLEIDLE;
652                sendQueuedMessage();
653                log.trace("Received unhandled response: {}", l);
654            }
655        } else if ((requestState & THROTTLESTATSENT) == THROTTLESTATSENT) {
656            log.trace("Current throttle status is THROTTLESTATSENT");
657            // This throttle has requested status information, so we need
658            // to process those messages.
659            if (l.getElement(0) == XNetConstants.LOCO_INFO_NORMAL_UNIT) {
660                if (l.getElement(1) == XNetConstants.LOCO_FUNCTION_STATUS_HIGH_MOM) {
661                    /* handle information response about F13-F28 Momentary
662                     Status*/
663                    log.trace("Throttle - message is LOCO_FUNCTION_STATUS_HIGH_MOM");
664                    int b3 = l.getElement(2);
665                    int b4 = l.getElement(3);
666                    parseFunctionHighMomentaryInformation(b3, b4);
667                    //We've processed this request, so set the status to Idle.
668                    requestState = THROTTLEIDLE;
669                    sendQueuedMessage();
670                } else {
671                    log.trace("Throttle - message is LOCO_INFO_NORMAL_UNIT");
672                    /* there is no address sent with this information */
673                    int b1 = l.getElement(1);
674                    int b2 = l.getElement(2);
675                    int b3 = l.getElement(3);
676                    int b4 = l.getElement(4);
677
678                    parseSpeedAndAvailability(b1);
679                    parseSpeedAndDirection(b2);
680                    parseFunctionInformation(b3, b4);
681
682                    //We've processed this request, so set the status to Idle.
683                    requestState = THROTTLEIDLE;
684                    sendQueuedMessage();
685                    // And then we want to request the Function Momentary Status
686                    sendFunctionStatusInformationRequest();
687                }
688            } else if (l.getElement(0) == XNetConstants.LOCO_INFO_MUED_UNIT) {
689                log.trace("Throttle - message is LOCO_INFO_MUED_UNIT ");
690                /* there is no address sent with this information */
691                int b1 = l.getElement(1);
692                int b2 = l.getElement(2);
693                int b3 = l.getElement(3);
694                int b4 = l.getElement(4);
695                // Element 5 is the consist address, it can only be in the
696                // range 1-99
697                int b5 = l.getElement(5);
698
699                log.trace("Locomotive {} inconsist {} ", getDccAddress(), b5);
700
701                parseSpeedAndAvailability(b1);
702                parseSpeedAndDirection(b2);
703                parseFunctionInformation(b3, b4);
704
705                // We've processed this request, so set the status to Idle.
706                requestState = THROTTLEIDLE;
707                sendQueuedMessage();
708                // And then we want to request the Function Momentary Status
709                sendFunctionStatusInformationRequest();
710            } else if (l.getElement(0) == XNetConstants.LOCO_INFO_DH_UNIT) {
711                log.trace("Throttle - message is LOCO_INFO_DH_UNIT ");
712                /* there is no address sent with this information */
713                int b1 = l.getElement(1);
714                int b2 = l.getElement(2);
715                int b3 = l.getElement(3);
716                int b4 = l.getElement(4);
717
718                // elements 5 and 6 contain the address of the other unit
719                // in the DH
720                int b5 = l.getElement(5);
721                int b6 = l.getElement(6);
722
723                if (log.isDebugEnabled()) {
724                    int address2 = (b5 == 0x00) ? b6 : ((b5 * 256) & 0xFF00) + (b6 & 0xFF) - 0xC000;
725                    log.trace("Locomotive {} in Double Header with {}",
726                            getDccAddress(), address2);
727                }
728
729                parseSpeedAndAvailability(b1);
730                parseSpeedAndDirection(b2);
731                parseFunctionInformation(b3, b4);
732
733                // We've processed this request, so set the status to Idle.
734                requestState = THROTTLEIDLE;
735                sendQueuedMessage();
736                // And then we want to request the Function Momentary Status
737                sendFunctionStatusInformationRequest();
738            } else if (l.getElement(0) == XNetConstants.LOCO_INFO_MU_ADDRESS) {
739                log.trace("Throttle - message is LOCO_INFO_MU ADDRESS ");
740                /* there is no address sent with this information */
741                int b1 = l.getElement(1);
742                int b2 = l.getElement(2);
743
744                parseSpeedAndAvailability(b1);
745                parseSpeedAndDirection(b2);
746
747                //We've processed this request, so set the status to Idle.
748                requestState = THROTTLEIDLE;
749                sendQueuedMessage();
750                // And then we want to request the Function Momentary Status
751                sendFunctionStatusInformationRequest();
752            } else if (l.getElement(0) == XNetConstants.LOCO_INFO_RESPONSE) {
753                log.trace("Throttle - message is LOCO_INFO_RESPONSE ");
754                if (l.getElement(1) == XNetConstants.LOCO_NOT_AVAILABLE) {
755                    /* the address is in bytes 3 and 4*/
756                    if (getDccAddressHigh() == l.getElement(2) && getDccAddressLow() == l.getElement(3)) {
757                        locoInUse();
758                    }
759                    // We've processed this request, so set the status to Idle.
760                    requestState = THROTTLEIDLE;
761                    sendQueuedMessage();
762                } else if (l.getElement(1) == XNetConstants.LOCO_FUNCTION_STATUS) {
763                    /* Bytes 3 and 4 contain function momentary status information */
764                    int b3 = l.getElement(2);
765                    int b4 = l.getElement(3);
766                    parseFunctionMomentaryInformation(b3, b4);
767                    // We've processed this request, so set the status to Idle.
768                    requestState = THROTTLEIDLE;
769                    sendQueuedMessage();
770                    // And then we want to request the Function Status for F13-F28
771                    sendFunctionHighInformationRequest();
772
773                } else if (l.getElement(1) == XNetConstants.LOCO_FUNCTION_STATUS_HIGH) {
774                    /* Bytes 3 and 4 contain function status information for F13-F28*/
775                    int b3 = l.getElement(2);
776                    int b4 = l.getElement(3);
777                    parseFunctionHighInformation(b3, b4);
778                    //We've processed this request, so set the status to Idle.
779                    requestState = THROTTLEIDLE;
780                    sendQueuedMessage();
781                    // And then we want to request the Function Momentary Status
782                    // for functions F13-F28
783                    sendFunctionHighMomentaryStatusRequest();
784                } else {
785                    // The reply to the outstanding request has been consumed,
786                    // so no timeout will ever fire for it.  Without this
787                    // branch an unrecognized subtype leaves requestState stuck
788                    // (never returns to THROTTLEIDLE) and every command queued
789                    // afterwards is silently held back forever.
790                    log.debug("Unhandled LOCO_INFO_RESPONSE subtype {} while in THROTTLESTATSENT, resuming queue",
791                            l.getElement(1));
792                    requestState = THROTTLEIDLE;
793                    sendQueuedMessage();
794                }
795            } else if (l.isRetransmittableErrorMsg()) {
796                /* this is a communications error */
797                log.trace("Communications error occurred - message received was: {}", l);
798            } else if (l.isUnsupportedError()) {
799                /* The Command Station does not support this command */
800                log.error("Unsupported Command Sent to command station");
801                if ((requestState & THROTTLEMOMSTATSENT) == THROTTLEMOMSTATSENT) {
802                    // if momentary is not supported, try requesting the
803                    // high function state.
804                    requestState = THROTTLEIDLE;
805                    sendFunctionHighInformationRequest();
806                } else {
807                    requestState = THROTTLEIDLE;
808                    sendQueuedMessage();
809                }
810            } else {
811                /* this is an unknown error */
812                requestState = THROTTLEIDLE;
813                sendQueuedMessage();
814                log.trace("Received unhandled response: {}", l);
815            }
816        }
817    }
818
819    private void locoInUse() {
820        if (isAvailable) {
821            //Set the Is available flag to Throttle.False
822            log.info("Loco {} In use by another device", getDccAddress());
823            setIsAvailable(false);
824        }
825    }
826
827    /**
828     * {@inheritDoc}
829     */
830    @Override
831    public void message(XNetMessage l) {
832        // no need to handle outgoing messages.
833    }
834
835    /**
836     * {@inheritDoc}
837     */
838    @Override
839    public void notifyTimeout(XNetMessage msg) {
840        log.debug("Notified of timeout on message {} , {} retries available.",
841                msg, msg.getRetries());
842        if (msg.getRetries() > 0) {
843            // If the message still has retries available, send it back to
844            // the traffic controller.
845            synchronized (this) {
846                tc.sendXNetMessage(msg, this);
847                // the reply is expected again, restart the watchdog from here
848                armWatchdog();
849            }
850        } else {
851            // Try to send the next queued message,  if one is available.
852            sendQueuedMessage();
853        }
854    }
855
856    // Status Information processing routines
857    // Used for return values from Status requests.
858    /**
859     * Get SpeedStep and availability information.
860     * @param b1 1st byte of message to examine
861     */
862    protected void parseSpeedAndAvailability(int b1) {
863        /* the first data bite indicates the speed step mode, and
864         if the locomotive is being controlled by another throttle */
865
866        if ((b1 & 0x08) == 0x08 && this.isAvailable) {
867            locoInUse();
868        } else if ((b1 & 0x08) == 0x00 && !this.isAvailable) {
869            log.trace("Loco Is Available");
870            setIsAvailable(true);
871        }
872        if ((b1 & 0x01) == 0x01) {
873            log.trace("Speed Step setting 27");
874            notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_27);
875        } else if ((b1 & 0x02) == 0x02) {
876            log.trace("Speed Step setting 28");
877            notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_28);
878        } else if ((b1 & 0x04) == 0x04) {
879            log.trace("Speed Step setting 128");
880            notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_128);
881        } else {
882            log.trace("Speed Step setting 14");
883            notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_14);
884        }
885    }
886
887    protected void notifyNewSpeedStepMode(SpeedStepMode mode) {
888        if (this.speedStepMode != mode) {
889            firePropertyChange(SPEEDSTEPS,
890                    this.speedStepMode,
891                    this.speedStepMode = mode);
892        }
893    }
894
895    /**
896     * Get Speed and Direction information.
897     * @param b2 2nd byte of message to examine
898     */
899    protected void parseSpeedAndDirection(int b2) {
900        /* the second byte indicates the speed and direction setting */
901
902        if ((b2 & 0x80) == 0x80 && !this.isForward) {
903            notifyNewDirection(true);
904        } else if ((b2 & 0x80) == 0x00 && this.isForward) {
905            notifyNewDirection(false);
906        }
907
908        if (this.speedStepMode == SpeedStepMode.NMRA_DCC_128) {
909            // We're in 128 speed step mode
910            int speedVal = b2 & 0x7f;
911            // The first speed step used is actually at 2 for 128
912            // speed step mode.
913            if (speedVal >= 1) {
914                speedVal -= 1;
915            }
916            if (java.lang.Math.abs(
917                    this.getSpeedSetting() - ((float) speedVal / (float) 126)) >= 0.0079) {
918                synchronized(this) {
919                    firePropertyChange(SPEEDSETTING, this.speedSetting,
920                            this.speedSetting = (float) speedVal / (float) 126);
921                }
922            }
923        } else if (this.speedStepMode == SpeedStepMode.NMRA_DCC_28) {
924            // We're in 28 speed step mode
925            // We have to re-arrange the bits, since bit 4 is the LSB,
926            // but other bits are in order from 0-3
927            int speedVal = ((b2 & 0x0F) << 1)
928                    + ((b2 & 0x10) >> 4);
929            // The first speed step used is actually at 4 for 28
930            // speed step mode.
931            if (speedVal >= 3) {
932                speedVal -= 3;
933            } else {
934                speedVal = 0;
935            }
936            if (java.lang.Math.abs(
937                    this.getSpeedSetting() - ((float) speedVal / (float) 28)) >= 0.035) {
938                synchronized(this) {
939                    firePropertyChange(SPEEDSETTING, this.speedSetting,
940                            this.speedSetting = (float) speedVal / (float) 28);
941                }
942            }
943        } else if (this.speedStepMode == SpeedStepMode.NMRA_DCC_27) {
944            // We're in 27 speed step mode
945            // We have to re-arrange the bits, since bit 4 is the LSB,
946            // but other bits are in order from 0-3
947            int speedVal = ((b2 & 0x0F) << 1)
948                    + ((b2 & 0x10) >> 4);
949            // The first speed step used is actually at 4 for 27
950            // speed step mode.
951            if (speedVal >= 3) {
952                speedVal -= 3;
953            } else {
954                speedVal = 0;
955            }
956            if (java.lang.Math.abs(
957                    this.getSpeedSetting() - ((float) speedVal / (float) 27)) >= 0.037) {
958                synchronized(this) {
959                    firePropertyChange(SPEEDSETTING, this.speedSetting,
960                            this.speedSetting = (float) speedVal / (float) 27);
961                }
962            }
963        } else {
964            // Assume we're in 14 speed step mode.
965            int speedVal = (b2 & 0x0F);
966            if (speedVal >= 1) {
967                speedVal -= 1;
968            }
969            if (java.lang.Math.abs(
970                    this.getSpeedSetting() - ((float) speedVal / (float) 14)) >= 0.071) {
971                synchronized(this) {
972                    firePropertyChange(SPEEDSETTING, this.speedSetting,
973                            this.speedSetting = (float) speedVal / (float) 14);
974                }
975            }
976        }
977    }
978
979    protected void notifyNewDirection(boolean forward) {
980        firePropertyChange(ISFORWARD, this.isForward, this.isForward = forward);
981        log.trace("Throttle - Changed direction to {} Locomotive: {}", forward ? "forward" : "reverse", getDccAddress());
982    }
983
984    protected void parseFunctionInformation(int b3, int b4) {
985        log.trace("Parsing Function F0-F12 status, function bytes: {} and {}",
986                b3, b4);
987        /* data byte 3 is the status of F0 F4 F3 F2 F1 */
988        updateFunction(0, (b3 & 0x10) == 0x10);
989        updateFunction(1, (b3 & 0x01) == 0x01);
990        updateFunction(2, (b3 & 0x02) == 0x02);
991        updateFunction(3, (b3 & 0x04) == 0x04);
992        updateFunction(4, (b3 & 0x08) == 0x08);
993        /* data byte 4 is the status of F12 F11 F10 F9 F8 F7 F6 F5 */
994        updateFunction(5, (b4 & 0x01) == 0x01);
995        updateFunction(6, (b4 & 0x02) == 0x02);
996        updateFunction(7, (b4 & 0x04) == 0x04);
997        updateFunction(8, (b4 & 0x08) == 0x08);
998        updateFunction(9, (b4 & 0x10) == 0x10);
999        updateFunction(10, (b4 & 0x20) == 0x20);
1000        updateFunction(11, (b4 & 0x40) == 0x40);
1001        updateFunction(12, (b4 & 0x80) == 0x80);
1002    }
1003
1004    protected void parseFunctionHighInformation(int b3, int b4) {
1005        log.trace("Parsing Function F13-F28 status, function bytes: {} and {}",
1006                b3, b4);
1007        /* data byte 3 is the status of F20 F19 F18 F17 F16 F15 F14 F13 */
1008        updateFunction(13, (b3 & 0x01) == 0x01);
1009        updateFunction(14, (b3 & 0x02) == 0x02);
1010        updateFunction(15, (b3 & 0x04) == 0x04);
1011        updateFunction(16, (b3 & 0x08) == 0x08);
1012        updateFunction(17, (b3 & 0x10) == 0x10);
1013        updateFunction(18, (b3 & 0x20) == 0x20);
1014        updateFunction(19, (b3 & 0x40) == 0x40);
1015        updateFunction(20, (b3 & 0x80) == 0x80);
1016        /* data byte 4 is the status of F28 F27 F26 F25 F24 F23 F22 F21 */
1017        updateFunction(21, (b4 & 0x01) == 0x01);
1018        updateFunction(22, (b4 & 0x02) == 0x02);
1019        updateFunction(23, (b4 & 0x04) == 0x04);
1020        updateFunction(24, (b4 & 0x08) == 0x08);
1021        updateFunction(25, (b4 & 0x10) == 0x10);
1022        updateFunction(26, (b4 & 0x20) == 0x20);
1023        updateFunction(27, (b4 & 0x40) == 0x40);
1024        updateFunction(28, (b4 & 0x80) == 0x80);
1025
1026    }
1027
1028    protected void parseFunctionMomentaryInformation(int b3, int b4) {
1029        log.trace("Parsing Function Momentary status, function bytes: {} and {}",
1030                b3, b4);
1031        /* data byte 3 is the momentary status of F0 F4 F3 F2 F1 */
1032        checkForFunctionMomentaryValueChange(0, b3, 0x10, getFunctionMomentary(0));
1033        checkForFunctionMomentaryValueChange(1, b3, 0x01, getFunctionMomentary(1));
1034        checkForFunctionMomentaryValueChange(2, b3, 0x02, getFunctionMomentary(2));
1035        checkForFunctionMomentaryValueChange(3, b3, 0x04, getFunctionMomentary(3));
1036        checkForFunctionMomentaryValueChange(4, b3, 0x08, getFunctionMomentary(4));
1037        /* data byte 4 is the momentary status of F12 F11 F10 F9 F8 F7 F6 F5 */
1038        checkForFunctionMomentaryValueChange(5, b4, 0x01, getFunctionMomentary(5));
1039        checkForFunctionMomentaryValueChange(6, b4, 0x02, getFunctionMomentary(6));
1040        checkForFunctionMomentaryValueChange(7, b4, 0x04, getFunctionMomentary(7));
1041        checkForFunctionMomentaryValueChange(8, b4, 0x08, getFunctionMomentary(8));
1042        checkForFunctionMomentaryValueChange(9, b4, 0x10, getFunctionMomentary(9));
1043        checkForFunctionMomentaryValueChange(10, b4, 0x20, getFunctionMomentary(10));
1044        checkForFunctionMomentaryValueChange(11, b4, 0x40, getFunctionMomentary(11));
1045        checkForFunctionMomentaryValueChange(12, b4, 0x80, getFunctionMomentary(12));
1046    }
1047
1048    protected void parseFunctionHighMomentaryInformation(int b3, int b4) {
1049        log.trace("Parsing Function F13-F28 Momentary status, function bytes: {} and {}",
1050                b3, b4);
1051        /* data byte 3 is the momentary status of F20 F19 F17 F16 F15 F14 F13 */
1052        checkForFunctionMomentaryValueChange(13, b3, 0x01, getFunctionMomentary(13));
1053        checkForFunctionMomentaryValueChange(14, b3, 0x02, getFunctionMomentary(14));
1054        checkForFunctionMomentaryValueChange(15, b3, 0x04, getFunctionMomentary(15));
1055        checkForFunctionMomentaryValueChange(16, b3, 0x08, getFunctionMomentary(16));
1056        checkForFunctionMomentaryValueChange(17, b3, 0x10, getFunctionMomentary(17));
1057        checkForFunctionMomentaryValueChange(18, b3, 0x20, getFunctionMomentary(18));
1058        checkForFunctionMomentaryValueChange(19, b3, 0x40, getFunctionMomentary(19));
1059        checkForFunctionMomentaryValueChange(20, b3, 0x80, getFunctionMomentary(20));
1060        /* data byte 4 is the momentary status of F28 F27 F26 F25 F24 F23 F22 F21 */
1061        checkForFunctionMomentaryValueChange(21, b4, 0x01, getFunctionMomentary(21));
1062        checkForFunctionMomentaryValueChange(22, b4, 0x02, getFunctionMomentary(22));
1063        checkForFunctionMomentaryValueChange(23, b4, 0x04, getFunctionMomentary(23));
1064        checkForFunctionMomentaryValueChange(24, b4, 0x08, getFunctionMomentary(24));
1065        checkForFunctionMomentaryValueChange(25, b4, 0x10, getFunctionMomentary(25));
1066        checkForFunctionMomentaryValueChange(26, b4, 0x20, getFunctionMomentary(26));
1067        checkForFunctionMomentaryValueChange(27, b4, 0x40, getFunctionMomentary(27));
1068        checkForFunctionMomentaryValueChange(28, b4, 0x80, getFunctionMomentary(28));
1069    }
1070
1071    protected void checkForFunctionMomentaryValueChange(int funcNum, int bytevalue, int bitmask, boolean currentValue) {
1072        if ((bytevalue & bitmask) == bitmask && !currentValue) {
1073            updateFunctionMomentary(funcNum, true);
1074        } else if ((bytevalue & bitmask) == 0x00 && currentValue) {
1075            updateFunctionMomentary(funcNum, false);
1076        }
1077    }
1078
1079    /**
1080     * Set the internal isAvailable property.
1081     *
1082     * @param available true if available; false otherwise
1083     */
1084    protected void setIsAvailable(boolean available) {
1085        firePropertyChange("IsAvailable", this.isAvailable, this.isAvailable = available);
1086        /* if we're setting this to true, stop the timer,
1087         otherwise start the timer. */
1088        if (available) {
1089            stopStatusTimer();
1090        } else {
1091            startStatusTimer();
1092        }
1093    }
1094
1095    /**
1096     * Set up the status timer, and start it.
1097     */
1098    protected void startStatusTimer() {
1099        log.debug("Status Timer Started");
1100
1101        if (statusTask != null) {
1102            statusTask.cancel();
1103            statusTask = null;
1104        }
1105        statusTask = new java.util.TimerTask() {
1106            @Override
1107            public void run() {
1108                /* If the timer times out, just send a status
1109                 request message */
1110                sendStatusInformationRequest();
1111            }
1112        };
1113
1114        jmri.util.TimerUtil.schedule(statusTask, statTimeoutValue, statTimeoutValue);
1115    }
1116
1117    /**
1118     * Stop the Status Timer
1119     */
1120    protected void stopStatusTimer() {
1121        log.debug("Status Timer Stopped");
1122        if (statusTask != null) {
1123            try {
1124                statusTask.cancel();
1125            } catch (IllegalStateException ise) {
1126                log.debug("Timer already canceled");
1127            }
1128            statusTask = null;
1129        }
1130    }
1131
1132    /**
1133     * Arm the watchdog for the message just handed to the traffic controller.
1134     * <p>
1135     * Any watchdog already armed is cancelled first, so the delay is always
1136     * counted from the most recent transmission and a legitimate retransmission
1137     * never trips it.
1138     */
1139    protected synchronized void armWatchdog() {
1140        cancelWatchdog();
1141        if (!watchdogEnabled) {
1142            return; // disposed of, nothing is waiting for a reply any more
1143        }
1144        watchdogArmedAt = System.currentTimeMillis();
1145        scheduleWatchdog(watchdogInterval);
1146    }
1147
1148    private synchronized void scheduleWatchdog(long delay) {
1149        java.util.TimerTask task = new java.util.TimerTask() {
1150            @Override
1151            public void run() {
1152                watchdogExpired(this);
1153            }
1154        };
1155        watchdogTask = task;
1156        jmri.util.TimerUtil.schedule(task, delay);
1157    }
1158
1159    /**
1160     * Disarm the watchdog, the throttle is no longer waiting for a reply.
1161     */
1162    protected synchronized void cancelWatchdog() {
1163        if (watchdogTask != null) {
1164            watchdogTask.cancel();
1165            watchdogTask = null;
1166        }
1167    }
1168
1169    /**
1170     * The reply to the outstanding message never arrived. Return to idle and
1171     * restart the queue, otherwise this throttle would stay silent until the
1172     * end of the session.
1173     *
1174     * @param task the expiring task, used to ignore an expiry which a later
1175     *             transmission has already superseded
1176     */
1177    private synchronized void watchdogExpired(java.util.TimerTask task) {
1178        if (task != watchdogTask) {
1179            return; // superseded, the message sent since owns the watchdog
1180        }
1181        watchdogTask = null;
1182        if (!watchdogEnabled || requestState == THROTTLEIDLE) {
1183            return;
1184        }
1185        long waited = System.currentTimeMillis() - watchdogArmedAt;
1186        long maxWait = (long) watchdogInterval * THROTTLE_WATCHDOG_MAX_MULTIPLE;
1187        if (tc.isAtRest()) {
1188            // Nothing is in flight and nothing is queued, so the reply this
1189            // throttle is waiting for is already gone.  Waiting longer cannot
1190            // help: this is the state the whole watchdog exists for.
1191            if (watchdogAlreadyReported()) {
1192                log.debug("Throttle {} - traffic controller at rest with a reply still due after"
1193                        + " {}ms in state {}, restarting the queue", getDccAddress(), waited, requestState);
1194            } else {
1195                log.info("Throttle {} - traffic controller at rest with a reply still due after"
1196                        + " {}ms in state {}, restarting the queue", getDccAddress(), waited, requestState);
1197            }
1198        } else if (waited >= maxWait) {
1199            if (watchdogAlreadyReported()) {
1200                log.debug("Throttle {} - no reply after {}ms in state {}, restarting the queue",
1201                        getDccAddress(), waited, requestState);
1202            } else {
1203                log.warn("Throttle {} - no reply after {}ms in state {}, restarting the queue",
1204                        getDccAddress(), waited, requestState);
1205            }
1206        } else {
1207            // The traffic controller still has work in hand, the reply may yet
1208            // be legitimately late.  Look again later, up to the hard deadline.
1209            scheduleWatchdog(Math.min(watchdogInterval, maxWait - waited));
1210            return;
1211        }
1212        requestState = THROTTLEIDLE;
1213        sendQueuedMessage();
1214    }
1215
1216    /**
1217     * Tell whether this stuck episode has already been reported, and mark it
1218     * reported.
1219     * <p>
1220     * The first expiry is worth a warning: a reply was lost and the queue had
1221     * to be restarted. Until a reply is received again, further expiries only
1222     * repeat that the command station is still not answering, which would
1223     * otherwise put a warning in the log every
1224     * {@value #THROTTLE_WATCHDOG_INTERVAL} milliseconds for as long as the
1225     * throttle exists.
1226     *
1227     * @return true if a warning has already been issued since the last reply
1228     */
1229    @GuardedBy("this")
1230    private boolean watchdogAlreadyReported() {
1231        boolean reported = watchdogReported;
1232        watchdogReported = true;
1233        return reported;
1234    }
1235
1236    /**
1237     * Set how long this throttle waits for a reply, once the traffic controller
1238     * is at rest, before giving up and restarting its queue. While the traffic
1239     * controller is busy the throttle waits up to
1240     * {@value #THROTTLE_WATCHDOG_MAX_MULTIPLE} times as long.
1241     * Takes effect on the next message sent.
1242     *
1243     * @param interval interval in milliseconds
1244     */
1245    public synchronized void setWatchdogInterval(int interval) {
1246        watchdogInterval = interval;
1247    }
1248
1249    @Override
1250    public LocoAddress getLocoAddress() {
1251        return new DccLocoAddress(address, XNetThrottleManager.isLongAddress(address));
1252    }
1253
1254    // A queue to hold outstanding messages
1255    protected LinkedBlockingQueue<RequestMessage> requestList;
1256
1257    /**
1258     * Send message from queue.
1259     */
1260    protected synchronized void sendQueuedMessage() {
1261
1262        RequestMessage msg;
1263        // check to see if the queue has a message in it, and if it does,
1264        // remove the first message
1265        if (!requestList.isEmpty()) {
1266            log.debug("sending message to traffic controller");
1267            // if the queue is not empty, remove the first message
1268            // from the queue, send the message, and set the state machine
1269            // to the required state.
1270            try {
1271                msg = requestList.take();
1272            } catch (java.lang.InterruptedException ie) {
1273                return; // if there was an error, exit.
1274            }
1275            requestState = msg.getState();
1276            tc.sendXNetMessage(msg.getMsg(), this);
1277            if (requestState == THROTTLEIDLE) {
1278                // a message sent without waiting for its reply, e.g. Z21
1279                cancelWatchdog();
1280            } else {
1281                // a reply is expected, guard against never getting one
1282                armWatchdog();
1283            }
1284        } else {
1285            log.debug("message queue empty");
1286            // if the queue is empty, set the state to idle.
1287            requestState = THROTTLEIDLE;
1288            cancelWatchdog();
1289        }
1290    }
1291
1292    /**
1293     * Queue a message.
1294     * @param m message to send
1295     * @param s state
1296     */
1297    protected synchronized void queueMessage(XNetMessage m, int s) {
1298        log.debug("adding message to message queue");
1299        // put the message in the queue
1300        RequestMessage msg = new RequestMessage(m, s);
1301        try {
1302            requestList.put(msg);
1303        } catch (java.lang.InterruptedException ie) {
1304            log.trace("Interrupted while queueing message {}", msg);
1305        }
1306        // if the state is idle, trigger the message send
1307        if (requestState == THROTTLEIDLE) {
1308            sendQueuedMessage();
1309        }
1310    }
1311
1312    /**
1313     * Internal class to hold a request message, along with the associated
1314     * throttle state.
1315     */
1316    protected static class RequestMessage {
1317
1318        private final int state;
1319        private final XNetMessage msg;
1320
1321        RequestMessage(XNetMessage m, int s) {
1322            state = s;
1323            msg = m;
1324        }
1325
1326        int getState() {
1327            return state;
1328        }
1329
1330        XNetMessage getMsg() {
1331            return msg;
1332        }
1333    }
1334
1335    // register for notification
1336    private static final Logger log = LoggerFactory.getLogger(XNetThrottle.class);
1337
1338}