001package jmri.jmrix.dccpp;
002
003import java.util.concurrent.Delayed;
004import java.util.concurrent.TimeUnit;
005import java.util.regex.Matcher;
006import java.util.regex.Pattern;
007import java.util.regex.PatternSyntaxException;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011import javax.annotation.CheckForNull;
012import javax.annotation.Nonnull;
013
014/**
015 * Represents a single command or response on the DCC-EX.
016 * <p>
017 * Content is represented with ints to avoid the problems with sign-extension
018 * that bytes have, and because a Java char is actually a variable number of
019 * bytes in Unicode.
020 *
021 * @author Bob Jacobsen Copyright (C) 2002
022 * @author Paul Bender Copyright (C) 2003-2010
023 * @author Mark Underwood Copyright (C) 2015
024 * @author Costin Grigoras Copyright (C) 2018
025 * @author Harald Barth Copyright (C) 2019
026 *
027 * Based on XNetMessage by Bob Jacobsen and Paul Bender
028 */
029
030/*
031 * A few words on implementation:
032 *
033 * DCCppMessage objects are (usually) created by calling one of the static makeMessageType()
034 * methods, and are then consumed by the TrafficController/Packetizer by being converted to
035 * a String and sent out the port.
036 * <p>
037 * Internally the DCCppMessage is actually stored as a String, and alongside that is kept
038 * a Regex for easy extraction of the values where needed in the code.
039 * <p>
040 * The various getParameter() type functions are mainly for convenience in places such as the
041 * port monitor where we want to be able to extract the /meaning/ of the DCCppMessage and
042 * present it in a human readable form.  Using the getParameterType() methods insulates
043 * the higher level code from needing to know what order/format the actual message is
044 * in.
045 */
046public class DCCppMessage extends jmri.jmrix.AbstractMRMessage implements Delayed {
047
048    private static int _nRetries = 3;
049
050    /* According to the specification, DCC-EX has a maximum timing
051     interval of 500 milliseconds during normal communications */
052    protected static final int DCCppProgrammingTimeout = 10000;  // TODO: Appropriate value for DCC-EX?
053    private static int DCCppMessageTimeout = 5000;  // TODO: Appropriate value for DCC-EX?
054
055    private StringBuilder myMessage;
056    private String myRegex;
057    private char opcode;
058
059    /**
060     * Create a new object, representing a specific-length message.
061     *
062     * @param len Total bytes in message, including opcode and error-detection
063     *            byte.
064     */
065    //NOTE: Not used anywhere useful... consider removing.
066    public DCCppMessage(int len) {
067        super(len);
068        setBinary(false);
069        setRetries(_nRetries);
070        setTimeout(DCCppMessageTimeout);
071        if (len > DCCppConstants.MAX_MESSAGE_SIZE || len < 0) {
072            log.error("Invalid length in ctor: {}", len);
073        }
074        _nDataChars = len;
075        myRegex = "";
076        myMessage = new StringBuilder(len);
077    }
078
079    /**
080     * Create a new object, that is a copy of an existing message.
081     *
082     * @param message existing message.
083     */
084    public DCCppMessage(DCCppMessage message) {
085        super(message);
086        setBinary(false);
087        setRetries(_nRetries);
088        setTimeout(DCCppMessageTimeout);
089        myRegex = message.myRegex;
090        myMessage = message.myMessage;
091        toStringCache = message.toStringCache;
092    }
093
094    /**
095     * Create an DCCppMessage from an DCCppReply.
096     * Not used.  Really, not even possible.  Consider removing.
097     * @param message existing reply to replicate.
098     */
099    public DCCppMessage(DCCppReply message) {
100        super(message.getNumDataElements());
101        setBinary(false);
102        setRetries(_nRetries);
103        setTimeout(DCCppMessageTimeout);
104        for (int i = 0; i < message.getNumDataElements(); i++) {
105            setElement(i, message.getElement(i));
106        }
107    }
108
109    /**
110     * Create a DCCppMessage from a String containing bytes.
111     * <p>
112     * Since DCCppMessages are text, there is no Hex-to-byte conversion.
113     * <p>
114     * NOTE 15-Feb-17: un-Deprecating this function so that it can be used in
115     * the DCCppOverTCP server/client interface.
116     * Messages shouldn't be parsed, they are already in DCC-EX format,
117     * so we need the string constructor to generate a DCCppMessage from
118     * the incoming byte stream.
119     * @param s message in string form.
120     */
121    public DCCppMessage(String s) {
122        setBinary(false);
123        setRetries(_nRetries);
124        setTimeout(DCCppMessageTimeout);
125        myMessage = new StringBuilder(s); // yes, copy... or... maybe not.
126        toStringCache = s;
127        // gather bytes in result
128        setRegex();
129        _nDataChars = myMessage.length();
130        _dataChars = new int[_nDataChars];
131    }
132
133    // Partial constructor used in the static getMessageType() calls below.
134    protected DCCppMessage(char c) {
135        setBinary(false);
136        setRetries(_nRetries);
137        setTimeout(DCCppMessageTimeout);
138        opcode = c;
139        myMessage = new StringBuilder(Character.toString(c));
140        _nDataChars = myMessage.length();
141    }
142
143    protected DCCppMessage(char c, String regex) {
144        setBinary(false);
145        setRetries(_nRetries);
146        setTimeout(DCCppMessageTimeout);
147        opcode = c;
148        myRegex = regex;
149        myMessage = new StringBuilder(Character.toString(c));
150        _nDataChars = myMessage.length();
151    }
152
153    private void setRegex() {
154        switch (myMessage.charAt(0)) {
155            case DCCppConstants.THROTTLE_CMD:
156                if ((match(toString(), DCCppConstants.THROTTLE_CMD_REGEX, "ctor")) != null) {
157                    myRegex = DCCppConstants.THROTTLE_CMD_REGEX;
158                } else if ((match(toString(), DCCppConstants.THROTTLE_V3_CMD_REGEX, "ctor")) != null) {
159                    myRegex = DCCppConstants.THROTTLE_V3_CMD_REGEX;
160                }
161                break;
162            case DCCppConstants.FUNCTION_CMD:
163                myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
164                break;
165            case DCCppConstants.FUNCTION_V4_CMD:
166                myRegex = DCCppConstants.FUNCTION_V4_CMD_REGEX;
167                break;
168            case DCCppConstants.FORGET_CAB_CMD:
169                myRegex = DCCppConstants.FORGET_CAB_CMD_REGEX;
170                break;
171            case DCCppConstants.ACCESSORY_CMD:
172                myRegex = DCCppConstants.ACCESSORY_CMD_REGEX;
173                break;
174            case DCCppConstants.TURNOUT_CMD:
175                if ((match(toString(), DCCppConstants.TURNOUT_ADD_REGEX, "ctor")) != null) {
176                    myRegex = DCCppConstants.TURNOUT_ADD_REGEX;
177                } else if ((match(toString(), DCCppConstants.TURNOUT_ADD_DCC_REGEX, "ctor")) != null) {
178                    myRegex = DCCppConstants.TURNOUT_ADD_DCC_REGEX;
179                } else if ((match(toString(), DCCppConstants.TURNOUT_ADD_SERVO_REGEX, "ctor")) != null) {
180                    myRegex = DCCppConstants.TURNOUT_ADD_SERVO_REGEX;
181                } else if ((match(toString(), DCCppConstants.TURNOUT_ADD_VPIN_REGEX, "ctor")) != null) {
182                    myRegex = DCCppConstants.TURNOUT_ADD_VPIN_REGEX;
183                } else if ((match(toString(), DCCppConstants.TURNOUT_DELETE_REGEX, "ctor")) != null) {
184                    myRegex = DCCppConstants.TURNOUT_DELETE_REGEX;
185                } else if ((match(toString(), DCCppConstants.TURNOUT_LIST_REGEX, "ctor")) != null) {
186                    myRegex = DCCppConstants.TURNOUT_LIST_REGEX;
187                } else if ((match(toString(), DCCppConstants.TURNOUT_CMD_REGEX, "ctor")) != null) {
188                    myRegex = DCCppConstants.TURNOUT_CMD_REGEX;
189                } else if ((match(toString(), DCCppConstants.TURNOUT_IMPL_REGEX, "ctor")) != null) {
190                    myRegex = DCCppConstants.TURNOUT_IMPL_REGEX;
191                } else {
192                    myRegex = "";
193                }
194                break;
195            case DCCppConstants.SENSOR_CMD:
196                if ((match(toString(), DCCppConstants.SENSOR_ADD_REGEX, "ctor")) != null) {
197                    myRegex = DCCppConstants.SENSOR_ADD_REGEX;
198                } else if ((match(toString(), DCCppConstants.SENSOR_DELETE_REGEX, "ctor")) != null) {
199                    myRegex = DCCppConstants.SENSOR_DELETE_REGEX;
200                } else if ((match(toString(), DCCppConstants.SENSOR_LIST_REGEX, "ctor")) != null) {
201                    myRegex = DCCppConstants.SENSOR_LIST_REGEX;
202                } else {
203                    myRegex = "";
204                }
205                break;
206            case DCCppConstants.OUTPUT_CMD:
207                if ((match(toString(), DCCppConstants.OUTPUT_ADD_REGEX, "ctor")) != null) {
208                    myRegex = DCCppConstants.OUTPUT_ADD_REGEX;
209                } else if ((match(toString(), DCCppConstants.OUTPUT_DELETE_REGEX, "ctor")) != null) {
210                    myRegex = DCCppConstants.OUTPUT_DELETE_REGEX;
211                } else if ((match(toString(), DCCppConstants.OUTPUT_LIST_REGEX, "ctor")) != null) {
212                    myRegex = DCCppConstants.OUTPUT_LIST_REGEX;
213                } else if ((match(toString(), DCCppConstants.OUTPUT_CMD_REGEX, "ctor")) != null) {
214                    myRegex = DCCppConstants.OUTPUT_CMD_REGEX;
215                } else {
216                    myRegex = "";
217                }
218                break;
219            case DCCppConstants.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-EX, 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-EX 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-EX 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-EX 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-EX
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
1989    public static DCCppMessage makeStartExrailMsg(int id) {
1990        DCCppMessage m = makeMessage("/ START " + id); // </ START id>
1991        m.myRegex = DCCppConstants.CONTROL_CMD_REGEX;
1992        m._nDataChars = m.toString().length();
1993        return (m);
1994    }
1995    public static DCCppMessage makeStartExrailMsg(int id, int address) {
1996        DCCppMessage m = makeMessage("/ START " + address + " " + id); // </ START cab id>
1997        m.myRegex = DCCppConstants.CONTROL_CMD_REGEX;
1998        m._nDataChars = m.toString().length();
1999        return (m);
2000    }
2001    public static DCCppMessage makeCurrentMaxesMsg() {
2002        DCCppMessage m = makeMessage(DCCppConstants.CURRENT_MAXES); // <JG>
2003        m.myRegex = DCCppConstants.CURRENT_MAXES_REGEX;
2004        m._nDataChars = m.toString().length();
2005        return (m);
2006    }
2007    public static DCCppMessage makeCurrentValuesMsg() {
2008        DCCppMessage m = makeMessage(DCCppConstants.CURRENT_VALUES); // <JI>
2009        m.myRegex = DCCppConstants.CURRENT_VALUES_REGEX;
2010        m._nDataChars = m.toString().length();
2011        return (m);
2012    }
2013
2014    public static DCCppMessage makeClockRequestTimeMsg() {
2015        DCCppMessage m = makeMessage(DCCppConstants.CLOCK_REQUEST_TIME); // <JC>
2016        m.myRegex = DCCppConstants.CLOCK_REQUEST_TIME_REGEX;
2017        m._nDataChars = m.toString().length();
2018        return (m);
2019    }
2020    public static DCCppMessage makeClockSetMsg(int minutes, int rate) {
2021        DCCppMessage m = makeMessage(DCCppConstants.CLOCK_REQUEST_TIME + " " + minutes + " " + rate); //<JC 123 12>
2022        m.myRegex = DCCppConstants.CLOCK_SET_REGEX;
2023        m._nDataChars = m.toString().length();
2024        return (m);
2025    }
2026    public static DCCppMessage makeClockSetMsg(int minutes) {
2027        DCCppMessage m = makeMessage(DCCppConstants.CLOCK_REQUEST_TIME + " " + minutes); //<JC 123>
2028        m.myRegex = DCCppConstants.CLOCK_SET_REGEX;
2029        m._nDataChars = m.toString().length();
2030        return (m);
2031    }
2032
2033    public static DCCppMessage makeTrackManagerRequestMsg() {
2034        return (new DCCppMessage(DCCppConstants.TRACKMANAGER_CMD, DCCppConstants.TRACKMANAGER_CMD_REGEX));
2035    }
2036
2037    public static DCCppMessage makeMessage(String msg) {
2038        return (new DCCppMessage(msg));
2039    }
2040
2041    /**
2042     * Create/Delete/Query Sensor.
2043     * <p>
2044     * sensor, or {@code <X>} if no sensors defined.
2045     * @param id pin pullup (0-32767).
2046     * @param pin Arduino pin index of sensor.
2047     * @param pullup true if use internal pullup for PIN, false if not.
2048     * @return message to create the sensor.
2049     */
2050    public static DCCppMessage makeSensorAddMsg(int id, int pin, int pullup) {
2051        // Sanity check inputs
2052        // TODO: Optional sanity check pin number vs. Arduino model.
2053        if (id < 0 || id > DCCppConstants.MAX_SENSOR_ID) {
2054            return (null);
2055        }
2056
2057        DCCppMessage m = new DCCppMessage(DCCppConstants.SENSOR_CMD);
2058        m.myMessage.append(" ").append(id);
2059        m.myMessage.append(" ").append(pin);
2060        m.myMessage.append(" ").append(pullup);
2061        m.myRegex = DCCppConstants.SENSOR_ADD_REGEX;
2062
2063        m._nDataChars = m.toString().length();
2064        return (m);
2065    }
2066
2067    public static DCCppMessage makeSensorDeleteMsg(int id) {
2068        // Sanity check inputs
2069        if (id < 0 || id > DCCppConstants.MAX_SENSOR_ID) {
2070            return (null);
2071        }
2072
2073        DCCppMessage m = new DCCppMessage(DCCppConstants.SENSOR_CMD);
2074        m.myMessage.append(" ").append(id);
2075        m.myRegex = DCCppConstants.SENSOR_DELETE_REGEX;
2076
2077        m._nDataChars = m.toString().length();
2078        return (m);
2079    }
2080
2081    public static DCCppMessage makeSensorListMsg() {
2082        return (new DCCppMessage(DCCppConstants.SENSOR_CMD, DCCppConstants.SENSOR_LIST_REGEX));
2083    }
2084
2085    /**
2086     * Query All Sensors States.
2087     *
2088     * @return message to query all sensor states.
2089     */
2090    public static DCCppMessage makeQuerySensorStatesMsg() {
2091        return (new DCCppMessage(DCCppConstants.QUERY_SENSOR_STATES_CMD, DCCppConstants.QUERY_SENSOR_STATES_REGEX));
2092    }
2093
2094    /**
2095     * Write Direct CV Byte to Programming Track
2096     * <p>
2097     * Format: {@code <W CV VALUE CALLBACKNUM CALLBACKSUB>}
2098     * <p>
2099     * CV: the number of the Configuration Variable
2100     * memory location in the decoder to write to (1-1024) VALUE: the value to
2101     * be written to the Configuration Variable memory location (0-255)
2102     * CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base
2103     * Station and is simply echoed back in the output - useful for external
2104     * programs that call this function CALLBACKSUB: a second arbitrary integer
2105     * (0-32767) that is ignored by the Base Station and is simply echoed back
2106     * in the output - useful for external programs (e.g. DCC-EX Interface) that
2107     * call this function
2108     * <p>
2109     * Note: The two-argument form embeds the opcode in CALLBACKSUB to aid in
2110     * decoding the responses.
2111     * <p>
2112     * returns: {@code <r CALLBACKNUM|CALLBACKSUB|CV Value)} where VALUE is a
2113     * number from 0-255 as read from the requested CV, or -1 if verification
2114     * read fails
2115     * @param cv CV index, 1-1024.
2116     * @param val new CV value, 0-255.
2117     * @return message to write Direct CV.
2118     */
2119    public static DCCppMessage makeWriteDirectCVMsg(int cv, int val) {
2120        return (makeWriteDirectCVMsg(cv, val, 0, DCCppConstants.PROG_WRITE_CV_BYTE));
2121    }
2122
2123    public static DCCppMessage makeWriteDirectCVMsg(int cv, int val, int callbacknum, int callbacksub) {
2124        // Sanity check inputs
2125        if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
2126            return (null);
2127        }
2128        if (val < 0 || val > DCCppConstants.MAX_DIRECT_CV_VAL) {
2129            return (null);
2130        }
2131        if (callbacknum < 0 || callbacknum > DCCppConstants.MAX_CALLBACK_NUM) {
2132            return (null);
2133        }
2134        if (callbacksub < 0 || callbacksub > DCCppConstants.MAX_CALLBACK_SUB) {
2135            return (null);
2136        }
2137
2138        DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_WRITE_CV_BYTE);
2139        m.myMessage.append(" ").append(cv);
2140        m.myMessage.append(" ").append(val);
2141        m.myMessage.append(" ").append(callbacknum);
2142        m.myMessage.append(" ").append(callbacksub);
2143        m.myRegex = DCCppConstants.PROG_WRITE_BYTE_REGEX;
2144
2145        m._nDataChars = m.toString().length();
2146        m.setTimeout(DCCppProgrammingTimeout);
2147        return (m);
2148    }
2149
2150    public static DCCppMessage makeWriteDirectCVMsgV4(int cv, int val) {
2151        // Sanity check inputs
2152        if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
2153            return (null);
2154        }
2155        if (val < 0 || val > DCCppConstants.MAX_DIRECT_CV_VAL) {
2156            return (null);
2157        }
2158
2159        DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_WRITE_CV_BYTE);
2160        m.myMessage.append(" ").append(cv);
2161        m.myMessage.append(" ").append(val);
2162        m.myRegex = DCCppConstants.PROG_WRITE_BYTE_V4_REGEX;
2163
2164        m._nDataChars = m.toString().length();
2165        m.setTimeout(DCCppProgrammingTimeout);
2166        return (m);
2167    }
2168
2169    /**
2170     * Write Direct CV Bit to Programming Track.
2171     * <p>
2172     * Format: {@code <B CV BIT VALUE CALLBACKNUM CALLBACKSUB>}
2173     * <p>
2174     * writes, and then verifies, a single bit within a Configuration Variable
2175     * to the decoder of an engine on the programming track
2176     * <p>
2177     * CV: the number of the Configuration Variable memory location in the
2178     * decoder to write to (1-1024) BIT: the bit number of the Configurarion
2179     * Variable memory location to write (0-7) VALUE: the value of the bit to be
2180     * written (0-1) CALLBACKNUM: an arbitrary integer (0-32767) that is ignored
2181     * by the Base Station and is simply echoed back in the output - useful for
2182     * external programs that call this function CALLBACKSUB: a second arbitrary
2183     * integer (0-32767) that is ignored by the Base Station and is simply
2184     * echoed back in the output - useful for external programs (e.g. DCC-EX
2185     * Interface) that call this function
2186     * <p>
2187     * Note: The two-argument form embeds the opcode in CALLBACKSUB to aid in
2188     * decoding the responses.
2189     * <p>
2190     * returns: {@code <r CALLBACKNUM|CALLBACKSUB|CV BIT VALUE)} where VALUE is
2191     * a number from 0-1 as read from the requested CV bit, or -1 if
2192     * verification read fails
2193     * @param cv CV index, 1-1024.
2194     * @param bit bit index, 0-7
2195     * @param val bit value, 0-1.
2196     * @return message to write direct CV bit.
2197     */
2198    public static DCCppMessage makeBitWriteDirectCVMsg(int cv, int bit, int val) {
2199        return (makeBitWriteDirectCVMsg(cv, bit, val, 0, DCCppConstants.PROG_WRITE_CV_BIT));
2200    }
2201
2202    public static DCCppMessage makeBitWriteDirectCVMsg(int cv, int bit, int val, int callbacknum, int callbacksub) {
2203
2204        // Sanity Check Inputs
2205        if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
2206            return (null);
2207        }
2208        if (bit < 0 || bit > 7) {
2209            return (null);
2210        }
2211        if (callbacknum < 0 || callbacknum > DCCppConstants.MAX_CALLBACK_NUM) {
2212            return (null);
2213        }
2214        if (callbacksub < 0 || callbacksub > DCCppConstants.MAX_CALLBACK_SUB) {
2215            return (null);
2216        }
2217
2218        DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_WRITE_CV_BIT);
2219        m.myMessage.append(" ").append(cv);
2220        m.myMessage.append(" ").append(bit);
2221        m.myMessage.append(" ").append(val == 0 ? "0" : "1");
2222        m.myMessage.append(" ").append(callbacknum);
2223        m.myMessage.append(" ").append(callbacksub);
2224        m.myRegex = DCCppConstants.PROG_WRITE_BIT_REGEX;
2225
2226        m._nDataChars = m.toString().length();
2227        m.setTimeout(DCCppProgrammingTimeout);
2228        return (m);
2229    }
2230
2231    public static DCCppMessage makeBitWriteDirectCVMsgV4(int cv, int bit, int val) {
2232        // Sanity Check Inputs
2233        if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
2234            return (null);
2235        }
2236        if (bit < 0 || bit > 7) {
2237            return (null);
2238        }
2239
2240        DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_WRITE_CV_BIT);
2241        m.myMessage.append(" ").append(cv);
2242        m.myMessage.append(" ").append(bit);
2243        m.myMessage.append(" ").append(val == 0 ? "0" : "1");
2244        m.myRegex = DCCppConstants.PROG_WRITE_BIT_V4_REGEX;
2245
2246        m._nDataChars = m.toString().length();
2247        m.setTimeout(DCCppProgrammingTimeout);
2248        return (m);
2249    }
2250
2251
2252    /**
2253     * Read Direct CV Byte from Programming Track.
2254     * <p>
2255     * Format: {@code <R CV CALLBACKNUM CALLBACKSUB>}
2256     * <p>
2257     * reads a Configuration Variable from the decoder of an engine on the
2258     * programming track
2259     * <p>
2260     * CV: the number of the Configuration Variable memory location in the
2261     * decoder to read from (1-1024) CALLBACKNUM: an arbitrary integer (0-32767)
2262     * that is ignored by the Base Station and is simply echoed back in the
2263     * output - useful for external programs that call this function
2264     * CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the
2265     * Base Station and is simply echoed back in the output - useful for
2266     * external programs (e.g. DCC-EX Interface) that call this function
2267     * <p>
2268     * Note: The two-argument form embeds the opcode in CALLBACKSUB to aid in
2269     * decoding the responses.
2270     * <p>
2271     * returns: {@code <r CALLBACKNUM|CALLBACKSUB|CV VALUE>} where VALUE is a
2272     * number from 0-255 as read from the requested CV, or -1 if read could not
2273     * be verified
2274     * @param cv CV index.
2275     * @return message to send read direct CV.
2276     */
2277    public static DCCppMessage makeReadDirectCVMsg(int cv) {
2278        return (makeReadDirectCVMsg(cv, 0, DCCppConstants.PROG_READ_CV));
2279    }
2280
2281    public static DCCppMessage makeReadDirectCVMsg(int cv, int callbacknum, int callbacksub) {
2282        // Sanity check inputs
2283        if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
2284            return (null);
2285        }
2286        if (callbacknum < 0 || callbacknum > DCCppConstants.MAX_CALLBACK_NUM) {
2287            return (null);
2288        }
2289        if (callbacksub < 0 || callbacksub > DCCppConstants.MAX_CALLBACK_SUB) {
2290            return (null);
2291        }
2292
2293        DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_READ_CV);
2294        m.myMessage.append(" ").append(cv);
2295        m.myMessage.append(" ").append(callbacknum);
2296        m.myMessage.append(" ").append(callbacksub);
2297        m.myRegex = DCCppConstants.PROG_READ_CV_REGEX;
2298
2299        m._nDataChars = m.toString().length();
2300        m.setTimeout(DCCppProgrammingTimeout);
2301        return (m);
2302    }
2303
2304    /**
2305     * Verify Direct CV Byte from Programming Track.
2306     * <p>
2307     * Format: {@code <V CV STARTVAL>}
2308     * <p>
2309     * Verifies a Configuration Variable from the decoder of an engine on the
2310     * programming track. Returns the current value of that CV.
2311     * Used as faster replacement for 'R'eadCV command
2312     * <p>
2313     * CV: the number of the Configuration Variable memory location in the
2314     * decoder to read from (1-1024) STARTVAL: a "guess" as to the current
2315     * value of the CV. DCC-EX will try this value first, then read and return
2316     * the current value if different
2317     * <p>
2318     * returns: {@code <v CV VALUE>} where VALUE is a
2319     * number from 0-255 as read from the requested CV, -1 if read could not
2320     * be performed
2321     * @param cv CV index.
2322     * @param startVal "guess" as to current value
2323     * @return message to send verify direct CV.
2324     */
2325    public static DCCppMessage makeVerifyCVMsg(int cv, int startVal) {
2326        // Sanity check inputs
2327        if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
2328            return (null);
2329        }
2330        DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_VERIFY_CV);
2331        m.myMessage.append(" ").append(cv);
2332        m.myMessage.append(" ").append(startVal);
2333        m.myRegex = DCCppConstants.PROG_VERIFY_REGEX;
2334
2335        m._nDataChars = m.toString().length();
2336        m.setTimeout(DCCppProgrammingTimeout);
2337        return (m);
2338    }
2339
2340    /**
2341     * Write Direct CV Byte to Main Track
2342     * <p>
2343     * Format: {@code <w CAB CV VALUE>}
2344     * <p>
2345     * Writes, without any verification, a Configuration Variable to the decoder
2346     * of an engine on the main operations track.
2347     *
2348     * @param address the short (1-127) or long (128-10293) address of the
2349     *                  engine decoder.
2350     * @param cv the number of the Configuration Variable memory location in the
2351     *                  decoder to write to (1-1024).
2352     * @param val the value to be written to the
2353     *                  Configuration Variable memory location (0-255).
2354     * @return message to Write CV in Ops Mode.
2355     */
2356    @CheckForNull
2357    public static DCCppMessage makeWriteOpsModeCVMsg(int address, int cv, int val) {
2358        // Sanity check inputs
2359        if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2360            return (null);
2361        }
2362        if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
2363            return (null);
2364        }
2365        if (val < 0 || val > DCCppConstants.MAX_DIRECT_CV_VAL) {
2366            return (null);
2367        }
2368
2369        DCCppMessage m = new DCCppMessage(DCCppConstants.OPS_WRITE_CV_BYTE);
2370        m.myMessage.append(" ").append(address);
2371        m.myMessage.append(" ").append(cv);
2372        m.myMessage.append(" ").append(val);
2373        m.myRegex = DCCppConstants.OPS_WRITE_BYTE_REGEX;
2374
2375        m._nDataChars = m.toString().length();
2376        m.setTimeout(DCCppProgrammingTimeout);
2377        return (m);
2378    }
2379
2380    /**
2381     * Write Direct CV Bit to Main Track.
2382     * <p>
2383     * Format: {@code <b CAB CV BIT VALUE>}
2384     * <p>
2385     * writes, without any verification, a single bit within a Configuration
2386     * Variable to the decoder of an engine on the main operations track
2387     * <p>
2388     * CAB: the short (1-127) or long (128-10293) address of the engine decoder
2389     * CV: the number of the Configuration Variable memory location in the
2390     * decoder to write to (1-1024) BIT: the bit number of the Configuration
2391     * Variable register to write (0-7) VALUE: the value of the bit to be
2392     * written (0-1)
2393     * <p>
2394     * returns: NONE
2395     * @param address loco cab address.
2396     * @param cv CV index, 1-1024.
2397     * @param bit bit index, 0-7.
2398     * @param val bit value, 0 or 1.
2399     * @return message to write direct CV bit to main track.
2400     */
2401    public static DCCppMessage makeBitWriteOpsModeCVMsg(int address, int cv, int bit, int val) {
2402        // Sanity Check Inputs
2403        if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2404            return (null);
2405        }
2406        if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
2407            return (null);
2408        }
2409        if (bit < 0 || bit > 7) {
2410            return (null);
2411        }
2412
2413        DCCppMessage m = new DCCppMessage(DCCppConstants.OPS_WRITE_CV_BIT);
2414        m.myMessage.append(" ").append(address);
2415        m.myMessage.append(" ").append(cv);
2416        m.myMessage.append(" ").append(bit);
2417        m.myMessage.append(" ").append(val == 0 ? "0" : "1");
2418
2419        m.myRegex = DCCppConstants.OPS_WRITE_BIT_REGEX;
2420
2421        m._nDataChars = m.toString().length();
2422        m.setTimeout(DCCppProgrammingTimeout);
2423        return (m);
2424    }
2425
2426    /**
2427     * Set Track Power ON or OFF.
2428     * <p>
2429     * Format: {@code <1> (ON) or <0> (OFF)}
2430     *
2431     * @return message to send track power on or off.
2432     * @param on true on, false off.
2433     */
2434    public static DCCppMessage makeSetTrackPowerMsg(boolean on) {
2435        return (new DCCppMessage((on ? DCCppConstants.TRACK_POWER_ON : DCCppConstants.TRACK_POWER_OFF),
2436                DCCppConstants.TRACK_POWER_REGEX));
2437    }
2438
2439    public static DCCppMessage makeTrackPowerOnMsg() {
2440        return (makeSetTrackPowerMsg(true));
2441    }
2442
2443    public static DCCppMessage makeTrackPowerOffMsg() {
2444        return (makeSetTrackPowerMsg(false));
2445    }
2446
2447    /**
2448     * Read main operations track current
2449     * <p>
2450     * Format: {@code <c>}
2451     *
2452     * reads current being drawn on main operations track
2453     * 
2454     * @return (for DCC-EX), 1 or more of  {@code <c MeterName value C/V unit min max res warn>}
2455     * where name and settings are used to define arbitrary meters on the DCC-EX side
2456     * AND {@code <a CURRENT>} where CURRENT = 0-1024, based on
2457     * exponentially-smoothed weighting scheme
2458     *
2459     */
2460    public static DCCppMessage makeReadTrackCurrentMsg() {
2461        return (new DCCppMessage(DCCppConstants.READ_TRACK_CURRENT, DCCppConstants.READ_TRACK_CURRENT_REGEX));
2462    }
2463
2464    /**
2465     * Read DCC-EX Base Station Status
2466     * <p>
2467     * Format: {@code <s>}
2468     * <p>
2469     * returns status messages containing track power status, throttle status,
2470     * turn-out status, and a version number NOTE: this is very useful as a
2471     * first command for an interface to send to this sketch in order to verify
2472     * connectivity and update any GUI to reflect actual throttle and turn-out
2473     * settings
2474     *
2475     * @return series of status messages that can be read by an interface to
2476     * determine status of DCC-EX Base Station and important settings
2477     */
2478    public static DCCppMessage makeCSStatusMsg() {
2479        return (new DCCppMessage(DCCppConstants.READ_CS_STATUS, DCCppConstants.READ_CS_STATUS_REGEX));
2480    }
2481
2482    /**
2483     * Get number of supported slots for this DCC-EX Base Station Status
2484     * <p>
2485     * Format: {@code <N>}
2486     * <p>
2487     * returns number of slots NOTE: this is not implemented in older versions
2488     * which then do not return anything at all
2489     *
2490     * @return status message with to get number of slots.
2491     */
2492    public static DCCppMessage makeCSMaxNumSlotsMsg() {
2493        return (new DCCppMessage(DCCppConstants.READ_MAXNUMSLOTS, DCCppConstants.READ_MAXNUMSLOTS_REGEX));
2494    }
2495    
2496    /**
2497     * Generate a function message using the V4 'F' syntax supported by DCC-EX
2498     * @param cab cab address to send function to
2499     * @param func function number to set
2500     * @param state new state of function 0/1
2501     * @return function functionV4message
2502     */
2503    public static DCCppMessage makeFunctionV4Message(int cab, int func, boolean state) {
2504        // Sanity check inputs
2505        if (cab < 0 || cab > DCCppConstants.MAX_LOCO_ADDRESS) {
2506            return (null);
2507        }
2508        if (func < 0 || func > DCCppConstants.MAX_FUNCTION_NUMBER) {
2509            return (null);
2510        }
2511        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_V4_CMD);
2512        m.myMessage.append(" ").append(cab);
2513        m.myMessage.append(" ").append(func);
2514        m.myMessage.append(" ").append(state?1:0); //1 or 0 for true or false
2515        m.myRegex = DCCppConstants.FUNCTION_V4_CMD_REGEX;
2516        m._nDataChars = m.toString().length();
2517        return (m);
2518    }
2519
2520    /**
2521     * Generate a "Forget Cab" message '-'
2522     *
2523     * @param cab cab address to send function to (or 0 for all)
2524     * @return forget message to be sent
2525     */
2526    public static DCCppMessage makeForgetCabMessage(int cab) {
2527        // Sanity check inputs
2528        if (cab < 0 || cab > DCCppConstants.MAX_LOCO_ADDRESS) {
2529            return (null);
2530        }
2531        DCCppMessage m = new DCCppMessage(DCCppConstants.FORGET_CAB_CMD);
2532        if (cab > 0) {
2533            m.myMessage.append(" ").append(cab);
2534        }
2535        m.myRegex = DCCppConstants.FORGET_CAB_CMD_REGEX;
2536        m._nDataChars = m.toString().length();
2537        return (m);
2538    }
2539
2540    /**
2541     * Generate an emergency stop for the specified address.
2542     * <p>
2543     * Note: This just sends a THROTTLE command with speed = -1
2544     *
2545     * @param register Register Number for the loco assigned address.
2546     * @param address is the locomotive address.
2547     * @return message to send e stop to the specified address.
2548     */
2549    public static DCCppMessage makeAddressedEmergencyStop(int register, int address) {
2550        // Sanity check inputs
2551        if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2552            return (null);
2553        }
2554
2555        DCCppMessage m = new DCCppMessage(DCCppConstants.THROTTLE_CMD);
2556        m.myMessage.append(" ").append(register);
2557        m.myMessage.append(" ").append(address);
2558        m.myMessage.append(" -1 1");
2559        m.myRegex = DCCppConstants.THROTTLE_CMD_REGEX;
2560
2561        m._nDataChars = m.toString().length();
2562        return (m);
2563    }
2564
2565    /**
2566     * Generate an emergency stop for the specified address.
2567     * <p>
2568     * Note: This just sends a THROTTLE command with speed = -1
2569     *
2570     * @param address is the locomotive address.
2571     * @return message to send e stop to the specified address.
2572     */
2573    public static DCCppMessage makeAddressedEmergencyStop(int address) {
2574        // Sanity check inputs
2575        if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2576            return (null);
2577        }
2578
2579        DCCppMessage m = new DCCppMessage(DCCppConstants.THROTTLE_CMD);
2580        m.myMessage.append(" ").append(address);
2581        m.myMessage.append(" -1 1");
2582        m.myRegex = DCCppConstants.THROTTLE_V3_CMD_REGEX;
2583
2584        m._nDataChars = m.toString().length();
2585        return (m);
2586    }
2587
2588    /**
2589     * Generate an emergency stop for all locos in reminder table.
2590     * @return message to send e stop for all locos
2591     */
2592    public static DCCppMessage makeEmergencyStopAllMsg() {
2593        DCCppMessage m = new DCCppMessage(DCCppConstants.ESTOP_ALL_CMD);
2594        m.myRegex = DCCppConstants.ESTOP_ALL_REGEX;
2595
2596        m._nDataChars = m.toString().length();
2597        return (m);
2598    }
2599
2600    /**
2601     * Generate a Speed and Direction Request message
2602     *
2603     * @param register  is the DCC-EX base station register assigned.
2604     * @param address   is the locomotive address
2605     * @param speed     a normalized speed value (a floating point number
2606     *                  between 0 and 1). A negative value indicates emergency
2607     *                  stop.
2608     * @param isForward true for forward, false for reverse.
2609     *
2610     * Format: {@code <t REGISTER CAB SPEED DIRECTION>}
2611     *
2612     * sets the throttle for a given register/cab combination
2613     *
2614     * REGISTER: an internal register number, from 1 through MAX_MAIN_REGISTERS
2615     *   (inclusive), to store the DCC packet used to control this throttle
2616     *   setting 
2617     * CAB: the short (1-127) or long (128-10293) address of the engine decoder 
2618     * SPEED: throttle speed from 0-126, or -1 for emergency stop (resets SPEED to 0) 
2619     * DIRECTION: 1=forward, 0=reverse. Setting direction
2620     *   when speed=0 or speed=-1 only effects directionality of cab lighting for
2621     *   a stopped train
2622     *
2623     * @return {@code <T REGISTER CAB SPEED DIRECTION>}
2624     *
2625     */
2626    public static DCCppMessage makeSpeedAndDirectionMsg(int register, int address, float speed, boolean isForward) {
2627        // Sanity check inputs
2628        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2629            return (null);
2630        }
2631
2632        DCCppMessage m = new DCCppMessage(DCCppConstants.THROTTLE_CMD);
2633        m.myMessage.append(" ").append(register);
2634        m.myMessage.append(" ").append(address);
2635        if (speed < 0.0) {
2636            m.myMessage.append(" -1");
2637        } else {
2638            int speedVal = java.lang.Math.round(speed * 126);
2639            if (speed > 0 && speedVal == 0) {
2640                speedVal = 1;           // ensure non-zero input results in non-zero output
2641            }
2642            speedVal = Math.min(speedVal, DCCppConstants.MAX_SPEED);
2643            m.myMessage.append(" ").append(speedVal);
2644        }
2645        m.myMessage.append(" ").append(isForward ? "1" : "0");
2646
2647        m.myRegex = DCCppConstants.THROTTLE_CMD_REGEX;
2648
2649        m._nDataChars = m.toString().length();
2650        return (m);
2651    }
2652
2653    /**
2654     * Generate a Speed and Direction Request message
2655     *
2656     * @param address   is the locomotive address
2657     * @param speed     a normalized speed value (a floating point number
2658     *                  between 0 and 1). A negative value indicates emergency
2659     *                  stop.
2660     * @param isForward true for forward, false for reverse.
2661     *
2662     * Format: {@code <t CAB SPEED DIRECTION>}
2663     *
2664     * sets the throttle for a given register/cab combination
2665     *
2666     * CAB: the short (1-127) or long (128-10293) address of the engine decoder 
2667     * SPEED: throttle speed from 0-126, or -1 for emergency stop (resets SPEED to 0) 
2668     * DIRECTION: 1=forward, 0=reverse. Setting direction
2669     *   when speed=0 or speed=-1 only effects directionality of cab lighting for
2670     *   a stopped train
2671     *
2672     * @return {@code <T CAB SPEED DIRECTION>}
2673     *
2674     */
2675    public static DCCppMessage makeSpeedAndDirectionMsg(int address, float speed, boolean isForward) {
2676        // Sanity check inputs
2677        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2678            return (null);
2679        }
2680
2681        DCCppMessage m = new DCCppMessage(DCCppConstants.THROTTLE_CMD);
2682        m.myMessage.append(" ").append(address);
2683        if (speed < 0.0) {
2684            m.myMessage.append(" -1");
2685        } else {
2686            int speedVal = java.lang.Math.round(speed * 126);
2687            if (speed > 0 && speedVal == 0) {
2688                speedVal = 1;           // ensure non-zero input results in non-zero output
2689            }
2690            speedVal = Math.min(speedVal, DCCppConstants.MAX_SPEED);
2691            m.myMessage.append(" ").append(speedVal);
2692        }
2693        m.myMessage.append(" ").append(isForward ? "1" : "0");
2694
2695        m.myRegex = DCCppConstants.THROTTLE_V3_CMD_REGEX;
2696
2697        m._nDataChars = m.toString().length();
2698        return (m);
2699    }
2700
2701    /*
2702     * Function Group Messages (common serial format)
2703     * <p>
2704     * Format: {@code <f CAB BYTE1 [BYTE2]>}
2705     * <p>
2706     * turns on and off engine decoder functions F0-F28 (F0 is sometimes called
2707     * FL) NOTE: setting requests transmitted directly to mobile engine decoder
2708     * --- current state of engine functions is not stored by this program
2709     * <p>
2710     * CAB: the short (1-127) or long (128-10293) address of the engine decoder
2711     * <p>
2712     * To set functions F0-F4 on (=1) or off (=0):
2713     * <p>
2714     * BYTE1: 128 + F1*1 + F2*2 + F3*4 + F4*8 + F0*16 BYTE2: omitted
2715     * <p>
2716     * To set functions F5-F8 on (=1) or off (=0):
2717     * <p>
2718     * BYTE1: 176 + F5*1 + F6*2 + F7*4 + F8*8 BYTE2: omitted
2719     * <p>
2720     * To set functions F9-F12 on (=1) or off (=0):
2721     * <p>
2722     * BYTE1: 160 + F9*1 +F10*2 + F11*4 + F12*8 BYTE2: omitted
2723     * <p>
2724     * To set functions F13-F20 on (=1) or off (=0):
2725     * <p>
2726     * BYTE1: 222 BYTE2: F13*1 + F14*2 + F15*4 + F16*8 + F17*16 + F18*32 +
2727     * F19*64 + F20*128
2728     * <p>
2729     * To set functions F21-F28 on (=1) of off (=0):
2730     * <p>
2731     * BYTE1: 223 BYTE2: F21*1 + F22*2 + F23*4 + F24*8 + F25*16 + F26*32 +
2732     * F27*64 + F28*128
2733     * <p>
2734     * returns: NONE
2735     * <p>
2736     */
2737    /**
2738     * Generate a Function Group One Operation Request message.
2739     *
2740     * @param address is the locomotive address
2741     * @param f0      is true if f0 is on, false if f0 is off
2742     * @param f1      is true if f1 is on, false if f1 is off
2743     * @param f2      is true if f2 is on, false if f2 is off
2744     * @param f3      is true if f3 is on, false if f3 is off
2745     * @param f4      is true if f4 is on, false if f4 is off
2746     * @return message to set function group 1.
2747     */
2748    public static DCCppMessage makeFunctionGroup1OpsMsg(int address,
2749            boolean f0,
2750            boolean f1,
2751            boolean f2,
2752            boolean f3,
2753            boolean f4) {
2754        // Sanity check inputs
2755        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2756            return (null);
2757        }
2758
2759        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
2760        m.myMessage.append(" ").append(address);
2761
2762        int byte1 = 128 + (f0 ? 16 : 0);
2763        byte1 += (f1 ? 1 : 0);
2764        byte1 += (f2 ? 2 : 0);
2765        byte1 += (f3 ? 4 : 0);
2766        byte1 += (f4 ? 8 : 0);
2767        m.myMessage.append(" ").append(byte1);
2768        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
2769
2770        m._nDataChars = m.toString().length();
2771        return (m);
2772    }
2773
2774    /**
2775     * Generate a Function Group One Set Momentary Functions message.
2776     *
2777     * @param address is the locomotive address
2778     * @param f0      is true if f0 is momentary
2779     * @param f1      is true if f1 is momentary
2780     * @param f2      is true if f2 is momentary
2781     * @param f3      is true if f3 is momentary
2782     * @param f4      is true if f4 is momentary
2783     * @return message to set momentary function group 1.
2784     */
2785    public static DCCppMessage makeFunctionGroup1SetMomMsg(int address,
2786            boolean f0,
2787            boolean f1,
2788            boolean f2,
2789            boolean f3,
2790            boolean f4) {
2791
2792        // Sanity check inputs
2793        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2794            return (null);
2795        }
2796
2797        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
2798        m.myMessage.append(" ").append(address);
2799
2800        int byte1 = 128 + (f0 ? 16 : 0);
2801        byte1 += (f1 ? 1 : 0);
2802        byte1 += (f2 ? 2 : 0);
2803        byte1 += (f3 ? 4 : 0);
2804        byte1 += (f4 ? 8 : 0);
2805
2806        m.myMessage.append(" ").append(byte1);
2807        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
2808
2809        m._nDataChars = m.toString().length();
2810        return (m);
2811    }
2812
2813    /**
2814     * Generate a Function Group Two Operation Request message.
2815     *
2816     * @param address is the locomotive address
2817     * @param f5      is true if f5 is on, false if f5 is off
2818     * @param f6      is true if f6 is on, false if f6 is off
2819     * @param f7      is true if f7 is on, false if f7 is off
2820     * @param f8      is true if f8 is on, false if f8 is off
2821     * @return message to set function group 2.
2822     */
2823    public static DCCppMessage makeFunctionGroup2OpsMsg(int address,
2824            boolean f5,
2825            boolean f6,
2826            boolean f7,
2827            boolean f8) {
2828
2829        // Sanity check inputs
2830        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2831            return (null);
2832        }
2833
2834        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
2835        m.myMessage.append(" ").append(address);
2836
2837        int byte1 = 176;
2838        byte1 += (f5 ? 1 : 0);
2839        byte1 += (f6 ? 2 : 0);
2840        byte1 += (f7 ? 4 : 0);
2841        byte1 += (f8 ? 8 : 0);
2842
2843        m.myMessage.append(" ").append(byte1);
2844        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
2845
2846        m._nDataChars = m.toString().length();
2847        return (m);
2848    }
2849
2850    /**
2851     * Generate a Function Group Two Set Momentary Functions message.
2852     *
2853     * @param address is the locomotive address
2854     * @param f5      is true if f5 is momentary
2855     * @param f6      is true if f6 is momentary
2856     * @param f7      is true if f7 is momentary
2857     * @param f8      is true if f8 is momentary
2858     * @return message to set momentary function group 2.
2859     */
2860    public static DCCppMessage makeFunctionGroup2SetMomMsg(int address,
2861            boolean f5,
2862            boolean f6,
2863            boolean f7,
2864            boolean f8) {
2865
2866        // Sanity check inputs
2867        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2868            return (null);
2869        }
2870
2871        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
2872        m.myMessage.append(" ").append(address);
2873
2874        int byte1 = 176;
2875        byte1 += (f5 ? 1 : 0);
2876        byte1 += (f6 ? 2 : 0);
2877        byte1 += (f7 ? 4 : 0);
2878        byte1 += (f8 ? 8 : 0);
2879        m.myMessage.append(" ").append(byte1);
2880        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
2881
2882        m._nDataChars = m.toString().length();
2883        return (m);
2884    }
2885
2886    /**
2887     * Generate a Function Group Three Operation Request message.
2888     *
2889     * @param address is the locomotive address
2890     * @param f9      is true if f9 is on, false if f9 is off
2891     * @param f10     is true if f10 is on, false if f10 is off
2892     * @param f11     is true if f11 is on, false if f11 is off
2893     * @param f12     is true if f12 is on, false if f12 is off
2894     * @return message to set function group 3.
2895     */
2896    public static DCCppMessage makeFunctionGroup3OpsMsg(int address,
2897            boolean f9,
2898            boolean f10,
2899            boolean f11,
2900            boolean f12) {
2901
2902        // Sanity check inputs
2903        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2904            return (null);
2905        }
2906
2907        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
2908        m.myMessage.append(" ").append(address);
2909
2910        int byte1 = 160;
2911        byte1 += (f9 ? 1 : 0);
2912        byte1 += (f10 ? 2 : 0);
2913        byte1 += (f11 ? 4 : 0);
2914        byte1 += (f12 ? 8 : 0);
2915        m.myMessage.append(" ").append(byte1);
2916        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
2917
2918        m._nDataChars = m.toString().length();
2919        return (m);
2920    }
2921
2922    /**
2923     * Generate a Function Group Three Set Momentary Functions message.
2924     *
2925     * @param address is the locomotive address
2926     * @param f9      is true if f9 is momentary
2927     * @param f10     is true if f10 is momentary
2928     * @param f11     is true if f11 is momentary
2929     * @param f12     is true if f12 is momentary
2930     * @return message to set momentary function group 3.
2931     */
2932    public static DCCppMessage makeFunctionGroup3SetMomMsg(int address,
2933            boolean f9,
2934            boolean f10,
2935            boolean f11,
2936            boolean f12) {
2937
2938        // Sanity check inputs
2939        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2940            return (null);
2941        }
2942
2943        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
2944        m.myMessage.append(" ").append(address);
2945
2946        int byte1 = 160;
2947        byte1 += (f9 ? 1 : 0);
2948        byte1 += (f10 ? 2 : 0);
2949        byte1 += (f11 ? 4 : 0);
2950        byte1 += (f12 ? 8 : 0);
2951        m.myMessage.append(" ").append(byte1);
2952        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
2953
2954        m._nDataChars = m.toString().length();
2955        return (m);
2956    }
2957
2958    /**
2959     * Generate a Function Group Four Operation Request message.
2960     *
2961     * @param address is the locomotive address
2962     * @param f13     is true if f13 is on, false if f13 is off
2963     * @param f14     is true if f14 is on, false if f14 is off
2964     * @param f15     is true if f15 is on, false if f15 is off
2965     * @param f16     is true if f18 is on, false if f16 is off
2966     * @param f17     is true if f17 is on, false if f17 is off
2967     * @param f18     is true if f18 is on, false if f18 is off
2968     * @param f19     is true if f19 is on, false if f19 is off
2969     * @param f20     is true if f20 is on, false if f20 is off
2970     * @return message to set function group 4.
2971     */
2972    public static DCCppMessage makeFunctionGroup4OpsMsg(int address,
2973            boolean f13,
2974            boolean f14,
2975            boolean f15,
2976            boolean f16,
2977            boolean f17,
2978            boolean f18,
2979            boolean f19,
2980            boolean f20) {
2981
2982        // Sanity check inputs
2983        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
2984            return (null);
2985        }
2986
2987        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
2988        m.myMessage.append(" ").append(address);
2989
2990        int byte2 = 0;
2991        byte2 += (f13 ? 1 : 0);
2992        byte2 += (f14 ? 2 : 0);
2993        byte2 += (f15 ? 4 : 0);
2994        byte2 += (f16 ? 8 : 0);
2995        byte2 += (f17 ? 16 : 0);
2996        byte2 += (f18 ? 32 : 0);
2997        byte2 += (f19 ? 64 : 0);
2998        byte2 += (f20 ? 128 : 0);
2999        m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP4_BYTE1);
3000        m.myMessage.append(" ").append(byte2);
3001        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
3002
3003        m._nDataChars = m.toString().length();
3004        return (m);
3005    }
3006
3007    /**
3008     * Generate a Function Group Four Set Momentary Function message.
3009     *
3010     * @param address is the locomotive address
3011     * @param f13     is true if f13 is Momentary
3012     * @param f14     is true if f14 is Momentary
3013     * @param f15     is true if f15 is Momentary
3014     * @param f16     is true if f18 is Momentary
3015     * @param f17     is true if f17 is Momentary
3016     * @param f18     is true if f18 is Momentary
3017     * @param f19     is true if f19 is Momentary
3018     * @param f20     is true if f20 is Momentary
3019     * @return message to set momentary function group 4.
3020     */
3021    public static DCCppMessage makeFunctionGroup4SetMomMsg(int address,
3022            boolean f13,
3023            boolean f14,
3024            boolean f15,
3025            boolean f16,
3026            boolean f17,
3027            boolean f18,
3028            boolean f19,
3029            boolean f20) {
3030
3031        // Sanity check inputs
3032        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
3033            return (null);
3034        }
3035
3036        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
3037        m.myMessage.append(" ").append(address);
3038
3039        int byte2 = 0;
3040        byte2 += (f13 ? 1 : 0);
3041        byte2 += (f14 ? 2 : 0);
3042        byte2 += (f15 ? 4 : 0);
3043        byte2 += (f16 ? 8 : 0);
3044        byte2 += (f17 ? 16 : 0);
3045        byte2 += (f18 ? 32 : 0);
3046        byte2 += (f19 ? 64 : 0);
3047        byte2 += (f20 ? 128 : 0);
3048
3049        m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP4_BYTE1);
3050        m.myMessage.append(" ").append(byte2);
3051        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
3052
3053        m._nDataChars = m.toString().length();
3054        return (m);
3055    }
3056
3057    /**
3058     * Generate a Function Group Five Operation Request message.
3059     *
3060     * @param address is the locomotive address
3061     * @param f21     is true if f21 is on, false if f21 is off
3062     * @param f22     is true if f22 is on, false if f22 is off
3063     * @param f23     is true if f23 is on, false if f23 is off
3064     * @param f24     is true if f24 is on, false if f24 is off
3065     * @param f25     is true if f25 is on, false if f25 is off
3066     * @param f26     is true if f26 is on, false if f26 is off
3067     * @param f27     is true if f27 is on, false if f27 is off
3068     * @param f28     is true if f28 is on, false if f28 is off
3069     * @return message to set function group 5.
3070     */
3071    public static DCCppMessage makeFunctionGroup5OpsMsg(int address,
3072            boolean f21,
3073            boolean f22,
3074            boolean f23,
3075            boolean f24,
3076            boolean f25,
3077            boolean f26,
3078            boolean f27,
3079            boolean f28) {
3080        // Sanity check inputs
3081        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
3082            return (null);
3083        }
3084
3085        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
3086        m.myMessage.append(" ").append(address);
3087
3088        int byte2 = 0;
3089        byte2 += (f21 ? 1 : 0);
3090        byte2 += (f22 ? 2 : 0);
3091        byte2 += (f23 ? 4 : 0);
3092        byte2 += (f24 ? 8 : 0);
3093        byte2 += (f25 ? 16 : 0);
3094        byte2 += (f26 ? 32 : 0);
3095        byte2 += (f27 ? 64 : 0);
3096        byte2 += (f28 ? 128 : 0);
3097        log.debug("DCCppMessage: Byte2 = {}", byte2);
3098
3099        m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP5_BYTE1);
3100        m.myMessage.append(" ").append(byte2);
3101        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
3102
3103        m._nDataChars = m.toString().length();
3104        return (m);
3105    }
3106
3107    /**
3108     * Generate a Function Group Five Set Momentary Function message.
3109     *
3110     * @param address is the locomotive address
3111     * @param f21     is true if f21 is momentary
3112     * @param f22     is true if f22 is momentary
3113     * @param f23     is true if f23 is momentary
3114     * @param f24     is true if f24 is momentary
3115     * @param f25     is true if f25 is momentary
3116     * @param f26     is true if f26 is momentary
3117     * @param f27     is true if f27 is momentary
3118     * @param f28     is true if f28 is momentary
3119     * @return message to set momentary function group 5.
3120     */
3121    public static DCCppMessage makeFunctionGroup5SetMomMsg(int address,
3122            boolean f21,
3123            boolean f22,
3124            boolean f23,
3125            boolean f24,
3126            boolean f25,
3127            boolean f26,
3128            boolean f27,
3129            boolean f28) {
3130
3131        // Sanity check inputs
3132        if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
3133            return (null);
3134        }
3135
3136        DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
3137        m.myMessage.append(" ").append(address);
3138
3139        int byte2 = 0;
3140        byte2 += (f21 ? 1 : 0);
3141        byte2 += (f22 ? 2 : 0);
3142        byte2 += (f23 ? 4 : 0);
3143        byte2 += (f24 ? 8 : 0);
3144        byte2 += (f25 ? 16 : 0);
3145        byte2 += (f26 ? 32 : 0);
3146        byte2 += (f27 ? 64 : 0);
3147        byte2 += (f28 ? 128 : 0);
3148
3149        m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP5_BYTE1);
3150        m.myMessage.append(" ").append(byte2);
3151        m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
3152
3153        m._nDataChars = m.toString().length();
3154        return (m);
3155    }
3156
3157    /*
3158     * Build an Emergency Off Message
3159     */
3160
3161    /*
3162     * Test Code Functions... not for normal use
3163     */
3164
3165    /**
3166     * Write DCC Packet to a specified Register on the Main.
3167     * <br>
3168     * DCC-EX BaseStation code appends its own error-correction byte so we must
3169     * not provide one.
3170     *
3171     * @param register the DCC-EX BaseStation main register number to use
3172     * @param numBytes the number of bytes in the packet
3173     * @param bytes    byte array representing the packet. The first
3174     *                 {@code num_bytes} are used.
3175     * @return the formatted message to send
3176     */
3177    public static DCCppMessage makeWriteDCCPacketMainMsg(int register, int numBytes, byte[] bytes) {
3178        // Sanity Check Inputs
3179        if (register < 0 || register > DCCppConstants.MAX_MAIN_REGISTERS || numBytes < 2 || numBytes > 5) {
3180            return (null);
3181        }
3182
3183        DCCppMessage m = new DCCppMessage(DCCppConstants.WRITE_DCC_PACKET_MAIN);
3184        m.myMessage.append(" ").append(register);
3185        for (int k = 0; k < numBytes; k++) {
3186            m.myMessage.append(" ").append(jmri.util.StringUtil.twoHexFromInt(bytes[k]));
3187        }
3188        m.myRegex = DCCppConstants.WRITE_DCC_PACKET_MAIN_REGEX;
3189        return (m);
3190
3191    }
3192
3193    /**
3194     * Write DCC Packet to a specified Register on the Programming Track.
3195     * <br><br>
3196     * DCC-EX BaseStation code appends its own error-correction byte so we must
3197     * not provide one.
3198     *
3199     * @param register the DCC-EX BaseStation main register number to use
3200     * @param numBytes the number of bytes in the packet
3201     * @param bytes    byte array representing the packet. The first
3202     *                 {@code num_bytes} are used.
3203     * @return the formatted message to send
3204     */
3205    public static DCCppMessage makeWriteDCCPacketProgMsg(int register, int numBytes, byte[] bytes) {
3206        // Sanity Check Inputs
3207        if (register < 0 || register > DCCppConstants.MAX_MAIN_REGISTERS || numBytes < 2 || numBytes > 5) {
3208            return (null);
3209        }
3210
3211        DCCppMessage m = new DCCppMessage(DCCppConstants.WRITE_DCC_PACKET_PROG);
3212        m.myMessage.append(" ").append(register);
3213        for (int k = 0; k < numBytes; k++) {
3214            m.myMessage.append(" ").append(jmri.util.StringUtil.twoHexFromInt(bytes[k]));
3215        }
3216        m.myRegex = DCCppConstants.WRITE_DCC_PACKET_PROG_REGEX;
3217        return (m);
3218
3219    }
3220
3221//    public static DCCppMessage makeCheckFreeMemMsg() {
3222//        return (new DCCppMessage(DCCppConstants.GET_FREE_MEMORY, DCCppConstants.GET_FREE_MEMORY_REGEX));
3223//    }
3224//
3225    public static DCCppMessage makeListRegisterContentsMsg() {
3226        return (new DCCppMessage(DCCppConstants.LIST_REGISTER_CONTENTS,
3227                DCCppConstants.LIST_REGISTER_CONTENTS_REGEX));
3228    }
3229    /**
3230     * Request LCD Messages used for Virtual LCD Display
3231     * <p>
3232     * Format: {@code <@>}
3233     * <p>
3234     * tells EX_CommandStation to send any LCD message updates to this instance of JMRI
3235     * @return the formatted message to send
3236     */
3237    public static DCCppMessage makeLCDRequestMsg() {
3238        return (new DCCppMessage(DCCppConstants.LCD_TEXT_CMD, DCCppConstants.LCD_TEXT_CMD_REGEX));
3239    }
3240
3241
3242    /**
3243     * This implementation of equals is targeted to the background function
3244     * refreshing in SerialDCCppPacketizer. To keep only one function group in
3245     * the refresh queue the logic is as follows. Two messages are equal if they
3246     * are:
3247     * <ul>
3248     * <li>actually identical, or</li>
3249     * <li>a function call to the same address and same function group</li>
3250     * </ul>
3251     */
3252    @Override
3253    public boolean equals(final Object obj) {
3254        if (obj == null) {
3255            return false;
3256        }
3257
3258        if (!(obj instanceof DCCppMessage)) {
3259            return false;
3260        }
3261
3262        final DCCppMessage other = (DCCppMessage) obj;
3263
3264        final String myCmd = this.toString();
3265        final String otherCmd = other.toString();
3266
3267        if (myCmd.equals(otherCmd)) {
3268            return true;
3269        }
3270
3271        if (!(myCmd.charAt(0) == DCCppConstants.FUNCTION_CMD) || !(otherCmd.charAt(0) == DCCppConstants.FUNCTION_CMD)) {
3272            return false;
3273        }
3274
3275        final int mySpace1 = myCmd.indexOf(' ', 2);
3276        final int otherSpace1 = otherCmd.indexOf(' ', 2);
3277
3278        if (mySpace1 != otherSpace1) {
3279            return false;
3280        }
3281
3282        if (!myCmd.subSequence(2, mySpace1).equals(otherCmd.subSequence(2, otherSpace1))) {
3283            return false;
3284        }
3285
3286        int mySpace2 = myCmd.indexOf(' ', mySpace1 + 1);
3287        if (mySpace2 < 0) {
3288            mySpace2 = myCmd.length();
3289        }
3290
3291        int otherSpace2 = otherCmd.indexOf(' ', otherSpace1 + 1);
3292        if (otherSpace2 < 0) {
3293            otherSpace2 = otherCmd.length();
3294        }
3295
3296        final int myBaseFunction = Integer.parseInt(myCmd.substring(mySpace1 + 1, mySpace2));
3297        final int otherBaseFunction = Integer.parseInt(otherCmd.substring(otherSpace1 + 1, otherSpace2));
3298
3299        if (myBaseFunction == otherBaseFunction) {
3300            return true;
3301        }
3302
3303        return getFuncBaseByte1(myBaseFunction) == getFuncBaseByte1(otherBaseFunction);
3304    }
3305
3306    @Override
3307    public int hashCode() {
3308        return toString().hashCode();
3309    }
3310
3311    /**
3312     * Get the function group from the first byte of the function setting call.
3313     *
3314     * @param byte1 first byte (mixed in with function bits for groups 1 to 3,
3315     *              or standalone value for groups 4 and 5)
3316     * @return the base group
3317     */
3318    private static int getFuncBaseByte1(final int byte1) {
3319        if (byte1 == DCCppConstants.FUNCTION_GROUP4_BYTE1 || byte1 == DCCppConstants.FUNCTION_GROUP5_BYTE1) {
3320            return byte1;
3321        }
3322
3323        if (byte1 < 160) {
3324            return 128;
3325        }
3326
3327        if (byte1 < 176) {
3328            return 160;
3329        }
3330
3331        return 176;
3332    }
3333
3334    /**
3335     * When is this message supposed to be resent?
3336     */
3337    private long expireTime;
3338
3339    /**
3340     * Before adding the message to the delay queue call this method to set when
3341     * the message should be repeated. The only time guarantee is that it will
3342     * be repeated after <u>at least</u> this much time, but it can be
3343     * significantly longer until it is repeated, function of the message queue
3344     * length.
3345     *
3346     * @param millis milliseconds in the future
3347     */
3348    public void delayFor(final long millis) {
3349        expireTime = System.currentTimeMillis() + millis;
3350    }
3351
3352    /**
3353     * Comparing two queued message for refreshing the function calls, based on
3354     * their expected execution time.
3355     */
3356    @Override
3357    public int compareTo(@Nonnull final Delayed o) {
3358        final long diff = this.expireTime - ((DCCppMessage) o).expireTime;
3359
3360        if (diff < 0) {
3361            return -1;
3362        }
3363
3364        if (diff > 0) {
3365            return 1;
3366        }
3367
3368        return 0;
3369    }
3370
3371    /**
3372     * From the {@link Delayed} interface, how long this message still has until
3373     * it should be executed.
3374     */
3375    @Override
3376    public long getDelay(final TimeUnit unit) {
3377        return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
3378    }
3379
3380    // initialize logging
3381    private static final Logger log = LoggerFactory.getLogger(DCCppMessage.class);
3382
3383}