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("{}", LenzCommandStation.calcLocoAddress(getElement(2), getElement(3))); // address printed as: "1234" = OK
1689                        log.debug(text); // address printed as: "1,234" = WRONG
1690                    }
1691                    speed
1692                            = (((getElement(4) & 0x10) >> 4)
1693                            + ((getElement(4) & 0x0F) << 1));
1694                    if (speed >= 3) {
1695                        speed -= 3;
1696                    }
1697                    text += speed;
1698                    if ((getElement(4) & 0x80) != 0) {
1699                        text += " " + Bundle.getMessage(X_NET_MESSAGE_SET_DIRECTION, Bundle.getMessage(FORWARD));
1700                    } else {
1701                        text += " " + Bundle.getMessage(X_NET_MESSAGE_SET_DIRECTION, Bundle.getMessage(REVERSE));
1702                    }
1703                    text += " " + Bundle.getMessage(SPEED_STEP_MODE_X, 27) + ".";
1704                    break;
1705                case XNetConstants.LOCO_SPEED_28:
1706                    text = text
1707                            + Bundle.getMessage(X_NET_MESSAGE_SET_SPEED,
1708                            LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)))
1709                            + " ";
1710                    speed
1711                            = (((getElement(4) & 0x10) >> 4)
1712                            + ((getElement(4) & 0x0F) << 1));
1713                    if (speed >= 3) {
1714                        speed -= 3;
1715                    }
1716                    text += speed;
1717                    if ((getElement(4) & 0x80) != 0) {
1718                        text += " " + Bundle.getMessage(X_NET_MESSAGE_SET_DIRECTION, Bundle.getMessage(FORWARD));
1719                    } else {
1720                        text += " " + Bundle.getMessage(X_NET_MESSAGE_SET_DIRECTION, Bundle.getMessage(REVERSE));
1721                    }
1722                    text += " " + Bundle.getMessage(SPEED_STEP_MODE_X, 28) + ".";
1723                    break;
1724                case XNetConstants.LOCO_SPEED_128:
1725                    if ((getElement(4) & 0x80) != 0) {
1726                        direction = Bundle.getMessage(FORWARD);
1727                    } else {
1728                        direction = Bundle.getMessage(REVERSE);
1729                    }
1730                    text = text
1731                            + Bundle.getMessage(X_NET_MESSAGE_SET_SPEED,
1732                            LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)))
1733                            + " "
1734                            + (getElement(4) & 0x7F) + " " + Bundle.getMessage(X_NET_MESSAGE_SET_DIRECTION, direction);
1735                    text += " " + Bundle.getMessage(SPEED_STEP_MODE_X, 128) + ".";
1736                    break;
1737                case XNetConstants.LOCO_SET_FUNC_GROUP1:
1738                    text = text + buildSetFunctionGroup1MonitorString();
1739                    break;
1740                case XNetConstants.LOCO_SET_FUNC_GROUP2:
1741                    text = text + buildSetFunctionGroup2MonitorString();
1742                    break;
1743                case XNetConstants.LOCO_SET_FUNC_GROUP3:
1744                    text = text + buildSetFunctionGroup3MonitorString();
1745                    break;
1746                case XNetConstants.LOCO_SET_FUNC_GROUP4:
1747                    text = text + buildSetFunctionGroup4MonitorString();
1748                    break;
1749                case XNetConstants.LOCO_SET_FUNC_GROUP5:
1750                    text = text + buildSetFunctionGroup5MonitorString();
1751                    break;
1752                case XNetConstants.LOCO_SET_FUNC_Group1:
1753                    text = text + buildSetFunctionGroup1MomentaryMonitorString();
1754                    break;
1755                case XNetConstants.LOCO_SET_FUNC_Group2:
1756                    text = text + buildSetFunctionGroup2MomentaryMonitorString();
1757                    break;
1758                case XNetConstants.LOCO_SET_FUNC_Group3:
1759                    text = text + buildSetFunctionGroup3MomentaryMonitorString();
1760                    break;
1761                case XNetConstants.LOCO_SET_FUNC_Group4:
1762                    text = text + buildSetFunctionGroup4MomentaryMonitorString();
1763                    break;
1764                case XNetConstants.LOCO_SET_FUNC_Group5:
1765                    text = text + buildSetFunctionGroup5MomentaryMonitorString();
1766                    break;
1767                case XNetConstants.LOCO_ADD_MULTI_UNIT_REQ:
1768                    text = Bundle.getMessage("XNetMessageAddToConsistDirNormalRequest",
1769                           LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)),
1770                           getElement(4));
1771                    break;
1772                case (XNetConstants.LOCO_ADD_MULTI_UNIT_REQ | 0x01):
1773                    text = Bundle.getMessage("XNetMessageAddToConsistDirReverseRequest",
1774                           LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)),
1775                           getElement(4));
1776                    break;
1777                case (XNetConstants.LOCO_REM_MULTI_UNIT_REQ):
1778                    text = Bundle.getMessage("XNetMessageRemoveFromConsistRequest",
1779                           LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)),
1780                           getElement(4));
1781                    break;
1782                case (XNetConstants.LOCO_IN_MULTI_UNIT_REQ_FORWARD):
1783                    text = Bundle.getMessage("XNetMessageSearchCSStackForwardNextMULoco",
1784                           getElement(2),
1785                           LenzCommandStation.calcLocoAddress(getElement(3), getElement(4)));
1786                    break;
1787                case (XNetConstants.LOCO_IN_MULTI_UNIT_REQ_BACKWARD):
1788                    text = Bundle.getMessage("XNetMessageSearchCSStackBackwardNextMULoco",
1789                           getElement(2),
1790                           LenzCommandStation.calcLocoAddress(getElement(3), getElement(4)));
1791                    break;
1792                default:
1793                    text = toString();
1794            }
1795            // Emergency Stop a locomotive
1796        } else if (getElement(0) == XNetConstants.EMERGENCY_STOP) {
1797            text = Bundle.getMessage("XNetMessageAddressedEmergencyStopRequest",
1798              LenzCommandStation.calcLocoAddress(getElement(1), getElement(2)));
1799            // Disolve or Establish a Double Header
1800        } else if (getElement(0) == XNetConstants.LOCO_DOUBLEHEAD
1801                && getElement(1) == XNetConstants.LOCO_DOUBLEHEAD_BYTE2) {
1802            int loco1 = LenzCommandStation.calcLocoAddress(getElement(2), getElement(3));
1803            int loco2 = LenzCommandStation.calcLocoAddress(getElement(4), getElement(5));
1804            if (loco2 == 0) {
1805                text = Bundle.getMessage("XNetMessageDisolveDoubleHeaderRequest",loco1);
1806            } else {
1807                text = Bundle.getMessage("XNetMessageBuildDoubleHeaderRequest",loco1,loco2);
1808            }
1809            // Locomotive Status Request messages
1810        } else if (getElement(0) == XNetConstants.LOCO_STATUS_REQ) {
1811            switch (getElement(1)) {
1812                case XNetConstants.LOCO_INFO_REQ_FUNC:
1813                    text = Bundle.getMessage("XNetMessageRequestLocoFunctionMomStatus",
1814                            LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)));
1815                    break;
1816                case XNetConstants.LOCO_INFO_REQ_FUNC_HI_ON:
1817                    text = Bundle.getMessage("XNetMessageRequestLocoFunctionHighStatus",
1818                            LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)));
1819                    break;
1820                case XNetConstants.LOCO_INFO_REQ_FUNC_HI_MOM:
1821                    text = Bundle.getMessage("XNetMessageRequestLocoFunctionHighMomStatus",
1822                            LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)));
1823                    break;
1824                case XNetConstants.LOCO_INFO_REQ_V3:
1825                    text = Bundle.getMessage("XNetMessageRequestLocoInfo",
1826                            LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)));
1827                    break;
1828                case XNetConstants.LOCO_STACK_SEARCH_FWD:
1829                    text = Bundle.getMessage("XNetMessageSearchCSStackForward",
1830                           LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)));
1831                    break;
1832                case XNetConstants.LOCO_STACK_SEARCH_BKWD:
1833                    text = Bundle.getMessage("XNetMessageSearchCSStackBackward",
1834                           LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)));
1835                    break;
1836                case XNetConstants.LOCO_STACK_DELETE:
1837                    text = Bundle.getMessage("XNetMessageDeleteAddressOnStack",
1838                            LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)));
1839                    break;
1840                default:
1841                    text = toString();
1842            }
1843        } else if(getElement(0) == XNetConstants.CS_MULTI_UNIT_REQ) {
1844            if (getElement(1) == XNetConstants.CS_MULTI_UNIT_REQ_FWD){
1845                text = Bundle.getMessage("XNetMessageSearchCSStackForwardConsistAddress",
1846                           getElement(2));
1847            } else if(getElement(1) == XNetConstants.CS_MULTI_UNIT_REQ_BKWD){
1848                text = Bundle.getMessage("XNetMessageSearchCSStackBackwardConsistAddress",
1849                           getElement(2));
1850            } else {
1851                    text = toString();
1852            }
1853            // Accessory Info Request message
1854        } else if (getElement(0) == XNetConstants.ACC_INFO_REQ) {
1855            String nibblekey=(((getElement(2) & 0x01) == 0x01) ? "FeedbackEncoderUpperNibble" : "FeedbackEncoderLowerNibble");
1856            text = Bundle.getMessage("XNetMessageFeedbackRequest",
1857                       getElement(1),
1858                       Bundle.getMessage(nibblekey));
1859        } else if (getElement(0) == XNetConstants.ACC_OPER_REQ) {
1860            String messageKey =(((getElement(2) & 0x08) == 0x08) ? "XNetMessageAccessoryDecoderOnRequest" : "XNetMessageAccessoryDecoderOffRequest");
1861            int baseaddress = getElement(1);
1862            int subaddress = ((getElement(2) & 0x06) >> 1);
1863            int address = (baseaddress * 4) + subaddress + 1;
1864            int output = (getElement(2) & 0x01);
1865            text = Bundle.getMessage(messageKey,address, baseaddress,subaddress,output);
1866        } else if (getElement(0) == XNetConstants.ALL_ESTOP) {
1867            text = Bundle.getMessage("XNetMessageRequestEmergencyStop");
1868        } else {
1869            text = toString();
1870        }
1871        return text;
1872   }
1873
1874   private String buildSetFunctionGroup1MonitorString() {
1875       String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X, 1) +
1876               " " + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
1877       int element4 = getElement(4);
1878       if ((element4 & 0x10) != 0) {
1879           text += "F0 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1880       } else {
1881           text += "F0 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1882       }
1883       if ((element4 & 0x01) != 0) {
1884           text += "F1 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1885       } else {
1886           text += "F1 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1887       }
1888       if ((element4 & 0x02) != 0) {
1889           text += "F2 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1890       } else {
1891           text += "F2 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1892       }
1893       if ((element4 & 0x04) != 0) {
1894           text += "F3 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1895       } else {
1896           text += "F3 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1897       }
1898       if ((element4 & 0x08) != 0) {
1899           text += "F4 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1900       } else {
1901           text += "F4 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1902
1903       }
1904       return text;
1905   }
1906
1907   private String buildSetFunctionGroup2MonitorString(){
1908       String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X, 2) + " "
1909               + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
1910       int element4 = getElement(4);
1911       if ((element4 & 0x01) != 0) {
1912           text += "F5 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1913       } else {
1914           text += "F5 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1915       }
1916       if ((element4 & 0x02) != 0) {
1917           text += "F6 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1918       } else {
1919           text += "F6 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1920       }
1921       if ((element4 & 0x04) != 0) {
1922           text += "F7 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1923       } else {
1924           text += "F7 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1925       }
1926       if ((element4 & 0x08) != 0) {
1927           text += "F8 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1928       } else {
1929           text += "F8 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1930       }
1931       return text;
1932   }
1933
1934    private String buildSetFunctionGroup3MonitorString() {
1935        String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X, 3) + " "
1936                + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
1937        int element4 = getElement(4);
1938        if ((element4 & 0x01) != 0) {
1939            text += "F9 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1940        } else {
1941            text += "F9 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1942        }
1943        if ((element4 & 0x02) != 0) {
1944            text += "F10 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1945        } else {
1946            text += "F10 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1947        }
1948        if ((element4 & 0x04) != 0) {
1949            text += "F11 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1950        } else {
1951            text += "F11 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1952        }
1953        if ((element4 & 0x08) != 0) {
1954            text += "F12 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1955        } else {
1956            text += "F12 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1957        }
1958        return text;
1959    }
1960
1961    private String buildSetFunctionGroup4MonitorString() {
1962        String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X, 4) + " " + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
1963        int element4 = getElement(4);
1964        if ((element4 & 0x01) != 0) {
1965            text += "F13 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1966        } else {
1967            text += "F13 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1968        }
1969        if ((element4 & 0x02) != 0) {
1970            text += "F14 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1971        } else {
1972            text += "F14 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1973        }
1974        if ((element4 & 0x04) != 0) {
1975            text += "F15 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1976        } else {
1977            text += "F15 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1978        }
1979        if ((element4 & 0x08) != 0) {
1980            text += "F16 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1981        } else {
1982            text += "F16 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1983        }
1984        if ((element4 & 0x10) != 0) {
1985            text += "F17 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1986        } else {
1987            text += "F17 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1988        }
1989        if ((element4 & 0x20) != 0) {
1990            text += "F18 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1991        } else {
1992            text += "F18 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1993        }
1994        if ((element4 & 0x40) != 0) {
1995            text += "F19 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
1996        } else {
1997            text += "F19 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
1998        }
1999        if ((element4 & 0x80) != 0) {
2000            text += "F20 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
2001        } else {
2002            text += "F20 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
2003        }
2004        return text;
2005    }
2006
2007    private String buildSetFunctionGroup5MonitorString() {
2008        String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X, 5) + " " + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
2009        int element4 = getElement(4);
2010        if ((element4 & 0x01) != 0) {
2011            text += "F21 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
2012        } else {
2013            text += "F21 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
2014        }
2015        if ((element4 & 0x02) != 0) {
2016            text += "F22 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
2017        } else {
2018            text += "F22 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
2019        }
2020        if ((element4 & 0x04) != 0) {
2021            text += "F23 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
2022        } else {
2023            text += "F23 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
2024        }
2025        if ((element4 & 0x08) != 0) {
2026            text += "F24 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
2027        } else {
2028            text += "F24 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
2029        }
2030        if ((element4 & 0x10) != 0) {
2031            text += "F25 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
2032        } else {
2033            text += "F25 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
2034        }
2035        if ((element4 & 0x20) != 0) {
2036            text += "F26 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
2037        } else {
2038            text += "F26 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
2039        }
2040        if ((element4 & 0x40) != 0) {
2041            text += "F27 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
2042        } else {
2043            text += "F27 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
2044        }
2045        if ((element4 & 0x80) != 0) {
2046            text += "F28 " + Bundle.getMessage(POWER_STATE_ON) + "; ";
2047        } else {
2048            text += "F28 " + Bundle.getMessage(POWER_STATE_OFF) + "; ";
2049        }
2050        return text;
2051    }
2052
2053        private String buildSetFunctionGroup1MomentaryMonitorString() {
2054            String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X_MOMENTARY, 1) + " "
2055                + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
2056        int element4 = getElement(4);
2057        if ((element4 & 0x10) == 0) {
2058            text += "F0 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2059        } else {
2060            text += "F0 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2061        }
2062        if ((element4 & 0x01) == 0) {
2063            text += "F1 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2064        } else {
2065            text += "F1 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2066        }
2067        if ((element4 & 0x02) == 0) {
2068            text += "F2 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2069        } else {
2070            text += "F2 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2071        }
2072        if ((element4 & 0x04) == 0) {
2073            text += "F3 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2074        } else {
2075            text += "F3 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2076        }
2077        if ((element4 & 0x08) == 0) {
2078            text += "F4 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2079        } else {
2080            text += "F4 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2081        }
2082        return text;
2083    }
2084
2085    private String buildSetFunctionGroup2MomentaryMonitorString() {
2086        String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X_MOMENTARY, 2) + " "
2087                + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
2088        int element4 = getElement(4);
2089        if ((element4 & 0x01) == 0) {
2090            text += "F5 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2091        } else {
2092            text += "F5 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2093        }
2094        if ((element4 & 0x02) == 0) {
2095            text += "F6 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2096        } else {
2097            text += "F6 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2098        }
2099        if ((element4 & 0x04) == 0) {
2100            text += "F7 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2101        } else {
2102            text += "F7 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2103        }
2104        if ((element4 & 0x08) == 0) {
2105            text += "F8 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2106        } else {
2107            text += "F8 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2108        }
2109        return text;
2110    }
2111
2112    private String buildSetFunctionGroup3MomentaryMonitorString() {
2113        String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X_MOMENTARY, 3) + " " + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
2114        int element4 = getElement(4);
2115        if ((element4 & 0x01) == 0) {
2116            text += "F9 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2117        } else {
2118            text += "F9 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2119        }
2120        if ((element4 & 0x02) == 0) {
2121            text += "F10 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2122        } else {
2123            text += "F10 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2124        }
2125        if ((element4 & 0x04) == 0) {
2126            text += "F11 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2127        } else {
2128            text += "F11 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2129        }
2130        if ((element4 & 0x08) == 0) {
2131            text += "F12 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2132        } else {
2133            text += "F12 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2134        }
2135        return text;
2136    }
2137
2138
2139    private String buildSetFunctionGroup4MomentaryMonitorString() {
2140        String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X_MOMENTARY, 4) + " "
2141                + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
2142        int element4 = getElement(4);
2143        if ((element4 & 0x01) == 0) {
2144            text += "F13 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2145        } else {
2146            text += "F13 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2147        }
2148        if ((element4 & 0x02) == 0) {
2149            text += "F14 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2150        } else {
2151            text += "F14 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2152        }
2153        if ((element4 & 0x04) == 0) {
2154            text += "F15 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2155        } else {
2156            text += "F15 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2157        }
2158        if ((element4 & 0x08) == 0) {
2159            text += "F16 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2160        } else {
2161            text += "F16 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2162        }
2163        if ((element4 & 0x10) == 0) {
2164            text += "F17 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2165        } else {
2166            text += "F17 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2167        }
2168        if ((element4 & 0x20) == 0) {
2169            text += "F18 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2170        } else {
2171            text += "F18 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2172        }
2173        if ((element4 & 0x40) == 0) {
2174            text += "F19 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2175        } else {
2176            text += "F19 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2177        }
2178        if ((element4 & 0x80) == 0) {
2179            text += "F20 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2180        } else {
2181            text += "F20 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2182        }
2183        return text;
2184}
2185
2186    private String buildSetFunctionGroup5MomentaryMonitorString() {
2187        String text = Bundle.getMessage(X_NET_MESSAGE_SET_FUNCTION_GROUP_X_MOMENTARY, 5) + " " + LenzCommandStation.calcLocoAddress(getElement(2), getElement(3)) + " ";
2188        int element4 = getElement(4);
2189        if ((element4 & 0x01) == 0) {
2190            text += "F21 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2191        } else {
2192            text += "F21 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2193        }
2194        if ((element4 & 0x02) == 0) {
2195            text += "F22 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2196        } else {
2197            text += "F22 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2198        }
2199        if ((element4 & 0x04) == 0) {
2200            text += "F23 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2201        } else {
2202            text += "F23 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2203        }
2204        if ((element4 & 0x08) == 0) {
2205            text += "F24 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2206        } else {
2207            text += "F24 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2208        }
2209        if ((element4 & 0x10) == 0) {
2210            text += "F25 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2211        } else {
2212            text += "F25 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2213        }
2214        if ((element4 & 0x20) == 0) {
2215            text += "F26 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2216        } else {
2217            text += "F26 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2218        }
2219        if ((element4 & 0x40) == 0) {
2220            text += "F27 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2221        } else {
2222            text += "F27 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2223        }
2224        if ((element4 & 0x80) == 0) {
2225            text += "F28 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; ";
2226        } else {
2227            text += "F28 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; ";
2228        }
2229        return text;
2230    }
2231
2232    // initialize logging
2233    private static final Logger log = LoggerFactory.getLogger(XNetMessage.class);
2234
2235}