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