001package jmri.jmrix.lenz;
002
003import java.io.Serializable;
004
005import org.slf4j.Logger;
006import org.slf4j.LoggerFactory;
007import jmri.SpeedStepMode;
008
009/**
010 * Represents a single command or response on the XpressNet.
011 * <p>
012 * Content is represented with ints to avoid the problems with sign-extension
013 * that bytes have, and because a Java char is actually a variable number of
014 * bytes in Unicode.
015 *
016 * @author Bob Jacobsen Copyright (C) 2002
017 * @author Paul Bender Copyright (C) 2003-2010
018  *
019 */
020public class XNetMessage extends jmri.jmrix.AbstractMRMessage implements Serializable {
021
022    private static final String X_NET_MESSAGE_REQUEST_LI_BAUD = "XNetMessageRequestLIBaud";
023    private static final String X_NET_MESSAGE_REQUEST_SERVICE_MODE_READ_DIRECT_V_36 = "XNetMessageRequestServiceModeReadDirectV36";
024    private static final String X_NET_MESSAGE_REQUEST_SERVICE_MODE_WRITE_DIRECT_V_36 = "XNetMessageRequestServiceModeWriteDirectV36";
025    private static final String FORWARD = "Forward";
026    private static final String REVERSE = "Reverse";
027    private static final String X_NET_MESSAGE_SET_SPEED = "XNetMessageSetSpeed";
028    private static final String X_NET_MESSAGE_SET_DIRECTION = "XNetMessageSetDirection";
029    private static final String X_NET_MESSAGE_SET_FUNCTION_GROUP_X = "XNetMessageSetFunctionGroupX";
030    private static final String POWER_STATE_ON = "PowerStateOn";
031    private static final String POWER_STATE_OFF = "PowerStateOff";
032    private static final String SPEED_STEP_MODE_X = "SpeedStepModeX";
033    private static final String X_NET_MESSAGE_SET_FUNCTION_GROUP_X_MOMENTARY = "XNetMessageSetFunctionGroupXMomentary";
034    private static final String FUNCTION_CONTINUOUS = "FunctionContinuous";
035    private static final String FUNCTION_MOMENTARY = "FunctionMomentary";
036    private static int _nRetries = 5;
037
038    /* According to the specification, XpressNet has a maximum timing
039     interval of 500 milliseconds during normal communications */
040    protected static final int XNetProgrammingTimeout = 10000;
041    private static int XNetMessageTimeout = 5000;
042
043    /**
044     * Create a new object, representing a specific-length message.
045     *
046     * @param len Total bytes in message, including opcode and error-detection
047     *            byte.  Valid values are 0 to 15 (0x0 to 0xF).
048     */
049    public XNetMessage(int len) {
050        super(len);
051        if (len > 15 ) {  // only check upper bound. Lower bound checked in
052                          // super call.
053            log.error("Invalid length in ctor: {}", len);
054            throw new IllegalArgumentException("Invalid length in ctor: " + len);
055        }
056        setBinary(true);
057        setRetries(_nRetries);
058        setTimeout(XNetMessageTimeout);
059        _nDataChars = len;
060    }
061
062    /**
063     * Create a new object, that is a copy of an existing message.
064     *
065     * @param message an existing XpressNet message
066     */
067    public XNetMessage(XNetMessage message) {
068        super(message);
069        setBinary(true);
070        setRetries(_nRetries);
071        setTimeout(XNetMessageTimeout);
072    }
073
074    /**
075     * Create an XNetMessage from an XNetReply.
076     * @param message existing XNetReply.
077     */
078    public XNetMessage(XNetReply message) {
079        super(message.getNumDataElements());
080        setBinary(true);
081        setRetries(_nRetries);
082        setTimeout(XNetMessageTimeout);
083        for (int i = 0; i < message.getNumDataElements(); i++) {
084            setElement(i, message.getElement(i));
085        }
086    }
087
088    /**
089     * Create an XNetMessage from a String containing bytes.
090     * @param s string containing data bytes.
091     */
092    public XNetMessage(String s) {
093        setBinary(true);
094        setRetries(_nRetries);
095        setTimeout(XNetMessageTimeout);
096        // gather bytes in result
097        byte[] b = jmri.util.StringUtil.bytesFromHexString(s);
098        if (b.length == 0) {
099            // no such thing as a zero-length message
100            _nDataChars = 0;
101            _dataChars = null;
102            return;
103        }
104        _nDataChars = b.length;
105        _dataChars = new int[_nDataChars];
106        for (int i = 0; i < b.length; i++) {
107            setElement(i, b[i]);
108        }
109    }
110
111    // note that the opcode is part of the message, so we treat it
112    // directly
113    // WARNING: use this only with opcodes that have a variable number
114    // of arguments following included. Otherwise, just use setElement
115    @Override
116    public void setOpCode(int i) {
117        if (i > 0xF || i < 0) {
118            log.error("Opcode invalid: {}", i);
119        }
120        setElement(0, ((i * 16) & 0xF0) | ((getNumDataElements() - 2) & 0xF));
121    }
122
123    @Override
124    public int getOpCode() {
125        return (getElement(0) / 16) & 0xF;
126    }
127
128    /**
129     * Get a String representation of the op code in hex.
130     * {@inheritDoc}
131     */
132    @Override
133    public String getOpCodeHex() {
134        return "0x" + Integer.toHexString(getOpCode());
135    }
136
137    /**
138     * Check whether the message has a valid parity.
139     * @return true if parity valid, else false.
140     */
141    public boolean checkParity() {
142        int len = getNumDataElements();
143        int chksum = 0x00;  /* the seed */
144
145        int loop;
146
147        for (loop = 0; loop < len - 1; loop++) {  // calculate contents for data part
148            chksum ^= getElement(loop);
149        }
150        return ((chksum & 0xFF) == getElement(len - 1));
151    }
152
153    public void setParity() {
154        int len = getNumDataElements();
155        int chksum = 0x00;  /* the seed */
156
157        int loop;
158
159        for (loop = 0; loop < len - 1; loop++) {  // calculate contents for data part
160            chksum ^= getElement(loop);
161        }
162        setElement(len - 1, chksum & 0xFF);
163    }
164
165    /**
166     * Get an integer representation of a BCD value.
167     * @param n message element index.
168     * @return integer of BCD.
169     */
170    public Integer getElementBCD(int n) {
171        return Integer.decode(Integer.toHexString(getElement(n)));
172    }
173
174    /**
175     * Get the message length.
176     * @return message length.
177     */
178    public int length() {
179        return _nDataChars;
180    }
181
182    /**
183     * Set the default number of retries for an XpressNet message.
184     *
185     * @param t number of retries to attempt
186     */
187    public static void setXNetMessageRetries(int t) {
188        _nRetries = t;
189    }
190
191    /**
192     * Set the default timeout for an XpressNet message.
193     *
194     * @param t Timeout in milliseconds
195     */
196    public static void setXNetMessageTimeout(int t) {
197        XNetMessageTimeout = t;
198    }
199
200    /**
201     * Most messages are sent with a reply expected, but
202     * we have a few that we treat as though the reply is always
203     * a broadcast message, because the reply usually comes to us
204     * that way.
205     * {@inheritDoc}
206     */
207    @Override
208    public boolean replyExpected() {
209        return !broadcastReply;
210    }
211
212    private boolean broadcastReply = false;
213
214    /**
215     * Tell the traffic controller we expect this
216     * message to have a broadcast reply.
217     */
218    public void setBroadcastReply() {
219        broadcastReply = true;
220    }
221
222    // decode messages of a particular form
223    // create messages of a particular form
224
225    /**
226     * Encapsulate an NMRA DCC packet in an XpressNet message.
227     * <p>
228     * On Current (v3.5) Lenz command stations, the Operations Mode
229     *     Programming Request is implemented by sending a packet directly
230     *     to the rails.  This packet is not checked by the XpressNet
231     *     protocol, and is just the track packet with an added header
232     *     byte.
233     *     <p>
234     *     NOTE: Lenz does not say this will work for anything but 5
235     *     byte packets.
236     * @param packet byte array containing packet data elements.
237     * @return message to send DCC packet.
238     */
239    public static XNetMessage getNMRAXNetMsg(byte[] packet) {
240        XNetMessage msg = new XNetMessage(packet.length + 2);
241        msg.setOpCode((XNetConstants.OPS_MODE_PROG_REQ & 0xF0) >> 4);
242        msg.setElement(1, 0x30);
243        for (int i = 0; i < packet.length; i++) {
244            msg.setElement((i + 2), packet[i] & 0xff);
245        }
246        msg.setParity();
247        return (msg);
248    }
249
250    /*
251     * The next group of routines are used by Feedback and/or turnout
252     * control code.  These are used in multiple places within the code,
253     * so they appear here.
254     */
255
256    /**
257     * Generate a message to change turnout state.
258     * @param pNumber address number.
259     * @param pClose true if set turnout closed.
260     * @param pThrow true if set turnout thrown.
261     * @param pOn accessory line true for on, false off.
262     * @return message containing turnout command.
263     */
264    public static XNetMessage getTurnoutCommandMsg(int pNumber, boolean pClose,
265            boolean pThrow, boolean pOn) {
266        XNetMessage l = new XNetMessage(4);
267        l.setElement(0, XNetConstants.ACC_OPER_REQ);
268
269        // compute address byte fields
270        int hiadr = (pNumber - 1) / 4;
271        int loadr = ((pNumber - 1) - hiadr * 4) * 2;
272        // The MSB of the upper nibble is required to be set on
273        // The rest of the upper nibble should be zeros.
274        // The MSB of the lower nibble says weather or not the
275        // accessory line should be "on" or "off"
276        if (!pOn) {
277            loadr |= 0x80;
278        } else {
279            loadr |= 0x88;
280        }
281        // If we are sending a "throw" command, we set the LSB of the
282        // lower nibble on, otherwise, we leave it "off".
283        if (pThrow) {
284            loadr |= 0x01;
285        }
286
287        // we don't know how to command both states right now!
288        if (pClose && pThrow) {
289            log.error("XpressNet turnout logic can't handle both THROWN and CLOSED yet");
290        }
291        // store and send
292        l.setElement(1, hiadr);
293        l.setElement(2, loadr);
294        l.setParity(); // Set the parity bit
295
296        return l;
297    }
298
299    /**
300     * Generate a message to receive the feedback information for an upper or
301     * lower nibble of the feedback address in question.
302     * @param pNumber feedback address.
303     * @param pLowerNibble true for upper nibble, else false for lower.
304     * @return feedback request message.
305     */
306    public static XNetMessage getFeedbackRequestMsg(int pNumber,
307            boolean pLowerNibble) {
308        XNetMessage l = new XNetMessage(4);
309        l.setBroadcastReply();  // we the message reply as a broadcast message.
310        l.setElement(0, XNetConstants.ACC_INFO_REQ);
311
312        // compute address byte field
313        l.setElement(1, (pNumber - 1) / 4);
314        // The MSB of the upper nibble is required to be set on
315        // The rest of the upper nibble should be zeros.
316        // The LSB of the lower nibble says weather or not the
317        // information request is for the upper or lower nibble.
318        if (pLowerNibble) {
319            l.setElement(2, 0x80);
320        } else {
321            l.setElement(2, 0x81);
322        }
323        l.setParity(); // Set the parity bit
324        return l;
325    }
326
327    /*
328     * Next, we have some messages related to sending programming commands.
329     */
330
331    public static XNetMessage getServiceModeResultsMsg() {
332        XNetMessage m = new XNetMessage(3);
333        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
334        m.setTimeout(XNetProgrammingTimeout);
335        m.setElement(0, XNetConstants.CS_REQUEST);
336        m.setElement(1, XNetConstants.SERVICE_MODE_CSRESULT);
337        m.setParity(); // Set the parity bit
338        return m;
339    }
340
341    public static XNetMessage getExitProgModeMsg() {
342        XNetMessage m = new XNetMessage(3);
343        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
344        m.setElement(0, XNetConstants.CS_REQUEST);
345        m.setElement(1, XNetConstants.RESUME_OPS);
346        m.setParity();
347        return m;
348    }
349
350    public static XNetMessage getReadPagedCVMsg(int cv) {
351        XNetMessage m = new XNetMessage(4);
352        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
353        m.setTimeout(XNetProgrammingTimeout);
354        m.setElement(0, XNetConstants.PROG_READ_REQUEST);
355        m.setElement(1, XNetConstants.PROG_READ_MODE_PAGED);
356        m.setElement(2, (0xff & cv));
357        m.setParity(); // Set the parity bit
358        return m;
359    }
360
361    public static XNetMessage getReadDirectCVMsg(int cv) {
362        XNetMessage m = new XNetMessage(4);
363        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
364        m.setTimeout(XNetProgrammingTimeout);
365        m.setElement(0, XNetConstants.PROG_READ_REQUEST);
366        if (cv < 0x0100) /* Use the version 3.5 command for CVs <= 256 */ {
367            m.setElement(1, XNetConstants.PROG_READ_MODE_CV);
368        } else if (cv == 0x0400) /* For CV1024, we need to send the version 3.6
369         command for CVs 1 to 256, sending a 0 for the
370         CV */ {
371            m.setElement(1, XNetConstants.PROG_READ_MODE_CV_V36);
372        } else /* and the version 3.6 command for CVs > 256 */ {
373            m.setElement(1, XNetConstants.PROG_READ_MODE_CV_V36 | ((cv & 0x0300) >> 8));
374        }
375        m.setElement(2, (0xff & cv));
376        m.setParity(); // Set the parity bit
377        return m;
378    }
379
380    public static XNetMessage getWritePagedCVMsg(int cv, int val) {
381        XNetMessage m = new XNetMessage(5);
382        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
383        m.setTimeout(XNetProgrammingTimeout);
384        m.setElement(0, XNetConstants.PROG_WRITE_REQUEST);
385        m.setElement(1, XNetConstants.PROG_WRITE_MODE_PAGED);
386        m.setElement(2, (0xff & cv));
387        m.setElement(3, val);
388        m.setParity(); // Set the parity bit
389        return m;
390    }
391
392    public static XNetMessage getWriteDirectCVMsg(int cv, int val) {
393        XNetMessage m = new XNetMessage(5);
394        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
395        m.setTimeout(XNetProgrammingTimeout);
396        m.setElement(0, XNetConstants.PROG_WRITE_REQUEST);
397        if (cv < 0x0100) /* Use the version 3.5 command for CVs <= 256 */ {
398            m.setElement(1, XNetConstants.PROG_WRITE_MODE_CV);
399        } else if (cv == 0x0400) /* For CV1024, we need to send the version 3.6
400         command for CVs 1 to 256, sending a 0 for the
401         CV */ {
402            m.setElement(1, XNetConstants.PROG_WRITE_MODE_CV_V36);
403        } else /* and the version 3.6 command for CVs > 256 */ {
404            m.setElement(1, XNetConstants.PROG_WRITE_MODE_CV_V36 | ((cv & 0x0300) >> 8));
405        }
406        m.setElement(2, (0xff & cv));
407        m.setElement(3, val);
408        m.setParity(); // Set the parity bit
409        return m;
410    }
411
412    public static XNetMessage getReadRegisterMsg(int reg) {
413        if (reg > 8) {
414            log.error("register number too large: {}",reg);
415        }
416        XNetMessage m = new XNetMessage(4);
417        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
418        m.setTimeout(XNetProgrammingTimeout);
419        m.setElement(0, XNetConstants.PROG_READ_REQUEST);
420        m.setElement(1, XNetConstants.PROG_READ_MODE_REGISTER);
421        m.setElement(2, (0x0f & reg));
422        m.setParity(); // Set the parity bit
423        return m;
424    }
425
426    public static XNetMessage getWriteRegisterMsg(int reg, int val) {
427        if (reg > 8) {
428            log.error("register number too large: {}",reg);
429        }
430        XNetMessage m = new XNetMessage(5);
431        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
432        m.setTimeout(XNetProgrammingTimeout);
433        m.setElement(0, XNetConstants.PROG_WRITE_REQUEST);
434        m.setElement(1, XNetConstants.PROG_WRITE_MODE_REGISTER);
435        m.setElement(2, (0x0f & reg));
436        m.setElement(3, val);
437        m.setParity(); // Set the parity bit
438        return m;
439    }
440
441    public static XNetMessage getWriteOpsModeCVMsg(int AH, int AL, int cv, int val) {
442        XNetMessage m = new XNetMessage(8);
443        m.setElement(0, XNetConstants.OPS_MODE_PROG_REQ);
444        m.setElement(1, XNetConstants.OPS_MODE_PROG_WRITE_REQ);
445        m.setElement(2, AH);
446        m.setElement(3, AL);
447        /* Element 4 is 0xEC + the upper two  bits of the 10 bit CV address.
448         NOTE: This is the track packet CV, not the human readable CV, so
449         its value actually is one less than what we normally think of it as.*/
450        int temp = (cv - 1) & 0x0300;
451        temp = temp / 0x00FF;
452        m.setElement(4, 0xEC + temp);
453        /* Element 5 is the lower 8 bits of the cv */
454        m.setElement(5, ((0x00ff & cv) - 1));
455        m.setElement(6, val);
456        m.setParity(); // Set the parity bit
457        return m;
458    }
459
460    public static XNetMessage getVerifyOpsModeCVMsg(int AH, int AL, int cv, int val) {
461        XNetMessage m = new XNetMessage(8);
462        m.setElement(0, XNetConstants.OPS_MODE_PROG_REQ);
463        m.setElement(1, XNetConstants.OPS_MODE_PROG_WRITE_REQ);
464        m.setElement(2, AH);
465        m.setElement(3, AL);
466        /* Element 4 is 0xE4 + the upper two  bits of the 10 bit CV address.
467         NOTE: This is the track packet CV, not the human readable CV, so
468         its value actually is one less than what we normally think of it as.*/
469        int temp = (cv - 1) & 0x0300;
470        temp = temp / 0x00FF;
471        m.setElement(4, 0xE4 + temp);
472        /* Element 5 is the lower 8 bits of the cv */
473        m.setElement(5, ((0x00ff & cv) - 1));
474        m.setElement(6, val);
475        m.setParity(); // Set the parity bit
476        return m;
477    }
478
479    public static XNetMessage getBitWriteOpsModeCVMsg(int AH, int AL, int cv, int bit, boolean value) {
480        XNetMessage m = new XNetMessage(8);
481        m.setElement(0, XNetConstants.OPS_MODE_PROG_REQ);
482        m.setElement(1, XNetConstants.OPS_MODE_PROG_WRITE_REQ);
483        m.setElement(2, AH);
484        m.setElement(3, AL);
485        /* Element 4 is 0xE8 + the upper two  bits of the 10 bit CV address.
486         NOTE: This is the track packet CV, not the human readable CV, so
487         its value actually is one less than what we normally think of it as.*/
488        int temp = (cv - 1) & 0x0300;
489        temp = temp / 0x00FF;
490        m.setElement(4, 0xE8 + temp);
491        /* Element 5 is the lower 8 bits of the cv */
492        m.setElement(5, ((0x00ff & cv) - 1));
493        /* Since this is a bit write, Element 6 is:
494         0xE0 +
495         bit 3 is the value to write
496         bit's 0-2 are the location of the bit we are changing */
497        if (value) {
498            m.setElement(6, ((0xe8) | (bit & 0xff)));
499        } else // value == false
500        {
501            m.setElement(6, ((0xe0) | (bit & 0xff)));
502        }
503        m.setParity(); // Set the parity bit
504        return m;
505    }
506
507    public static XNetMessage getBitVerifyOpsModeCVMsg(int AH, int AL, int cv, int bit, boolean value) {
508        XNetMessage m = new XNetMessage(8);
509        m.setElement(0, XNetConstants.OPS_MODE_PROG_REQ);
510        m.setElement(1, XNetConstants.OPS_MODE_PROG_WRITE_REQ);
511        m.setElement(2, AH);
512        m.setElement(3, AL);
513        /* Element 4 is 0xE8 + the upper two  bits of the 10 bit CV address.
514         NOTE: This is the track packet CV, not the human readable CV, so
515         its value actually is one less than what we normally think of it as.*/
516        int temp = (cv - 1) & 0x0300;
517        temp = temp / 0x00FF;
518        m.setElement(4, 0xE8 + temp);
519        /* Element 5 is the lower 8 bits of the cv */
520        m.setElement(5, ((0x00ff & cv) - 1));
521        /* Since this is a bit verify, Element 6 is:
522         0xF0 +
523         bit 3 is the value to write
524         bit's 0-2 are the location of the bit we are changing */
525        if (value) {
526            m.setElement(6, ((0xf8) | (bit & 0xff)));
527        } else // value == false
528        {
529            m.setElement(6, ((0xf0) | (bit & 0xff)));
530        }
531        m.setParity(); // Set the parity bit
532        return m;
533    }
534
535    /*
536     * Next, we have routines to generate XpressNet Messages for building
537     * and tearing down a consist or a double header.
538     */
539
540    /**
541     * Build a Double Header.
542     *
543     * @param address1 the first address in the consist
544     * @param address2 the second address in the consist.
545     * @return message to build double header.
546     */
547    public static XNetMessage getBuildDoubleHeaderMsg(int address1, int address2) {
548        XNetMessage msg = new XNetMessage(7);
549        msg.setElement(0, XNetConstants.LOCO_DOUBLEHEAD);
550        msg.setElement(1, XNetConstants.LOCO_DOUBLEHEAD_BYTE2);
551        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address1));
552        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address1));
553        msg.setElement(4, LenzCommandStation.getDCCAddressHigh(address2));
554        msg.setElement(5, LenzCommandStation.getDCCAddressLow(address2));
555        msg.setParity();
556        return (msg);
557    }
558
559    /**
560     * Dissolve a Double Header.
561     *
562     * @param address one of the two addresses in the Double Header
563     * @return message to dissolve a double header.
564     */
565    public static XNetMessage getDisolveDoubleHeaderMsg(int address) {
566        // All we have to do is call getBuildDoubleHeaderMsg with the
567        // second address as a zero
568        return (getBuildDoubleHeaderMsg(address, 0));
569    }
570
571    /**
572     * Add a Single address to a specified Advanced consist.
573     *
574     * @param consist the consist address (1-99)
575     * @param address the locomotive address to add.
576     * @param isNormalDir tells us if the locomotive is going forward when
577     * the consist is going forward.
578     * @return message to add address to consist.
579     */
580    public static XNetMessage getAddLocoToConsistMsg(int consist, int address,
581            boolean isNormalDir) {
582        XNetMessage msg = new XNetMessage(6);
583        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
584        if (isNormalDir) {
585            msg.setElement(1, XNetConstants.LOCO_ADD_MULTI_UNIT_REQ);
586        } else {
587            msg.setElement(1, XNetConstants.LOCO_ADD_MULTI_UNIT_REQ | 0x01);
588        }
589        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
590        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
591        msg.setElement(4, consist);
592        msg.setParity();
593        return (msg);
594    }
595
596    /**
597     * Remove a Single address to a specified Advanced consist.
598     *
599     * @param consist the consist address (1-99)
600     * @param address the locomotive address to remove
601     * @return message to remove single address from consist.
602     */
603    public static XNetMessage getRemoveLocoFromConsistMsg(int consist, int address) {
604        XNetMessage msg = new XNetMessage(6);
605        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
606        msg.setElement(1, XNetConstants.LOCO_REM_MULTI_UNIT_REQ);
607        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
608        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
609        msg.setElement(4, consist);
610        msg.setParity();
611        return (msg);
612    }
613
614
615    /*
616     * Next, we have routines to generate XpressNet Messages for search
617     * and manipulation of the Command Station Database
618     */
619
620    /**
621     * Given a locomotive address, search the database for the next
622     * member.
623     * (if the Address is zero start at the beginning of the database).
624     *
625     * @param address is the locomotive address
626     * @param searchForward indicates to search the database Forward if
627     * true, or backwards if False
628     * @return message to request next address.
629     */
630    public static XNetMessage getNextAddressOnStackMsg(int address, boolean searchForward) {
631        XNetMessage msg = new XNetMessage(5);
632        msg.setElement(0, XNetConstants.LOCO_STATUS_REQ);
633        if (searchForward) {
634            msg.setElement(1, XNetConstants.LOCO_STACK_SEARCH_FWD);
635        } else {
636            msg.setElement(1, XNetConstants.LOCO_STACK_SEARCH_BKWD);
637        }
638        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
639        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
640        msg.setParity();
641        return (msg);
642    }
643
644    /**
645     * Given a consist address, search the database for the next Consist
646     * address.
647     *
648     * @param address is the consist address (in the range 1-99).
649     * If the Address is zero start at the beginning of the database.
650     * @param searchForward indicates to search the database Forward if
651     * true, or backwards if false
652     * @return message to get next consist address.
653     */
654    public static XNetMessage getDBSearchMsgConsistAddress(int address, boolean searchForward) {
655        XNetMessage msg = new XNetMessage(4);
656        msg.setElement(0, XNetConstants.CS_MULTI_UNIT_REQ);
657        if (searchForward) {
658            msg.setElement(1, XNetConstants.CS_MULTI_UNIT_REQ_FWD);
659        } else {
660            msg.setElement(1, XNetConstants.CS_MULTI_UNIT_REQ_BKWD);
661        }
662        msg.setElement(2, address);
663        msg.setParity();
664        return (msg);
665    }
666
667    /**
668     * Given a consist and a locomotive address, search the database for
669     * the next Locomotive in the consist.
670     *
671     * @param consist the consist address (1-99).
672     * If the Consist Address is zero start at the begining of the database
673     * @param address the locomotive address.
674     * If the Address is zero start at the begining of the consist
675     * @param searchForward indicates to search the database Forward if
676     * true, or backwards if False
677     * @return  message to request next loco in consist.
678     */
679    public static XNetMessage getDBSearchMsgNextMULoco(int consist, int address, boolean searchForward) {
680        XNetMessage msg = new XNetMessage(6);
681        msg.setElement(0, XNetConstants.LOCO_IN_MULTI_UNIT_SEARCH_REQ);
682        if (searchForward) {
683            msg.setElement(1, XNetConstants.LOCO_IN_MULTI_UNIT_REQ_FORWARD);
684        } else {
685            msg.setElement(1, XNetConstants.LOCO_IN_MULTI_UNIT_REQ_BACKWARD);
686        }
687        msg.setElement(2, consist);
688        msg.setElement(3, LenzCommandStation.getDCCAddressHigh(address));
689        msg.setElement(4, LenzCommandStation.getDCCAddressLow(address));
690        msg.setParity();
691        return (msg);
692    }
693
694    /**
695     * Given a locomotive address, delete it from the database .
696     *
697     * @param address the locomotive address
698     * @return message to delete loco address from stack.
699     */
700    public static XNetMessage getDeleteAddressOnStackMsg(int address) {
701        XNetMessage msg = new XNetMessage(5);
702        msg.setElement(0, XNetConstants.LOCO_STATUS_REQ);
703        msg.setElement(1, XNetConstants.LOCO_STACK_DELETE);
704        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
705        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
706        msg.setParity();
707        return (msg);
708    }
709
710    /**
711     * Given a locomotive address, request its status .
712     *
713     * @param address the locomotive address
714     * @return message to request loco status.
715     */
716    public static XNetMessage getLocomotiveInfoRequestMsg(int address) {
717        XNetMessage msg = new XNetMessage(5);
718        msg.setElement(0, XNetConstants.LOCO_STATUS_REQ);
719        msg.setElement(1, XNetConstants.LOCO_INFO_REQ_V3);
720        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
721        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
722        msg.setParity();
723        return (msg);
724    }
725
726    /**
727     * Given a locomotive address, request the function state (momentary status).
728     *
729     * @param address the locomotive address
730     * @return momentary function state request request.
731     */
732    public static XNetMessage getLocomotiveFunctionStatusMsg(int address) {
733        XNetMessage msg = new XNetMessage(5);
734        msg.setElement(0, XNetConstants.LOCO_STATUS_REQ);
735        msg.setElement(1, XNetConstants.LOCO_INFO_REQ_FUNC);
736        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
737        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
738        msg.setParity();
739        return (msg);
740    }
741
742    /**
743     * Given a locomotive address, request the function on/off state
744     * for functions 13-28
745     *
746     * @param address the locomotive address
747     * @return function state request request f13-f28.
748     */
749    public static XNetMessage getLocomotiveFunctionHighOnStatusMsg(int address) {
750        XNetMessage msg = new XNetMessage(5);
751        msg.setElement(0, XNetConstants.LOCO_STATUS_REQ);
752        msg.setElement(1, XNetConstants.LOCO_INFO_REQ_FUNC_HI_ON);
753        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
754        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
755        msg.setParity();
756        return (msg);
757    }
758
759    /**
760     * Given a locomotive address, request the function state (momentary status)
761     * for high functions (functions 13-28).
762     *
763     * @param address the locomotive address
764     * @return momentary function state request request f13-f28.
765     */
766    public static XNetMessage getLocomotiveFunctionHighMomStatusMsg(int address) {
767        XNetMessage msg = new XNetMessage(5);
768        msg.setElement(0, XNetConstants.LOCO_STATUS_REQ);
769        msg.setElement(1, XNetConstants.LOCO_INFO_REQ_FUNC_HI_MOM);
770        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
771        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
772        msg.setParity();
773        return (msg);
774    }
775
776    /*
777     * Generate an emergency stop for the specified address.
778     *
779     * @param address the locomotive address
780     */
781    public static XNetMessage getAddressedEmergencyStop(int address) {
782        XNetMessage msg = new XNetMessage(4);
783        msg.setElement(0, XNetConstants.EMERGENCY_STOP);
784        msg.setElement(1, LenzCommandStation.getDCCAddressHigh(address));
785        // set to the upper
786        // byte of the  DCC address
787        msg.setElement(2, LenzCommandStation.getDCCAddressLow(address));
788        // set to the lower byte
789        //of the DCC address
790        msg.setParity(); // Set the parity bit
791        return msg;
792    }
793
794    /**
795     * Generate a Speed and Direction Request message.
796     *
797     * @param address the locomotive address
798     * @param speedStepMode the speedstep mode see @jmri.DccThrottle
799     *                       for possible values.
800     * @param speed a normalized speed value (a floating point number between 0
801     *              and 1).  A negative value indicates emergency stop.
802     * @param isForward true for forward, false for reverse.
803     * @return set speed and direction message.
804     */
805    public static XNetMessage getSpeedAndDirectionMsg(int address,
806            SpeedStepMode speedStepMode,
807            float speed,
808            boolean isForward) {
809        XNetMessage msg = new XNetMessage(6);
810        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
811        int element4value = 0;   /* this is for holding the speed and
812         direction setting */
813
814        if (speedStepMode == SpeedStepMode.NMRA_DCC_128) {
815            // We're in 128 speed step mode
816            msg.setElement(1, XNetConstants.LOCO_SPEED_128);
817            // Now, we need to figure out what to send in element 4
818            // Remember, the speed steps are identified as 0-127 (in
819            // 128 step mode), not 1-128.
820            int speedVal = java.lang.Math.round(speed * 126);
821            // speed step 1 is reserved to indicate emergency stop,
822            // so we need to step over speed step 1
823            if (speedVal >= 1) {
824                element4value = speedVal + 1;
825            }
826        } else if (speedStepMode == SpeedStepMode.NMRA_DCC_28) {
827            // We're in 28 speed step mode
828            msg.setElement(1, XNetConstants.LOCO_SPEED_28);
829            // Now, we need to figure out what to send in element 4
830            int speedVal = java.lang.Math.round(speed * 28);
831            // The first speed step used is actually at 4 for 28
832            // speed step mode.
833            if (speedVal >= 1) {
834                speedVal += 3;
835            }
836            // We have to re-arange the bits, since bit 4 is the LSB,
837            // but other bits are in order from 0-3
838            element4value = ((speedVal & 0x1e) >> 1)
839                    + ((speedVal & 0x01) << 4);
840        } else if (speedStepMode == SpeedStepMode.NMRA_DCC_27) {
841            // We're in 27 speed step mode
842            msg.setElement(1, XNetConstants.LOCO_SPEED_27);
843            // Now, we need to figure out what to send in element 4
844            int speedVal = java.lang.Math.round(speed * 27);
845            // The first speed step used is actually at 4 for 27
846            // speed step mode.
847            if (speedVal >= 1) {
848                speedVal += 3;
849            }
850            // We have to re-arange the bits, since bit 4 is the LSB,
851            // but other bits are in order from 0-3
852            element4value = ((speedVal & 0x1e) >> 1)
853                    + ((speedVal & 0x01) << 4);
854        } else {
855            // We're in 14 speed step mode
856            msg.setElement(1, XNetConstants.LOCO_SPEED_14);
857            // Now, we need to figure out what to send in element 4
858            element4value = (int) (speed * 14);
859            int speedVal = java.lang.Math.round(speed * 14);
860            // The first speed step used is actually at 2 for 14
861            // speed step mode.
862            if (speedVal >= 1) {
863                element4value += 1;
864            }
865        }
866        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
867        // set to the upper byte of the  DCC address
868        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
869        // set to the lower byte
870        //of the DCC address
871        if (isForward) {
872            /* the direction bit is always the most significant bit */
873            element4value += 128;
874        }
875        msg.setElement(4, element4value);
876        msg.setParity(); // Set the parity bit
877        return msg;
878    }
879
880    /**
881     * Generate a Function Group One Operation Request message.
882     *
883     * @param address the locomotive address
884     * @param f0 is true if f0 is on, false if f0 is off
885     * @param f1 is true if f1 is on, false if f1 is off
886     * @param f2 is true if f2 is on, false if f2 is off
887     * @param f3 is true if f3 is on, false if f3 is off
888     * @param f4 is true if f4 is on, false if f4 is off
889     * @return set function group 1 message.
890     */
891    public static XNetMessage getFunctionGroup1OpsMsg(int address,
892            boolean f0,
893            boolean f1,
894            boolean f2,
895            boolean f3,
896            boolean f4) {
897        XNetMessage msg = new XNetMessage(6);
898        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
899        msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP1);
900        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
901        // set to the upper byte of the  DCC address
902        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
903        // set to the lower byte of the DCC address
904        // Now, we need to figure out what to send in element 3
905        int element4value = 0;
906        if (f0) {
907            element4value += 16;
908        }
909        if (f1) {
910            element4value += 1;
911        }
912        if (f2) {
913            element4value += 2;
914        }
915        if (f3) {
916            element4value += 4;
917        }
918        if (f4) {
919            element4value += 8;
920        }
921        msg.setElement(4, element4value);
922        msg.setParity(); // Set the parity bit
923        return msg;
924    }
925
926    /**
927     * Generate a Function Group One Set Momentary Functions message.
928     *
929     * @param address the locomotive address
930     * @param f0 is true if f0 is momentary
931     * @param f1 is true if f1 is momentary
932     * @param f2 is true if f2 is momentary
933     * @param f3 is true if f3 is momentary
934     * @param f4 is true if f4 is momentary
935     * @return set momentary function group 1 message.
936     */
937    public static XNetMessage getFunctionGroup1SetMomMsg(int address,
938            boolean f0,
939            boolean f1,
940            boolean f2,
941            boolean f3,
942            boolean f4) {
943        XNetMessage msg = new XNetMessage(6);
944        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
945        msg.setElement(1, XNetConstants.LOCO_SET_FUNC_Group1);
946        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
947        // set to the upper byte of the  DCC address
948        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
949        // set to the lower byte of the DCC address
950        // Now, we need to figure out what to send in element 3
951        int element4value = 0;
952        if (f0) {
953            element4value += 16;
954        }
955        if (f1) {
956            element4value += 1;
957        }
958        if (f2) {
959            element4value += 2;
960        }
961        if (f3) {
962            element4value += 4;
963        }
964        if (f4) {
965            element4value += 8;
966        }
967        msg.setElement(4, element4value);
968        msg.setParity(); // Set the parity bit
969        return msg;
970    }
971
972    /**
973     * Generate a Function Group Two Operation Request message.
974     *
975     * @param address the locomotive address
976     * @param f5 is true if f5 is on, false if f5 is off
977     * @param f6 is true if f6 is on, false if f6 is off
978     * @param f7 is true if f7 is on, false if f7 is off
979     * @param f8 is true if f8 is on, false if f8 is off
980     * @return set function group 2 message.
981     */
982    public static XNetMessage getFunctionGroup2OpsMsg(int address,
983            boolean f5,
984            boolean f6,
985            boolean f7,
986            boolean f8) {
987        XNetMessage msg = new XNetMessage(6);
988        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
989        msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP2);
990        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
991        // set to the upper byte of the DCC address
992        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
993        // set to the lower byte of the DCC address
994        // Now, we need to figure out what to send in element 3
995        int element4value = 0;
996        if (f5) {
997            element4value += 1;
998        }
999        if (f6) {
1000            element4value += 2;
1001        }
1002        if (f7) {
1003            element4value += 4;
1004        }
1005        if (f8) {
1006            element4value += 8;
1007        }
1008        msg.setElement(4, element4value);
1009        msg.setParity(); // Set the parity bit
1010        return msg;
1011    }
1012
1013    /**
1014     * Generate a Function Group Two Set Momentary Functions message.
1015     *
1016     * @param address the locomotive address
1017     * @param f5 is true if f5 is momentary
1018     * @param f6 is true if f6 is momentary
1019     * @param f7 is true if f7 is momentary
1020     * @param f8 is true if f8 is momentary
1021     * @return set momentary function group 2 message.
1022     */
1023    public static XNetMessage getFunctionGroup2SetMomMsg(int address,
1024            boolean f5,
1025            boolean f6,
1026            boolean f7,
1027            boolean f8) {
1028        XNetMessage msg = new XNetMessage(6);
1029        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
1030        msg.setElement(1, XNetConstants.LOCO_SET_FUNC_Group2);
1031        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
1032        // set to the upper byte of the  DCC address
1033        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
1034        // set to the lower byte of the DCC address
1035        // Now, we need to figure out what to send in element 3
1036        int element4value = 0;
1037        if (f5) {
1038            element4value += 1;
1039        }
1040        if (f6) {
1041            element4value += 2;
1042        }
1043        if (f7) {
1044            element4value += 4;
1045        }
1046        if (f8) {
1047            element4value += 8;
1048        }
1049        msg.setElement(4, element4value);
1050        msg.setParity(); // Set the parity bit
1051        return msg;
1052    }
1053
1054    /**
1055     * Generate a Function Group Three Operation Request message.
1056     *
1057     * @param address the locomotive address
1058     * @param f9 is true if f9 is on, false if f9 is off
1059     * @param f10 is true if f10 is on, false if f10 is off
1060     * @param f11 is true if f11 is on, false if f11 is off
1061     * @param f12 is true if f12 is on, false if f12 is off
1062     * @return set function group 3 message.
1063     */
1064    public static XNetMessage getFunctionGroup3OpsMsg(int address,
1065            boolean f9,
1066            boolean f10,
1067            boolean f11,
1068            boolean f12) {
1069        XNetMessage msg = new XNetMessage(6);
1070        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
1071        msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP3);
1072        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
1073        // set to the upper byte of the  DCC address
1074        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
1075        // set to the lower byte of the DCC address
1076        // Now, we need to figure out what to send in element 3
1077        int element4value = 0;
1078        if (f9) {
1079            element4value += 1;
1080        }
1081        if (f10) {
1082            element4value += 2;
1083        }
1084        if (f11) {
1085            element4value += 4;
1086        }
1087        if (f12) {
1088            element4value += 8;
1089        }
1090        msg.setElement(4, element4value);
1091        msg.setParity(); // Set the parity bit
1092        return msg;
1093    }
1094
1095    /**
1096     * Generate a Function Group Three Set Momentary Functions message.
1097     *
1098     * @param address the locomotive address
1099     * @param f9 is true if f9 is momentary
1100     * @param f10 is true if f10 is momentary
1101     * @param f11 is true if f11 is momentary
1102     * @param f12 is true if f12 is momentary
1103     * @return set momentary function group 3 message.
1104     */
1105    public static XNetMessage getFunctionGroup3SetMomMsg(int address,
1106            boolean f9,
1107            boolean f10,
1108            boolean f11,
1109            boolean f12) {
1110        XNetMessage msg = new XNetMessage(6);
1111        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
1112        msg.setElement(1, XNetConstants.LOCO_SET_FUNC_Group3);
1113        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
1114        // set to the upper byte of the  DCC address
1115        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
1116        // set to the lower byte of the DCC address
1117        // Now, we need to figure out what to send in element 3
1118        int element4value = 0;
1119        if (f9) {
1120            element4value += 1;
1121        }
1122        if (f10) {
1123            element4value += 2;
1124        }
1125        if (f11) {
1126            element4value += 4;
1127        }
1128        if (f12) {
1129            element4value += 8;
1130        }
1131        msg.setElement(4, element4value);
1132        msg.setParity(); // Set the parity bit
1133        return msg;
1134    }
1135
1136    /**
1137     * Generate a Function Group Four Operation Request message.
1138     *
1139     * @param address the locomotive address
1140     * @param f13 is true if f13 is on, false if f13 is off
1141     * @param f14 is true if f14 is on, false if f14 is off
1142     * @param f15 is true if f15 is on, false if f15 is off
1143     * @param f16 is true if f18 is on, false if f16 is off
1144     * @param f17 is true if f17 is on, false if f17 is off
1145     * @param f18 is true if f18 is on, false if f18 is off
1146     * @param f19 is true if f19 is on, false if f19 is off
1147     * @param f20 is true if f20 is on, false if f20 is off
1148     * @return set function group 4 message.
1149     */
1150    public static XNetMessage getFunctionGroup4OpsMsg(int address,
1151            boolean f13,
1152            boolean f14,
1153            boolean f15,
1154            boolean f16,
1155            boolean f17,
1156            boolean f18,
1157            boolean f19,
1158            boolean f20) {
1159        XNetMessage msg = new XNetMessage(6);
1160        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
1161        msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP4);
1162        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
1163        // set to the upper byte of the  DCC address
1164        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
1165        // set to the lower byte of the DCC address
1166        // Now, we need to figure out what to send in element 3
1167        int element4value = 0;
1168        if (f13) {
1169            element4value += 1;
1170        }
1171        if (f14) {
1172            element4value += 2;
1173        }
1174        if (f15) {
1175            element4value += 4;
1176        }
1177        if (f16) {
1178            element4value += 8;
1179        }
1180        if (f17) {
1181            element4value += 16;
1182        }
1183        if (f18) {
1184            element4value += 32;
1185        }
1186        if (f19) {
1187            element4value += 64;
1188        }
1189        if (f20) {
1190            element4value += 128;
1191        }
1192        msg.setElement(4, element4value);
1193        msg.setParity(); // Set the parity bit
1194        return msg;
1195    }
1196
1197    /**
1198     * Generate a Function Group Four Set Momentary Function message.
1199     *
1200     * @param address the locomotive address
1201     * @param f13 is true if f13 is Momentary
1202     * @param f14 is true if f14 is Momentary
1203     * @param f15 is true if f15 is Momentary
1204     * @param f16 is true if f18 is Momentary
1205     * @param f17 is true if f17 is Momentary
1206     * @param f18 is true if f18 is Momentary
1207     * @param f19 is true if f19 is Momentary
1208     * @param f20 is true if f20 is Momentary
1209     * @return set momentary function group 4 message.
1210     */
1211    public static XNetMessage getFunctionGroup4SetMomMsg(int address,
1212            boolean f13,
1213            boolean f14,
1214            boolean f15,
1215            boolean f16,
1216            boolean f17,
1217            boolean f18,
1218            boolean f19,
1219            boolean f20) {
1220        XNetMessage msg = new XNetMessage(6);
1221        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
1222        msg.setElement(1, XNetConstants.LOCO_SET_FUNC_Group4);
1223        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
1224        // set to the upper byte of the  DCC address
1225        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
1226        // set to the lower byte of the DCC address
1227        // Now, we need to figure out what to send in element 3
1228        int element4value = 0;
1229        if (f13) {
1230            element4value += 1;
1231        }
1232        if (f14) {
1233            element4value += 2;
1234        }
1235        if (f15) {
1236            element4value += 4;
1237        }
1238        if (f16) {
1239            element4value += 8;
1240        }
1241        if (f17) {
1242            element4value += 16;
1243        }
1244        if (f18) {
1245            element4value += 32;
1246        }
1247        if (f19) {
1248            element4value += 64;
1249        }
1250        if (f20) {
1251            element4value += 128;
1252        }
1253        msg.setElement(4, element4value);
1254        msg.setParity(); // Set the parity bit
1255        return msg;
1256    }
1257
1258    /**
1259     * Generate a Function Group Five Operation Request message.
1260     *
1261     * @param address the locomotive address
1262     * @param f21 is true if f21 is on, false if f21 is off
1263     * @param f22 is true if f22 is on, false if f22 is off
1264     * @param f23 is true if f23 is on, false if f23 is off
1265     * @param f24 is true if f24 is on, false if f24 is off
1266     * @param f25 is true if f25 is on, false if f25 is off
1267     * @param f26 is true if f26 is on, false if f26 is off
1268     * @param f27 is true if f27 is on, false if f27 is off
1269     * @param f28 is true if f28 is on, false if f28 is off
1270     * @return set function group 5 message.
1271     */
1272    public static XNetMessage getFunctionGroup5OpsMsg(int address,
1273            boolean f21,
1274            boolean f22,
1275            boolean f23,
1276            boolean f24,
1277            boolean f25,
1278            boolean f26,
1279            boolean f27,
1280            boolean f28) {
1281        XNetMessage msg = new XNetMessage(6);
1282        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
1283        msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP5);
1284        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
1285        // set to the upper byte of the  DCC address
1286        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
1287        // set to the lower byte of the DCC address
1288        // Now, we need to figure out what to send in element 3
1289        int element4value = 0;
1290        if (f21) {
1291            element4value += 1;
1292        }
1293        if (f22) {
1294            element4value += 2;
1295        }
1296        if (f23) {
1297            element4value += 4;
1298        }
1299        if (f24) {
1300            element4value += 8;
1301        }
1302        if (f25) {
1303            element4value += 16;
1304        }
1305        if (f26) {
1306            element4value += 32;
1307        }
1308        if (f27) {
1309            element4value += 64;
1310        }
1311        if (f28) {
1312            element4value += 128;
1313        }
1314        msg.setElement(4, element4value);
1315        msg.setParity(); // Set the parity bit
1316        return msg;
1317    }
1318
1319    /**
1320     * Generate a Function Group Five Set Momentary Function message.
1321     *
1322     * @param address the locomotive address
1323     * @param f21 is true if f21 is momentary
1324     * @param f22 is true if f22 is momentary
1325     * @param f23 is true if f23 is momentary
1326     * @param f24 is true if f24 is momentary
1327     * @param f25 is true if f25 is momentary
1328     * @param f26 is true if f26 is momentary
1329     * @param f27 is true if f27 is momentary
1330     * @param f28 is true if f28 is momentary
1331     * @return set momentary function group 5 message.
1332     */
1333    public static XNetMessage getFunctionGroup5SetMomMsg(int address,
1334            boolean f21,
1335            boolean f22,
1336            boolean f23,
1337            boolean f24,
1338            boolean f25,
1339            boolean f26,
1340            boolean f27,
1341            boolean f28) {
1342        XNetMessage msg = new XNetMessage(6);
1343        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
1344        msg.setElement(1, XNetConstants.LOCO_SET_FUNC_Group5);
1345        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
1346        // set to the upper byte of the  DCC address
1347        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
1348        // set to the lower byte of the DCC address
1349        // Now, we need to figure out what to send in element 3
1350        int element4value = 0;
1351        if (f21) {
1352            element4value += 1;
1353        }
1354        if (f22) {
1355            element4value += 2;
1356        }
1357        if (f23) {
1358            element4value += 4;
1359        }
1360        if (f24) {
1361            element4value += 8;
1362        }
1363        if (f25) {
1364            element4value += 16;
1365        }
1366        if (f26) {
1367            element4value += 32;
1368        }
1369        if (f27) {
1370            element4value += 64;
1371        }
1372        if (f28) {
1373            element4value += 128;
1374        }
1375        msg.setElement(4, element4value);
1376        msg.setParity(); // Set the parity bit
1377        return msg;
1378    }
1379
1380    /**
1381     * Build a Resume operations Message.
1382     * @return resume message.
1383     */
1384    public static XNetMessage getResumeOperationsMsg() {
1385        XNetMessage msg = new XNetMessage(3);
1386        msg.setElement(0, XNetConstants.CS_REQUEST);
1387        msg.setElement(1, XNetConstants.RESUME_OPS);
1388        msg.setParity();
1389        return (msg);
1390    }
1391
1392    /**
1393     * Build an EmergencyOff Message.
1394     * @return emergency off message.
1395     */
1396    public static XNetMessage getEmergencyOffMsg() {
1397        XNetMessage msg = new XNetMessage(3);
1398        msg.setElement(0, XNetConstants.CS_REQUEST);
1399        msg.setElement(1, XNetConstants.EMERGENCY_OFF);
1400        msg.setParity();
1401        return (msg);
1402    }
1403
1404    /**
1405     * Build an EmergencyStop Message.
1406     * @return emergency stop message.
1407     */
1408    public static XNetMessage getEmergencyStopMsg() {
1409        XNetMessage msg = new XNetMessage(2);
1410        msg.setElement(0, XNetConstants.ALL_ESTOP);
1411        msg.setParity();
1412        return (msg);
1413    }
1414
1415    /**
1416     * Generate the message to request the Command Station Hardware/Software
1417     * Version.
1418     * @return message to request CS hardware and software version.
1419     */
1420    public static XNetMessage getCSVersionRequestMessage() {
1421        XNetMessage msg = new XNetMessage(3);
1422        msg.setElement(0, XNetConstants.CS_REQUEST);
1423        msg.setElement(1, XNetConstants.CS_VERSION);
1424        msg.setParity(); // Set the parity bit
1425        return msg;
1426    }
1427
1428    /**
1429     * Generate the message to request the Command Station Status.
1430     * @return message to request CS status.
1431     */
1432    public static XNetMessage getCSStatusRequestMessage() {
1433        XNetMessage msg = new XNetMessage(3);
1434        msg.setElement(0, XNetConstants.CS_REQUEST);
1435        msg.setElement(1, XNetConstants.CS_STATUS);
1436        msg.setParity(); // Set the parity bit
1437        return msg;
1438    }
1439
1440    /**
1441     * Generate the message to set the Command Station to Auto or Manual restart
1442     * mode.
1443     * @param autoMode true if auto, false for manual.
1444     * @return message to set CS restart mode.
1445     */
1446    public static XNetMessage getCSAutoStartMessage(boolean autoMode) {
1447        XNetMessage msg = new XNetMessage(4);
1448        msg.setElement(0, XNetConstants.CS_SET_POWERMODE);
1449        msg.setElement(1, XNetConstants.CS_SET_POWERMODE);
1450        if (autoMode) {
1451            msg.setElement(2, XNetConstants.CS_POWERMODE_AUTO);
1452        } else {
1453            msg.setElement(2, XNetConstants.CS_POWERMODE_MANUAL);
1454        }
1455        msg.setParity(); // Set the parity bit
1456        return msg;
1457    }
1458
1459    /**
1460     * Generate the message to request the Computer Interface Hardware/Software
1461     * Version.
1462     * @return message to request interface hardware and software version.
1463     */
1464    public static XNetMessage getLIVersionRequestMessage() {
1465        XNetMessage msg = new XNetMessage(2);
1466        msg.setElement(0, XNetConstants.LI_VERSION_REQUEST);
1467        msg.setParity(); // Set the parity bit
1468        return msg;
1469    }
1470
1471    /**
1472     * Generate the message to set or request the Computer Interface Address.
1473     *
1474     * @param address Interface address (0-31). Send invalid address to request
1475     *                the address (32-255).
1476     * @return message to set or request interface address.
1477     */
1478    public static XNetMessage getLIAddressRequestMsg(int address) {
1479        XNetMessage msg = new XNetMessage(4);
1480        msg.setElement(0, XNetConstants.LI101_REQUEST);
1481        msg.setElement(1, XNetConstants.LI101_REQUEST_ADDRESS);
1482        msg.setElement(2, address);
1483        msg.setParity(); // Set the parity bit
1484        return msg;
1485    }
1486
1487    /**
1488     * Generate the message to set or request the Computer Interface speed.
1489     *
1490     * @param speed 1 is 19,200bps, 2 is 38,400bps, 3 is 57,600bps, 4 is
1491     *              115,200bps. Send invalid speed to request the current
1492     *              setting.
1493     * @return message for set / request interface speed.
1494     */
1495    public static XNetMessage getLISpeedRequestMsg(int speed) {
1496        XNetMessage msg = new XNetMessage(4);
1497        msg.setElement(0, XNetConstants.LI101_REQUEST);
1498        msg.setElement(1, XNetConstants.LI101_REQUEST_BAUD);
1499        msg.setElement(2, speed);
1500        msg.setParity(); // Set the parity bit
1501        return msg;
1502    }
1503
1504   /**
1505    * Generate text translations of messages for use in the XpressNet monitor.
1506    *
1507    * @return representation of the XNetMessage as a string.
1508    */
1509    @Override
1510   public String toMonitorString(){
1511        String text;
1512        /* Start decoding messages sent by the computer */
1513        /* Start with LI101F requests */
1514        if (getElement(0) == XNetConstants.LI_VERSION_REQUEST) {
1515            text = Bundle.getMessage("XNetMessageRequestLIVersion");
1516        } else if (getElement(0) == XNetConstants.LI101_REQUEST) {
1517            switch (getElement(1)) {
1518                case XNetConstants.LI101_REQUEST_ADDRESS:
1519                    text = Bundle.getMessage("XNetMessageRequestLIAddress", getElement(2));
1520                    break;
1521                case XNetConstants.LI101_REQUEST_BAUD:
1522                    switch (getElement(2)) {
1523                        case 1:
1524                            text = Bundle.getMessage(X_NET_MESSAGE_REQUEST_LI_BAUD,
1525                                   Bundle.getMessage("LIBaud19200"));
1526                            break;
1527                        case 2:
1528                            text = Bundle.getMessage(X_NET_MESSAGE_REQUEST_LI_BAUD,
1529                                   Bundle.getMessage("Baud38400"));
1530                            break;
1531                        case 3:
1532                            text = Bundle.getMessage(X_NET_MESSAGE_REQUEST_LI_BAUD,
1533                                   Bundle.getMessage("Baud57600"));
1534                            break;
1535                        case 4:
1536                            text = Bundle.getMessage(X_NET_MESSAGE_REQUEST_LI_BAUD,
1537                                   Bundle.getMessage("Baud115200"));
1538                            break;
1539                        default:
1540                            text = Bundle.getMessage(X_NET_MESSAGE_REQUEST_LI_BAUD,
1541                                   Bundle.getMessage("BaudOther"));
1542                    }
1543                    break;
1544                default:
1545                    text = toString();
1546            }
1547            /* Next, we have generic requests */
1548        } else if (getElement(0) == XNetConstants.CS_REQUEST) {
1549            switch (getElement(1)) {
1550                case XNetConstants.EMERGENCY_OFF:
1551                    text = Bundle.getMessage("XNetMessageRequestEmergencyOff");
1552                    break;
1553                case XNetConstants.RESUME_OPS:
1554                    text = Bundle.getMessage("XNetMessageRequestNormalOps");
1555                    break;
1556                case XNetConstants.SERVICE_MODE_CSRESULT:
1557                    text = Bundle.getMessage("XNetMessageRequestServiceModeResult");
1558                    break;
1559                case XNetConstants.CS_VERSION:
1560                    text = Bundle.getMessage("XNetMessageRequestCSVersion");
1561                    break;
1562                case XNetConstants.CS_STATUS:
1563                    text = Bundle.getMessage("XNetMessageRequestCSStatus");
1564                    break;
1565                default:
1566                    text = toString();
1567            }
1568        } else if (getElement(0) == XNetConstants.CS_SET_POWERMODE
1569                && getElement(1) == XNetConstants.CS_SET_POWERMODE
1570                && getElement(2) == XNetConstants.CS_POWERMODE_AUTO) {
1571            text = Bundle.getMessage("XNetMessageRequestCSPowerModeAuto");
1572        } else if (getElement(0) == XNetConstants.CS_SET_POWERMODE
1573                && getElement(1) == XNetConstants.CS_SET_POWERMODE
1574                && getElement(2) == XNetConstants.CS_POWERMODE_MANUAL) {
1575            text = Bundle.getMessage("XNetMessageRequestCSPowerModeManual");
1576            /* Next, we have Programming Requests */
1577        } else if (getElement(0) == XNetConstants.PROG_READ_REQUEST) {
1578            switch (getElement(1)) {
1579                case XNetConstants.PROG_READ_MODE_REGISTER:
1580                    text = Bundle.getMessage("XNetMessageRequestServiceModeReadRegister",getElement(2));
1581                    break;
1582                case XNetConstants.PROG_READ_MODE_CV:
1583                    text = Bundle.getMessage("XNetMessageRequestServiceModeReadDirect",getElement(2));
1584                    break;
1585                case XNetConstants.PROG_READ_MODE_PAGED:
1586                    text = Bundle.getMessage("XNetMessageRequestServiceModeReadPaged",getElement(2));
1587                    break;
1588                case XNetConstants.PROG_READ_MODE_CV_V36:
1589                    text = Bundle.getMessage(X_NET_MESSAGE_REQUEST_SERVICE_MODE_READ_DIRECT_V_36,(getElement(2)== 0 ? 1024 : getElement(2)));
1590                    break;
1591                case XNetConstants.PROG_READ_MODE_CV_V36 + 1:
1592                    text = Bundle.getMessage(X_NET_MESSAGE_REQUEST_SERVICE_MODE_READ_DIRECT_V_36,(256 + getElement(2)));
1593                    break;
1594                case XNetConstants.PROG_READ_MODE_CV_V36 + 2:
1595                    text = Bundle.getMessage(X_NET_MESSAGE_REQUEST_SERVICE_MODE_READ_DIRECT_V_36,(512 + getElement(2)));
1596                    break;
1597                case XNetConstants.PROG_READ_MODE_CV_V36 + 3:
1598                    text = Bundle.getMessage(X_NET_MESSAGE_REQUEST_SERVICE_MODE_READ_DIRECT_V_36,(768 + getElement(2)));
1599                    break;
1600                default:
1601                    text = toString();
1602            }
1603        } else if (getElement(0) == XNetConstants.PROG_WRITE_REQUEST) {
1604            switch (getElement(1)) {
1605                case XNetConstants.PROG_WRITE_MODE_REGISTER:
1606                    text = Bundle.getMessage("XNetMessageRequestServiceModeWriteRegister",getElement(2),getElement(3));
1607                    break;
1608                case XNetConstants.PROG_WRITE_MODE_CV:
1609                    text = Bundle.getMessage("XNetMessageRequestServiceModeWriteDirect",getElement(2),getElement(3));
1610                    break;
1611                case XNetConstants.PROG_WRITE_MODE_PAGED:
1612                    text = Bundle.getMessage("XNetMessageRequestServiceModeWritePaged",getElement(2),getElement(3));
1613                    break;
1614                case XNetConstants.PROG_WRITE_MODE_CV_V36:
1615                    text = Bundle.getMessage(X_NET_MESSAGE_REQUEST_SERVICE_MODE_WRITE_DIRECT_V_36,(getElement(2)== 0 ? 1024 : getElement(2)),getElement(3));
1616                    break;
1617                case (XNetConstants.PROG_WRITE_MODE_CV_V36 + 1):
1618                    text = Bundle.getMessage(X_NET_MESSAGE_REQUEST_SERVICE_MODE_WRITE_DIRECT_V_36,(256 + getElement(2)),getElement(3));
1619                    break;
1620                case (XNetConstants.PROG_WRITE_MODE_CV_V36 + 2):
1621                    text = Bundle.getMessage(X_NET_MESSAGE_REQUEST_SERVICE_MODE_WRITE_DIRECT_V_36,(512 + getElement(2)),getElement(3));
1622                    break;
1623                case (XNetConstants.PROG_WRITE_MODE_CV_V36 + 3):
1624                    text = Bundle.getMessage(X_NET_MESSAGE_REQUEST_SERVICE_MODE_WRITE_DIRECT_V_36,(768 + getElement(2)),getElement(3));
1625                    break;
1626                default:
1627                    text = toString();
1628            }
1629        } else if (getElement(0) == XNetConstants.OPS_MODE_PROG_REQ) {
1630            switch (getElement(1)) {
1631                case XNetConstants.OPS_MODE_PROG_WRITE_REQ:
1632                    if ((getElement(4) & 0xEC) == 0xEC
1633                            || (getElement(4) & 0xE4) == 0xE4) {
1634                        String message = "";
1635                        if ((getElement(4) & 0xEC) == 0xEC) {
1636                            message ="XNetMessageOpsModeByteWrite";
1637                        } else if ((getElement(4) & 0xE4) == 0xE4) {
1638                            message ="XNetMessageOpsModeByteVerify";
1639                        }
1640                        text = Bundle.getMessage(message,
1641                               getElement(6),
1642                               (1 + getElement(5) + ((getElement(4) & 0x03) << 8)),
1643                               LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)));
1644                        break;
1645                    } else if ((getElement(4) & 0xE8) == 0xE8) {
1646                        String message;
1647                        if ((getElement(6) & 0x10) == 0x10) {
1648                            message ="XNetMessageOpsModeBitVerify";
1649                        } else {
1650                            message ="XNetMessageOpsModeBitWrite";
1651                        }
1652                        text = Bundle.getMessage(message,
1653                                ((getElement(6) & 0x08) >> 3),
1654                                (1 + getElement(5) + ((getElement(4) & 0x03) << 8)),
1655                                (getElement(6) & 0x07),
1656                                LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)));
1657                        break;
1658                    }
1659                    //$FALL-THROUGH$
1660                default:
1661                    text = toString();
1662            }
1663            // Next, decode the locomotive operation requests
1664        } else if (getElement(0) == XNetConstants.LOCO_OPER_REQ) {
1665            text = "Mobile Decoder Operations Request: ";
1666            int speed;
1667            String direction;
1668            switch (getElement(1)) {
1669                case XNetConstants.LOCO_SPEED_14:
1670                    if ((getElement(4) & 0x80) != 0) {
1671                        direction = Bundle.getMessage(FORWARD);
1672                    } else {
1673                        direction = Bundle.getMessage(REVERSE);
1674                    }
1675                    text = text
1676                            + Bundle.getMessage(X_NET_MESSAGE_SET_SPEED,
1677                            LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)))
1678                            + " " + (getElement(4) & 0x0f)
1679                            + " " + Bundle.getMessage(X_NET_MESSAGE_SET_DIRECTION, direction);
1680                    text += " " + Bundle.getMessage(SPEED_STEP_MODE_X, 14) + ".";
1681                    break;
1682                case XNetConstants.LOCO_SPEED_27:
1683                    text = text
1684                            + Bundle.getMessage(X_NET_MESSAGE_SET_SPEED,
1685                            LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)))
1686                            + " ";
1687                    if(log.isDebugEnabled()) {
1688                        log.debug("LOCO_SPEED_27 {} {}", LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)), text);
1689                        // address printed as: "1234" = OK
1690                        // address printed as: "1,234" = WRONG
1691                    }
1692                    speed
1693                            = (((getElement(4) & 0x10) >> 4)
1694                            + ((getElement(4) & 0x0F) << 1));
1695                    if (speed >= 3) {
1696                        speed -= 3;
1697                    }
1698                    text += speed;
1699                    if ((getElement(4) & 0x80) != 0) {
1700                        text += " " + Bundle.getMessage(X_NET_MESSAGE_SET_DIRECTION, Bundle.getMessage(FORWARD));
1701                    } else {
1702                        text += " " + Bundle.getMessage(X_NET_MESSAGE_SET_DIRECTION, Bundle.getMessage(REVERSE));
1703                    }
1704                    text += " " + Bundle.getMessage(SPEED_STEP_MODE_X, 27) + ".";
1705                    break;
1706                case XNetConstants.LOCO_SPEED_28:
1707                    text = text
1708                            + Bundle.getMessage(X_NET_MESSAGE_SET_SPEED,
1709                            LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)))
1710                            + " ";
1711                    speed
1712                            = (((getElement(4) & 0x10) >> 4)
1713                            + ((getElement(4) & 0x0F) << 1));
1714                    if (speed >= 3) {
1715                        speed -= 3;
1716                    }
1717                    text += speed;
1718                    if ((getElement(4) & 0x80) != 0) {
1719                        text += " " + Bundle.getMessage(X_NET_MESSAGE_SET_DIRECTION, Bundle.getMessage(FORWARD));
1720                    } else {
1721                        text += " " + Bundle.getMessage(X_NET_MESSAGE_SET_DIRECTION, Bundle.getMessage(REVERSE));
1722                    }
1723                    text += " " + Bundle.getMessage(SPEED_STEP_MODE_X, 28) + ".";
1724                    break;
1725                case XNetConstants.LOCO_SPEED_128:
1726                    if ((getElement(4) & 0x80) != 0) {
1727                        direction = Bundle.getMessage(FORWARD);
1728                    } else {
1729                        direction = Bundle.getMessage(REVERSE);
1730                    }
1731                    text = text
1732                            + Bundle.getMessage(X_NET_MESSAGE_SET_SPEED,
1733                            LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)))
1734                            + " "
1735                            + (getElement(4) & 0x7F) + " " + Bundle.getMessage(X_NET_MESSAGE_SET_DIRECTION, direction);
1736                    text += " " + Bundle.getMessage(SPEED_STEP_MODE_X, 128) + ".";
1737                    break;
1738                case XNetConstants.LOCO_SET_FUNC_GROUP1:
1739                    text = text + buildSetFunctionGroup1MonitorString();
1740                    break;
1741                case XNetConstants.LOCO_SET_FUNC_GROUP2:
1742                    text = text + buildSetFunctionGroup2MonitorString();
1743                    break;
1744                case XNetConstants.LOCO_SET_FUNC_GROUP3:
1745                    text = text + buildSetFunctionGroup3MonitorString();
1746                    break;
1747                case XNetConstants.LOCO_SET_FUNC_GROUP4:
1748                    text = text + buildSetFunctionGroup4MonitorString();
1749                    break;
1750                case XNetConstants.LOCO_SET_FUNC_GROUP5:
1751                    text = text + buildSetFunctionGroup5MonitorString();
1752                    break;
1753                case XNetConstants.LOCO_SET_FUNC_Group1:
1754                    text = text + buildSetFunctionGroup1MomentaryMonitorString();
1755                    break;
1756                case XNetConstants.LOCO_SET_FUNC_Group2:
1757                    text = text + buildSetFunctionGroup2MomentaryMonitorString();
1758                    break;
1759                case XNetConstants.LOCO_SET_FUNC_Group3:
1760                    text = text + buildSetFunctionGroup3MomentaryMonitorString();
1761                    break;
1762                case XNetConstants.LOCO_SET_FUNC_Group4:
1763                    text = text + buildSetFunctionGroup4MomentaryMonitorString();
1764                    break;
1765                case XNetConstants.LOCO_SET_FUNC_Group5:
1766                    text = text + buildSetFunctionGroup5MomentaryMonitorString();
1767                    break;
1768                case XNetConstants.LOCO_ADD_MULTI_UNIT_REQ:
1769                    text = Bundle.getMessage("XNetMessageAddToConsistDirNormalRequest",
1770                           LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)),
1771                           getElement(4));
1772                    break;
1773                case (XNetConstants.LOCO_ADD_MULTI_UNIT_REQ | 0x01):
1774                    text = Bundle.getMessage("XNetMessageAddToConsistDirReverseRequest",
1775                           LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)),
1776                           getElement(4));
1777                    break;
1778                case (XNetConstants.LOCO_REM_MULTI_UNIT_REQ):
1779                    text = Bundle.getMessage("XNetMessageRemoveFromConsistRequest",
1780                           LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)),
1781                           getElement(4));
1782                    break;
1783                case (XNetConstants.LOCO_IN_MULTI_UNIT_REQ_FORWARD):
1784                    text = Bundle.getMessage("XNetMessageSearchCSStackForwardNextMULoco",
1785                           getElement(2),
1786                           LenzCommandStation.calcLocoAddress(getElement(3), getElement(4)));
1787                    break;
1788                case (XNetConstants.LOCO_IN_MULTI_UNIT_REQ_BACKWARD):
1789                    text = Bundle.getMessage("XNetMessageSearchCSStackBackwardNextMULoco",
1790                           getElement(2),
1791                           LenzCommandStation.calcLocoAddress(getElement(3), getElement(4)));
1792                    break;
1793                default:
1794                    text = toString();
1795            }
1796            // Emergency Stop a locomotive
1797        } else if (getElement(0) == XNetConstants.EMERGENCY_STOP) {
1798            text = Bundle.getMessage("XNetMessageAddressedEmergencyStopRequest",
1799              LenzCommandStation.calcLocoAddress(getElement(1), getElement(2)));
1800            // Disolve or Establish a Double Header
1801        } else if (getElement(0) == XNetConstants.LOCO_DOUBLEHEAD
1802                && getElement(1) == XNetConstants.LOCO_DOUBLEHEAD_BYTE2) {
1803            int loco1 = LenzCommandStation.calcLocoAddress(getElement(2), getElement(3));
1804            int loco2 = LenzCommandStation.calcLocoAddress(getElement(4), getElement(5));
1805            if (loco2 == 0) {
1806                text = Bundle.getMessage("XNetMessageDisolveDoubleHeaderRequest",loco1);
1807            } else {
1808                text = Bundle.getMessage("XNetMessageBuildDoubleHeaderRequest",loco1,loco2);
1809            }
1810            // Locomotive Status Request messages
1811        } else if (getElement(0) == XNetConstants.LOCO_STATUS_REQ) {
1812            switch (getElement(1)) {
1813                case XNetConstants.LOCO_INFO_REQ_FUNC:
1814                    text = Bundle.getMessage("XNetMessageRequestLocoFunctionMomStatus",
1815                            LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)));
1816                    break;
1817                case XNetConstants.LOCO_INFO_REQ_FUNC_HI_ON:
1818                    text = Bundle.getMessage("XNetMessageRequestLocoFunctionHighStatus",
1819                            LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)));
1820                    break;
1821                case XNetConstants.LOCO_INFO_REQ_FUNC_HI_MOM:
1822                    text = Bundle.getMessage("XNetMessageRequestLocoFunctionHighMomStatus",
1823                            LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)));
1824                    break;
1825                case XNetConstants.LOCO_INFO_REQ_V3:
1826                    text = Bundle.getMessage("XNetMessageRequestLocoInfo",
1827                            LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)));
1828                    break;
1829                case XNetConstants.LOCO_STACK_SEARCH_FWD:
1830                    text = Bundle.getMessage("XNetMessageSearchCSStackForward",
1831                           LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)));
1832                    break;
1833                case XNetConstants.LOCO_STACK_SEARCH_BKWD:
1834                    text = Bundle.getMessage("XNetMessageSearchCSStackBackward",
1835                           LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)));
1836                    break;
1837                case XNetConstants.LOCO_STACK_DELETE:
1838                    text = Bundle.getMessage("XNetMessageDeleteAddressOnStack",
1839                            LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)));
1840                    break;
1841                default:
1842                    text = toString();
1843            }
1844        } else if(getElement(0) == XNetConstants.CS_MULTI_UNIT_REQ) {
1845            if (getElement(1) == XNetConstants.CS_MULTI_UNIT_REQ_FWD){
1846                text = Bundle.getMessage("XNetMessageSearchCSStackForwardConsistAddress",
1847                           getElement(2));
1848            } else if(getElement(1) == XNetConstants.CS_MULTI_UNIT_REQ_BKWD){
1849                text = Bundle.getMessage("XNetMessageSearchCSStackBackwardConsistAddress",
1850                           getElement(2));
1851            } else {
1852                    text = toString();
1853            }
1854            // Accessory Info Request message
1855        } else if (getElement(0) == XNetConstants.ACC_INFO_REQ) {
1856            String nibblekey=(((getElement(2) & 0x01) == 0x01) ? "FeedbackEncoderUpperNibble" : "FeedbackEncoderLowerNibble");
1857            text = Bundle.getMessage("XNetMessageFeedbackRequest",
1858                       getElement(1),
1859                       Bundle.getMessage(nibblekey));
1860        } else if (getElement(0) == XNetConstants.ACC_OPER_REQ) {
1861            String messageKey =(((getElement(2) & 0x08) == 0x08) ? "XNetMessageAccessoryDecoderOnRequest" : "XNetMessageAccessoryDecoderOffRequest");
1862            int baseaddress = getElement(1);
1863            int subaddress = ((getElement(2) & 0x06) >> 1);
1864            int address = (baseaddress * 4) + subaddress + 1;
1865            int output = (getElement(2) & 0x01);
1866            text = Bundle.getMessage(messageKey,address, baseaddress,subaddress,output);
1867        } else if (getElement(0) == XNetConstants.ALL_ESTOP) {
1868            text = Bundle.getMessage("XNetMessageRequestEmergencyStop");
1869        } else {
1870            text = toString();
1871        }
1872        return text;
1873   }
1874
1875   private String buildSetFunctionGroup1MonitorString() {
1876       String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X, 1) +
1877               " " + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
1878       int element4 = getElement(4);
1879       if ((element4 & 0x10) != 0) {
1880           text += "F0 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1881       } else {
1882           text += "F0 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1883       }
1884       if ((element4 & 0x01) != 0) {
1885           text += "F1 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1886       } else {
1887           text += "F1 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1888       }
1889       if ((element4 & 0x02) != 0) {
1890           text += "F2 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1891       } else {
1892           text += "F2 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1893       }
1894       if ((element4 & 0x04) != 0) {
1895           text += "F3 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1896       } else {
1897           text += "F3 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1898       }
1899       if ((element4 & 0x08) != 0) {
1900           text += "F4 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1901       } else {
1902           text += "F4 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1903
1904       }
1905       return text;
1906   }
1907
1908   private String buildSetFunctionGroup2MonitorString(){
1909       String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X, 2) + " "
1910               + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
1911       int element4 = getElement(4);
1912       if ((element4 & 0x01) != 0) {
1913           text += "F5 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1914       } else {
1915           text += "F5 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1916       }
1917       if ((element4 & 0x02) != 0) {
1918           text += "F6 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1919       } else {
1920           text += "F6 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1921       }
1922       if ((element4 & 0x04) != 0) {
1923           text += "F7 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1924       } else {
1925           text += "F7 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1926       }
1927       if ((element4 & 0x08) != 0) {
1928           text += "F8 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1929       } else {
1930           text += "F8 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1931       }
1932       return text;
1933   }
1934
1935    private String buildSetFunctionGroup3MonitorString() {
1936        String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X, 3) + " "
1937                + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
1938        int element4 = getElement(4);
1939        if ((element4 & 0x01) != 0) {
1940            text += "F9 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1941        } else {
1942            text += "F9 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1943        }
1944        if ((element4 & 0x02) != 0) {
1945            text += "F10 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1946        } else {
1947            text += "F10 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1948        }
1949        if ((element4 & 0x04) != 0) {
1950            text += "F11 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1951        } else {
1952            text += "F11 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1953        }
1954        if ((element4 & 0x08) != 0) {
1955            text += "F12 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1956        } else {
1957            text += "F12 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1958        }
1959        return text;
1960    }
1961
1962    private String buildSetFunctionGroup4MonitorString() {
1963        String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X, 4) + " " + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
1964        int element4 = getElement(4);
1965        if ((element4 & 0x01) != 0) {
1966            text += "F13 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1967        } else {
1968            text += "F13 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1969        }
1970        if ((element4 & 0x02) != 0) {
1971            text += "F14 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1972        } else {
1973            text += "F14 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1974        }
1975        if ((element4 & 0x04) != 0) {
1976            text += "F15 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1977        } else {
1978            text += "F15 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1979        }
1980        if ((element4 & 0x08) != 0) {
1981            text += "F16 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1982        } else {
1983            text += "F16 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1984        }
1985        if ((element4 & 0x10) != 0) {
1986            text += "F17 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1987        } else {
1988            text += "F17 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1989        }
1990        if ((element4 & 0x20) != 0) {
1991            text += "F18 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1992        } else {
1993            text += "F18 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1994        }
1995        if ((element4 & 0x40) != 0) {
1996            text += "F19 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1997        } else {
1998            text += "F19 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1999        }
2000        if ((element4 & 0x80) != 0) {
2001            text += "F20 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
2002        } else {
2003            text += "F20 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
2004        }
2005        return text;
2006    }
2007
2008    private String buildSetFunctionGroup5MonitorString() {
2009        String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X, 5) + " " + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
2010        int element4 = getElement(4);
2011        if ((element4 & 0x01) != 0) {
2012            text += "F21 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
2013        } else {
2014            text += "F21 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
2015        }
2016        if ((element4 & 0x02) != 0) {
2017            text += "F22 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
2018        } else {
2019            text += "F22 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
2020        }
2021        if ((element4 & 0x04) != 0) {
2022            text += "F23 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
2023        } else {
2024            text += "F23 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
2025        }
2026        if ((element4 & 0x08) != 0) {
2027            text += "F24 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
2028        } else {
2029            text += "F24 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
2030        }
2031        if ((element4 & 0x10) != 0) {
2032            text += "F25 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
2033        } else {
2034            text += "F25 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
2035        }
2036        if ((element4 & 0x20) != 0) {
2037            text += "F26 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
2038        } else {
2039            text += "F26 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
2040        }
2041        if ((element4 & 0x40) != 0) {
2042            text += "F27 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
2043        } else {
2044            text += "F27 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
2045        }
2046        if ((element4 & 0x80) != 0) {
2047            text += "F28 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
2048        } else {
2049            text += "F28 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
2050        }
2051        return text;
2052    }
2053
2054        private String buildSetFunctionGroup1MomentaryMonitorString() {
2055            String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X_MOMENTARY, 1) + " "
2056                + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
2057        int element4 = getElement(4);
2058        if ((element4 & 0x10) == 0) {
2059            text += "F0 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2060        } else {
2061            text += "F0 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2062        }
2063        if ((element4 & 0x01) == 0) {
2064            text += "F1 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2065        } else {
2066            text += "F1 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2067        }
2068        if ((element4 & 0x02) == 0) {
2069            text += "F2 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2070        } else {
2071            text += "F2 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2072        }
2073        if ((element4 & 0x04) == 0) {
2074            text += "F3 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2075        } else {
2076            text += "F3 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2077        }
2078        if ((element4 & 0x08) == 0) {
2079            text += "F4 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2080        } else {
2081            text += "F4 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2082        }
2083        return text;
2084    }
2085
2086    private String buildSetFunctionGroup2MomentaryMonitorString() {
2087        String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X_MOMENTARY, 2) + " "
2088                + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
2089        int element4 = getElement(4);
2090        if ((element4 & 0x01) == 0) {
2091            text += "F5 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2092        } else {
2093            text += "F5 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2094        }
2095        if ((element4 & 0x02) == 0) {
2096            text += "F6 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2097        } else {
2098            text += "F6 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2099        }
2100        if ((element4 & 0x04) == 0) {
2101            text += "F7 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2102        } else {
2103            text += "F7 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2104        }
2105        if ((element4 & 0x08) == 0) {
2106            text += "F8 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2107        } else {
2108            text += "F8 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2109        }
2110        return text;
2111    }
2112
2113    private String buildSetFunctionGroup3MomentaryMonitorString() {
2114        String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X_MOMENTARY, 3) + " " + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
2115        int element4 = getElement(4);
2116        if ((element4 & 0x01) == 0) {
2117            text += "F9 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2118        } else {
2119            text += "F9 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2120        }
2121        if ((element4 & 0x02) == 0) {
2122            text += "F10 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2123        } else {
2124            text += "F10 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2125        }
2126        if ((element4 & 0x04) == 0) {
2127            text += "F11 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2128        } else {
2129            text += "F11 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2130        }
2131        if ((element4 & 0x08) == 0) {
2132            text += "F12 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2133        } else {
2134            text += "F12 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2135        }
2136        return text;
2137    }
2138
2139
2140    private String buildSetFunctionGroup4MomentaryMonitorString() {
2141        String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X_MOMENTARY, 4) + " "
2142                + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
2143        int element4 = getElement(4);
2144        if ((element4 & 0x01) == 0) {
2145            text += "F13 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2146        } else {
2147            text += "F13 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2148        }
2149        if ((element4 & 0x02) == 0) {
2150            text += "F14 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2151        } else {
2152            text += "F14 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2153        }
2154        if ((element4 & 0x04) == 0) {
2155            text += "F15 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2156        } else {
2157            text += "F15 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2158        }
2159        if ((element4 & 0x08) == 0) {
2160            text += "F16 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2161        } else {
2162            text += "F16 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2163        }
2164        if ((element4 & 0x10) == 0) {
2165            text += "F17 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2166        } else {
2167            text += "F17 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2168        }
2169        if ((element4 & 0x20) == 0) {
2170            text += "F18 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2171        } else {
2172            text += "F18 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2173        }
2174        if ((element4 & 0x40) == 0) {
2175            text += "F19 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2176        } else {
2177            text += "F19 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2178        }
2179        if ((element4 & 0x80) == 0) {
2180            text += "F20 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2181        } else {
2182            text += "F20 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2183        }
2184        return text;
2185}
2186
2187    private String buildSetFunctionGroup5MomentaryMonitorString() {
2188        String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X_MOMENTARY, 5) + " " + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
2189        int element4 = getElement(4);
2190        if ((element4 & 0x01) == 0) {
2191            text += "F21 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2192        } else {
2193            text += "F21 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2194        }
2195        if ((element4 & 0x02) == 0) {
2196            text += "F22 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2197        } else {
2198            text += "F22 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2199        }
2200        if ((element4 & 0x04) == 0) {
2201            text += "F23 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2202        } else {
2203            text += "F23 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2204        }
2205        if ((element4 & 0x08) == 0) {
2206            text += "F24 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2207        } else {
2208            text += "F24 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2209        }
2210        if ((element4 & 0x10) == 0) {
2211            text += "F25 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2212        } else {
2213            text += "F25 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2214        }
2215        if ((element4 & 0x20) == 0) {
2216            text += "F26 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2217        } else {
2218            text += "F26 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2219        }
2220        if ((element4 & 0x40) == 0) {
2221            text += "F27 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2222        } else {
2223            text += "F27 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2224        }
2225        if ((element4 & 0x80) == 0) {
2226            text += "F28 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2227        } else {
2228            text += "F28 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2229        }
2230        return text;
2231    }
2232
2233    // initialize logging
2234    private static final Logger log = LoggerFactory.getLogger(XNetMessage.class);
2235
2236}