001package jmri.jmrix.dccpp;
002
003import java.util.LinkedHashMap;
004import java.util.ArrayList;
005import java.util.regex.Matcher;
006import java.util.regex.Pattern;
007import javax.annotation.Nonnull;
008
009import org.apache.commons.lang3.StringUtils;
010import java.util.regex.PatternSyntaxException;
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014import jmri.configurexml.AbstractXmlAdapter;
015
016/**
017 * Represents a single response from the DCC-EX system.
018 *
019 * @author Paul Bender Copyright (C) 2004
020 * @author Mark Underwood Copyright (C) 2015
021 * @author Harald Barth Copyright (C) 2019
022 * 
023 * Based on XNetReply
024 */
025
026/*
027 * A few notes on implementation
028 *
029 * DCCppReply objects are (usually) created by parsing a String that is the
030 * result of an incoming reply message from the Base Station. The information is
031 * stored as a String, along with a Regex string that allows the individual data
032 * elements to be extracted when needed.
033 *
034 * Listeners and other higher level code should first check to make sure the
035 * DCCppReply is of the correct type by calling the relevant isMessageType()
036 * method. Then, call the various getThisDataElement() method to retrieve the
037 * data of interest.
038 *
039 * For example, to get the Speed from a Throttle Reply, first check that it /is/
040 * a ThrottleReply by calling message.isThrottleReply(), and then get the speed
041 * by calling message.getSpeedInt() or getSpeedString().
042 *
043 * The reason for all of this misdirection is to make sure that the upper layer
044 * JMRI code is isolated/insulated from any changes in the actual Base Station
045 * message format. For example, there is no need for the listener code to know
046 * that the speed is the second number after the "T" in the reply (nor that a
047 * Throttle reply starts with a "T").
048 */
049
050public class DCCppReply extends jmri.jmrix.AbstractMRReply {
051
052    protected String myRegex;
053    protected StringBuilder myReply;
054
055    // Create a new reply.
056    public DCCppReply() {
057        super();
058        setBinary(false);
059        myRegex = "";
060        myReply = new StringBuilder();
061    }
062
063    // Create a new reply from an existing reply
064    public DCCppReply(DCCppReply reply) {
065        super(reply);
066        setBinary(false);
067        myRegex = reply.myRegex;
068        myReply = reply.myReply;
069    }
070
071    // Create a new reply from a string
072    public DCCppReply(String reply) {
073        super();
074        setBinary(false);
075        myRegex = "";
076        myReply = new StringBuilder(reply);
077        _nDataChars = reply.length();
078    }
079
080    /**
081     * Override default toString.
082     * @return myReply StringBuilder toString.
083     */
084    @Override
085    public String toString() {
086        log.trace("DCCppReply.toString(): msg '{}'", myReply);
087        return myReply.toString();
088    }
089
090    /**
091     * Generate text translations of replies for use in the DCCpp monitor.
092     *
093     * @return representation of the DCCppReply as a string.
094     **/
095    @Override
096    public String toMonitorString() {
097        // Beautify and display
098        String text;
099
100        switch (getOpCodeChar()) {
101            case DCCppConstants.THROTTLE_REPLY:
102                text = "Throttle Reply: ";
103                text += "Register: " + getRegisterString() + ", ";
104                text += "Speed: " + getSpeedString() + ", ";
105                text += "Direction: " + getDirectionString();
106                break;
107            case DCCppConstants.TURNOUT_REPLY:
108                if (isTurnoutCmdReply()) {
109                    text = "Turnout Reply: ";
110                    text += "ID: " + getTOIDString() + ", ";
111                    text += "Dir: " + getTOStateString();
112                } else if (isTurnoutDefReply()) {
113                    text = "Turnout Def Reply: ";
114                    text += "ID:" + getTOIDString() + ", ";
115                    text += "Address:" + getTOAddressString() + ", ";
116                    text += "Index:" + getTOAddressIndexString() + ", ";
117                    // if we are able to parse the address and index we can convert it
118                    // to a standard DCC address for display.
119                    if (getTOAddressInt() != -1 && getTOAddressIndexInt() != -1) {
120                        int boardAddr = getTOAddressInt();
121                        int boardIndex = getTOAddressIndexInt();
122                        int dccAddress = (((boardAddr - 1) * 4) + boardIndex) + 1;
123                        text += "DCC Address: " + dccAddress + ", ";
124                    }
125                    text += "Dir: " + getTOStateString();
126                } else if (isTurnoutDefDCCReply()) {
127                    text = "Turnout Def DCC Reply: ";
128                    text += "ID:" + getTOIDString() + ", ";
129                    text += "Address:" + getTOAddressString() + ", ";
130                    text += "Index:" + getTOAddressIndexString() + ", ";
131                    // if we are able to parse the address and index we can convert it
132                    // to a standard DCC address for display.
133                    if (getTOAddressInt() != -1 && getTOAddressIndexInt() != -1) {
134                        int boardAddr = getTOAddressInt();
135                        int boardIndex = getTOAddressIndexInt();
136                        int dccAddress = (((boardAddr - 1) * 4) + boardIndex) + 1;
137                        text += "DCC Address:" + dccAddress + ", ";
138                    }
139                    text += "Dir:" + getTOStateString();
140                } else if (isTurnoutDefServoReply()) {
141                    text = "Turnout Def SERVO Reply: ";
142                    text += "ID:" + getTOIDString() + ", ";
143                    text += "Pin:" + getTOPinInt() + ", ";
144                    text += "ThrownPos:" + getTOThrownPositionInt() + ", ";
145                    text += "ClosedPos:" + getTOClosedPositionInt() + ", ";
146                    text += "Profile:" + getTOProfileInt() + ", ";
147                    text += "Dir:" + getTOStateString();
148                } else if (isTurnoutDefVpinReply()) {
149                    text = "Turnout Def VPIN Reply: ";
150                    text += "ID:" + getTOIDString() + ", ";
151                    text += "Pin:" + getTOPinInt() + ", ";
152                    text += "Dir:" + getTOStateString();
153                } else if (isTurnoutDefLCNReply()) {
154                    text = "Turnout Def LCN Reply: ";
155                    text += "ID:" + getTOIDString() + ", ";
156                    text += "Dir:" + getTOStateString();
157                } else {
158                    text = "Unknown Turnout Reply Format: ";
159                    text += toString();
160                }
161                break;
162            case DCCppConstants.SENSOR_REPLY_H:
163                text = "Sensor Reply (Inactive): ";
164                text += "Number: " + getSensorNumString() + ", ";
165                text += "State: INACTIVE";
166                break;
167            case DCCppConstants.SENSOR_REPLY_L:
168                // Also covers the V1.0 version SENSOR_REPLY
169                if (isSensorDefReply()) {
170                    text = "Sensor Def Reply: ";
171                    text += "Number: " + getSensorDefNumString() + ", ";
172                    text += "Pin: " + getSensorDefPinString() + ", ";
173                    text += "Pullup: " + getSensorDefPullupString();
174                } else {
175                    text = "Sensor Reply (Active): ";
176                    text += "Number: " + getSensorNumString() + ", ";
177                    text += "State: ACTIVE";
178                }
179                break;
180            case DCCppConstants.OUTPUT_REPLY:
181                if (isOutputCmdReply()) {
182                    text = "Output Command Reply: ";
183                    text += "Number: " + getOutputNumString() + ", ";
184                    text += "State: " + getOutputCmdStateString();
185                } else if (isOutputDefReply()) {
186                    text = "Output Command Reply: ";
187                    text += "Number: " + getOutputNumString() + ", ";
188                    text += "Pin: " + getOutputListPinString() + ", ";
189                    text += "Flags: " + getOutputListIFlagString() + ", ";
190                    text += "State: " + getOutputListStateString();
191                } else {
192                    text = "Invalid Output Reply Format: ";
193                    text += toString();
194                }
195                break;
196            case DCCppConstants.PROGRAM_REPLY:
197                if (isProgramBitReply()) {
198                    text = "Program Bit Reply: ";
199                    text += "CallbackNum:" + getCallbackNumString() + ", ";
200                    text += "Sub:" + getCallbackSubString() + ", ";
201                    text += "CV:" + getCVString() + ", ";
202                    text += "Bit:" + getProgramBitString() + ", ";
203                    text += "Value:" + getReadValueString();
204                } else if (isProgramReplyV4()) {
205                    text = "Program Reply: ";
206                    text += "CV:" + getCVString() + ", ";
207                    text += "Value:" + getReadValueString();
208                } else if (isProgramBitReplyV4()) {
209                    text = "Program Bit Reply: ";
210                    text += "CV:" + getCVString() + ", ";
211                    text += "Bit:" + getProgramBitString() + ", ";
212                    text += "Value:" + getReadValueString();
213                } else if (isProgramLocoIdReply()) {
214                    text = "Program LocoId Reply: ";
215                    text += "LocoId:" + getLocoIdInt();
216                } else {
217                    text = "Program Reply: ";
218                    text += "CallbackNum:" + getCallbackNumString() + ", ";
219                    text += "Sub:" + getCallbackSubString() + ", ";
220                    text += "CV:" + getCVString() + ", ";
221                    text += "Value:" + getReadValueString();
222                }
223                break;
224            case DCCppConstants.VERIFY_REPLY:
225                text = "Prog Verify Reply: ";
226                text += "CV: " + getCVString() + ", ";
227                text += "Value: " + getReadValueString();
228                break;
229            case DCCppConstants.STATUS_REPLY:
230                text = "Status:";
231                text += "Station: " + getStationType();
232                text += ", Build: " + getBuildString();
233                text += ", Version: " + getVersion();
234                break;
235            case DCCppConstants.POWER_REPLY:
236                if (isNamedPowerReply()) {
237                    text = "Power Status: ";
238                    text += "Name:" + getPowerDistrictName();
239                    text += " Status:" + getPowerDistrictStatus();
240                } else {
241                    text = "Power Status: ";
242                    text += (getPowerBool() ? "ON" : "OFF");
243                }
244                break;
245            case DCCppConstants.CURRENT_REPLY:
246                text = "Current: " + getCurrentString() + " / 1024";
247                break;
248            case DCCppConstants.METER_REPLY:
249                text = String.format(
250                        "Meter reply: name %s, value %.2f, type %s, unit %s, min %.2f, max %.2f, resolution %.2f, warn %.2f",
251                        getMeterName(), getMeterValue(), getMeterType(),
252                        getMeterUnit(), getMeterMinValue(), getMeterMaxValue(),
253                        getMeterResolution(), getMeterWarnValue());
254                break;
255            case DCCppConstants.WRITE_EEPROM_REPLY:
256                text = "Write EEPROM Reply... ";
257                // TODO: Don't use getProgValueString()
258                text += "Turnouts: " + getValueString(1) + ", ";
259                text += "Sensors: " + getValueString(2) + ", ";
260                text += "Outputs: " + getValueString(3);
261                break;
262            case DCCppConstants.COMM_TYPE_REPLY:
263                text = "Comm Type Reply ";
264                text += "Type: " + getCommTypeInt();
265                text += " Port: " + getCommTypeValueString();
266                break;
267            case DCCppConstants.MADC_FAIL_REPLY:
268                text = "No Sensor/Turnout/Output Reply ";
269                break;
270            case DCCppConstants.MADC_SUCCESS_REPLY:
271                text = "Sensor/Turnout/Output MADC Success Reply ";
272                break;
273            case DCCppConstants.MAXNUMSLOTS_REPLY:
274                text = "Number of slots reply: " + getValueString(1);
275                break;
276            case DCCppConstants.DIAG_REPLY:
277                text = "DIAG: " + getValueString(1);
278                break;
279            case DCCppConstants.LOCO_STATE_REPLY:
280                text = "Loco State: LocoId:" + getLocoIdInt();
281                text += " Dir:" + getDirectionString();
282                text += " Speed:" + getSpeedInt();
283                text += " F0-28:" + getFunctionsString();
284                break;
285            case DCCppConstants.THROTTLE_COMMANDS_REPLY:
286                if (isTurnoutIDsReply()) {    
287                    text = "Turnout IDs:" + getTurnoutIDList();
288                    break;
289                } else if (isTurnoutIDReply()) {    
290                    text = "Turnout ID:" + getTOIDString();
291                    text += " State:" + getTurnoutStateString();
292                    text += " Desc:'" + getTurnoutDescString() + "'";
293                    break;
294                } else if (isRosterIDsReply()) {    
295                    text = "RosterIDs:" + getRosterIDList();
296                    break;
297                } else if (isRosterIDReply()) {    
298                    text = "RosterID:" + getRosterIDString();
299                    text += " Desc:'" + getRosterDescString() + "'";
300                    text += " Fkeys:'" + getRosterFKeysString() + "'";
301                    break;
302                } else if (isAutomationIDsReply()) {    
303                    text = "AutomationIDs:" + getAutomationIDList();
304                    break;
305                } else if (isAutomationIDReply()) {    
306                    text = "AutomationID:" + getAutomationIDString();
307                    text += " Type:" + getAutomationTypeString();
308                    text += " Desc:'" + getAutomationDescString() + "'";
309                    break;
310                } else if (isAutomationStateReply()) {    
311                    text = "AutomationID:" + getAutomationIDString();
312                    text += " State:" + getAutomationStateString() + "'";
313                    break;
314                } else if (isAutomationCaptionReply()) {    
315                    text = "AutomationID:" + getAutomationIDString();
316                    text += " Caption:'" + getAutomationCaptionString() + "'";
317                    break;
318                } else if (isCurrentMaxesReply()) {    
319                    text = "CurrentMaxes:" + getCurrentMaxesList();
320                    break;
321                } else if (isCurrentValuesReply()) {    
322                    text = "CurrentValues:" + getCurrentValuesList();
323                    break;
324                } else if (isClockReply()) {    
325                    String hhmm = String.format("%02d:%02d",
326                            getClockMinutesInt() / 60,
327                            getClockMinutesInt() % 60);
328                    text = "FastClock Reply: " + hhmm;
329                    if (!getClockRateString().isEmpty()) {
330                        text += ", Rate:" + getClockRateString();
331                    }
332                    break;
333                }
334                text = "Unknown Message: '" + toString() + "'";
335                break;
336            case DCCppConstants.TRACKMANAGER_CMD:
337                text = "TrackManager Letter:" + getTrackManagerLetter() + " Mode:" + getTrackManagerMode();
338                break;
339            case DCCppConstants.LCD_TEXT_CMD:
340                text = "LCD Text '" + getLCDTextString() + "', disp " + getLCDDisplayNumString() + ", line " + getLCDLineNumString();
341                break;
342            case DCCppConstants.MESSAGE_REPLY:
343                text = "Message: '" + getMessageString() + "'";
344                break;
345                
346            default:
347                text = "Unrecognized reply: '" + toString() + "'";
348        }
349
350        return text;
351    }
352
353    /**
354     * Generate properties list for certain replies
355     *
356     * @return list of all properties as a string
357     **/
358    public String getPropertiesAsString() {
359        StringBuilder text = new StringBuilder();
360        StringBuilder comma = new StringBuilder();
361        switch (getOpCodeChar()) {
362            case DCCppConstants.TURNOUT_REPLY:
363            case DCCppConstants.SENSOR_REPLY:
364            case DCCppConstants.OUTPUT_REPLY:
365                // write out properties in comment
366                getProperties().forEach((key, value) -> {
367                    text.append(comma).append(key).append(":").append(value);
368                    comma.setLength(0);
369                    comma.append(",");
370                });
371
372                break;
373            default:
374                break;
375        }
376        return text.toString();
377    }
378
379    /**
380     * build a propertylist from reply values.
381     *
382     * @return properties hashmap
383     **/
384    public LinkedHashMap<String, Object> getProperties() {
385        LinkedHashMap<String, Object> properties = new LinkedHashMap<>();
386        switch (getOpCodeChar()) {
387            case DCCppConstants.TURNOUT_REPLY:
388                if (isTurnoutDefDCCReply()) {
389                    properties.put(DCCppConstants.PROP_TYPE, DCCppConstants.TURNOUT_TYPE_DCC);
390                    properties.put(DCCppConstants.PROP_ID, getTOIDInt());
391                    properties.put(DCCppConstants.PROP_ADDRESS, getTOAddressInt());
392                    properties.put(DCCppConstants.PROP_INDEX, getTOAddressIndexInt());
393                    // if we are able to parse the address and index we can convert it
394                    // to a standard DCC address for display.
395                    if (getTOAddressInt() != -1 && getTOAddressIndexInt() != -1) {
396                        int boardAddr = getTOAddressInt();
397                        int boardIndex = getTOAddressIndexInt();
398                        int dccAddress = (((boardAddr - 1) * 4) + boardIndex) + 1;
399                        properties.put(DCCppConstants.PROP_DCCADDRESS, dccAddress);
400                    }
401                } else if (isTurnoutDefServoReply()) {
402                    properties.put(DCCppConstants.PROP_TYPE, DCCppConstants.TURNOUT_TYPE_SERVO);
403                    properties.put(DCCppConstants.PROP_ID, getTOIDInt());
404                    properties.put(DCCppConstants.PROP_PIN, getTOPinInt());
405                    properties.put(DCCppConstants.PROP_THROWNPOS, getTOThrownPositionInt());
406                    properties.put(DCCppConstants.PROP_CLOSEDPOS, getTOClosedPositionInt());
407                    properties.put(DCCppConstants.PROP_PROFILE, getTOProfileInt());
408                } else if (isTurnoutDefVpinReply()) {
409                    properties.put(DCCppConstants.PROP_TYPE, DCCppConstants.TURNOUT_TYPE_VPIN);
410                    properties.put(DCCppConstants.PROP_ID, getTOIDInt());
411                    properties.put(DCCppConstants.PROP_PIN, getTOPinInt());
412                } else if (isTurnoutDefLCNReply()) {
413                    properties.put(DCCppConstants.PROP_TYPE, DCCppConstants.TURNOUT_TYPE_LCN);
414                    properties.put(DCCppConstants.PROP_ID, getTOIDInt());
415                }
416                break;
417            case DCCppConstants.SENSOR_REPLY:
418                if (isSensorDefReply()) {
419                    properties.put(DCCppConstants.PROP_TYPE, DCCppConstants.SENSOR_TYPE);
420                    properties.put(DCCppConstants.PROP_ID, getSensorDefNumInt());
421                    properties.put(DCCppConstants.PROP_PIN, getSensorDefPinInt());
422                    properties.put(DCCppConstants.PROP_PULLUP, getSensorDefPullupBool());
423                }
424                break;
425            case DCCppConstants.OUTPUT_REPLY:
426                if (isOutputDefReply()) {
427                    properties.put(DCCppConstants.PROP_TYPE, DCCppConstants.OUTPUT_TYPE);
428                    properties.put(DCCppConstants.PROP_ID, getOutputNumInt());
429                    properties.put(DCCppConstants.PROP_PIN, getOutputListPinInt());
430                    properties.put(DCCppConstants.PROP_IFLAG, getOutputListIFlagInt());
431                }
432                break;
433            default:
434                break;
435        }
436        return properties;
437    }
438
439    public void parseReply(String s) {
440        DCCppReply r = DCCppReply.parseDCCppReply(s);
441        log.debug("in parseReply() string: {}", s);
442        if (!(r.toString().isBlank())) {
443            this.myRegex = r.myRegex;
444            this.myReply = r.myReply;
445            this._nDataChars = r._nDataChars;
446            log.trace("copied: this: {}", this);
447        }
448    }
449
450    ///
451    ///
452    /// TODO: Stopped Refactoring to StringBuilder here 12/12/15
453    ///
454    ///
455
456    /**
457     * Parses a string and generates a DCCppReply from the string contents
458     *
459     * @param s String to be parsed
460     * @return DCCppReply or empty string if not a valid formatted string
461     */
462    @Nonnull
463    public static DCCppReply parseDCCppReply(String s) {
464
465        if (log.isTraceEnabled()) {
466            log.trace("Parse charAt(0): {}", s.charAt(0));
467        }
468        DCCppReply r = new DCCppReply(s);
469        switch (s.charAt(0)) {
470            case DCCppConstants.STATUS_REPLY:
471                if (s.matches(DCCppConstants.STATUS_REPLY_BSC_REGEX)) {
472                    log.debug("BSC Status Reply: '{}'", r);
473                    r.myRegex = DCCppConstants.STATUS_REPLY_BSC_REGEX;
474                } else if (s.matches(DCCppConstants.STATUS_REPLY_ESP32_REGEX)) {
475                    log.debug("ESP32 Status Reply: '{}'", r);
476                    r.myRegex = DCCppConstants.STATUS_REPLY_ESP32_REGEX;
477                } else if (s.matches(DCCppConstants.STATUS_REPLY_REGEX)) {
478                    log.debug("Original Status Reply: '{}'", r);
479                    r.myRegex = DCCppConstants.STATUS_REPLY_REGEX;
480                } else if (s.matches(DCCppConstants.STATUS_REPLY_DCCEX_REGEX)) {
481                    log.debug("DCC-EX Status Reply: '{}'", r);
482                    r.myRegex = DCCppConstants.STATUS_REPLY_DCCEX_REGEX;
483                }
484                return (r);
485            case DCCppConstants.THROTTLE_REPLY:
486                if (s.matches(DCCppConstants.THROTTLE_REPLY_REGEX)) {
487                    log.debug("Throttle Reply: '{}'", r);
488                    r.myRegex = DCCppConstants.THROTTLE_REPLY_REGEX;
489                }
490                return (r);
491            case DCCppConstants.TURNOUT_REPLY:
492                // the order of checking the reply here is critical as both the
493                // TURNOUT_DEF_REPLY and TURNOUT_REPLY regex strings start with 
494                // the same strings but have different meanings.
495                if (s.matches(DCCppConstants.TURNOUT_DEF_REPLY_REGEX)) {
496                    r.myRegex = DCCppConstants.TURNOUT_DEF_REPLY_REGEX;
497                } else if (s.matches(DCCppConstants.TURNOUT_DEF_DCC_REPLY_REGEX)) {
498                    r.myRegex = DCCppConstants.TURNOUT_DEF_DCC_REPLY_REGEX;
499                } else if (s.matches(DCCppConstants.TURNOUT_DEF_SERVO_REPLY_REGEX)) {
500                    r.myRegex = DCCppConstants.TURNOUT_DEF_SERVO_REPLY_REGEX;
501                } else if (s.matches(DCCppConstants.TURNOUT_DEF_VPIN_REPLY_REGEX)) {
502                    r.myRegex = DCCppConstants.TURNOUT_DEF_VPIN_REPLY_REGEX;
503                } else if (s.matches(DCCppConstants.TURNOUT_DEF_LCN_REPLY_REGEX)) {
504                    r.myRegex = DCCppConstants.TURNOUT_DEF_LCN_REPLY_REGEX;
505                } else if (s.matches(DCCppConstants.TURNOUT_REPLY_REGEX)) {
506                    r.myRegex = DCCppConstants.TURNOUT_REPLY_REGEX;
507                } else if (s.matches(DCCppConstants.MADC_FAIL_REPLY_REGEX)) {
508                    r.myRegex = DCCppConstants.MADC_FAIL_REPLY_REGEX;
509                }
510                log.debug("Parsed Reply: '{}' length {}", r.toString(), r._nDataChars);
511                return (r);
512            case DCCppConstants.OUTPUT_REPLY:
513                if (s.matches(DCCppConstants.OUTPUT_DEF_REPLY_REGEX)) {
514                    r.myRegex = DCCppConstants.OUTPUT_DEF_REPLY_REGEX;
515                } else if (s.matches(DCCppConstants.OUTPUT_REPLY_REGEX)) {
516                    r.myRegex = DCCppConstants.OUTPUT_REPLY_REGEX;
517                }
518                log.debug("Parsed Reply: '{}' length {}", r, r._nDataChars);
519                return (r);
520            case DCCppConstants.PROGRAM_REPLY:
521                if (s.matches(DCCppConstants.PROGRAM_BIT_REPLY_REGEX)) {
522                    log.debug("Matches ProgBitReply");
523                    r.myRegex = DCCppConstants.PROGRAM_BIT_REPLY_REGEX;
524                } else if (s.matches(DCCppConstants.PROGRAM_BIT_REPLY_V4_REGEX)) {
525                    log.debug("Matches ProgBitReplyV2");
526                    r.myRegex = DCCppConstants.PROGRAM_BIT_REPLY_V4_REGEX;
527                } else if (s.matches(DCCppConstants.PROGRAM_REPLY_REGEX)) {
528                    log.debug("Matches ProgReply");
529                    r.myRegex = DCCppConstants.PROGRAM_REPLY_REGEX;
530                } else if (s.matches(DCCppConstants.PROGRAM_REPLY_V4_REGEX)) {
531                    log.debug("Matches ProgReplyV2");
532                    r.myRegex = DCCppConstants.PROGRAM_REPLY_V4_REGEX;
533                } else if (s.matches(DCCppConstants.PROGRAM_LOCOID_REPLY_REGEX)) {
534                    log.debug("Matches ProgLocoIDReply");
535                    r.myRegex = DCCppConstants.PROGRAM_LOCOID_REPLY_REGEX;
536                } else {
537                    log.debug("Does not match ProgReply Regex");
538                }
539                return (r);
540            case DCCppConstants.VERIFY_REPLY:
541                if (s.matches(DCCppConstants.PROGRAM_VERIFY_REPLY_REGEX)) {
542                    log.debug("Matches VerifyReply");
543                    r.myRegex = DCCppConstants.PROGRAM_VERIFY_REPLY_REGEX;
544                } else {
545                    log.debug("Does not match VerifyReply Regex");
546                }
547                return (r);
548            case DCCppConstants.POWER_REPLY:
549                if (s.matches(DCCppConstants.TRACK_POWER_REPLY_NAMED_REGEX)) {
550                    r.myRegex = DCCppConstants.TRACK_POWER_REPLY_NAMED_REGEX;
551                } else if (s.matches(DCCppConstants.TRACK_POWER_REPLY_REGEX)) {
552                    r.myRegex = DCCppConstants.TRACK_POWER_REPLY_REGEX;
553                }
554                return (r);
555            case DCCppConstants.CURRENT_REPLY:
556                if (s.matches(DCCppConstants.CURRENT_REPLY_NAMED_REGEX)) {
557                    r.myRegex = DCCppConstants.CURRENT_REPLY_NAMED_REGEX;
558                } else if (s.matches(DCCppConstants.CURRENT_REPLY_REGEX)) {
559                    r.myRegex = DCCppConstants.CURRENT_REPLY_REGEX;
560                }
561                return (r);
562            case DCCppConstants.METER_REPLY:
563                if (s.matches(DCCppConstants.METER_REPLY_REGEX)) {
564                    r.myRegex = DCCppConstants.METER_REPLY_REGEX;
565                }
566                return (r);
567            case DCCppConstants.MAXNUMSLOTS_REPLY:
568                if (s.matches(DCCppConstants.MAXNUMSLOTS_REPLY_REGEX)) {
569                    r.myRegex = DCCppConstants.MAXNUMSLOTS_REPLY_REGEX;
570                }
571                return (r);
572            case DCCppConstants.DIAG_REPLY:
573                if (s.matches(DCCppConstants.DIAG_REPLY_REGEX)) {
574                    r.myRegex = DCCppConstants.DIAG_REPLY_REGEX;
575                }
576                return (r);
577            case DCCppConstants.LCD_TEXT_REPLY:
578                if (s.matches(DCCppConstants.LCD_TEXT_REPLY_REGEX)) {
579                    r.myRegex = DCCppConstants.LCD_TEXT_REPLY_REGEX;
580                }
581                return (r);
582            case DCCppConstants.MESSAGE_REPLY:
583                if (s.matches(DCCppConstants.MESSAGE_REPLY_REGEX)) {
584                    r.myRegex = DCCppConstants.MESSAGE_REPLY_REGEX;
585                }
586                return (r);
587            case DCCppConstants.WRITE_EEPROM_REPLY:
588                if (s.matches(DCCppConstants.WRITE_EEPROM_REPLY_REGEX)) {
589                    r.myRegex = DCCppConstants.WRITE_EEPROM_REPLY_REGEX;
590                }
591                return (r);
592            case DCCppConstants.SENSOR_REPLY_H:
593                if (s.matches(DCCppConstants.SENSOR_INACTIVE_REPLY_REGEX)) {
594                    r.myRegex = DCCppConstants.SENSOR_INACTIVE_REPLY_REGEX;
595                }
596                return (r);
597            case DCCppConstants.SENSOR_REPLY_L:
598                if (s.matches(DCCppConstants.SENSOR_DEF_REPLY_REGEX)) {
599                    r.myRegex = DCCppConstants.SENSOR_DEF_REPLY_REGEX;
600                } else if (s.matches(DCCppConstants.SENSOR_ACTIVE_REPLY_REGEX)) {
601                    r.myRegex = DCCppConstants.SENSOR_ACTIVE_REPLY_REGEX;
602                }
603                return (r);
604            case DCCppConstants.MADC_FAIL_REPLY:
605                r.myRegex = DCCppConstants.MADC_FAIL_REPLY_REGEX;
606                return (r);
607            case DCCppConstants.MADC_SUCCESS_REPLY:
608                r.myRegex = DCCppConstants.MADC_SUCCESS_REPLY_REGEX;
609                return (r);
610            case DCCppConstants.COMM_TYPE_REPLY:
611                r.myRegex = DCCppConstants.COMM_TYPE_REPLY_REGEX;
612                return (r);
613            case DCCppConstants.LOCO_STATE_REPLY:
614                r.myRegex = DCCppConstants.LOCO_STATE_REGEX;
615                return (r);
616            case DCCppConstants.THROTTLE_COMMANDS_REPLY:
617                if (s.matches(DCCppConstants.TURNOUT_IDS_REPLY_REGEX)) {
618                    r.myRegex = DCCppConstants.TURNOUT_IDS_REPLY_REGEX;
619                } else if (s.matches(DCCppConstants.TURNOUT_ID_REPLY_REGEX)) {
620                    r.myRegex = DCCppConstants.TURNOUT_ID_REPLY_REGEX;
621                } else if (s.matches(DCCppConstants.ROSTER_IDS_REPLY_REGEX)) {
622                    r.myRegex = DCCppConstants.ROSTER_IDS_REPLY_REGEX;
623                } else if (s.matches(DCCppConstants.ROSTER_ID_REPLY_REGEX)) {
624                    r.myRegex = DCCppConstants.ROSTER_ID_REPLY_REGEX;
625                } else if (s.matches(DCCppConstants.AUTOMATION_IDS_REPLY_REGEX)) {
626                    r.myRegex = DCCppConstants.AUTOMATION_IDS_REPLY_REGEX;
627                } else if (s.matches(DCCppConstants.AUTOMATION_ID_REPLY_REGEX)) {
628                    r.myRegex = DCCppConstants.AUTOMATION_ID_REPLY_REGEX;
629                } else if (s.matches(DCCppConstants.AUTOMATION_STATE_REPLY_REGEX)) {
630                    r.myRegex = DCCppConstants.AUTOMATION_STATE_REPLY_REGEX;
631                } else if (s.matches(DCCppConstants.AUTOMATION_CAPTION_REPLY_REGEX)) {
632                    r.myRegex = DCCppConstants.AUTOMATION_CAPTION_REPLY_REGEX;
633                } else if (s.matches(DCCppConstants.CURRENT_MAXES_REPLY_REGEX)) {
634                    r.myRegex = DCCppConstants.CURRENT_MAXES_REPLY_REGEX;
635                } else if (s.matches(DCCppConstants.CURRENT_VALUES_REPLY_REGEX)) {
636                    r.myRegex = DCCppConstants.CURRENT_VALUES_REPLY_REGEX;
637                } else if (s.matches(DCCppConstants.CLOCK_REPLY_REGEX)) {
638                    r.myRegex = DCCppConstants.CLOCK_REPLY_REGEX;
639                }
640                log.debug("Parsed Reply: '{}' length {}", r.toString(), r._nDataChars);
641                return (r);
642            case DCCppConstants.TRACKMANAGER_CMD:
643                if (s.matches(DCCppConstants.TRACKMANAGER_REPLY_REGEX)) {
644                    r.myRegex = DCCppConstants.TRACKMANAGER_REPLY_REGEX;
645                }
646                return (r);
647            default:
648                return (r);
649        }
650    }
651
652    /**
653     * Not really used inside of DCC-EX. Just here to play nicely with the
654     * inheritance. 
655     * TODO: If this is unused, can we just not override it and (not) "use" 
656     * the superclass version? 
657     * ANSWER: No, we can't because the superclass looks in the _datachars
658     * element, which we don't use, and which will contain garbage data.
659     * Better to return something meaningful.
660     * 
661     * @return first char of myReply as integer
662     */
663    @Override
664    public int getOpCode() {
665        if (myReply.length() > 0) {
666            return (Character.getNumericValue(myReply.charAt(0)));
667        } else {
668            return (0);
669        }
670    }
671
672    /**
673     * Get the opcode as a one character string.
674     * 
675     * @return first char of myReply
676     */
677    public char getOpCodeChar() {
678        if (myReply.length() > 0) {
679            return (myReply.charAt(0));
680        } else {
681            return (0);
682        }
683    }
684
685    @Override
686    public int getElement(int n) {
687        if ((n >= 0) && (n < myReply.length())) {
688            return (myReply.charAt(n));
689        } else {
690            return (' ');
691        }
692    }
693
694    @Override
695    public void setElement(int n, int v) {
696        // We want the ASCII value, not the string interpretation of the int
697        char c = (char) (v & 0xFF);
698        if (myReply == null) {
699            myReply = new StringBuilder(Character.toString(c));
700        } else if (n >= myReply.length()) {
701            myReply.append(c);
702        } else if (n > 0) {
703            myReply.setCharAt(n, c);
704        }
705    }
706
707    public boolean getValueBool(int idx) {
708        Matcher m = DCCppReply.match(myReply.toString(), myRegex, "gvb");
709        if (m == null) {
710            log.error("DCCppReply '{}' not matched by '{}'", this.toString(), myRegex);
711            return (false);
712        } else if (idx <= m.groupCount()) {
713            return (!m.group(idx).equals("0"));
714        } else {
715            log.error("DCCppReply bool value index too big. idx = {} msg = {}", idx, this.toString());
716            return (false);
717        }
718    }
719
720    public String getValueString(int idx) {
721        Matcher m = DCCppReply.match(myReply.toString(), myRegex, "gvs");
722        if (m == null) {
723            log.error("DCCppReply '{}' not matched by '{}'", this.toString(), myRegex);
724            return ("");
725        } else if (idx <= m.groupCount()) {
726            return (m.group(idx));
727        } else {
728            log.error("DCCppReply string value index too big. idx = {} msg = {}", idx, this.toString());
729            return ("");
730        }
731    }
732
733    // is there a match at idx?
734    public boolean valueExists(int idx) {
735        Matcher m = DCCppReply.match(myReply.toString(), myRegex, "gvs");
736        return (m != null) && (idx <= m.groupCount());
737    }
738
739    public int getValueInt(int idx) {
740        Matcher m = DCCppReply.match(myReply.toString(), myRegex, "gvi");
741        if (m == null) {
742            log.error("DCCppReply '{}' not matched by '{}'", this.toString(), myRegex);
743            return (0);
744        } else if (idx <= m.groupCount()) {
745            return (Integer.parseInt(m.group(idx)));
746        } else {
747            log.error("DCCppReply int value index too big. idx = {} msg = {}", idx, this.toString());
748            return (0);
749        }
750    }
751
752    public double getValueDouble(int idx) {
753        Matcher m = DCCppReply.match(myReply.toString(), myRegex, "gvd");
754        if (m == null) {
755            log.error("DCCppReply '{}' not matched by '{}'", this.toString(), myRegex);
756            return (0.0);
757        } else if (idx <= m.groupCount()) {
758            return (Double.parseDouble(m.group(idx)));
759        } else {
760            log.error("DCCppReply double value index too big. idx = {} msg = {}", idx, this.toString());
761            return (0.0);
762        }
763    }
764
765    /*
766     * skipPrefix is not used at this point in time, but is defined as abstract
767     * in AbstractMRReply
768     */
769    @Override
770    protected int skipPrefix(int index) {
771        return -1;
772    }
773
774    @Override
775    public int maxSize() {
776        return DCCppConstants.MAX_REPLY_SIZE;
777    }
778
779    public int getLength() {
780        return (myReply.length());
781    }
782
783    /*
784     * Some notes on DCC-EX messages and responses...
785     *
786     * Messages that have responses expected: 
787     * t : <T REGISTER SPEED DIRECTION>
788     * f : (none)
789     * a : (none)
790     * T : <H ID THROW>
791     * w : (none)
792     * b : (none)
793     * W : <r CALLBACKNUM|CALLBACKSUB|CV CV_Value>
794     * B : <r CALLBACKNUM CALLBACKSUB|CV|Bit CV_Bit_Value>
795     * R : <r CALLBACKNUM|CALLBACKSUB|CV CV_Value>
796     * 1 : <p1>
797     * 0 : <p0>
798     * c : <a CURRENT>
799     * s : Series of status messages... 
800     *     <p[0,1]> Power state
801     *     <T ...>Throttle responses from all registers
802     *     <iDCC-EX ... > Base station version and build date
803     *     <H ID ADDR INDEX THROW> All turnout states.
804     *
805     * Unsolicited Replies:
806     *     <Q snum [0,1]> Sensor reply.
807     * Debug messages:
808     * M : (none)
809     * P : (none)
810     * f : <f MEM>
811     * L : <M ... data ... >
812     */
813
814    // -------------------------------------------------------------------
815    // Message helper functions
816    // Core methods
817
818    protected boolean matches(String pat) {
819        return (match(this.toString(), pat, "Validator") != null);
820    }
821
822    protected static Matcher match(String s, String pat, String name) {
823        try {
824            Pattern p = Pattern.compile(pat);
825            Matcher m = p.matcher(s);
826            if (!m.matches()) {
827                log.trace("No Match {} Command: {} pattern {}", name, s, pat);
828                return (null);
829            }
830            return (m);
831
832        } catch (PatternSyntaxException e) {
833            log.error("Malformed DCC-EX reply syntax! s = {}", pat);
834            return (null);
835        } catch (IllegalStateException e) {
836            log.error("Group called before match operation executed string = {}", s);
837            return (null);
838        } catch (IndexOutOfBoundsException e) {
839            log.error("Index out of bounds string = {}", s);
840            return (null);
841        }
842    }
843
844    public String getStationType() {
845        if (this.isStatusReply()) {
846            return (this.getValueString(1)); // 1st match in all versions
847        } else {
848            return ("Unknown");
849        }
850    }
851
852    // build value is 3rd match in v3+, 2nd in previous
853    public String getBuildString() {
854        if (this.isStatusReply()) {
855            if (this.valueExists(3)) {
856                return(this.getValueString(3));
857            } else {
858                return(this.getValueString(2));
859            }
860        } else {
861            return("Unknown");
862        }
863    }
864
865    // look for canonical version in 2nd match
866    public String getVersion() {
867        if (this.isStatusReply()) {
868            String s = this.getValueString(2);
869            if (jmri.Version.isCanonicalVersion(s)) {
870                return s;
871            } else {
872                return ("0.0.0");
873            }
874        } else {
875            return ("Unknown");
876        }
877    }
878
879    // Helper methods for Throttle and LocoState Replies
880
881    public int getLocoIdInt() {
882        if (this.isLocoStateReply() || this.isProgramLocoIdReply()) {
883            return (this.getValueInt(1));
884        } else {
885            log.error("LocoId Parser called on non-LocoId message type {}", this.getOpCodeChar());
886            return (-1);
887        }
888    }
889
890    public int getSpeedByteInt() {
891        if (this.isLocoStateReply()) {
892            return (this.getValueInt(3));
893        } else {
894            log.error("ThrottleReply Parser called on non-Throttle message type {}", this.getOpCodeChar());
895            return (0);
896        }
897    }
898
899    public int getFunctionsInt() {
900        if (this.isLocoStateReply()) {
901            return (this.getValueInt(4));
902        } else {
903            log.error("ThrottleReply Parser called on non-Throttle message type {}", this.getOpCodeChar());
904            return (0);
905        }
906    }
907
908    public String getFunctionsString() {
909        if (this.isLocoStateReply()) {
910            return new StringBuilder(StringUtils.leftPad(Integer.toBinaryString(this.getValueInt(4)), 29, "0"))
911                    .reverse().toString();
912        } else {
913            log.error("ThrottleReply Parser called on non-Throttle message type {}", this.getOpCodeChar());
914            return ("not a locostate!");
915        }
916    }
917
918    public String getRegisterString() {
919        return String.valueOf(getRegisterInt());
920    }
921
922    public int getRegisterInt() {
923        if (this.isThrottleReply()) {
924            return (this.getValueInt(1));
925        } else {
926            log.error("ThrottleReply Parser called on non-Throttle message type {}", this.getOpCodeChar());
927            return (0);
928        }
929    }
930
931    public String getSpeedString() {
932        return String.valueOf(getSpeedInt());
933    }
934
935    public int getSpeedInt() {
936        if (this.isThrottleReply()) {
937            return (this.getValueInt(2));
938        } else if (this.isLocoStateReply()) {
939            int speed = this.getValueInt(3) & 0x7f; // drop direction bit
940            if (speed == 1) {
941                return -1; // special case for eStop
942            }
943            if (speed > 1) {
944                return speed - 1; // bump speeds down 1 due to eStop at 1
945            }
946            return 0; // stop is zero
947        } else {
948            log.error("ThrottleReply Parser called on non-Throttle message type {}", this.getOpCodeChar());
949            return (0);
950        }
951    }
952
953    public boolean isEStop() {
954        return getSpeedInt() == -1;
955    }
956
957    public String getDirectionString() {
958        // Will return "Forward" (true) or "Reverse" (false)
959        return (getDirectionInt() == 1 ? "Forward" : "Reverse");
960    }
961
962    public int getDirectionInt() {
963        // Will return 1 (true) or 0 (false)
964        if (this.isThrottleReply()) {
965            return (this.getValueInt(3));
966        } else if (this.isLocoStateReply()) {
967            return this.getValueInt(3) >> 7;
968        } else {
969            log.error("ThrottleReply Parser called on non-ThrottleReply message type {}", this.getOpCodeChar());
970            return (0);
971        }
972    }
973
974    public boolean getDirectionBool() {
975        // Will return true or false
976        if (this.isThrottleReply()) {
977            return (this.getValueBool(3));
978        } else if (this.isLocoStateReply()) {
979            return ((this.getValueInt(3) >> 7) == 1);
980        } else {
981            log.error("ThrottleReply Parser called on non-ThrottleReply message type {}", this.getOpCodeChar());
982            return (false);
983        }
984    }
985
986    public boolean getIsForward() {
987        return getDirectionBool();
988    }
989
990    // ------------------------------------------------------
991    // Helper methods for Turnout Replies
992
993    public String getTOIDString() {
994        if (this.isTurnoutReply() || this.isTurnoutIDReply()) {
995            return (this.getValueString(1));
996        } else {
997            log.error("TurnoutReply Parser called on non-TurnoutReply message type {}", this.getOpCodeChar());
998            return ("0");
999        }
1000    }
1001    public int getTOIDInt() {
1002        if (this.isTurnoutReply() || this.isTurnoutIDReply()) {
1003            return (this.getValueInt(1));
1004        } else {
1005            log.error("TurnoutReply Parser called on non-TurnoutReply message type {}", this.getOpCodeChar());
1006            return (0);
1007        }
1008    }
1009
1010    public String getTOAddressString() {
1011        if (this.isTurnoutDefReply() || this.isTurnoutDefDCCReply()) {
1012            return (this.getValueString(2));
1013        } else {
1014            return ("-1");
1015        }
1016    }
1017
1018    public int getTOAddressInt() {
1019        if (this.isTurnoutDefReply() || this.isTurnoutDefDCCReply()) {
1020            return (this.getValueInt(2));
1021        } else {
1022            return (-1);
1023        }
1024    }
1025
1026    public String getTOAddressIndexString() {
1027        if (this.isTurnoutDefReply() || this.isTurnoutDefDCCReply()) {
1028            return (this.getValueString(3));
1029        } else {
1030            return ("-1");
1031        }
1032    }
1033
1034    public int getTOAddressIndexInt() {
1035        if (this.isTurnoutDefReply() || this.isTurnoutDefDCCReply()) {
1036            return (this.getValueInt(3));
1037        } else {
1038            return (-1);
1039        }
1040    }
1041
1042    public int getTOPinInt() {
1043        if (this.isTurnoutDefServoReply() || this.isTurnoutDefVpinReply()) {
1044            return (this.getValueInt(2));
1045        } else {
1046            return (-1);
1047        }
1048    }
1049
1050    public int getTOThrownPositionInt() {
1051        if (this.isTurnoutDefServoReply()) {
1052            return (this.getValueInt(3));
1053        } else {
1054            return (-1);
1055        }
1056    }
1057
1058    public int getTOClosedPositionInt() {
1059        if (this.isTurnoutDefServoReply()) {
1060            return (this.getValueInt(4));
1061        } else {
1062            return (-1);
1063        }
1064    }
1065
1066    public int getTOProfileInt() {
1067        if (this.isTurnoutDefServoReply()) {
1068            return (this.getValueInt(5));
1069        } else {
1070            return (-1);
1071        }
1072    }
1073
1074    public String getTOStateString() {
1075        // Will return human readable state. To get string value for command,
1076        // use
1077        // getTOStateInt().toString()
1078        if (this.isTurnoutReply()) {
1079            return (this.getTOStateInt() == 1 ? "THROWN" : "CLOSED");
1080        } else {
1081            log.error("TurnoutReply Parser called on non-TurnoutReply message type {}", this.getOpCodeChar());
1082            return ("Not a Turnout");
1083        }
1084    }
1085
1086    public int getTOStateInt() {
1087        // Will return 1 (true - thrown) or 0 (false - closed)
1088        if (this.isTurnoutDefReply() || this.isTurnoutDefDCCReply()) { // turnout
1089                                                                       // list
1090                                                                       // response
1091            return (this.getValueInt(4));
1092        } else if (this.isTurnoutDefServoReply()) { // servo turnout
1093            return (this.getValueInt(6));
1094        } else if (this.isTurnoutDefVpinReply()) { // vpin turnout
1095            return (this.getValueInt(3));
1096        } else if (this.isTurnoutDefLCNReply()) { // LCN turnout
1097            return (this.getValueInt(2));
1098        } else if (this.isTurnoutReply()) { // single turnout response
1099            return (this.getValueInt(2));
1100        } else {
1101            log.error("TOStateInt Parser called on non-TOStateInt message type {}", this.getOpCodeChar());
1102            return (0);
1103        }
1104    }
1105
1106    public boolean getTOIsThrown() {
1107        return (this.getTOStateInt() == 1);
1108    }
1109
1110    public boolean getTOIsClosed() {
1111        return (!this.getTOIsThrown());
1112    }
1113
1114    public String getRosterIDString() {
1115        return (Integer.toString(this.getRosterIDInt()));
1116    }
1117    public int getRosterIDInt() {
1118        if (this.isRosterIDReply()) {
1119            return (this.getValueInt(1));
1120        } else {
1121            log.error("RosterIDInt Parser called on non-RosterIDInt message type {}", this.getOpCodeChar());
1122            return (0);
1123        }
1124    }
1125    public String getAutomationIDString() {
1126        return (Integer.toString(this.getAutomationIDInt()));
1127    }
1128    public int getAutomationIDInt() {
1129        if (this.isAutomationIDReply() || this.isAutomationStateReply() || this.isAutomationCaptionReply()) {
1130            return (this.getValueInt(1));
1131        } else {
1132            log.error("AutomationIDInt Parser called on non-AutomationIDInt message '{}'", this.toString());
1133            return (0);
1134        }
1135    }
1136    
1137    // ------------------------------------------------------
1138    // Helper methods for Program Replies
1139
1140    public String getCallbackNumString() {
1141        if (this.isProgramReply() || isProgramBitReply()) {
1142            return (this.getValueString(1));
1143        } else {
1144            log.error("ProgramReply Parser called on non-ProgramReply message type {}", this.getOpCodeChar());
1145            return ("0");
1146        }
1147    }
1148
1149    public int getCallbackNumInt() {
1150        if (this.isProgramReply() || isProgramBitReply() ) {
1151            return(this.getValueInt(1));
1152        } else {
1153            log.error("ProgramReply Parser called on non-ProgramReply message type {}", this.getOpCodeChar());
1154            return(0);
1155        }
1156    }
1157
1158    public String getCallbackSubString() {
1159        if (this.isProgramReply() || isProgramBitReply()) {
1160            return (this.getValueString(2));
1161        } else {
1162            log.error("ProgramReply Parser called on non-ProgramReply message type {}", this.getOpCodeChar());
1163            return ("0");
1164        }
1165    }
1166
1167    public int getCallbackSubInt() {
1168        if (this.isProgramReply() || isProgramBitReply()) {
1169            return (this.getValueInt(2));
1170        } else {
1171            log.error("ProgramReply Parser called on non-ProgramReply message type {}", this.getOpCodeChar());
1172            return (0);
1173        }
1174    }
1175
1176    public String getCVString() {
1177        if (this.isProgramReply()) {
1178            return (this.getValueString(3));
1179        } else if (this.isProgramBitReply()) {
1180            return (this.getValueString(3));
1181        } else if (this.isVerifyReply()) {
1182            return (this.getValueString(1));
1183        } else if (this.isProgramReplyV4()) {
1184            return (this.getValueString(1));
1185        } else if (this.isProgramBitReplyV4()) {
1186            return (this.getValueString(1));
1187        } else {
1188            log.error("ProgramReply Parser called on non-ProgramReply message type {}", this.getOpCodeChar());
1189            return ("0");
1190        }
1191    }
1192
1193    public int getCVInt() {
1194        if (this.isProgramReply()) {
1195            return (this.getValueInt(3));
1196        } else if (this.isProgramBitReply()) {
1197            return (this.getValueInt(3));
1198        } else if (this.isVerifyReply()) {
1199            return (this.getValueInt(1));
1200        } else if (this.isProgramReplyV4()) {
1201            return (this.getValueInt(1));
1202        } else if (this.isProgramBitReplyV4()) {
1203            return (this.getValueInt(1));
1204        } else {
1205            log.error("ProgramReply Parser called on non-ProgramReply message type {}", this.getOpCodeChar());
1206            return (0);
1207        }
1208    }
1209
1210    public String getProgramBitString() {
1211        if (this.isProgramBitReply()) {
1212            return (this.getValueString(4));
1213        } else if (this.isProgramBitReplyV4()) {
1214            return (this.getValueString(2));
1215        } else {
1216            log.error("ProgramReply Parser called on non-ProgramReply message type {}", this.getOpCodeChar());
1217            return ("0");
1218        }
1219    }
1220
1221    public int getProgramBitInt() {
1222        if (this.isProgramBitReply()) {
1223            return (this.getValueInt(4));
1224        } else {
1225            log.error("ProgramReply Parser called on non-ProgramReply message type {}", this.getOpCodeChar());
1226            return (0);
1227        }
1228    }
1229
1230    public String getReadValueString() {
1231        if (this.isProgramReply()) {
1232            return (this.getValueString(4));
1233        } else if (this.isProgramBitReply()) {
1234            return (this.getValueString(5));
1235        } else if (this.isProgramBitReplyV4()) {
1236            return (this.getValueString(3));
1237        } else if (this.isProgramReplyV4()) {
1238            return (this.getValueString(2));
1239        } else if (this.isVerifyReply()) {
1240            return (this.getValueString(2));
1241        } else {
1242            log.error("ProgramReply Parser called on non-ProgramReply message type {}", this.getOpCodeChar());
1243            return ("0");
1244        }
1245    }
1246
1247    public int getReadValueInt() {
1248        return (Integer.parseInt(this.getReadValueString()));
1249    }
1250
1251    public String getPowerDistrictName() {
1252        if (this.isNamedPowerReply()) {
1253            return (this.getValueString(2));
1254        } else {
1255            log.error("NamedPowerReply Parser called on non-NamedPowerReply message type '{}' message '{}'",
1256                    this.getOpCodeChar(), this.toString());
1257            return ("");
1258        }
1259    }
1260
1261    public String getPowerDistrictStatus() {
1262        if (this.isNamedPowerReply()) {
1263            switch (this.getValueString(1)) {
1264                case DCCppConstants.POWER_OFF:
1265                    return ("OFF");
1266                case DCCppConstants.POWER_ON:
1267                    return ("ON");
1268                default:
1269                    return ("OVERLOAD");
1270            }
1271        } else {
1272            log.error("NamedPowerReply Parser called on non-NamedPowerReply message type {} message {}",
1273                    this.getOpCodeChar(), this.toString());
1274            return ("");
1275        }
1276    }
1277
1278    public String getCurrentString() {
1279        if (this.isCurrentReply()) {
1280            if (this.isNamedCurrentReply()) {
1281                return (this.getValueString(2));
1282            }
1283            return (this.getValueString(1));
1284        } else {
1285            log.error("CurrentReply Parser called on non-CurrentReply message type {} message {}", this.getOpCodeChar(),
1286                    this.toString());
1287            return ("0");
1288        }
1289    }
1290
1291    public int getCurrentInt() {
1292        return (Integer.parseInt(this.getCurrentString()));
1293    }
1294
1295    public String getMeterName() {
1296        if (this.isMeterReply()) {
1297            return (this.getValueString(1));
1298        } else {
1299            log.error("MeterReply Parser called on non-MeterReply message type '{}' message '{}'", this.getOpCodeChar(),
1300                    this.toString());
1301            return ("");
1302        }
1303    }
1304
1305    public double getMeterValue() {
1306        if (this.isMeterReply()) {
1307            return (this.getValueDouble(2));
1308        } else {
1309            log.error("MeterReply Parser called on non-MeterReply message type '{}' message '{}'", this.getOpCodeChar(),
1310                    this.toString());
1311            return (0.0);
1312        }
1313    }
1314
1315    public String getMeterType() {
1316        if (this.isMeterReply()) {
1317            String t = getValueString(3);
1318            if (t.equals(DCCppConstants.VOLTAGE) || t.equals(DCCppConstants.CURRENT)) {
1319                return (t);
1320            } else {
1321                log.warn("Meter Type '{}' is not valid type in message '{}'", t, this.toString());
1322                return ("");
1323            }
1324        } else {
1325            log.error("MeterReply Parser called on non-MeterReply message type '{}' message '{}'", this.getOpCodeChar(),
1326                    this.toString());
1327            return ("");
1328        }
1329    }
1330
1331    public jmri.Meter.Unit getMeterUnit() {
1332        if (this.isMeterReply()) {
1333            String us = this.getValueString(4);
1334            AbstractXmlAdapter.EnumIO<jmri.Meter.Unit> map =
1335                    new AbstractXmlAdapter.EnumIoNames<>(jmri.Meter.Unit.class);
1336            return (map.inputFromString(us));
1337        } else {
1338            log.error("MeterReply Parser called on non-MeterReply message type '{}' message '{}'", this.getOpCodeChar(),
1339                    this.toString());
1340            return (jmri.Meter.Unit.NoPrefix);
1341        }
1342    }
1343
1344    public double getMeterMinValue() {
1345        if (this.isMeterReply()) {
1346            return (this.getValueDouble(5));
1347        } else {
1348            log.error("MeterReply Parser called on non-MeterReply message type '{}' message '{}'", this.getOpCodeChar(),
1349                    this.toString());
1350            return (0.0);
1351        }
1352    }
1353
1354    public double getMeterMaxValue() {
1355        if (this.isMeterReply()) {
1356            return (this.getValueDouble(6));
1357        } else {
1358            log.error("MeterReply Parser called on non-MeterReply message type '{}' message '{}'", this.getOpCodeChar(),
1359                    this.toString());
1360            return (0.0);
1361        }
1362    }
1363
1364    public double getMeterResolution() {
1365        if (this.isMeterReply()) {
1366            return (this.getValueDouble(7));
1367        } else {
1368            log.error("MeterReply Parser called on non-MeterReply message type '{}' message '{}'", this.getOpCodeChar(),
1369                    this.toString());
1370            return (0.0);
1371        }
1372    }
1373
1374    public double getMeterWarnValue() {
1375        if (this.isMeterReply()) {
1376            return (this.getValueDouble(8));
1377        } else {
1378            log.error("MeterReply Parser called on non-MeterReply message type '{}' message '{}'", this.getOpCodeChar(),
1379                    this.toString());
1380            return (0.0);
1381        }
1382    }
1383
1384    public boolean isMeterTypeVolt() {
1385        if (this.isMeterReply()) {
1386            return (this.getMeterType().equals(DCCppConstants.VOLTAGE));
1387        } else {
1388            log.error("MeterReply Parser called on non-MeterReply message type '{}' message '{}'", this.getOpCodeChar(),
1389                    this.toString());
1390            return (false);
1391        }
1392    }
1393
1394    public boolean isMeterTypeCurrent() {
1395        if (this.isMeterReply()) {
1396            return (this.getMeterType().equals(DCCppConstants.CURRENT));
1397        } else {
1398            log.error("MeterReply Parser called on non-MeterReply message type '{}' message '{}'", this.getOpCodeChar(),
1399                    this.toString());
1400            return (false);
1401        }
1402    }
1403
1404    public boolean getPowerBool() {
1405        if (this.isPowerReply()) {
1406            return (this.getValueString(1).equals(DCCppConstants.POWER_ON));
1407        } else {
1408            log.error("PowerReply Parser called on non-PowerReply message type {} message {}", this.getOpCodeChar(),
1409                    this.toString());
1410            return (false);
1411        }
1412    }
1413
1414    public String getTurnoutDefNumString() {
1415        if (this.isTurnoutDefReply() || this.isTurnoutDefDCCReply()) {
1416            return (this.getValueString(1));
1417        } else {
1418            log.error("TurnoutDefReply Parser called on non-TurnoutDefReply message type {}", this.getOpCodeChar());
1419            return ("0");
1420        }
1421    }
1422
1423    public int getTurnoutDefNumInt() {
1424        return (Integer.parseInt(this.getTurnoutDefNumString()));
1425    }
1426
1427    public String getTurnoutDefAddrString() {
1428        if (this.isTurnoutDefReply() || this.isTurnoutDefDCCReply()) {
1429            return (this.getValueString(2));
1430        } else {
1431            log.error("TurnoutDefReply Parser called on non-TurnoutDefReply message type {}", this.getOpCodeChar());
1432            return ("0");
1433        }
1434    }
1435
1436    public int getTurnoutDefAddrInt() {
1437        return (Integer.parseInt(this.getTurnoutDefAddrString()));
1438    }
1439
1440    public String getTurnoutDefSubAddrString() {
1441        if (this.isTurnoutDefReply() || this.isTurnoutDefDCCReply()) {
1442            return (this.getValueString(3));
1443        } else {
1444            log.error("TurnoutDefReply Parser called on non-TurnoutDefReply message type {}", this.getOpCodeChar());
1445            return ("0");
1446        }
1447    }
1448
1449    public int getTurnoutDefSubAddrInt() {
1450        return (Integer.parseInt(this.getTurnoutDefSubAddrString()));
1451    }
1452
1453    public String getOutputNumString() {
1454        if (this.isOutputDefReply() || this.isOutputCmdReply()) {
1455            return (this.getValueString(1));
1456        } else {
1457            log.error("OutputAddReply Parser called on non-OutputAddReply message type {}", this.getOpCodeChar());
1458            return ("0");
1459        }
1460    }
1461
1462    public int getOutputNumInt() {
1463        return (Integer.parseInt(this.getOutputNumString()));
1464    }
1465
1466    public String getOutputListPinString() {
1467        if (this.isOutputDefReply()) {
1468            return (this.getValueString(2));
1469        } else {
1470            log.error("OutputAddReply Parser called on non-OutputAddReply message type {}", this.getOpCodeChar());
1471            return ("0");
1472        }
1473    }
1474
1475    public int getOutputListPinInt() {
1476        return (Integer.parseInt(this.getOutputListPinString()));
1477    }
1478
1479    public String getOutputListIFlagString() {
1480        if (this.isOutputDefReply()) {
1481            return (this.getValueString(3));
1482        } else {
1483            log.error("OutputListReply Parser called on non-OutputListReply message type {}", this.getOpCodeChar());
1484            return ("0");
1485        }
1486    }
1487
1488    public int getOutputListIFlagInt() {
1489        return (Integer.parseInt(this.getOutputListIFlagString()));
1490    }
1491
1492    public String getOutputListStateString() {
1493        if (this.isOutputDefReply()) {
1494            return (this.getValueString(4));
1495        } else {
1496            log.error("OutputListReply Parser called on non-OutputListReply message type {}", this.getOpCodeChar());
1497            return ("0");
1498        }
1499    }
1500
1501    public int getOutputListStateInt() {
1502        return (Integer.parseInt(this.getOutputListStateString()));
1503    }
1504
1505    public boolean getOutputCmdStateBool() {
1506        if (this.isOutputCmdReply()) {
1507            return ((this.getValueBool(2)));
1508        } else {
1509            log.error("OutputCmdReply Parser called on non-OutputCmdReply message type {}", this.getOpCodeChar());
1510            return (false);
1511        }
1512    }
1513
1514    public String getOutputCmdStateString() {
1515        if (this.isOutputCmdReply()) {
1516            return (this.getValueBool(2) ? "HIGH" : "LOW");
1517        } else {
1518            log.error("OutputCmdReply Parser called on non-OutputCmdReply message type {}", this.getOpCodeChar());
1519            return ("0");
1520        }
1521    }
1522
1523    public int getOutputCmdStateInt() {
1524        return (this.getOutputCmdStateBool() ? 1 : 0);
1525    }
1526
1527    public boolean getOutputIsHigh() {
1528        return (this.getOutputCmdStateBool());
1529    }
1530
1531    public boolean getOutputIsLow() {
1532        return (!this.getOutputCmdStateBool());
1533    }
1534
1535    public String getSensorDefNumString() {
1536        if (this.isSensorDefReply()) {
1537            return (this.getValueString(1));
1538        } else {
1539            log.error("SensorDefReply Parser called on non-SensorDefReply message type {}", this.getOpCodeChar());
1540            return ("0");
1541        }
1542    }
1543
1544    public int getSensorDefNumInt() {
1545        return (Integer.parseInt(this.getSensorDefNumString()));
1546    }
1547
1548    public String getSensorDefPinString() {
1549        if (this.isSensorDefReply()) {
1550            return (this.getValueString(2));
1551        } else {
1552            log.error("SensorDefReply Parser called on non-SensorDefReply message type {}", this.getOpCodeChar());
1553            return ("0");
1554        }
1555    }
1556
1557    public int getSensorDefPinInt() {
1558        return (Integer.parseInt(this.getSensorDefPinString()));
1559    }
1560
1561    public String getSensorDefPullupString() {
1562        if (this.isSensorDefReply()) {
1563            return (this.getSensorDefPullupBool() ? "Pullup" : "NoPullup");
1564        } else {
1565            log.error("SensorDefReply Parser called on non-SensorDefReply message type {}", this.getOpCodeChar());
1566            return ("Not a Sensor");
1567        }
1568    }
1569
1570    public int getSensorDefPullupInt() {
1571        if (this.isSensorDefReply()) {
1572            return (this.getValueInt(3));
1573        } else {
1574            log.error("SensorDefReply Parser called on non-SensorDefReply message type {}", this.getOpCodeChar());
1575            return (0);
1576        }
1577    }
1578
1579    public boolean getSensorDefPullupBool() {
1580        if (this.isSensorDefReply()) {
1581            return (this.getValueBool(3));
1582        } else {
1583            log.error("SensorDefReply Parser called on non-SensorDefReply message type {}", this.getOpCodeChar());
1584            return (false);
1585        }
1586    }
1587
1588    public String getSensorNumString() {
1589        if (this.isSensorReply()) {
1590            return (this.getValueString(1));
1591        } else {
1592            log.error("SensorReply Parser called on non-SensorReply message type {}", this.getOpCodeChar());
1593            return ("0");
1594        }
1595    }
1596
1597    public int getSensorNumInt() {
1598        return (Integer.parseInt(this.getSensorNumString()));
1599    }
1600
1601    public String getSensorStateString() {
1602        if (this.isSensorReply()) {
1603            return (this.myRegex.equals(DCCppConstants.SENSOR_ACTIVE_REPLY_REGEX) ? "Active" : "Inactive");
1604        } else {
1605            log.error("SensorReply Parser called on non-SensorReply message type {}", this.getOpCodeChar());
1606            return ("Not a Sensor");
1607        }
1608    }
1609
1610    public int getSensorStateInt() {
1611        if (this.isSensorReply()) {
1612            return (this.myRegex.equals(DCCppConstants.SENSOR_ACTIVE_REPLY_REGEX) ? 1 : 0);
1613        } else {
1614            log.error("SensorReply Parser called on non-SensorReply message type {}", this.getOpCodeChar());
1615            return (0);
1616        }
1617    }
1618
1619    public boolean getSensorIsActive() {
1620        return (this.myRegex.equals(DCCppConstants.SENSOR_ACTIVE_REPLY_REGEX));
1621    }
1622
1623    public boolean getSensorIsInactive() {
1624        return (this.myRegex.equals(DCCppConstants.SENSOR_INACTIVE_REPLY_REGEX));
1625    }
1626
1627    public int getCommTypeInt() {
1628        if (this.isCommTypeReply()) {
1629            return (this.getValueInt(1));
1630        } else {
1631            log.error("CommTypeReply Parser called on non-CommTypeReply message type {}", this.getOpCodeChar());
1632            return (0);
1633        }
1634    }
1635
1636    public String getCommTypeValueString() {
1637        if (this.isCommTypeReply()) {
1638            return (this.getValueString(2));
1639        } else {
1640            log.error("CommTypeReply Parser called on non-CommTypeReply message type {}", this.getOpCodeChar());
1641            return ("N/A");
1642        }
1643    }
1644
1645    public ArrayList<Integer> getTurnoutIDList() {
1646        ArrayList<Integer> ids=new ArrayList<Integer>();
1647        if (this.isTurnoutIDsReply()) {
1648            String idList = this.getValueString(1);
1649            if (!idList.isEmpty()) {
1650                String[] idStrings = idList.split(" ");
1651                for (String idString : idStrings) {
1652                    try {
1653                        int id = Integer.parseInt(idString);
1654                        if (id >= 1 && id <= DCCppConstants.MAX_TURNOUT_ADDRESS) {
1655                            ids.add(id);
1656                        } else {
1657                            // DCC-EX WiFi buffer overflow (Mega+WiFi) can corrupt the jT reply, producing out-of-range IDs
1658                            log.warn("Ignoring out-of-range turnout ID {} in jT response (buffer overflow?)", id);
1659                        }
1660                    } catch (NumberFormatException e) {
1661                        log.warn("Ignoring malformed token '{}' in jT response", idString);
1662                    }
1663                }
1664            }
1665        } else {
1666            log.error("TurnoutIDsReply Parser called on non-TurnoutIDsReply message type {}", this.getOpCodeChar());
1667        }
1668        return ids;
1669    }
1670    public String getTurnoutStateString() {
1671        if (this.isTurnoutIDReply()) {
1672            return (this.getValueString(2));
1673        } else {
1674            log.error("getTurnoutStateString Parser called on non-TurnoutID message type {}", this.getOpCodeChar());
1675            return ("0");
1676        }
1677    }
1678    public String getTurnoutDescString() {
1679        if (this.isTurnoutIDReply()) {
1680            return (this.getValueString(3));
1681        } else {
1682            log.error("getTurnoutDescString Parser called on non-TurnoutID message type {}", this.getOpCodeChar());
1683            return ("0");
1684        }
1685    }
1686    public String getRosterDescString() {
1687        if (this.isRosterIDReply()) {
1688            return (this.getValueString(2));
1689        } else {
1690            log.error("getRosterDescString called on non-RosterIDReply message type {}", this.getOpCodeChar());
1691            return ("");
1692        }
1693    }
1694    public String getRosterFKeysString() {
1695        if (this.isRosterIDReply()) {
1696            return (this.getValueString(3));
1697        } else {
1698            log.error("getRosterFKeysString called on non-RosterIDReply message type {}", this.getOpCodeChar());
1699            return ("");
1700        }
1701    }
1702    public ArrayList<Integer> getRosterIDList() {
1703        ArrayList<Integer> ids=new ArrayList<Integer>(); 
1704        if (this.isRosterIDsReply()) {
1705            String idList = this.getValueString(1);
1706            if (!idList.isEmpty()) {
1707                String[] idStrings = idList.split(" ");
1708                for (String idString : idStrings) {
1709                    ids.add(Integer.parseInt(idString));
1710                }
1711            }
1712        } else {
1713            log.error("getRosterIDList called on non-RosterIDsReply message type {}", this.getOpCodeChar());
1714        }
1715        return ids;
1716    }
1717
1718    public String getAutomationTypeString() {
1719        if (this.isAutomationIDReply()) {
1720            return (this.getValueString(2));
1721        } else {
1722            log.error("getAutomationTypeString called on non-AutomationIDReply message type {}", this.getOpCodeChar());
1723            return ("");
1724        }
1725    }
1726    public String getAutomationDescString() {
1727        if (this.isAutomationIDReply()) {
1728            return (this.getValueString(3));
1729        } else {
1730            log.error("getAutomationDescString called on non-AutomationIDReply message type {}", this.getOpCodeChar());
1731            return ("");
1732        }
1733    }
1734    public String getAutomationStateString() {
1735        if (this.isAutomationStateReply()) {
1736            return (this.getValueString(2));
1737        } else {
1738            log.error("getAutomationStateString called on non-AutomationStateReply message '{}'", this.toString());
1739            return ("");
1740        }
1741    }
1742    public String getAutomationCaptionString() {
1743        if (this.isAutomationCaptionReply()) {
1744            return (this.getValueString(2));
1745        } else {
1746            log.error("getAutomationCaptionString called on non-AutomationCaption message '{}'", this.toString());
1747            return ("");
1748        }
1749    }
1750    public ArrayList<Integer> getAutomationIDList() {
1751        ArrayList<Integer> ids=new ArrayList<Integer>(); 
1752        if (this.isAutomationIDsReply()) {
1753            String idList = this.getValueString(1);
1754            if (!idList.isEmpty()) {
1755                String[] idStrings = idList.split(" ");
1756                for (String idString : idStrings) {
1757                    ids.add(Integer.parseInt(idString));
1758                }
1759            }
1760        } else {
1761            log.error("getAutomationIDList called on non-AutomationIDsReply message type {}", this.getOpCodeChar());
1762        }
1763        return ids;
1764    }
1765    public ArrayList<Integer> getCurrentMaxesList() {
1766        ArrayList<Integer> cml=new ArrayList<Integer>();
1767        if (this.isCurrentMaxesReply()) {
1768            String sml = this.getValueString(1);
1769            if (!sml.isEmpty()) {
1770                String[] mss = sml.split(" ");
1771                for (String ms : mss) {
1772                    cml.add(Integer.parseInt(ms));
1773                }
1774            }
1775        } else {
1776            log.error("getCurrentMaxesList called on non-CurrentMaxesListReply message type {}", this.getOpCodeChar());
1777        }
1778        return cml;
1779    }
1780    public ArrayList<Integer> getCurrentValuesList() {
1781        ArrayList<Integer> cvl=new ArrayList<Integer>();
1782        if (this.isCurrentValuesReply()) {
1783            String svl = this.getValueString(1);
1784            if (!svl.isEmpty()) {
1785                String[] vss = svl.split(" ");
1786                for (String vs : vss) {
1787                    cvl.add(Integer.parseInt(vs));
1788                }
1789            }
1790        } else {
1791            log.error("getCurrentValuesList called on non-CurrentValuesListReply message type {}", this.getOpCodeChar());
1792        }
1793        return cvl;
1794    }
1795
1796    public char getTrackManagerLetter() {
1797        if (this.isTrackManagerReply()) {
1798            String s = this.getValueString(1);
1799            if (!s.isEmpty()) {                
1800                return (s.charAt(0)); //convert to a char
1801            } else {
1802                return ('0');
1803            }
1804        } else {
1805            log.error("getTrackManagerLetter Parser called on non-TrackManager message type {}", this.getOpCodeChar());
1806            return ('0');
1807        }
1808    }
1809    public String getTrackManagerMode() {
1810        if (this.isTrackManagerReply()) {
1811            return (this.getValueString(2));
1812        } else {
1813            log.error("getTrackManagerMode Parser called on non-TrackManager message type {}", this.getOpCodeChar());
1814            return ("0");
1815        }
1816    }
1817
1818    public String getClockMinutesString() {
1819        if (this.isClockReply()) {
1820            return (this.getValueString(1));
1821        } else {
1822            log.error("getClockTimeString Parser called on non-getClockTimeString message type {}", this.getOpCodeChar());
1823            return ("0");
1824        }
1825    }
1826    public int getClockMinutesInt() {
1827        return (Integer.parseInt(this.getClockMinutesString()));
1828    }
1829    public String getClockRateString() {
1830        if (this.isClockReply()) {
1831            return (this.getValueString(2));
1832        } else {
1833            log.error("getClockRateString Parser called on non-getClockRateString message type {}", this.getOpCodeChar());
1834            return ("0");
1835        }
1836    }
1837    public int getClockRateInt() {
1838        return (Integer.parseInt(this.getClockRateString()));
1839    }
1840
1841    // <@ 0 8 "message text">
1842    public boolean isLCDTextReply() {
1843        return (this.matches(DCCppConstants.LCD_TEXT_REPLY_REGEX));
1844    }   
1845    public String getLCDTextString() {
1846        if (this.isLCDTextReply()) {
1847            return (this.getValueString(3));
1848        } else {
1849            log.error("getLCDTextString Parser called on non-LCDTextString message type {}", this.getOpCodeChar());
1850            return ("error");
1851        }
1852    }
1853    public String getLCDDisplayNumString() {
1854        if (this.isLCDTextReply()) {
1855            return (this.getValueString(1));
1856        } else {
1857            log.error("getLCDDisplayNumString Parser called on non-LCDTextString message type {}", this.getOpCodeChar());
1858            return ("error");
1859        }
1860    }
1861    public int getLCDDisplayNumInt() {
1862        return (Integer.parseInt(this.getLCDDisplayNumString()));
1863    }
1864    public String getLCDLineNumString() {
1865        if (this.isLCDTextReply()) {
1866            return (this.getValueString(2));
1867        } else {
1868            log.error("getLCDLineNumString Parser called on non-LCDTextString message type {}", this.getOpCodeChar());
1869            return ("error");
1870        }
1871    }
1872    public int getLCDLineNumInt() {
1873        return (Integer.parseInt(this.getLCDLineNumString()));
1874    }
1875
1876    // <m "message text">
1877    public boolean isMessageReply() {
1878        return (this.matches(DCCppConstants.MESSAGE_REPLY_REGEX));
1879    }
1880    public String getMessageString() {
1881        if (this.isMessageReply()) {
1882            return (this.getValueString(1));
1883        } else {
1884            log.error("getMessageString Parser called on non-Message message type {}", this.getOpCodeChar());
1885            return ("error");
1886        }
1887    }
1888
1889    // -------------------------------------------------------------------
1890
1891    // Message Identification functions
1892    public boolean isThrottleReply() {
1893        return (this.getOpCodeChar() == DCCppConstants.THROTTLE_REPLY);
1894    }
1895
1896    public boolean isTurnoutReply() {
1897        return (this.getOpCodeChar() == DCCppConstants.TURNOUT_REPLY);
1898    }
1899
1900    public boolean isTurnoutCmdReply() {
1901        return (this.matches(DCCppConstants.TURNOUT_REPLY_REGEX));
1902    }
1903
1904    public boolean isProgramReply() {
1905        return (this.matches(DCCppConstants.PROGRAM_REPLY_REGEX));
1906    }
1907
1908    public boolean isProgramReplyV4() {
1909        return (this.matches(DCCppConstants.PROGRAM_REPLY_V4_REGEX));
1910    }
1911
1912    public boolean isProgramLocoIdReply() {
1913        return (this.matches(DCCppConstants.PROGRAM_LOCOID_REPLY_REGEX));
1914    }
1915
1916    public boolean isVerifyReply() {
1917        return (this.matches(DCCppConstants.PROGRAM_VERIFY_REPLY_REGEX));
1918    }
1919
1920    public boolean isProgramBitReply() {
1921        return (this.matches(DCCppConstants.PROGRAM_BIT_REPLY_REGEX));
1922    }
1923
1924    public boolean isProgramBitReplyV4() {
1925        return (this.matches(DCCppConstants.PROGRAM_BIT_REPLY_V4_REGEX));
1926    }
1927
1928    public boolean isPowerReply() {
1929        return (this.getOpCodeChar() == DCCppConstants.POWER_REPLY);
1930    }
1931
1932    public boolean isNamedPowerReply() {
1933        return (this.matches(DCCppConstants.TRACK_POWER_REPLY_NAMED_REGEX));
1934    }
1935
1936    public boolean isMaxNumSlotsReply() {
1937        return (this.matches(DCCppConstants.MAXNUMSLOTS_REPLY_REGEX));
1938    }
1939
1940    public boolean isDiagReply() {
1941        return (this.matches(DCCppConstants.DIAG_REPLY_REGEX));
1942    }
1943
1944    public boolean isCurrentReply() {
1945        return (this.getOpCodeChar() == DCCppConstants.CURRENT_REPLY);
1946    }
1947
1948    public boolean isNamedCurrentReply() {
1949        return (this.matches(DCCppConstants.CURRENT_REPLY_NAMED_REGEX));
1950    }
1951
1952    public boolean isMeterReply() {
1953        return (this.matches(DCCppConstants.METER_REPLY_REGEX));
1954    }
1955
1956    public boolean isSensorReply() {
1957        return ((this.getOpCodeChar() == DCCppConstants.SENSOR_REPLY) ||
1958                (this.getOpCodeChar() == DCCppConstants.SENSOR_REPLY_H) ||
1959                (this.getOpCodeChar() == DCCppConstants.SENSOR_REPLY_L));
1960    }
1961
1962    public boolean isSensorDefReply() {
1963        return (this.matches(DCCppConstants.SENSOR_DEF_REPLY_REGEX));
1964    }
1965
1966    public boolean isTurnoutDefReply() {
1967        return (this.matches(DCCppConstants.TURNOUT_DEF_REPLY_REGEX));
1968    }
1969
1970    public boolean isTurnoutDefDCCReply() {
1971        return (this.matches(DCCppConstants.TURNOUT_DEF_DCC_REPLY_REGEX));
1972    }
1973
1974    public boolean isTurnoutDefServoReply() {
1975        return (this.matches(DCCppConstants.TURNOUT_DEF_SERVO_REPLY_REGEX));
1976    }
1977
1978    public boolean isTurnoutDefVpinReply() {
1979        return (this.matches(DCCppConstants.TURNOUT_DEF_VPIN_REPLY_REGEX));
1980    }
1981
1982    public boolean isTurnoutDefLCNReply() {
1983        return (this.matches(DCCppConstants.TURNOUT_DEF_LCN_REPLY_REGEX));
1984    }
1985
1986    public boolean isMADCFailReply() {
1987        return (this.getOpCodeChar() == DCCppConstants.MADC_FAIL_REPLY);
1988    }
1989
1990    public boolean isMADCSuccessReply() {
1991        return (this.getOpCodeChar() == DCCppConstants.MADC_SUCCESS_REPLY);
1992    }
1993
1994    public boolean isStatusReply() {
1995        return (this.getOpCodeChar() == DCCppConstants.STATUS_REPLY);
1996    }
1997
1998    public boolean isOutputReply() {
1999        return (this.getOpCodeChar() == DCCppConstants.OUTPUT_REPLY);
2000    }
2001
2002    public boolean isOutputDefReply() {
2003        return (this.matches(DCCppConstants.OUTPUT_DEF_REPLY_REGEX));
2004    }
2005
2006    public boolean isOutputCmdReply() {
2007        return (this.matches(DCCppConstants.OUTPUT_REPLY_REGEX));
2008    }
2009
2010    public boolean isCommTypeReply() {
2011        return (this.matches(DCCppConstants.COMM_TYPE_REPLY_REGEX));
2012    }
2013
2014    public boolean isWriteEepromReply() {
2015        return (this.matches(DCCppConstants.WRITE_EEPROM_REPLY_REGEX));
2016    }
2017
2018    public boolean isLocoStateReply() {
2019        return (this.getOpCodeChar() == DCCppConstants.LOCO_STATE_REPLY);
2020    }
2021    
2022    public boolean isTurnoutIDsReply() {
2023        return (this.matches(DCCppConstants.TURNOUT_IDS_REPLY_REGEX));
2024    }
2025    public boolean isTurnoutIDReply() {
2026        return (this.matches(DCCppConstants.TURNOUT_ID_REPLY_REGEX));
2027    }
2028    public boolean isRosterIDsReply() {
2029        return (this.matches(DCCppConstants.ROSTER_IDS_REPLY_REGEX));
2030    }
2031    public boolean isRosterIDReply() {
2032        return (this.matches(DCCppConstants.ROSTER_ID_REPLY_REGEX));
2033    }
2034    public boolean isAutomationIDsReply() {
2035        return (this.matches(DCCppConstants.AUTOMATION_IDS_REPLY_REGEX));
2036    }
2037    public boolean isAutomationIDReply() {
2038        return (this.matches(DCCppConstants.AUTOMATION_ID_REPLY_REGEX));
2039    }
2040    public boolean isAutomationStateReply() {
2041        return (this.matches(DCCppConstants.AUTOMATION_STATE_REPLY_REGEX));
2042    }
2043    public boolean isAutomationCaptionReply() {
2044        return (this.matches(DCCppConstants.AUTOMATION_CAPTION_REPLY_REGEX));
2045    }
2046    public boolean isCurrentMaxesReply() {
2047        return (this.matches(DCCppConstants.CURRENT_MAXES_REPLY_REGEX));
2048    }
2049    public boolean isCurrentValuesReply() {
2050        return (this.matches(DCCppConstants.CURRENT_VALUES_REPLY_REGEX));
2051    }
2052    public boolean isClockReply() {
2053        return (this.matches(DCCppConstants.CLOCK_REPLY_REGEX));
2054    }
2055
2056    public boolean isTrackManagerReply() {
2057        return (this.matches(DCCppConstants.TRACKMANAGER_REPLY_REGEX));
2058    }
2059
2060    public boolean isValidReplyFormat() {
2061        if ((this.matches(DCCppConstants.THROTTLE_REPLY_REGEX)) ||
2062                (this.matches(DCCppConstants.MAXNUMSLOTS_REPLY_REGEX)) ||
2063                (this.matches(DCCppConstants.TURNOUT_REPLY_REGEX)) ||
2064                (this.matches(DCCppConstants.PROGRAM_REPLY_REGEX)) ||
2065                (this.matches(DCCppConstants.PROGRAM_REPLY_V4_REGEX)) ||
2066                (this.matches(DCCppConstants.PROGRAM_LOCOID_REPLY_REGEX)) ||
2067                (this.matches(DCCppConstants.PROGRAM_VERIFY_REPLY_REGEX)) ||
2068                (this.matches(DCCppConstants.TRACK_POWER_REPLY_REGEX)) ||
2069                (this.matches(DCCppConstants.TRACK_POWER_REPLY_NAMED_REGEX)) ||
2070                (this.matches(DCCppConstants.CURRENT_REPLY_REGEX)) ||
2071                (this.matches(DCCppConstants.CURRENT_REPLY_NAMED_REGEX)) ||
2072                (this.matches(DCCppConstants.METER_REPLY_REGEX)) ||
2073                (this.matches(DCCppConstants.SENSOR_REPLY_REGEX)) ||
2074                (this.matches(DCCppConstants.SENSOR_DEF_REPLY_REGEX)) ||
2075                (this.matches(DCCppConstants.SENSOR_INACTIVE_REPLY_REGEX)) ||
2076                (this.matches(DCCppConstants.SENSOR_ACTIVE_REPLY_REGEX)) ||
2077                (this.matches(DCCppConstants.OUTPUT_REPLY_REGEX)) ||
2078                (this.matches(DCCppConstants.OUTPUT_DEF_REPLY_REGEX)) ||
2079                (this.matches(DCCppConstants.MADC_FAIL_REPLY_REGEX)) ||
2080                (this.matches(DCCppConstants.MADC_SUCCESS_REPLY_REGEX)) ||
2081                (this.matches(DCCppConstants.STATUS_REPLY_REGEX)) ||
2082                (this.matches(DCCppConstants.STATUS_REPLY_BSC_REGEX)) ||
2083                (this.matches(DCCppConstants.STATUS_REPLY_ESP32_REGEX)) ||
2084                (this.matches(DCCppConstants.STATUS_REPLY_DCCEX_REGEX)) ||
2085                (this.matches(DCCppConstants.LOCO_STATE_REGEX)) ||
2086                (this.matches(DCCppConstants.TURNOUT_IDS_REPLY_REGEX)) ||
2087                (this.matches(DCCppConstants.TURNOUT_ID_REPLY_REGEX)) ||
2088                (this.matches(DCCppConstants.ROSTER_IDS_REPLY_REGEX)) ||
2089                (this.matches(DCCppConstants.ROSTER_ID_REPLY_REGEX)) ||
2090                (this.matches(DCCppConstants.AUTOMATION_IDS_REPLY_REGEX)) ||
2091                (this.matches(DCCppConstants.AUTOMATION_ID_REPLY_REGEX)) ||
2092                (this.matches(DCCppConstants.AUTOMATION_STATE_REPLY_REGEX)) ||
2093                (this.matches(DCCppConstants.AUTOMATION_CAPTION_REPLY_REGEX)) ||
2094                (this.matches(DCCppConstants.CURRENT_MAXES_REPLY_REGEX)) ||
2095                (this.matches(DCCppConstants.CURRENT_VALUES_REPLY_REGEX)) ||
2096                (this.matches(DCCppConstants.TURNOUT_IMPL_REGEX)) ||
2097                (this.matches(DCCppConstants.TURNOUT_DEF_REPLY_REGEX)) ||
2098                (this.matches(DCCppConstants.TURNOUT_DEF_DCC_REPLY_REGEX)) ||
2099                (this.matches(DCCppConstants.TURNOUT_DEF_SERVO_REPLY_REGEX)) ||
2100                (this.matches(DCCppConstants.TURNOUT_DEF_VPIN_REPLY_REGEX)) ||
2101                (this.matches(DCCppConstants.TURNOUT_DEF_LCN_REPLY_REGEX)) ||
2102                (this.matches(DCCppConstants.LCD_TEXT_REPLY_REGEX)) ||
2103                (this.matches(DCCppConstants.MESSAGE_REPLY_REGEX)) ||
2104                (this.matches(DCCppConstants.CLOCK_REPLY_REGEX)) ||
2105                (this.matches(DCCppConstants.DIAG_REPLY_REGEX)) ||
2106                (this.matches(DCCppConstants.TRACKMANAGER_REPLY_REGEX))) {
2107            return (true);
2108        } else {
2109            return (false);
2110        }
2111    }
2112
2113    // initialize logging
2114    private static final Logger log = LoggerFactory.getLogger(DCCppReply.class);
2115
2116}