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