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