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