001package jmri.jmrix.dccpp;
002
003import java.util.concurrent.Delayed;
004import java.util.concurrent.TimeUnit;
005import java.util.regex.Matcher;
006import java.util.regex.Pattern;
007import java.util.regex.PatternSyntaxException;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011import javax.annotation.CheckForNull;
012import javax.annotation.Nonnull;
013
014/**
015 * Represents a single command or response on the DCC++.
016 * <p>
017 * Content is represented with ints to avoid the problems with sign-extension
018 * that bytes have, and because a Java char is actually a variable number of
019 * bytes in Unicode.
020 *
021 * @author Bob Jacobsen Copyright (C) 2002
022 * @author Paul Bender Copyright (C) 2003-2010
023 * @author Mark Underwood Copyright (C) 2015
024 * @author Costin Grigoras Copyright (C) 2018
025 * @author Harald Barth Copyright (C) 2019
026 *
027 * Based on XNetMessage by Bob Jacobsen and Paul Bender
028 */
029
030/*
031 * A few words on implementation:
032 *
033 * DCCppMessage objects are (usually) created by calling one of the static makeMessageType()
034 * methods, and are then consumed by the TrafficController/Packetizer by being converted to
035 * a String and sent out the port.
036 * <p>
037 * Internally the DCCppMessage is actually stored as a String, and alongside that is kept
038 * a Regex for easy extraction of the values where needed in the code.
039 * <p>
040 * The various getParameter() type functions are mainly for convenience in places such as the
041 * port monitor where we want to be able to extract the /meaning/ of the DCCppMessage and
042 * present it in a human readable form.  Using the getParameterType() methods insulates
043 * the higher level code from needing to know what order/format the actual message is
044 * in.
045 */
046public class DCCppMessage extends jmri.jmrix.AbstractMRMessage implements Delayed {
047
048    private static int _nRetries = 3;
049
050    /* According to the specification, DCC++ has a maximum timing
051     interval of 500 milliseconds during normal communications */
052    protected static final int DCCppProgrammingTimeout = 10000;  // TODO: Appropriate value for DCC++?
053    private static int DCCppMessageTimeout = 5000;  // TODO: Appropriate value for DCC++?
054
055    //private ArrayList<Integer> valueList = new ArrayList<>();
056    private StringBuilder myMessage;
057    private String myRegex;
058    private char opcode;
059
060    /**
061     * Create a new object, representing a specific-length message.
062     *
063     * @param len Total bytes in message, including opcode and error-detection
064     *            byte.
065     */
066    //NOTE: Not used anywhere useful... consider removing.
067    public DCCppMessage(int len) {
068        super(len);
069        setBinary(false);
070        setRetries(_nRetries);
071        setTimeout(DCCppMessageTimeout);
072        if (len > DCCppConstants.MAX_MESSAGE_SIZE || len < 0) {
073            log.error("Invalid length in ctor: {}", len);
074        }
075        _nDataChars = len;
076        myRegex = "";
077        myMessage = new StringBuilder(len);
078    }
079
080    /**
081     * Create a new object, that is a copy of an existing message.
082     *
083     * @param message existing message.
084     */
085    public DCCppMessage(DCCppMessage message) {
086        super(message);
087        setBinary(false);
088        setRetries(_nRetries);
089        setTimeout(DCCppMessageTimeout);
090        myRegex = message.myRegex;
091        myMessage = message.myMessage;
092        toStringCache = message.toStringCache;
093    }
094
095    /**
096     * Create an DCCppMessage from an DCCppReply.
097     * Not used.  Really, not even possible.  Consider removing.
098     * @param message existing reply to replicate.
099     */
100    public DCCppMessage(DCCppReply message) {
101        super(message.getNumDataElements());
102        setBinary(false);
103        setRetries(_nRetries);
104        setTimeout(DCCppMessageTimeout);
105        for (int i = 0; i < message.getNumDataElements(); i++) {
106            setElement(i, message.getElement(i));
107        }
108    }
109
110    /**
111     * Create a DCCppMessage from a String containing bytes.
112     * <p>
113     * Since DCCppMessages are text, there is no Hex-to-byte conversion.
114     * <p>
115     * NOTE 15-Feb-17: un-Deprecating this function so that it can be used in
116     * the DCCppOverTCP server/client interface. 
117     * Messages shouldn't be parsed, they are already in DCC++ format,
118     * so we need the string constructor to generate a DCCppMessage from 
119     * the incoming byte stream.
120     * @param s message in string form.
121     */
122    public DCCppMessage(String s) {
123        setBinary(false);
124        setRetries(_nRetries);
125        setTimeout(DCCppMessageTimeout);
126        myMessage = new StringBuilder(s); // yes, copy... or... maybe not.
127        toStringCache = s;
128        // gather bytes in result
129        setRegex();
130        _nDataChars = myMessage.length();
131        _dataChars = new int[_nDataChars];
132    }
133
134    // Partial constructor used in the static getMessageType() calls below.
135    protected DCCppMessage(char c) {
136        setBinary(false);
137        setRetries(_nRetries);
138        setTimeout(DCCppMessageTimeout);
139        opcode = c;
140        myMessage = new StringBuilder(Character.toString(c));
141        _nDataChars = myMessage.length();
142    }
143
144    protected DCCppMessage(char c, String regex) {
145        setBinary(false);
146        setRetries(_nRetries);
147        setTimeout(DCCppMessageTimeout);
148        opcode = c;
149        myRegex = regex;
150        myMessage = new StringBuilder(Character.toString(c));
151        _nDataChars = myMessage.length();
152    }
153
154    /**
155     * Parse a string and generate/return a DCCppMessage object.
156     *
157     * @param s String of DCC++ message without the {@literal < >} brackets
158     * @return DCCppMessage
159     */
160    public static DCCppMessage parseDCCppMessage(String s) {
161        // Need to parse the string and construct a message from it.
162        Matcher m;
163        switch (s.charAt(0)) {
164            case DCCppConstants.ACCESSORY_CMD:
165                if ((m = match(s, DCCppConstants.ACCESSORY_CMD_REGEX, "ctor")) != null) {
166                    int addr = Integer.parseInt(m.group(1));
167                    int sub = Integer.parseInt(m.group(2));
168                    int v = (m.group(3).equals("0") ? 0 : 1);
169                    return (DCCppMessage.makeAccessoryDecoderMsg(addr, sub, (v == 1)));
170                } else {
171                    return (null);
172                }
173            case DCCppConstants.CLEAR_EEPROM_CMD:
174                return (new DCCppMessage(DCCppConstants.CLEAR_EEPROM_CMD, DCCppConstants.CLEAR_EEPROM_REGEX));
175            case DCCppConstants.FUNCTION_CMD:
176                break;
177            case DCCppConstants.FUNCTION_V2_CMD:
178                if ((m = match(s, DCCppConstants.FUNCTION_V2_CMD_REGEX, "ctor")) != null) {
179                    int cab = Integer.parseInt(m.group(1));
180                    int func = Integer.parseInt(m.group(2));
181                    int state = Integer.parseInt(m.group(3));
182                    return (DCCppMessage.makeFunctionV2Message(cab, func, state));
183                } else {
184                    return (null);
185                }
186            case DCCppConstants.FORGET_CAB_CMD:
187                if ((m = match(s, DCCppConstants.FORGET_CAB_CMD_REGEX, "ctor")) != null) {
188                    int cab;
189                    if (m.group(1).equals("")) { //no cab entered, forget all
190                        cab = 0;
191                    } else {
192                        cab = Integer.parseInt(m.group(1));
193                    }
194                    return (DCCppMessage.makeForgetCabMessage(cab));
195                } else {
196                    return (null);
197                }
198            case DCCppConstants.LIST_REGISTER_CONTENTS:
199                return (new DCCppMessage(DCCppConstants.LIST_REGISTER_CONTENTS, DCCppConstants.LIST_REGISTER_CONTENTS_REGEX));
200            case DCCppConstants.OPS_WRITE_CV_BIT:
201                if ((m = match(s, DCCppConstants.OPS_WRITE_BIT_REGEX, "ctor")) != null) {
202                    int addr = Integer.parseInt(m.group(1));
203                    int cv = Integer.parseInt(m.group(2));
204                    int bit = Integer.parseInt(m.group(3));
205                    int val = (m.group(4).equals("0") ? 0 : 1);
206                    return (DCCppMessage.makeBitWriteOpsModeCVMsg(addr, cv, bit, val));
207                } else {
208                    return (null);
209                }
210            case DCCppConstants.OPS_WRITE_CV_BYTE:
211                if ((m = match(s, DCCppConstants.OPS_WRITE_BYTE_REGEX, "ctor")) != null) {
212                    int addr = Integer.parseInt(m.group(1));
213                    int cv = Integer.parseInt(m.group(2));
214                    int val = Integer.parseInt(m.group(3));
215                    return (DCCppMessage.makeWriteOpsModeCVMsg(addr, cv, val));
216                } else {
217                    return (null);
218                }
219            case DCCppConstants.PROG_READ_CV:
220                if ((m = match(s, DCCppConstants.PROG_READ_REGEX, "ctor")) != null) {
221                    int cv = Integer.parseInt(m.group(1));
222                    int cb = Integer.parseInt(m.group(2));
223                    int cs = Integer.parseInt(m.group(3));
224                    return (DCCppMessage.makeReadDirectCVMsg(cv, cb, cs));
225                } else {
226                    return (null);
227                }
228            case DCCppConstants.PROG_VERIFY_CV:
229                if ((m = match(s, DCCppConstants.PROG_VERIFY_REGEX, "ctor")) != null) {
230                    int cv = Integer.parseInt(m.group(1));
231                    int sv = Integer.parseInt(m.group(2));
232                    return (DCCppMessage.makeVerifyCVMsg(cv, sv));
233                } else {
234                    return (null);
235                }
236            case DCCppConstants.PROG_WRITE_CV_BIT:
237                if ((m = match(s, DCCppConstants.PROG_WRITE_BIT_REGEX, "ctor")) != null) {
238                    int cv = Integer.parseInt(m.group(1));
239                    int bit = Integer.parseInt(m.group(2));
240                    int val = (m.group(3).equals("0") ? 0 : 1);
241                    int addr = Integer.parseInt(m.group(4));
242                    int sub = Integer.parseInt(m.group(5));
243                    return (DCCppMessage.makeBitWriteDirectCVMsg(cv, bit, val, addr, sub));
244                } else {
245                    return (null);
246                }
247            case DCCppConstants.PROG_WRITE_CV_BYTE:
248                if ((m = match(s, DCCppConstants.PROG_WRITE_BYTE_REGEX, "ctor")) != null) {
249                    int cv = Integer.parseInt(m.group(1));
250                    int val = Integer.parseInt(m.group(2));
251                    int addr = Integer.parseInt(m.group(3));
252                    int sub = Integer.parseInt(m.group(4));
253                    return (DCCppMessage.makeWriteDirectCVMsg(cv, val, addr, sub));
254                } else {
255                    return (null);
256                }
257            case DCCppConstants.READ_CS_STATUS:
258                return (new DCCppMessage(DCCppConstants.READ_CS_STATUS, DCCppConstants.READ_CS_STATUS_REGEX));
259            case DCCppConstants.READ_MAXNUMSLOTS:
260                return (new DCCppMessage(DCCppConstants.READ_MAXNUMSLOTS, DCCppConstants.READ_MAXNUMSLOTS_REGEX));
261            case DCCppConstants.READ_TRACK_CURRENT:
262                return (DCCppMessage.makeReadTrackCurrentMsg());
263            case DCCppConstants.SENSOR_CMD:
264                if ((m = match(s, DCCppConstants.SENSOR_ADD_REGEX, "ctor")) != null) {
265                    int id = Integer.parseInt(m.group(1));
266                    int pin = Integer.parseInt(m.group(2));
267                    int pullup = (m.group(4).equals("0") ? 0 : 1);
268                    return (DCCppMessage.makeSensorAddMsg(id, pin, pullup));
269                } else if ((m = match(s, DCCppConstants.SENSOR_DELETE_REGEX, "ctor")) != null) {
270                    int id = Integer.parseInt(m.group(1));
271                    return (DCCppMessage.makeSensorDeleteMsg(id));
272                } else if ((match(s, DCCppConstants.SENSOR_LIST_REGEX, "ctor")) != null) {
273                    return (new DCCppMessage(DCCppConstants.SENSOR_CMD, DCCppConstants.SENSOR_LIST_REGEX));
274                } else {
275                    return (null);
276                }
277            case DCCppConstants.THROTTLE_CMD:
278                if ((m = match(s, DCCppConstants.THROTTLE_CMD_REGEX, "ctor")) != null) {
279                    int reg = Integer.parseInt(m.group(1));
280                    int addr = Integer.parseInt(m.group(2));
281                    float speed = Float.parseFloat(m.group(3)); // Note: gets converted to int inside makeSpeedAndDirectionMsg()
282                    int fwd = (m.group(4).equals("0") ? 0 : 1);
283                    return (DCCppMessage.makeSpeedAndDirectionMsg(reg, addr, speed, (fwd == 1)));
284                } else {
285                    return (null);
286                }
287            case DCCppConstants.TRACK_POWER_OFF:
288                return (DCCppMessage.makeTrackPowerOffMsg());
289            case DCCppConstants.TRACK_POWER_ON:
290                return (DCCppMessage.makeTrackPowerOnMsg());
291            case DCCppConstants.TURNOUT_CMD:
292                if ((m = match(s, DCCppConstants.TURNOUT_ADD_REGEX, "ctor")) != null) {
293                    int id = Integer.parseInt(m.group(1));
294                    int addr = Integer.parseInt(m.group(2));
295                    int sub = Integer.parseInt(m.group(3));
296                    return (DCCppMessage.makeTurnoutAddMsg(id, addr, sub));
297                } else if ((m = match(s, DCCppConstants.TURNOUT_DELETE_REGEX, "ctor")) != null) {
298                    int id = Integer.parseInt(m.group(1));
299                    return (DCCppMessage.makeTurnoutDeleteMsg(id));
300                } else if ((match(s, DCCppConstants.TURNOUT_LIST_REGEX, "ctor")) != null) {
301                    return (new DCCppMessage(DCCppConstants.TURNOUT_CMD, DCCppConstants.TURNOUT_LIST_REGEX));
302                } else if ((m = match(s, DCCppConstants.TURNOUT_CMD_REGEX, "ctor")) != null) {
303                    int id = Integer.parseInt(m.group(1));
304                    int thrown = m.group(2).equals("0") ? 0 : 1;
305                    return (DCCppMessage.makeTurnoutCommandMsg(id, (thrown == 1)));
306                } else {
307                    return (null);
308                }
309            case DCCppConstants.OUTPUT_CMD:
310                if ((m = match(s, DCCppConstants.OUTPUT_CMD_REGEX, "ctor")) != null) {
311                    int id = Integer.parseInt(m.group(1));
312                    int state = m.group(2).equals("0") ? 0 : 1;
313                    return (DCCppMessage.makeOutputCmdMsg(id, (state == 1)));
314                } else if ((m = match(s, DCCppConstants.OUTPUT_ADD_REGEX, "ctor")) != null) {
315                    int id = Integer.parseInt(m.group(1));
316                    int pin = Integer.parseInt(m.group(2));
317                    int iflag = Integer.parseInt(m.group(3));
318                    return (DCCppMessage.makeOutputAddMsg(id, pin, iflag));
319                } else if ((m = match(s, DCCppConstants.OUTPUT_DELETE_REGEX, "ctor")) != null) {
320                    int id = Integer.parseInt(m.group(1));
321                    return (DCCppMessage.makeOutputDeleteMsg(id));
322                } else if ((m = match(s, DCCppConstants.OUTPUT_LIST_REGEX, "ctor")) != null) {
323                    return (DCCppMessage.makeOutputListMsg());
324                } else {
325                    return (null);
326                }
327            case DCCppConstants.WRITE_DCC_PACKET_MAIN:
328                break;
329            case DCCppConstants.WRITE_DCC_PACKET_PROG:
330                break;
331            case DCCppConstants.WRITE_TO_EEPROM_CMD:
332                return (new DCCppMessage(DCCppConstants.WRITE_TO_EEPROM_CMD, DCCppConstants.WRITE_TO_EEPROM_REGEX));
333            case DCCppConstants.QUERY_SENSOR_STATES_CMD:
334                return (new DCCppMessage(DCCppConstants.QUERY_SENSOR_STATES_CMD, DCCppConstants.QUERY_SENSOR_STATES_REGEX));
335            default:
336                return (null);
337        }
338        return (null);
339    }
340
341    private void setRegex() {
342        switch (myMessage.charAt(0)) {
343            case DCCppConstants.THROTTLE_CMD:
344                myRegex = DCCppConstants.THROTTLE_CMD_REGEX;
345                break;
346            case DCCppConstants.FUNCTION_CMD:
347                myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
348                break;
349            case DCCppConstants.FUNCTION_V2_CMD:
350                myRegex = DCCppConstants.FUNCTION_V2_CMD_REGEX;
351                break;
352            case DCCppConstants.FORGET_CAB_CMD:
353                myRegex = DCCppConstants.FORGET_CAB_CMD_REGEX;
354                break;
355            case DCCppConstants.ACCESSORY_CMD:
356                myRegex = DCCppConstants.ACCESSORY_CMD_REGEX;
357                break;
358            case DCCppConstants.TURNOUT_CMD:
359                if ((match(toString(), DCCppConstants.TURNOUT_ADD_REGEX, "ctor")) != null) {
360                    myRegex = DCCppConstants.TURNOUT_ADD_REGEX;
361                } else if ((match(toString(), DCCppConstants.TURNOUT_DELETE_REGEX, "ctor")) != null) {
362                    myRegex = DCCppConstants.TURNOUT_DELETE_REGEX;
363                } else if ((match(toString(), DCCppConstants.TURNOUT_LIST_REGEX, "ctor")) != null) {
364                    myRegex = DCCppConstants.TURNOUT_LIST_REGEX;
365                } else if ((match(toString(), DCCppConstants.TURNOUT_CMD_REGEX, "ctor")) != null) {
366                    myRegex = DCCppConstants.TURNOUT_CMD_REGEX;
367                } else {
368                    myRegex = "";
369                }
370                break;
371            case DCCppConstants.SENSOR_CMD:
372                if ((match(toString(), DCCppConstants.SENSOR_ADD_REGEX, "ctor")) != null) {
373                    myRegex = DCCppConstants.SENSOR_ADD_REGEX;
374                } else if ((match(toString(), DCCppConstants.SENSOR_DELETE_REGEX, "ctor")) != null) {
375                    myRegex = DCCppConstants.SENSOR_DELETE_REGEX;
376                } else if ((match(toString(), DCCppConstants.SENSOR_LIST_REGEX, "ctor")) != null) {
377                    myRegex = DCCppConstants.SENSOR_LIST_REGEX;
378                } else {
379                    myRegex = "";
380                }
381                break;
382            case DCCppConstants.OUTPUT_CMD:
383                if ((match(toString(), DCCppConstants.OUTPUT_ADD_REGEX, "ctor")) != null) {
384                    myRegex = DCCppConstants.OUTPUT_ADD_REGEX;
385                } else if ((match(toString(), DCCppConstants.OUTPUT_DELETE_REGEX, "ctor")) != null) {
386                    myRegex = DCCppConstants.OUTPUT_DELETE_REGEX;
387                } else if ((match(toString(), DCCppConstants.OUTPUT_LIST_REGEX, "ctor")) != null) {
388                    myRegex = DCCppConstants.OUTPUT_LIST_REGEX;
389                } else if ((match(toString(), DCCppConstants.OUTPUT_CMD_REGEX, "ctor")) != null) {
390                    myRegex = DCCppConstants.OUTPUT_CMD_REGEX;
391                } else {
392                    myRegex = "";
393                }
394                break;
395            case DCCppConstants.OPS_WRITE_CV_BYTE:
396                myRegex = DCCppConstants.OPS_WRITE_BYTE_REGEX;
397                break;
398            case DCCppConstants.OPS_WRITE_CV_BIT:
399                myRegex = DCCppConstants.OPS_WRITE_BIT_REGEX;
400                break;
401            case DCCppConstants.PROG_WRITE_CV_BYTE:
402                myRegex = DCCppConstants.PROG_WRITE_BYTE_REGEX;
403                break;
404            case DCCppConstants.PROG_WRITE_CV_BIT:
405                myRegex = DCCppConstants.PROG_WRITE_BIT_REGEX;
406                break;
407            case DCCppConstants.PROG_READ_CV:
408                myRegex = DCCppConstants.PROG_READ_REGEX;
409                break;
410            case DCCppConstants.PROG_VERIFY_CV:
411                myRegex = DCCppConstants.PROG_VERIFY_REGEX;
412                break;
413            case DCCppConstants.TRACK_POWER_ON:
414            case DCCppConstants.TRACK_POWER_OFF:
415                myRegex = DCCppConstants.TRACK_POWER_REGEX;
416                break;
417            case DCCppConstants.READ_TRACK_CURRENT:
418                myRegex = DCCppConstants.READ_TRACK_CURRENT_REGEX;
419                break;
420            case DCCppConstants.READ_CS_STATUS:
421                myRegex = DCCppConstants.READ_CS_STATUS_REGEX;
422                break;
423            case DCCppConstants.READ_MAXNUMSLOTS:
424                myRegex = DCCppConstants.READ_MAXNUMSLOTS_REGEX;
425                break;
426            case DCCppConstants.WRITE_TO_EEPROM_CMD:
427                myRegex = DCCppConstants.WRITE_TO_EEPROM_REGEX;
428                break;
429            case DCCppConstants.CLEAR_EEPROM_CMD:
430                myRegex = DCCppConstants.CLEAR_EEPROM_REGEX;
431                break;
432            case DCCppConstants.QUERY_SENSOR_STATES_CMD:
433                myRegex = DCCppConstants.QUERY_SENSOR_STATES_REGEX;
434                break;
435            case DCCppConstants.WRITE_DCC_PACKET_MAIN:
436                myRegex = DCCppConstants.WRITE_DCC_PACKET_MAIN_REGEX;
437                break;
438            case DCCppConstants.WRITE_DCC_PACKET_PROG:
439                myRegex = DCCppConstants.WRITE_DCC_PACKET_PROG_REGEX;
440                break;
441            case DCCppConstants.LIST_REGISTER_CONTENTS:
442                myRegex = DCCppConstants.LIST_REGISTER_CONTENTS_REGEX;
443                break;
444            case DCCppConstants.DIAG_CMD:
445                myRegex = DCCppConstants.DIAG_CMD_REGEX;                
446                break;
447            default:
448                myRegex = "";
449        }
450    }
451
452    private String toStringCache = null;
453
454    /**
455     * Converts DCCppMessage to String format (without the {@code <>} brackets)
456     *
457     * @return String form of message.
458     */
459    @Override
460    public String toString() {
461        if (toStringCache == null) {
462            toStringCache = myMessage.toString();
463        }
464
465        return toStringCache;
466        /*
467        String s = Character.toString(opcode);
468        for (int i = 0; i < valueList.size(); i++) {
469            s += " ";
470            s += valueList.get(i).toString();
471        }
472        return(s);
473         */
474    }
475
476    /**
477     * Generate text translations of messages for use in the DCCpp monitor.
478     *
479     * @return representation of the DCCpp as a string.
480     */
481    @Override
482    public String toMonitorString() {
483        // Beautify and display
484        String text;
485
486        switch (getOpCodeChar()) {
487            case DCCppConstants.THROTTLE_CMD:
488                text = "Throttle Cmd: ";
489                text += "Register: " + getRegisterString();
490                text += ", Address: " + getAddressString();
491                text += ", Speed: " + getSpeedString();
492                text += ", Direction: " + getDirectionString();
493                break;
494            case DCCppConstants.FUNCTION_CMD:
495                text = "Function Cmd: ";
496                text += "Address: " + getFuncAddressString();
497                text += ", Byte 1: " + getFuncByte1String();
498                text += ", Byte 2: " + getFuncByte2String();
499                text += ", (No Reply Expected)";
500                break;
501            case DCCppConstants.FUNCTION_V2_CMD:
502                text = "Function Cmd: ";
503                if (isFunctionV2Message()) {
504                    text += "CAB: " + getFuncV2CabString();
505                    text += ", FUNC: " + getFuncV2FuncString();
506                    text += ", State: " + getFuncV2StateString();
507                    text += ", (No Reply Expected)";
508                } else {
509                    text += "Invalid syntax: '" + toString() + "'";                                        
510                }
511                break;
512            case DCCppConstants.FORGET_CAB_CMD:
513                text = "Forget Cab: ";
514                if (isForgetCabMessage()) {
515                    text += "CAB: " + (getForgetCabString().equals("")?"[ALL]":getForgetCabString());
516                    text += ", (No Reply Expected)";
517                } else {
518                    text += "Invalid syntax: '" + toString() + "'";                    
519                }
520                break;
521            case DCCppConstants.ACCESSORY_CMD:
522                text = "Accessory Decoder Cmd: ";
523                text += "Address: " + getAccessoryAddrString();
524                text += ", Subaddr: " + getAccessorySubString();
525                text += ", State: " + getAccessoryStateString();
526                break;
527            case DCCppConstants.TURNOUT_CMD:
528                if (isTurnoutAddMessage()) {
529                    text = "Add Turnout: ";
530                    text += "ID: " + getTOIDString();
531                    text += ", Address: " + getTOAddressString();
532                    text += ", Subaddr: " + getTOSubAddressString();
533                } else if (isTurnoutDeleteMessage()) {
534                    text = "Delete Turnout: ";
535                    text += "ID: " + getTOIDString();
536                } else if (isListTurnoutsMessage()) {
537                    text = "List Turnouts...";
538                } else {
539                    text = "Turnout Cmd: ";
540                    text += "ID: " + getTOIDString();
541                    text += ", State: " + getTOStateString();
542                }
543                break;
544            case DCCppConstants.OUTPUT_CMD:
545                if (isOutputCmdMessage()) {
546                    text = "Output Cmd: ";
547                    text += "ID: " + getOutputIDString();
548                    text += ", State: " + getOutputStateString();
549                } else if (isOutputAddMessage()) {
550                    text = "Add Output: ";
551                    text += "ID: " + getOutputIDString();
552                    text += ", Pin: " + getOutputPinString();
553                    text += ", IFlag: " + getOutputIFlagString();
554                } else if (isOutputDeleteMessage()) {
555                    text = "Delete Output: ";
556                    text += "ID: " + getOutputIDString();
557                } else if (isListOutputsMessage()) {
558                    text = "List Outputs...";
559                } else {
560                    text = "Invalid Output Command: " + toString();
561                }
562                break;
563            case DCCppConstants.SENSOR_CMD:
564                if (isSensorAddMessage()) {
565                    text = "Add Sensor: ";
566                    text += "ID: " + getSensorIDString();
567                    text += ", Pin: " + getSensorPinString();
568                    text += ", Pullup: " + getSensorPullupString();
569                } else if (isSensorDeleteMessage()) {
570                    text = "Delete Sensor: ";
571                    text += "ID: " + getSensorIDString();
572                } else if (isListSensorsMessage()) {
573                    text = "List Sensors...";
574                } else {
575                    text = "Unknown Sensor Cmd...";
576                }
577                break;
578            case DCCppConstants.OPS_WRITE_CV_BYTE:
579                text = "Ops Write Byte Cmd: "; // <w cab cv val>
580                text += "Address: " + getOpsWriteAddrString() + ", ";
581                text += "CV: " + getOpsWriteCVString() + ", ";
582                text += "Value: " + getOpsWriteValueString();
583                break;
584            case DCCppConstants.OPS_WRITE_CV_BIT: // <b cab cv bit val>
585                text = "Ops Write Bit Cmd: ";
586                text += "Address: " + getOpsWriteAddrString() + ", ";
587                text += "CV: " + getOpsWriteCVString() + ", ";
588                text += "Bit: " + getOpsWriteBitString() + ", ";
589                text += "Value: " + getOpsWriteValueString();
590                break;
591            case DCCppConstants.PROG_WRITE_CV_BYTE:
592                text = "Prog Write Byte Cmd: ";
593                text += "CV : " + getCVString();
594                text += ", Value: " + getProgValueString();
595                text += ", Callback Num: " + getCallbackNumString();
596                text += ", Callback Sub: " + getCallbackSubString();
597                break;
598
599            case DCCppConstants.PROG_WRITE_CV_BIT:
600                text = "Prog Write Bit Cmd: ";
601                text += "CV : " + getCVString();
602                text += ", Bit : " + getBitString();
603                text += ", Value: " + getProgValueString();
604                text += ", Callback Num: " + getCallbackNumString();
605                text += ", Callback Sub: " + getCallbackSubString();
606                break;
607            case DCCppConstants.PROG_READ_CV:
608                text = "Prog Read Cmd: ";
609                text += "CV: " + getCVString();
610                text += ", Callback Num: " + getCallbackNumString();
611                text += ", Callback Sub: " + getCallbackSubString();
612                break;
613            case DCCppConstants.PROG_VERIFY_CV:
614                text = "Prog Verify Cmd:  ";
615                text += "CV: " + getCVString();
616                text += ", startVal: " + getProgValueString();
617                break;
618            case DCCppConstants.TRACK_POWER_ON:
619                text = "Track Power ON Cmd ";
620                break;
621            case DCCppConstants.TRACK_POWER_OFF:
622                text = "Track Power OFF Cmd ";
623                break;
624            case DCCppConstants.READ_TRACK_CURRENT:
625                text = "Read Track Current Cmd ";
626                break;
627            case DCCppConstants.READ_CS_STATUS:
628                text = "Status Cmd ";
629                break;
630            case DCCppConstants.READ_MAXNUMSLOTS:
631                text = "Get MaxNumSlots Cmd ";
632                break;
633            case DCCppConstants.WRITE_DCC_PACKET_MAIN:
634                text = "Write DCC Packet Main Cmd: ";
635                text += "Register: " + getRegisterString();
636                text += ", Packet:" + getPacketString();
637                break;
638            case DCCppConstants.WRITE_DCC_PACKET_PROG:
639                text = "Write DCC Packet Prog Cmd: ";
640                text += "Register: " + getRegisterString();
641                text += ", Packet:" + getPacketString();
642                break;
643            case DCCppConstants.LIST_REGISTER_CONTENTS:
644                text = "List Register Contents Cmd: ";
645                text += toString();
646                break;
647            case DCCppConstants.WRITE_TO_EEPROM_CMD:
648                text = "Write to EEPROM Cmd: ";
649                text += toString();
650                break;
651            case DCCppConstants.CLEAR_EEPROM_CMD:
652                text = "Clear EEPROM Cmd: ";
653                text += toString();
654                break;
655            case DCCppConstants.QUERY_SENSOR_STATES_CMD:
656                text = "Query Sensor States Cmd: '" + toString() + "'";
657                break;               
658            case DCCppConstants.DIAG_CMD:
659                text = "Diag Cmd: '" + toString() + "'";
660                break;               
661            case DCCppConstants.ESTOP_ALL_CMD:
662                text = "eStop All Locos Cmd: '" + toString() + "'";
663                break;               
664            default:
665                text = "Unknown Message: '" + toString() + "'";
666        }
667
668        return text;
669    }
670
671    @Override
672    public int getNumDataElements() {
673        return (myMessage.length());
674        // return(_nDataChars);
675    }
676
677    @Override
678    public int getElement(int n) {
679        return (this.myMessage.charAt(n));
680    }
681
682    @Override
683    public void setElement(int n, int v) {
684        // We want the ASCII value, not the string interpretation of the int
685        char c = (char) (v & 0xFF);
686        if (n >= myMessage.length()) {
687            myMessage.append(c);
688        } else if (n > 0) {
689            myMessage.setCharAt(n, c);
690        }
691        toStringCache = null;
692    }
693    // For DCC++, the opcode is the first character in the
694    // command (after the < ).
695
696    // note that the opcode is part of the message, so we treat it
697    // directly
698    // WARNING: use this only with opcodes that have a variable number
699    // of arguments following included. Otherwise, just use setElement
700    @Override
701    public void setOpCode(int i) {
702        if (i > 0xFF || i < 0) {
703            log.error("Opcode invalid: {}", i);
704        }
705        opcode = (char) (i & 0xFF);
706        myMessage.setCharAt(0, opcode);
707        toStringCache = null;
708    }
709
710    @Override
711    public int getOpCode() {
712        return (opcode & 0xFF);
713    }
714
715    public char getOpCodeChar() {
716        //return(opcode);
717        return (myMessage.charAt(0));
718    }
719
720    @Deprecated
721    public String getOpCodeString() {
722        return (Character.toString(opcode));
723    }
724
725    private int getGroupCount() {
726        Matcher m = match(toString(), myRegex, "gvs");
727        assert m != null;
728        return m.groupCount();
729    }
730
731    public String getValueString(int idx) {
732        Matcher m = match(toString(), myRegex, "gvs");
733        if (m == null) {
734            log.error("DCCppMessage '{}' not matched by '{}'", this.toString(), myRegex);
735            return ("");
736        } else if (idx <= m.groupCount()) {
737            return (m.group(idx));
738        } else {
739            log.error("DCCppMessage value index too big. idx = {} msg = {}", idx, this);
740            return ("");
741        }
742    }
743
744    public int getValueInt(int idx) {
745        Matcher m = match(toString(), myRegex, "gvi");
746        if (m == null) {
747            log.error("DCCppMessage '{}' not matched by '{}'", this.toString(), myRegex);
748            return (0);
749        } else if (idx <= m.groupCount()) {
750            return (Integer.parseInt(m.group(idx)));
751        } else {
752            log.error("DCCppMessage value index too big. idx = {} msg = {}", idx, this);
753            return (0);
754        }
755    }
756
757    public boolean getValueBool(int idx) {
758        log.debug("msg = {}, regex = {}", this, myRegex);
759        Matcher m = match(toString(), myRegex, "gvb");
760
761        if (m == null) {
762            log.error("DCCppMessage '{}' not matched by '{}'", this.toString(), myRegex);
763            return (false);
764        } else if (idx <= m.groupCount()) {
765            return (!m.group(idx).equals("0"));
766        } else {
767            log.error("DCCppMessage value index too big. idx = {} msg = {}", idx, this);
768            return (false);
769        }
770    }
771
772    /**
773     * @return the message length
774     */
775    public int length() {
776        return (myMessage.length());
777    }
778
779    /**
780     * Change the default number of retries for an DCC++ message.
781     *
782     * @param t number of retries to attempt
783     */
784    public static void setDCCppMessageRetries(int t) {
785        _nRetries = t;
786    }
787
788    /**
789     * Change the default timeout for a DCC++ message.
790     *
791     * @param t Timeout in milliseconds
792     */
793    public static void setDCCppMessageTimeout(int t) {
794        DCCppMessageTimeout = t;
795    }
796
797    //------------------------------------------------------
798    // Message Helper Functions
799    // Core methods
800    /**
801     * Returns true if this DCCppMessage is properly formatted (or will generate
802     * a properly formatted command when converted to String).
803     *
804     * @return boolean true/false
805     */
806    public boolean isValidMessageFormat() {
807        return this.match(this.myRegex) != null;
808    }
809
810    /**
811     * Matches this DCCppMessage against the given regex 'pat'
812     *
813     * @param pat Regex
814     * @return Matcher or null if no match.
815     */
816    private Matcher match(String pat) {
817        return (match(this.toString(), pat, "Validator"));
818    }
819
820    /**
821     * matches the given string against the given Regex pattern.
822     *
823     * @param s    string to be matched
824     * @param pat  Regex string to match against
825     * @param name Text name to use in debug messages.
826     * @return Matcher or null if no match
827     */
828    @CheckForNull
829    private static Matcher match(String s, String pat, String name) {
830        try {
831            Pattern p = Pattern.compile(pat);
832            Matcher m = p.matcher(s);
833            if (!m.matches()) {
834                log.trace("No Match {} Command: '{}' Pattern: '{}'", name, s, pat);
835                return null;
836            }
837            return m;
838
839        } catch (PatternSyntaxException e) {
840            log.error("Malformed DCC++ message syntax! s = {}", pat);
841            return (null);
842        } catch (IllegalStateException e) {
843            log.error("Group called before match operation executed string= {}", s);
844            return (null);
845        } catch (IndexOutOfBoundsException e) {
846            log.error("Index out of bounds string= {}", s);
847            return (null);
848        }
849    }
850
851    // Identity Methods
852    public boolean isThrottleMessage() {
853        return (this.getOpCodeChar() == DCCppConstants.THROTTLE_CMD);
854    }
855
856    public boolean isAccessoryMessage() {
857        return (this.getOpCodeChar() == DCCppConstants.ACCESSORY_CMD);
858    }
859
860    public boolean isFunctionMessage() {
861        return (this.getOpCodeChar() == DCCppConstants.FUNCTION_CMD);
862    }
863
864    public boolean isFunctionV2Message() {
865        return (this.match(DCCppConstants.FUNCTION_V2_CMD_REGEX) != null);
866    }
867
868    public boolean isForgetCabMessage() {
869        return (this.match(DCCppConstants.FORGET_CAB_CMD_REGEX) != null);
870    }
871
872    public boolean isTurnoutMessage() {
873        return (this.getOpCodeChar() == DCCppConstants.TURNOUT_CMD);
874    }
875
876    public boolean isSensorMessage() {
877        return (this.getOpCodeChar() == DCCppConstants.SENSOR_CMD);
878    }
879
880    public boolean isEEPROMWriteMessage() {
881        return (this.getOpCodeChar() == DCCppConstants.WRITE_TO_EEPROM_CMD);
882    }
883
884    public boolean isEEPROMClearMessage() {
885        return (this.getOpCodeChar() == DCCppConstants.CLEAR_EEPROM_CMD);
886    }
887
888    public boolean isOpsWriteByteMessage() {
889        return (this.getOpCodeChar() == DCCppConstants.OPS_WRITE_CV_BYTE);
890    }
891
892    public boolean isOpsWriteBitMessage() {
893        return (this.getOpCodeChar() == DCCppConstants.OPS_WRITE_CV_BIT);
894    }
895
896    public boolean isProgWriteByteMessage() {
897        return (this.getOpCodeChar() == DCCppConstants.PROG_WRITE_CV_BYTE);
898    }
899
900    public boolean isProgWriteBitMessage() {
901        return (this.getOpCodeChar() == DCCppConstants.PROG_WRITE_CV_BIT);
902    }
903
904    public boolean isProgReadMessage() {
905        return (this.getOpCodeChar() == DCCppConstants.PROG_READ_CV);
906    }
907
908    public boolean isProgVerifyMessage() {
909        return (this.getOpCodeChar() == DCCppConstants.PROG_VERIFY_CV);
910    }
911
912    public boolean isTurnoutCmdMessage() {
913        return (this.match(DCCppConstants.TURNOUT_CMD_REGEX) != null);
914    }
915
916    public boolean isTurnoutAddMessage() {
917        return (this.match(DCCppConstants.TURNOUT_ADD_REGEX) != null);
918    }
919
920    public boolean isTurnoutDeleteMessage() {
921        return (this.match(DCCppConstants.TURNOUT_DELETE_REGEX) != null);
922    }
923
924    public boolean isListTurnoutsMessage() {
925        return (this.match(DCCppConstants.TURNOUT_LIST_REGEX) != null);
926    }
927
928    public boolean isSensorAddMessage() {
929        return (this.match(DCCppConstants.SENSOR_ADD_REGEX) != null);
930    }
931
932    public boolean isSensorDeleteMessage() {
933        return (this.match(DCCppConstants.SENSOR_DELETE_REGEX) != null);
934    }
935
936    public boolean isListSensorsMessage() {
937        return (this.match(DCCppConstants.SENSOR_LIST_REGEX) != null);
938    }
939
940    //public boolean isOutputCmdMessage() { return(this.getOpCodeChar() == DCCppConstants.OUTPUT_CMD); }
941    public boolean isOutputCmdMessage() {
942        return (this.match(DCCppConstants.OUTPUT_CMD_REGEX) != null);
943    }
944
945    public boolean isOutputAddMessage() {
946        return (this.match(DCCppConstants.OUTPUT_ADD_REGEX) != null);
947    }
948
949    public boolean isOutputDeleteMessage() {
950        return (this.match(DCCppConstants.OUTPUT_DELETE_REGEX) != null);
951    }
952
953    public boolean isListOutputsMessage() {
954        return (this.match(DCCppConstants.OUTPUT_LIST_REGEX) != null);
955    }
956
957    public boolean isQuerySensorStatesMessage() {
958        return (this.match(DCCppConstants.QUERY_SENSOR_STATES_REGEX) != null);
959    }
960
961    public boolean isWriteDccPacketMessage() {
962        return ((this.getOpCodeChar() == DCCppConstants.WRITE_DCC_PACKET_MAIN) || (this.getOpCodeChar() == DCCppConstants.WRITE_DCC_PACKET_PROG));
963    }
964
965    //------------------------------------------------------
966    // Helper methods for Sensor Query Commands
967    public String getOutputIDString() {
968        if (this.isOutputAddMessage() || this.isOutputDeleteMessage() || this.isOutputCmdMessage()) {
969            return getValueString(1);
970        } else {
971            log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar());
972            return ("0");
973        }
974    }
975
976    public int getOutputIDInt() {
977        if (this.isOutputAddMessage() || this.isOutputDeleteMessage() || this.isOutputCmdMessage()) {
978            return (getValueInt(1)); // assumes stored as an int!
979        } else {
980            log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar());
981            return (0);
982        }
983    }
984
985    public String getOutputPinString() {
986        if (this.isOutputAddMessage()) {
987            return (getValueString(2));
988        } else {
989            log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar());
990            return ("0");
991        }
992    }
993
994    public int getOutputPinInt() {
995        if (this.isOutputAddMessage()) {
996            return (getValueInt(2));
997        } else {
998            log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar());
999            return (0);
1000        }
1001    }
1002
1003    public String getOutputIFlagString() {
1004        if (this.isOutputAddMessage()) {
1005            return (getValueString(3));
1006        } else {
1007            log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar());
1008            return ("0");
1009        }
1010    }
1011
1012    public int getOutputIFlagInt() {
1013        if (this.isOutputAddMessage()) {
1014            return (getValueInt(3));
1015        } else {
1016            log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar());
1017            return (0);
1018        }
1019    }
1020
1021    public String getOutputStateString() {
1022        if (isOutputCmdMessage()) {
1023            return (this.getOutputStateInt() == 1 ? "HIGH" : "LOW");
1024        } else {
1025            return ("Not a Turnout");
1026        }
1027    }
1028
1029    public int getOutputStateInt() {
1030        if (isOutputCmdMessage()) {
1031            return (getValueInt(2));
1032        } else {
1033            log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar());
1034            return (0);
1035        }
1036    }
1037
1038    public boolean getOutputStateBool() {
1039        if (this.isOutputCmdMessage()) {
1040            return (getValueInt(2) != 0);
1041        } else {
1042            log.error("Output Parser called on non-Output message type {} message {}", this.getOpCodeChar(), this);
1043            return (false);
1044        }
1045    }
1046
1047    public String getSensorIDString() {
1048        if (this.isSensorAddMessage()) {
1049            return getValueString(1);
1050        } else {
1051            log.error("Sensor Parser called on non-Sensor message type {}", this.getOpCodeChar());
1052            return ("0");
1053        }
1054    }
1055
1056    public int getSensorIDInt() {
1057        if (this.isSensorAddMessage()) {
1058            return (getValueInt(1)); // assumes stored as an int!
1059        } else {
1060            log.error("Sensor Parser called on non-Sensor message type {}", this.getOpCodeChar());
1061            return (0);
1062        }
1063    }
1064
1065    public String getSensorPinString() {
1066        if (this.isSensorAddMessage()) {
1067            return (getValueString(2));
1068        } else {
1069            log.error("Sensor Parser called on non-Sensor message type {}", this.getOpCodeChar());
1070            return ("0");
1071        }
1072    }
1073
1074    public int getSensorPinInt() {
1075        if (this.isSensorAddMessage()) {
1076            return (getValueInt(2));
1077        } else {
1078            log.error("Sensor Parser called on non-Sensor message type {}", this.getOpCodeChar());
1079            return (0);
1080        }
1081    }
1082
1083    public String getSensorPullupString() {
1084        if (isSensorAddMessage()) {
1085            return (getValueBool(3) ? "PULLUP" : "NO PULLUP");
1086        } else {
1087            return ("Not a Sensor");
1088        }
1089    }
1090
1091    public int getSensorPullupInt() {
1092        if (this.isSensorAddMessage()) {
1093            return (getValueInt(3));
1094        } else {
1095            log.error("Sensor Parser called on non-Sensor message type {} message {}", this.getOpCodeChar(), this);
1096            return (0);
1097        }
1098    }
1099
1100    public boolean getSensorPullupBool() {
1101        if (this.isSensorAddMessage()) {
1102            return (getValueBool(3));
1103        } else {
1104            log.error("Sensor Parser called on non-Sensor message type {} message {}", this.getOpCodeChar(), this);
1105            return (false);
1106        }
1107    }
1108
1109    // Helper methods for Accessory Decoder Commands
1110    public String getAccessoryAddrString() {
1111        if (this.isAccessoryMessage()) {
1112            return (getValueString(1));
1113        } else {
1114            log.error("Accessory Parser called on non-Accessory message type {}", this.getOpCodeChar());
1115            return ("0");
1116        }
1117    }
1118
1119    public int getAccessoryAddrInt() {
1120        if (this.isAccessoryMessage()) {
1121            return (getValueInt(1));
1122        } else {
1123            log.error("Accessory Parser called on non-Accessory message type {}", this.getOpCodeChar());
1124            return (0);
1125        }
1126        //return(Integer.parseInt(this.getAccessoryAddrString()));
1127    }
1128
1129    public String getAccessorySubString() {
1130        if (this.isAccessoryMessage()) {
1131            return (getValueString(2));
1132        } else {
1133            log.error("Accessory Parser called on non-Accessory message type {} message {}", this.getOpCodeChar(), this);
1134            return ("0");
1135        }
1136    }
1137
1138    public int getAccessorySubInt() {
1139        if (this.isAccessoryMessage()) {
1140            return (getValueInt(2));
1141        } else {
1142            log.error("Accessory Parser called on non-Accessory message type {} message {}", this.getOpCodeChar(), this);
1143            return (0);
1144        }
1145    }
1146
1147    public String getAccessoryStateString() {
1148        if (isAccessoryMessage()) {
1149            return (this.getAccessoryStateInt() == 1 ? "ON" : "OFF");
1150        } else {
1151            return ("Not an Accessory Decoder");
1152        }
1153    }
1154
1155    public int getAccessoryStateInt() {
1156        if (this.isAccessoryMessage()) {
1157            return (getValueInt(3));
1158        } else {
1159            log.error("Accessory Parser called on non-Accessory message type {} message {}", this.getOpCodeChar(), this);
1160            return (0);
1161        }
1162    }
1163
1164    //------------------------------------------------------
1165    // Helper methods for Throttle Commands
1166    public String getRegisterString() {
1167        if (this.isThrottleMessage() || this.isWriteDccPacketMessage()) {
1168            return (getValueString(1));
1169        } else {
1170            log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar());
1171            return ("0");
1172        }
1173    }
1174
1175    public int getRegisterInt() {
1176        if (this.isThrottleMessage()) {
1177            return (getValueInt(1));
1178        } else {
1179            log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar());
1180            return (0);
1181        }
1182    }
1183
1184    public String getAddressString() {
1185        if (this.isThrottleMessage()) {
1186            return (getValueString(2));
1187        } else {
1188            log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar());
1189            return ("0");
1190        }
1191    }
1192
1193    public int getAddressInt() {
1194        if (this.isThrottleMessage()) {
1195            return (getValueInt(2));
1196        } else {
1197            log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar());
1198            return (0);
1199        }
1200    }
1201
1202    public String getSpeedString() {
1203        if (this.isThrottleMessage()) {
1204            return (getValueString(3));
1205        } else {
1206            log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar());
1207            return ("0");
1208        }
1209    }
1210
1211    public int getSpeedInt() {
1212        if (this.isThrottleMessage()) {
1213            return (getValueInt(3));
1214        } else {
1215            log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar());
1216            return (0);
1217        }
1218    }
1219
1220    public String getDirectionString() {
1221        if (this.isThrottleMessage()) {
1222            return (this.getDirectionInt() == 1 ? "Forward" : "Reverse");
1223        } else {
1224            log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar());
1225            return ("Not a Throttle");
1226        }
1227    }
1228
1229    public int getDirectionInt() {
1230        if (this.isThrottleMessage()) {
1231            return (getValueInt(4));
1232        } else {
1233            log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar());
1234            return (0);
1235        }
1236    }
1237
1238    //------------------------------------------------------
1239    // Helper methods for Function Commands
1240    public String getFuncAddressString() {
1241        if (this.isFunctionMessage()) {
1242            return (getValueString(1));
1243        } else {
1244            log.error("Function Parser called on non-Function message type {}", this.getOpCodeChar());
1245            return ("0");
1246        }
1247    }
1248
1249    public int getFuncAddressInt() {
1250        if (this.isFunctionMessage()) {
1251            return (getValueInt(1));
1252        } else {
1253            log.error("Function Parser called on non-Function message type {}", this.getOpCodeChar());
1254            return (0);
1255        }
1256    }
1257
1258    public String getFuncByte1String() {
1259        if (this.isFunctionMessage()) {
1260            return (getValueString(2));
1261        } else {
1262            log.error("Function Parser called on non-Function message type {}", this.getOpCodeChar());
1263            return ("0");
1264        }
1265    }
1266
1267    public int getFuncByte1Int() {
1268        if (this.isFunctionMessage()) {
1269            return (getValueInt(2));
1270        } else {
1271            log.error("Function Parser called on non-Function message type {}", this.getOpCodeChar());
1272            return (0);
1273        }
1274    }
1275
1276    public String getFuncByte2String() {
1277        if (this.isFunctionMessage()) {
1278            return (getValueString(3));
1279        } else {
1280            log.error("Function Parser called on non-Function message type {}", this.getOpCodeChar());
1281            return ("0");
1282        }
1283    }
1284
1285    public int getFuncByte2Int() {
1286        if (this.isFunctionMessage()) {
1287            return (getValueInt(3));
1288        } else {
1289            log.error("Function Parser called on non-Function message type {}", this.getOpCodeChar());
1290            return (0);
1291        }
1292    }
1293
1294    public String getFuncV2CabString() {
1295        if (this.isFunctionV2Message()) {
1296            return (getValueString(1));
1297        } else {
1298            log.error("Function Parser called on non-Function V2 message type {}", this.getOpCodeChar());
1299            return ("0");
1300        }
1301    }
1302
1303    public String getFuncV2FuncString() {
1304        if (this.isFunctionV2Message()) {
1305            return (getValueString(2));
1306        } else {
1307            log.error("Function Parser called on non-Function V2 message type {}", this.getOpCodeChar());
1308            return ("0");
1309        }
1310    }
1311
1312    public String getFuncV2StateString() {
1313        if (this.isFunctionV2Message()) {
1314            return (getValueString(3));
1315        } else {
1316            log.error("Function Parser called on non-Function V2 message type {}", this.getOpCodeChar());
1317            return ("0");
1318        }
1319    }
1320
1321    public String getForgetCabString() {
1322        if (this.isForgetCabMessage()) {
1323            return (getValueString(1));
1324        } else {
1325            log.error("Function Parser called on non-Forget Cab message type {}", this.getOpCodeChar());
1326            return ("0");
1327        }
1328    }
1329
1330    //------------------------------------------------------
1331    // Helper methods for Turnout Commands
1332    public String getTOIDString() {
1333        if (this.isTurnoutMessage()) {
1334            return (getValueString(1));
1335        } else {
1336            log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this);
1337            return ("0");
1338        }
1339    }
1340
1341    public int getTOIDInt() {
1342        if (this.isTurnoutMessage()) {
1343            return (getValueInt(1));
1344        } else {
1345            log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this);
1346            return (0);
1347        }
1348    }
1349
1350    public String getTOStateString() {
1351        if (isTurnoutMessage()) {
1352            return (this.getTOStateInt() == 1 ? "THROWN" : "CLOSED");
1353        } else {
1354            return ("Not a Turnout");
1355        }
1356    }
1357
1358    public int getTOStateInt() {
1359        if (this.isTurnoutMessage()) {
1360            return (getValueInt(2));
1361        } else {
1362            log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this);
1363            return (0);
1364        }
1365    }
1366
1367    public String getTOAddressString() {
1368        if (this.isTurnoutAddMessage()) {
1369            return (getValueString(2));
1370        } else {
1371            log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this);
1372            return ("0");
1373        }
1374    }
1375
1376    public int getTOAddressInt() {
1377        if (this.isTurnoutAddMessage()) {
1378            return (getValueInt(2));
1379        } else {
1380            log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this);
1381            return (0);
1382        }
1383    }
1384
1385    public String getTOSubAddressString() {
1386        if (this.isTurnoutAddMessage()) {
1387            return (getValueString(3));
1388        } else {
1389            log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this);
1390            return ("0");
1391        }
1392    }
1393
1394    public int getTOSubAddressInt() {
1395        if (this.isTurnoutAddMessage()) {
1396            return (getValueInt(3));
1397        } else {
1398            log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this);
1399            return (0);
1400        }
1401    }
1402
1403    //------------------------------------------------------
1404    // Helper methods for Ops Write Byte Commands
1405    public String getOpsWriteAddrString() {
1406        if (this.isOpsWriteByteMessage() || this.isOpsWriteBitMessage()) {
1407            return (getValueString(1));
1408        } else {
1409            return ("0");
1410        }
1411    }
1412
1413    public int getOpsWriteAddrInt() {
1414        if (this.isOpsWriteByteMessage() || this.isOpsWriteBitMessage()) {
1415            return (getValueInt(1));
1416        } else {
1417            return (0);
1418        }
1419    }
1420
1421    public String getOpsWriteCVString() {
1422        if (this.isOpsWriteByteMessage() || this.isOpsWriteBitMessage()) {
1423            return (getValueString(2));
1424        } else {
1425            return ("0");
1426        }
1427    }
1428
1429    public int getOpsWriteCVInt() {
1430        if (this.isOpsWriteByteMessage() || this.isOpsWriteBitMessage()) {
1431            return (getValueInt(2));
1432        } else {
1433            return (0);
1434        }
1435    }
1436
1437    public String getOpsWriteBitString() {
1438        if (this.isOpsWriteBitMessage()) {
1439            return (getValueString(3));
1440        } else {
1441            return ("0");
1442        }
1443    }
1444
1445    public int getOpsWriteBitInt() {
1446        if (this.isOpsWriteBitMessage()) {
1447            return (getValueInt(3));
1448        } else {
1449            return (0);
1450        }
1451    }
1452
1453    public String getOpsWriteValueString() {
1454        if (this.isOpsWriteByteMessage()) {
1455            return (getValueString(3));
1456        } else if (this.isOpsWriteBitMessage()) {
1457            return (getValueString(4));
1458        } else {
1459            log.error("Ops Program Parser called on non-OpsProgram message type {}", this.getOpCodeChar());
1460            return ("0");
1461        }
1462    }
1463
1464    public int getOpsWriteValueInt() {
1465        if (this.isOpsWriteByteMessage()) {
1466            return (getValueInt(3));
1467        } else if (this.isOpsWriteBitMessage()) {
1468            return (getValueInt(4));
1469        } else {
1470            return (0);
1471        }
1472    }
1473
1474    //------------------------------------------------------
1475    // Helper methods for Prog Write and Read Byte Commands
1476    public String getCVString() {
1477        if (this.isProgWriteByteMessage() || this.isProgWriteBitMessage() || this.isProgReadMessage() || this.isProgVerifyMessage()) {
1478            return (getValueString(1));
1479        } else {
1480            return ("0");
1481        }
1482    }
1483
1484    public int getCVInt() {
1485        if (this.isProgWriteByteMessage() || this.isProgWriteBitMessage() || this.isProgReadMessage() || this.isProgVerifyMessage()) {
1486            return (getValueInt(1));
1487        } else {
1488            return (0);
1489        }
1490    }
1491
1492    public String getCallbackNumString() {
1493        int idx;
1494        if (this.isProgWriteByteMessage()) {
1495            idx = 3;
1496        } else if (this.isProgWriteBitMessage()) {
1497            idx = 4;
1498        } else if (this.isProgReadMessage()) {
1499            idx = 2;
1500        } else {
1501            return ("0");
1502        }
1503        return (getValueString(idx));
1504    }
1505
1506    public int getCallbackNumInt() {
1507        int idx;
1508        if (this.isProgWriteByteMessage()) {
1509            idx = 3;
1510        } else if (this.isProgWriteBitMessage()) {
1511            idx = 4;
1512        } else if (this.isProgReadMessage()) {
1513            idx = 2;
1514        } else {
1515            return (0);
1516        }
1517        return (getValueInt(idx));
1518    }
1519
1520    public String getCallbackSubString() {
1521        int idx;
1522        if (this.isProgWriteByteMessage()) {
1523            idx = 4;
1524        } else if (this.isProgWriteBitMessage()) {
1525            idx = 5;
1526        } else if (this.isProgReadMessage()) {
1527            idx = 3;
1528        } else {
1529            return ("0");
1530        }
1531        return (getValueString(idx));
1532    }
1533
1534    public int getCallbackSubInt() {
1535        int idx;
1536        if (this.isProgWriteByteMessage()) {
1537            idx = 4;
1538        } else if (this.isProgWriteBitMessage()) {
1539            idx = 5;
1540        } else if (this.isProgReadMessage()) {
1541            idx = 3;
1542        } else {
1543            return (0);
1544        }
1545        return (getValueInt(idx));
1546    }
1547
1548    public String getProgValueString() {
1549        int idx;
1550        if (this.isProgWriteByteMessage() || this.isProgVerifyMessage()) {
1551            idx = 2;
1552        } else if (this.isProgWriteBitMessage()) {
1553            idx = 3;
1554        } else {
1555            return ("0");
1556        }
1557        return (getValueString(idx));
1558    }
1559
1560    public int getProgValueInt() {
1561        int idx;
1562        if (this.isProgWriteByteMessage() || this.isProgVerifyMessage()) {
1563            idx = 2;
1564        } else if (this.isProgWriteBitMessage()) {
1565            idx = 3;
1566        } else {
1567            return (0);
1568        }
1569        return (getValueInt(idx));
1570    }
1571
1572    //------------------------------------------------------
1573    // Helper methods for Prog Write Bit Commands
1574    public String getBitString() {
1575        if (this.isProgWriteBitMessage()) {
1576            return (getValueString(2));
1577        } else {
1578            log.error("PWBit Parser called on non-PWBit message type {}", this.getOpCodeChar());
1579            return ("0");
1580        }
1581    }
1582
1583    public int getBitInt() {
1584        if (this.isProgWriteBitMessage()) {
1585            return (getValueInt(2));
1586        } else {
1587            return (0);
1588        }
1589    }
1590
1591    public String getPacketString() {
1592        if (this.isWriteDccPacketMessage()) {
1593            StringBuilder b = new StringBuilder();
1594            for (int i = 2; i <= getGroupCount() - 1; i++) {
1595                b.append(this.getValueString(i));
1596            }
1597            return (b.toString());
1598        } else {
1599            log.error("Write Dcc Packet parser called on non-Dcc Packet message type {}", this.getOpCodeChar());
1600            return ("0");
1601        }
1602    }
1603
1604    //------------------------------------------------------
1605
1606    /*
1607     * Most messages are sent with a reply expected, but
1608     * we have a few that we treat as though the reply is always
1609     * a broadcast message, because the reply usually comes to us
1610     * that way.
1611     */
1612    // TODO: Not sure this is useful in DCC++
1613    @Override
1614    public boolean replyExpected() {
1615        boolean retv;
1616        switch (this.getOpCodeChar()) {
1617            case DCCppConstants.THROTTLE_CMD:
1618            case DCCppConstants.TURNOUT_CMD:
1619            case DCCppConstants.SENSOR_CMD:
1620            case DCCppConstants.PROG_WRITE_CV_BYTE:
1621            case DCCppConstants.PROG_WRITE_CV_BIT:
1622            case DCCppConstants.PROG_READ_CV:
1623            case DCCppConstants.PROG_VERIFY_CV:
1624            case DCCppConstants.TRACK_POWER_ON:
1625            case DCCppConstants.TRACK_POWER_OFF:
1626            case DCCppConstants.READ_TRACK_CURRENT:
1627            case DCCppConstants.READ_CS_STATUS:
1628            case DCCppConstants.READ_MAXNUMSLOTS:
1629//            case DCCppConstants.GET_FREE_MEMORY:
1630            case DCCppConstants.OUTPUT_CMD:
1631            case DCCppConstants.LIST_REGISTER_CONTENTS:
1632                retv = true;
1633                break;
1634            default:
1635                retv = false;
1636        }
1637        return (retv);
1638    }
1639
1640    // decode messages of a particular form
1641    // create messages of a particular form
1642
1643    /*
1644     * The next group of routines are used by Feedback and/or turnout
1645     * control code.  These are used in multiple places within the code,
1646     * so they appear here.
1647     */
1648    
1649    /**
1650     * Stationary Decoder Message.
1651     * <p>
1652     * Note that many decoders and controllers combine the ADDRESS and
1653     * SUBADDRESS into a single number, N, from 1 through a max of 2044, where
1654     * <p>
1655     * {@code N = (ADDRESS - 1) * 4 + SUBADDRESS + 1, for all ADDRESS>0}
1656     * <p>
1657     * OR
1658     * <p>
1659     * {@code ADDRESS = INT((N - 1) / 4) + 1}
1660     *    {@code SUBADDRESS = (N - 1) % 4}
1661     * <p>
1662     * @param address the primary address of the decoder (0-511).
1663     * @param subaddress the subaddress of the decoder (0-3).
1664     * @param activate true on, false off.
1665     * @return accessory decoder message.
1666     */
1667    public static DCCppMessage makeAccessoryDecoderMsg(int address, int subaddress, boolean activate) {
1668        // Sanity check inputs
1669        if (address < 0 || address > DCCppConstants.MAX_ACC_DECODER_ADDRESS) {
1670            return (null);
1671        }
1672        if (subaddress < 0 || subaddress > DCCppConstants.MAX_ACC_DECODER_SUBADDR) {
1673            return (null);
1674        }
1675
1676        DCCppMessage m = new DCCppMessage(DCCppConstants.ACCESSORY_CMD);
1677
1678        m.myMessage.append(" ").append(address);
1679        m.myMessage.append(" ").append(subaddress);
1680        m.myMessage.append(" ").append(activate ? "1" : "0");
1681        m.myRegex = DCCppConstants.ACCESSORY_CMD_REGEX;
1682
1683        m._nDataChars = m.toString().length();
1684        return (m);
1685    }
1686
1687    public static DCCppMessage makeAccessoryDecoderMsg(int address, boolean activate) {
1688        // Convert the single address to an address/subaddress pair:
1689        // address = (address - 1) * 4 + subaddress + 1 for address>0;
1690        int addr, subaddr;
1691        if (address > 0) {
1692            addr = ((address - 1) / (DCCppConstants.MAX_ACC_DECODER_SUBADDR + 1)) + 1;
1693            subaddr = (address - 1) % (DCCppConstants.MAX_ACC_DECODER_SUBADDR + 1);
1694        } else {
1695            addr = subaddr = 0;
1696        }
1697        log.debug("makeAccessoryDecoderMsg address {}, addr {}, subaddr {}, activate {}", address, addr, subaddr, activate);
1698        return (makeAccessoryDecoderMsg(addr, subaddr, activate));
1699    }
1700
1701    /**
1702     * Predefined Turnout Control Message.
1703     * <p>
1704     * @param id the numeric ID (0-32767) of the turnout to control.
1705     * @param thrown true thrown, false closed.
1706     * @return message to set turnout.
1707     */
1708    public static DCCppMessage makeTurnoutCommandMsg(int id, boolean thrown) {
1709        // Sanity check inputs
1710        if (id < 0 || id > DCCppConstants.MAX_TURNOUT_ADDRESS) {
1711            return (null);
1712        }
1713        // Need to also validate whether turnout is predefined?  Where to store the IDs?
1714        // Turnout Command
1715
1716        DCCppMessage m = new DCCppMessage(DCCppConstants.TURNOUT_CMD);
1717        m.myMessage.append(" ").append(id);
1718        m.myMessage.append((thrown ? " 1" : " 0"));
1719        m.myRegex = DCCppConstants.TURNOUT_CMD_REGEX;
1720
1721        m._nDataChars = m.toString().length();
1722        return (m);
1723    }
1724
1725    public static DCCppMessage makeOutputCmdMsg(int id, boolean state) {
1726        // Sanity check inputs
1727        if (id < 0 || id > DCCppConstants.MAX_TURNOUT_ADDRESS) {
1728            return (null);
1729        }
1730
1731        DCCppMessage m = new DCCppMessage(DCCppConstants.OUTPUT_CMD);
1732        m.myMessage.append(" ").append(id);
1733        m.myMessage.append(" ").append(state ? "1" : "0");
1734        m.myRegex = DCCppConstants.OUTPUT_CMD_REGEX;
1735
1736        m._nDataChars = m.toString().length();
1737        return (m);
1738    }
1739
1740    public static DCCppMessage makeOutputAddMsg(int id, int pin, int iflag) {
1741        // Sanity check inputs
1742        if (id < 0 || id > DCCppConstants.MAX_TURNOUT_ADDRESS) {
1743            return (null);
1744        }
1745
1746        DCCppMessage m = new DCCppMessage(DCCppConstants.OUTPUT_CMD);
1747        m.myMessage.append(" ").append(id);
1748        m.myMessage.append(" ").append(pin);
1749        m.myMessage.append(" ").append(iflag);
1750        m.myRegex = DCCppConstants.OUTPUT_ADD_REGEX;
1751
1752        m._nDataChars = m.toString().length();
1753        return (m);
1754    }
1755
1756    public static DCCppMessage makeOutputDeleteMsg(int id) {
1757        // Sanity check inputs
1758        if (id < 0 || id > DCCppConstants.MAX_TURNOUT_ADDRESS) {
1759            return (null);
1760        }
1761
1762        DCCppMessage m = new DCCppMessage(DCCppConstants.OUTPUT_CMD);
1763        m.myMessage.append(" ").append(id);
1764        m.myRegex = DCCppConstants.OUTPUT_DELETE_REGEX;
1765
1766        m._nDataChars = m.toString().length();
1767        return (m);
1768    }
1769
1770    public static DCCppMessage makeOutputListMsg() {
1771        return (new DCCppMessage(DCCppConstants.OUTPUT_CMD, DCCppConstants.OUTPUT_LIST_REGEX));
1772    }
1773
1774    public static DCCppMessage makeTurnoutAddMsg(int id, int addr, int subaddr) {
1775        // Sanity check inputs
1776        if (id < 0 || id > DCCppConstants.MAX_TURNOUT_ADDRESS) {
1777            log.error("turnout Id {} must be between {} and {}", id, 0, DCCppConstants.MAX_TURNOUT_ADDRESS);
1778            return (null);
1779        }
1780        if (addr < 0 || addr > DCCppConstants.MAX_ACC_DECODER_ADDRESS) {
1781            log.error("turnout address {} must be between {} and {}", id, 0, DCCppConstants.MAX_ACC_DECODER_ADDRESS);
1782            return (null);
1783        }
1784        if (subaddr < 0 || subaddr > DCCppConstants.MAX_ACC_DECODER_SUBADDR) {
1785            log.error("turnout subaddress {} must be between {} and {}", id, 0, DCCppConstants.MAX_ACC_DECODER_SUBADDR);
1786            return (null);
1787        }
1788
1789        DCCppMessage m = new DCCppMessage(DCCppConstants.TURNOUT_CMD);
1790        m.myMessage.append(" ").append(id);
1791        m.myMessage.append(" ").append(addr);
1792        m.myMessage.append(" ").append(subaddr);
1793        m.myRegex = DCCppConstants.TURNOUT_ADD_REGEX;
1794
1795        m._nDataChars = m.toString().length();
1796        return (m);
1797    }
1798
1799    public static DCCppMessage makeTurnoutDeleteMsg(int id) {
1800        // Sanity check inputs
1801        if (id < 0 || id > DCCppConstants.MAX_TURNOUT_ADDRESS) {
1802            return (null);
1803        }
1804
1805        DCCppMessage m = new DCCppMessage(DCCppConstants.TURNOUT_CMD);
1806        m.myMessage.append(" ").append(id);
1807        m.myRegex = DCCppConstants.TURNOUT_DELETE_REGEX;
1808
1809        m._nDataChars = m.toString().length();
1810        return (m);
1811    }
1812
1813    public static DCCppMessage makeTurnoutListMsg() {
1814        return (new DCCppMessage(DCCppConstants.TURNOUT_CMD, DCCppConstants.TURNOUT_LIST_REGEX));
1815    }
1816
1817    /**
1818     * Create/Delete/Query Sensor.
1819     * <p>
1820     * sensor, or {@code <X>} if no sensors defined.
1821     * @param id pin pullup (0-32767).
1822     * @param pin Arduino pin index of sensor.
1823     * @param pullup true if use internal pullup for PIN, false if not.
1824     * @return message to create the sensor.
1825     */
1826    public static DCCppMessage makeSensorAddMsg(int id, int pin, int pullup) {
1827        // Sanity check inputs
1828        // TODO: Optional sanity check pin number vs. Arduino model.
1829        if (id < 0 || id > DCCppConstants.MAX_SENSOR_ID) {
1830            return (null);
1831        }
1832
1833        DCCppMessage m = new DCCppMessage(DCCppConstants.SENSOR_CMD);
1834        m.myMessage.append(" ").append(id);
1835        m.myMessage.append(" ").append(pin);
1836        m.myMessage.append(" ").append(pullup);
1837        m.myRegex = DCCppConstants.SENSOR_ADD_REGEX;
1838
1839        m._nDataChars = m.toString().length();
1840        return (m);
1841    }
1842
1843    public static DCCppMessage makeSensorDeleteMsg(int id) {
1844        // Sanity check inputs
1845        if (id < 0 || id > DCCppConstants.MAX_SENSOR_ID) {
1846            return (null);
1847        }
1848
1849        DCCppMessage m = new DCCppMessage(DCCppConstants.SENSOR_CMD);
1850        m.myMessage.append(" ").append(id);
1851        m.myRegex = DCCppConstants.SENSOR_DELETE_REGEX;
1852
1853        m._nDataChars = m.toString().length();
1854        return (m);
1855    }
1856
1857    public static DCCppMessage makeSensorListMsg() {
1858        return (new DCCppMessage(DCCppConstants.SENSOR_CMD, DCCppConstants.SENSOR_LIST_REGEX));
1859    }
1860
1861    /**
1862     * Query All Sensors States.
1863     * <p>
1864     * @return message to query all sensor states.
1865     */
1866    public static DCCppMessage makeQuerySensorStatesMsg() {
1867        return (new DCCppMessage(DCCppConstants.QUERY_SENSOR_STATES_CMD, DCCppConstants.QUERY_SENSOR_STATES_REGEX));
1868    }
1869
1870    /**
1871     * Write Direct CV Byte to Programming Track
1872     * <p>
1873     * Format: {@code <W CV VALUE CALLBACKNUM CALLBACKSUB>}
1874     * <p>
1875     * ID: the numeric ID (0-32767) of the turnout to control THROW: 0
1876     * (unthrown) or 1 (thrown) CV: the number of the Configuration Variable
1877     * memory location in the decoder to write to (1-1024) VALUE: the value to
1878     * be written to the Configuration Variable memory location (0-255)
1879     * CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base
1880     * Station and is simply echoed back in the output - useful for external
1881     * programs that call this function CALLBACKSUB: a second arbitrary integer
1882     * (0-32767) that is ignored by the Base Station and is simply echoed back
1883     * in the output - useful for external programs (e.g. DCC++ Interface) that
1884     * call this function
1885     * <p>
1886     * Note: The two-argument form embeds the opcode in CALLBACKSUB to aid in
1887     * decoding the responses.
1888     * <p>
1889     * returns: {@code <r CALLBACKNUM|CALLBACKSUB|CV Value)} where VALUE is a
1890     * number from 0-255 as read from the requested CV, or -1 if verification
1891     * read fails
1892     * @param cv CV index, 1-1024.
1893     * @param val new CV value, 0-255.
1894     * @return message to write Direct CV.
1895     */
1896    public static DCCppMessage makeWriteDirectCVMsg(int cv, int val) {
1897        return (makeWriteDirectCVMsg(cv, val, 0, DCCppConstants.PROG_WRITE_CV_BYTE));
1898    }
1899
1900    public static DCCppMessage makeWriteDirectCVMsg(int cv, int val, int callbacknum, int callbacksub) {
1901        // Sanity check inputs
1902        if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
1903            return (null);
1904        }
1905        if (val < 0 || val > DCCppConstants.MAX_DIRECT_CV_VAL) {
1906            return (null);
1907        }
1908        if (callbacknum < 0 || callbacknum > DCCppConstants.MAX_CALLBACK_NUM) {
1909            return (null);
1910        }
1911        if (callbacksub < 0 || callbacksub > DCCppConstants.MAX_CALLBACK_SUB) {
1912            return (null);
1913        }
1914
1915        DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_WRITE_CV_BYTE);
1916        m.myMessage.append(" ").append(cv);
1917        m.myMessage.append(" ").append(val);
1918        m.myMessage.append(" ").append(callbacknum);
1919        m.myMessage.append(" ").append(callbacksub);
1920        m.myRegex = DCCppConstants.PROG_WRITE_BYTE_REGEX;
1921
1922        m._nDataChars = m.toString().length();
1923        m.setTimeout(DCCppProgrammingTimeout);
1924        return (m);
1925    }
1926
1927    /**
1928     * Write Direct CV Bit to Programming Track.
1929     * <p>
1930     * Format: {@code <B CV BIT VALUE CALLBACKNUM CALLBACKSUB>}
1931     * <p>
1932     * writes, and then verifies, a single bit within a Configuration Variable
1933     * to the decoder of an engine on the programming track
1934     * <p>
1935     * CV: the number of the Configuration Variable memory location in the
1936     * decoder to write to (1-1024) BIT: the bit number of the Configurarion
1937     * Variable memory location to write (0-7) VALUE: the value of the bit to be
1938     * written (0-1) CALLBACKNUM: an arbitrary integer (0-32767) that is ignored
1939     * by the Base Station and is simply echoed back in the output - useful for
1940     * external programs that call this function CALLBACKSUB: a second arbitrary
1941     * integer (0-32767) that is ignored by the Base Station and is simply
1942     * echoed back in the output - useful for external programs (e.g. DCC++
1943     * Interface) that call this function
1944     * <p>
1945     * Note: The two-argument form embeds the opcode in CALLBACKSUB to aid in
1946     * decoding the responses.
1947     * <p>
1948     * returns: {@code <r CALLBACKNUM|CALLBACKSUB|CV BIT VALUE)} where VALUE is
1949     * a number from 0-1 as read from the requested CV bit, or -1 if
1950     * verification read fails
1951     * @param cv CV index, 1-1024.
1952     * @param bit bit index, 0-7
1953     * @param val bit value, 0-1.
1954     * @return message to write direct CV bit.
1955     */
1956    public static DCCppMessage makeBitWriteDirectCVMsg(int cv, int bit, int val) {
1957        return (makeBitWriteDirectCVMsg(cv, bit, val, 0, DCCppConstants.PROG_WRITE_CV_BIT));
1958    }
1959
1960    public static DCCppMessage makeBitWriteDirectCVMsg(int cv, int bit, int val, int callbacknum, int callbacksub) {
1961
1962        // Sanity Check Inputs
1963        if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
1964            return (null);
1965        }
1966        if (bit < 0 || bit > 7) {
1967            return (null);
1968        }
1969        if (callbacknum < 0 || callbacknum > DCCppConstants.MAX_CALLBACK_NUM) {
1970            return (null);
1971        }
1972        if (callbacksub < 0 || callbacksub > DCCppConstants.MAX_CALLBACK_SUB) {
1973            return (null);
1974        }
1975
1976        DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_WRITE_CV_BIT);
1977        m.myMessage.append(" ").append(cv);
1978        m.myMessage.append(" ").append(bit);
1979        m.myMessage.append(" ").append(val == 0 ? "0" : "1");
1980        m.myMessage.append(" ").append(callbacknum);
1981        m.myMessage.append(" ").append(callbacksub);
1982        m.myRegex = DCCppConstants.PROG_WRITE_BIT_REGEX;
1983
1984        m._nDataChars = m.toString().length();
1985        m.setTimeout(DCCppProgrammingTimeout);
1986        return (m);
1987    }
1988
1989    /**
1990     * Read Direct CV Byte from Programming Track.
1991     * <p>
1992     * Format: {@code <R CV CALLBACKNUM CALLBACKSUB>}
1993     * <p>
1994     * reads a Configuration Variable from the decoder of an engine on the
1995     * programming track
1996     * <p>
1997     * CV: the number of the Configuration Variable memory location in the
1998     * decoder to read from (1-1024) CALLBACKNUM: an arbitrary integer (0-32767)
1999     * that is ignored by the Base Station and is simply echoed back in the
2000     * output - useful for external programs that call this function
2001     * CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the
2002     * Base Station and is simply echoed back in the output - useful for
2003     * external programs (e.g. DCC++ Interface) that call this function
2004     * <p>
2005     * Note: The two-argument form embeds the opcode in CALLBACKSUB to aid in
2006     * decoding the responses.
2007     * <p>
2008     * returns: {@code <r CALLBACKNUM|CALLBACKSUB|CV VALUE>} where VALUE is a
2009     * number from 0-255 as read from the requested CV, or -1 if read could not
2010     * be verified
2011     * @param cv CV index.
2012     * @return message to send read direct CV.
2013     */
2014    public static DCCppMessage makeReadDirectCVMsg(int cv) {
2015        return (makeReadDirectCVMsg(cv, 0, DCCppConstants.PROG_READ_CV));
2016    }
2017
2018    public static DCCppMessage makeReadDirectCVMsg(int cv, int callbacknum, int callbacksub) {
2019        // Sanity check inputs
2020        if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
2021            return (null);
2022        }
2023        if (callbacknum < 0 || callbacknum > DCCppConstants.MAX_CALLBACK_NUM) {
2024            return (null);
2025        }
2026        if (callbacksub < 0 || callbacksub > DCCppConstants.MAX_CALLBACK_SUB) {
2027            return (null);
2028        }
2029
2030        DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_READ_CV);
2031        m.myMessage.append(" ").append(cv);
2032        m.myMessage.append(" ").append(callbacknum);
2033        m.myMessage.append(" ").append(callbacksub);
2034        m.myRegex = DCCppConstants.PROG_READ_REGEX;
2035
2036        m._nDataChars = m.toString().length();
2037        m.setTimeout(DCCppProgrammingTimeout);
2038        return (m);
2039    }
2040
2041    /**
2042     * Verify Direct CV Byte from Programming Track.
2043     * <p>
2044     * Format: {@code <V CV STARTVAL>}
2045     * <p>
2046     * Verifies a Configuration Variable from the decoder of an engine on the
2047     * programming track. Returns the current value of that CV.
2048     * Used as faster replacement for 'R'eadCV command
2049     * <p>
2050     * CV: the number of the Configuration Variable memory location in the
2051     * decoder to read from (1-1024) STARTVAL: a "guess" as to the current
2052     * value of the CV. DCC-EX will try this value first, then read and return
2053     * the current value if different
2054     * <p>
2055     * returns: {@code <v CV VALUE>} where VALUE is a
2056     * number from 0-255 as read from the requested CV, -1 if read could not
2057     * be performed
2058     * @param cv CV index.
2059     * @param startVal "guess" as to current value
2060     * @return message to send verify direct CV.
2061     */
2062    public static DCCppMessage makeVerifyCVMsg(int cv, int startVal) {
2063        // Sanity check inputs
2064        if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
2065            return (null);
2066        }
2067        DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_VERIFY_CV);
2068        m.myMessage.append(" ").append(cv);
2069        m.myMessage.append(" ").append(startVal);
2070        m.myRegex = DCCppConstants.PROG_VERIFY_REGEX;
2071
2072        m._nDataChars = m.toString().length();
2073        m.setTimeout(DCCppProgrammingTimeout);
2074        return (m);
2075    }
2076
2077    /**
2078     * Write Direct CV Byte to Main Track
2079     * <p>
2080     * Format: {@code <w CAB CV VALUE>}
2081     * <p>
2082     * Writes, without any verification, a Configuration Variable to the decoder
2083     * of an engine on the main operations track.
2084     * <p>
2085     * @param address the short (1-127) or long (128-10293) address of the 
2086     *                  engine decoder.
2087     * @param cv the number of the Configuration Variable memory location in the
2088     *                  decoder to write to (1-1024).
2089     * @param val the value to be written to the
2090     *                  Configuration Variable memory location (0-255).
2091     * @return message to Write CV in Ops Mode.
2092     */
2093    @CheckForNull
2094    public static DCCppMessage makeWriteOpsModeCVMsg(int address, int cv, int val) {
2095        // Sanity check inputs
2096        if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2097            return (null);
2098        }
2099        if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
2100            return (null);
2101        }
2102        if (val < 0 || val > DCCppConstants.MAX_DIRECT_CV_VAL) {
2103            return (null);
2104        }
2105
2106        DCCppMessage m = new DCCppMessage(DCCppConstants.OPS_WRITE_CV_BYTE);
2107        m.myMessage.append(" ").append(address);
2108        m.myMessage.append(" ").append(cv);
2109        m.myMessage.append(" ").append(val);
2110        m.myRegex = DCCppConstants.OPS_WRITE_BYTE_REGEX;
2111
2112        m._nDataChars = m.toString().length();
2113        m.setTimeout(DCCppProgrammingTimeout);
2114        return (m);
2115    }
2116
2117    /**
2118     * Write Direct CV Bit to Main Track.
2119     * <p>
2120     * Format: {@code <b CAB CV BIT VALUE>}
2121     * <p>
2122     * writes, without any verification, a single bit within a Configuration
2123     * Variable to the decoder of an engine on the main operations track
2124     * <p>
2125     * CAB: the short (1-127) or long (128-10293) address of the engine decoder
2126     * CV: the number of the Configuration Variable memory location in the
2127     * decoder to write to (1-1024) BIT: the bit number of the Configuration
2128     * Variable register to write (0-7) VALUE: the value of the bit to be
2129     * written (0-1)
2130     * <p>
2131     * returns: NONE
2132     * @param address loco cab address.
2133     * @param cv CV index, 1-1024.
2134     * @param bit bit index, 0-7.
2135     * @param val bit value, 0 or 1.
2136     * @return message to write direct CV bit to main track.
2137     */
2138    public static DCCppMessage makeBitWriteOpsModeCVMsg(int address, int cv, int bit, int val) {
2139        // Sanity Check Inputs
2140        if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2141            return (null);
2142        }
2143        if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
2144            return (null);
2145        }
2146        if (bit < 0 || bit > 7) {
2147            return (null);
2148        }
2149
2150        DCCppMessage m = new DCCppMessage(DCCppConstants.OPS_WRITE_CV_BIT);
2151        m.myMessage.append(" ").append(address);
2152        m.myMessage.append(" ").append(cv);
2153        m.myMessage.append(" ").append(bit);
2154        m.myMessage.append(" ").append(val == 0 ? "0" : "1");
2155
2156        m.myRegex = DCCppConstants.OPS_WRITE_BIT_REGEX;
2157
2158        m._nDataChars = m.toString().length();
2159        m.setTimeout(DCCppProgrammingTimeout);
2160        return (m);
2161    }
2162
2163    /**
2164     * Set Track Power ON or OFF.
2165     * <p>
2166     * Format: {@code <1> (ON) or <0> (OFF)}
2167     * <p>
2168     * @return message to send track power on or off.
2169     * @param on true on, false off.
2170     */
2171    public static DCCppMessage makeSetTrackPowerMsg(boolean on) {
2172        return (new DCCppMessage((on ? DCCppConstants.TRACK_POWER_ON : DCCppConstants.TRACK_POWER_OFF),
2173                DCCppConstants.TRACK_POWER_REGEX));
2174    }
2175
2176    public static DCCppMessage makeTrackPowerOnMsg() {
2177        return (makeSetTrackPowerMsg(true));
2178    }
2179
2180    public static DCCppMessage makeTrackPowerOffMsg() {
2181        return (makeSetTrackPowerMsg(false));
2182    }
2183
2184    /**
2185     * Read main operations track current
2186     * <p>
2187     * Format: {@code <c>}
2188     * <p>
2189     * reads current being drawn on main operations track
2190     * <p>
2191     * @return (for DCC-EX), 1 or more of  {@code <c MeterName value C/V unit min max res warn>}
2192     * where name and settings are used to define arbitrary meters on the DCC-EX side
2193     * AND {@code <a CURRENT>} where CURRENT = 0-1024, based on 
2194     * exponentially-smoothed weighting scheme
2195     * 
2196     */
2197    public static DCCppMessage makeReadTrackCurrentMsg() {
2198        return (new DCCppMessage(DCCppConstants.READ_TRACK_CURRENT, DCCppConstants.READ_TRACK_CURRENT_REGEX));
2199    }
2200
2201    /**
2202     * Read DCC++ Base Station Status
2203     * <p>
2204     * Format: {@code <s>}
2205     * <p>
2206     * returns status messages containing track power status, throttle status,
2207     * turn-out status, and a version number NOTE: this is very useful as a
2208     * first command for an interface to send to this sketch in order to verify
2209     * connectivity and update any GUI to reflect actual throttle and turn-out
2210     * settings
2211     * <p>
2212     * @return series of status messages that can be read by an interface to
2213     * determine status of DCC++ Base Station and important settings
2214     */
2215    public static DCCppMessage makeCSStatusMsg() {
2216        return (new DCCppMessage(DCCppConstants.READ_CS_STATUS, DCCppConstants.READ_CS_STATUS_REGEX));
2217    }
2218
2219    /**
2220     * Get number of supported slots for this DCC++ Base Station Status
2221     * <p>
2222     * Format: {@code <N>}
2223     * <p>
2224     * returns number of slots NOTE: this is not implemented in older versions
2225     * which then do not return anything at all
2226     * <p>
2227     * @return status message with to get number of slots.
2228     */
2229    public static DCCppMessage makeCSMaxNumSlotsMsg() {
2230        return (new DCCppMessage(DCCppConstants.READ_MAXNUMSLOTS, DCCppConstants.READ_MAXNUMSLOTS_REGEX));
2231    }
2232    /**
2233     * Generate a function message using the V2 'F' syntax supported by DCC-EX
2234     * <p>
2235     * @param cab cab address to send function to
2236     * @param func function number to set
2237     * @param state new state of function 0/1
2238     * @return function V2 message
2239     */
2240    public static DCCppMessage makeFunctionV2Message(int cab, int func, int state) {
2241        // Sanity check inputs
2242        if (cab < 0 || cab > DCCppConstants.MAX_LOCO_ADDRESS) {
2243            return (null);
2244        }
2245        if (func < 0 || func > DCCppConstants.MAX_FUNCTION_NUMBER) {
2246            return (null);
2247        }
2248        if (state < 0 || state > 1) {
2249            return (null);
2250        }
2251        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_V2_CMD);
2252        m.myMessage.append(" ").append(cab);
2253        m.myMessage.append(" ").append(func);
2254        m.myMessage.append(" ").append(state);
2255        m.myRegex = DCCppConstants.FUNCTION_V2_CMD_REGEX;
2256        m._nDataChars = m.toString().length();
2257        return (m);
2258    }
2259
2260    /**
2261     * Generate a "Forget Cab" message '-'
2262     * <p>
2263     * @param cab cab address to send function to (or 0 for all)
2264     * @return forget message to be sent
2265     */
2266    public static DCCppMessage makeForgetCabMessage(int cab) {
2267        // Sanity check inputs
2268        if (cab < 0 || cab > DCCppConstants.MAX_LOCO_ADDRESS) {
2269            return (null);
2270        }
2271        DCCppMessage m = new DCCppMessage(DCCppConstants.FORGET_CAB_CMD);
2272        if (cab > 0) {
2273            m.myMessage.append(" ").append(cab);
2274        }
2275        m.myRegex = DCCppConstants.FORGET_CAB_CMD_REGEX;
2276        m._nDataChars = m.toString().length();
2277        return (m);
2278    }
2279
2280    /**
2281     * Generate an emergency stop for the specified address.
2282     * <p>
2283     * Note: This just sends a THROTTLE command with speed = -1
2284     * 
2285     * @param register Register Number for the loco assigned address.
2286     * @param address is the locomotive address.
2287     * @return message to send e stop to the specified address.
2288     */
2289    public static DCCppMessage makeAddressedEmergencyStop(int register, int address) {
2290        // Sanity check inputs
2291        if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2292            return (null);
2293        }
2294
2295        DCCppMessage m = new DCCppMessage(DCCppConstants.THROTTLE_CMD);
2296        m.myMessage.append(" ").append(register);
2297        m.myMessage.append(" ").append(address);
2298        m.myMessage.append(" -1 1");
2299        m.myRegex = DCCppConstants.THROTTLE_CMD_REGEX;
2300
2301        m._nDataChars = m.toString().length();
2302        return (m);
2303    }
2304
2305    /**
2306     * Generate an emergency stop for all locos in reminder table.
2307     * @return message to send e stop for all locos
2308     */
2309    public static DCCppMessage makeEmergencyStopAllMsg() {
2310        DCCppMessage m = new DCCppMessage(DCCppConstants.ESTOP_ALL_CMD);
2311        m.myRegex = DCCppConstants.ESTOP_ALL_REGEX;
2312
2313        m._nDataChars = m.toString().length();
2314        return (m);
2315    }
2316
2317    /**
2318     * Generate a Speed and Direction Request message
2319     *
2320     * @param register  is the DCC++ base station register assigned.
2321     * @param address   is the locomotive address
2322     * @param speed     a normalized speed value (a floating point number
2323     *                  between 0 and 1). A negative value indicates emergency
2324     *                  stop.
2325     * @param isForward true for forward, false for reverse.
2326     *
2327     * Format: {@code <t REGISTER CAB SPEED DIRECTION>}
2328     *
2329     * sets the throttle for a given register/cab combination
2330     *
2331     * REGISTER: an internal register number, from 1 through MAX_MAIN_REGISTERS
2332     * (inclusive), to store the DCC packet used to control this throttle
2333     * setting CAB: the short (1-127) or long (128-10293) address of the engine
2334     * decoder SPEED: throttle speed from 0-126, or -1 for emergency stop
2335     * (resets SPEED to 0) DIRECTION: 1=forward, 0=reverse. Setting direction
2336     * when speed=0 or speed=-1 only effects directionality of cab lighting for
2337     * a stopped train
2338     *
2339     * @return {@code <T REGISTER SPEED DIRECTION>}
2340     *
2341     */
2342    public static DCCppMessage makeSpeedAndDirectionMsg(int register, int address, float speed, boolean isForward) {
2343        // Sanity check inputs
2344        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2345            return (null);
2346        }
2347
2348        DCCppMessage m = new DCCppMessage(DCCppConstants.THROTTLE_CMD);
2349        m.myMessage.append(" ").append(register);
2350        m.myMessage.append(" ").append(address);
2351        if (speed < 0.0) {
2352            m.myMessage.append(" -1");
2353        } else {
2354            int speedVal = java.lang.Math.round(speed * 126);
2355            speedVal = Math.min(speedVal, DCCppConstants.MAX_SPEED);
2356            m.myMessage.append(" ").append(speedVal);
2357        }
2358        m.myMessage.append(" ").append(isForward ? "1" : "0");
2359
2360        m.myRegex = DCCppConstants.THROTTLE_CMD_REGEX;
2361
2362        m._nDataChars = m.toString().length();
2363        return (m);
2364    }
2365
2366    /*
2367     * Function Group Messages (common serial format)
2368     * <p>
2369     * Format: {@code <f CAB BYTE1 [BYTE2]>}
2370     * <p>
2371     * turns on and off engine decoder functions F0-F28 (F0 is sometimes called
2372     * FL) NOTE: setting requests transmitted directly to mobile engine decoder
2373     * --- current state of engine functions is not stored by this program
2374     * <p>
2375     * CAB: the short (1-127) or long (128-10293) address of the engine decoder
2376     * <p>
2377     * To set functions F0-F4 on (=1) or off (=0):
2378     * <p>
2379     * BYTE1: 128 + F1*1 + F2*2 + F3*4 + F4*8 + F0*16 BYTE2: omitted
2380     * <p>
2381     * To set functions F5-F8 on (=1) or off (=0):
2382     * <p>
2383     * BYTE1: 176 + F5*1 + F6*2 + F7*4 + F8*8 BYTE2: omitted
2384     * <p>
2385     * To set functions F9-F12 on (=1) or off (=0):
2386     * <p>
2387     * BYTE1: 160 + F9*1 +F10*2 + F11*4 + F12*8 BYTE2: omitted
2388     * <p>
2389     * To set functions F13-F20 on (=1) or off (=0):
2390     * <p>
2391     * BYTE1: 222 BYTE2: F13*1 + F14*2 + F15*4 + F16*8 + F17*16 + F18*32 +
2392     * F19*64 + F20*128
2393     * <p>
2394     * To set functions F21-F28 on (=1) of off (=0):
2395     * <p>
2396     * BYTE1: 223 BYTE2: F21*1 + F22*2 + F23*4 + F24*8 + F25*16 + F26*32 +
2397     * F27*64 + F28*128
2398     * <p>
2399     * returns: NONE
2400     * <p>
2401     */
2402    /**
2403     * Generate a Function Group One Operation Request message.
2404     *
2405     * @param address is the locomotive address
2406     * @param f0      is true if f0 is on, false if f0 is off
2407     * @param f1      is true if f1 is on, false if f1 is off
2408     * @param f2      is true if f2 is on, false if f2 is off
2409     * @param f3      is true if f3 is on, false if f3 is off
2410     * @param f4      is true if f4 is on, false if f4 is off
2411     * @return message to set function group 1.
2412     */
2413    public static DCCppMessage makeFunctionGroup1OpsMsg(int address,
2414            boolean f0,
2415            boolean f1,
2416            boolean f2,
2417            boolean f3,
2418            boolean f4) {
2419        // Sanity check inputs
2420        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2421            return (null);
2422        }
2423
2424        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
2425        m.myMessage.append(" ").append(address);
2426
2427        int byte1 = 128 + (f0 ? 16 : 0);
2428        byte1 += (f1 ? 1 : 0);
2429        byte1 += (f2 ? 2 : 0);
2430        byte1 += (f3 ? 4 : 0);
2431        byte1 += (f4 ? 8 : 0);
2432        m.myMessage.append(" ").append(byte1);
2433        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
2434
2435        m._nDataChars = m.toString().length();
2436        return (m);
2437    }
2438
2439    /**
2440     * Generate a Function Group One Set Momentary Functions message.
2441     *
2442     * @param address is the locomotive address
2443     * @param f0      is true if f0 is momentary
2444     * @param f1      is true if f1 is momentary
2445     * @param f2      is true if f2 is momentary
2446     * @param f3      is true if f3 is momentary
2447     * @param f4      is true if f4 is momentary
2448     * @return message to set momentary function group 1.
2449     */
2450    public static DCCppMessage makeFunctionGroup1SetMomMsg(int address,
2451            boolean f0,
2452            boolean f1,
2453            boolean f2,
2454            boolean f3,
2455            boolean f4) {
2456
2457        // Sanity check inputs
2458        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2459            return (null);
2460        }
2461
2462        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
2463        m.myMessage.append(" ").append(address);
2464
2465        int byte1 = 128 + (f0 ? 16 : 0);
2466        byte1 += (f1 ? 1 : 0);
2467        byte1 += (f2 ? 2 : 0);
2468        byte1 += (f3 ? 4 : 0);
2469        byte1 += (f4 ? 8 : 0);
2470
2471        m.myMessage.append(" ").append(byte1);
2472        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
2473
2474        m._nDataChars = m.toString().length();
2475        return (m);
2476    }
2477
2478    /**
2479     * Generate a Function Group Two Operation Request message.
2480     *
2481     * @param address is the locomotive address
2482     * @param f5      is true if f5 is on, false if f5 is off
2483     * @param f6      is true if f6 is on, false if f6 is off
2484     * @param f7      is true if f7 is on, false if f7 is off
2485     * @param f8      is true if f8 is on, false if f8 is off
2486     * @return message to set function group 2.
2487     */
2488    public static DCCppMessage makeFunctionGroup2OpsMsg(int address,
2489            boolean f5,
2490            boolean f6,
2491            boolean f7,
2492            boolean f8) {
2493
2494        // Sanity check inputs
2495        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2496            return (null);
2497        }
2498
2499        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
2500        m.myMessage.append(" ").append(address);
2501
2502        int byte1 = 176;
2503        byte1 += (f5 ? 1 : 0);
2504        byte1 += (f6 ? 2 : 0);
2505        byte1 += (f7 ? 4 : 0);
2506        byte1 += (f8 ? 8 : 0);
2507
2508        m.myMessage.append(" ").append(byte1);
2509        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
2510
2511        m._nDataChars = m.toString().length();
2512        return (m);
2513    }
2514
2515    /**
2516     * Generate a Function Group Two Set Momentary Functions message.
2517     *
2518     * @param address is the locomotive address
2519     * @param f5      is true if f5 is momentary
2520     * @param f6      is true if f6 is momentary
2521     * @param f7      is true if f7 is momentary
2522     * @param f8      is true if f8 is momentary
2523     * @return message to set momentary function group 2.
2524     */
2525    public static DCCppMessage makeFunctionGroup2SetMomMsg(int address,
2526            boolean f5,
2527            boolean f6,
2528            boolean f7,
2529            boolean f8) {
2530
2531        // Sanity check inputs
2532        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2533            return (null);
2534        }
2535
2536        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
2537        m.myMessage.append(" ").append(address);
2538
2539        int byte1 = 176;
2540        byte1 += (f5 ? 1 : 0);
2541        byte1 += (f6 ? 2 : 0);
2542        byte1 += (f7 ? 4 : 0);
2543        byte1 += (f8 ? 8 : 0);
2544        m.myMessage.append(" ").append(byte1);
2545        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
2546
2547        m._nDataChars = m.toString().length();
2548        return (m);
2549    }
2550
2551    /**
2552     * Generate a Function Group Three Operation Request message.
2553     *
2554     * @param address is the locomotive address
2555     * @param f9      is true if f9 is on, false if f9 is off
2556     * @param f10     is true if f10 is on, false if f10 is off
2557     * @param f11     is true if f11 is on, false if f11 is off
2558     * @param f12     is true if f12 is on, false if f12 is off
2559     * @return message to set function group 3.
2560     */
2561    public static DCCppMessage makeFunctionGroup3OpsMsg(int address,
2562            boolean f9,
2563            boolean f10,
2564            boolean f11,
2565            boolean f12) {
2566
2567        // Sanity check inputs
2568        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2569            return (null);
2570        }
2571
2572        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
2573        m.myMessage.append(" ").append(address);
2574
2575        int byte1 = 160;
2576        byte1 += (f9 ? 1 : 0);
2577        byte1 += (f10 ? 2 : 0);
2578        byte1 += (f11 ? 4 : 0);
2579        byte1 += (f12 ? 8 : 0);
2580        m.myMessage.append(" ").append(byte1);
2581        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
2582
2583        m._nDataChars = m.toString().length();
2584        return (m);
2585    }
2586
2587    /**
2588     * Generate a Function Group Three Set Momentary Functions message.
2589     *
2590     * @param address is the locomotive address
2591     * @param f9      is true if f9 is momentary
2592     * @param f10     is true if f10 is momentary
2593     * @param f11     is true if f11 is momentary
2594     * @param f12     is true if f12 is momentary
2595     * @return message to set momentary function group 3.
2596     */
2597    public static DCCppMessage makeFunctionGroup3SetMomMsg(int address,
2598            boolean f9,
2599            boolean f10,
2600            boolean f11,
2601            boolean f12) {
2602
2603        // Sanity check inputs
2604        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2605            return (null);
2606        }
2607
2608        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
2609        m.myMessage.append(" ").append(address);
2610
2611        int byte1 = 160;
2612        byte1 += (f9 ? 1 : 0);
2613        byte1 += (f10 ? 2 : 0);
2614        byte1 += (f11 ? 4 : 0);
2615        byte1 += (f12 ? 8 : 0);
2616        m.myMessage.append(" ").append(byte1);
2617        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
2618
2619        m._nDataChars = m.toString().length();
2620        return (m);
2621    }
2622
2623    /**
2624     * Generate a Function Group Four Operation Request message.
2625     *
2626     * @param address is the locomotive address
2627     * @param f13     is true if f13 is on, false if f13 is off
2628     * @param f14     is true if f14 is on, false if f14 is off
2629     * @param f15     is true if f15 is on, false if f15 is off
2630     * @param f16     is true if f18 is on, false if f16 is off
2631     * @param f17     is true if f17 is on, false if f17 is off
2632     * @param f18     is true if f18 is on, false if f18 is off
2633     * @param f19     is true if f19 is on, false if f19 is off
2634     * @param f20     is true if f20 is on, false if f20 is off
2635     * @return message to set function group 4.
2636     */
2637    public static DCCppMessage makeFunctionGroup4OpsMsg(int address,
2638            boolean f13,
2639            boolean f14,
2640            boolean f15,
2641            boolean f16,
2642            boolean f17,
2643            boolean f18,
2644            boolean f19,
2645            boolean f20) {
2646
2647        // Sanity check inputs
2648        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2649            return (null);
2650        }
2651
2652        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
2653        m.myMessage.append(" ").append(address);
2654
2655        int byte2 = 0;
2656        byte2 += (f13 ? 1 : 0);
2657        byte2 += (f14 ? 2 : 0);
2658        byte2 += (f15 ? 4 : 0);
2659        byte2 += (f16 ? 8 : 0);
2660        byte2 += (f17 ? 16 : 0);
2661        byte2 += (f18 ? 32 : 0);
2662        byte2 += (f19 ? 64 : 0);
2663        byte2 += (f20 ? 128 : 0);
2664        m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP4_BYTE1);
2665        m.myMessage.append(" ").append(byte2);
2666        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
2667
2668        m._nDataChars = m.toString().length();
2669        return (m);
2670    }
2671
2672    /**
2673     * Generate a Function Group Four Set Momentary Function message.
2674     *
2675     * @param address is the locomotive address
2676     * @param f13     is true if f13 is Momentary
2677     * @param f14     is true if f14 is Momentary
2678     * @param f15     is true if f15 is Momentary
2679     * @param f16     is true if f18 is Momentary
2680     * @param f17     is true if f17 is Momentary
2681     * @param f18     is true if f18 is Momentary
2682     * @param f19     is true if f19 is Momentary
2683     * @param f20     is true if f20 is Momentary
2684     * @return message to set momentary function group 4.
2685     */
2686    public static DCCppMessage makeFunctionGroup4SetMomMsg(int address,
2687            boolean f13,
2688            boolean f14,
2689            boolean f15,
2690            boolean f16,
2691            boolean f17,
2692            boolean f18,
2693            boolean f19,
2694            boolean f20) {
2695
2696        // Sanity check inputs
2697        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2698            return (null);
2699        }
2700
2701        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
2702        m.myMessage.append(" ").append(address);
2703
2704        int byte2 = 0;
2705        byte2 += (f13 ? 1 : 0);
2706        byte2 += (f14 ? 2 : 0);
2707        byte2 += (f15 ? 4 : 0);
2708        byte2 += (f16 ? 8 : 0);
2709        byte2 += (f17 ? 16 : 0);
2710        byte2 += (f18 ? 32 : 0);
2711        byte2 += (f19 ? 64 : 0);
2712        byte2 += (f20 ? 128 : 0);
2713
2714        m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP4_BYTE1);
2715        m.myMessage.append(" ").append(byte2);
2716        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
2717
2718        m._nDataChars = m.toString().length();
2719        return (m);
2720    }
2721
2722    /**
2723     * Generate a Function Group Five Operation Request message.
2724     *
2725     * @param address is the locomotive address
2726     * @param f21     is true if f21 is on, false if f21 is off
2727     * @param f22     is true if f22 is on, false if f22 is off
2728     * @param f23     is true if f23 is on, false if f23 is off
2729     * @param f24     is true if f24 is on, false if f24 is off
2730     * @param f25     is true if f25 is on, false if f25 is off
2731     * @param f26     is true if f26 is on, false if f26 is off
2732     * @param f27     is true if f27 is on, false if f27 is off
2733     * @param f28     is true if f28 is on, false if f28 is off
2734     * @return message to set function group 5.
2735     */
2736    public static DCCppMessage makeFunctionGroup5OpsMsg(int address,
2737            boolean f21,
2738            boolean f22,
2739            boolean f23,
2740            boolean f24,
2741            boolean f25,
2742            boolean f26,
2743            boolean f27,
2744            boolean f28) {
2745        // Sanity check inputs
2746        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2747            return (null);
2748        }
2749
2750        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
2751        m.myMessage.append(" ").append(address);
2752
2753        int byte2 = 0;
2754        byte2 += (f21 ? 1 : 0);
2755        byte2 += (f22 ? 2 : 0);
2756        byte2 += (f23 ? 4 : 0);
2757        byte2 += (f24 ? 8 : 0);
2758        byte2 += (f25 ? 16 : 0);
2759        byte2 += (f26 ? 32 : 0);
2760        byte2 += (f27 ? 64 : 0);
2761        byte2 += (f28 ? 128 : 0);
2762        log.debug("DCCppMessage: Byte2 = {}", byte2);
2763
2764        m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP5_BYTE1);
2765        m.myMessage.append(" ").append(byte2);
2766        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
2767
2768        m._nDataChars = m.toString().length();
2769        return (m);
2770    }
2771
2772    /**
2773     * Generate a Function Group Five Set Momentary Function message.
2774     *
2775     * @param address is the locomotive address
2776     * @param f21     is true if f21 is momentary
2777     * @param f22     is true if f22 is momentary
2778     * @param f23     is true if f23 is momentary
2779     * @param f24     is true if f24 is momentary
2780     * @param f25     is true if f25 is momentary
2781     * @param f26     is true if f26 is momentary
2782     * @param f27     is true if f27 is momentary
2783     * @param f28     is true if f28 is momentary
2784     * @return message to set momentary function group 5.
2785     */
2786    public static DCCppMessage makeFunctionGroup5SetMomMsg(int address,
2787            boolean f21,
2788            boolean f22,
2789            boolean f23,
2790            boolean f24,
2791            boolean f25,
2792            boolean f26,
2793            boolean f27,
2794            boolean f28) {
2795
2796        // Sanity check inputs
2797        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2798            return (null);
2799        }
2800
2801        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
2802        m.myMessage.append(" ").append(address);
2803
2804        int byte2 = 0;
2805        byte2 += (f21 ? 1 : 0);
2806        byte2 += (f22 ? 2 : 0);
2807        byte2 += (f23 ? 4 : 0);
2808        byte2 += (f24 ? 8 : 0);
2809        byte2 += (f25 ? 16 : 0);
2810        byte2 += (f26 ? 32 : 0);
2811        byte2 += (f27 ? 64 : 0);
2812        byte2 += (f28 ? 128 : 0);
2813
2814        m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP5_BYTE1);
2815        m.myMessage.append(" ").append(byte2);
2816        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
2817
2818        m._nDataChars = m.toString().length();
2819        return (m);
2820    }
2821
2822    /*
2823     * Build an Emergency Off Message
2824     */
2825
2826    /*
2827     * Test Code Functions... not for normal use
2828     */
2829
2830    /**
2831     * Write DCC Packet to a specified Register on the Main.
2832     * <br>
2833     * DCC++ BaseStation code appends its own error-correction byte so we must
2834     * not provide one.
2835     *
2836     * @param register the DCC++ BaseStation main register number to use
2837     * @param numBytes the number of bytes in the packet
2838     * @param bytes    byte array representing the packet. The first
2839     *                 {@code num_bytes} are used.
2840     * @return the formatted message to send
2841     */
2842    public static DCCppMessage makeWriteDCCPacketMainMsg(int register, int numBytes, byte[] bytes) {
2843        // Sanity Check Inputs
2844        if (register < 0 || register > DCCppConstants.MAX_MAIN_REGISTERS || numBytes < 2 || numBytes > 5) {
2845            return (null);
2846        }
2847
2848        DCCppMessage m = new DCCppMessage(DCCppConstants.WRITE_DCC_PACKET_MAIN);
2849        m.myMessage.append(" ").append(register);
2850        for (int k = 0; k < numBytes; k++) {
2851            m.myMessage.append(" ").append(jmri.util.StringUtil.twoHexFromInt(bytes[k]));
2852        }
2853        m.myRegex = DCCppConstants.WRITE_DCC_PACKET_MAIN_REGEX;
2854        return (m);
2855
2856    }
2857
2858    /**
2859     * Write DCC Packet to a specified Register on the Programming Track.
2860     * <br><br>
2861     * DCC++ BaseStation code appends its own error-correction byte so we must
2862     * not provide one.
2863     *
2864     * @param register the DCC++ BaseStation main register number to use
2865     * @param numBytes the number of bytes in the packet
2866     * @param bytes    byte array representing the packet. The first
2867     *                 {@code num_bytes} are used.
2868     * @return the formatted message to send
2869     */
2870    public static DCCppMessage makeWriteDCCPacketProgMsg(int register, int numBytes, byte[] bytes) {
2871        // Sanity Check Inputs
2872        if (register < 0 || register > DCCppConstants.MAX_MAIN_REGISTERS || numBytes < 2 || numBytes > 5) {
2873            return (null);
2874        }
2875
2876        DCCppMessage m = new DCCppMessage(DCCppConstants.WRITE_DCC_PACKET_PROG);
2877        m.myMessage.append(" ").append(register);
2878        for (int k = 0; k < numBytes; k++) {
2879            m.myMessage.append(" ").append(jmri.util.StringUtil.twoHexFromInt(bytes[k]));
2880        }
2881        m.myRegex = DCCppConstants.WRITE_DCC_PACKET_PROG_REGEX;
2882        return (m);
2883
2884    }
2885
2886//    public static DCCppMessage makeCheckFreeMemMsg() {
2887//        return (new DCCppMessage(DCCppConstants.GET_FREE_MEMORY, DCCppConstants.GET_FREE_MEMORY_REGEX));
2888//    }
2889//
2890    public static DCCppMessage makeListRegisterContentsMsg() {
2891        return (new DCCppMessage(DCCppConstants.LIST_REGISTER_CONTENTS,
2892                DCCppConstants.LIST_REGISTER_CONTENTS_REGEX));
2893    }
2894
2895    /**
2896     * This implementation of equals is targeted to the background function
2897     * refreshing in SerialDCCppPacketizer. To keep only one function group in
2898     * the refresh queue the logic is as follows. Two messages are equal if they
2899     * are:
2900     * <ul>
2901     * <li>actually identical, or</li>
2902     * <li>a function call to the same address and same function group</li>
2903     * </ul>
2904     */
2905    @Override
2906    public boolean equals(final Object obj) {
2907        if (obj == null) {
2908            return false;
2909        }
2910
2911        if (!(obj instanceof DCCppMessage)) {
2912            return false;
2913        }
2914
2915        final DCCppMessage other = (DCCppMessage) obj;
2916
2917        final String myCmd = this.toString();
2918        final String otherCmd = other.toString();
2919
2920        if (myCmd.equals(otherCmd)) {
2921            return true;
2922        }
2923
2924        if (!(myCmd.charAt(0) == DCCppConstants.FUNCTION_CMD) || !(otherCmd.charAt(0) == DCCppConstants.FUNCTION_CMD)) {
2925            return false;
2926        }
2927
2928        final int mySpace1 = myCmd.indexOf(' ', 2);
2929        final int otherSpace1 = otherCmd.indexOf(' ', 2);
2930
2931        if (mySpace1 != otherSpace1) {
2932            return false;
2933        }
2934
2935        if (!myCmd.subSequence(2, mySpace1).equals(otherCmd.subSequence(2, otherSpace1))) {
2936            return false;
2937        }
2938
2939        int mySpace2 = myCmd.indexOf(' ', mySpace1 + 1);
2940        if (mySpace2 < 0) {
2941            mySpace2 = myCmd.length();
2942        }
2943
2944        int otherSpace2 = otherCmd.indexOf(' ', otherSpace1 + 1);
2945        if (otherSpace2 < 0) {
2946            otherSpace2 = otherCmd.length();
2947        }
2948
2949        final int myBaseFunction = Integer.parseInt(myCmd.substring(mySpace1 + 1, mySpace2));
2950        final int otherBaseFunction = Integer.parseInt(otherCmd.substring(otherSpace1 + 1, otherSpace2));
2951
2952        if (myBaseFunction == otherBaseFunction) {
2953            return true;
2954        }
2955
2956        return getFuncBaseByte1(myBaseFunction) == getFuncBaseByte1(otherBaseFunction);
2957    }
2958
2959    @Override
2960    public int hashCode() {
2961        return toString().hashCode();
2962    }
2963
2964    /**
2965     * Get the function group from the first byte of the function setting call.
2966     *
2967     * @param byte1 first byte (mixed in with function bits for groups 1 to 3,
2968     *              or standalone value for groups 4 and 5)
2969     * @return the base group
2970     */
2971    private static int getFuncBaseByte1(final int byte1) {
2972        if (byte1 == DCCppConstants.FUNCTION_GROUP4_BYTE1 || byte1 == DCCppConstants.FUNCTION_GROUP5_BYTE1) {
2973            return byte1;
2974        }
2975
2976        if (byte1 < 160) {
2977            return 128;
2978        }
2979
2980        if (byte1 < 176) {
2981            return 160;
2982        }
2983
2984        return 176;
2985    }
2986
2987    /**
2988     * When is this message supposed to be resent?
2989     */
2990    private long expireTime;
2991
2992    /**
2993     * Before adding the message to the delay queue call this method to set when
2994     * the message should be repeated. The only time guarantee is that it will
2995     * be repeated after <u>at least</u> this much time, but it can be
2996     * significantly longer until it is repeated, function of the message queue
2997     * length.
2998     *
2999     * @param millis milliseconds in the future
3000     */
3001    public void delayFor(final long millis) {
3002        expireTime = System.currentTimeMillis() + millis;
3003    }
3004
3005    /**
3006     * Comparing two queued message for refreshing the function calls, based on
3007     * their expected execution time.
3008     */
3009    @Override
3010    public int compareTo(@Nonnull final Delayed o) {
3011        final long diff = this.expireTime - ((DCCppMessage) o).expireTime;
3012
3013        if (diff < 0) {
3014            return -1;
3015        }
3016
3017        if (diff > 0) {
3018            return 1;
3019        }
3020
3021        return 0;
3022    }
3023
3024    /**
3025     * From the {@link Delayed} interface, how long this message still has until
3026     * it should be executed.
3027     */
3028    @Override
3029    public long getDelay(final TimeUnit unit) {
3030        return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
3031    }
3032
3033    // initialize logging
3034    private static final Logger log = LoggerFactory.getLogger(DCCppMessage.class);
3035
3036}