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