001package jmri.jmrix.dccpp; 002 003import java.util.concurrent.Delayed; 004import java.util.concurrent.TimeUnit; 005import java.util.regex.Matcher; 006import java.util.regex.Pattern; 007import java.util.regex.PatternSyntaxException; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011import javax.annotation.CheckForNull; 012import javax.annotation.Nonnull; 013 014/** 015 * Represents a single command or response on the DCC++. 016 * <p> 017 * Content is represented with ints to avoid the problems with sign-extension 018 * that bytes have, and because a Java char is actually a variable number of 019 * bytes in Unicode. 020 * 021 * @author Bob Jacobsen Copyright (C) 2002 022 * @author Paul Bender Copyright (C) 2003-2010 023 * @author Mark Underwood Copyright (C) 2015 024 * @author Costin Grigoras Copyright (C) 2018 025 * @author Harald Barth Copyright (C) 2019 026 * 027 * Based on XNetMessage by Bob Jacobsen and Paul Bender 028 */ 029 030/* 031 * A few words on implementation: 032 * 033 * DCCppMessage objects are (usually) created by calling one of the static makeMessageType() 034 * methods, and are then consumed by the TrafficController/Packetizer by being converted to 035 * a String and sent out the port. 036 * <p> 037 * Internally the DCCppMessage is actually stored as a String, and alongside that is kept 038 * a Regex for easy extraction of the values where needed in the code. 039 * <p> 040 * The various getParameter() type functions are mainly for convenience in places such as the 041 * port monitor where we want to be able to extract the /meaning/ of the DCCppMessage and 042 * present it in a human readable form. Using the getParameterType() methods insulates 043 * the higher level code from needing to know what order/format the actual message is 044 * in. 045 */ 046public class DCCppMessage extends jmri.jmrix.AbstractMRMessage implements Delayed { 047 048 private static int _nRetries = 3; 049 050 /* According to the specification, DCC++ has a maximum timing 051 interval of 500 milliseconds during normal communications */ 052 protected static final int DCCppProgrammingTimeout = 10000; // TODO: Appropriate value for DCC++? 053 private static int DCCppMessageTimeout = 5000; // TODO: Appropriate value for DCC++? 054 055 //private ArrayList<Integer> valueList = new ArrayList<>(); 056 private StringBuilder myMessage; 057 private String myRegex; 058 private char opcode; 059 060 /** 061 * Create a new object, representing a specific-length message. 062 * 063 * @param len Total bytes in message, including opcode and error-detection 064 * byte. 065 */ 066 //NOTE: Not used anywhere useful... consider removing. 067 public DCCppMessage(int len) { 068 super(len); 069 setBinary(false); 070 setRetries(_nRetries); 071 setTimeout(DCCppMessageTimeout); 072 if (len > DCCppConstants.MAX_MESSAGE_SIZE || len < 0) { 073 log.error("Invalid length in ctor: {}", len); 074 } 075 _nDataChars = len; 076 myRegex = ""; 077 myMessage = new StringBuilder(len); 078 } 079 080 /** 081 * Create a new object, that is a copy of an existing message. 082 * 083 * @param message existing message. 084 */ 085 public DCCppMessage(DCCppMessage message) { 086 super(message); 087 setBinary(false); 088 setRetries(_nRetries); 089 setTimeout(DCCppMessageTimeout); 090 myRegex = message.myRegex; 091 myMessage = message.myMessage; 092 toStringCache = message.toStringCache; 093 } 094 095 /** 096 * Create an DCCppMessage from an DCCppReply. 097 * Not used. Really, not even possible. Consider removing. 098 * @param message existing reply to replicate. 099 */ 100 public DCCppMessage(DCCppReply message) { 101 super(message.getNumDataElements()); 102 setBinary(false); 103 setRetries(_nRetries); 104 setTimeout(DCCppMessageTimeout); 105 for (int i = 0; i < message.getNumDataElements(); i++) { 106 setElement(i, message.getElement(i)); 107 } 108 } 109 110 /** 111 * Create a DCCppMessage from a String containing bytes. 112 * <p> 113 * Since DCCppMessages are text, there is no Hex-to-byte conversion. 114 * <p> 115 * NOTE 15-Feb-17: un-Deprecating this function so that it can be used in 116 * the DCCppOverTCP server/client interface. 117 * Messages shouldn't be parsed, they are already in DCC++ format, 118 * so we need the string constructor to generate a DCCppMessage from 119 * the incoming byte stream. 120 * @param s message in string form. 121 */ 122 public DCCppMessage(String s) { 123 setBinary(false); 124 setRetries(_nRetries); 125 setTimeout(DCCppMessageTimeout); 126 myMessage = new StringBuilder(s); // yes, copy... or... maybe not. 127 toStringCache = s; 128 // gather bytes in result 129 setRegex(); 130 _nDataChars = myMessage.length(); 131 _dataChars = new int[_nDataChars]; 132 } 133 134 // Partial constructor used in the static getMessageType() calls below. 135 protected DCCppMessage(char c) { 136 setBinary(false); 137 setRetries(_nRetries); 138 setTimeout(DCCppMessageTimeout); 139 opcode = c; 140 myMessage = new StringBuilder(Character.toString(c)); 141 _nDataChars = myMessage.length(); 142 } 143 144 protected DCCppMessage(char c, String regex) { 145 setBinary(false); 146 setRetries(_nRetries); 147 setTimeout(DCCppMessageTimeout); 148 opcode = c; 149 myRegex = regex; 150 myMessage = new StringBuilder(Character.toString(c)); 151 _nDataChars = myMessage.length(); 152 } 153 154 private void setRegex() { 155 switch (myMessage.charAt(0)) { 156 case DCCppConstants.THROTTLE_CMD: 157 if ((match(toString(), DCCppConstants.THROTTLE_CMD_REGEX, "ctor")) != null) { 158 myRegex = DCCppConstants.THROTTLE_CMD_REGEX; 159 } else if ((match(toString(), DCCppConstants.THROTTLE_V3_CMD_REGEX, "ctor")) != null) { 160 myRegex = DCCppConstants.THROTTLE_V3_CMD_REGEX; 161 } 162 break; 163 case DCCppConstants.FUNCTION_CMD: 164 myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 165 break; 166 case DCCppConstants.FUNCTION_V4_CMD: 167 myRegex = DCCppConstants.FUNCTION_V4_CMD_REGEX; 168 break; 169 case DCCppConstants.FORGET_CAB_CMD: 170 myRegex = DCCppConstants.FORGET_CAB_CMD_REGEX; 171 break; 172 case DCCppConstants.ACCESSORY_CMD: 173 myRegex = DCCppConstants.ACCESSORY_CMD_REGEX; 174 break; 175 case DCCppConstants.TURNOUT_CMD: 176 if ((match(toString(), DCCppConstants.TURNOUT_ADD_REGEX, "ctor")) != null) { 177 myRegex = DCCppConstants.TURNOUT_ADD_REGEX; 178 } else if ((match(toString(), DCCppConstants.TURNOUT_ADD_DCC_REGEX, "ctor")) != null) { 179 myRegex = DCCppConstants.TURNOUT_ADD_DCC_REGEX; 180 } else if ((match(toString(), DCCppConstants.TURNOUT_ADD_SERVO_REGEX, "ctor")) != null) { 181 myRegex = DCCppConstants.TURNOUT_ADD_SERVO_REGEX; 182 } else if ((match(toString(), DCCppConstants.TURNOUT_ADD_VPIN_REGEX, "ctor")) != null) { 183 myRegex = DCCppConstants.TURNOUT_ADD_VPIN_REGEX; 184 } else if ((match(toString(), DCCppConstants.TURNOUT_DELETE_REGEX, "ctor")) != null) { 185 myRegex = DCCppConstants.TURNOUT_DELETE_REGEX; 186 } else if ((match(toString(), DCCppConstants.TURNOUT_LIST_REGEX, "ctor")) != null) { 187 myRegex = DCCppConstants.TURNOUT_LIST_REGEX; 188 } else if ((match(toString(), DCCppConstants.TURNOUT_CMD_REGEX, "ctor")) != null) { 189 myRegex = DCCppConstants.TURNOUT_CMD_REGEX; 190 } else if ((match(toString(), DCCppConstants.TURNOUT_IMPL_REGEX, "ctor")) != null) { 191 myRegex = DCCppConstants.TURNOUT_IMPL_REGEX; 192 } else { 193 myRegex = ""; 194 } 195 break; 196 case DCCppConstants.SENSOR_CMD: 197 if ((match(toString(), DCCppConstants.SENSOR_ADD_REGEX, "ctor")) != null) { 198 myRegex = DCCppConstants.SENSOR_ADD_REGEX; 199 } else if ((match(toString(), DCCppConstants.SENSOR_DELETE_REGEX, "ctor")) != null) { 200 myRegex = DCCppConstants.SENSOR_DELETE_REGEX; 201 } else if ((match(toString(), DCCppConstants.SENSOR_LIST_REGEX, "ctor")) != null) { 202 myRegex = DCCppConstants.SENSOR_LIST_REGEX; 203 } else { 204 myRegex = ""; 205 } 206 break; 207 case DCCppConstants.OUTPUT_CMD: 208 if ((match(toString(), DCCppConstants.OUTPUT_ADD_REGEX, "ctor")) != null) { 209 myRegex = DCCppConstants.OUTPUT_ADD_REGEX; 210 } else if ((match(toString(), DCCppConstants.OUTPUT_DELETE_REGEX, "ctor")) != null) { 211 myRegex = DCCppConstants.OUTPUT_DELETE_REGEX; 212 } else if ((match(toString(), DCCppConstants.OUTPUT_LIST_REGEX, "ctor")) != null) { 213 myRegex = DCCppConstants.OUTPUT_LIST_REGEX; 214 } else if ((match(toString(), DCCppConstants.OUTPUT_CMD_REGEX, "ctor")) != null) { 215 myRegex = DCCppConstants.OUTPUT_CMD_REGEX; 216 } else { 217 myRegex = ""; 218 } 219 break; 220 case DCCppConstants.OPS_WRITE_CV_BYTE: 221 if ((match(toString(), DCCppConstants.PROG_WRITE_BYTE_V4_REGEX, "ctor")) != null) { 222 myRegex = DCCppConstants.PROG_WRITE_BYTE_V4_REGEX; 223 } else { 224 myRegex = DCCppConstants.OPS_WRITE_BYTE_REGEX; 225 } 226 break; 227 case DCCppConstants.OPS_WRITE_CV_BIT: 228 myRegex = DCCppConstants.OPS_WRITE_BIT_REGEX; 229 break; 230 case DCCppConstants.PROG_WRITE_CV_BYTE: 231 myRegex = DCCppConstants.PROG_WRITE_BYTE_REGEX; 232 break; 233 case DCCppConstants.PROG_WRITE_CV_BIT: 234 if ((match(toString(), DCCppConstants.PROG_WRITE_BIT_V4_REGEX, "ctor")) != null) { 235 myRegex = DCCppConstants.PROG_WRITE_BIT_V4_REGEX; 236 } else { 237 myRegex = DCCppConstants.PROG_WRITE_BIT_REGEX; 238 } 239 break; 240 case DCCppConstants.PROG_READ_CV: 241 if ((match(toString(), DCCppConstants.PROG_READ_CV_REGEX, "ctor")) != null) { //match from longest to shortest 242 myRegex = DCCppConstants.PROG_READ_CV_REGEX; 243 } else if ((match(toString(), DCCppConstants.PROG_READ_CV_V4_REGEX, "ctor")) != null) { 244 myRegex = DCCppConstants.PROG_READ_CV_V4_REGEX; 245 } else { 246 myRegex = DCCppConstants.PROG_READ_LOCOID_REGEX; 247 } 248 break; 249 case DCCppConstants.PROG_VERIFY_CV: 250 myRegex = DCCppConstants.PROG_VERIFY_REGEX; 251 break; 252 case DCCppConstants.TRACK_POWER_ON: 253 case DCCppConstants.TRACK_POWER_OFF: 254 myRegex = DCCppConstants.TRACK_POWER_REGEX; 255 break; 256 case DCCppConstants.READ_TRACK_CURRENT: 257 myRegex = DCCppConstants.READ_TRACK_CURRENT_REGEX; 258 break; 259 case DCCppConstants.READ_CS_STATUS: 260 myRegex = DCCppConstants.READ_CS_STATUS_REGEX; 261 break; 262 case DCCppConstants.READ_MAXNUMSLOTS: 263 myRegex = DCCppConstants.READ_MAXNUMSLOTS_REGEX; 264 break; 265 case DCCppConstants.WRITE_TO_EEPROM_CMD: 266 myRegex = DCCppConstants.WRITE_TO_EEPROM_REGEX; 267 break; 268 case DCCppConstants.CLEAR_EEPROM_CMD: 269 myRegex = DCCppConstants.CLEAR_EEPROM_REGEX; 270 break; 271 case DCCppConstants.QUERY_SENSOR_STATES_CMD: 272 myRegex = DCCppConstants.QUERY_SENSOR_STATES_REGEX; 273 break; 274 case DCCppConstants.WRITE_DCC_PACKET_MAIN: 275 myRegex = DCCppConstants.WRITE_DCC_PACKET_MAIN_REGEX; 276 break; 277 case DCCppConstants.WRITE_DCC_PACKET_PROG: 278 myRegex = DCCppConstants.WRITE_DCC_PACKET_PROG_REGEX; 279 break; 280 case DCCppConstants.LIST_REGISTER_CONTENTS: 281 myRegex = DCCppConstants.LIST_REGISTER_CONTENTS_REGEX; 282 break; 283 case DCCppConstants.DIAG_CMD: 284 myRegex = DCCppConstants.DIAG_CMD_REGEX; 285 break; 286 case DCCppConstants.CONTROL_CMD: 287 myRegex = DCCppConstants.CONTROL_CMD_REGEX; 288 break; 289 case DCCppConstants.THROTTLE_COMMANDS: 290 if ((match(toString(), DCCppConstants.TURNOUT_IDS_REGEX, "ctor")) != null) { 291 myRegex = DCCppConstants.TURNOUT_IDS_REGEX; 292 } else if ((match(toString(), DCCppConstants.TURNOUT_ID_REGEX, "ctor")) != null) { 293 myRegex = DCCppConstants.TURNOUT_ID_REGEX; 294 } else if ((match(toString(), DCCppConstants.CLOCK_REQUEST_TIME_REGEX, "ctor")) != null) { //<JC> 295 myRegex = DCCppConstants.CLOCK_REQUEST_TIME_REGEX; 296 } else if ((match(toString(), DCCppConstants.CLOCK_SET_REGEX, "ctor")) != null) { 297 myRegex = DCCppConstants.CLOCK_SET_REGEX; 298 } else { 299 myRegex = ""; 300 } 301 break; 302 case DCCppConstants.TRACKMANAGER_CMD: 303 myRegex = DCCppConstants.TRACKMANAGER_CMD_REGEX; 304 break; 305 default: 306 myRegex = ""; 307 } 308 } 309 310 private String toStringCache = null; 311 312 /** 313 * Converts DCCppMessage to String format (without the {@code <>} brackets) 314 * 315 * @return String form of message. 316 */ 317 @Override 318 public String toString() { 319 if (toStringCache == null) { 320 toStringCache = myMessage.toString(); 321 } 322 323 return toStringCache; 324 /* 325 String s = Character.toString(opcode); 326 for (int i = 0; i < valueList.size(); i++) { 327 s += " "; 328 s += valueList.get(i).toString(); 329 } 330 return(s); 331 */ 332 } 333 334 /** 335 * Generate text translations of messages for use in the DCCpp monitor. 336 * 337 * @return representation of the DCCpp as a string. 338 */ 339 @Override 340 public String toMonitorString() { 341 // Beautify and display 342 String text; 343 344 switch (getOpCodeChar()) { 345 case DCCppConstants.THROTTLE_CMD: 346 if (isThrottleMessage()) { 347 text = "Throttle Cmd: "; 348 text += "Register: " + getRegisterString(); 349 text += ", Address: " + getAddressString(); 350 text += ", Speed: " + getSpeedString(); 351 text += ", Direction: " + getDirectionString(); 352 } else if (isThrottleV3Message()) { 353 text = "Throttle Cmd: "; 354 text += "Address: " + getAddressString(); 355 text += ", Speed: " + getSpeedString(); 356 text += ", Direction: " + getDirectionString(); 357 } else { 358 text = "Invalid syntax: '" + toString() + "'"; 359 } 360 break; 361 case DCCppConstants.FUNCTION_CMD: 362 text = "Function Cmd: "; 363 text += "Address: " + getFuncAddressString(); 364 text += ", Byte 1: " + getFuncByte1String(); 365 text += ", Byte 2: " + getFuncByte2String(); 366 text += ", (No Reply Expected)"; 367 break; 368 case DCCppConstants.FUNCTION_V4_CMD: 369 text = "Function Cmd: "; 370 if (isFunctionV4Message()) { 371 text += "CAB: " + getFuncV4CabString(); 372 text += ", FUNC: " + getFuncV4FuncString(); 373 text += ", State: " + getFuncV4StateString(); 374 } else { 375 text += "Invalid syntax: '" + toString() + "'"; 376 } 377 break; 378 case DCCppConstants.FORGET_CAB_CMD: 379 text = "Forget Cab: "; 380 if (isForgetCabMessage()) { 381 text += "CAB: " + (getForgetCabString().equals("")?"[ALL]":getForgetCabString()); 382 text += ", (No Reply Expected)"; 383 } else { 384 text += "Invalid syntax: '" + toString() + "'"; 385 } 386 break; 387 case DCCppConstants.ACCESSORY_CMD: 388 text = "Accessory Decoder Cmd: "; 389 text += "Address: " + getAccessoryAddrString(); 390 text += ", Subaddr: " + getAccessorySubString(); 391 text += ", State: " + getAccessoryStateString(); 392 break; 393 case DCCppConstants.TURNOUT_CMD: 394 if (isTurnoutAddMessage()) { 395 text = "Add Turnout: "; 396 text += "ID: " + getTOIDString(); 397 text += ", Address: " + getTOAddressString(); 398 text += ", Subaddr: " + getTOSubAddressString(); 399 } else if (isTurnoutAddDCCMessage()) { 400 text = "Add Turnout DCC: "; 401 text += "ID:" + getTOIDString(); 402 text += ", Address:" + getTOAddressString(); 403 text += ", Subaddr:" + getTOSubAddressString(); 404 } else if (isTurnoutAddServoMessage()) { 405 text = "Add Turnout Servo: "; 406 text += "ID:" + getTOIDString(); 407 text += ", Pin:" + getTOPinInt(); 408 text += ", ThrownPos:" + getTOThrownPositionInt(); 409 text += ", ClosedPos:" + getTOClosedPositionInt(); 410 text += ", Profile:" + getTOProfileInt(); 411 } else if (isTurnoutAddVpinMessage()) { 412 text = "Add Turnout Vpin: "; 413 text += "ID:" + getTOIDString(); 414 text += ", Pin:" + getTOPinInt(); 415 } else if (isTurnoutDeleteMessage()) { 416 text = "Delete Turnout: "; 417 text += "ID: " + getTOIDString(); 418 } else if (isListTurnoutsMessage()) { 419 text = "List Turnouts..."; 420 } else if (isTurnoutCmdMessage()) { 421 text = "Turnout Cmd: "; 422 text += "ID: " + getTOIDString(); 423 text += ", State: " + getTOStateString(); 424 } else if (isTurnoutImplementationMessage()) { 425 text = "Request implementation for Turnout "; 426 text += getTOIDString(); 427 } else { 428 text = "Unmatched Turnout Cmd: " + toString(); 429 } 430 break; 431 case DCCppConstants.OUTPUT_CMD: 432 if (isOutputCmdMessage()) { 433 text = "Output Cmd: "; 434 text += "ID: " + getOutputIDString(); 435 text += ", State: " + getOutputStateString(); 436 } else if (isOutputAddMessage()) { 437 text = "Add Output: "; 438 text += "ID: " + getOutputIDString(); 439 text += ", Pin: " + getOutputPinString(); 440 text += ", IFlag: " + getOutputIFlagString(); 441 } else if (isOutputDeleteMessage()) { 442 text = "Delete Output: "; 443 text += "ID: " + getOutputIDString(); 444 } else if (isListOutputsMessage()) { 445 text = "List Outputs..."; 446 } else { 447 text = "Invalid Output Command: " + toString(); 448 } 449 break; 450 case DCCppConstants.SENSOR_CMD: 451 if (isSensorAddMessage()) { 452 text = "Add Sensor: "; 453 text += "ID: " + getSensorIDString(); 454 text += ", Pin: " + getSensorPinString(); 455 text += ", Pullup: " + getSensorPullupString(); 456 } else if (isSensorDeleteMessage()) { 457 text = "Delete Sensor: "; 458 text += "ID: " + getSensorIDString(); 459 } else if (isListSensorsMessage()) { 460 text = "List Sensors..."; 461 } else { 462 text = "Unknown Sensor Cmd..."; 463 } 464 break; 465 case DCCppConstants.OPS_WRITE_CV_BYTE: 466 text = "Ops Write Byte Cmd: "; // <w cab cv val> 467 text += "Address: " + getOpsWriteAddrString() + ", "; 468 text += "CV: " + getOpsWriteCVString() + ", "; 469 text += "Value: " + getOpsWriteValueString(); 470 break; 471 case DCCppConstants.OPS_WRITE_CV_BIT: // <b cab cv bit val> 472 text = "Ops Write Bit Cmd: "; 473 text += "Address: " + getOpsWriteAddrString() + ", "; 474 text += "CV: " + getOpsWriteCVString() + ", "; 475 text += "Bit: " + getOpsWriteBitString() + ", "; 476 text += "Value: " + getOpsWriteValueString(); 477 break; 478 case DCCppConstants.PROG_WRITE_CV_BYTE: 479 text = "Prog Write Byte Cmd: "; 480 text += "CV: " + getCVString(); 481 text += ", Value: " + getProgValueString(); 482 if (!isProgWriteByteMessageV4()) { 483 text += ", Callback Num: " + getCallbackNumString(); 484 text += ", Sub: " + getCallbackSubString(); 485 } 486 break; 487 488 case DCCppConstants.PROG_WRITE_CV_BIT: 489 text = "Prog Write Bit Cmd: "; 490 text += "CV: " + getCVString(); 491 text += ", Bit: " + getBitString(); 492 text += ", Value: " + getProgValueString(); 493 if (!isProgWriteBitMessageV4()) { 494 text += ", Callback Num: " + getCallbackNumString(); 495 text += ", Sub: " + getCallbackSubString(); 496 } 497 break; 498 case DCCppConstants.PROG_READ_CV: 499 if (isProgReadCVMessage()) { 500 text = "Prog Read Cmd: "; 501 text += "CV: " + getCVString(); 502 text += ", Callback Num: " + getCallbackNumString(); 503 text += ", Sub: " + getCallbackSubString(); 504 } else if (isProgReadCVMessageV4()) { 505 text = "Prog Read CV: "; 506 text += "CV:" + getCVString(); 507 } else { // if (isProgReadLocoIdMessage()) 508 text = "Prog Read LocoID Cmd"; 509 } 510 break; 511 case DCCppConstants.PROG_VERIFY_CV: 512 text = "Prog Verify Cmd: "; 513 text += "CV: " + getCVString(); 514 text += ", startVal: " + getProgValueString(); 515 break; 516 case DCCppConstants.TRACK_POWER_ON: 517 text = "Track Power ON Cmd "; 518 break; 519 case DCCppConstants.TRACK_POWER_OFF: 520 text = "Track Power OFF Cmd "; 521 break; 522 case DCCppConstants.READ_TRACK_CURRENT: 523 text = "Read Track Current Cmd "; 524 break; 525 case DCCppConstants.READ_CS_STATUS: 526 text = "Status Cmd "; 527 break; 528 case DCCppConstants.READ_MAXNUMSLOTS: 529 text = "Get MaxNumSlots Cmd "; 530 break; 531 case DCCppConstants.WRITE_DCC_PACKET_MAIN: 532 text = "Write DCC Packet Main Cmd: "; 533 text += "Register: " + getRegisterString(); 534 text += ", Packet:" + getPacketString(); 535 break; 536 case DCCppConstants.WRITE_DCC_PACKET_PROG: 537 text = "Write DCC Packet Prog Cmd: "; 538 text += "Register: " + getRegisterString(); 539 text += ", Packet:" + getPacketString(); 540 break; 541 case DCCppConstants.LIST_REGISTER_CONTENTS: 542 text = "List Register Contents Cmd: "; 543 text += toString(); 544 break; 545 case DCCppConstants.WRITE_TO_EEPROM_CMD: 546 text = "Write to EEPROM Cmd: "; 547 text += toString(); 548 break; 549 case DCCppConstants.CLEAR_EEPROM_CMD: 550 text = "Clear EEPROM Cmd: "; 551 text += toString(); 552 break; 553 case DCCppConstants.QUERY_SENSOR_STATES_CMD: 554 text = "Query Sensor States Cmd: '" + toString() + "'"; 555 break; 556 case DCCppConstants.DIAG_CMD: 557 text = "Diag Cmd: '" + toString() + "'"; 558 break; 559 case DCCppConstants.CONTROL_CMD: 560 text = "Control Cmd: '" + toString() + "'"; 561 break; 562 case DCCppConstants.ESTOP_ALL_CMD: 563 text = "eStop All Locos Cmd: '" + toString() + "'"; 564 break; 565 case DCCppConstants.THROTTLE_COMMANDS: 566 if (isTurnoutIDsMessage()) { 567 text = "Request Turnout ID list"; 568 break; 569 } else if (isTurnoutIDMessage()) { 570 text = "Request details for Turnout " + getTOIDString(); 571 break; 572 } else if (isClockRequestTimeMessage()) { 573 text = "Request clock update from CS"; 574 break; 575 } else if (isClockSetTimeMessage()) { 576 String hhmm = String.format("%02d:%02d", 577 getClockMinutesInt() / 60, 578 getClockMinutesInt() % 60); 579 text = "FastClock Send: " + hhmm; 580 if (!getClockRateString().isEmpty()) { 581 text += ", Rate:" + getClockRateString(); 582 if (getClockRateInt()==0) { 583 text += " (paused)"; 584 } 585 } 586 break; 587 } 588 text = "Unknown Message: '" + toString() + "'"; 589 break; 590 case DCCppConstants.TRACKMANAGER_CMD: 591 text = "Request TrackManager Config: '" + toString() + "'"; 592 break; 593 case DCCppConstants.LCD_TEXT_CMD: 594 text = "Request LCD Messages: '" + toString() + "'"; 595 break; 596 default: 597 text = "Unknown Message: '" + toString() + "'"; 598 } 599 600 return text; 601 } 602 603 @Override 604 public int getNumDataElements() { 605 return (myMessage.length()); 606 // return(_nDataChars); 607 } 608 609 @Override 610 public int getElement(int n) { 611 return (this.myMessage.charAt(n)); 612 } 613 614 @Override 615 public void setElement(int n, int v) { 616 // We want the ASCII value, not the string interpretation of the int 617 char c = (char) (v & 0xFF); 618 if (n >= myMessage.length()) { 619 myMessage.append(c); 620 } else if (n > 0) { 621 myMessage.setCharAt(n, c); 622 } 623 toStringCache = null; 624 } 625 // For DCC++, the opcode is the first character in the 626 // command (after the < ). 627 628 // note that the opcode is part of the message, so we treat it 629 // directly 630 // WARNING: use this only with opcodes that have a variable number 631 // of arguments following included. Otherwise, just use setElement 632 @Override 633 public void setOpCode(int i) { 634 if (i > 0xFF || i < 0) { 635 log.error("Opcode invalid: {}", i); 636 } 637 opcode = (char) (i & 0xFF); 638 myMessage.setCharAt(0, opcode); 639 toStringCache = null; 640 } 641 642 @Override 643 public int getOpCode() { 644 return (opcode & 0xFF); 645 } 646 647 public char getOpCodeChar() { 648 //return(opcode); 649 return (myMessage.charAt(0)); 650 } 651 652 private int getGroupCount() { 653 Matcher m = match(toString(), myRegex, "gvs"); 654 assert m != null; 655 return m.groupCount(); 656 } 657 658 public String getValueString(int idx) { 659 Matcher m = match(toString(), myRegex, "gvs"); 660 if (m == null) { 661 log.error("DCCppMessage '{}' not matched by '{}'", this.toString(), myRegex); 662 return (""); 663 } else if (idx <= m.groupCount()) { 664 return (m.group(idx)); 665 } else { 666 log.error("DCCppMessage value index too big. idx = {} msg = {}", idx, this); 667 return (""); 668 } 669 } 670 671 public int getValueInt(int idx) { 672 Matcher m = match(toString(), myRegex, "gvi"); 673 if (m == null) { 674 log.error("DCCppMessage '{}' not matched by '{}'", this.toString(), myRegex); 675 return (0); 676 } else if (idx <= m.groupCount()) { 677 return (Integer.parseInt(m.group(idx))); 678 } else { 679 log.error("DCCppMessage value index too big. idx = {} msg = {}", idx, this); 680 return (0); 681 } 682 } 683 684 public boolean getValueBool(int idx) { 685 log.debug("msg = {}, regex = {}", this, myRegex); 686 Matcher m = match(toString(), myRegex, "gvb"); 687 688 if (m == null) { 689 log.error("DCCppMessage '{}' not matched by '{}'", this.toString(), myRegex); 690 return (false); 691 } else if (idx <= m.groupCount()) { 692 return (!m.group(idx).equals("0")); 693 } else { 694 log.error("DCCppMessage value index too big. idx = {} msg = {}", idx, this); 695 return (false); 696 } 697 } 698 699 /** 700 * @return the message length 701 */ 702 public int length() { 703 return (myMessage.length()); 704 } 705 706 /** 707 * Change the default number of retries for an DCC++ message. 708 * 709 * @param t number of retries to attempt 710 */ 711 public static void setDCCppMessageRetries(int t) { 712 _nRetries = t; 713 } 714 715 /** 716 * Change the default timeout for a DCC++ message. 717 * 718 * @param t Timeout in milliseconds 719 */ 720 public static void setDCCppMessageTimeout(int t) { 721 DCCppMessageTimeout = t; 722 } 723 724 //------------------------------------------------------ 725 // Message Helper Functions 726 // Core methods 727 /** 728 * Returns true if this DCCppMessage is properly formatted (or will generate 729 * a properly formatted command when converted to String). 730 * 731 * @return boolean true/false 732 */ 733 public boolean isValidMessageFormat() { 734 return this.match(this.myRegex) != null; 735 } 736 737 /** 738 * Matches this DCCppMessage against the given regex 'pat' 739 * 740 * @param pat Regex 741 * @return Matcher or null if no match. 742 */ 743 private Matcher match(String pat) { 744 return (match(this.toString(), pat, "Validator")); 745 } 746 747 /** 748 * matches the given string against the given Regex pattern. 749 * 750 * @param s string to be matched 751 * @param pat Regex string to match against 752 * @param name Text name to use in debug messages. 753 * @return Matcher or null if no match 754 */ 755 @CheckForNull 756 private static Matcher match(String s, String pat, String name) { 757 try { 758 Pattern p = Pattern.compile(pat); 759 Matcher m = p.matcher(s); 760 if (!m.matches()) { 761 log.trace("No Match {} Command: '{}' Pattern: '{}'", name, s, pat); 762 return null; 763 } 764 return m; 765 766 } catch (PatternSyntaxException e) { 767 log.error("Malformed DCC++ message syntax! s = {}", pat); 768 return (null); 769 } catch (IllegalStateException e) { 770 log.error("Group called before match operation executed string= {}", s); 771 return (null); 772 } catch (IndexOutOfBoundsException e) { 773 log.error("Index out of bounds string= {}", s); 774 return (null); 775 } 776 } 777 778 // Identity Methods 779 public boolean isThrottleMessage() { 780 return (this.match(DCCppConstants.THROTTLE_CMD_REGEX) != null); 781 } 782 783 public boolean isThrottleV3Message() { 784 return (this.match(DCCppConstants.THROTTLE_V3_CMD_REGEX) != null); 785 } 786 787 public boolean isAccessoryMessage() { 788 return (this.getOpCodeChar() == DCCppConstants.ACCESSORY_CMD); 789 } 790 791 public boolean isFunctionMessage() { 792 return (this.getOpCodeChar() == DCCppConstants.FUNCTION_CMD); 793 } 794 795 public boolean isFunctionV4Message() { 796 return (this.match(DCCppConstants.FUNCTION_V4_CMD_REGEX) != null); 797 } 798 799 public boolean isForgetCabMessage() { 800 return (this.match(DCCppConstants.FORGET_CAB_CMD_REGEX) != null); 801 } 802 803 public boolean isTurnoutMessage() { 804 return (this.getOpCodeChar() == DCCppConstants.TURNOUT_CMD); 805 } 806 807 public boolean isSensorMessage() { 808 return (this.getOpCodeChar() == DCCppConstants.SENSOR_CMD); 809 } 810 811 public boolean isEEPROMWriteMessage() { 812 return (this.getOpCodeChar() == DCCppConstants.WRITE_TO_EEPROM_CMD); 813 } 814 815 public boolean isEEPROMClearMessage() { 816 return (this.getOpCodeChar() == DCCppConstants.CLEAR_EEPROM_CMD); 817 } 818 819 public boolean isOpsWriteByteMessage() { 820 return (this.getOpCodeChar() == DCCppConstants.OPS_WRITE_CV_BYTE); 821 } 822 823 public boolean isOpsWriteBitMessage() { 824 return (this.getOpCodeChar() == DCCppConstants.OPS_WRITE_CV_BIT); 825 } 826 827 public boolean isProgWriteByteMessage() { 828 return (this.getOpCodeChar() == DCCppConstants.PROG_WRITE_CV_BYTE); 829 } 830 831 public boolean isProgWriteByteMessageV4() { 832 return (this.match(DCCppConstants.PROG_WRITE_BYTE_V4_REGEX) != null); 833 } 834 835 public boolean isProgWriteBitMessage() { 836 return (this.getOpCodeChar() == DCCppConstants.PROG_WRITE_CV_BIT); 837 } 838 839 public boolean isProgWriteBitMessageV4() { 840 return (this.match(DCCppConstants.PROG_WRITE_BIT_V4_REGEX) != null); 841 } 842 843 public boolean isProgReadCVMessage() { 844 return (this.match(DCCppConstants.PROG_READ_CV_REGEX) != null); 845 } 846 847 public boolean isProgReadCVMessageV4() { 848 return (this.match(DCCppConstants.PROG_READ_CV_V4_REGEX) != null); 849 } 850 851 public boolean isProgReadLocoIdMessage() { 852 return (this.match(DCCppConstants.PROG_READ_LOCOID_REGEX) != null); 853 } 854 855 public boolean isProgVerifyMessage() { 856 return (this.getOpCodeChar() == DCCppConstants.PROG_VERIFY_CV); 857 } 858 859 public boolean isTurnoutCmdMessage() { 860 return (this.match(DCCppConstants.TURNOUT_CMD_REGEX) != null); 861 } 862 863 public boolean isTurnoutAddMessage() { 864 return (this.match(DCCppConstants.TURNOUT_ADD_REGEX) != null); 865 } 866 867 public boolean isTurnoutAddDCCMessage() { 868 return (this.match(DCCppConstants.TURNOUT_ADD_DCC_REGEX) != null); 869 } 870 871 public boolean isTurnoutAddServoMessage() { 872 return (this.match(DCCppConstants.TURNOUT_ADD_SERVO_REGEX) != null); 873 } 874 875 public boolean isTurnoutAddVpinMessage() { 876 return (this.match(DCCppConstants.TURNOUT_ADD_VPIN_REGEX) != null); 877 } 878 879 public boolean isTurnoutDeleteMessage() { 880 return (this.match(DCCppConstants.TURNOUT_DELETE_REGEX) != null); 881 } 882 883 public boolean isListTurnoutsMessage() { 884 return (this.match(DCCppConstants.TURNOUT_LIST_REGEX) != null); 885 } 886 887 public boolean isSensorAddMessage() { 888 return (this.match(DCCppConstants.SENSOR_ADD_REGEX) != null); 889 } 890 891 public boolean isSensorDeleteMessage() { 892 return (this.match(DCCppConstants.SENSOR_DELETE_REGEX) != null); 893 } 894 895 public boolean isListSensorsMessage() { 896 return (this.match(DCCppConstants.SENSOR_LIST_REGEX) != null); 897 } 898 899 //public boolean isOutputCmdMessage() { return(this.getOpCodeChar() == DCCppConstants.OUTPUT_CMD); } 900 public boolean isOutputCmdMessage() { 901 return (this.match(DCCppConstants.OUTPUT_CMD_REGEX) != null); 902 } 903 904 public boolean isOutputAddMessage() { 905 return (this.match(DCCppConstants.OUTPUT_ADD_REGEX) != null); 906 } 907 908 public boolean isOutputDeleteMessage() { 909 return (this.match(DCCppConstants.OUTPUT_DELETE_REGEX) != null); 910 } 911 912 public boolean isListOutputsMessage() { 913 return (this.match(DCCppConstants.OUTPUT_LIST_REGEX) != null); 914 } 915 916 public boolean isQuerySensorStatesMessage() { 917 return (this.match(DCCppConstants.QUERY_SENSOR_STATES_REGEX) != null); 918 } 919 920 public boolean isWriteDccPacketMessage() { 921 return ((this.getOpCodeChar() == DCCppConstants.WRITE_DCC_PACKET_MAIN) || (this.getOpCodeChar() == DCCppConstants.WRITE_DCC_PACKET_PROG)); 922 } 923 924 public boolean isTurnoutIDsMessage() { 925 return (this.match(DCCppConstants.TURNOUT_IDS_REGEX) != null); 926 } 927 public boolean isTurnoutIDMessage() { 928 return (this.match(DCCppConstants.TURNOUT_ID_REGEX) != null); 929 } 930 public boolean isClockRequestTimeMessage() { 931 return (this.match(DCCppConstants.CLOCK_REQUEST_TIME_REGEX) != null); 932 } 933 public boolean isClockSetTimeMessage() { 934 return (this.match(DCCppConstants.CLOCK_SET_REGEX) != null); 935 } 936 937 public boolean isTrackManagerRequestMessage() { 938 return (this.match(DCCppConstants.TRACKMANAGER_CMD_REGEX) != null); 939 } 940 941 public boolean isTurnoutImplementationMessage() { 942 return (this.match(DCCppConstants.TURNOUT_IMPL_REGEX) != null); 943 } 944 945 946 //------------------------------------------------------ 947 // Helper methods for Sensor Query Commands 948 public String getOutputIDString() { 949 if (this.isOutputAddMessage() || this.isOutputDeleteMessage() || this.isOutputCmdMessage()) { 950 return getValueString(1); 951 } else { 952 log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar()); 953 return ("0"); 954 } 955 } 956 957 public int getOutputIDInt() { 958 if (this.isOutputAddMessage() || this.isOutputDeleteMessage() || this.isOutputCmdMessage()) { 959 return (getValueInt(1)); // assumes stored as an int! 960 } else { 961 log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar()); 962 return (0); 963 } 964 } 965 966 public String getOutputPinString() { 967 if (this.isOutputAddMessage()) { 968 return (getValueString(2)); 969 } else { 970 log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar()); 971 return ("0"); 972 } 973 } 974 975 public int getOutputPinInt() { 976 if (this.isOutputAddMessage()) { 977 return (getValueInt(2)); 978 } else { 979 log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar()); 980 return (0); 981 } 982 } 983 984 public String getOutputIFlagString() { 985 if (this.isOutputAddMessage()) { 986 return (getValueString(3)); 987 } else { 988 log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar()); 989 return ("0"); 990 } 991 } 992 993 public int getOutputIFlagInt() { 994 if (this.isOutputAddMessage()) { 995 return (getValueInt(3)); 996 } else { 997 log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar()); 998 return (0); 999 } 1000 } 1001 1002 public String getOutputStateString() { 1003 if (isOutputCmdMessage()) { 1004 return (this.getOutputStateInt() == 1 ? "HIGH" : "LOW"); 1005 } else { 1006 return ("Not a Turnout"); 1007 } 1008 } 1009 1010 public int getOutputStateInt() { 1011 if (isOutputCmdMessage()) { 1012 return (getValueInt(2)); 1013 } else { 1014 log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar()); 1015 return (0); 1016 } 1017 } 1018 1019 public boolean getOutputStateBool() { 1020 if (this.isOutputCmdMessage()) { 1021 return (getValueInt(2) != 0); 1022 } else { 1023 log.error("Output Parser called on non-Output message type {} message {}", this.getOpCodeChar(), this); 1024 return (false); 1025 } 1026 } 1027 1028 public String getSensorIDString() { 1029 if (this.isSensorAddMessage()) { 1030 return getValueString(1); 1031 } else { 1032 log.error("Sensor Parser called on non-Sensor message type {}", this.getOpCodeChar()); 1033 return ("0"); 1034 } 1035 } 1036 1037 public int getSensorIDInt() { 1038 if (this.isSensorAddMessage()) { 1039 return (getValueInt(1)); // assumes stored as an int! 1040 } else { 1041 log.error("Sensor Parser called on non-Sensor message type {}", this.getOpCodeChar()); 1042 return (0); 1043 } 1044 } 1045 1046 public String getSensorPinString() { 1047 if (this.isSensorAddMessage()) { 1048 return (getValueString(2)); 1049 } else { 1050 log.error("Sensor Parser called on non-Sensor message type {}", this.getOpCodeChar()); 1051 return ("0"); 1052 } 1053 } 1054 1055 public int getSensorPinInt() { 1056 if (this.isSensorAddMessage()) { 1057 return (getValueInt(2)); 1058 } else { 1059 log.error("Sensor Parser called on non-Sensor message type {}", this.getOpCodeChar()); 1060 return (0); 1061 } 1062 } 1063 1064 public String getSensorPullupString() { 1065 if (isSensorAddMessage()) { 1066 return (getValueBool(3) ? "PULLUP" : "NO PULLUP"); 1067 } else { 1068 return ("Not a Sensor"); 1069 } 1070 } 1071 1072 public int getSensorPullupInt() { 1073 if (this.isSensorAddMessage()) { 1074 return (getValueInt(3)); 1075 } else { 1076 log.error("Sensor Parser called on non-Sensor message type {} message {}", this.getOpCodeChar(), this); 1077 return (0); 1078 } 1079 } 1080 1081 public boolean getSensorPullupBool() { 1082 if (this.isSensorAddMessage()) { 1083 return (getValueBool(3)); 1084 } else { 1085 log.error("Sensor Parser called on non-Sensor message type {} message {}", this.getOpCodeChar(), this); 1086 return (false); 1087 } 1088 } 1089 1090 // Helper methods for Accessory Decoder Commands 1091 public String getAccessoryAddrString() { 1092 if (this.isAccessoryMessage()) { 1093 return (getValueString(1)); 1094 } else { 1095 log.error("Accessory Parser called on non-Accessory message type {}", this.getOpCodeChar()); 1096 return ("0"); 1097 } 1098 } 1099 1100 public int getAccessoryAddrInt() { 1101 if (this.isAccessoryMessage()) { 1102 return (getValueInt(1)); 1103 } else { 1104 log.error("Accessory Parser called on non-Accessory message type {}", this.getOpCodeChar()); 1105 return (0); 1106 } 1107 //return(Integer.parseInt(this.getAccessoryAddrString())); 1108 } 1109 1110 public String getAccessorySubString() { 1111 if (this.isAccessoryMessage()) { 1112 return (getValueString(2)); 1113 } else { 1114 log.error("Accessory Parser called on non-Accessory message type {} message {}", this.getOpCodeChar(), this); 1115 return ("0"); 1116 } 1117 } 1118 1119 public int getAccessorySubInt() { 1120 if (this.isAccessoryMessage()) { 1121 return (getValueInt(2)); 1122 } else { 1123 log.error("Accessory Parser called on non-Accessory message type {} message {}", this.getOpCodeChar(), this); 1124 return (0); 1125 } 1126 } 1127 1128 public String getAccessoryStateString() { 1129 if (isAccessoryMessage()) { 1130 return (this.getAccessoryStateInt() == 1 ? "ON" : "OFF"); 1131 } else { 1132 return ("Not an Accessory Decoder"); 1133 } 1134 } 1135 1136 public int getAccessoryStateInt() { 1137 if (this.isAccessoryMessage()) { 1138 return (getValueInt(3)); 1139 } else { 1140 log.error("Accessory Parser called on non-Accessory message type {} message {}", this.getOpCodeChar(), this); 1141 return (0); 1142 } 1143 } 1144 1145 //------------------------------------------------------ 1146 // Helper methods for Throttle Commands 1147 public String getRegisterString() { 1148 if (this.isThrottleMessage() || this.isWriteDccPacketMessage()) { 1149 return (getValueString(1)); 1150 } else { 1151 log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar()); 1152 return ("0"); 1153 } 1154 } 1155 1156 public int getRegisterInt() { 1157 if (this.isThrottleMessage()) { 1158 return (getValueInt(1)); 1159 } else { 1160 log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar()); 1161 return (0); 1162 } 1163 } 1164 1165 public String getAddressString() { 1166 if (this.isThrottleMessage()) { 1167 return (getValueString(2)); 1168 } else if (this.isThrottleV3Message()) { 1169 return (getValueString(1)); 1170 } else { 1171 log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar()); 1172 return ("0"); 1173 } 1174 } 1175 1176 public int getAddressInt() { 1177 if (this.isThrottleMessage()) { 1178 return (getValueInt(2)); 1179 } else if (this.isThrottleV3Message()) { 1180 return (getValueInt(1)); 1181 } else { 1182 log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar()); 1183 return (0); 1184 } 1185 } 1186 1187 public String getSpeedString() { 1188 if (this.isThrottleMessage()) { 1189 return (getValueString(3)); 1190 } else if (this.isThrottleV3Message()) { 1191 return (getValueString(2)); 1192 } else { 1193 log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar()); 1194 return ("0"); 1195 } 1196 } 1197 1198 public int getSpeedInt() { 1199 if (this.isThrottleMessage()) { 1200 return (getValueInt(3)); 1201 } else if (this.isThrottleV3Message()) { 1202 return (getValueInt(2)); 1203 } else { 1204 log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar()); 1205 return (0); 1206 } 1207 } 1208 1209 public String getDirectionString() { 1210 if (this.isThrottleMessage() || this.isThrottleV3Message()) { 1211 return (this.getDirectionInt() == 1 ? "Forward" : "Reverse"); 1212 } else { 1213 log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar()); 1214 return ("Not a Throttle"); 1215 } 1216 } 1217 1218 public int getDirectionInt() { 1219 if (this.isThrottleMessage()) { 1220 return (getValueInt(4)); 1221 } else if (this.isThrottleV3Message()) { 1222 return (getValueInt(3)); 1223 } else { 1224 log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar()); 1225 return (0); 1226 } 1227 } 1228 1229 //------------------------------------------------------ 1230 // Helper methods for Function Commands 1231 public String getFuncAddressString() { 1232 if (this.isFunctionMessage()) { 1233 return (getValueString(1)); 1234 } else { 1235 log.error("Function Parser called on non-Function message type {}", this.getOpCodeChar()); 1236 return ("0"); 1237 } 1238 } 1239 1240 public int getFuncAddressInt() { 1241 if (this.isFunctionMessage()) { 1242 return (getValueInt(1)); 1243 } else { 1244 log.error("Function Parser called on non-Function message type {}", this.getOpCodeChar()); 1245 return (0); 1246 } 1247 } 1248 1249 public String getFuncByte1String() { 1250 if (this.isFunctionMessage()) { 1251 return (getValueString(2)); 1252 } else { 1253 log.error("Function Parser called on non-Function message type {}", this.getOpCodeChar()); 1254 return ("0"); 1255 } 1256 } 1257 1258 public int getFuncByte1Int() { 1259 if (this.isFunctionMessage()) { 1260 return (getValueInt(2)); 1261 } else { 1262 log.error("Function Parser called on non-Function message type {}", this.getOpCodeChar()); 1263 return (0); 1264 } 1265 } 1266 1267 public String getFuncByte2String() { 1268 if (this.isFunctionMessage()) { 1269 return (getValueString(3)); 1270 } else { 1271 log.error("Function Parser called on non-Function message type {}", this.getOpCodeChar()); 1272 return ("0"); 1273 } 1274 } 1275 1276 public int getFuncByte2Int() { 1277 if (this.isFunctionMessage()) { 1278 return (getValueInt(3)); 1279 } else { 1280 log.error("Function Parser called on non-Function message type {}", this.getOpCodeChar()); 1281 return (0); 1282 } 1283 } 1284 1285 public String getFuncV4CabString() { 1286 if (this.isFunctionV4Message()) { 1287 return (getValueString(1)); 1288 } else { 1289 log.error("Function Parser called on non-Function V4 message type {}", this.getOpCodeChar()); 1290 return ("0"); 1291 } 1292 } 1293 1294 public String getFuncV4FuncString() { 1295 if (this.isFunctionV4Message()) { 1296 return (getValueString(2)); 1297 } else { 1298 log.error("Function Parser called on non-Function V4 message type {}", this.getOpCodeChar()); 1299 return ("0"); 1300 } 1301 } 1302 1303 public String getFuncV4StateString() { 1304 if (this.isFunctionV4Message()) { 1305 return (getValueString(3)); 1306 } else { 1307 log.error("Function Parser called on non-Function V4 message type {}", this.getOpCodeChar()); 1308 return ("0"); 1309 } 1310 } 1311 1312 public String getForgetCabString() { 1313 if (this.isForgetCabMessage()) { 1314 return (getValueString(1)); 1315 } else { 1316 log.error("Function Parser called on non-Forget Cab message type {}", this.getOpCodeChar()); 1317 return ("0"); 1318 } 1319 } 1320 1321 //------------------------------------------------------ 1322 // Helper methods for Turnout Commands 1323 public String getTOIDString() { 1324 if (this.isTurnoutMessage() || isTurnoutIDMessage()) { 1325 return (getValueString(1)); 1326 } else { 1327 log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this); 1328 return ("0"); 1329 } 1330 } 1331 1332 public int getTOIDInt() { 1333 if (this.isTurnoutMessage() || isTurnoutIDMessage()) { 1334 return (getValueInt(1)); 1335 } else { 1336 log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this); 1337 return (0); 1338 } 1339 } 1340 1341 public String getTOStateString() { 1342 if (isTurnoutMessage()) { 1343 return (this.getTOStateInt() == 1 ? "THROWN" : "CLOSED"); 1344 } else { 1345 return ("Not a Turnout"); 1346 } 1347 } 1348 1349 public int getTOStateInt() { 1350 if (this.isTurnoutMessage()) { 1351 return (getValueInt(2)); 1352 } else { 1353 log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this); 1354 return (0); 1355 } 1356 } 1357 1358 public String getTOAddressString() { 1359 if (this.isTurnoutAddMessage() || this.isTurnoutAddDCCMessage()) { 1360 return (getValueString(2)); 1361 } else { 1362 log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this); 1363 return ("0"); 1364 } 1365 } 1366 1367 public int getTOAddressInt() { 1368 if (this.isTurnoutAddMessage() || this.isTurnoutAddDCCMessage()) { 1369 return (getValueInt(2)); 1370 } else { 1371 log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this); 1372 return (0); 1373 } 1374 } 1375 1376 public String getTOSubAddressString() { 1377 if (this.isTurnoutAddMessage() || this.isTurnoutAddDCCMessage()) { 1378 return (getValueString(3)); 1379 } else { 1380 log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this); 1381 return ("0"); 1382 } 1383 } 1384 1385 public int getTOSubAddressInt() { 1386 if (this.isTurnoutAddMessage() || this.isTurnoutAddDCCMessage()) { 1387 return (getValueInt(3)); 1388 } else { 1389 log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this); 1390 return (0); 1391 } 1392 } 1393 1394 public int getTOThrownPositionInt() { 1395 if (this.isTurnoutAddServoMessage()) { 1396 return (getValueInt(3)); 1397 } else { 1398 log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this); 1399 return (0); 1400 } 1401 } 1402 1403 public int getTOClosedPositionInt() { 1404 if (this.isTurnoutAddServoMessage()) { 1405 return (getValueInt(4)); 1406 } else { 1407 log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this); 1408 return (0); 1409 } 1410 } 1411 1412 public int getTOProfileInt() { 1413 if (this.isTurnoutAddServoMessage()) { 1414 return (getValueInt(5)); 1415 } else { 1416 log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this); 1417 return (0); 1418 } 1419 } 1420 1421 public int getTOPinInt() { 1422 if (this.isTurnoutAddServoMessage() || this.isTurnoutAddVpinMessage()) { 1423 return (getValueInt(2)); 1424 } else { 1425 log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this); 1426 return (0); 1427 } 1428 } 1429 public String getClockMinutesString() { 1430 if (this.isClockSetTimeMessage()) { 1431 return (this.getValueString(1)); 1432 } else { 1433 log.error("getClockTimeString Parser called on non-getClockTimeString message type {}", this.getOpCodeChar()); 1434 return ("0"); 1435 } 1436 } 1437 public int getClockMinutesInt() { 1438 return (Integer.parseInt(this.getClockMinutesString())); 1439 } 1440 public String getClockRateString() { 1441 if (this.isClockSetTimeMessage()) { 1442 return (this.getValueString(2)); 1443 } else { 1444 log.error("getClockRateString Parser called on non-getClockRateString message type {}", this.getOpCodeChar()); 1445 return ("0"); 1446 } 1447 } 1448 public int getClockRateInt() { 1449 return (Integer.parseInt(this.getClockRateString())); 1450 } 1451 1452 //------------------------------------------------------ 1453 // Helper methods for Ops Write Byte Commands 1454 public String getOpsWriteAddrString() { 1455 if (this.isOpsWriteByteMessage() || this.isOpsWriteBitMessage()) { 1456 return (getValueString(1)); 1457 } else { 1458 return ("0"); 1459 } 1460 } 1461 1462 public int getOpsWriteAddrInt() { 1463 if (this.isOpsWriteByteMessage() || this.isOpsWriteBitMessage()) { 1464 return (getValueInt(1)); 1465 } else { 1466 return (0); 1467 } 1468 } 1469 1470 public String getOpsWriteCVString() { 1471 if (this.isOpsWriteByteMessage() || this.isOpsWriteBitMessage()) { 1472 return (getValueString(2)); 1473 } else { 1474 return ("0"); 1475 } 1476 } 1477 1478 public int getOpsWriteCVInt() { 1479 if (this.isOpsWriteByteMessage() || this.isOpsWriteBitMessage()) { 1480 return (getValueInt(2)); 1481 } else { 1482 return (0); 1483 } 1484 } 1485 1486 public String getOpsWriteBitString() { 1487 if (this.isOpsWriteBitMessage()) { 1488 return (getValueString(3)); 1489 } else { 1490 return ("0"); 1491 } 1492 } 1493 1494 public int getOpsWriteBitInt() { 1495 if (this.isOpsWriteBitMessage()) { 1496 return (getValueInt(3)); 1497 } else { 1498 return (0); 1499 } 1500 } 1501 1502 public String getOpsWriteValueString() { 1503 if (this.isOpsWriteByteMessage()) { 1504 return (getValueString(3)); 1505 } else if (this.isOpsWriteBitMessage()) { 1506 return (getValueString(4)); 1507 } else { 1508 log.error("Ops Program Parser called on non-OpsProgram message type {}", this.getOpCodeChar()); 1509 return ("0"); 1510 } 1511 } 1512 1513 public int getOpsWriteValueInt() { 1514 if (this.isOpsWriteByteMessage()) { 1515 return (getValueInt(3)); 1516 } else if (this.isOpsWriteBitMessage()) { 1517 return (getValueInt(4)); 1518 } else { 1519 return (0); 1520 } 1521 } 1522 1523 // ------------------------------------------------------ 1524 // Helper methods for Prog Write and Read Byte Commands 1525 public String getCVString() { 1526 if (this.isProgWriteByteMessage() || 1527 this.isProgWriteBitMessage() || 1528 this.isProgReadCVMessage() || 1529 this.isProgReadCVMessageV4() || 1530 this.isProgVerifyMessage()) { 1531 return (getValueString(1)); 1532 } else { 1533 return ("0"); 1534 } 1535 } 1536 1537 public int getCVInt() { 1538 if (this.isProgWriteByteMessage() || 1539 this.isProgWriteBitMessage() || 1540 this.isProgReadCVMessage() || 1541 this.isProgReadCVMessageV4() || 1542 this.isProgVerifyMessage()) { 1543 return (getValueInt(1)); 1544 } else { 1545 return (0); 1546 } 1547 } 1548 1549 public String getCallbackNumString() { 1550 int idx; 1551 if (this.isProgWriteByteMessage()) { 1552 idx = 3; 1553 } else if (this.isProgWriteBitMessage()) { 1554 idx = 4; 1555 } else if (this.isProgReadCVMessage()) { 1556 idx = 2; 1557 } else { 1558 return ("0"); 1559 } 1560 return (getValueString(idx)); 1561 } 1562 1563 public int getCallbackNumInt() { 1564 int idx; 1565 if (this.isProgWriteByteMessage()) { 1566 idx = 3; 1567 } else if (this.isProgWriteBitMessage()) { 1568 idx = 4; 1569 } else if (this.isProgReadCVMessage()) { 1570 idx = 2; 1571 } else { 1572 return (0); 1573 } 1574 return (getValueInt(idx)); 1575 } 1576 1577 public String getCallbackSubString() { 1578 int idx; 1579 if (this.isProgWriteByteMessage()) { 1580 idx = 4; 1581 } else if (this.isProgWriteBitMessage()) { 1582 idx = 5; 1583 } else if (this.isProgReadCVMessage()) { 1584 idx = 3; 1585 } else { 1586 return ("0"); 1587 } 1588 return (getValueString(idx)); 1589 } 1590 1591 public int getCallbackSubInt() { 1592 int idx; 1593 if (this.isProgWriteByteMessage()) { 1594 idx = 4; 1595 } else if (this.isProgWriteBitMessage()) { 1596 idx = 5; 1597 } else if (this.isProgReadCVMessage()) { 1598 idx = 3; 1599 } else { 1600 return (0); 1601 } 1602 return (getValueInt(idx)); 1603 } 1604 1605 public String getProgValueString() { 1606 int idx; 1607 if (this.isProgWriteByteMessage() || this.isProgVerifyMessage()) { 1608 idx = 2; 1609 } else if (this.isProgWriteBitMessage()) { 1610 idx = 3; 1611 } else { 1612 return ("0"); 1613 } 1614 return (getValueString(idx)); 1615 } 1616 1617 public int getProgValueInt() { 1618 int idx; 1619 if (this.isProgWriteByteMessage() || this.isProgVerifyMessage()) { 1620 idx = 2; 1621 } else if (this.isProgWriteBitMessage()) { 1622 idx = 3; 1623 } else { 1624 return (0); 1625 } 1626 return (getValueInt(idx)); 1627 } 1628 1629 //------------------------------------------------------ 1630 // Helper methods for Prog Write Bit Commands 1631 public String getBitString() { 1632 if (this.isProgWriteBitMessage()) { 1633 return (getValueString(2)); 1634 } else { 1635 log.error("PWBit Parser called on non-PWBit message type {}", this.getOpCodeChar()); 1636 return ("0"); 1637 } 1638 } 1639 1640 public int getBitInt() { 1641 if (this.isProgWriteBitMessage()) { 1642 return (getValueInt(2)); 1643 } else { 1644 return (0); 1645 } 1646 } 1647 1648 public String getPacketString() { 1649 if (this.isWriteDccPacketMessage()) { 1650 StringBuilder b = new StringBuilder(); 1651 for (int i = 2; i <= getGroupCount() - 1; i++) { 1652 b.append(this.getValueString(i)); 1653 } 1654 return (b.toString()); 1655 } else { 1656 log.error("Write Dcc Packet parser called on non-Dcc Packet message type {}", this.getOpCodeChar()); 1657 return ("0"); 1658 } 1659 } 1660 1661 //------------------------------------------------------ 1662 1663 /* 1664 * Most messages are sent with a reply expected, but 1665 * we have a few that we treat as though the reply is always 1666 * a broadcast message, because the reply usually comes to us 1667 * that way. 1668 */ 1669 // TODO: Not sure this is useful in DCC++ 1670 @Override 1671 public boolean replyExpected() { 1672 boolean retv; 1673 switch (this.getOpCodeChar()) { 1674 case DCCppConstants.TURNOUT_CMD: 1675 case DCCppConstants.SENSOR_CMD: 1676 case DCCppConstants.PROG_WRITE_CV_BYTE: 1677 case DCCppConstants.PROG_WRITE_CV_BIT: 1678 case DCCppConstants.PROG_READ_CV: 1679 case DCCppConstants.PROG_VERIFY_CV: 1680 case DCCppConstants.TRACK_POWER_ON: 1681 case DCCppConstants.TRACK_POWER_OFF: 1682 case DCCppConstants.READ_TRACK_CURRENT: 1683 case DCCppConstants.READ_CS_STATUS: 1684 case DCCppConstants.READ_MAXNUMSLOTS: 1685 case DCCppConstants.OUTPUT_CMD: 1686 case DCCppConstants.LIST_REGISTER_CONTENTS: 1687 retv = true; 1688 break; 1689 default: 1690 retv = false; 1691 } 1692 return (retv); 1693 } 1694 1695 // decode messages of a particular form 1696 // create messages of a particular form 1697 1698 /* 1699 * The next group of routines are used by Feedback and/or turnout 1700 * control code. These are used in multiple places within the code, 1701 * so they appear here. 1702 */ 1703 1704 /** 1705 * Stationary Decoder Message. 1706 * <p> 1707 * Note that many decoders and controllers combine the ADDRESS and 1708 * SUBADDRESS into a single number, N, from 1 through a max of 2044, where 1709 * <p> 1710 * {@code N = (ADDRESS - 1) * 4 + SUBADDRESS + 1, for all ADDRESS>0} 1711 * <p> 1712 * OR 1713 * <p> 1714 * {@code ADDRESS = INT((N - 1) / 4) + 1} 1715 * {@code SUBADDRESS = (N - 1) % 4} 1716 * 1717 * @param address the primary address of the decoder (0-511). 1718 * @param subaddress the subaddress of the decoder (0-3). 1719 * @param activate true on, false off. 1720 * @return accessory decoder message. 1721 */ 1722 public static DCCppMessage makeAccessoryDecoderMsg(int address, int subaddress, boolean activate) { 1723 // Sanity check inputs 1724 if (address < 0 || address > DCCppConstants.MAX_ACC_DECODER_ADDRESS) { 1725 return (null); 1726 } 1727 if (subaddress < 0 || subaddress > DCCppConstants.MAX_ACC_DECODER_SUBADDR) { 1728 return (null); 1729 } 1730 1731 DCCppMessage m = new DCCppMessage(DCCppConstants.ACCESSORY_CMD); 1732 1733 m.myMessage.append(" ").append(address); 1734 m.myMessage.append(" ").append(subaddress); 1735 m.myMessage.append(" ").append(activate ? "1" : "0"); 1736 m.myRegex = DCCppConstants.ACCESSORY_CMD_REGEX; 1737 1738 m._nDataChars = m.toString().length(); 1739 return (m); 1740 } 1741 1742 public static DCCppMessage makeAccessoryDecoderMsg(int address, boolean activate) { 1743 // Convert the single address to an address/subaddress pair: 1744 // address = (address - 1) * 4 + subaddress + 1 for address>0; 1745 int addr, subaddr; 1746 if (address > 0) { 1747 addr = ((address - 1) / (DCCppConstants.MAX_ACC_DECODER_SUBADDR + 1)) + 1; 1748 subaddr = (address - 1) % (DCCppConstants.MAX_ACC_DECODER_SUBADDR + 1); 1749 } else { 1750 addr = subaddr = 0; 1751 } 1752 log.debug("makeAccessoryDecoderMsg address {}, addr {}, subaddr {}, activate {}", address, addr, subaddr, activate); 1753 return (makeAccessoryDecoderMsg(addr, subaddr, activate)); 1754 } 1755 1756 /** 1757 * Predefined Turnout Control Message. 1758 * 1759 * @param id the numeric ID (0-32767) of the turnout to control. 1760 * @param thrown true thrown, false closed. 1761 * @return message to set turnout. 1762 */ 1763 public static DCCppMessage makeTurnoutCommandMsg(int id, boolean thrown) { 1764 // Sanity check inputs 1765 if (id < 0 || id > DCCppConstants.MAX_TURNOUT_ADDRESS) { 1766 return (null); 1767 } 1768 // Need to also validate whether turnout is predefined? Where to store the IDs? 1769 // Turnout Command 1770 1771 DCCppMessage m = new DCCppMessage(DCCppConstants.TURNOUT_CMD); 1772 m.myMessage.append(" ").append(id); 1773 m.myMessage.append((thrown ? " 1" : " 0")); 1774 m.myRegex = DCCppConstants.TURNOUT_CMD_REGEX; 1775 1776 m._nDataChars = m.toString().length(); 1777 return (m); 1778 } 1779 1780 public static DCCppMessage makeOutputCmdMsg(int id, boolean state) { 1781 // Sanity check inputs 1782 if (id < 0 || id > DCCppConstants.MAX_TURNOUT_ADDRESS) { 1783 return (null); 1784 } 1785 1786 DCCppMessage m = new DCCppMessage(DCCppConstants.OUTPUT_CMD); 1787 m.myMessage.append(" ").append(id); 1788 m.myMessage.append(" ").append(state ? "1" : "0"); 1789 m.myRegex = DCCppConstants.OUTPUT_CMD_REGEX; 1790 1791 m._nDataChars = m.toString().length(); 1792 return (m); 1793 } 1794 1795 public static DCCppMessage makeOutputAddMsg(int id, int pin, int iflag) { 1796 // Sanity check inputs 1797 if (id < 0 || id > DCCppConstants.MAX_TURNOUT_ADDRESS) { 1798 return (null); 1799 } 1800 1801 DCCppMessage m = new DCCppMessage(DCCppConstants.OUTPUT_CMD); 1802 m.myMessage.append(" ").append(id); 1803 m.myMessage.append(" ").append(pin); 1804 m.myMessage.append(" ").append(iflag); 1805 m.myRegex = DCCppConstants.OUTPUT_ADD_REGEX; 1806 1807 m._nDataChars = m.toString().length(); 1808 return (m); 1809 } 1810 1811 public static DCCppMessage makeOutputDeleteMsg(int id) { 1812 // Sanity check inputs 1813 if (id < 0 || id > DCCppConstants.MAX_TURNOUT_ADDRESS) { 1814 return (null); 1815 } 1816 1817 DCCppMessage m = new DCCppMessage(DCCppConstants.OUTPUT_CMD); 1818 m.myMessage.append(" ").append(id); 1819 m.myRegex = DCCppConstants.OUTPUT_DELETE_REGEX; 1820 1821 m._nDataChars = m.toString().length(); 1822 return (m); 1823 } 1824 1825 public static DCCppMessage makeOutputListMsg() { 1826 return (new DCCppMessage(DCCppConstants.OUTPUT_CMD, DCCppConstants.OUTPUT_LIST_REGEX)); 1827 } 1828 1829 public static DCCppMessage makeTurnoutAddMsg(int id, int addr, int subaddr) { 1830 // Sanity check inputs 1831 if (id < 0 || id > DCCppConstants.MAX_TURNOUT_ADDRESS) { 1832 log.error("turnout Id {} must be between {} and {}", id, 0, DCCppConstants.MAX_TURNOUT_ADDRESS); 1833 return (null); 1834 } 1835 if (addr < 0 || addr > DCCppConstants.MAX_ACC_DECODER_ADDRESS) { 1836 log.error("turnout address {} must be between {} and {}", id, 0, DCCppConstants.MAX_ACC_DECODER_ADDRESS); 1837 return (null); 1838 } 1839 if (subaddr < 0 || subaddr > DCCppConstants.MAX_ACC_DECODER_SUBADDR) { 1840 log.error("turnout subaddress {} must be between {} and {}", id, 0, DCCppConstants.MAX_ACC_DECODER_SUBADDR); 1841 return (null); 1842 } 1843 1844 DCCppMessage m = new DCCppMessage(DCCppConstants.TURNOUT_CMD); 1845 m.myMessage.append(" ").append(id); 1846 m.myMessage.append(" ").append(addr); 1847 m.myMessage.append(" ").append(subaddr); 1848 m.myRegex = DCCppConstants.TURNOUT_ADD_REGEX; 1849 1850 m._nDataChars = m.toString().length(); 1851 return (m); 1852 } 1853 1854 public static DCCppMessage makeTurnoutDeleteMsg(int id) { 1855 // Sanity check inputs 1856 if (id < 0 || id > DCCppConstants.MAX_TURNOUT_ADDRESS) { 1857 return (null); 1858 } 1859 1860 DCCppMessage m = new DCCppMessage(DCCppConstants.TURNOUT_CMD); 1861 m.myMessage.append(" ").append(id); 1862 m.myRegex = DCCppConstants.TURNOUT_DELETE_REGEX; 1863 1864 m._nDataChars = m.toString().length(); 1865 return (m); 1866 } 1867 1868 public static DCCppMessage makeTurnoutListMsg() { 1869 return (new DCCppMessage(DCCppConstants.TURNOUT_CMD, DCCppConstants.TURNOUT_LIST_REGEX)); 1870 } 1871 1872 public static DCCppMessage makeTurnoutIDsMsg() { 1873 DCCppMessage m = makeMessage(DCCppConstants.TURNOUT_IDS); // <JT> 1874 m.myRegex = DCCppConstants.TURNOUT_IDS_REGEX; 1875 m._nDataChars = m.toString().length(); 1876 return (m); 1877 } 1878 public static DCCppMessage makeTurnoutIDMsg(int id) { 1879 DCCppMessage m = makeMessage(DCCppConstants.TURNOUT_IDS + " " + id); //<JT 123> 1880 m.myRegex = DCCppConstants.TURNOUT_ID_REGEX; 1881 m._nDataChars = m.toString().length(); 1882 return (m); 1883 } 1884 public static DCCppMessage makeTurnoutImplMsg(int id) { 1885 DCCppMessage m = makeMessage(DCCppConstants.TURNOUT_CMD + " " + id + " X"); //<T id X> 1886 m.myRegex = DCCppConstants.TURNOUT_IMPL_REGEX; 1887 m._nDataChars = m.toString().length(); 1888 return (m); 1889 } 1890 public static DCCppMessage makeClockRequestTimeMsg() { 1891 DCCppMessage m = makeMessage(DCCppConstants.CLOCK_REQUEST_TIME); // <JC> 1892 m.myRegex = DCCppConstants.CLOCK_REQUEST_TIME_REGEX; 1893 m._nDataChars = m.toString().length(); 1894 return (m); 1895 } 1896 public static DCCppMessage makeClockSetMsg(int minutes, int rate) { 1897 DCCppMessage m = makeMessage(DCCppConstants.CLOCK_REQUEST_TIME + " " + minutes + " " + rate); //<JC 123 12> 1898 m.myRegex = DCCppConstants.CLOCK_SET_REGEX; 1899 m._nDataChars = m.toString().length(); 1900 return (m); 1901 } 1902 public static DCCppMessage makeClockSetMsg(int minutes) { 1903 DCCppMessage m = makeMessage(DCCppConstants.CLOCK_REQUEST_TIME + " " + minutes); //<JC 123> 1904 m.myRegex = DCCppConstants.CLOCK_SET_REGEX; 1905 m._nDataChars = m.toString().length(); 1906 return (m); 1907 } 1908 1909 public static DCCppMessage makeTrackManagerRequestMsg() { 1910 return (new DCCppMessage(DCCppConstants.TRACKMANAGER_CMD, DCCppConstants.TRACKMANAGER_CMD_REGEX)); 1911 } 1912 1913 public static DCCppMessage makeMessage(String msg) { 1914 return (new DCCppMessage(msg)); 1915 } 1916 1917 /** 1918 * Create/Delete/Query Sensor. 1919 * <p> 1920 * sensor, or {@code <X>} if no sensors defined. 1921 * @param id pin pullup (0-32767). 1922 * @param pin Arduino pin index of sensor. 1923 * @param pullup true if use internal pullup for PIN, false if not. 1924 * @return message to create the sensor. 1925 */ 1926 public static DCCppMessage makeSensorAddMsg(int id, int pin, int pullup) { 1927 // Sanity check inputs 1928 // TODO: Optional sanity check pin number vs. Arduino model. 1929 if (id < 0 || id > DCCppConstants.MAX_SENSOR_ID) { 1930 return (null); 1931 } 1932 1933 DCCppMessage m = new DCCppMessage(DCCppConstants.SENSOR_CMD); 1934 m.myMessage.append(" ").append(id); 1935 m.myMessage.append(" ").append(pin); 1936 m.myMessage.append(" ").append(pullup); 1937 m.myRegex = DCCppConstants.SENSOR_ADD_REGEX; 1938 1939 m._nDataChars = m.toString().length(); 1940 return (m); 1941 } 1942 1943 public static DCCppMessage makeSensorDeleteMsg(int id) { 1944 // Sanity check inputs 1945 if (id < 0 || id > DCCppConstants.MAX_SENSOR_ID) { 1946 return (null); 1947 } 1948 1949 DCCppMessage m = new DCCppMessage(DCCppConstants.SENSOR_CMD); 1950 m.myMessage.append(" ").append(id); 1951 m.myRegex = DCCppConstants.SENSOR_DELETE_REGEX; 1952 1953 m._nDataChars = m.toString().length(); 1954 return (m); 1955 } 1956 1957 public static DCCppMessage makeSensorListMsg() { 1958 return (new DCCppMessage(DCCppConstants.SENSOR_CMD, DCCppConstants.SENSOR_LIST_REGEX)); 1959 } 1960 1961 /** 1962 * Query All Sensors States. 1963 * 1964 * @return message to query all sensor states. 1965 */ 1966 public static DCCppMessage makeQuerySensorStatesMsg() { 1967 return (new DCCppMessage(DCCppConstants.QUERY_SENSOR_STATES_CMD, DCCppConstants.QUERY_SENSOR_STATES_REGEX)); 1968 } 1969 1970 /** 1971 * Write Direct CV Byte to Programming Track 1972 * <p> 1973 * Format: {@code <W CV VALUE CALLBACKNUM CALLBACKSUB>} 1974 * <p> 1975 * CV: the number of the Configuration Variable 1976 * memory location in the decoder to write to (1-1024) VALUE: the value to 1977 * be written to the Configuration Variable memory location (0-255) 1978 * CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base 1979 * Station and is simply echoed back in the output - useful for external 1980 * programs that call this function CALLBACKSUB: a second arbitrary integer 1981 * (0-32767) that is ignored by the Base Station and is simply echoed back 1982 * in the output - useful for external programs (e.g. DCC++ Interface) that 1983 * call this function 1984 * <p> 1985 * Note: The two-argument form embeds the opcode in CALLBACKSUB to aid in 1986 * decoding the responses. 1987 * <p> 1988 * returns: {@code <r CALLBACKNUM|CALLBACKSUB|CV Value)} where VALUE is a 1989 * number from 0-255 as read from the requested CV, or -1 if verification 1990 * read fails 1991 * @param cv CV index, 1-1024. 1992 * @param val new CV value, 0-255. 1993 * @return message to write Direct CV. 1994 */ 1995 public static DCCppMessage makeWriteDirectCVMsg(int cv, int val) { 1996 return (makeWriteDirectCVMsg(cv, val, 0, DCCppConstants.PROG_WRITE_CV_BYTE)); 1997 } 1998 1999 public static DCCppMessage makeWriteDirectCVMsg(int cv, int val, int callbacknum, int callbacksub) { 2000 // Sanity check inputs 2001 if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) { 2002 return (null); 2003 } 2004 if (val < 0 || val > DCCppConstants.MAX_DIRECT_CV_VAL) { 2005 return (null); 2006 } 2007 if (callbacknum < 0 || callbacknum > DCCppConstants.MAX_CALLBACK_NUM) { 2008 return (null); 2009 } 2010 if (callbacksub < 0 || callbacksub > DCCppConstants.MAX_CALLBACK_SUB) { 2011 return (null); 2012 } 2013 2014 DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_WRITE_CV_BYTE); 2015 m.myMessage.append(" ").append(cv); 2016 m.myMessage.append(" ").append(val); 2017 m.myMessage.append(" ").append(callbacknum); 2018 m.myMessage.append(" ").append(callbacksub); 2019 m.myRegex = DCCppConstants.PROG_WRITE_BYTE_REGEX; 2020 2021 m._nDataChars = m.toString().length(); 2022 m.setTimeout(DCCppProgrammingTimeout); 2023 return (m); 2024 } 2025 2026 public static DCCppMessage makeWriteDirectCVMsgV4(int cv, int val) { 2027 // Sanity check inputs 2028 if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) { 2029 return (null); 2030 } 2031 if (val < 0 || val > DCCppConstants.MAX_DIRECT_CV_VAL) { 2032 return (null); 2033 } 2034 2035 DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_WRITE_CV_BYTE); 2036 m.myMessage.append(" ").append(cv); 2037 m.myMessage.append(" ").append(val); 2038 m.myRegex = DCCppConstants.PROG_WRITE_BYTE_V4_REGEX; 2039 2040 m._nDataChars = m.toString().length(); 2041 m.setTimeout(DCCppProgrammingTimeout); 2042 return (m); 2043 } 2044 2045 /** 2046 * Write Direct CV Bit to Programming Track. 2047 * <p> 2048 * Format: {@code <B CV BIT VALUE CALLBACKNUM CALLBACKSUB>} 2049 * <p> 2050 * writes, and then verifies, a single bit within a Configuration Variable 2051 * to the decoder of an engine on the programming track 2052 * <p> 2053 * CV: the number of the Configuration Variable memory location in the 2054 * decoder to write to (1-1024) BIT: the bit number of the Configurarion 2055 * Variable memory location to write (0-7) VALUE: the value of the bit to be 2056 * written (0-1) CALLBACKNUM: an arbitrary integer (0-32767) that is ignored 2057 * by the Base Station and is simply echoed back in the output - useful for 2058 * external programs that call this function CALLBACKSUB: a second arbitrary 2059 * integer (0-32767) that is ignored by the Base Station and is simply 2060 * echoed back in the output - useful for external programs (e.g. DCC++ 2061 * Interface) that call this function 2062 * <p> 2063 * Note: The two-argument form embeds the opcode in CALLBACKSUB to aid in 2064 * decoding the responses. 2065 * <p> 2066 * returns: {@code <r CALLBACKNUM|CALLBACKSUB|CV BIT VALUE)} where VALUE is 2067 * a number from 0-1 as read from the requested CV bit, or -1 if 2068 * verification read fails 2069 * @param cv CV index, 1-1024. 2070 * @param bit bit index, 0-7 2071 * @param val bit value, 0-1. 2072 * @return message to write direct CV bit. 2073 */ 2074 public static DCCppMessage makeBitWriteDirectCVMsg(int cv, int bit, int val) { 2075 return (makeBitWriteDirectCVMsg(cv, bit, val, 0, DCCppConstants.PROG_WRITE_CV_BIT)); 2076 } 2077 2078 public static DCCppMessage makeBitWriteDirectCVMsg(int cv, int bit, int val, int callbacknum, int callbacksub) { 2079 2080 // Sanity Check Inputs 2081 if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) { 2082 return (null); 2083 } 2084 if (bit < 0 || bit > 7) { 2085 return (null); 2086 } 2087 if (callbacknum < 0 || callbacknum > DCCppConstants.MAX_CALLBACK_NUM) { 2088 return (null); 2089 } 2090 if (callbacksub < 0 || callbacksub > DCCppConstants.MAX_CALLBACK_SUB) { 2091 return (null); 2092 } 2093 2094 DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_WRITE_CV_BIT); 2095 m.myMessage.append(" ").append(cv); 2096 m.myMessage.append(" ").append(bit); 2097 m.myMessage.append(" ").append(val == 0 ? "0" : "1"); 2098 m.myMessage.append(" ").append(callbacknum); 2099 m.myMessage.append(" ").append(callbacksub); 2100 m.myRegex = DCCppConstants.PROG_WRITE_BIT_REGEX; 2101 2102 m._nDataChars = m.toString().length(); 2103 m.setTimeout(DCCppProgrammingTimeout); 2104 return (m); 2105 } 2106 2107 public static DCCppMessage makeBitWriteDirectCVMsgV4(int cv, int bit, int val) { 2108 // Sanity Check Inputs 2109 if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) { 2110 return (null); 2111 } 2112 if (bit < 0 || bit > 7) { 2113 return (null); 2114 } 2115 2116 DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_WRITE_CV_BIT); 2117 m.myMessage.append(" ").append(cv); 2118 m.myMessage.append(" ").append(bit); 2119 m.myMessage.append(" ").append(val == 0 ? "0" : "1"); 2120 m.myRegex = DCCppConstants.PROG_WRITE_BIT_V4_REGEX; 2121 2122 m._nDataChars = m.toString().length(); 2123 m.setTimeout(DCCppProgrammingTimeout); 2124 return (m); 2125 } 2126 2127 2128 /** 2129 * Read Direct CV Byte from Programming Track. 2130 * <p> 2131 * Format: {@code <R CV CALLBACKNUM CALLBACKSUB>} 2132 * <p> 2133 * reads a Configuration Variable from the decoder of an engine on the 2134 * programming track 2135 * <p> 2136 * CV: the number of the Configuration Variable memory location in the 2137 * decoder to read from (1-1024) CALLBACKNUM: an arbitrary integer (0-32767) 2138 * that is ignored by the Base Station and is simply echoed back in the 2139 * output - useful for external programs that call this function 2140 * CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the 2141 * Base Station and is simply echoed back in the output - useful for 2142 * external programs (e.g. DCC++ Interface) that call this function 2143 * <p> 2144 * Note: The two-argument form embeds the opcode in CALLBACKSUB to aid in 2145 * decoding the responses. 2146 * <p> 2147 * returns: {@code <r CALLBACKNUM|CALLBACKSUB|CV VALUE>} where VALUE is a 2148 * number from 0-255 as read from the requested CV, or -1 if read could not 2149 * be verified 2150 * @param cv CV index. 2151 * @return message to send read direct CV. 2152 */ 2153 public static DCCppMessage makeReadDirectCVMsg(int cv) { 2154 return (makeReadDirectCVMsg(cv, 0, DCCppConstants.PROG_READ_CV)); 2155 } 2156 2157 public static DCCppMessage makeReadDirectCVMsg(int cv, int callbacknum, int callbacksub) { 2158 // Sanity check inputs 2159 if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) { 2160 return (null); 2161 } 2162 if (callbacknum < 0 || callbacknum > DCCppConstants.MAX_CALLBACK_NUM) { 2163 return (null); 2164 } 2165 if (callbacksub < 0 || callbacksub > DCCppConstants.MAX_CALLBACK_SUB) { 2166 return (null); 2167 } 2168 2169 DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_READ_CV); 2170 m.myMessage.append(" ").append(cv); 2171 m.myMessage.append(" ").append(callbacknum); 2172 m.myMessage.append(" ").append(callbacksub); 2173 m.myRegex = DCCppConstants.PROG_READ_CV_REGEX; 2174 2175 m._nDataChars = m.toString().length(); 2176 m.setTimeout(DCCppProgrammingTimeout); 2177 return (m); 2178 } 2179 2180 /** 2181 * Verify Direct CV Byte from Programming Track. 2182 * <p> 2183 * Format: {@code <V CV STARTVAL>} 2184 * <p> 2185 * Verifies a Configuration Variable from the decoder of an engine on the 2186 * programming track. Returns the current value of that CV. 2187 * Used as faster replacement for 'R'eadCV command 2188 * <p> 2189 * CV: the number of the Configuration Variable memory location in the 2190 * decoder to read from (1-1024) STARTVAL: a "guess" as to the current 2191 * value of the CV. DCC-EX will try this value first, then read and return 2192 * the current value if different 2193 * <p> 2194 * returns: {@code <v CV VALUE>} where VALUE is a 2195 * number from 0-255 as read from the requested CV, -1 if read could not 2196 * be performed 2197 * @param cv CV index. 2198 * @param startVal "guess" as to current value 2199 * @return message to send verify direct CV. 2200 */ 2201 public static DCCppMessage makeVerifyCVMsg(int cv, int startVal) { 2202 // Sanity check inputs 2203 if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) { 2204 return (null); 2205 } 2206 DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_VERIFY_CV); 2207 m.myMessage.append(" ").append(cv); 2208 m.myMessage.append(" ").append(startVal); 2209 m.myRegex = DCCppConstants.PROG_VERIFY_REGEX; 2210 2211 m._nDataChars = m.toString().length(); 2212 m.setTimeout(DCCppProgrammingTimeout); 2213 return (m); 2214 } 2215 2216 /** 2217 * Write Direct CV Byte to Main Track 2218 * <p> 2219 * Format: {@code <w CAB CV VALUE>} 2220 * <p> 2221 * Writes, without any verification, a Configuration Variable to the decoder 2222 * of an engine on the main operations track. 2223 * 2224 * @param address the short (1-127) or long (128-10293) address of the 2225 * engine decoder. 2226 * @param cv the number of the Configuration Variable memory location in the 2227 * decoder to write to (1-1024). 2228 * @param val the value to be written to the 2229 * Configuration Variable memory location (0-255). 2230 * @return message to Write CV in Ops Mode. 2231 */ 2232 @CheckForNull 2233 public static DCCppMessage makeWriteOpsModeCVMsg(int address, int cv, int val) { 2234 // Sanity check inputs 2235 if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2236 return (null); 2237 } 2238 if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) { 2239 return (null); 2240 } 2241 if (val < 0 || val > DCCppConstants.MAX_DIRECT_CV_VAL) { 2242 return (null); 2243 } 2244 2245 DCCppMessage m = new DCCppMessage(DCCppConstants.OPS_WRITE_CV_BYTE); 2246 m.myMessage.append(" ").append(address); 2247 m.myMessage.append(" ").append(cv); 2248 m.myMessage.append(" ").append(val); 2249 m.myRegex = DCCppConstants.OPS_WRITE_BYTE_REGEX; 2250 2251 m._nDataChars = m.toString().length(); 2252 m.setTimeout(DCCppProgrammingTimeout); 2253 return (m); 2254 } 2255 2256 /** 2257 * Write Direct CV Bit to Main Track. 2258 * <p> 2259 * Format: {@code <b CAB CV BIT VALUE>} 2260 * <p> 2261 * writes, without any verification, a single bit within a Configuration 2262 * Variable to the decoder of an engine on the main operations track 2263 * <p> 2264 * CAB: the short (1-127) or long (128-10293) address of the engine decoder 2265 * CV: the number of the Configuration Variable memory location in the 2266 * decoder to write to (1-1024) BIT: the bit number of the Configuration 2267 * Variable register to write (0-7) VALUE: the value of the bit to be 2268 * written (0-1) 2269 * <p> 2270 * returns: NONE 2271 * @param address loco cab address. 2272 * @param cv CV index, 1-1024. 2273 * @param bit bit index, 0-7. 2274 * @param val bit value, 0 or 1. 2275 * @return message to write direct CV bit to main track. 2276 */ 2277 public static DCCppMessage makeBitWriteOpsModeCVMsg(int address, int cv, int bit, int val) { 2278 // Sanity Check Inputs 2279 if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2280 return (null); 2281 } 2282 if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) { 2283 return (null); 2284 } 2285 if (bit < 0 || bit > 7) { 2286 return (null); 2287 } 2288 2289 DCCppMessage m = new DCCppMessage(DCCppConstants.OPS_WRITE_CV_BIT); 2290 m.myMessage.append(" ").append(address); 2291 m.myMessage.append(" ").append(cv); 2292 m.myMessage.append(" ").append(bit); 2293 m.myMessage.append(" ").append(val == 0 ? "0" : "1"); 2294 2295 m.myRegex = DCCppConstants.OPS_WRITE_BIT_REGEX; 2296 2297 m._nDataChars = m.toString().length(); 2298 m.setTimeout(DCCppProgrammingTimeout); 2299 return (m); 2300 } 2301 2302 /** 2303 * Set Track Power ON or OFF. 2304 * <p> 2305 * Format: {@code <1> (ON) or <0> (OFF)} 2306 * 2307 * @return message to send track power on or off. 2308 * @param on true on, false off. 2309 */ 2310 public static DCCppMessage makeSetTrackPowerMsg(boolean on) { 2311 return (new DCCppMessage((on ? DCCppConstants.TRACK_POWER_ON : DCCppConstants.TRACK_POWER_OFF), 2312 DCCppConstants.TRACK_POWER_REGEX)); 2313 } 2314 2315 public static DCCppMessage makeTrackPowerOnMsg() { 2316 return (makeSetTrackPowerMsg(true)); 2317 } 2318 2319 public static DCCppMessage makeTrackPowerOffMsg() { 2320 return (makeSetTrackPowerMsg(false)); 2321 } 2322 2323 /** 2324 * Read main operations track current 2325 * <p> 2326 * Format: {@code <c>} 2327 * 2328 * reads current being drawn on main operations track 2329 * 2330 * @return (for DCC-EX), 1 or more of {@code <c MeterName value C/V unit min max res warn>} 2331 * where name and settings are used to define arbitrary meters on the DCC-EX side 2332 * AND {@code <a CURRENT>} where CURRENT = 0-1024, based on 2333 * exponentially-smoothed weighting scheme 2334 * 2335 */ 2336 public static DCCppMessage makeReadTrackCurrentMsg() { 2337 return (new DCCppMessage(DCCppConstants.READ_TRACK_CURRENT, DCCppConstants.READ_TRACK_CURRENT_REGEX)); 2338 } 2339 2340 /** 2341 * Read DCC++ Base Station Status 2342 * <p> 2343 * Format: {@code <s>} 2344 * <p> 2345 * returns status messages containing track power status, throttle status, 2346 * turn-out status, and a version number NOTE: this is very useful as a 2347 * first command for an interface to send to this sketch in order to verify 2348 * connectivity and update any GUI to reflect actual throttle and turn-out 2349 * settings 2350 * 2351 * @return series of status messages that can be read by an interface to 2352 * determine status of DCC++ Base Station and important settings 2353 */ 2354 public static DCCppMessage makeCSStatusMsg() { 2355 return (new DCCppMessage(DCCppConstants.READ_CS_STATUS, DCCppConstants.READ_CS_STATUS_REGEX)); 2356 } 2357 2358 /** 2359 * Get number of supported slots for this DCC++ Base Station Status 2360 * <p> 2361 * Format: {@code <N>} 2362 * <p> 2363 * returns number of slots NOTE: this is not implemented in older versions 2364 * which then do not return anything at all 2365 * 2366 * @return status message with to get number of slots. 2367 */ 2368 public static DCCppMessage makeCSMaxNumSlotsMsg() { 2369 return (new DCCppMessage(DCCppConstants.READ_MAXNUMSLOTS, DCCppConstants.READ_MAXNUMSLOTS_REGEX)); 2370 } 2371 2372 /** 2373 * Generate a function message using the V4 'F' syntax supported by DCC-EX 2374 * @param cab cab address to send function to 2375 * @param func function number to set 2376 * @param state new state of function 0/1 2377 * @return function functionV4message 2378 */ 2379 public static DCCppMessage makeFunctionV4Message(int cab, int func, boolean state) { 2380 // Sanity check inputs 2381 if (cab < 0 || cab > DCCppConstants.MAX_LOCO_ADDRESS) { 2382 return (null); 2383 } 2384 if (func < 0 || func > DCCppConstants.MAX_FUNCTION_NUMBER) { 2385 return (null); 2386 } 2387 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_V4_CMD); 2388 m.myMessage.append(" ").append(cab); 2389 m.myMessage.append(" ").append(func); 2390 m.myMessage.append(" ").append(state?1:0); //1 or 0 for true or false 2391 m.myRegex = DCCppConstants.FUNCTION_V4_CMD_REGEX; 2392 m._nDataChars = m.toString().length(); 2393 return (m); 2394 } 2395 2396 /** 2397 * Generate a "Forget Cab" message '-' 2398 * 2399 * @param cab cab address to send function to (or 0 for all) 2400 * @return forget message to be sent 2401 */ 2402 public static DCCppMessage makeForgetCabMessage(int cab) { 2403 // Sanity check inputs 2404 if (cab < 0 || cab > DCCppConstants.MAX_LOCO_ADDRESS) { 2405 return (null); 2406 } 2407 DCCppMessage m = new DCCppMessage(DCCppConstants.FORGET_CAB_CMD); 2408 if (cab > 0) { 2409 m.myMessage.append(" ").append(cab); 2410 } 2411 m.myRegex = DCCppConstants.FORGET_CAB_CMD_REGEX; 2412 m._nDataChars = m.toString().length(); 2413 return (m); 2414 } 2415 2416 /** 2417 * Generate an emergency stop for the specified address. 2418 * <p> 2419 * Note: This just sends a THROTTLE command with speed = -1 2420 * 2421 * @param register Register Number for the loco assigned address. 2422 * @param address is the locomotive address. 2423 * @return message to send e stop to the specified address. 2424 */ 2425 public static DCCppMessage makeAddressedEmergencyStop(int register, int address) { 2426 // Sanity check inputs 2427 if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2428 return (null); 2429 } 2430 2431 DCCppMessage m = new DCCppMessage(DCCppConstants.THROTTLE_CMD); 2432 m.myMessage.append(" ").append(register); 2433 m.myMessage.append(" ").append(address); 2434 m.myMessage.append(" -1 1"); 2435 m.myRegex = DCCppConstants.THROTTLE_CMD_REGEX; 2436 2437 m._nDataChars = m.toString().length(); 2438 return (m); 2439 } 2440 2441 /** 2442 * Generate an emergency stop for the specified address. 2443 * <p> 2444 * Note: This just sends a THROTTLE command with speed = -1 2445 * 2446 * @param address is the locomotive address. 2447 * @return message to send e stop to the specified address. 2448 */ 2449 public static DCCppMessage makeAddressedEmergencyStop(int address) { 2450 // Sanity check inputs 2451 if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2452 return (null); 2453 } 2454 2455 DCCppMessage m = new DCCppMessage(DCCppConstants.THROTTLE_CMD); 2456 m.myMessage.append(" ").append(address); 2457 m.myMessage.append(" -1 1"); 2458 m.myRegex = DCCppConstants.THROTTLE_V3_CMD_REGEX; 2459 2460 m._nDataChars = m.toString().length(); 2461 return (m); 2462 } 2463 2464 /** 2465 * Generate an emergency stop for all locos in reminder table. 2466 * @return message to send e stop for all locos 2467 */ 2468 public static DCCppMessage makeEmergencyStopAllMsg() { 2469 DCCppMessage m = new DCCppMessage(DCCppConstants.ESTOP_ALL_CMD); 2470 m.myRegex = DCCppConstants.ESTOP_ALL_REGEX; 2471 2472 m._nDataChars = m.toString().length(); 2473 return (m); 2474 } 2475 2476 /** 2477 * Generate a Speed and Direction Request message 2478 * 2479 * @param register is the DCC++ base station register assigned. 2480 * @param address is the locomotive address 2481 * @param speed a normalized speed value (a floating point number 2482 * between 0 and 1). A negative value indicates emergency 2483 * stop. 2484 * @param isForward true for forward, false for reverse. 2485 * 2486 * Format: {@code <t REGISTER CAB SPEED DIRECTION>} 2487 * 2488 * sets the throttle for a given register/cab combination 2489 * 2490 * REGISTER: an internal register number, from 1 through MAX_MAIN_REGISTERS 2491 * (inclusive), to store the DCC packet used to control this throttle 2492 * setting 2493 * CAB: the short (1-127) or long (128-10293) address of the engine decoder 2494 * SPEED: throttle speed from 0-126, or -1 for emergency stop (resets SPEED to 0) 2495 * DIRECTION: 1=forward, 0=reverse. Setting direction 2496 * when speed=0 or speed=-1 only effects directionality of cab lighting for 2497 * a stopped train 2498 * 2499 * @return {@code <T REGISTER CAB SPEED DIRECTION>} 2500 * 2501 */ 2502 public static DCCppMessage makeSpeedAndDirectionMsg(int register, int address, float speed, boolean isForward) { 2503 // Sanity check inputs 2504 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2505 return (null); 2506 } 2507 2508 DCCppMessage m = new DCCppMessage(DCCppConstants.THROTTLE_CMD); 2509 m.myMessage.append(" ").append(register); 2510 m.myMessage.append(" ").append(address); 2511 if (speed < 0.0) { 2512 m.myMessage.append(" -1"); 2513 } else { 2514 int speedVal = java.lang.Math.round(speed * 126); 2515 if (speed > 0 && speedVal == 0) { 2516 speedVal = 1; // ensure non-zero input results in non-zero output 2517 } 2518 speedVal = Math.min(speedVal, DCCppConstants.MAX_SPEED); 2519 m.myMessage.append(" ").append(speedVal); 2520 } 2521 m.myMessage.append(" ").append(isForward ? "1" : "0"); 2522 2523 m.myRegex = DCCppConstants.THROTTLE_CMD_REGEX; 2524 2525 m._nDataChars = m.toString().length(); 2526 return (m); 2527 } 2528 2529 /** 2530 * Generate a Speed and Direction Request message 2531 * 2532 * @param address is the locomotive address 2533 * @param speed a normalized speed value (a floating point number 2534 * between 0 and 1). A negative value indicates emergency 2535 * stop. 2536 * @param isForward true for forward, false for reverse. 2537 * 2538 * Format: {@code <t CAB SPEED DIRECTION>} 2539 * 2540 * sets the throttle for a given register/cab combination 2541 * 2542 * CAB: the short (1-127) or long (128-10293) address of the engine decoder 2543 * SPEED: throttle speed from 0-126, or -1 for emergency stop (resets SPEED to 0) 2544 * DIRECTION: 1=forward, 0=reverse. Setting direction 2545 * when speed=0 or speed=-1 only effects directionality of cab lighting for 2546 * a stopped train 2547 * 2548 * @return {@code <T CAB SPEED DIRECTION>} 2549 * 2550 */ 2551 public static DCCppMessage makeSpeedAndDirectionMsg(int address, float speed, boolean isForward) { 2552 // Sanity check inputs 2553 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2554 return (null); 2555 } 2556 2557 DCCppMessage m = new DCCppMessage(DCCppConstants.THROTTLE_CMD); 2558 m.myMessage.append(" ").append(address); 2559 if (speed < 0.0) { 2560 m.myMessage.append(" -1"); 2561 } else { 2562 int speedVal = java.lang.Math.round(speed * 126); 2563 if (speed > 0 && speedVal == 0) { 2564 speedVal = 1; // ensure non-zero input results in non-zero output 2565 } 2566 speedVal = Math.min(speedVal, DCCppConstants.MAX_SPEED); 2567 m.myMessage.append(" ").append(speedVal); 2568 } 2569 m.myMessage.append(" ").append(isForward ? "1" : "0"); 2570 2571 m.myRegex = DCCppConstants.THROTTLE_V3_CMD_REGEX; 2572 2573 m._nDataChars = m.toString().length(); 2574 return (m); 2575 } 2576 2577 /* 2578 * Function Group Messages (common serial format) 2579 * <p> 2580 * Format: {@code <f CAB BYTE1 [BYTE2]>} 2581 * <p> 2582 * turns on and off engine decoder functions F0-F28 (F0 is sometimes called 2583 * FL) NOTE: setting requests transmitted directly to mobile engine decoder 2584 * --- current state of engine functions is not stored by this program 2585 * <p> 2586 * CAB: the short (1-127) or long (128-10293) address of the engine decoder 2587 * <p> 2588 * To set functions F0-F4 on (=1) or off (=0): 2589 * <p> 2590 * BYTE1: 128 + F1*1 + F2*2 + F3*4 + F4*8 + F0*16 BYTE2: omitted 2591 * <p> 2592 * To set functions F5-F8 on (=1) or off (=0): 2593 * <p> 2594 * BYTE1: 176 + F5*1 + F6*2 + F7*4 + F8*8 BYTE2: omitted 2595 * <p> 2596 * To set functions F9-F12 on (=1) or off (=0): 2597 * <p> 2598 * BYTE1: 160 + F9*1 +F10*2 + F11*4 + F12*8 BYTE2: omitted 2599 * <p> 2600 * To set functions F13-F20 on (=1) or off (=0): 2601 * <p> 2602 * BYTE1: 222 BYTE2: F13*1 + F14*2 + F15*4 + F16*8 + F17*16 + F18*32 + 2603 * F19*64 + F20*128 2604 * <p> 2605 * To set functions F21-F28 on (=1) of off (=0): 2606 * <p> 2607 * BYTE1: 223 BYTE2: F21*1 + F22*2 + F23*4 + F24*8 + F25*16 + F26*32 + 2608 * F27*64 + F28*128 2609 * <p> 2610 * returns: NONE 2611 * <p> 2612 */ 2613 /** 2614 * Generate a Function Group One Operation Request message. 2615 * 2616 * @param address is the locomotive address 2617 * @param f0 is true if f0 is on, false if f0 is off 2618 * @param f1 is true if f1 is on, false if f1 is off 2619 * @param f2 is true if f2 is on, false if f2 is off 2620 * @param f3 is true if f3 is on, false if f3 is off 2621 * @param f4 is true if f4 is on, false if f4 is off 2622 * @return message to set function group 1. 2623 */ 2624 public static DCCppMessage makeFunctionGroup1OpsMsg(int address, 2625 boolean f0, 2626 boolean f1, 2627 boolean f2, 2628 boolean f3, 2629 boolean f4) { 2630 // Sanity check inputs 2631 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2632 return (null); 2633 } 2634 2635 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 2636 m.myMessage.append(" ").append(address); 2637 2638 int byte1 = 128 + (f0 ? 16 : 0); 2639 byte1 += (f1 ? 1 : 0); 2640 byte1 += (f2 ? 2 : 0); 2641 byte1 += (f3 ? 4 : 0); 2642 byte1 += (f4 ? 8 : 0); 2643 m.myMessage.append(" ").append(byte1); 2644 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 2645 2646 m._nDataChars = m.toString().length(); 2647 return (m); 2648 } 2649 2650 /** 2651 * Generate a Function Group One Set Momentary Functions message. 2652 * 2653 * @param address is the locomotive address 2654 * @param f0 is true if f0 is momentary 2655 * @param f1 is true if f1 is momentary 2656 * @param f2 is true if f2 is momentary 2657 * @param f3 is true if f3 is momentary 2658 * @param f4 is true if f4 is momentary 2659 * @return message to set momentary function group 1. 2660 */ 2661 public static DCCppMessage makeFunctionGroup1SetMomMsg(int address, 2662 boolean f0, 2663 boolean f1, 2664 boolean f2, 2665 boolean f3, 2666 boolean f4) { 2667 2668 // Sanity check inputs 2669 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2670 return (null); 2671 } 2672 2673 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 2674 m.myMessage.append(" ").append(address); 2675 2676 int byte1 = 128 + (f0 ? 16 : 0); 2677 byte1 += (f1 ? 1 : 0); 2678 byte1 += (f2 ? 2 : 0); 2679 byte1 += (f3 ? 4 : 0); 2680 byte1 += (f4 ? 8 : 0); 2681 2682 m.myMessage.append(" ").append(byte1); 2683 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 2684 2685 m._nDataChars = m.toString().length(); 2686 return (m); 2687 } 2688 2689 /** 2690 * Generate a Function Group Two Operation Request message. 2691 * 2692 * @param address is the locomotive address 2693 * @param f5 is true if f5 is on, false if f5 is off 2694 * @param f6 is true if f6 is on, false if f6 is off 2695 * @param f7 is true if f7 is on, false if f7 is off 2696 * @param f8 is true if f8 is on, false if f8 is off 2697 * @return message to set function group 2. 2698 */ 2699 public static DCCppMessage makeFunctionGroup2OpsMsg(int address, 2700 boolean f5, 2701 boolean f6, 2702 boolean f7, 2703 boolean f8) { 2704 2705 // Sanity check inputs 2706 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2707 return (null); 2708 } 2709 2710 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 2711 m.myMessage.append(" ").append(address); 2712 2713 int byte1 = 176; 2714 byte1 += (f5 ? 1 : 0); 2715 byte1 += (f6 ? 2 : 0); 2716 byte1 += (f7 ? 4 : 0); 2717 byte1 += (f8 ? 8 : 0); 2718 2719 m.myMessage.append(" ").append(byte1); 2720 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 2721 2722 m._nDataChars = m.toString().length(); 2723 return (m); 2724 } 2725 2726 /** 2727 * Generate a Function Group Two Set Momentary Functions message. 2728 * 2729 * @param address is the locomotive address 2730 * @param f5 is true if f5 is momentary 2731 * @param f6 is true if f6 is momentary 2732 * @param f7 is true if f7 is momentary 2733 * @param f8 is true if f8 is momentary 2734 * @return message to set momentary function group 2. 2735 */ 2736 public static DCCppMessage makeFunctionGroup2SetMomMsg(int address, 2737 boolean f5, 2738 boolean f6, 2739 boolean f7, 2740 boolean f8) { 2741 2742 // Sanity check inputs 2743 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2744 return (null); 2745 } 2746 2747 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 2748 m.myMessage.append(" ").append(address); 2749 2750 int byte1 = 176; 2751 byte1 += (f5 ? 1 : 0); 2752 byte1 += (f6 ? 2 : 0); 2753 byte1 += (f7 ? 4 : 0); 2754 byte1 += (f8 ? 8 : 0); 2755 m.myMessage.append(" ").append(byte1); 2756 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 2757 2758 m._nDataChars = m.toString().length(); 2759 return (m); 2760 } 2761 2762 /** 2763 * Generate a Function Group Three Operation Request message. 2764 * 2765 * @param address is the locomotive address 2766 * @param f9 is true if f9 is on, false if f9 is off 2767 * @param f10 is true if f10 is on, false if f10 is off 2768 * @param f11 is true if f11 is on, false if f11 is off 2769 * @param f12 is true if f12 is on, false if f12 is off 2770 * @return message to set function group 3. 2771 */ 2772 public static DCCppMessage makeFunctionGroup3OpsMsg(int address, 2773 boolean f9, 2774 boolean f10, 2775 boolean f11, 2776 boolean f12) { 2777 2778 // Sanity check inputs 2779 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2780 return (null); 2781 } 2782 2783 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 2784 m.myMessage.append(" ").append(address); 2785 2786 int byte1 = 160; 2787 byte1 += (f9 ? 1 : 0); 2788 byte1 += (f10 ? 2 : 0); 2789 byte1 += (f11 ? 4 : 0); 2790 byte1 += (f12 ? 8 : 0); 2791 m.myMessage.append(" ").append(byte1); 2792 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 2793 2794 m._nDataChars = m.toString().length(); 2795 return (m); 2796 } 2797 2798 /** 2799 * Generate a Function Group Three Set Momentary Functions message. 2800 * 2801 * @param address is the locomotive address 2802 * @param f9 is true if f9 is momentary 2803 * @param f10 is true if f10 is momentary 2804 * @param f11 is true if f11 is momentary 2805 * @param f12 is true if f12 is momentary 2806 * @return message to set momentary function group 3. 2807 */ 2808 public static DCCppMessage makeFunctionGroup3SetMomMsg(int address, 2809 boolean f9, 2810 boolean f10, 2811 boolean f11, 2812 boolean f12) { 2813 2814 // Sanity check inputs 2815 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2816 return (null); 2817 } 2818 2819 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 2820 m.myMessage.append(" ").append(address); 2821 2822 int byte1 = 160; 2823 byte1 += (f9 ? 1 : 0); 2824 byte1 += (f10 ? 2 : 0); 2825 byte1 += (f11 ? 4 : 0); 2826 byte1 += (f12 ? 8 : 0); 2827 m.myMessage.append(" ").append(byte1); 2828 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 2829 2830 m._nDataChars = m.toString().length(); 2831 return (m); 2832 } 2833 2834 /** 2835 * Generate a Function Group Four Operation Request message. 2836 * 2837 * @param address is the locomotive address 2838 * @param f13 is true if f13 is on, false if f13 is off 2839 * @param f14 is true if f14 is on, false if f14 is off 2840 * @param f15 is true if f15 is on, false if f15 is off 2841 * @param f16 is true if f18 is on, false if f16 is off 2842 * @param f17 is true if f17 is on, false if f17 is off 2843 * @param f18 is true if f18 is on, false if f18 is off 2844 * @param f19 is true if f19 is on, false if f19 is off 2845 * @param f20 is true if f20 is on, false if f20 is off 2846 * @return message to set function group 4. 2847 */ 2848 public static DCCppMessage makeFunctionGroup4OpsMsg(int address, 2849 boolean f13, 2850 boolean f14, 2851 boolean f15, 2852 boolean f16, 2853 boolean f17, 2854 boolean f18, 2855 boolean f19, 2856 boolean f20) { 2857 2858 // Sanity check inputs 2859 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2860 return (null); 2861 } 2862 2863 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 2864 m.myMessage.append(" ").append(address); 2865 2866 int byte2 = 0; 2867 byte2 += (f13 ? 1 : 0); 2868 byte2 += (f14 ? 2 : 0); 2869 byte2 += (f15 ? 4 : 0); 2870 byte2 += (f16 ? 8 : 0); 2871 byte2 += (f17 ? 16 : 0); 2872 byte2 += (f18 ? 32 : 0); 2873 byte2 += (f19 ? 64 : 0); 2874 byte2 += (f20 ? 128 : 0); 2875 m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP4_BYTE1); 2876 m.myMessage.append(" ").append(byte2); 2877 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 2878 2879 m._nDataChars = m.toString().length(); 2880 return (m); 2881 } 2882 2883 /** 2884 * Generate a Function Group Four Set Momentary Function message. 2885 * 2886 * @param address is the locomotive address 2887 * @param f13 is true if f13 is Momentary 2888 * @param f14 is true if f14 is Momentary 2889 * @param f15 is true if f15 is Momentary 2890 * @param f16 is true if f18 is Momentary 2891 * @param f17 is true if f17 is Momentary 2892 * @param f18 is true if f18 is Momentary 2893 * @param f19 is true if f19 is Momentary 2894 * @param f20 is true if f20 is Momentary 2895 * @return message to set momentary function group 4. 2896 */ 2897 public static DCCppMessage makeFunctionGroup4SetMomMsg(int address, 2898 boolean f13, 2899 boolean f14, 2900 boolean f15, 2901 boolean f16, 2902 boolean f17, 2903 boolean f18, 2904 boolean f19, 2905 boolean f20) { 2906 2907 // Sanity check inputs 2908 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2909 return (null); 2910 } 2911 2912 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 2913 m.myMessage.append(" ").append(address); 2914 2915 int byte2 = 0; 2916 byte2 += (f13 ? 1 : 0); 2917 byte2 += (f14 ? 2 : 0); 2918 byte2 += (f15 ? 4 : 0); 2919 byte2 += (f16 ? 8 : 0); 2920 byte2 += (f17 ? 16 : 0); 2921 byte2 += (f18 ? 32 : 0); 2922 byte2 += (f19 ? 64 : 0); 2923 byte2 += (f20 ? 128 : 0); 2924 2925 m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP4_BYTE1); 2926 m.myMessage.append(" ").append(byte2); 2927 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 2928 2929 m._nDataChars = m.toString().length(); 2930 return (m); 2931 } 2932 2933 /** 2934 * Generate a Function Group Five Operation Request message. 2935 * 2936 * @param address is the locomotive address 2937 * @param f21 is true if f21 is on, false if f21 is off 2938 * @param f22 is true if f22 is on, false if f22 is off 2939 * @param f23 is true if f23 is on, false if f23 is off 2940 * @param f24 is true if f24 is on, false if f24 is off 2941 * @param f25 is true if f25 is on, false if f25 is off 2942 * @param f26 is true if f26 is on, false if f26 is off 2943 * @param f27 is true if f27 is on, false if f27 is off 2944 * @param f28 is true if f28 is on, false if f28 is off 2945 * @return message to set function group 5. 2946 */ 2947 public static DCCppMessage makeFunctionGroup5OpsMsg(int address, 2948 boolean f21, 2949 boolean f22, 2950 boolean f23, 2951 boolean f24, 2952 boolean f25, 2953 boolean f26, 2954 boolean f27, 2955 boolean f28) { 2956 // Sanity check inputs 2957 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2958 return (null); 2959 } 2960 2961 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 2962 m.myMessage.append(" ").append(address); 2963 2964 int byte2 = 0; 2965 byte2 += (f21 ? 1 : 0); 2966 byte2 += (f22 ? 2 : 0); 2967 byte2 += (f23 ? 4 : 0); 2968 byte2 += (f24 ? 8 : 0); 2969 byte2 += (f25 ? 16 : 0); 2970 byte2 += (f26 ? 32 : 0); 2971 byte2 += (f27 ? 64 : 0); 2972 byte2 += (f28 ? 128 : 0); 2973 log.debug("DCCppMessage: Byte2 = {}", byte2); 2974 2975 m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP5_BYTE1); 2976 m.myMessage.append(" ").append(byte2); 2977 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 2978 2979 m._nDataChars = m.toString().length(); 2980 return (m); 2981 } 2982 2983 /** 2984 * Generate a Function Group Five Set Momentary Function message. 2985 * 2986 * @param address is the locomotive address 2987 * @param f21 is true if f21 is momentary 2988 * @param f22 is true if f22 is momentary 2989 * @param f23 is true if f23 is momentary 2990 * @param f24 is true if f24 is momentary 2991 * @param f25 is true if f25 is momentary 2992 * @param f26 is true if f26 is momentary 2993 * @param f27 is true if f27 is momentary 2994 * @param f28 is true if f28 is momentary 2995 * @return message to set momentary function group 5. 2996 */ 2997 public static DCCppMessage makeFunctionGroup5SetMomMsg(int address, 2998 boolean f21, 2999 boolean f22, 3000 boolean f23, 3001 boolean f24, 3002 boolean f25, 3003 boolean f26, 3004 boolean f27, 3005 boolean f28) { 3006 3007 // Sanity check inputs 3008 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 3009 return (null); 3010 } 3011 3012 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 3013 m.myMessage.append(" ").append(address); 3014 3015 int byte2 = 0; 3016 byte2 += (f21 ? 1 : 0); 3017 byte2 += (f22 ? 2 : 0); 3018 byte2 += (f23 ? 4 : 0); 3019 byte2 += (f24 ? 8 : 0); 3020 byte2 += (f25 ? 16 : 0); 3021 byte2 += (f26 ? 32 : 0); 3022 byte2 += (f27 ? 64 : 0); 3023 byte2 += (f28 ? 128 : 0); 3024 3025 m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP5_BYTE1); 3026 m.myMessage.append(" ").append(byte2); 3027 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 3028 3029 m._nDataChars = m.toString().length(); 3030 return (m); 3031 } 3032 3033 /* 3034 * Build an Emergency Off Message 3035 */ 3036 3037 /* 3038 * Test Code Functions... not for normal use 3039 */ 3040 3041 /** 3042 * Write DCC Packet to a specified Register on the Main. 3043 * <br> 3044 * DCC++ BaseStation code appends its own error-correction byte so we must 3045 * not provide one. 3046 * 3047 * @param register the DCC++ BaseStation main register number to use 3048 * @param numBytes the number of bytes in the packet 3049 * @param bytes byte array representing the packet. The first 3050 * {@code num_bytes} are used. 3051 * @return the formatted message to send 3052 */ 3053 public static DCCppMessage makeWriteDCCPacketMainMsg(int register, int numBytes, byte[] bytes) { 3054 // Sanity Check Inputs 3055 if (register < 0 || register > DCCppConstants.MAX_MAIN_REGISTERS || numBytes < 2 || numBytes > 5) { 3056 return (null); 3057 } 3058 3059 DCCppMessage m = new DCCppMessage(DCCppConstants.WRITE_DCC_PACKET_MAIN); 3060 m.myMessage.append(" ").append(register); 3061 for (int k = 0; k < numBytes; k++) { 3062 m.myMessage.append(" ").append(jmri.util.StringUtil.twoHexFromInt(bytes[k])); 3063 } 3064 m.myRegex = DCCppConstants.WRITE_DCC_PACKET_MAIN_REGEX; 3065 return (m); 3066 3067 } 3068 3069 /** 3070 * Write DCC Packet to a specified Register on the Programming Track. 3071 * <br><br> 3072 * DCC++ BaseStation code appends its own error-correction byte so we must 3073 * not provide one. 3074 * 3075 * @param register the DCC++ BaseStation main register number to use 3076 * @param numBytes the number of bytes in the packet 3077 * @param bytes byte array representing the packet. The first 3078 * {@code num_bytes} are used. 3079 * @return the formatted message to send 3080 */ 3081 public static DCCppMessage makeWriteDCCPacketProgMsg(int register, int numBytes, byte[] bytes) { 3082 // Sanity Check Inputs 3083 if (register < 0 || register > DCCppConstants.MAX_MAIN_REGISTERS || numBytes < 2 || numBytes > 5) { 3084 return (null); 3085 } 3086 3087 DCCppMessage m = new DCCppMessage(DCCppConstants.WRITE_DCC_PACKET_PROG); 3088 m.myMessage.append(" ").append(register); 3089 for (int k = 0; k < numBytes; k++) { 3090 m.myMessage.append(" ").append(jmri.util.StringUtil.twoHexFromInt(bytes[k])); 3091 } 3092 m.myRegex = DCCppConstants.WRITE_DCC_PACKET_PROG_REGEX; 3093 return (m); 3094 3095 } 3096 3097// public static DCCppMessage makeCheckFreeMemMsg() { 3098// return (new DCCppMessage(DCCppConstants.GET_FREE_MEMORY, DCCppConstants.GET_FREE_MEMORY_REGEX)); 3099// } 3100// 3101 public static DCCppMessage makeListRegisterContentsMsg() { 3102 return (new DCCppMessage(DCCppConstants.LIST_REGISTER_CONTENTS, 3103 DCCppConstants.LIST_REGISTER_CONTENTS_REGEX)); 3104 } 3105 /** 3106 * Request LCD Messages used for Virtual LCD Display 3107 * <p> 3108 * Format: {@code <@>} 3109 * <p> 3110 * tells EX_CommandStation to send any LCD message updates to this instance of JMRI 3111 * @return the formatted message to send 3112 */ 3113 public static DCCppMessage makeLCDRequestMsg() { 3114 return (new DCCppMessage(DCCppConstants.LCD_TEXT_CMD, DCCppConstants.LCD_TEXT_CMD_REGEX)); 3115 } 3116 3117 3118 /** 3119 * This implementation of equals is targeted to the background function 3120 * refreshing in SerialDCCppPacketizer. To keep only one function group in 3121 * the refresh queue the logic is as follows. Two messages are equal if they 3122 * are: 3123 * <ul> 3124 * <li>actually identical, or</li> 3125 * <li>a function call to the same address and same function group</li> 3126 * </ul> 3127 */ 3128 @Override 3129 public boolean equals(final Object obj) { 3130 if (obj == null) { 3131 return false; 3132 } 3133 3134 if (!(obj instanceof DCCppMessage)) { 3135 return false; 3136 } 3137 3138 final DCCppMessage other = (DCCppMessage) obj; 3139 3140 final String myCmd = this.toString(); 3141 final String otherCmd = other.toString(); 3142 3143 if (myCmd.equals(otherCmd)) { 3144 return true; 3145 } 3146 3147 if (!(myCmd.charAt(0) == DCCppConstants.FUNCTION_CMD) || !(otherCmd.charAt(0) == DCCppConstants.FUNCTION_CMD)) { 3148 return false; 3149 } 3150 3151 final int mySpace1 = myCmd.indexOf(' ', 2); 3152 final int otherSpace1 = otherCmd.indexOf(' ', 2); 3153 3154 if (mySpace1 != otherSpace1) { 3155 return false; 3156 } 3157 3158 if (!myCmd.subSequence(2, mySpace1).equals(otherCmd.subSequence(2, otherSpace1))) { 3159 return false; 3160 } 3161 3162 int mySpace2 = myCmd.indexOf(' ', mySpace1 + 1); 3163 if (mySpace2 < 0) { 3164 mySpace2 = myCmd.length(); 3165 } 3166 3167 int otherSpace2 = otherCmd.indexOf(' ', otherSpace1 + 1); 3168 if (otherSpace2 < 0) { 3169 otherSpace2 = otherCmd.length(); 3170 } 3171 3172 final int myBaseFunction = Integer.parseInt(myCmd.substring(mySpace1 + 1, mySpace2)); 3173 final int otherBaseFunction = Integer.parseInt(otherCmd.substring(otherSpace1 + 1, otherSpace2)); 3174 3175 if (myBaseFunction == otherBaseFunction) { 3176 return true; 3177 } 3178 3179 return getFuncBaseByte1(myBaseFunction) == getFuncBaseByte1(otherBaseFunction); 3180 } 3181 3182 @Override 3183 public int hashCode() { 3184 return toString().hashCode(); 3185 } 3186 3187 /** 3188 * Get the function group from the first byte of the function setting call. 3189 * 3190 * @param byte1 first byte (mixed in with function bits for groups 1 to 3, 3191 * or standalone value for groups 4 and 5) 3192 * @return the base group 3193 */ 3194 private static int getFuncBaseByte1(final int byte1) { 3195 if (byte1 == DCCppConstants.FUNCTION_GROUP4_BYTE1 || byte1 == DCCppConstants.FUNCTION_GROUP5_BYTE1) { 3196 return byte1; 3197 } 3198 3199 if (byte1 < 160) { 3200 return 128; 3201 } 3202 3203 if (byte1 < 176) { 3204 return 160; 3205 } 3206 3207 return 176; 3208 } 3209 3210 /** 3211 * When is this message supposed to be resent? 3212 */ 3213 private long expireTime; 3214 3215 /** 3216 * Before adding the message to the delay queue call this method to set when 3217 * the message should be repeated. The only time guarantee is that it will 3218 * be repeated after <u>at least</u> this much time, but it can be 3219 * significantly longer until it is repeated, function of the message queue 3220 * length. 3221 * 3222 * @param millis milliseconds in the future 3223 */ 3224 public void delayFor(final long millis) { 3225 expireTime = System.currentTimeMillis() + millis; 3226 } 3227 3228 /** 3229 * Comparing two queued message for refreshing the function calls, based on 3230 * their expected execution time. 3231 */ 3232 @Override 3233 public int compareTo(@Nonnull final Delayed o) { 3234 final long diff = this.expireTime - ((DCCppMessage) o).expireTime; 3235 3236 if (diff < 0) { 3237 return -1; 3238 } 3239 3240 if (diff > 0) { 3241 return 1; 3242 } 3243 3244 return 0; 3245 } 3246 3247 /** 3248 * From the {@link Delayed} interface, how long this message still has until 3249 * it should be executed. 3250 */ 3251 @Override 3252 public long getDelay(final TimeUnit unit) { 3253 return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); 3254 } 3255 3256 // initialize logging 3257 private static final Logger log = LoggerFactory.getLogger(DCCppMessage.class); 3258 3259}