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