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