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