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