001package jmri.jmrix.lenz;
002
003import java.io.Serializable;
004import java.lang.reflect.Constructor;
005import java.lang.reflect.InvocationTargetException;
006import java.util.ArrayList;
007import java.util.List;
008import java.util.Set;
009
010import org.reflections.Reflections;
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013import jmri.SpeedStepMode;
014
015/**
016 * Represents a single command or response on the XpressNet.
017 * <p>
018 * Content is represented with ints to avoid the problems with sign-extension
019 * that bytes have, and because a Java char is actually a variable number of
020 * bytes in Unicode.
021 *
022 * @author Bob Jacobsen Copyright (C) 2002
023 * @author Paul Bender Copyright (C) 2003-2010
024  *
025 */
026public class XNetMessage extends jmri.jmrix.AbstractMRMessage implements Serializable {
027
028    private static int _nRetries = 5;
029
030    /* According to the specification, XpressNet has a maximum timing
031     interval of 500 milliseconds during normal communications */
032    protected static final int XNetProgrammingTimeout = 10000;
033    private static int XNetMessageTimeout = 5000;
034
035    /**
036     * Create a new object, representing a specific-length message.
037     *
038     * @param len Total bytes in message, including opcode and error-detection
039     *            byte.  Valid values are 0 to 15 (0x0 to 0xF).
040     */
041    public XNetMessage(int len) {
042        super(len);
043        if (len > 15 ) {  // only check upper bound. Lower bound checked in
044                          // super call.
045            log.error("Invalid length in ctor: {}", len);
046            throw new IllegalArgumentException("Invalid length in ctor: " + len);
047        }
048        setBinary(true);
049        setRetries(_nRetries);
050        setTimeout(XNetMessageTimeout);
051        _nDataChars = len;
052    }
053
054    /**
055     * Create a new object, that is a copy of an existing message.
056     *
057     * @param message an existing XpressNet message
058     */
059    public XNetMessage(XNetMessage message) {
060        super(message);
061        setBinary(true);
062        setRetries(_nRetries);
063        setTimeout(XNetMessageTimeout);
064    }
065
066    /**
067     * Create an XNetMessage from an XNetReply.
068     * @param message existing XNetReply.
069     */
070    public XNetMessage(XNetReply message) {
071        super(message.getNumDataElements());
072        setBinary(true);
073        setRetries(_nRetries);
074        setTimeout(XNetMessageTimeout);
075        for (int i = 0; i < message.getNumDataElements(); i++) {
076            setElement(i, message.getElement(i));
077        }
078    }
079
080    /**
081     * Create an XNetMessage from a String containing bytes.
082     * @param s string containing data bytes.
083     */
084    public XNetMessage(String s) {
085        setBinary(true);
086        setRetries(_nRetries);
087        setTimeout(XNetMessageTimeout);
088        // gather bytes in result
089        byte[] b = jmri.util.StringUtil.bytesFromHexString(s);
090        if (b.length == 0) {
091            // no such thing as a zero-length message
092            _nDataChars = 0;
093            _dataChars = null;
094            return;
095        }
096        _nDataChars = b.length;
097        _dataChars = new int[_nDataChars];
098        for (int i = 0; i < b.length; i++) {
099            setElement(i, b[i]);
100        }
101    }
102
103    // note that the opcode is part of the message, so we treat it
104    // directly
105    // WARNING: use this only with opcodes that have a variable number
106    // of arguments following included. Otherwise, just use setElement
107    @Override
108    public void setOpCode(int i) {
109        if (i > 0xF || i < 0) {
110            log.error("Opcode invalid: {}", i);
111        }
112        setElement(0, ((i * 16) & 0xF0) | ((getNumDataElements() - 2) & 0xF));
113    }
114
115    @Override
116    public int getOpCode() {
117        return (getElement(0) / 16) & 0xF;
118    }
119
120    /**
121     * Get a String representation of the op code in hex.
122     * {@inheritDoc}
123     */
124    @Override
125    public String getOpCodeHex() {
126        return "0x" + Integer.toHexString(getOpCode());
127    }
128
129    /**
130     * Check whether the message has a valid parity.
131     * @return true if parity valid, else false.
132     */
133    public boolean checkParity() {
134        int len = getNumDataElements();
135        int chksum = 0x00;  /* the seed */
136
137        int loop;
138
139        for (loop = 0; loop < len - 1; loop++) {  // calculate contents for data part
140            chksum ^= getElement(loop);
141        }
142        return ((chksum & 0xFF) == getElement(len - 1));
143    }
144
145    public void setParity() {
146        int len = getNumDataElements();
147        int chksum = 0x00;  /* the seed */
148
149        int loop;
150
151        for (loop = 0; loop < len - 1; loop++) {  // calculate contents for data part
152            chksum ^= getElement(loop);
153        }
154        setElement(len - 1, chksum & 0xFF);
155    }
156
157    /**
158     * Get an integer representation of a BCD value.
159     * @param n message element index.
160     * @return integer of BCD.
161     */
162    public Integer getElementBCD(int n) {
163        return Integer.decode(Integer.toHexString(getElement(n)));
164    }
165
166    /**
167     * Get the message length.
168     * @return message length.
169     */
170    public int length() {
171        return _nDataChars;
172    }
173
174    /**
175     * Set the default number of retries for an XpressNet message.
176     *
177     * @param t number of retries to attempt
178     */
179    public static void setXNetMessageRetries(int t) {
180        _nRetries = t;
181    }
182
183    /**
184     * Set the default timeout for an XpressNet message.
185     *
186     * @param t Timeout in milliseconds
187     */
188    public static void setXNetMessageTimeout(int t) {
189        XNetMessageTimeout = t;
190    }
191
192    /**
193     * Most messages are sent with a reply expected, but
194     * we have a few that we treat as though the reply is always
195     * a broadcast message, because the reply usually comes to us
196     * that way.
197     * {@inheritDoc}
198     */
199    @Override
200    public boolean replyExpected() {
201        return !broadcastReply;
202    }
203
204    private boolean broadcastReply = false;
205
206    /**
207     * Tell the traffic controller we expect this
208     * message to have a broadcast reply.
209     */
210    public void setBroadcastReply() {
211        broadcastReply = true;
212    }
213
214    // decode messages of a particular form
215    // create messages of a particular form
216
217    /**
218     * Encapsulate an NMRA DCC packet in an XpressNet message.
219     * <p>
220     * On Current (v3.5) Lenz command stations, the Operations Mode
221     *     Programming Request is implemented by sending a packet directly
222     *     to the rails.  This packet is not checked by the XpressNet
223     *     protocol, and is just the track packet with an added header
224     *     byte.
225     *     <p>
226     *     NOTE: Lenz does not say this will work for anything but 5
227     *     byte packets.
228     * @param packet byte array containing packet data elements.
229     * @return message to send DCC packet.
230     */
231    public static XNetMessage getNMRAXNetMsg(byte[] packet) {
232        XNetMessage msg = new XNetMessage(packet.length + 2);
233        msg.setOpCode((XNetConstants.OPS_MODE_PROG_REQ & 0xF0) >> 4);
234        msg.setElement(1, 0x30);
235        for (int i = 0; i < packet.length; i++) {
236            msg.setElement((i + 2), packet[i] & 0xff);
237        }
238        msg.setParity();
239        return (msg);
240    }
241
242    /*
243     * The next group of routines are used by Feedback and/or turnout
244     * control code.  These are used in multiple places within the code,
245     * so they appear here.
246     */
247
248    /**
249     * Generate a message to change turnout state.
250     * @param pNumber address number.
251     * @param pClose true if set turnout closed.
252     * @param pThrow true if set turnout thrown.
253     * @param pOn accessory line true for on, false off.
254     * @return message containing turnout command.
255     */
256    public static XNetMessage getTurnoutCommandMsg(int pNumber, boolean pClose,
257            boolean pThrow, boolean pOn) {
258        XNetMessage l = new XNetMessage(4);
259        l.setElement(0, XNetConstants.ACC_OPER_REQ);
260
261        // compute address byte fields
262        int hiadr = (pNumber - 1) / 4;
263        int loadr = ((pNumber - 1) - hiadr * 4) * 2;
264        // The MSB of the upper nibble is required to be set on
265        // The rest of the upper nibble should be zeros.
266        // The MSB of the lower nibble says weather or not the
267        // accessory line should be "on" or "off"
268        if (!pOn) {
269            loadr |= 0x80;
270        } else {
271            loadr |= 0x88;
272        }
273        // If we are sending a "throw" command, we set the LSB of the
274        // lower nibble on, otherwise, we leave it "off".
275        if (pThrow) {
276            loadr |= 0x01;
277        }
278
279        // we don't know how to command both states right now!
280        if (pClose && pThrow) {
281            log.error("XpressNet turnout logic can't handle both THROWN and CLOSED yet");
282        }
283        // store and send
284        l.setElement(1, hiadr);
285        l.setElement(2, loadr);
286        l.setParity(); // Set the parity bit
287
288        return l;
289    }
290
291    /**
292     * Generate a message to receive the feedback information for an upper or
293     * lower nibble of the feedback address in question.
294     * @param pNumber feedback address.
295     * @param pLowerNibble true for upper nibble, else false for lower.
296     * @return feedback request message.
297     */
298    public static XNetMessage getFeedbackRequestMsg(int pNumber,
299            boolean pLowerNibble) {
300        XNetMessage l = new XNetMessage(4);
301        l.setBroadcastReply();  // we the message reply as a broadcast message.
302        l.setElement(0, XNetConstants.ACC_INFO_REQ);
303
304        // compute address byte field
305        l.setElement(1, (pNumber - 1) / 4);
306        // The MSB of the upper nibble is required to be set on
307        // The rest of the upper nibble should be zeros.
308        // The LSB of the lower nibble says weather or not the
309        // information request is for the upper or lower nibble.
310        if (pLowerNibble) {
311            l.setElement(2, 0x80);
312        } else {
313            l.setElement(2, 0x81);
314        }
315        l.setParity(); // Set the parity bit
316        return l;
317    }
318
319    /*
320     * Next, we have some messages related to sending programming commands.
321     */
322
323    public static XNetMessage getServiceModeResultsMsg() {
324        XNetMessage m = new XNetMessage(3);
325        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
326        m.setTimeout(XNetProgrammingTimeout);
327        m.setElement(0, XNetConstants.CS_REQUEST);
328        m.setElement(1, XNetConstants.SERVICE_MODE_CSRESULT);
329        m.setParity(); // Set the parity bit
330        return m;
331    }
332
333    public static XNetMessage getExitProgModeMsg() {
334        XNetMessage m = new XNetMessage(3);
335        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
336        m.setElement(0, XNetConstants.CS_REQUEST);
337        m.setElement(1, XNetConstants.RESUME_OPS);
338        m.setParity();
339        return m;
340    }
341
342    public static XNetMessage getReadPagedCVMsg(int cv) {
343        XNetMessage m = new XNetMessage(4);
344        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
345        m.setTimeout(XNetProgrammingTimeout);
346        m.setElement(0, XNetConstants.PROG_READ_REQUEST);
347        m.setElement(1, XNetConstants.PROG_READ_MODE_PAGED);
348        m.setElement(2, (0xff & cv));
349        m.setParity(); // Set the parity bit
350        return m;
351    }
352
353    public static XNetMessage getReadDirectCVMsg(int cv) {
354        XNetMessage m = new XNetMessage(4);
355        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
356        m.setTimeout(XNetProgrammingTimeout);
357        m.setElement(0, XNetConstants.PROG_READ_REQUEST);
358        if (cv < 0x0100) /* Use the version 3.5 command for CVs <= 256 */ {
359            m.setElement(1, XNetConstants.PROG_READ_MODE_CV);
360        } else if (cv == 0x0400) /* For CV1024, we need to send the version 3.6
361         command for CVs 1 to 256, sending a 0 for the
362         CV */ {
363            m.setElement(1, XNetConstants.PROG_READ_MODE_CV_V36);
364        } else /* and the version 3.6 command for CVs > 256 */ {
365            m.setElement(1, XNetConstants.PROG_READ_MODE_CV_V36 | ((cv & 0x0300) >> 8));
366        }
367        m.setElement(2, (0xff & cv));
368        m.setParity(); // Set the parity bit
369        return m;
370    }
371
372    public static XNetMessage getWritePagedCVMsg(int cv, int val) {
373        XNetMessage m = new XNetMessage(5);
374        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
375        m.setTimeout(XNetProgrammingTimeout);
376        m.setElement(0, XNetConstants.PROG_WRITE_REQUEST);
377        m.setElement(1, XNetConstants.PROG_WRITE_MODE_PAGED);
378        m.setElement(2, (0xff & cv));
379        m.setElement(3, val);
380        m.setParity(); // Set the parity bit
381        return m;
382    }
383
384    public static XNetMessage getWriteDirectCVMsg(int cv, int val) {
385        XNetMessage m = new XNetMessage(5);
386        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
387        m.setTimeout(XNetProgrammingTimeout);
388        m.setElement(0, XNetConstants.PROG_WRITE_REQUEST);
389        if (cv < 0x0100) /* Use the version 3.5 command for CVs <= 256 */ {
390            m.setElement(1, XNetConstants.PROG_WRITE_MODE_CV);
391        } else if (cv == 0x0400) /* For CV1024, we need to send the version 3.6
392         command for CVs 1 to 256, sending a 0 for the
393         CV */ {
394            m.setElement(1, XNetConstants.PROG_WRITE_MODE_CV_V36);
395        } else /* and the version 3.6 command for CVs > 256 */ {
396            m.setElement(1, XNetConstants.PROG_WRITE_MODE_CV_V36 | ((cv & 0x0300) >> 8));
397        }
398        m.setElement(2, (0xff & cv));
399        m.setElement(3, val);
400        m.setParity(); // Set the parity bit
401        return m;
402    }
403
404    public static XNetMessage getReadRegisterMsg(int reg) {
405        if (reg > 8) {
406            log.error("register number too large: {}",reg);
407        }
408        XNetMessage m = new XNetMessage(4);
409        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
410        m.setTimeout(XNetProgrammingTimeout);
411        m.setElement(0, XNetConstants.PROG_READ_REQUEST);
412        m.setElement(1, XNetConstants.PROG_READ_MODE_REGISTER);
413        m.setElement(2, (0x0f & reg));
414        m.setParity(); // Set the parity bit
415        return m;
416    }
417
418    public static XNetMessage getWriteRegisterMsg(int reg, int val) {
419        if (reg > 8) {
420            log.error("register number too large: {}",reg);
421        }
422        XNetMessage m = new XNetMessage(5);
423        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
424        m.setTimeout(XNetProgrammingTimeout);
425        m.setElement(0, XNetConstants.PROG_WRITE_REQUEST);
426        m.setElement(1, XNetConstants.PROG_WRITE_MODE_REGISTER);
427        m.setElement(2, (0x0f & reg));
428        m.setElement(3, val);
429        m.setParity(); // Set the parity bit
430        return m;
431    }
432
433    public static XNetMessage getWriteOpsModeCVMsg(int AH, int AL, int cv, int val) {
434        XNetMessage m = new XNetMessage(8);
435        m.setElement(0, XNetConstants.OPS_MODE_PROG_REQ);
436        m.setElement(1, XNetConstants.OPS_MODE_PROG_WRITE_REQ);
437        m.setElement(2, AH);
438        m.setElement(3, AL);
439        /* Element 4 is 0xEC + the upper two  bits of the 10 bit CV address.
440         NOTE: This is the track packet CV, not the human readable CV, so
441         its value actually is one less than what we normally think of it as.*/
442        int temp = (cv - 1) & 0x0300;
443        temp = temp / 0x00FF;
444        m.setElement(4, 0xEC + temp);
445        /* Element 5 is the lower 8 bits of the cv */
446        m.setElement(5, ((0x00ff & cv) - 1));
447        m.setElement(6, val);
448        m.setParity(); // Set the parity bit
449        return m;
450    }
451
452    public static XNetMessage getVerifyOpsModeCVMsg(int AH, int AL, int cv, int val) {
453        XNetMessage m = new XNetMessage(8);
454        m.setElement(0, XNetConstants.OPS_MODE_PROG_REQ);
455        m.setElement(1, XNetConstants.OPS_MODE_PROG_READ_REQ);
456        m.setElement(2, AH);
457        m.setElement(3, AL);
458        /* Element 4 is 0xE4 + the upper two  bits of the 10 bit CV address.
459         NOTE: This is the track packet CV, not the human readable CV, so
460         its value actually is one less than what we normally think of it as.*/
461        int temp = (cv - 1) & 0x0300;
462        temp = temp / 0x00FF;
463        m.setElement(4, 0xE4 + temp);
464        /* Element 5 is the lower 8 bits of the cv */
465        m.setElement(5, ((0x00ff & cv) - 1));
466        m.setElement(6, val);
467        m.setParity(); // Set the parity bit
468        return m;
469    }
470
471    public static XNetMessage getBitWriteOpsModeCVMsg(int AH, int AL, int cv, int bit, boolean value) {
472        XNetMessage m = new XNetMessage(8);
473        m.setElement(0, XNetConstants.OPS_MODE_PROG_REQ);
474        m.setElement(1, XNetConstants.OPS_MODE_PROG_WRITE_REQ);
475        m.setElement(2, AH);
476        m.setElement(3, AL);
477        /* Element 4 is 0xE8 + the upper two  bits of the 10 bit CV address.
478         NOTE: This is the track packet CV, not the human readable CV, so
479         its value actually is one less than what we normally think of it as.*/
480        int temp = (cv - 1) & 0x0300;
481        temp = temp / 0x00FF;
482        m.setElement(4, 0xE8 + temp);
483        /* Element 5 is the lower 8 bits of the cv */
484        m.setElement(5, ((0x00ff & cv) - 1));
485        /* Since this is a bit write, Element 6 is:
486         0xE0 +
487         bit 3 is the value to write
488         bit's 0-2 are the location of the bit we are changing */
489        if (value) {
490            m.setElement(6, ((0xe8) | (bit & 0xff)));
491        } else // value == false
492        {
493            m.setElement(6, ((0xe0) | (bit & 0xff)));
494        }
495        m.setParity(); // Set the parity bit
496        return m;
497    }
498
499    public static XNetMessage getBitVerifyOpsModeCVMsg(int AH, int AL, int cv, int bit, boolean value) {
500        XNetMessage m = new XNetMessage(8);
501        m.setElement(0, XNetConstants.OPS_MODE_PROG_REQ);
502        m.setElement(1, XNetConstants.OPS_MODE_PROG_WRITE_REQ);
503        m.setElement(2, AH);
504        m.setElement(3, AL);
505        /* Element 4 is 0xE8 + the upper two  bits of the 10 bit CV address.
506         NOTE: This is the track packet CV, not the human readable CV, so
507         its value actually is one less than what we normally think of it as.*/
508        int temp = (cv - 1) & 0x0300;
509        temp = temp / 0x00FF;
510        m.setElement(4, 0xE8 + temp);
511        /* Element 5 is the lower 8 bits of the cv */
512        m.setElement(5, ((0x00ff & cv) - 1));
513        /* Since this is a bit verify, Element 6 is:
514         0xF0 +
515         bit 3 is the value to write
516         bit's 0-2 are the location of the bit we are changing */
517        if (value) {
518            m.setElement(6, ((0xf8) | (bit & 0xff)));
519        } else // value == false
520        {
521            m.setElement(6, ((0xf0) | (bit & 0xff)));
522        }
523        m.setParity(); // Set the parity bit
524        return m;
525    }
526
527    public static XNetMessage getOpsModeResultsMsg() {
528        XNetMessage m = new XNetMessage(3);
529        m.setTimeout(XNetProgrammingTimeout);
530        m.setElement(0, XNetConstants.CS_REQUEST);
531        m.setElement(1, XNetConstants.OPS_MODE_CSRESULT);
532        m.setParity(); // Set the parity bit
533        return m;
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_MOMENTARY);
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_MOMENTARY);
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_MOMENTARY);
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        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP4,
1160                f13, f14, f15, f16, f17, f18, f19, f20);
1161    }
1162
1163    /**
1164     * Generate a Function Group Four Set Momentary Function message.
1165     *
1166     * @param address the locomotive address
1167     * @param f13 is true if f13 is Momentary
1168     * @param f14 is true if f14 is Momentary
1169     * @param f15 is true if f15 is Momentary
1170     * @param f16 is true if f18 is Momentary
1171     * @param f17 is true if f17 is Momentary
1172     * @param f18 is true if f18 is Momentary
1173     * @param f19 is true if f19 is Momentary
1174     * @param f20 is true if f20 is Momentary
1175     * @return set momentary function group 4 message.
1176     */
1177    public static XNetMessage getFunctionGroup4SetMomMsg(int address,
1178            boolean f13,
1179            boolean f14,
1180            boolean f15,
1181            boolean f16,
1182            boolean f17,
1183            boolean f18,
1184            boolean f19,
1185            boolean f20) {
1186        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP4_MOMENTARY,
1187                f13, f14, f15, f16, f17, f18, f19, f20);
1188    }
1189
1190    /**
1191     * Generate a Function Group Five Operation Request message.
1192     *
1193     * @param address the locomotive address
1194     * @param f21 is true if f21 is on, false if f21 is off
1195     * @param f22 is true if f22 is on, false if f22 is off
1196     * @param f23 is true if f23 is on, false if f23 is off
1197     * @param f24 is true if f24 is on, false if f24 is off
1198     * @param f25 is true if f25 is on, false if f25 is off
1199     * @param f26 is true if f26 is on, false if f26 is off
1200     * @param f27 is true if f27 is on, false if f27 is off
1201     * @param f28 is true if f28 is on, false if f28 is off
1202     * @return set function group 5 message.
1203     */
1204    public static XNetMessage getFunctionGroup5OpsMsg(int address,
1205            boolean f21,
1206            boolean f22,
1207            boolean f23,
1208            boolean f24,
1209            boolean f25,
1210            boolean f26,
1211            boolean f27,
1212            boolean f28) {
1213        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP5,
1214            f21, f22, f23, f24, f25, f26, f27, f28);
1215    }
1216
1217    /**
1218     * Generate a Function Group Five Set Momentary Function message.
1219     *
1220     * @param address the locomotive address
1221     * @param f21 is true if f21 is momentary
1222     * @param f22 is true if f22 is momentary
1223     * @param f23 is true if f23 is momentary
1224     * @param f24 is true if f24 is momentary
1225     * @param f25 is true if f25 is momentary
1226     * @param f26 is true if f26 is momentary
1227     * @param f27 is true if f27 is momentary
1228     * @param f28 is true if f28 is momentary
1229     * @return set momentary function group 5 message.
1230     */
1231    public static XNetMessage getFunctionGroup5SetMomMsg(int address,
1232            boolean f21,
1233            boolean f22,
1234            boolean f23,
1235            boolean f24,
1236            boolean f25,
1237            boolean f26,
1238            boolean f27,
1239            boolean f28) {
1240        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP5_MOMENTARY,
1241            f21, f22, f23, f24, f25, f26, f27, f28);
1242    }
1243
1244    // Generate a Function Group Operation Request message for some specific case.
1245    private static XNetMessage getFunctionGroupNOpsMsg(int address, int byte1,
1246            boolean fA,
1247            boolean fB,
1248            boolean fC,
1249            boolean fD,
1250            boolean fE,
1251            boolean fF,
1252            boolean fG,
1253            boolean fH) {
1254        XNetMessage msg = new XNetMessage(6);
1255        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
1256        msg.setElement(1, byte1);
1257        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
1258        // set to the upper byte of the  DCC address
1259        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
1260        // set to the lower byte of the DCC address
1261        // Now, we need to figure out what to send in element 3
1262        int element4value = 0;
1263        if (fA) {
1264            element4value += 1;
1265        }
1266        if (fB) {
1267            element4value += 2;
1268        }
1269        if (fC) {
1270            element4value += 4;
1271        }
1272        if (fD) {
1273            element4value += 8;
1274        }
1275        if (fE) {
1276            element4value += 16;
1277        }
1278        if (fF) {
1279            element4value += 32;
1280        }
1281        if (fG) {
1282            element4value += 64;
1283        }
1284        if (fH) {
1285            element4value += 128;
1286        }
1287        msg.setElement(4, element4value);
1288        msg.setParity(); // Set the parity bit
1289        return msg;
1290    }
1291
1292    public static XNetMessage getFunctionGroup6OpsMsg(int address,
1293            boolean fA, boolean fB, boolean fC, boolean fD,
1294            boolean fE, boolean fF, boolean fG, boolean fH) {
1295        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP6,
1296            fA, fB, fC, fD, fE, fF, fG, fH);
1297    }
1298
1299    public static XNetMessage getFunctionGroup7OpsMsg(int address,
1300            boolean fA, boolean fB, boolean fC, boolean fD,
1301            boolean fE, boolean fF, boolean fG, boolean fH) {
1302        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP7,
1303            fA, fB, fC, fD, fE, fF, fG, fH);
1304    }
1305
1306    public static XNetMessage getFunctionGroup8OpsMsg(int address,
1307            boolean fA, boolean fB, boolean fC, boolean fD,
1308            boolean fE, boolean fF, boolean fG, boolean fH) {
1309        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP8,
1310            fA, fB, fC, fD, fE, fF, fG, fH);
1311    }
1312
1313    public static XNetMessage getFunctionGroup9OpsMsg(int address,
1314            boolean fA, boolean fB, boolean fC, boolean fD,
1315            boolean fE, boolean fF, boolean fG, boolean fH) {
1316        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP9,
1317            fA, fB, fC, fD, fE, fF, fG, fH);
1318    }
1319
1320    public static XNetMessage getFunctionGroup10OpsMsg(int address,
1321            boolean fA, boolean fB, boolean fC, boolean fD,
1322            boolean fE, boolean fF, boolean fG, boolean fH) {
1323        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP10,
1324            fA, fB, fC, fD, fE, fF, fG, fH);
1325    }
1326
1327    public static XNetMessage getFunctionGroup6SetMomMsg(int address,
1328            boolean fA, boolean fB, boolean fC, boolean fD,
1329            boolean fE, boolean fF, boolean fG, boolean fH) {
1330        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP6_MOMENTARY,
1331            fA, fB, fC, fD, fE, fF, fG, fH);
1332    }
1333
1334    public static XNetMessage getFunctionGroup7SetMomMsg(int address,
1335            boolean fA, boolean fB, boolean fC, boolean fD,
1336            boolean fE, boolean fF, boolean fG, boolean fH) {
1337        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP7_MOMENTARY,
1338            fA, fB, fC, fD, fE, fF, fG, fH);
1339    }
1340
1341    public static XNetMessage getFunctionGroup8SetMomMsg(int address,
1342            boolean fA, boolean fB, boolean fC, boolean fD,
1343            boolean fE, boolean fF, boolean fG, boolean fH) {
1344        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP8_MOMENTARY,
1345            fA, fB, fC, fD, fE, fF, fG, fH);
1346    }
1347
1348    public static XNetMessage getFunctionGroup9SetMomMsg(int address,
1349            boolean fA, boolean fB, boolean fC, boolean fD,
1350            boolean fE, boolean fF, boolean fG, boolean fH) {
1351        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP9_MOMENTARY,
1352            fA, fB, fC, fD, fE, fF, fG, fH);
1353    }
1354
1355    public static XNetMessage getFunctionGroup10SetMomMsg(int address,
1356            boolean fA, boolean fB, boolean fC, boolean fD,
1357            boolean fE, boolean fF, boolean fG, boolean fH) {
1358        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP10_MOMENTARY,
1359            fA, fB, fC, fD, fE, fF, fG, fH);
1360    }
1361
1362    /**
1363     * Build a Resume operations Message.
1364     * @return resume message.
1365     */
1366    public static XNetMessage getResumeOperationsMsg() {
1367        XNetMessage msg = new XNetMessage(3);
1368        msg.setElement(0, XNetConstants.CS_REQUEST);
1369        msg.setElement(1, XNetConstants.RESUME_OPS);
1370        msg.setParity();
1371        return (msg);
1372    }
1373
1374    /**
1375     * Build an EmergencyOff Message.
1376     * @return emergency off message.
1377     */
1378    public static XNetMessage getEmergencyOffMsg() {
1379        XNetMessage msg = new XNetMessage(3);
1380        msg.setElement(0, XNetConstants.CS_REQUEST);
1381        msg.setElement(1, XNetConstants.EMERGENCY_OFF);
1382        msg.setParity();
1383        return (msg);
1384    }
1385
1386    /**
1387     * Build an EmergencyStop Message.
1388     * @return emergency stop message.
1389     */
1390    public static XNetMessage getEmergencyStopMsg() {
1391        XNetMessage msg = new XNetMessage(2);
1392        msg.setElement(0, XNetConstants.ALL_ESTOP);
1393        msg.setParity();
1394        return (msg);
1395    }
1396
1397    /**
1398     * Generate the message to request the Command Station Hardware/Software
1399     * Version.
1400     * @return message to request CS hardware and software version.
1401     */
1402    public static XNetMessage getCSVersionRequestMessage() {
1403        XNetMessage msg = new XNetMessage(3);
1404        msg.setElement(0, XNetConstants.CS_REQUEST);
1405        msg.setElement(1, XNetConstants.CS_VERSION);
1406        msg.setParity(); // Set the parity bit
1407        return msg;
1408    }
1409
1410    /**
1411     * Generate the message to request the Command Station Status.
1412     * @return message to request CS status.
1413     */
1414    public static XNetMessage getCSStatusRequestMessage() {
1415        XNetMessage msg = new XNetMessage(3);
1416        msg.setElement(0, XNetConstants.CS_REQUEST);
1417        msg.setElement(1, XNetConstants.CS_STATUS);
1418        msg.setParity(); // Set the parity bit
1419        return msg;
1420    }
1421
1422    /**
1423     * Generate the message to set the Command Station to Auto or Manual restart
1424     * mode.
1425     * @param autoMode true if auto, false for manual.
1426     * @return message to set CS restart mode.
1427     */
1428    public static XNetMessage getCSAutoStartMessage(boolean autoMode) {
1429        XNetMessage msg = new XNetMessage(4);
1430        msg.setElement(0, XNetConstants.CS_SET_POWERMODE);
1431        msg.setElement(1, XNetConstants.CS_SET_POWERMODE);
1432        if (autoMode) {
1433            msg.setElement(2, XNetConstants.CS_POWERMODE_AUTO);
1434        } else {
1435            msg.setElement(2, XNetConstants.CS_POWERMODE_MANUAL);
1436        }
1437        msg.setParity(); // Set the parity bit
1438        return msg;
1439    }
1440
1441    /**
1442     * Generate the message to request the Computer Interface Hardware/Software
1443     * Version.
1444     * @return message to request interface hardware and software version.
1445     */
1446    public static XNetMessage getLIVersionRequestMessage() {
1447        XNetMessage msg = new XNetMessage(2);
1448        msg.setElement(0, XNetConstants.LI_VERSION_REQUEST);
1449        msg.setParity(); // Set the parity bit
1450        return msg;
1451    }
1452
1453    /**
1454     * Generate the message to set or request the Computer Interface Address.
1455     *
1456     * @param address Interface address (0-31). Send invalid address to request
1457     *                the address (32-255).
1458     * @return message to set or request interface address.
1459     */
1460    public static XNetMessage getLIAddressRequestMsg(int address) {
1461        XNetMessage msg = new XNetMessage(4);
1462        msg.setElement(0, XNetConstants.LI101_REQUEST);
1463        msg.setElement(1, XNetConstants.LI101_REQUEST_ADDRESS);
1464        msg.setElement(2, address);
1465        msg.setParity(); // Set the parity bit
1466        return msg;
1467    }
1468
1469    /**
1470     * Generate the message to set or request the Computer Interface speed.
1471     *
1472     * @param speed 1 is 19,200bps, 2 is 38,400bps, 3 is 57,600bps, 4 is
1473     *              115,200bps. Send invalid speed to request the current
1474     *              setting.
1475     * @return message for set / request interface speed.
1476     */
1477    public static XNetMessage getLISpeedRequestMsg(int speed) {
1478        XNetMessage msg = new XNetMessage(4);
1479        msg.setElement(0, XNetConstants.LI101_REQUEST);
1480        msg.setElement(1, XNetConstants.LI101_REQUEST_BAUD);
1481        msg.setElement(2, speed);
1482        msg.setParity(); // Set the parity bit
1483        return msg;
1484    }
1485
1486    private static final List<XPressNetMessageFormatter> formatterList = new ArrayList<>();
1487
1488   /**
1489    * Generate text translations of messages for use in the XpressNet monitor.
1490    *
1491    * @return representation of the XNetMessage as a string.
1492    */
1493    @Override
1494   public String toMonitorString() {
1495        if (formatterList.isEmpty()) {
1496            try {
1497                Reflections reflections = new Reflections("jmri.jmrix");
1498                Set<Class<? extends XPressNetMessageFormatter>> f = reflections.getSubTypesOf(XPressNetMessageFormatter.class);
1499                for (Class<?> c : f) {
1500                    log.debug("Found formatter: {}", f.getClass().getName());
1501                    Constructor<?> ctor = c.getConstructor();
1502                    formatterList.add((XPressNetMessageFormatter) ctor.newInstance());
1503                }
1504            } catch (NoSuchMethodException | SecurityException | InstantiationException |
1505                     IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
1506                log.error("Error instantiating formatter", e);
1507            }
1508        }
1509
1510        return formatterList.stream().filter(f -> f.handlesMessage(this)).findFirst().map(f -> f.formatMessage(this)).orElse(this.toString());
1511    }
1512
1513    // initialize logging
1514    private static final Logger log = LoggerFactory.getLogger(XNetMessage.class);
1515
1516}