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