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    @GuardedBy("this")
030    protected XNetTrafficController tc;
031
032    // status of the throttle
033    protected static final int THROTTLEIDLE = 0;  // Idle Throttle
034    protected static final int THROTTLESTATSENT = 1;  // Sent Status request
035    protected static final int THROTTLESPEEDSENT = 2;  // Sent speed/dir command to locomotive
036    protected static final int THROTTLEFUNCSENT = 4;   // Sent a function command to locomotive.
037    protected static final int THROTTLEMOMSTATSENT = 8;  // Sent Momentary Status request for F0-F12
038    protected static final int THROTTLEHIGHSTATSENT = 16;  // Sent Status request for F13-F28
039    protected static final int THROTTLEHIGHMOMSTATSENT = 32;  // Sent Momentary Status request for F13-F28
040
041    protected int requestState = THROTTLEIDLE;
042
043    protected int address;
044
045    // Get the number of valid functions from the software version number.
046    // Declared private static so it can be called as an argument to super(..)
047    private static int numberOfFuns(XNetTrafficController controller) {
048        int version = (int) controller.getCommandStation().getCommandStationSoftwareVersionBCD();
049        if (version < 0x40) return 29;  // 0 - 28
050        return 69;                      // 0 - 68
051    }
052    /**
053     * Constructor
054     * @param memo system connection.
055     * @param controller system connection traffic controller.
056     */
057    public XNetThrottle(XNetSystemConnectionMemo memo, XNetTrafficController controller) {
058        super(memo, numberOfFuns(controller));
059        tc = controller;
060        requestList = new LinkedBlockingQueue<>();
061
062        log.info("Throttle created with no address and version {}", controller.getCommandStation().getCommandStationSoftwareVersionBCD());
063    }
064
065    /**
066     * Constructor.
067     * @param memo system connection.
068     * @param address loco address.
069     * @param controller system connection traffic controller.
070     */
071    public XNetThrottle(XNetSystemConnectionMemo memo, LocoAddress address, XNetTrafficController controller) {
072        super(memo, numberOfFuns(controller));
073        this.tc = controller;
074        this.setDccAddress(address.getNumber());
075        this.speedStepMode = jmri.SpeedStepMode.NMRA_DCC_128;
076        setIsAvailable(false);
077
078        requestList = new LinkedBlockingQueue<>();
079        sendStatusInformationRequest();
080        log.info("Throttle created for address {} with version {}",
081            address, controller.getCommandStation().getCommandStationSoftwareVersionBCD());
082    }
083
084    /*
085     * Set the traffic controller used with this throttle.
086     */
087    public synchronized void setXNetTrafficController(XNetTrafficController controller) {
088        tc = controller;
089    }
090
091    protected synchronized boolean csVersionSupportFn13to28() {
092        if (tc.getCommandStation().getCommandStationSoftwareVersionBCD() < 0x36) {
093            log.info("Functions F13-F28 unavailable in CS software version {}",
094                    tc.getCommandStation().getCommandStationSoftwareVersion());
095            return false;
096        }
097        return true;
098    }
099
100    protected synchronized boolean csVersionSupportFn29to68() {
101        if (tc.getCommandStation().getCommandStationSoftwareVersionBCD() < 0x40) {
102            log.info("Functions F29-68 unavailable in CS software version {}",
103                    tc.getCommandStation().getCommandStationSoftwareVersion());
104            return false;
105        }
106        return true;
107    }
108
109    /**
110     * Send the XpressNet message to set the state of locomotive direction and
111     * functions F0, F1, F2, F3, F4.
112     */
113    @Override
114    protected void sendFunctionGroup1() {
115        XNetMessage msg = XNetMessage.getFunctionGroup1OpsMsg(this.getDccAddress(),
116                getFunction(0), getFunction(1), getFunction(2), getFunction(3), getFunction(4));
117        // now, queue the message for sending to the command station
118        queueMessage(msg, THROTTLEFUNCSENT);
119    }
120
121    /**
122     * Send the XpressNet message to set the state of functions F5, F6, F7, F8.
123     */
124    @Override
125    protected void sendFunctionGroup2() {
126        XNetMessage msg = XNetMessage.getFunctionGroup2OpsMsg(this.getDccAddress(),
127                getFunction(5), getFunction(6), getFunction(7), getFunction(8));
128        // now, queue the message for sending to the command station
129        queueMessage(msg, THROTTLEFUNCSENT);
130    }
131
132    /**
133     * Send the XpressNet message to set the state of functions F9, F10, F11,
134     * F12.
135     */
136    @Override
137    protected void sendFunctionGroup3() {
138        XNetMessage msg = XNetMessage.getFunctionGroup3OpsMsg(this.getDccAddress(),
139                getFunction(9), getFunction(10), getFunction(11), getFunction(12));
140        // now, queue the message for sending to the command station
141        queueMessage(msg, THROTTLEFUNCSENT);
142    }
143
144    /**
145     * Send the XpressNet message to set the state of functions F13, F14, F15,
146     * F16, F17, F18, F19, F20.
147     */
148    @Override
149    protected void sendFunctionGroup4() {
150        if (csVersionSupportFn13to28()) {
151            XNetMessage msg = XNetMessage.getFunctionGroup4OpsMsg(this.getDccAddress(),
152                    getFunction(13), getFunction(14), getFunction(15), getFunction(16),
153                    getFunction(17), getFunction(18), getFunction(19), getFunction(20));
154            // now, queue the message for sending to the command station
155            queueMessage(msg, THROTTLEFUNCSENT);
156        }
157    }
158
159    /**
160     * Send the XpressNet message to set the state of functions F21, F22, F23,
161     * F24, F25, F26, F27, F28.
162     */
163    @Override
164    protected void sendFunctionGroup5() {
165        if (csVersionSupportFn13to28()) {
166            XNetMessage msg = XNetMessage.getFunctionGroup5OpsMsg(this.getDccAddress(),
167                    getFunction(21), getFunction(22), getFunction(23), getFunction(24),
168                    getFunction(25), getFunction(26), getFunction(27), getFunction(28));
169            // now, queue the message for sending to the command station
170            queueMessage(msg, THROTTLEFUNCSENT);
171        }
172    }
173
174    /**
175     * Send the XpressNet message to set the state of functions F29-36
176     */
177    @Override
178    protected void sendFunctionGroup6() {
179        if (csVersionSupportFn29to68()) {
180            int i = 29;
181            XNetMessage msg = XNetMessage.getFunctionGroup6OpsMsg(this.getDccAddress(),
182                    getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3),
183                    getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7));
184            // now, queue the message for sending to the command station
185            queueMessage(msg, THROTTLEFUNCSENT);
186        }
187    }
188
189    /**
190     * Send the XpressNet message to set the state of functions F37-44
191     */
192    @Override
193    protected void sendFunctionGroup7() {
194        if (csVersionSupportFn29to68()) {
195            int i = 37;
196            XNetMessage msg = XNetMessage.getFunctionGroup7OpsMsg(this.getDccAddress(),
197                    getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3),
198                    getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7));
199            // now, queue the message for sending to the command station
200            queueMessage(msg, THROTTLEFUNCSENT);
201        }
202    }
203
204    /**
205     * Send the XpressNet message to set the state of functions F45-52
206     */
207    @Override
208    protected void sendFunctionGroup8() {
209        if (csVersionSupportFn29to68()) {
210            int i = 45;
211            XNetMessage msg = XNetMessage.getFunctionGroup8OpsMsg(this.getDccAddress(),
212                    getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3),
213                    getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7));
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 F53-60
221     */
222    @Override
223    protected void sendFunctionGroup9() {
224        if (csVersionSupportFn29to68()) {
225            int i = 53;
226            XNetMessage msg = XNetMessage.getFunctionGroup9OpsMsg(this.getDccAddress(),
227                    getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3),
228                    getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7));
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 F61-68
236     */
237    @Override
238    protected void sendFunctionGroup10() {
239        if (csVersionSupportFn29to68()) {
240            int i = 61;
241            XNetMessage msg = XNetMessage.getFunctionGroup10OpsMsg(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 Momentary state of locomotive
251     * functions F0, F1, F2, F3, F4.
252     */
253    @Override
254    protected void sendMomentaryFunctionGroup1() {
255        XNetMessage msg = XNetMessage.getFunctionGroup1SetMomMsg(this.getDccAddress(),
256           getFunctionMomentary(0), getFunctionMomentary(1), getFunctionMomentary(2),
257           getFunctionMomentary(3), getFunctionMomentary(4));
258        // now, queue the message for sending to the command station
259        queueMessage(msg, THROTTLEFUNCSENT);
260    }
261
262    /**
263     * Send the XpressNet message to set the momentary state of functions F5,
264     * F6, F7, F8.
265     */
266    @Override
267    protected void sendMomentaryFunctionGroup2() {
268        XNetMessage msg = XNetMessage.getFunctionGroup2SetMomMsg(this.getDccAddress(),
269            getFunctionMomentary(5), getFunctionMomentary(6),
270            getFunctionMomentary(7), getFunctionMomentary(8));
271        // now, queue the message for sending to the command station
272        queueMessage(msg, THROTTLEFUNCSENT);
273    }
274
275    /**
276     * Send the XpressNet message to set the momentary state of functions F9,
277     * F10, F11, F12.
278     */
279    @Override
280    protected void sendMomentaryFunctionGroup3() {
281        XNetMessage msg = XNetMessage.getFunctionGroup3SetMomMsg(this.getDccAddress(),
282            getFunctionMomentary(9), getFunctionMomentary(10),
283           getFunctionMomentary(11), getFunctionMomentary(12));
284        // now, queue the message for sending to the command station
285        queueMessage(msg, THROTTLEFUNCSENT);
286    }
287
288    /**
289     * Send the XpressNet message to set the momentary state of functions
290     * F13, F14, F15, F16, F17, F18, F19, F20.
291     */
292    @Override
293    protected void sendMomentaryFunctionGroup4() {
294        if (csVersionSupportFn13to28()) {
295            XNetMessage msg = XNetMessage.getFunctionGroup4SetMomMsg(this.getDccAddress(),
296               getFunctionMomentary(13), getFunctionMomentary(14),
297               getFunctionMomentary(15), getFunctionMomentary(16),
298               getFunctionMomentary(17), getFunctionMomentary(18),
299               getFunctionMomentary(19), getFunctionMomentary(20));
300            // now, queue the message for sending to the command station
301            queueMessage(msg, THROTTLEFUNCSENT);
302        }
303    }
304
305    /**
306     * Send the XpressNet message to set the momentary state of functions F21,
307     * F22, F23, F24, F25, F26, F27, F28.
308     */
309    @Override
310    protected void sendMomentaryFunctionGroup5() {
311        if (csVersionSupportFn13to28()) {
312            XNetMessage msg = XNetMessage.getFunctionGroup5SetMomMsg(this.getDccAddress(),
313                getFunctionMomentary(21), getFunctionMomentary(22), getFunctionMomentary(23),
314                getFunctionMomentary(24), getFunctionMomentary(25), getFunctionMomentary(26),
315                getFunctionMomentary(27), getFunctionMomentary(28));
316            // now, queue the message for sending to the command station
317            queueMessage(msg, THROTTLEFUNCSENT);
318        }
319    }
320
321    /**
322     * Send the XpressNet message to set the momentary state of functions F29-36
323     */
324    @Override
325    protected void sendMomentaryFunctionGroup6() {
326        if (csVersionSupportFn29to68()) {
327            int i = 29;
328            XNetMessage msg = XNetMessage.getFunctionGroup6SetMomMsg(this.getDccAddress(),
329                getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2),
330                getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5),
331                getFunctionMomentary(i+6), getFunctionMomentary(i+7));
332            // now, queue the message for sending to the command station
333            queueMessage(msg, THROTTLEFUNCSENT);
334        }
335    }
336
337    /**
338     * Send the XpressNet message to set the momentary state of functions F37-44
339     */
340    @Override
341    protected void sendMomentaryFunctionGroup7() {
342        if (csVersionSupportFn29to68()) {
343            int i = 37;
344            XNetMessage msg = XNetMessage.getFunctionGroup7SetMomMsg(this.getDccAddress(),
345                getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2),
346                getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5),
347                getFunctionMomentary(i+6), getFunctionMomentary(i+7));
348            // now, queue the message for sending to the command station
349            queueMessage(msg, THROTTLEFUNCSENT);
350        }
351    }
352
353    /**
354     * Send the XpressNet message to set the momentary state of functions F45-52
355     */
356    @Override
357    protected void sendMomentaryFunctionGroup8() {
358        if (csVersionSupportFn29to68()) {
359            int i = 45;
360            XNetMessage msg = XNetMessage.getFunctionGroup8SetMomMsg(this.getDccAddress(),
361                getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2),
362                getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5),
363                getFunctionMomentary(i+6), getFunctionMomentary(i+7));
364            // now, queue the message for sending to the command station
365            queueMessage(msg, THROTTLEFUNCSENT);
366        }
367    }
368
369    /**
370     * Send the XpressNet message to set the momentary state of functions F53-60
371     */
372    @Override
373    protected void sendMomentaryFunctionGroup9() {
374        if (csVersionSupportFn29to68()) {
375            int i = 53;
376            XNetMessage msg = XNetMessage.getFunctionGroup9SetMomMsg(this.getDccAddress(),
377                getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2),
378                getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5),
379                getFunctionMomentary(i+6), getFunctionMomentary(i+7));
380            // now, queue the message for sending to the command station
381            queueMessage(msg, THROTTLEFUNCSENT);
382        }
383    }
384
385    /**
386     * Send the XpressNet message to set the momentary state of functions F61-68
387     */
388    @Override
389    protected void sendMomentaryFunctionGroup10() {
390        if (csVersionSupportFn29to68()) {
391            int i = 61;
392            XNetMessage msg = XNetMessage.getFunctionGroup10SetMomMsg(this.getDccAddress(),
393                getFunctionMomentary(i), getFunctionMomentary(i+1), getFunctionMomentary(i+2),
394                getFunctionMomentary(i+3), getFunctionMomentary(i+4), getFunctionMomentary(i+5),
395                getFunctionMomentary(i+6), getFunctionMomentary(i+7));
396            // now, queue the message for sending to the command station
397            queueMessage(msg, THROTTLEFUNCSENT);
398        }
399    }
400
401    /**
402     * Notify listeners and send the new speed to the command station.
403     */
404    @Override
405    public synchronized void setSpeedSetting(float speed) {
406        log.debug("set Speed to: {} Current step mode is: {}", speed, this.speedStepMode);
407        super.setSpeedSetting(speed);
408        if (speed < 0) {
409            /* we're sending an emergency stop to this locomotive only */
410            sendEmergencyStop();
411        } else {
412            if (speed > 1) {
413                speed = (float) 1.0;
414            }
415            /* we're sending a speed to the locomotive */
416            XNetMessage msg = XNetMessage.getSpeedAndDirectionMsg(getDccAddress(),
417                    this.speedStepMode,
418                    speed,
419                    this.isForward);
420            // now, queue the message for sending to the command station
421            queueMessage(msg, THROTTLESPEEDSENT);
422        }
423    }
424
425    /**
426     * Since XpressNet has a seperate Opcode for emergency stop, we're setting
427     * this up as a seperate protected function.
428     */
429    protected void sendEmergencyStop() {
430        /* Emergency stop sent */
431        XNetMessage msg = XNetMessage.getAddressedEmergencyStop(this.getDccAddress());
432        // now, queue the message for sending to the command station
433        queueMessage(msg, THROTTLESPEEDSENT);
434    }
435
436    /**
437     * When we set the direction, we're going to set the speed to zero as well.
438     */
439    @Override
440    public void setIsForward(boolean forward) {
441        super.setIsForward(forward);
442        synchronized(this) {
443            setSpeedSetting(this.speedSetting);
444        }
445    }
446
447    /**
448     * Set the speed step value and the related speedIncrement value.
449     *
450     * @param Mode the current speed step mode - default should be 128 speed
451     *             step mode in most cases
452     */
453    @Override
454    public void setSpeedStepMode(SpeedStepMode Mode) {
455        super.setSpeedStepMode(Mode);
456        // On a Lenz system, we need to send the speed to make sure the
457        // command station knows about the change.
458        synchronized(this) {
459            setSpeedSetting(this.speedSetting);
460        }
461    }
462
463    /**
464     * Dispose when finished with this object. After this, further usage of this
465     * Throttle object will result in a JmriException.
466     * <p>
467     * This is quite problematic, because a using object doesn't know when it's
468     * the last user.
469     */
470    @Override
471    public void throttleDispose() {
472        active = false;
473        stopStatusTimer();
474        finishRecord();
475    }
476
477    public int setDccAddress(int newaddress) {
478        address = newaddress;
479        return address;
480    }
481
482    public int getDccAddress() {
483        return address;
484    }
485
486    protected int getDccAddressHigh() {
487        return LenzCommandStation.getDCCAddressHigh(this.address);
488    }
489
490    protected int getDccAddressLow() {
491        return LenzCommandStation.getDCCAddressLow(this.address);
492    }
493
494    /**
495     * Send a request to get the speed, direction and function status from the
496     * command station.
497     */
498    protected synchronized void sendStatusInformationRequest() {
499        /* Send the request for status */
500        XNetMessage msg = XNetMessage.getLocomotiveInfoRequestMsg(this.address);
501        msg.setRetries(1); // Since we repeat this ourselves, don't ask the
502        // traffic controller to do this for us.
503        // now, we queue the message for sending to the command station
504        queueMessage(msg, THROTTLESTATSENT);
505    }
506
507    /**
508     * Send a request to get the status of functions from the command station.
509     */
510    protected synchronized void sendFunctionStatusInformationRequest() {
511        log.debug("Throttle {} sending request for function momentary status.", address);
512        /* Send the request for Function status */
513        XNetMessage msg = XNetMessage.getLocomotiveFunctionStatusMsg(this.address);
514        queueMessage(msg, (THROTTLEMOMSTATSENT | THROTTLESTATSENT));
515    }
516
517    /**
518     * Send a request to get the on/off status of functions 13-28 from the
519     * command station.
520     */
521    protected synchronized void sendFunctionHighInformationRequest() {
522        if (csVersionSupportFn13to28()) {
523            log.debug("Throttle {} sending request for high function momentary status.", address);
524            /* Send the request for Function status */
525            XNetMessage msg = XNetMessage.getLocomotiveFunctionHighOnStatusMsg(this.address);
526            // now, we send the message to the command station
527            queueMessage(msg, THROTTLEHIGHSTATSENT | THROTTLESTATSENT);
528        }
529    }
530
531    /**
532     * Send a request to get the status of functions from the command station.
533     */
534    protected synchronized void sendFunctionHighMomentaryStatusRequest() {
535        if (csVersionSupportFn13to28()) {
536            log.debug("Throttle {} sending request for function momentary status.", address);
537            /* Send the request for Function status */
538            XNetMessage msg = XNetMessage.getLocomotiveFunctionHighMomStatusMsg(this.address);
539            // now, we send the message to the command station
540            queueMessage(msg, (THROTTLEHIGHMOMSTATSENT | THROTTLESTATSENT));
541        }
542    }
543
544    // Handle incoming messages for This throttle.
545    @Override
546    public void message(XNetReply l) {
547        // First, we want to see if this throttle is waiting for a message
548        //or not.
549        log.debug("Throttle {} - received message {}", getDccAddress(), l);
550        if (requestState == THROTTLEIDLE) {
551            log.trace("Current throttle status is THROTTLEIDLE");
552            // We haven't sent anything, but we might be told someone else
553            // has taken over this address
554            if (l.getElement(0) == XNetConstants.LOCO_INFO_RESPONSE) {
555                log.trace("Throttle - message is LOCO_INFO_RESPONSE ");
556                if (l.getElement(1) == XNetConstants.LOCO_NOT_AVAILABLE
557                        && getDccAddressHigh() == l.getElement(2)
558                        && getDccAddressLow() == l.getElement(3)) {
559                    locoInUse();
560                }
561            }
562        } else if ((requestState & THROTTLESPEEDSENT) == THROTTLESPEEDSENT
563                || (requestState & THROTTLEFUNCSENT) == THROTTLEFUNCSENT) {
564            log.trace("Current throttle status is THROTTLESPEEDSENT");
565            // For a Throttle Command, we're just looking for a return
566            // acknowledgment, Either a Success or Failure message.
567            if (l.isOkMessage()) {
568                log.trace("Last Command processed successfully.");
569                // Since we received an "ok",  we want to make sure
570                // "isAvailable reflects we are in control
571                setIsAvailable(true);
572                requestState = THROTTLEIDLE;
573                sendQueuedMessage();
574            } else if (l.isRetransmittableErrorMsg()) {
575                /* this is a communications error */
576                log.trace("Communications error occurred - message received was: {}", l);
577            } else if (l.isUnsupportedError()) {
578                /* The Command Station does not support this command */
579                log.error("Unsupported Command Sent to command station");
580                requestState = THROTTLEIDLE;
581                sendQueuedMessage();
582            } else {
583                /* this is an unknown error */
584                requestState = THROTTLEIDLE;
585                sendQueuedMessage();
586                log.trace("Received unhandled response: {}", l);
587            }
588        } else if ((requestState & THROTTLESTATSENT) == THROTTLESTATSENT) {
589            log.trace("Current throttle status is THROTTLESTATSENT");
590            // This throttle has requested status information, so we need
591            // to process those messages.
592            if (l.getElement(0) == XNetConstants.LOCO_INFO_NORMAL_UNIT) {
593                if (l.getElement(1) == XNetConstants.LOCO_FUNCTION_STATUS_HIGH_MOM) {
594                    /* handle information response about F13-F28 Momentary
595                     Status*/
596                    log.trace("Throttle - message is LOCO_FUNCTION_STATUS_HIGH_MOM");
597                    int b3 = l.getElement(2);
598                    int b4 = l.getElement(3);
599                    parseFunctionHighMomentaryInformation(b3, b4);
600                    //We've processed this request, so set the status to Idle.
601                    requestState = THROTTLEIDLE;
602                    sendQueuedMessage();
603                } else {
604                    log.trace("Throttle - message is LOCO_INFO_NORMAL_UNIT");
605                    /* there is no address sent with this information */
606                    int b1 = l.getElement(1);
607                    int b2 = l.getElement(2);
608                    int b3 = l.getElement(3);
609                    int b4 = l.getElement(4);
610
611                    parseSpeedAndAvailability(b1);
612                    parseSpeedAndDirection(b2);
613                    parseFunctionInformation(b3, b4);
614
615                    //We've processed this request, so set the status to Idle.
616                    requestState = THROTTLEIDLE;
617                    sendQueuedMessage();
618                    // And then we want to request the Function Momentary Status
619                    sendFunctionStatusInformationRequest();
620                }
621            } else if (l.getElement(0) == XNetConstants.LOCO_INFO_MUED_UNIT) {
622                log.trace("Throttle - message is LOCO_INFO_MUED_UNIT ");
623                /* there is no address sent with this information */
624                int b1 = l.getElement(1);
625                int b2 = l.getElement(2);
626                int b3 = l.getElement(3);
627                int b4 = l.getElement(4);
628                // Element 5 is the consist address, it can only be in the
629                // range 1-99
630                int b5 = l.getElement(5);
631
632                log.trace("Locomotive {} inconsist {} ", getDccAddress(), b5);
633
634                parseSpeedAndAvailability(b1);
635                parseSpeedAndDirection(b2);
636                parseFunctionInformation(b3, b4);
637
638                // We've processed this request, so set the status to Idle.
639                requestState = THROTTLEIDLE;
640                sendQueuedMessage();
641                // And then we want to request the Function Momentary Status
642                sendFunctionStatusInformationRequest();
643            } else if (l.getElement(0) == XNetConstants.LOCO_INFO_DH_UNIT) {
644                log.trace("Throttle - message is LOCO_INFO_DH_UNIT ");
645                /* there is no address sent with this information */
646                int b1 = l.getElement(1);
647                int b2 = l.getElement(2);
648                int b3 = l.getElement(3);
649                int b4 = l.getElement(4);
650
651                // elements 5 and 6 contain the address of the other unit
652                // in the DH
653                int b5 = l.getElement(5);
654                int b6 = l.getElement(6);
655
656                if (log.isDebugEnabled()) {
657                    int address2 = (b5 == 0x00) ? b6 : ((b5 * 256) & 0xFF00) + (b6 & 0xFF) - 0xC000;
658                    log.trace("Locomotive {} in Double Header with {}",
659                            getDccAddress(), address2);
660                }
661
662                parseSpeedAndAvailability(b1);
663                parseSpeedAndDirection(b2);
664                parseFunctionInformation(b3, b4);
665
666                // We've processed this request, so set the status to Idle.
667                requestState = THROTTLEIDLE;
668                sendQueuedMessage();
669                // And then we want to request the Function Momentary Status
670                sendFunctionStatusInformationRequest();
671            } else if (l.getElement(0) == XNetConstants.LOCO_INFO_MU_ADDRESS) {
672                log.trace("Throttle - message is LOCO_INFO_MU ADDRESS ");
673                /* there is no address sent with this information */
674                int b1 = l.getElement(1);
675                int b2 = l.getElement(2);
676
677                parseSpeedAndAvailability(b1);
678                parseSpeedAndDirection(b2);
679
680                //We've processed this request, so set the status to Idle.
681                requestState = THROTTLEIDLE;
682                sendQueuedMessage();
683                // And then we want to request the Function Momentary Status
684                sendFunctionStatusInformationRequest();
685            } else if (l.getElement(0) == XNetConstants.LOCO_INFO_RESPONSE) {
686                log.trace("Throttle - message is LOCO_INFO_RESPONSE ");
687                if (l.getElement(1) == XNetConstants.LOCO_NOT_AVAILABLE) {
688                    /* the address is in bytes 3 and 4*/
689                    if (getDccAddressHigh() == l.getElement(2) && getDccAddressLow() == l.getElement(3)) {
690                        locoInUse();
691                    }
692                    // We've processed this request, so set the status to Idle.
693                    requestState = THROTTLEIDLE;
694                    sendQueuedMessage();
695                } else if (l.getElement(1) == XNetConstants.LOCO_FUNCTION_STATUS) {
696                    /* Bytes 3 and 4 contain function momentary status information */
697                    int b3 = l.getElement(2);
698                    int b4 = l.getElement(3);
699                    parseFunctionMomentaryInformation(b3, b4);
700                    // We've processed this request, so set the status to Idle.
701                    requestState = THROTTLEIDLE;
702                    sendQueuedMessage();
703                    // And then we want to request the Function Status for F13-F28
704                    sendFunctionHighInformationRequest();
705
706                } else if (l.getElement(1) == XNetConstants.LOCO_FUNCTION_STATUS_HIGH) {
707                    /* Bytes 3 and 4 contain function status information for F13-F28*/
708                    int b3 = l.getElement(2);
709                    int b4 = l.getElement(3);
710                    parseFunctionHighInformation(b3, b4);
711                    //We've processed this request, so set the status to Idle.
712                    requestState = THROTTLEIDLE;
713                    sendQueuedMessage();
714                    // And then we want to request the Function Momentary Status
715                    // for functions F13-F28
716                    sendFunctionHighMomentaryStatusRequest();
717                } else {
718                    // The reply to the outstanding request has been consumed,
719                    // so no timeout will ever fire for it.  Without this
720                    // branch an unrecognized subtype leaves requestState stuck
721                    // (never returns to THROTTLEIDLE) and every command queued
722                    // afterwards is silently held back forever.
723                    log.debug("Unhandled LOCO_INFO_RESPONSE subtype {} while in THROTTLESTATSENT, resuming queue",
724                            l.getElement(1));
725                    requestState = THROTTLEIDLE;
726                    sendQueuedMessage();
727                }
728            } else if (l.isRetransmittableErrorMsg()) {
729                /* this is a communications error */
730                log.trace("Communications error occurred - message received was: {}", l);
731            } else if (l.isUnsupportedError()) {
732                /* The Command Station does not support this command */
733                log.error("Unsupported Command Sent to command station");
734                if ((requestState & THROTTLEMOMSTATSENT) == THROTTLEMOMSTATSENT) {
735                    // if momentary is not supported, try requesting the
736                    // high function state.
737                    requestState = THROTTLEIDLE;
738                    sendFunctionHighInformationRequest();
739                } else {
740                    requestState = THROTTLEIDLE;
741                    sendQueuedMessage();
742                }
743            } else {
744                /* this is an unknown error */
745                requestState = THROTTLEIDLE;
746                sendQueuedMessage();
747                log.trace("Received unhandled response: {}", l);
748            }
749        }
750    }
751
752    private void locoInUse() {
753        if (isAvailable) {
754            //Set the Is available flag to Throttle.False
755            log.info("Loco {} In use by another device", getDccAddress());
756            setIsAvailable(false);
757        }
758    }
759
760    /**
761     * {@inheritDoc}
762     */
763    @Override
764    public void message(XNetMessage l) {
765        // no need to handle outgoing messages.
766    }
767
768    /**
769     * {@inheritDoc}
770     */
771    @Override
772    public void notifyTimeout(XNetMessage msg) {
773        log.debug("Notified of timeout on message {} , {} retries available.",
774                msg, msg.getRetries());
775        if (msg.getRetries() > 0) {
776            // If the message still has retries available, send it back to
777            // the traffic controller.
778            synchronized (this) {
779                tc.sendXNetMessage(msg, this);
780            }
781        } else {
782            // Try to send the next queued message,  if one is available.
783            sendQueuedMessage();
784        }
785    }
786
787    // Status Information processing routines
788    // Used for return values from Status requests.
789    /**
790     * Get SpeedStep and availability information.
791     * @param b1 1st byte of message to examine
792     */
793    protected void parseSpeedAndAvailability(int b1) {
794        /* the first data bite indicates the speed step mode, and
795         if the locomotive is being controlled by another throttle */
796
797        if ((b1 & 0x08) == 0x08 && this.isAvailable) {
798            locoInUse();
799        } else if ((b1 & 0x08) == 0x00 && !this.isAvailable) {
800            log.trace("Loco Is Available");
801            setIsAvailable(true);
802        }
803        if ((b1 & 0x01) == 0x01) {
804            log.trace("Speed Step setting 27");
805            notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_27);
806        } else if ((b1 & 0x02) == 0x02) {
807            log.trace("Speed Step setting 28");
808            notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_28);
809        } else if ((b1 & 0x04) == 0x04) {
810            log.trace("Speed Step setting 128");
811            notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_128);
812        } else {
813            log.trace("Speed Step setting 14");
814            notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_14);
815        }
816    }
817
818    protected void notifyNewSpeedStepMode(SpeedStepMode mode) {
819        if (this.speedStepMode != mode) {
820            firePropertyChange(SPEEDSTEPS,
821                    this.speedStepMode,
822                    this.speedStepMode = mode);
823        }
824    }
825
826    /**
827     * Get Speed and Direction information.
828     * @param b2 2nd byte of message to examine
829     */
830    protected void parseSpeedAndDirection(int b2) {
831        /* the second byte indicates the speed and direction setting */
832
833        if ((b2 & 0x80) == 0x80 && !this.isForward) {
834            notifyNewDirection(true);
835        } else if ((b2 & 0x80) == 0x00 && this.isForward) {
836            notifyNewDirection(false);
837        }
838
839        if (this.speedStepMode == SpeedStepMode.NMRA_DCC_128) {
840            // We're in 128 speed step mode
841            int speedVal = b2 & 0x7f;
842            // The first speed step used is actually at 2 for 128
843            // speed step mode.
844            if (speedVal >= 1) {
845                speedVal -= 1;
846            }
847            if (java.lang.Math.abs(
848                    this.getSpeedSetting() - ((float) speedVal / (float) 126)) >= 0.0079) {
849                synchronized(this) {
850                    firePropertyChange(SPEEDSETTING, this.speedSetting,
851                            this.speedSetting = (float) speedVal / (float) 126);
852                }
853            }
854        } else if (this.speedStepMode == SpeedStepMode.NMRA_DCC_28) {
855            // We're in 28 speed step mode
856            // We have to re-arrange the bits, since bit 4 is the LSB,
857            // but other bits are in order from 0-3
858            int speedVal = ((b2 & 0x0F) << 1)
859                    + ((b2 & 0x10) >> 4);
860            // The first speed step used is actually at 4 for 28
861            // speed step mode.
862            if (speedVal >= 3) {
863                speedVal -= 3;
864            } else {
865                speedVal = 0;
866            }
867            if (java.lang.Math.abs(
868                    this.getSpeedSetting() - ((float) speedVal / (float) 28)) >= 0.035) {
869                synchronized(this) {
870                    firePropertyChange(SPEEDSETTING, this.speedSetting,
871                            this.speedSetting = (float) speedVal / (float) 28);
872                }
873            }
874        } else if (this.speedStepMode == SpeedStepMode.NMRA_DCC_27) {
875            // We're in 27 speed step mode
876            // We have to re-arrange the bits, since bit 4 is the LSB,
877            // but other bits are in order from 0-3
878            int speedVal = ((b2 & 0x0F) << 1)
879                    + ((b2 & 0x10) >> 4);
880            // The first speed step used is actually at 4 for 27
881            // speed step mode.
882            if (speedVal >= 3) {
883                speedVal -= 3;
884            } else {
885                speedVal = 0;
886            }
887            if (java.lang.Math.abs(
888                    this.getSpeedSetting() - ((float) speedVal / (float) 27)) >= 0.037) {
889                synchronized(this) {
890                    firePropertyChange(SPEEDSETTING, this.speedSetting,
891                            this.speedSetting = (float) speedVal / (float) 27);
892                }
893            }
894        } else {
895            // Assume we're in 14 speed step mode.
896            int speedVal = (b2 & 0x0F);
897            if (speedVal >= 1) {
898                speedVal -= 1;
899            }
900            if (java.lang.Math.abs(
901                    this.getSpeedSetting() - ((float) speedVal / (float) 14)) >= 0.071) {
902                synchronized(this) {
903                    firePropertyChange(SPEEDSETTING, this.speedSetting,
904                            this.speedSetting = (float) speedVal / (float) 14);
905                }
906            }
907        }
908    }
909
910    protected void notifyNewDirection(boolean forward) {
911        firePropertyChange(ISFORWARD, this.isForward, this.isForward = forward);
912        log.trace("Throttle - Changed direction to {} Locomotive: {}", forward ? "forward" : "reverse", getDccAddress());
913    }
914
915    protected void parseFunctionInformation(int b3, int b4) {
916        log.trace("Parsing Function F0-F12 status, function bytes: {} and {}",
917                b3, b4);
918        /* data byte 3 is the status of F0 F4 F3 F2 F1 */
919        updateFunction(0, (b3 & 0x10) == 0x10);
920        updateFunction(1, (b3 & 0x01) == 0x01);
921        updateFunction(2, (b3 & 0x02) == 0x02);
922        updateFunction(3, (b3 & 0x04) == 0x04);
923        updateFunction(4, (b3 & 0x08) == 0x08);
924        /* data byte 4 is the status of F12 F11 F10 F9 F8 F7 F6 F5 */
925        updateFunction(5, (b4 & 0x01) == 0x01);
926        updateFunction(6, (b4 & 0x02) == 0x02);
927        updateFunction(7, (b4 & 0x04) == 0x04);
928        updateFunction(8, (b4 & 0x08) == 0x08);
929        updateFunction(9, (b4 & 0x10) == 0x10);
930        updateFunction(10, (b4 & 0x20) == 0x20);
931        updateFunction(11, (b4 & 0x40) == 0x40);
932        updateFunction(12, (b4 & 0x80) == 0x80);
933    }
934
935    protected void parseFunctionHighInformation(int b3, int b4) {
936        log.trace("Parsing Function F13-F28 status, function bytes: {} and {}",
937                b3, b4);
938        /* data byte 3 is the status of F20 F19 F18 F17 F16 F15 F14 F13 */
939        updateFunction(13, (b3 & 0x01) == 0x01);
940        updateFunction(14, (b3 & 0x02) == 0x02);
941        updateFunction(15, (b3 & 0x04) == 0x04);
942        updateFunction(16, (b3 & 0x08) == 0x08);
943        updateFunction(17, (b3 & 0x10) == 0x10);
944        updateFunction(18, (b3 & 0x20) == 0x20);
945        updateFunction(19, (b3 & 0x40) == 0x40);
946        updateFunction(20, (b3 & 0x80) == 0x80);
947        /* data byte 4 is the status of F28 F27 F26 F25 F24 F23 F22 F21 */
948        updateFunction(21, (b4 & 0x01) == 0x01);
949        updateFunction(22, (b4 & 0x02) == 0x02);
950        updateFunction(23, (b4 & 0x04) == 0x04);
951        updateFunction(24, (b4 & 0x08) == 0x08);
952        updateFunction(25, (b4 & 0x10) == 0x10);
953        updateFunction(26, (b4 & 0x20) == 0x20);
954        updateFunction(27, (b4 & 0x40) == 0x40);
955        updateFunction(28, (b4 & 0x80) == 0x80);
956
957    }
958
959    protected void parseFunctionMomentaryInformation(int b3, int b4) {
960        log.trace("Parsing Function Momentary status, function bytes: {} and {}",
961                b3, b4);
962        /* data byte 3 is the momentary status of F0 F4 F3 F2 F1 */
963        checkForFunctionMomentaryValueChange(0, b3, 0x10, getFunctionMomentary(0));
964        checkForFunctionMomentaryValueChange(1, b3, 0x01, getFunctionMomentary(1));
965        checkForFunctionMomentaryValueChange(2, b3, 0x02, getFunctionMomentary(2));
966        checkForFunctionMomentaryValueChange(3, b3, 0x04, getFunctionMomentary(3));
967        checkForFunctionMomentaryValueChange(4, b3, 0x08, getFunctionMomentary(4));
968        /* data byte 4 is the momentary status of F12 F11 F10 F9 F8 F7 F6 F5 */
969        checkForFunctionMomentaryValueChange(5, b4, 0x01, getFunctionMomentary(5));
970        checkForFunctionMomentaryValueChange(6, b4, 0x02, getFunctionMomentary(6));
971        checkForFunctionMomentaryValueChange(7, b4, 0x04, getFunctionMomentary(7));
972        checkForFunctionMomentaryValueChange(8, b4, 0x08, getFunctionMomentary(8));
973        checkForFunctionMomentaryValueChange(9, b4, 0x10, getFunctionMomentary(9));
974        checkForFunctionMomentaryValueChange(10, b4, 0x20, getFunctionMomentary(10));
975        checkForFunctionMomentaryValueChange(11, b4, 0x40, getFunctionMomentary(11));
976        checkForFunctionMomentaryValueChange(12, b4, 0x80, getFunctionMomentary(12));
977    }
978
979    protected void parseFunctionHighMomentaryInformation(int b3, int b4) {
980        log.trace("Parsing Function F13-F28 Momentary status, function bytes: {} and {}",
981                b3, b4);
982        /* data byte 3 is the momentary status of F20 F19 F17 F16 F15 F14 F13 */
983        checkForFunctionMomentaryValueChange(13, b3, 0x01, getFunctionMomentary(13));
984        checkForFunctionMomentaryValueChange(14, b3, 0x02, getFunctionMomentary(14));
985        checkForFunctionMomentaryValueChange(15, b3, 0x04, getFunctionMomentary(15));
986        checkForFunctionMomentaryValueChange(16, b3, 0x08, getFunctionMomentary(16));
987        checkForFunctionMomentaryValueChange(17, b3, 0x10, getFunctionMomentary(17));
988        checkForFunctionMomentaryValueChange(18, b3, 0x20, getFunctionMomentary(18));
989        checkForFunctionMomentaryValueChange(19, b3, 0x40, getFunctionMomentary(19));
990        checkForFunctionMomentaryValueChange(20, b3, 0x80, getFunctionMomentary(20));
991        /* data byte 4 is the momentary status of F28 F27 F26 F25 F24 F23 F22 F21 */
992        checkForFunctionMomentaryValueChange(21, b4, 0x01, getFunctionMomentary(21));
993        checkForFunctionMomentaryValueChange(22, b4, 0x02, getFunctionMomentary(22));
994        checkForFunctionMomentaryValueChange(23, b4, 0x04, getFunctionMomentary(23));
995        checkForFunctionMomentaryValueChange(24, b4, 0x08, getFunctionMomentary(24));
996        checkForFunctionMomentaryValueChange(25, b4, 0x10, getFunctionMomentary(25));
997        checkForFunctionMomentaryValueChange(26, b4, 0x20, getFunctionMomentary(26));
998        checkForFunctionMomentaryValueChange(27, b4, 0x40, getFunctionMomentary(27));
999        checkForFunctionMomentaryValueChange(28, b4, 0x80, getFunctionMomentary(28));
1000    }
1001
1002    protected void checkForFunctionMomentaryValueChange(int funcNum, int bytevalue, int bitmask, boolean currentValue) {
1003        if ((bytevalue & bitmask) == bitmask && !currentValue) {
1004            updateFunctionMomentary(funcNum, true);
1005        } else if ((bytevalue & bitmask) == 0x00 && currentValue) {
1006            updateFunctionMomentary(funcNum, false);
1007        }
1008    }
1009
1010    /**
1011     * Set the internal isAvailable property.
1012     *
1013     * @param available true if available; false otherwise
1014     */
1015    protected void setIsAvailable(boolean available) {
1016        firePropertyChange("IsAvailable", this.isAvailable, this.isAvailable = available);
1017        /* if we're setting this to true, stop the timer,
1018         otherwise start the timer. */
1019        if (available) {
1020            stopStatusTimer();
1021        } else {
1022            startStatusTimer();
1023        }
1024    }
1025
1026    /**
1027     * Set up the status timer, and start it.
1028     */
1029    protected void startStatusTimer() {
1030        log.debug("Status Timer Started");
1031
1032        if (statusTask != null) {
1033            statusTask.cancel();
1034            statusTask = null;
1035        }
1036        statusTask = new java.util.TimerTask() {
1037            @Override
1038            public void run() {
1039                /* If the timer times out, just send a status
1040                 request message */
1041                sendStatusInformationRequest();
1042            }
1043        };
1044
1045        jmri.util.TimerUtil.schedule(statusTask, statTimeoutValue, statTimeoutValue);
1046    }
1047
1048    /**
1049     * Stop the Status Timer
1050     */
1051    protected void stopStatusTimer() {
1052        log.debug("Status Timer Stopped");
1053        if (statusTask != null) {
1054            try {
1055                statusTask.cancel();
1056            } catch (IllegalStateException ise) {
1057                log.debug("Timer already canceled");
1058            }
1059            statusTask = null;
1060        }
1061    }
1062
1063    @Override
1064    public LocoAddress getLocoAddress() {
1065        return new DccLocoAddress(address, XNetThrottleManager.isLongAddress(address));
1066    }
1067
1068    // A queue to hold outstanding messages
1069    protected LinkedBlockingQueue<RequestMessage> requestList;
1070
1071    /**
1072     * Send message from queue.
1073     */
1074    protected synchronized void sendQueuedMessage() {
1075
1076        RequestMessage msg;
1077        // check to see if the queue has a message in it, and if it does,
1078        // remove the first message
1079        if (!requestList.isEmpty()) {
1080            log.debug("sending message to traffic controller");
1081            // if the queue is not empty, remove the first message
1082            // from the queue, send the message, and set the state machine
1083            // to the required state.
1084            try {
1085                msg = requestList.take();
1086            } catch (java.lang.InterruptedException ie) {
1087                return; // if there was an error, exit.
1088            }
1089            requestState = msg.getState();
1090            tc.sendXNetMessage(msg.getMsg(), this);
1091        } else {
1092            log.debug("message queue empty");
1093            // if the queue is empty, set the state to idle.
1094            requestState = THROTTLEIDLE;
1095        }
1096    }
1097
1098    /**
1099     * Queue a message.
1100     * @param m message to send
1101     * @param s state
1102     */
1103    protected synchronized void queueMessage(XNetMessage m, int s) {
1104        log.debug("adding message to message queue");
1105        // put the message in the queue
1106        RequestMessage msg = new RequestMessage(m, s);
1107        try {
1108            requestList.put(msg);
1109        } catch (java.lang.InterruptedException ie) {
1110            log.trace("Interrupted while queueing message {}", msg);
1111        }
1112        // if the state is idle, trigger the message send
1113        if (requestState == THROTTLEIDLE) {
1114            sendQueuedMessage();
1115        }
1116    }
1117
1118    /**
1119     * Internal class to hold a request message, along with the associated
1120     * throttle state.
1121     */
1122    protected static class RequestMessage {
1123
1124        private final int state;
1125        private final XNetMessage msg;
1126
1127        RequestMessage(XNetMessage m, int s) {
1128            state = s;
1129            msg = m;
1130        }
1131
1132        int getState() {
1133            return state;
1134        }
1135
1136        XNetMessage getMsg() {
1137            return msg;
1138        }
1139    }
1140
1141    // register for notification
1142    private static final Logger log = LoggerFactory.getLogger(XNetThrottle.class);
1143
1144}