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