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