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_WRITE_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    /*
528     * Next, we have routines to generate XpressNet Messages for building
529     * and tearing down a consist or a double header.
530     */
531
532    /**
533     * Build a Double Header.
534     *
535     * @param address1 the first address in the consist
536     * @param address2 the second address in the consist.
537     * @return message to build double header.
538     */
539    public static XNetMessage getBuildDoubleHeaderMsg(int address1, int address2) {
540        XNetMessage msg = new XNetMessage(7);
541        msg.setElement(0, XNetConstants.LOCO_DOUBLEHEAD);
542        msg.setElement(1, XNetConstants.LOCO_DOUBLEHEAD_BYTE2);
543        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address1));
544        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address1));
545        msg.setElement(4, LenzCommandStation.getDCCAddressHigh(address2));
546        msg.setElement(5, LenzCommandStation.getDCCAddressLow(address2));
547        msg.setParity();
548        return (msg);
549    }
550
551    /**
552     * Dissolve a Double Header.
553     *
554     * @param address one of the two addresses in the Double Header
555     * @return message to dissolve a double header.
556     */
557    public static XNetMessage getDisolveDoubleHeaderMsg(int address) {
558        // All we have to do is call getBuildDoubleHeaderMsg with the
559        // second address as a zero
560        return (getBuildDoubleHeaderMsg(address, 0));
561    }
562
563    /**
564     * Add a Single address to a specified Advanced consist.
565     *
566     * @param consist the consist address (1-99)
567     * @param address the locomotive address to add.
568     * @param isNormalDir tells us if the locomotive is going forward when
569     * the consist is going forward.
570     * @return message to add address to consist.
571     */
572    public static XNetMessage getAddLocoToConsistMsg(int consist, int address,
573            boolean isNormalDir) {
574        XNetMessage msg = new XNetMessage(6);
575        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
576        if (isNormalDir) {
577            msg.setElement(1, XNetConstants.LOCO_ADD_MULTI_UNIT_REQ);
578        } else {
579            msg.setElement(1, XNetConstants.LOCO_ADD_MULTI_UNIT_REQ | 0x01);
580        }
581        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
582        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
583        msg.setElement(4, consist);
584        msg.setParity();
585        return (msg);
586    }
587
588    /**
589     * Remove a Single address to a specified Advanced consist.
590     *
591     * @param consist the consist address (1-99)
592     * @param address the locomotive address to remove
593     * @return message to remove single address from consist.
594     */
595    public static XNetMessage getRemoveLocoFromConsistMsg(int consist, int address) {
596        XNetMessage msg = new XNetMessage(6);
597        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
598        msg.setElement(1, XNetConstants.LOCO_REM_MULTI_UNIT_REQ);
599        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
600        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
601        msg.setElement(4, consist);
602        msg.setParity();
603        return (msg);
604    }
605
606
607    /*
608     * Next, we have routines to generate XpressNet Messages for search
609     * and manipulation of the Command Station Database
610     */
611
612    /**
613     * Given a locomotive address, search the database for the next
614     * member.
615     * (if the Address is zero start at the beginning of the database).
616     *
617     * @param address is the locomotive address
618     * @param searchForward indicates to search the database Forward if
619     * true, or backwards if False
620     * @return message to request next address.
621     */
622    public static XNetMessage getNextAddressOnStackMsg(int address, boolean searchForward) {
623        XNetMessage msg = new XNetMessage(5);
624        msg.setElement(0, XNetConstants.LOCO_STATUS_REQ);
625        if (searchForward) {
626            msg.setElement(1, XNetConstants.LOCO_STACK_SEARCH_FWD);
627        } else {
628            msg.setElement(1, XNetConstants.LOCO_STACK_SEARCH_BKWD);
629        }
630        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
631        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
632        msg.setParity();
633        return (msg);
634    }
635
636    /**
637     * Given a consist address, search the database for the next Consist
638     * address.
639     *
640     * @param address is the consist address (in the range 1-99).
641     * If the Address is zero start at the beginning of the database.
642     * @param searchForward indicates to search the database Forward if
643     * true, or backwards if false
644     * @return message to get next consist address.
645     */
646    public static XNetMessage getDBSearchMsgConsistAddress(int address, boolean searchForward) {
647        XNetMessage msg = new XNetMessage(4);
648        msg.setElement(0, XNetConstants.CS_MULTI_UNIT_REQ);
649        if (searchForward) {
650            msg.setElement(1, XNetConstants.CS_MULTI_UNIT_REQ_FWD);
651        } else {
652            msg.setElement(1, XNetConstants.CS_MULTI_UNIT_REQ_BKWD);
653        }
654        msg.setElement(2, address);
655        msg.setParity();
656        return (msg);
657    }
658
659    /**
660     * Given a consist and a locomotive address, search the database for
661     * the next Locomotive in the consist.
662     *
663     * @param consist the consist address (1-99).
664     * If the Consist Address is zero start at the begining of the database
665     * @param address the locomotive address.
666     * If the Address is zero start at the begining of the consist
667     * @param searchForward indicates to search the database Forward if
668     * true, or backwards if False
669     * @return  message to request next loco in consist.
670     */
671    public static XNetMessage getDBSearchMsgNextMULoco(int consist, int address, boolean searchForward) {
672        XNetMessage msg = new XNetMessage(6);
673        msg.setElement(0, XNetConstants.LOCO_IN_MULTI_UNIT_SEARCH_REQ);
674        if (searchForward) {
675            msg.setElement(1, XNetConstants.LOCO_IN_MULTI_UNIT_REQ_FORWARD);
676        } else {
677            msg.setElement(1, XNetConstants.LOCO_IN_MULTI_UNIT_REQ_BACKWARD);
678        }
679        msg.setElement(2, consist);
680        msg.setElement(3, LenzCommandStation.getDCCAddressHigh(address));
681        msg.setElement(4, LenzCommandStation.getDCCAddressLow(address));
682        msg.setParity();
683        return (msg);
684    }
685
686    /**
687     * Given a locomotive address, delete it from the database .
688     *
689     * @param address the locomotive address
690     * @return message to delete loco address from stack.
691     */
692    public static XNetMessage getDeleteAddressOnStackMsg(int address) {
693        XNetMessage msg = new XNetMessage(5);
694        msg.setElement(0, XNetConstants.LOCO_STATUS_REQ);
695        msg.setElement(1, XNetConstants.LOCO_STACK_DELETE);
696        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
697        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
698        msg.setParity();
699        return (msg);
700    }
701
702    /**
703     * Given a locomotive address, request its status .
704     *
705     * @param address the locomotive address
706     * @return message to request loco status.
707     */
708    public static XNetMessage getLocomotiveInfoRequestMsg(int address) {
709        XNetMessage msg = new XNetMessage(5);
710        msg.setElement(0, XNetConstants.LOCO_STATUS_REQ);
711        msg.setElement(1, XNetConstants.LOCO_INFO_REQ_V3);
712        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
713        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
714        msg.setParity();
715        return (msg);
716    }
717
718    /**
719     * Given a locomotive address, request the function state (momentary status).
720     *
721     * @param address the locomotive address
722     * @return momentary function state request request.
723     */
724    public static XNetMessage getLocomotiveFunctionStatusMsg(int address) {
725        XNetMessage msg = new XNetMessage(5);
726        msg.setElement(0, XNetConstants.LOCO_STATUS_REQ);
727        msg.setElement(1, XNetConstants.LOCO_INFO_REQ_FUNC);
728        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
729        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
730        msg.setParity();
731        return (msg);
732    }
733
734    /**
735     * Given a locomotive address, request the function on/off state
736     * for functions 13-28
737     *
738     * @param address the locomotive address
739     * @return function state request request f13-f28.
740     */
741    public static XNetMessage getLocomotiveFunctionHighOnStatusMsg(int address) {
742        XNetMessage msg = new XNetMessage(5);
743        msg.setElement(0, XNetConstants.LOCO_STATUS_REQ);
744        msg.setElement(1, XNetConstants.LOCO_INFO_REQ_FUNC_HI_ON);
745        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
746        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
747        msg.setParity();
748        return (msg);
749    }
750
751    /**
752     * Given a locomotive address, request the function state (momentary status)
753     * for high functions (functions 13-28).
754     *
755     * @param address the locomotive address
756     * @return momentary function state request request f13-f28.
757     */
758    public static XNetMessage getLocomotiveFunctionHighMomStatusMsg(int address) {
759        XNetMessage msg = new XNetMessage(5);
760        msg.setElement(0, XNetConstants.LOCO_STATUS_REQ);
761        msg.setElement(1, XNetConstants.LOCO_INFO_REQ_FUNC_HI_MOM);
762        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
763        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
764        msg.setParity();
765        return (msg);
766    }
767
768    /*
769     * Generate an emergency stop for the specified address.
770     *
771     * @param address the locomotive address
772     */
773    public static XNetMessage getAddressedEmergencyStop(int address) {
774        XNetMessage msg = new XNetMessage(4);
775        msg.setElement(0, XNetConstants.EMERGENCY_STOP);
776        msg.setElement(1, LenzCommandStation.getDCCAddressHigh(address));
777        // set to the upper
778        // byte of the  DCC address
779        msg.setElement(2, LenzCommandStation.getDCCAddressLow(address));
780        // set to the lower byte
781        //of the DCC address
782        msg.setParity(); // Set the parity bit
783        return msg;
784    }
785
786    /**
787     * Generate a Speed and Direction Request message.
788     *
789     * @param address the locomotive address
790     * @param speedStepMode the speedstep mode see @jmri.DccThrottle
791     *                       for possible values.
792     * @param speed a normalized speed value (a floating point number between 0
793     *              and 1).  A negative value indicates emergency stop.
794     * @param isForward true for forward, false for reverse.
795     * @return set speed and direction message.
796     */
797    public static XNetMessage getSpeedAndDirectionMsg(int address,
798            SpeedStepMode speedStepMode,
799            float speed,
800            boolean isForward) {
801        XNetMessage msg = new XNetMessage(6);
802        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
803        int element4value = 0;   /* this is for holding the speed and
804         direction setting */
805
806        if (speedStepMode == SpeedStepMode.NMRA_DCC_128) {
807            // We're in 128 speed step mode
808            msg.setElement(1, XNetConstants.LOCO_SPEED_128);
809            // Now, we need to figure out what to send in element 4
810            // Remember, the speed steps are identified as 0-127 (in
811            // 128 step mode), not 1-128.
812            int speedVal = java.lang.Math.round(speed * 126);
813            // speed step 1 is reserved to indicate emergency stop,
814            // so we need to step over speed step 1
815            if (speedVal >= 1) {
816                element4value = speedVal + 1;
817            }
818        } else if (speedStepMode == SpeedStepMode.NMRA_DCC_28) {
819            // We're in 28 speed step mode
820            msg.setElement(1, XNetConstants.LOCO_SPEED_28);
821            // Now, we need to figure out what to send in element 4
822            int speedVal = java.lang.Math.round(speed * 28);
823            // The first speed step used is actually at 4 for 28
824            // speed step mode.
825            if (speedVal >= 1) {
826                speedVal += 3;
827            }
828            // We have to re-arange the bits, since bit 4 is the LSB,
829            // but other bits are in order from 0-3
830            element4value = ((speedVal & 0x1e) >> 1)
831                    + ((speedVal & 0x01) << 4);
832        } else if (speedStepMode == SpeedStepMode.NMRA_DCC_27) {
833            // We're in 27 speed step mode
834            msg.setElement(1, XNetConstants.LOCO_SPEED_27);
835            // Now, we need to figure out what to send in element 4
836            int speedVal = java.lang.Math.round(speed * 27);
837            // The first speed step used is actually at 4 for 27
838            // speed step mode.
839            if (speedVal >= 1) {
840                speedVal += 3;
841            }
842            // We have to re-arange the bits, since bit 4 is the LSB,
843            // but other bits are in order from 0-3
844            element4value = ((speedVal & 0x1e) >> 1)
845                    + ((speedVal & 0x01) << 4);
846        } else {
847            // We're in 14 speed step mode
848            msg.setElement(1, XNetConstants.LOCO_SPEED_14);
849            // Now, we need to figure out what to send in element 4
850            element4value = (int) (speed * 14);
851            int speedVal = java.lang.Math.round(speed * 14);
852            // The first speed step used is actually at 2 for 14
853            // speed step mode.
854            if (speedVal >= 1) {
855                element4value += 1;
856            }
857        }
858        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
859        // set to the upper byte of the  DCC address
860        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
861        // set to the lower byte
862        //of the DCC address
863        if (isForward) {
864            /* the direction bit is always the most significant bit */
865            element4value += 128;
866        }
867        msg.setElement(4, element4value);
868        msg.setParity(); // Set the parity bit
869        return msg;
870    }
871
872    /**
873     * Generate a Function Group One Operation Request message.
874     *
875     * @param address the locomotive address
876     * @param f0 is true if f0 is on, false if f0 is off
877     * @param f1 is true if f1 is on, false if f1 is off
878     * @param f2 is true if f2 is on, false if f2 is off
879     * @param f3 is true if f3 is on, false if f3 is off
880     * @param f4 is true if f4 is on, false if f4 is off
881     * @return set function group 1 message.
882     */
883    public static XNetMessage getFunctionGroup1OpsMsg(int address,
884            boolean f0,
885            boolean f1,
886            boolean f2,
887            boolean f3,
888            boolean f4) {
889        XNetMessage msg = new XNetMessage(6);
890        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
891        msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP1);
892        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
893        // set to the upper byte of the  DCC address
894        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
895        // set to the lower byte of the DCC address
896        // Now, we need to figure out what to send in element 3
897        int element4value = 0;
898        if (f0) {
899            element4value += 16;
900        }
901        if (f1) {
902            element4value += 1;
903        }
904        if (f2) {
905            element4value += 2;
906        }
907        if (f3) {
908            element4value += 4;
909        }
910        if (f4) {
911            element4value += 8;
912        }
913        msg.setElement(4, element4value);
914        msg.setParity(); // Set the parity bit
915        return msg;
916    }
917
918    /**
919     * Generate a Function Group One Set Momentary Functions message.
920     *
921     * @param address the locomotive address
922     * @param f0 is true if f0 is momentary
923     * @param f1 is true if f1 is momentary
924     * @param f2 is true if f2 is momentary
925     * @param f3 is true if f3 is momentary
926     * @param f4 is true if f4 is momentary
927     * @return set momentary function group 1 message.
928     */
929    public static XNetMessage getFunctionGroup1SetMomMsg(int address,
930            boolean f0,
931            boolean f1,
932            boolean f2,
933            boolean f3,
934            boolean f4) {
935        XNetMessage msg = new XNetMessage(6);
936        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
937        msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP1_MOMENTARY);
938        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
939        // set to the upper byte of the  DCC address
940        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
941        // set to the lower byte of the DCC address
942        // Now, we need to figure out what to send in element 3
943        int element4value = 0;
944        if (f0) {
945            element4value += 16;
946        }
947        if (f1) {
948            element4value += 1;
949        }
950        if (f2) {
951            element4value += 2;
952        }
953        if (f3) {
954            element4value += 4;
955        }
956        if (f4) {
957            element4value += 8;
958        }
959        msg.setElement(4, element4value);
960        msg.setParity(); // Set the parity bit
961        return msg;
962    }
963
964    /**
965     * Generate a Function Group Two Operation Request message.
966     *
967     * @param address the locomotive address
968     * @param f5 is true if f5 is on, false if f5 is off
969     * @param f6 is true if f6 is on, false if f6 is off
970     * @param f7 is true if f7 is on, false if f7 is off
971     * @param f8 is true if f8 is on, false if f8 is off
972     * @return set function group 2 message.
973     */
974    public static XNetMessage getFunctionGroup2OpsMsg(int address,
975            boolean f5,
976            boolean f6,
977            boolean f7,
978            boolean f8) {
979        XNetMessage msg = new XNetMessage(6);
980        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
981        msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP2);
982        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
983        // set to the upper byte of the DCC address
984        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
985        // set to the lower byte of the DCC address
986        // Now, we need to figure out what to send in element 3
987        int element4value = 0;
988        if (f5) {
989            element4value += 1;
990        }
991        if (f6) {
992            element4value += 2;
993        }
994        if (f7) {
995            element4value += 4;
996        }
997        if (f8) {
998            element4value += 8;
999        }
1000        msg.setElement(4, element4value);
1001        msg.setParity(); // Set the parity bit
1002        return msg;
1003    }
1004
1005    /**
1006     * Generate a Function Group Two Set Momentary Functions message.
1007     *
1008     * @param address the locomotive address
1009     * @param f5 is true if f5 is momentary
1010     * @param f6 is true if f6 is momentary
1011     * @param f7 is true if f7 is momentary
1012     * @param f8 is true if f8 is momentary
1013     * @return set momentary function group 2 message.
1014     */
1015    public static XNetMessage getFunctionGroup2SetMomMsg(int address,
1016            boolean f5,
1017            boolean f6,
1018            boolean f7,
1019            boolean f8) {
1020        XNetMessage msg = new XNetMessage(6);
1021        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
1022        msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP2_MOMENTARY);
1023        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
1024        // set to the upper byte of the  DCC address
1025        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
1026        // set to the lower byte of the DCC address
1027        // Now, we need to figure out what to send in element 3
1028        int element4value = 0;
1029        if (f5) {
1030            element4value += 1;
1031        }
1032        if (f6) {
1033            element4value += 2;
1034        }
1035        if (f7) {
1036            element4value += 4;
1037        }
1038        if (f8) {
1039            element4value += 8;
1040        }
1041        msg.setElement(4, element4value);
1042        msg.setParity(); // Set the parity bit
1043        return msg;
1044    }
1045
1046    /**
1047     * Generate a Function Group Three Operation Request message.
1048     *
1049     * @param address the locomotive address
1050     * @param f9 is true if f9 is on, false if f9 is off
1051     * @param f10 is true if f10 is on, false if f10 is off
1052     * @param f11 is true if f11 is on, false if f11 is off
1053     * @param f12 is true if f12 is on, false if f12 is off
1054     * @return set function group 3 message.
1055     */
1056    public static XNetMessage getFunctionGroup3OpsMsg(int address,
1057            boolean f9,
1058            boolean f10,
1059            boolean f11,
1060            boolean f12) {
1061        XNetMessage msg = new XNetMessage(6);
1062        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
1063        msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP3);
1064        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
1065        // set to the upper byte of the  DCC address
1066        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
1067        // set to the lower byte of the DCC address
1068        // Now, we need to figure out what to send in element 3
1069        int element4value = 0;
1070        if (f9) {
1071            element4value += 1;
1072        }
1073        if (f10) {
1074            element4value += 2;
1075        }
1076        if (f11) {
1077            element4value += 4;
1078        }
1079        if (f12) {
1080            element4value += 8;
1081        }
1082        msg.setElement(4, element4value);
1083        msg.setParity(); // Set the parity bit
1084        return msg;
1085    }
1086
1087    /**
1088     * Generate a Function Group Three Set Momentary Functions message.
1089     *
1090     * @param address the locomotive address
1091     * @param f9 is true if f9 is momentary
1092     * @param f10 is true if f10 is momentary
1093     * @param f11 is true if f11 is momentary
1094     * @param f12 is true if f12 is momentary
1095     * @return set momentary function group 3 message.
1096     */
1097    public static XNetMessage getFunctionGroup3SetMomMsg(int address,
1098            boolean f9,
1099            boolean f10,
1100            boolean f11,
1101            boolean f12) {
1102        XNetMessage msg = new XNetMessage(6);
1103        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
1104        msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP3_MOMENTARY);
1105        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
1106        // set to the upper byte of the  DCC address
1107        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
1108        // set to the lower byte of the DCC address
1109        // Now, we need to figure out what to send in element 3
1110        int element4value = 0;
1111        if (f9) {
1112            element4value += 1;
1113        }
1114        if (f10) {
1115            element4value += 2;
1116        }
1117        if (f11) {
1118            element4value += 4;
1119        }
1120        if (f12) {
1121            element4value += 8;
1122        }
1123        msg.setElement(4, element4value);
1124        msg.setParity(); // Set the parity bit
1125        return msg;
1126    }
1127
1128    /**
1129     * Generate a Function Group Four Operation Request message.
1130     *
1131     * @param address the locomotive address
1132     * @param f13 is true if f13 is on, false if f13 is off
1133     * @param f14 is true if f14 is on, false if f14 is off
1134     * @param f15 is true if f15 is on, false if f15 is off
1135     * @param f16 is true if f18 is on, false if f16 is off
1136     * @param f17 is true if f17 is on, false if f17 is off
1137     * @param f18 is true if f18 is on, false if f18 is off
1138     * @param f19 is true if f19 is on, false if f19 is off
1139     * @param f20 is true if f20 is on, false if f20 is off
1140     * @return set function group 4 message.
1141     */
1142    public static XNetMessage getFunctionGroup4OpsMsg(int address,
1143            boolean f13,
1144            boolean f14,
1145            boolean f15,
1146            boolean f16,
1147            boolean f17,
1148            boolean f18,
1149            boolean f19,
1150            boolean f20) {
1151        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP4,
1152                f13, f14, f15, f16, f17, f18, f19, f20);
1153    }
1154
1155    /**
1156     * Generate a Function Group Four Set Momentary Function message.
1157     *
1158     * @param address the locomotive address
1159     * @param f13 is true if f13 is Momentary
1160     * @param f14 is true if f14 is Momentary
1161     * @param f15 is true if f15 is Momentary
1162     * @param f16 is true if f18 is Momentary
1163     * @param f17 is true if f17 is Momentary
1164     * @param f18 is true if f18 is Momentary
1165     * @param f19 is true if f19 is Momentary
1166     * @param f20 is true if f20 is Momentary
1167     * @return set momentary function group 4 message.
1168     */
1169    public static XNetMessage getFunctionGroup4SetMomMsg(int address,
1170            boolean f13,
1171            boolean f14,
1172            boolean f15,
1173            boolean f16,
1174            boolean f17,
1175            boolean f18,
1176            boolean f19,
1177            boolean f20) {
1178        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP4_MOMENTARY,
1179                f13, f14, f15, f16, f17, f18, f19, f20);
1180    }
1181
1182    /**
1183     * Generate a Function Group Five Operation Request message.
1184     *
1185     * @param address the locomotive address
1186     * @param f21 is true if f21 is on, false if f21 is off
1187     * @param f22 is true if f22 is on, false if f22 is off
1188     * @param f23 is true if f23 is on, false if f23 is off
1189     * @param f24 is true if f24 is on, false if f24 is off
1190     * @param f25 is true if f25 is on, false if f25 is off
1191     * @param f26 is true if f26 is on, false if f26 is off
1192     * @param f27 is true if f27 is on, false if f27 is off
1193     * @param f28 is true if f28 is on, false if f28 is off
1194     * @return set function group 5 message.
1195     */
1196    public static XNetMessage getFunctionGroup5OpsMsg(int address,
1197            boolean f21,
1198            boolean f22,
1199            boolean f23,
1200            boolean f24,
1201            boolean f25,
1202            boolean f26,
1203            boolean f27,
1204            boolean f28) {
1205        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP5,
1206            f21, f22, f23, f24, f25, f26, f27, f28);
1207    }
1208
1209    /**
1210     * Generate a Function Group Five Set Momentary Function message.
1211     *
1212     * @param address the locomotive address
1213     * @param f21 is true if f21 is momentary
1214     * @param f22 is true if f22 is momentary
1215     * @param f23 is true if f23 is momentary
1216     * @param f24 is true if f24 is momentary
1217     * @param f25 is true if f25 is momentary
1218     * @param f26 is true if f26 is momentary
1219     * @param f27 is true if f27 is momentary
1220     * @param f28 is true if f28 is momentary
1221     * @return set momentary function group 5 message.
1222     */
1223    public static XNetMessage getFunctionGroup5SetMomMsg(int address,
1224            boolean f21,
1225            boolean f22,
1226            boolean f23,
1227            boolean f24,
1228            boolean f25,
1229            boolean f26,
1230            boolean f27,
1231            boolean f28) {
1232        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP5_MOMENTARY,
1233            f21, f22, f23, f24, f25, f26, f27, f28);
1234    }
1235
1236    // Generate a Function Group Operation Request message for some specific case.
1237    private static XNetMessage getFunctionGroupNOpsMsg(int address, int byte1,
1238            boolean fA,
1239            boolean fB,
1240            boolean fC,
1241            boolean fD,
1242            boolean fE,
1243            boolean fF,
1244            boolean fG,
1245            boolean fH) {
1246        XNetMessage msg = new XNetMessage(6);
1247        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
1248        msg.setElement(1, byte1);
1249        msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address));
1250        // set to the upper byte of the  DCC address
1251        msg.setElement(3, LenzCommandStation.getDCCAddressLow(address));
1252        // set to the lower byte of the DCC address
1253        // Now, we need to figure out what to send in element 3
1254        int element4value = 0;
1255        if (fA) {
1256            element4value += 1;
1257        }
1258        if (fB) {
1259            element4value += 2;
1260        }
1261        if (fC) {
1262            element4value += 4;
1263        }
1264        if (fD) {
1265            element4value += 8;
1266        }
1267        if (fE) {
1268            element4value += 16;
1269        }
1270        if (fF) {
1271            element4value += 32;
1272        }
1273        if (fG) {
1274            element4value += 64;
1275        }
1276        if (fH) {
1277            element4value += 128;
1278        }
1279        msg.setElement(4, element4value);
1280        msg.setParity(); // Set the parity bit
1281        return msg;
1282    }
1283
1284    public static XNetMessage getFunctionGroup6OpsMsg(int address,
1285            boolean fA, boolean fB, boolean fC, boolean fD,
1286            boolean fE, boolean fF, boolean fG, boolean fH) {
1287        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP6,
1288            fA, fB, fC, fD, fE, fF, fG, fH);
1289    }
1290
1291    public static XNetMessage getFunctionGroup7OpsMsg(int address,
1292            boolean fA, boolean fB, boolean fC, boolean fD,
1293            boolean fE, boolean fF, boolean fG, boolean fH) {
1294        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP7,
1295            fA, fB, fC, fD, fE, fF, fG, fH);
1296    }
1297
1298    public static XNetMessage getFunctionGroup8OpsMsg(int address,
1299            boolean fA, boolean fB, boolean fC, boolean fD,
1300            boolean fE, boolean fF, boolean fG, boolean fH) {
1301        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP8,
1302            fA, fB, fC, fD, fE, fF, fG, fH);
1303    }
1304
1305    public static XNetMessage getFunctionGroup9OpsMsg(int address,
1306            boolean fA, boolean fB, boolean fC, boolean fD,
1307            boolean fE, boolean fF, boolean fG, boolean fH) {
1308        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP9,
1309            fA, fB, fC, fD, fE, fF, fG, fH);
1310    }
1311
1312    public static XNetMessage getFunctionGroup10OpsMsg(int address,
1313            boolean fA, boolean fB, boolean fC, boolean fD,
1314            boolean fE, boolean fF, boolean fG, boolean fH) {
1315        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP10,
1316            fA, fB, fC, fD, fE, fF, fG, fH);
1317    }
1318
1319    public static XNetMessage getFunctionGroup6SetMomMsg(int address,
1320            boolean fA, boolean fB, boolean fC, boolean fD,
1321            boolean fE, boolean fF, boolean fG, boolean fH) {
1322        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP6_MOMENTARY,
1323            fA, fB, fC, fD, fE, fF, fG, fH);
1324    }
1325
1326    public static XNetMessage getFunctionGroup7SetMomMsg(int address,
1327            boolean fA, boolean fB, boolean fC, boolean fD,
1328            boolean fE, boolean fF, boolean fG, boolean fH) {
1329        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP7_MOMENTARY,
1330            fA, fB, fC, fD, fE, fF, fG, fH);
1331    }
1332
1333    public static XNetMessage getFunctionGroup8SetMomMsg(int address,
1334            boolean fA, boolean fB, boolean fC, boolean fD,
1335            boolean fE, boolean fF, boolean fG, boolean fH) {
1336        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP8_MOMENTARY,
1337            fA, fB, fC, fD, fE, fF, fG, fH);
1338    }
1339
1340    public static XNetMessage getFunctionGroup9SetMomMsg(int address,
1341            boolean fA, boolean fB, boolean fC, boolean fD,
1342            boolean fE, boolean fF, boolean fG, boolean fH) {
1343        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP9_MOMENTARY,
1344            fA, fB, fC, fD, fE, fF, fG, fH);
1345    }
1346
1347    public static XNetMessage getFunctionGroup10SetMomMsg(int address,
1348            boolean fA, boolean fB, boolean fC, boolean fD,
1349            boolean fE, boolean fF, boolean fG, boolean fH) {
1350        return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP10_MOMENTARY,
1351            fA, fB, fC, fD, fE, fF, fG, fH);
1352    }
1353
1354    /**
1355     * Build a Resume operations Message.
1356     * @return resume message.
1357     */
1358    public static XNetMessage getResumeOperationsMsg() {
1359        XNetMessage msg = new XNetMessage(3);
1360        msg.setElement(0, XNetConstants.CS_REQUEST);
1361        msg.setElement(1, XNetConstants.RESUME_OPS);
1362        msg.setParity();
1363        return (msg);
1364    }
1365
1366    /**
1367     * Build an EmergencyOff Message.
1368     * @return emergency off message.
1369     */
1370    public static XNetMessage getEmergencyOffMsg() {
1371        XNetMessage msg = new XNetMessage(3);
1372        msg.setElement(0, XNetConstants.CS_REQUEST);
1373        msg.setElement(1, XNetConstants.EMERGENCY_OFF);
1374        msg.setParity();
1375        return (msg);
1376    }
1377
1378    /**
1379     * Build an EmergencyStop Message.
1380     * @return emergency stop message.
1381     */
1382    public static XNetMessage getEmergencyStopMsg() {
1383        XNetMessage msg = new XNetMessage(2);
1384        msg.setElement(0, XNetConstants.ALL_ESTOP);
1385        msg.setParity();
1386        return (msg);
1387    }
1388
1389    /**
1390     * Generate the message to request the Command Station Hardware/Software
1391     * Version.
1392     * @return message to request CS hardware and software version.
1393     */
1394    public static XNetMessage getCSVersionRequestMessage() {
1395        XNetMessage msg = new XNetMessage(3);
1396        msg.setElement(0, XNetConstants.CS_REQUEST);
1397        msg.setElement(1, XNetConstants.CS_VERSION);
1398        msg.setParity(); // Set the parity bit
1399        return msg;
1400    }
1401
1402    /**
1403     * Generate the message to request the Command Station Status.
1404     * @return message to request CS status.
1405     */
1406    public static XNetMessage getCSStatusRequestMessage() {
1407        XNetMessage msg = new XNetMessage(3);
1408        msg.setElement(0, XNetConstants.CS_REQUEST);
1409        msg.setElement(1, XNetConstants.CS_STATUS);
1410        msg.setParity(); // Set the parity bit
1411        return msg;
1412    }
1413
1414    /**
1415     * Generate the message to set the Command Station to Auto or Manual restart
1416     * mode.
1417     * @param autoMode true if auto, false for manual.
1418     * @return message to set CS restart mode.
1419     */
1420    public static XNetMessage getCSAutoStartMessage(boolean autoMode) {
1421        XNetMessage msg = new XNetMessage(4);
1422        msg.setElement(0, XNetConstants.CS_SET_POWERMODE);
1423        msg.setElement(1, XNetConstants.CS_SET_POWERMODE);
1424        if (autoMode) {
1425            msg.setElement(2, XNetConstants.CS_POWERMODE_AUTO);
1426        } else {
1427            msg.setElement(2, XNetConstants.CS_POWERMODE_MANUAL);
1428        }
1429        msg.setParity(); // Set the parity bit
1430        return msg;
1431    }
1432
1433    /**
1434     * Generate the message to request the Computer Interface Hardware/Software
1435     * Version.
1436     * @return message to request interface hardware and software version.
1437     */
1438    public static XNetMessage getLIVersionRequestMessage() {
1439        XNetMessage msg = new XNetMessage(2);
1440        msg.setElement(0, XNetConstants.LI_VERSION_REQUEST);
1441        msg.setParity(); // Set the parity bit
1442        return msg;
1443    }
1444
1445    /**
1446     * Generate the message to set or request the Computer Interface Address.
1447     *
1448     * @param address Interface address (0-31). Send invalid address to request
1449     *                the address (32-255).
1450     * @return message to set or request interface address.
1451     */
1452    public static XNetMessage getLIAddressRequestMsg(int address) {
1453        XNetMessage msg = new XNetMessage(4);
1454        msg.setElement(0, XNetConstants.LI101_REQUEST);
1455        msg.setElement(1, XNetConstants.LI101_REQUEST_ADDRESS);
1456        msg.setElement(2, address);
1457        msg.setParity(); // Set the parity bit
1458        return msg;
1459    }
1460
1461    /**
1462     * Generate the message to set or request the Computer Interface speed.
1463     *
1464     * @param speed 1 is 19,200bps, 2 is 38,400bps, 3 is 57,600bps, 4 is
1465     *              115,200bps. Send invalid speed to request the current
1466     *              setting.
1467     * @return message for set / request interface speed.
1468     */
1469    public static XNetMessage getLISpeedRequestMsg(int speed) {
1470        XNetMessage msg = new XNetMessage(4);
1471        msg.setElement(0, XNetConstants.LI101_REQUEST);
1472        msg.setElement(1, XNetConstants.LI101_REQUEST_BAUD);
1473        msg.setElement(2, speed);
1474        msg.setParity(); // Set the parity bit
1475        return msg;
1476    }
1477
1478    private static final List<XPressNetMessageFormatter> formatterList = new ArrayList<>();
1479
1480   /**
1481    * Generate text translations of messages for use in the XpressNet monitor.
1482    *
1483    * @return representation of the XNetMessage as a string.
1484    */
1485    @Override
1486   public String toMonitorString() {
1487        if (formatterList.isEmpty()) {
1488            try {
1489                Reflections reflections = new Reflections("jmri.jmrix");
1490                Set<Class<? extends XPressNetMessageFormatter>> f = reflections.getSubTypesOf(XPressNetMessageFormatter.class);
1491                for (Class<?> c : f) {
1492                    log.debug("Found formatter: {}", f.getClass().getName());
1493                    Constructor<?> ctor = c.getConstructor();
1494                    formatterList.add((XPressNetMessageFormatter) ctor.newInstance());
1495                }
1496            } catch (NoSuchMethodException | SecurityException | InstantiationException |
1497                     IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
1498                log.error("Error instantiating formatter", e);
1499            }
1500        }
1501
1502        return formatterList.stream().filter(f -> f.handlesMessage(this)).findFirst().map(f -> f.formatMessage(this)).orElse(this.toString());
1503    }
1504
1505    // initialize logging
1506    private static final Logger log = LoggerFactory.getLogger(XNetMessage.class);
1507
1508}