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 static private so it can be called as an argument to super(..)
047    static private 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                }
718            } else if (l.isRetransmittableErrorMsg()) {
719                /* this is a communications error */
720                log.trace("Communications error occurred - message received was: {}", l);
721            } else if (l.isUnsupportedError()) {
722                /* The Command Station does not support this command */
723                log.error("Unsupported Command Sent to command station");
724                if ((requestState & THROTTLEMOMSTATSENT) == THROTTLEMOMSTATSENT) {
725                    // if momentary is not supported, try requesting the
726                    // high function state.
727                    requestState = THROTTLEIDLE;
728                    sendFunctionHighInformationRequest();
729                } else {
730                    requestState = THROTTLEIDLE;
731                    sendQueuedMessage();
732                }
733            } else {
734                /* this is an unknown error */
735                requestState = THROTTLEIDLE;
736                sendQueuedMessage();
737                log.trace("Received unhandled response: {}", l);
738            }
739        }
740    }
741
742    private void locoInUse() {
743        if (isAvailable) {
744            //Set the Is available flag to Throttle.False
745            log.info("Loco {} In use by another device", getDccAddress());
746            setIsAvailable(false);
747        }
748    }
749
750    /**
751     * {@inheritDoc}
752     */
753    @Override
754    public void message(XNetMessage l) {
755        // no need to handle outgoing messages.
756    }
757
758    /**
759     * {@inheritDoc}
760     */
761    @Override
762    public void notifyTimeout(XNetMessage msg) {
763        log.debug("Notified of timeout on message {} , {} retries available.",
764                msg, msg.getRetries());
765        if (msg.getRetries() > 0) {
766            // If the message still has retries available, send it back to
767            // the traffic controller.
768            synchronized (this) {
769                tc.sendXNetMessage(msg, this);
770            }
771        } else {
772            // Try to send the next queued message,  if one is available.
773            sendQueuedMessage();
774        }
775    }
776
777    // Status Information processing routines
778    // Used for return values from Status requests.
779    /**
780     * Get SpeedStep and availability information.
781     * @param b1 1st byte of message to examine
782     */
783    protected void parseSpeedAndAvailability(int b1) {
784        /* the first data bite indicates the speed step mode, and
785         if the locomotive is being controlled by another throttle */
786
787        if ((b1 & 0x08) == 0x08 && this.isAvailable) {
788            locoInUse();
789        } else if ((b1 & 0x08) == 0x00 && !this.isAvailable) {
790            log.trace("Loco Is Available");
791            setIsAvailable(true);
792        }
793        if ((b1 & 0x01) == 0x01) {
794            log.trace("Speed Step setting 27");
795            notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_27);
796        } else if ((b1 & 0x02) == 0x02) {
797            log.trace("Speed Step setting 28");
798            notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_28);
799        } else if ((b1 & 0x04) == 0x04) {
800            log.trace("Speed Step setting 128");
801            notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_128);
802        } else {
803            log.trace("Speed Step setting 14");
804            notifyNewSpeedStepMode(SpeedStepMode.NMRA_DCC_14);
805        }
806    }
807
808    protected void notifyNewSpeedStepMode(SpeedStepMode mode) {
809        if (this.speedStepMode != mode) {
810            firePropertyChange(SPEEDSTEPS,
811                    this.speedStepMode,
812                    this.speedStepMode = mode);
813        }
814    }
815
816    /**
817     * Get Speed and Direction information.
818     * @param b2 2nd byte of message to examine
819     */
820    protected void parseSpeedAndDirection(int b2) {
821        /* the second byte indicates the speed and direction setting */
822
823        if ((b2 & 0x80) == 0x80 && !this.isForward) {
824            notifyNewDirection(true);
825        } else if ((b2 & 0x80) == 0x00 && this.isForward) {
826            notifyNewDirection(false);
827        }
828
829        if (this.speedStepMode == SpeedStepMode.NMRA_DCC_128) {
830            // We're in 128 speed step mode
831            int speedVal = b2 & 0x7f;
832            // The first speed step used is actually at 2 for 128
833            // speed step mode.
834            if (speedVal >= 1) {
835                speedVal -= 1;
836            }
837            if (java.lang.Math.abs(
838                    this.getSpeedSetting() - ((float) speedVal / (float) 126)) >= 0.0079) {
839                synchronized(this) {
840                    firePropertyChange(SPEEDSETTING, this.speedSetting,
841                            this.speedSetting = (float) speedVal / (float) 126);
842                }
843            }
844        } else if (this.speedStepMode == SpeedStepMode.NMRA_DCC_28) {
845            // We're in 28 speed step mode
846            // We have to re-arrange the bits, since bit 4 is the LSB,
847            // but other bits are in order from 0-3
848            int speedVal = ((b2 & 0x0F) << 1)
849                    + ((b2 & 0x10) >> 4);
850            // The first speed step used is actually at 4 for 28
851            // speed step mode.
852            if (speedVal >= 3) {
853                speedVal -= 3;
854            } else {
855                speedVal = 0;
856            }
857            if (java.lang.Math.abs(
858                    this.getSpeedSetting() - ((float) speedVal / (float) 28)) >= 0.035) {
859                synchronized(this) {
860                    firePropertyChange(SPEEDSETTING, this.speedSetting,
861                            this.speedSetting = (float) speedVal / (float) 28);
862                }
863            }
864        } else if (this.speedStepMode == SpeedStepMode.NMRA_DCC_27) {
865            // We're in 27 speed step mode
866            // We have to re-arrange the bits, since bit 4 is the LSB,
867            // but other bits are in order from 0-3
868            int speedVal = ((b2 & 0x0F) << 1)
869                    + ((b2 & 0x10) >> 4);
870            // The first speed step used is actually at 4 for 27
871            // speed step mode.
872            if (speedVal >= 3) {
873                speedVal -= 3;
874            } else {
875                speedVal = 0;
876            }
877            if (java.lang.Math.abs(
878                    this.getSpeedSetting() - ((float) speedVal / (float) 27)) >= 0.037) {
879                synchronized(this) {
880                    firePropertyChange(SPEEDSETTING, this.speedSetting,
881                            this.speedSetting = (float) speedVal / (float) 27);
882                }
883            }
884        } else {
885            // Assume we're in 14 speed step mode.
886            int speedVal = (b2 & 0x0F);
887            if (speedVal >= 1) {
888                speedVal -= 1;
889            }
890            if (java.lang.Math.abs(
891                    this.getSpeedSetting() - ((float) speedVal / (float) 14)) >= 0.071) {
892                synchronized(this) {
893                    firePropertyChange(SPEEDSETTING, this.speedSetting,
894                            this.speedSetting = (float) speedVal / (float) 14);
895                }
896            }
897        }
898    }
899
900    protected void notifyNewDirection(boolean forward) {
901        firePropertyChange(ISFORWARD, this.isForward, this.isForward = forward);
902        log.trace("Throttle - Changed direction to {} Locomotive: {}", forward ? "forward" : "reverse", getDccAddress());
903    }
904
905    protected void parseFunctionInformation(int b3, int b4) {
906        log.trace("Parsing Function F0-F12 status, function bytes: {} and {}",
907                b3, b4);
908        /* data byte 3 is the status of F0 F4 F3 F2 F1 */
909        updateFunction(0, (b3 & 0x10) == 0x10);
910        updateFunction(1, (b3 & 0x01) == 0x01);
911        updateFunction(2, (b3 & 0x02) == 0x02);
912        updateFunction(3, (b3 & 0x04) == 0x04);
913        updateFunction(4, (b3 & 0x08) == 0x08);
914        /* data byte 4 is the status of F12 F11 F10 F9 F8 F7 F6 F5 */
915        updateFunction(5, (b4 & 0x01) == 0x01);
916        updateFunction(6, (b4 & 0x02) == 0x02);
917        updateFunction(7, (b4 & 0x04) == 0x04);
918        updateFunction(8, (b4 & 0x08) == 0x08);
919        updateFunction(9, (b4 & 0x10) == 0x10);
920        updateFunction(10, (b4 & 0x20) == 0x20);
921        updateFunction(11, (b4 & 0x40) == 0x40);
922        updateFunction(12, (b4 & 0x80) == 0x80);
923    }
924
925    protected void parseFunctionHighInformation(int b3, int b4) {
926        log.trace("Parsing Function F13-F28 status, function bytes: {} and {}",
927                b3, b4);
928        /* data byte 3 is the status of F20 F19 F18 F17 F16 F15 F14 F13 */
929        updateFunction(13, (b3 & 0x01) == 0x01);
930        updateFunction(14, (b3 & 0x02) == 0x02);
931        updateFunction(15, (b3 & 0x04) == 0x04);
932        updateFunction(16, (b3 & 0x08) == 0x08);
933        updateFunction(17, (b3 & 0x10) == 0x10);
934        updateFunction(18, (b3 & 0x20) == 0x20);
935        updateFunction(19, (b3 & 0x40) == 0x40);
936        updateFunction(20, (b3 & 0x80) == 0x80);
937        /* data byte 4 is the status of F28 F27 F26 F25 F24 F23 F22 F21 */
938        updateFunction(21, (b4 & 0x01) == 0x01);
939        updateFunction(22, (b4 & 0x02) == 0x02);
940        updateFunction(23, (b4 & 0x04) == 0x04);
941        updateFunction(24, (b4 & 0x08) == 0x08);
942        updateFunction(25, (b4 & 0x10) == 0x10);
943        updateFunction(26, (b4 & 0x20) == 0x20);
944        updateFunction(27, (b4 & 0x40) == 0x40);
945        updateFunction(28, (b4 & 0x80) == 0x80);
946
947    }
948
949    protected void parseFunctionMomentaryInformation(int b3, int b4) {
950        log.trace("Parsing Function Momentary status, function bytes: {} and {}",
951                b3, b4);
952        /* data byte 3 is the momentary status of F0 F4 F3 F2 F1 */
953        checkForFunctionMomentaryValueChange(0, b3, 0x10, getFunctionMomentary(0));
954        checkForFunctionMomentaryValueChange(1, b3, 0x01, getFunctionMomentary(1));
955        checkForFunctionMomentaryValueChange(2, b3, 0x02, getFunctionMomentary(2));
956        checkForFunctionMomentaryValueChange(3, b3, 0x04, getFunctionMomentary(3));
957        checkForFunctionMomentaryValueChange(4, b3, 0x08, getFunctionMomentary(4));
958        /* data byte 4 is the momentary status of F12 F11 F10 F9 F8 F7 F6 F5 */
959        checkForFunctionMomentaryValueChange(5, b4, 0x01, getFunctionMomentary(5));
960        checkForFunctionMomentaryValueChange(6, b4, 0x02, getFunctionMomentary(6));
961        checkForFunctionMomentaryValueChange(7, b4, 0x04, getFunctionMomentary(7));
962        checkForFunctionMomentaryValueChange(8, b4, 0x08, getFunctionMomentary(8));
963        checkForFunctionMomentaryValueChange(9, b4, 0x10, getFunctionMomentary(9));
964        checkForFunctionMomentaryValueChange(10, b4, 0x20, getFunctionMomentary(10));
965        checkForFunctionMomentaryValueChange(11, b4, 0x40, getFunctionMomentary(11));
966        checkForFunctionMomentaryValueChange(12, b4, 0x80, getFunctionMomentary(12));
967    }
968
969    protected void parseFunctionHighMomentaryInformation(int b3, int b4) {
970        log.trace("Parsing Function F13-F28 Momentary status, function bytes: {} and {}",
971                b3, b4);
972        /* data byte 3 is the momentary status of F20 F19 F17 F16 F15 F14 F13 */
973        checkForFunctionMomentaryValueChange(13, b3, 0x01, getFunctionMomentary(13));
974        checkForFunctionMomentaryValueChange(14, b3, 0x02, getFunctionMomentary(14));
975        checkForFunctionMomentaryValueChange(15, b3, 0x04, getFunctionMomentary(15));
976        checkForFunctionMomentaryValueChange(16, b3, 0x08, getFunctionMomentary(16));
977        checkForFunctionMomentaryValueChange(17, b3, 0x10, getFunctionMomentary(17));
978        checkForFunctionMomentaryValueChange(18, b3, 0x20, getFunctionMomentary(18));
979        checkForFunctionMomentaryValueChange(19, b3, 0x40, getFunctionMomentary(19));
980        checkForFunctionMomentaryValueChange(20, b3, 0x80, getFunctionMomentary(20));
981        /* data byte 4 is the momentary status of F28 F27 F26 F25 F24 F23 F22 F21 */
982        checkForFunctionMomentaryValueChange(21, b4, 0x01, getFunctionMomentary(21));
983        checkForFunctionMomentaryValueChange(22, b4, 0x02, getFunctionMomentary(22));
984        checkForFunctionMomentaryValueChange(23, b4, 0x04, getFunctionMomentary(23));
985        checkForFunctionMomentaryValueChange(24, b4, 0x08, getFunctionMomentary(24));
986        checkForFunctionMomentaryValueChange(25, b4, 0x10, getFunctionMomentary(25));
987        checkForFunctionMomentaryValueChange(26, b4, 0x20, getFunctionMomentary(26));
988        checkForFunctionMomentaryValueChange(27, b4, 0x40, getFunctionMomentary(27));
989        checkForFunctionMomentaryValueChange(28, b4, 0x80, getFunctionMomentary(28));
990    }
991
992    protected void checkForFunctionMomentaryValueChange(int funcNum, int bytevalue, int bitmask, boolean currentValue) {
993        if ((bytevalue & bitmask) == bitmask && !currentValue) {
994            updateFunctionMomentary(funcNum, true);
995        } else if ((bytevalue & bitmask) == 0x00 && currentValue) {
996            updateFunctionMomentary(funcNum, false);
997        }
998    }
999
1000    /**
1001     * Set the internal isAvailable property.
1002     *
1003     * @param available true if available; false otherwise
1004     */
1005    protected void setIsAvailable(boolean available) {
1006        firePropertyChange("IsAvailable", this.isAvailable, this.isAvailable = available);
1007        /* if we're setting this to true, stop the timer,
1008         otherwise start the timer. */
1009        if (available) {
1010            stopStatusTimer();
1011        } else {
1012            startStatusTimer();
1013        }
1014    }
1015
1016    /**
1017     * Set up the status timer, and start it.
1018     */
1019    protected void startStatusTimer() {
1020        log.debug("Status Timer Started");
1021
1022        if (statusTask != null) {
1023            statusTask.cancel();
1024            statusTask = null;
1025        }
1026        statusTask = new java.util.TimerTask() {
1027            @Override
1028            public void run() {
1029                /* If the timer times out, just send a status
1030                 request message */
1031                sendStatusInformationRequest();
1032            }
1033        };
1034
1035        jmri.util.TimerUtil.schedule(statusTask, statTimeoutValue, statTimeoutValue);
1036    }
1037
1038    /**
1039     * Stop the Status Timer
1040     */
1041    protected void stopStatusTimer() {
1042        log.debug("Status Timer Stopped");
1043        if (statusTask != null) {
1044            try {
1045                statusTask.cancel();
1046            } catch (IllegalStateException ise) {
1047                log.debug("Timer already canceled");
1048            }
1049            statusTask = null;
1050        }
1051    }
1052
1053    @Override
1054    public LocoAddress getLocoAddress() {
1055        return new DccLocoAddress(address, XNetThrottleManager.isLongAddress(address));
1056    }
1057
1058    // A queue to hold outstanding messages
1059    protected LinkedBlockingQueue<RequestMessage> requestList;
1060
1061    /**
1062     * Send message from queue.
1063     */
1064    protected synchronized void sendQueuedMessage() {
1065
1066        RequestMessage msg;
1067        // check to see if the queue has a message in it, and if it does,
1068        // remove the first message
1069        if (!requestList.isEmpty()) {
1070            log.debug("sending message to traffic controller");
1071            // if the queue is not empty, remove the first message
1072            // from the queue, send the message, and set the state machine
1073            // to the required state.
1074            try {
1075                msg = requestList.take();
1076            } catch (java.lang.InterruptedException ie) {
1077                return; // if there was an error, exit.
1078            }
1079            requestState = msg.getState();
1080            tc.sendXNetMessage(msg.getMsg(), this);
1081        } else {
1082            log.debug("message queue empty");
1083            // if the queue is empty, set the state to idle.
1084            requestState = THROTTLEIDLE;
1085        }
1086    }
1087
1088    /**
1089     * Queue a message.
1090     * @param m message to send
1091     * @param s state
1092     */
1093    protected synchronized void queueMessage(XNetMessage m, int s) {
1094        log.debug("adding message to message queue");
1095        // put the message in the queue
1096        RequestMessage msg = new RequestMessage(m, s);
1097        try {
1098            requestList.put(msg);
1099        } catch (java.lang.InterruptedException ie) {
1100            log.trace("Interrupted while queueing message {}", msg);
1101        }
1102        // if the state is idle, trigger the message send
1103        if (requestState == THROTTLEIDLE) {
1104            sendQueuedMessage();
1105        }
1106    }
1107
1108    /**
1109     * Internal class to hold a request message, along with the associated
1110     * throttle state.
1111     */
1112    protected static class RequestMessage {
1113
1114        private final int state;
1115        private final XNetMessage msg;
1116
1117        RequestMessage(XNetMessage m, int s) {
1118            state = s;
1119            msg = m;
1120        }
1121
1122        int getState() {
1123            return state;
1124        }
1125
1126        XNetMessage getMsg() {
1127            return msg;
1128        }
1129    }
1130
1131    // register for notification
1132    private static final Logger log = LoggerFactory.getLogger(XNetThrottle.class);
1133
1134}