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