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