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