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-EX. 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-EX has a maximum timing 051 interval of 500 milliseconds during normal communications */ 052 protected static final int DCCppProgrammingTimeout = 10000; // TODO: Appropriate value for DCC-EX? 053 private static int DCCppMessageTimeout = 5000; // TODO: Appropriate value for DCC-EX? 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-EX 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-EX, 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-EX 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-EX 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-EX 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-EX 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 1989 public static DCCppMessage makeStartExrailMsg(int id) { 1990 DCCppMessage m = makeMessage("/ START " + id); // </ START id> 1991 m.myRegex = DCCppConstants.CONTROL_CMD_REGEX; 1992 m._nDataChars = m.toString().length(); 1993 return (m); 1994 } 1995 public static DCCppMessage makeStartExrailMsg(int id, int address) { 1996 DCCppMessage m = makeMessage("/ START " + address + " " + id); // </ START cab id> 1997 m.myRegex = DCCppConstants.CONTROL_CMD_REGEX; 1998 m._nDataChars = m.toString().length(); 1999 return (m); 2000 } 2001 public static DCCppMessage makeCurrentMaxesMsg() { 2002 DCCppMessage m = makeMessage(DCCppConstants.CURRENT_MAXES); // <JG> 2003 m.myRegex = DCCppConstants.CURRENT_MAXES_REGEX; 2004 m._nDataChars = m.toString().length(); 2005 return (m); 2006 } 2007 public static DCCppMessage makeCurrentValuesMsg() { 2008 DCCppMessage m = makeMessage(DCCppConstants.CURRENT_VALUES); // <JI> 2009 m.myRegex = DCCppConstants.CURRENT_VALUES_REGEX; 2010 m._nDataChars = m.toString().length(); 2011 return (m); 2012 } 2013 2014 public static DCCppMessage makeClockRequestTimeMsg() { 2015 DCCppMessage m = makeMessage(DCCppConstants.CLOCK_REQUEST_TIME); // <JC> 2016 m.myRegex = DCCppConstants.CLOCK_REQUEST_TIME_REGEX; 2017 m._nDataChars = m.toString().length(); 2018 return (m); 2019 } 2020 public static DCCppMessage makeClockSetMsg(int minutes, int rate) { 2021 DCCppMessage m = makeMessage(DCCppConstants.CLOCK_REQUEST_TIME + " " + minutes + " " + rate); //<JC 123 12> 2022 m.myRegex = DCCppConstants.CLOCK_SET_REGEX; 2023 m._nDataChars = m.toString().length(); 2024 return (m); 2025 } 2026 public static DCCppMessage makeClockSetMsg(int minutes) { 2027 DCCppMessage m = makeMessage(DCCppConstants.CLOCK_REQUEST_TIME + " " + minutes); //<JC 123> 2028 m.myRegex = DCCppConstants.CLOCK_SET_REGEX; 2029 m._nDataChars = m.toString().length(); 2030 return (m); 2031 } 2032 2033 public static DCCppMessage makeTrackManagerRequestMsg() { 2034 return (new DCCppMessage(DCCppConstants.TRACKMANAGER_CMD, DCCppConstants.TRACKMANAGER_CMD_REGEX)); 2035 } 2036 2037 public static DCCppMessage makeMessage(String msg) { 2038 return (new DCCppMessage(msg)); 2039 } 2040 2041 /** 2042 * Create/Delete/Query Sensor. 2043 * <p> 2044 * sensor, or {@code <X>} if no sensors defined. 2045 * @param id pin pullup (0-32767). 2046 * @param pin Arduino pin index of sensor. 2047 * @param pullup true if use internal pullup for PIN, false if not. 2048 * @return message to create the sensor. 2049 */ 2050 public static DCCppMessage makeSensorAddMsg(int id, int pin, int pullup) { 2051 // Sanity check inputs 2052 // TODO: Optional sanity check pin number vs. Arduino model. 2053 if (id < 0 || id > DCCppConstants.MAX_SENSOR_ID) { 2054 return (null); 2055 } 2056 2057 DCCppMessage m = new DCCppMessage(DCCppConstants.SENSOR_CMD); 2058 m.myMessage.append(" ").append(id); 2059 m.myMessage.append(" ").append(pin); 2060 m.myMessage.append(" ").append(pullup); 2061 m.myRegex = DCCppConstants.SENSOR_ADD_REGEX; 2062 2063 m._nDataChars = m.toString().length(); 2064 return (m); 2065 } 2066 2067 public static DCCppMessage makeSensorDeleteMsg(int id) { 2068 // Sanity check inputs 2069 if (id < 0 || id > DCCppConstants.MAX_SENSOR_ID) { 2070 return (null); 2071 } 2072 2073 DCCppMessage m = new DCCppMessage(DCCppConstants.SENSOR_CMD); 2074 m.myMessage.append(" ").append(id); 2075 m.myRegex = DCCppConstants.SENSOR_DELETE_REGEX; 2076 2077 m._nDataChars = m.toString().length(); 2078 return (m); 2079 } 2080 2081 public static DCCppMessage makeSensorListMsg() { 2082 return (new DCCppMessage(DCCppConstants.SENSOR_CMD, DCCppConstants.SENSOR_LIST_REGEX)); 2083 } 2084 2085 /** 2086 * Query All Sensors States. 2087 * 2088 * @return message to query all sensor states. 2089 */ 2090 public static DCCppMessage makeQuerySensorStatesMsg() { 2091 return (new DCCppMessage(DCCppConstants.QUERY_SENSOR_STATES_CMD, DCCppConstants.QUERY_SENSOR_STATES_REGEX)); 2092 } 2093 2094 /** 2095 * Write Direct CV Byte to Programming Track 2096 * <p> 2097 * Format: {@code <W CV VALUE CALLBACKNUM CALLBACKSUB>} 2098 * <p> 2099 * CV: the number of the Configuration Variable 2100 * memory location in the decoder to write to (1-1024) VALUE: the value to 2101 * be written to the Configuration Variable memory location (0-255) 2102 * CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base 2103 * Station and is simply echoed back in the output - useful for external 2104 * programs that call this function CALLBACKSUB: a second arbitrary integer 2105 * (0-32767) that is ignored by the Base Station and is simply echoed back 2106 * in the output - useful for external programs (e.g. DCC-EX Interface) that 2107 * call this function 2108 * <p> 2109 * Note: The two-argument form embeds the opcode in CALLBACKSUB to aid in 2110 * decoding the responses. 2111 * <p> 2112 * returns: {@code <r CALLBACKNUM|CALLBACKSUB|CV Value)} where VALUE is a 2113 * number from 0-255 as read from the requested CV, or -1 if verification 2114 * read fails 2115 * @param cv CV index, 1-1024. 2116 * @param val new CV value, 0-255. 2117 * @return message to write Direct CV. 2118 */ 2119 public static DCCppMessage makeWriteDirectCVMsg(int cv, int val) { 2120 return (makeWriteDirectCVMsg(cv, val, 0, DCCppConstants.PROG_WRITE_CV_BYTE)); 2121 } 2122 2123 public static DCCppMessage makeWriteDirectCVMsg(int cv, int val, int callbacknum, int callbacksub) { 2124 // Sanity check inputs 2125 if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) { 2126 return (null); 2127 } 2128 if (val < 0 || val > DCCppConstants.MAX_DIRECT_CV_VAL) { 2129 return (null); 2130 } 2131 if (callbacknum < 0 || callbacknum > DCCppConstants.MAX_CALLBACK_NUM) { 2132 return (null); 2133 } 2134 if (callbacksub < 0 || callbacksub > DCCppConstants.MAX_CALLBACK_SUB) { 2135 return (null); 2136 } 2137 2138 DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_WRITE_CV_BYTE); 2139 m.myMessage.append(" ").append(cv); 2140 m.myMessage.append(" ").append(val); 2141 m.myMessage.append(" ").append(callbacknum); 2142 m.myMessage.append(" ").append(callbacksub); 2143 m.myRegex = DCCppConstants.PROG_WRITE_BYTE_REGEX; 2144 2145 m._nDataChars = m.toString().length(); 2146 m.setTimeout(DCCppProgrammingTimeout); 2147 return (m); 2148 } 2149 2150 public static DCCppMessage makeWriteDirectCVMsgV4(int cv, int val) { 2151 // Sanity check inputs 2152 if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) { 2153 return (null); 2154 } 2155 if (val < 0 || val > DCCppConstants.MAX_DIRECT_CV_VAL) { 2156 return (null); 2157 } 2158 2159 DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_WRITE_CV_BYTE); 2160 m.myMessage.append(" ").append(cv); 2161 m.myMessage.append(" ").append(val); 2162 m.myRegex = DCCppConstants.PROG_WRITE_BYTE_V4_REGEX; 2163 2164 m._nDataChars = m.toString().length(); 2165 m.setTimeout(DCCppProgrammingTimeout); 2166 return (m); 2167 } 2168 2169 /** 2170 * Write Direct CV Bit to Programming Track. 2171 * <p> 2172 * Format: {@code <B CV BIT VALUE CALLBACKNUM CALLBACKSUB>} 2173 * <p> 2174 * writes, and then verifies, a single bit within a Configuration Variable 2175 * to the decoder of an engine on the programming track 2176 * <p> 2177 * CV: the number of the Configuration Variable memory location in the 2178 * decoder to write to (1-1024) BIT: the bit number of the Configurarion 2179 * Variable memory location to write (0-7) VALUE: the value of the bit to be 2180 * written (0-1) CALLBACKNUM: an arbitrary integer (0-32767) that is ignored 2181 * by the Base Station and is simply echoed back in the output - useful for 2182 * external programs that call this function CALLBACKSUB: a second arbitrary 2183 * integer (0-32767) that is ignored by the Base Station and is simply 2184 * echoed back in the output - useful for external programs (e.g. DCC-EX 2185 * Interface) that call this function 2186 * <p> 2187 * Note: The two-argument form embeds the opcode in CALLBACKSUB to aid in 2188 * decoding the responses. 2189 * <p> 2190 * returns: {@code <r CALLBACKNUM|CALLBACKSUB|CV BIT VALUE)} where VALUE is 2191 * a number from 0-1 as read from the requested CV bit, or -1 if 2192 * verification read fails 2193 * @param cv CV index, 1-1024. 2194 * @param bit bit index, 0-7 2195 * @param val bit value, 0-1. 2196 * @return message to write direct CV bit. 2197 */ 2198 public static DCCppMessage makeBitWriteDirectCVMsg(int cv, int bit, int val) { 2199 return (makeBitWriteDirectCVMsg(cv, bit, val, 0, DCCppConstants.PROG_WRITE_CV_BIT)); 2200 } 2201 2202 public static DCCppMessage makeBitWriteDirectCVMsg(int cv, int bit, int val, int callbacknum, int callbacksub) { 2203 2204 // Sanity Check Inputs 2205 if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) { 2206 return (null); 2207 } 2208 if (bit < 0 || bit > 7) { 2209 return (null); 2210 } 2211 if (callbacknum < 0 || callbacknum > DCCppConstants.MAX_CALLBACK_NUM) { 2212 return (null); 2213 } 2214 if (callbacksub < 0 || callbacksub > DCCppConstants.MAX_CALLBACK_SUB) { 2215 return (null); 2216 } 2217 2218 DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_WRITE_CV_BIT); 2219 m.myMessage.append(" ").append(cv); 2220 m.myMessage.append(" ").append(bit); 2221 m.myMessage.append(" ").append(val == 0 ? "0" : "1"); 2222 m.myMessage.append(" ").append(callbacknum); 2223 m.myMessage.append(" ").append(callbacksub); 2224 m.myRegex = DCCppConstants.PROG_WRITE_BIT_REGEX; 2225 2226 m._nDataChars = m.toString().length(); 2227 m.setTimeout(DCCppProgrammingTimeout); 2228 return (m); 2229 } 2230 2231 public static DCCppMessage makeBitWriteDirectCVMsgV4(int cv, int bit, int val) { 2232 // Sanity Check Inputs 2233 if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) { 2234 return (null); 2235 } 2236 if (bit < 0 || bit > 7) { 2237 return (null); 2238 } 2239 2240 DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_WRITE_CV_BIT); 2241 m.myMessage.append(" ").append(cv); 2242 m.myMessage.append(" ").append(bit); 2243 m.myMessage.append(" ").append(val == 0 ? "0" : "1"); 2244 m.myRegex = DCCppConstants.PROG_WRITE_BIT_V4_REGEX; 2245 2246 m._nDataChars = m.toString().length(); 2247 m.setTimeout(DCCppProgrammingTimeout); 2248 return (m); 2249 } 2250 2251 2252 /** 2253 * Read Direct CV Byte from Programming Track. 2254 * <p> 2255 * Format: {@code <R CV CALLBACKNUM CALLBACKSUB>} 2256 * <p> 2257 * reads a Configuration Variable from the decoder of an engine on the 2258 * programming track 2259 * <p> 2260 * CV: the number of the Configuration Variable memory location in the 2261 * decoder to read from (1-1024) CALLBACKNUM: an arbitrary integer (0-32767) 2262 * that is ignored by the Base Station and is simply echoed back in the 2263 * output - useful for external programs that call this function 2264 * CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the 2265 * Base Station and is simply echoed back in the output - useful for 2266 * external programs (e.g. DCC-EX Interface) that call this function 2267 * <p> 2268 * Note: The two-argument form embeds the opcode in CALLBACKSUB to aid in 2269 * decoding the responses. 2270 * <p> 2271 * returns: {@code <r CALLBACKNUM|CALLBACKSUB|CV VALUE>} where VALUE is a 2272 * number from 0-255 as read from the requested CV, or -1 if read could not 2273 * be verified 2274 * @param cv CV index. 2275 * @return message to send read direct CV. 2276 */ 2277 public static DCCppMessage makeReadDirectCVMsg(int cv) { 2278 return (makeReadDirectCVMsg(cv, 0, DCCppConstants.PROG_READ_CV)); 2279 } 2280 2281 public static DCCppMessage makeReadDirectCVMsg(int cv, int callbacknum, int callbacksub) { 2282 // Sanity check inputs 2283 if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) { 2284 return (null); 2285 } 2286 if (callbacknum < 0 || callbacknum > DCCppConstants.MAX_CALLBACK_NUM) { 2287 return (null); 2288 } 2289 if (callbacksub < 0 || callbacksub > DCCppConstants.MAX_CALLBACK_SUB) { 2290 return (null); 2291 } 2292 2293 DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_READ_CV); 2294 m.myMessage.append(" ").append(cv); 2295 m.myMessage.append(" ").append(callbacknum); 2296 m.myMessage.append(" ").append(callbacksub); 2297 m.myRegex = DCCppConstants.PROG_READ_CV_REGEX; 2298 2299 m._nDataChars = m.toString().length(); 2300 m.setTimeout(DCCppProgrammingTimeout); 2301 return (m); 2302 } 2303 2304 /** 2305 * Verify Direct CV Byte from Programming Track. 2306 * <p> 2307 * Format: {@code <V CV STARTVAL>} 2308 * <p> 2309 * Verifies a Configuration Variable from the decoder of an engine on the 2310 * programming track. Returns the current value of that CV. 2311 * Used as faster replacement for 'R'eadCV command 2312 * <p> 2313 * CV: the number of the Configuration Variable memory location in the 2314 * decoder to read from (1-1024) STARTVAL: a "guess" as to the current 2315 * value of the CV. DCC-EX will try this value first, then read and return 2316 * the current value if different 2317 * <p> 2318 * returns: {@code <v CV VALUE>} where VALUE is a 2319 * number from 0-255 as read from the requested CV, -1 if read could not 2320 * be performed 2321 * @param cv CV index. 2322 * @param startVal "guess" as to current value 2323 * @return message to send verify direct CV. 2324 */ 2325 public static DCCppMessage makeVerifyCVMsg(int cv, int startVal) { 2326 // Sanity check inputs 2327 if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) { 2328 return (null); 2329 } 2330 DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_VERIFY_CV); 2331 m.myMessage.append(" ").append(cv); 2332 m.myMessage.append(" ").append(startVal); 2333 m.myRegex = DCCppConstants.PROG_VERIFY_REGEX; 2334 2335 m._nDataChars = m.toString().length(); 2336 m.setTimeout(DCCppProgrammingTimeout); 2337 return (m); 2338 } 2339 2340 /** 2341 * Write Direct CV Byte to Main Track 2342 * <p> 2343 * Format: {@code <w CAB CV VALUE>} 2344 * <p> 2345 * Writes, without any verification, a Configuration Variable to the decoder 2346 * of an engine on the main operations track. 2347 * 2348 * @param address the short (1-127) or long (128-10293) address of the 2349 * engine decoder. 2350 * @param cv the number of the Configuration Variable memory location in the 2351 * decoder to write to (1-1024). 2352 * @param val the value to be written to the 2353 * Configuration Variable memory location (0-255). 2354 * @return message to Write CV in Ops Mode. 2355 */ 2356 @CheckForNull 2357 public static DCCppMessage makeWriteOpsModeCVMsg(int address, int cv, int val) { 2358 // Sanity check inputs 2359 if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2360 return (null); 2361 } 2362 if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) { 2363 return (null); 2364 } 2365 if (val < 0 || val > DCCppConstants.MAX_DIRECT_CV_VAL) { 2366 return (null); 2367 } 2368 2369 DCCppMessage m = new DCCppMessage(DCCppConstants.OPS_WRITE_CV_BYTE); 2370 m.myMessage.append(" ").append(address); 2371 m.myMessage.append(" ").append(cv); 2372 m.myMessage.append(" ").append(val); 2373 m.myRegex = DCCppConstants.OPS_WRITE_BYTE_REGEX; 2374 2375 m._nDataChars = m.toString().length(); 2376 m.setTimeout(DCCppProgrammingTimeout); 2377 return (m); 2378 } 2379 2380 /** 2381 * Write Direct CV Bit to Main Track. 2382 * <p> 2383 * Format: {@code <b CAB CV BIT VALUE>} 2384 * <p> 2385 * writes, without any verification, a single bit within a Configuration 2386 * Variable to the decoder of an engine on the main operations track 2387 * <p> 2388 * CAB: the short (1-127) or long (128-10293) address of the engine decoder 2389 * CV: the number of the Configuration Variable memory location in the 2390 * decoder to write to (1-1024) BIT: the bit number of the Configuration 2391 * Variable register to write (0-7) VALUE: the value of the bit to be 2392 * written (0-1) 2393 * <p> 2394 * returns: NONE 2395 * @param address loco cab address. 2396 * @param cv CV index, 1-1024. 2397 * @param bit bit index, 0-7. 2398 * @param val bit value, 0 or 1. 2399 * @return message to write direct CV bit to main track. 2400 */ 2401 public static DCCppMessage makeBitWriteOpsModeCVMsg(int address, int cv, int bit, int val) { 2402 // Sanity Check Inputs 2403 if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2404 return (null); 2405 } 2406 if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) { 2407 return (null); 2408 } 2409 if (bit < 0 || bit > 7) { 2410 return (null); 2411 } 2412 2413 DCCppMessage m = new DCCppMessage(DCCppConstants.OPS_WRITE_CV_BIT); 2414 m.myMessage.append(" ").append(address); 2415 m.myMessage.append(" ").append(cv); 2416 m.myMessage.append(" ").append(bit); 2417 m.myMessage.append(" ").append(val == 0 ? "0" : "1"); 2418 2419 m.myRegex = DCCppConstants.OPS_WRITE_BIT_REGEX; 2420 2421 m._nDataChars = m.toString().length(); 2422 m.setTimeout(DCCppProgrammingTimeout); 2423 return (m); 2424 } 2425 2426 /** 2427 * Set Track Power ON or OFF. 2428 * <p> 2429 * Format: {@code <1> (ON) or <0> (OFF)} 2430 * 2431 * @return message to send track power on or off. 2432 * @param on true on, false off. 2433 */ 2434 public static DCCppMessage makeSetTrackPowerMsg(boolean on) { 2435 return (new DCCppMessage((on ? DCCppConstants.TRACK_POWER_ON : DCCppConstants.TRACK_POWER_OFF), 2436 DCCppConstants.TRACK_POWER_REGEX)); 2437 } 2438 2439 public static DCCppMessage makeTrackPowerOnMsg() { 2440 return (makeSetTrackPowerMsg(true)); 2441 } 2442 2443 public static DCCppMessage makeTrackPowerOffMsg() { 2444 return (makeSetTrackPowerMsg(false)); 2445 } 2446 2447 /** 2448 * Read main operations track current 2449 * <p> 2450 * Format: {@code <c>} 2451 * 2452 * reads current being drawn on main operations track 2453 * 2454 * @return (for DCC-EX), 1 or more of {@code <c MeterName value C/V unit min max res warn>} 2455 * where name and settings are used to define arbitrary meters on the DCC-EX side 2456 * AND {@code <a CURRENT>} where CURRENT = 0-1024, based on 2457 * exponentially-smoothed weighting scheme 2458 * 2459 */ 2460 public static DCCppMessage makeReadTrackCurrentMsg() { 2461 return (new DCCppMessage(DCCppConstants.READ_TRACK_CURRENT, DCCppConstants.READ_TRACK_CURRENT_REGEX)); 2462 } 2463 2464 /** 2465 * Read DCC-EX Base Station Status 2466 * <p> 2467 * Format: {@code <s>} 2468 * <p> 2469 * returns status messages containing track power status, throttle status, 2470 * turn-out status, and a version number NOTE: this is very useful as a 2471 * first command for an interface to send to this sketch in order to verify 2472 * connectivity and update any GUI to reflect actual throttle and turn-out 2473 * settings 2474 * 2475 * @return series of status messages that can be read by an interface to 2476 * determine status of DCC-EX Base Station and important settings 2477 */ 2478 public static DCCppMessage makeCSStatusMsg() { 2479 return (new DCCppMessage(DCCppConstants.READ_CS_STATUS, DCCppConstants.READ_CS_STATUS_REGEX)); 2480 } 2481 2482 /** 2483 * Get number of supported slots for this DCC-EX Base Station Status 2484 * <p> 2485 * Format: {@code <N>} 2486 * <p> 2487 * returns number of slots NOTE: this is not implemented in older versions 2488 * which then do not return anything at all 2489 * 2490 * @return status message with to get number of slots. 2491 */ 2492 public static DCCppMessage makeCSMaxNumSlotsMsg() { 2493 return (new DCCppMessage(DCCppConstants.READ_MAXNUMSLOTS, DCCppConstants.READ_MAXNUMSLOTS_REGEX)); 2494 } 2495 2496 /** 2497 * Generate a function message using the V4 'F' syntax supported by DCC-EX 2498 * @param cab cab address to send function to 2499 * @param func function number to set 2500 * @param state new state of function 0/1 2501 * @return function functionV4message 2502 */ 2503 public static DCCppMessage makeFunctionV4Message(int cab, int func, boolean state) { 2504 // Sanity check inputs 2505 if (cab < 0 || cab > DCCppConstants.MAX_LOCO_ADDRESS) { 2506 return (null); 2507 } 2508 if (func < 0 || func > DCCppConstants.MAX_FUNCTION_NUMBER) { 2509 return (null); 2510 } 2511 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_V4_CMD); 2512 m.myMessage.append(" ").append(cab); 2513 m.myMessage.append(" ").append(func); 2514 m.myMessage.append(" ").append(state?1:0); //1 or 0 for true or false 2515 m.myRegex = DCCppConstants.FUNCTION_V4_CMD_REGEX; 2516 m._nDataChars = m.toString().length(); 2517 return (m); 2518 } 2519 2520 /** 2521 * Generate a "Forget Cab" message '-' 2522 * 2523 * @param cab cab address to send function to (or 0 for all) 2524 * @return forget message to be sent 2525 */ 2526 public static DCCppMessage makeForgetCabMessage(int cab) { 2527 // Sanity check inputs 2528 if (cab < 0 || cab > DCCppConstants.MAX_LOCO_ADDRESS) { 2529 return (null); 2530 } 2531 DCCppMessage m = new DCCppMessage(DCCppConstants.FORGET_CAB_CMD); 2532 if (cab > 0) { 2533 m.myMessage.append(" ").append(cab); 2534 } 2535 m.myRegex = DCCppConstants.FORGET_CAB_CMD_REGEX; 2536 m._nDataChars = m.toString().length(); 2537 return (m); 2538 } 2539 2540 /** 2541 * Generate an emergency stop for the specified address. 2542 * <p> 2543 * Note: This just sends a THROTTLE command with speed = -1 2544 * 2545 * @param register Register Number for the loco assigned address. 2546 * @param address is the locomotive address. 2547 * @return message to send e stop to the specified address. 2548 */ 2549 public static DCCppMessage makeAddressedEmergencyStop(int register, int address) { 2550 // Sanity check inputs 2551 if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2552 return (null); 2553 } 2554 2555 DCCppMessage m = new DCCppMessage(DCCppConstants.THROTTLE_CMD); 2556 m.myMessage.append(" ").append(register); 2557 m.myMessage.append(" ").append(address); 2558 m.myMessage.append(" -1 1"); 2559 m.myRegex = DCCppConstants.THROTTLE_CMD_REGEX; 2560 2561 m._nDataChars = m.toString().length(); 2562 return (m); 2563 } 2564 2565 /** 2566 * Generate an emergency stop for the specified address. 2567 * <p> 2568 * Note: This just sends a THROTTLE command with speed = -1 2569 * 2570 * @param address is the locomotive address. 2571 * @return message to send e stop to the specified address. 2572 */ 2573 public static DCCppMessage makeAddressedEmergencyStop(int address) { 2574 // Sanity check inputs 2575 if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2576 return (null); 2577 } 2578 2579 DCCppMessage m = new DCCppMessage(DCCppConstants.THROTTLE_CMD); 2580 m.myMessage.append(" ").append(address); 2581 m.myMessage.append(" -1 1"); 2582 m.myRegex = DCCppConstants.THROTTLE_V3_CMD_REGEX; 2583 2584 m._nDataChars = m.toString().length(); 2585 return (m); 2586 } 2587 2588 /** 2589 * Generate an emergency stop for all locos in reminder table. 2590 * @return message to send e stop for all locos 2591 */ 2592 public static DCCppMessage makeEmergencyStopAllMsg() { 2593 DCCppMessage m = new DCCppMessage(DCCppConstants.ESTOP_ALL_CMD); 2594 m.myRegex = DCCppConstants.ESTOP_ALL_REGEX; 2595 2596 m._nDataChars = m.toString().length(); 2597 return (m); 2598 } 2599 2600 /** 2601 * Generate a Speed and Direction Request message 2602 * 2603 * @param register is the DCC-EX base station register assigned. 2604 * @param address is the locomotive address 2605 * @param speed a normalized speed value (a floating point number 2606 * between 0 and 1). A negative value indicates emergency 2607 * stop. 2608 * @param isForward true for forward, false for reverse. 2609 * 2610 * Format: {@code <t REGISTER CAB SPEED DIRECTION>} 2611 * 2612 * sets the throttle for a given register/cab combination 2613 * 2614 * REGISTER: an internal register number, from 1 through MAX_MAIN_REGISTERS 2615 * (inclusive), to store the DCC packet used to control this throttle 2616 * setting 2617 * CAB: the short (1-127) or long (128-10293) address of the engine decoder 2618 * SPEED: throttle speed from 0-126, or -1 for emergency stop (resets SPEED to 0) 2619 * DIRECTION: 1=forward, 0=reverse. Setting direction 2620 * when speed=0 or speed=-1 only effects directionality of cab lighting for 2621 * a stopped train 2622 * 2623 * @return {@code <T REGISTER CAB SPEED DIRECTION>} 2624 * 2625 */ 2626 public static DCCppMessage makeSpeedAndDirectionMsg(int register, int address, float speed, boolean isForward) { 2627 // Sanity check inputs 2628 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2629 return (null); 2630 } 2631 2632 DCCppMessage m = new DCCppMessage(DCCppConstants.THROTTLE_CMD); 2633 m.myMessage.append(" ").append(register); 2634 m.myMessage.append(" ").append(address); 2635 if (speed < 0.0) { 2636 m.myMessage.append(" -1"); 2637 } else { 2638 int speedVal = java.lang.Math.round(speed * 126); 2639 if (speed > 0 && speedVal == 0) { 2640 speedVal = 1; // ensure non-zero input results in non-zero output 2641 } 2642 speedVal = Math.min(speedVal, DCCppConstants.MAX_SPEED); 2643 m.myMessage.append(" ").append(speedVal); 2644 } 2645 m.myMessage.append(" ").append(isForward ? "1" : "0"); 2646 2647 m.myRegex = DCCppConstants.THROTTLE_CMD_REGEX; 2648 2649 m._nDataChars = m.toString().length(); 2650 return (m); 2651 } 2652 2653 /** 2654 * Generate a Speed and Direction Request message 2655 * 2656 * @param address is the locomotive address 2657 * @param speed a normalized speed value (a floating point number 2658 * between 0 and 1). A negative value indicates emergency 2659 * stop. 2660 * @param isForward true for forward, false for reverse. 2661 * 2662 * Format: {@code <t CAB SPEED DIRECTION>} 2663 * 2664 * sets the throttle for a given register/cab combination 2665 * 2666 * CAB: the short (1-127) or long (128-10293) address of the engine decoder 2667 * SPEED: throttle speed from 0-126, or -1 for emergency stop (resets SPEED to 0) 2668 * DIRECTION: 1=forward, 0=reverse. Setting direction 2669 * when speed=0 or speed=-1 only effects directionality of cab lighting for 2670 * a stopped train 2671 * 2672 * @return {@code <T CAB SPEED DIRECTION>} 2673 * 2674 */ 2675 public static DCCppMessage makeSpeedAndDirectionMsg(int address, float speed, boolean isForward) { 2676 // Sanity check inputs 2677 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2678 return (null); 2679 } 2680 2681 DCCppMessage m = new DCCppMessage(DCCppConstants.THROTTLE_CMD); 2682 m.myMessage.append(" ").append(address); 2683 if (speed < 0.0) { 2684 m.myMessage.append(" -1"); 2685 } else { 2686 int speedVal = java.lang.Math.round(speed * 126); 2687 if (speed > 0 && speedVal == 0) { 2688 speedVal = 1; // ensure non-zero input results in non-zero output 2689 } 2690 speedVal = Math.min(speedVal, DCCppConstants.MAX_SPEED); 2691 m.myMessage.append(" ").append(speedVal); 2692 } 2693 m.myMessage.append(" ").append(isForward ? "1" : "0"); 2694 2695 m.myRegex = DCCppConstants.THROTTLE_V3_CMD_REGEX; 2696 2697 m._nDataChars = m.toString().length(); 2698 return (m); 2699 } 2700 2701 /* 2702 * Function Group Messages (common serial format) 2703 * <p> 2704 * Format: {@code <f CAB BYTE1 [BYTE2]>} 2705 * <p> 2706 * turns on and off engine decoder functions F0-F28 (F0 is sometimes called 2707 * FL) NOTE: setting requests transmitted directly to mobile engine decoder 2708 * --- current state of engine functions is not stored by this program 2709 * <p> 2710 * CAB: the short (1-127) or long (128-10293) address of the engine decoder 2711 * <p> 2712 * To set functions F0-F4 on (=1) or off (=0): 2713 * <p> 2714 * BYTE1: 128 + F1*1 + F2*2 + F3*4 + F4*8 + F0*16 BYTE2: omitted 2715 * <p> 2716 * To set functions F5-F8 on (=1) or off (=0): 2717 * <p> 2718 * BYTE1: 176 + F5*1 + F6*2 + F7*4 + F8*8 BYTE2: omitted 2719 * <p> 2720 * To set functions F9-F12 on (=1) or off (=0): 2721 * <p> 2722 * BYTE1: 160 + F9*1 +F10*2 + F11*4 + F12*8 BYTE2: omitted 2723 * <p> 2724 * To set functions F13-F20 on (=1) or off (=0): 2725 * <p> 2726 * BYTE1: 222 BYTE2: F13*1 + F14*2 + F15*4 + F16*8 + F17*16 + F18*32 + 2727 * F19*64 + F20*128 2728 * <p> 2729 * To set functions F21-F28 on (=1) of off (=0): 2730 * <p> 2731 * BYTE1: 223 BYTE2: F21*1 + F22*2 + F23*4 + F24*8 + F25*16 + F26*32 + 2732 * F27*64 + F28*128 2733 * <p> 2734 * returns: NONE 2735 * <p> 2736 */ 2737 /** 2738 * Generate a Function Group One Operation Request message. 2739 * 2740 * @param address is the locomotive address 2741 * @param f0 is true if f0 is on, false if f0 is off 2742 * @param f1 is true if f1 is on, false if f1 is off 2743 * @param f2 is true if f2 is on, false if f2 is off 2744 * @param f3 is true if f3 is on, false if f3 is off 2745 * @param f4 is true if f4 is on, false if f4 is off 2746 * @return message to set function group 1. 2747 */ 2748 public static DCCppMessage makeFunctionGroup1OpsMsg(int address, 2749 boolean f0, 2750 boolean f1, 2751 boolean f2, 2752 boolean f3, 2753 boolean f4) { 2754 // Sanity check inputs 2755 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2756 return (null); 2757 } 2758 2759 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 2760 m.myMessage.append(" ").append(address); 2761 2762 int byte1 = 128 + (f0 ? 16 : 0); 2763 byte1 += (f1 ? 1 : 0); 2764 byte1 += (f2 ? 2 : 0); 2765 byte1 += (f3 ? 4 : 0); 2766 byte1 += (f4 ? 8 : 0); 2767 m.myMessage.append(" ").append(byte1); 2768 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 2769 2770 m._nDataChars = m.toString().length(); 2771 return (m); 2772 } 2773 2774 /** 2775 * Generate a Function Group One Set Momentary Functions message. 2776 * 2777 * @param address is the locomotive address 2778 * @param f0 is true if f0 is momentary 2779 * @param f1 is true if f1 is momentary 2780 * @param f2 is true if f2 is momentary 2781 * @param f3 is true if f3 is momentary 2782 * @param f4 is true if f4 is momentary 2783 * @return message to set momentary function group 1. 2784 */ 2785 public static DCCppMessage makeFunctionGroup1SetMomMsg(int address, 2786 boolean f0, 2787 boolean f1, 2788 boolean f2, 2789 boolean f3, 2790 boolean f4) { 2791 2792 // Sanity check inputs 2793 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2794 return (null); 2795 } 2796 2797 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 2798 m.myMessage.append(" ").append(address); 2799 2800 int byte1 = 128 + (f0 ? 16 : 0); 2801 byte1 += (f1 ? 1 : 0); 2802 byte1 += (f2 ? 2 : 0); 2803 byte1 += (f3 ? 4 : 0); 2804 byte1 += (f4 ? 8 : 0); 2805 2806 m.myMessage.append(" ").append(byte1); 2807 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 2808 2809 m._nDataChars = m.toString().length(); 2810 return (m); 2811 } 2812 2813 /** 2814 * Generate a Function Group Two Operation Request message. 2815 * 2816 * @param address is the locomotive address 2817 * @param f5 is true if f5 is on, false if f5 is off 2818 * @param f6 is true if f6 is on, false if f6 is off 2819 * @param f7 is true if f7 is on, false if f7 is off 2820 * @param f8 is true if f8 is on, false if f8 is off 2821 * @return message to set function group 2. 2822 */ 2823 public static DCCppMessage makeFunctionGroup2OpsMsg(int address, 2824 boolean f5, 2825 boolean f6, 2826 boolean f7, 2827 boolean f8) { 2828 2829 // Sanity check inputs 2830 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2831 return (null); 2832 } 2833 2834 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 2835 m.myMessage.append(" ").append(address); 2836 2837 int byte1 = 176; 2838 byte1 += (f5 ? 1 : 0); 2839 byte1 += (f6 ? 2 : 0); 2840 byte1 += (f7 ? 4 : 0); 2841 byte1 += (f8 ? 8 : 0); 2842 2843 m.myMessage.append(" ").append(byte1); 2844 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 2845 2846 m._nDataChars = m.toString().length(); 2847 return (m); 2848 } 2849 2850 /** 2851 * Generate a Function Group Two Set Momentary Functions message. 2852 * 2853 * @param address is the locomotive address 2854 * @param f5 is true if f5 is momentary 2855 * @param f6 is true if f6 is momentary 2856 * @param f7 is true if f7 is momentary 2857 * @param f8 is true if f8 is momentary 2858 * @return message to set momentary function group 2. 2859 */ 2860 public static DCCppMessage makeFunctionGroup2SetMomMsg(int address, 2861 boolean f5, 2862 boolean f6, 2863 boolean f7, 2864 boolean f8) { 2865 2866 // Sanity check inputs 2867 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2868 return (null); 2869 } 2870 2871 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 2872 m.myMessage.append(" ").append(address); 2873 2874 int byte1 = 176; 2875 byte1 += (f5 ? 1 : 0); 2876 byte1 += (f6 ? 2 : 0); 2877 byte1 += (f7 ? 4 : 0); 2878 byte1 += (f8 ? 8 : 0); 2879 m.myMessage.append(" ").append(byte1); 2880 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 2881 2882 m._nDataChars = m.toString().length(); 2883 return (m); 2884 } 2885 2886 /** 2887 * Generate a Function Group Three Operation Request message. 2888 * 2889 * @param address is the locomotive address 2890 * @param f9 is true if f9 is on, false if f9 is off 2891 * @param f10 is true if f10 is on, false if f10 is off 2892 * @param f11 is true if f11 is on, false if f11 is off 2893 * @param f12 is true if f12 is on, false if f12 is off 2894 * @return message to set function group 3. 2895 */ 2896 public static DCCppMessage makeFunctionGroup3OpsMsg(int address, 2897 boolean f9, 2898 boolean f10, 2899 boolean f11, 2900 boolean f12) { 2901 2902 // Sanity check inputs 2903 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2904 return (null); 2905 } 2906 2907 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 2908 m.myMessage.append(" ").append(address); 2909 2910 int byte1 = 160; 2911 byte1 += (f9 ? 1 : 0); 2912 byte1 += (f10 ? 2 : 0); 2913 byte1 += (f11 ? 4 : 0); 2914 byte1 += (f12 ? 8 : 0); 2915 m.myMessage.append(" ").append(byte1); 2916 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 2917 2918 m._nDataChars = m.toString().length(); 2919 return (m); 2920 } 2921 2922 /** 2923 * Generate a Function Group Three Set Momentary Functions message. 2924 * 2925 * @param address is the locomotive address 2926 * @param f9 is true if f9 is momentary 2927 * @param f10 is true if f10 is momentary 2928 * @param f11 is true if f11 is momentary 2929 * @param f12 is true if f12 is momentary 2930 * @return message to set momentary function group 3. 2931 */ 2932 public static DCCppMessage makeFunctionGroup3SetMomMsg(int address, 2933 boolean f9, 2934 boolean f10, 2935 boolean f11, 2936 boolean f12) { 2937 2938 // Sanity check inputs 2939 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2940 return (null); 2941 } 2942 2943 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 2944 m.myMessage.append(" ").append(address); 2945 2946 int byte1 = 160; 2947 byte1 += (f9 ? 1 : 0); 2948 byte1 += (f10 ? 2 : 0); 2949 byte1 += (f11 ? 4 : 0); 2950 byte1 += (f12 ? 8 : 0); 2951 m.myMessage.append(" ").append(byte1); 2952 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 2953 2954 m._nDataChars = m.toString().length(); 2955 return (m); 2956 } 2957 2958 /** 2959 * Generate a Function Group Four Operation Request message. 2960 * 2961 * @param address is the locomotive address 2962 * @param f13 is true if f13 is on, false if f13 is off 2963 * @param f14 is true if f14 is on, false if f14 is off 2964 * @param f15 is true if f15 is on, false if f15 is off 2965 * @param f16 is true if f18 is on, false if f16 is off 2966 * @param f17 is true if f17 is on, false if f17 is off 2967 * @param f18 is true if f18 is on, false if f18 is off 2968 * @param f19 is true if f19 is on, false if f19 is off 2969 * @param f20 is true if f20 is on, false if f20 is off 2970 * @return message to set function group 4. 2971 */ 2972 public static DCCppMessage makeFunctionGroup4OpsMsg(int address, 2973 boolean f13, 2974 boolean f14, 2975 boolean f15, 2976 boolean f16, 2977 boolean f17, 2978 boolean f18, 2979 boolean f19, 2980 boolean f20) { 2981 2982 // Sanity check inputs 2983 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 2984 return (null); 2985 } 2986 2987 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 2988 m.myMessage.append(" ").append(address); 2989 2990 int byte2 = 0; 2991 byte2 += (f13 ? 1 : 0); 2992 byte2 += (f14 ? 2 : 0); 2993 byte2 += (f15 ? 4 : 0); 2994 byte2 += (f16 ? 8 : 0); 2995 byte2 += (f17 ? 16 : 0); 2996 byte2 += (f18 ? 32 : 0); 2997 byte2 += (f19 ? 64 : 0); 2998 byte2 += (f20 ? 128 : 0); 2999 m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP4_BYTE1); 3000 m.myMessage.append(" ").append(byte2); 3001 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 3002 3003 m._nDataChars = m.toString().length(); 3004 return (m); 3005 } 3006 3007 /** 3008 * Generate a Function Group Four Set Momentary Function message. 3009 * 3010 * @param address is the locomotive address 3011 * @param f13 is true if f13 is Momentary 3012 * @param f14 is true if f14 is Momentary 3013 * @param f15 is true if f15 is Momentary 3014 * @param f16 is true if f18 is Momentary 3015 * @param f17 is true if f17 is Momentary 3016 * @param f18 is true if f18 is Momentary 3017 * @param f19 is true if f19 is Momentary 3018 * @param f20 is true if f20 is Momentary 3019 * @return message to set momentary function group 4. 3020 */ 3021 public static DCCppMessage makeFunctionGroup4SetMomMsg(int address, 3022 boolean f13, 3023 boolean f14, 3024 boolean f15, 3025 boolean f16, 3026 boolean f17, 3027 boolean f18, 3028 boolean f19, 3029 boolean f20) { 3030 3031 // Sanity check inputs 3032 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 3033 return (null); 3034 } 3035 3036 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 3037 m.myMessage.append(" ").append(address); 3038 3039 int byte2 = 0; 3040 byte2 += (f13 ? 1 : 0); 3041 byte2 += (f14 ? 2 : 0); 3042 byte2 += (f15 ? 4 : 0); 3043 byte2 += (f16 ? 8 : 0); 3044 byte2 += (f17 ? 16 : 0); 3045 byte2 += (f18 ? 32 : 0); 3046 byte2 += (f19 ? 64 : 0); 3047 byte2 += (f20 ? 128 : 0); 3048 3049 m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP4_BYTE1); 3050 m.myMessage.append(" ").append(byte2); 3051 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 3052 3053 m._nDataChars = m.toString().length(); 3054 return (m); 3055 } 3056 3057 /** 3058 * Generate a Function Group Five Operation Request message. 3059 * 3060 * @param address is the locomotive address 3061 * @param f21 is true if f21 is on, false if f21 is off 3062 * @param f22 is true if f22 is on, false if f22 is off 3063 * @param f23 is true if f23 is on, false if f23 is off 3064 * @param f24 is true if f24 is on, false if f24 is off 3065 * @param f25 is true if f25 is on, false if f25 is off 3066 * @param f26 is true if f26 is on, false if f26 is off 3067 * @param f27 is true if f27 is on, false if f27 is off 3068 * @param f28 is true if f28 is on, false if f28 is off 3069 * @return message to set function group 5. 3070 */ 3071 public static DCCppMessage makeFunctionGroup5OpsMsg(int address, 3072 boolean f21, 3073 boolean f22, 3074 boolean f23, 3075 boolean f24, 3076 boolean f25, 3077 boolean f26, 3078 boolean f27, 3079 boolean f28) { 3080 // Sanity check inputs 3081 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 3082 return (null); 3083 } 3084 3085 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 3086 m.myMessage.append(" ").append(address); 3087 3088 int byte2 = 0; 3089 byte2 += (f21 ? 1 : 0); 3090 byte2 += (f22 ? 2 : 0); 3091 byte2 += (f23 ? 4 : 0); 3092 byte2 += (f24 ? 8 : 0); 3093 byte2 += (f25 ? 16 : 0); 3094 byte2 += (f26 ? 32 : 0); 3095 byte2 += (f27 ? 64 : 0); 3096 byte2 += (f28 ? 128 : 0); 3097 log.debug("DCCppMessage: Byte2 = {}", byte2); 3098 3099 m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP5_BYTE1); 3100 m.myMessage.append(" ").append(byte2); 3101 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 3102 3103 m._nDataChars = m.toString().length(); 3104 return (m); 3105 } 3106 3107 /** 3108 * Generate a Function Group Five Set Momentary Function message. 3109 * 3110 * @param address is the locomotive address 3111 * @param f21 is true if f21 is momentary 3112 * @param f22 is true if f22 is momentary 3113 * @param f23 is true if f23 is momentary 3114 * @param f24 is true if f24 is momentary 3115 * @param f25 is true if f25 is momentary 3116 * @param f26 is true if f26 is momentary 3117 * @param f27 is true if f27 is momentary 3118 * @param f28 is true if f28 is momentary 3119 * @return message to set momentary function group 5. 3120 */ 3121 public static DCCppMessage makeFunctionGroup5SetMomMsg(int address, 3122 boolean f21, 3123 boolean f22, 3124 boolean f23, 3125 boolean f24, 3126 boolean f25, 3127 boolean f26, 3128 boolean f27, 3129 boolean f28) { 3130 3131 // Sanity check inputs 3132 if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) { 3133 return (null); 3134 } 3135 3136 DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD); 3137 m.myMessage.append(" ").append(address); 3138 3139 int byte2 = 0; 3140 byte2 += (f21 ? 1 : 0); 3141 byte2 += (f22 ? 2 : 0); 3142 byte2 += (f23 ? 4 : 0); 3143 byte2 += (f24 ? 8 : 0); 3144 byte2 += (f25 ? 16 : 0); 3145 byte2 += (f26 ? 32 : 0); 3146 byte2 += (f27 ? 64 : 0); 3147 byte2 += (f28 ? 128 : 0); 3148 3149 m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP5_BYTE1); 3150 m.myMessage.append(" ").append(byte2); 3151 m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX; 3152 3153 m._nDataChars = m.toString().length(); 3154 return (m); 3155 } 3156 3157 /* 3158 * Build an Emergency Off Message 3159 */ 3160 3161 /* 3162 * Test Code Functions... not for normal use 3163 */ 3164 3165 /** 3166 * Write DCC Packet to a specified Register on the Main. 3167 * <br> 3168 * DCC-EX BaseStation code appends its own error-correction byte so we must 3169 * not provide one. 3170 * 3171 * @param register the DCC-EX BaseStation main register number to use 3172 * @param numBytes the number of bytes in the packet 3173 * @param bytes byte array representing the packet. The first 3174 * {@code num_bytes} are used. 3175 * @return the formatted message to send 3176 */ 3177 public static DCCppMessage makeWriteDCCPacketMainMsg(int register, int numBytes, byte[] bytes) { 3178 // Sanity Check Inputs 3179 if (register < 0 || register > DCCppConstants.MAX_MAIN_REGISTERS || numBytes < 2 || numBytes > 5) { 3180 return (null); 3181 } 3182 3183 DCCppMessage m = new DCCppMessage(DCCppConstants.WRITE_DCC_PACKET_MAIN); 3184 m.myMessage.append(" ").append(register); 3185 for (int k = 0; k < numBytes; k++) { 3186 m.myMessage.append(" ").append(jmri.util.StringUtil.twoHexFromInt(bytes[k])); 3187 } 3188 m.myRegex = DCCppConstants.WRITE_DCC_PACKET_MAIN_REGEX; 3189 return (m); 3190 3191 } 3192 3193 /** 3194 * Write DCC Packet to a specified Register on the Programming Track. 3195 * <br><br> 3196 * DCC-EX BaseStation code appends its own error-correction byte so we must 3197 * not provide one. 3198 * 3199 * @param register the DCC-EX BaseStation main register number to use 3200 * @param numBytes the number of bytes in the packet 3201 * @param bytes byte array representing the packet. The first 3202 * {@code num_bytes} are used. 3203 * @return the formatted message to send 3204 */ 3205 public static DCCppMessage makeWriteDCCPacketProgMsg(int register, int numBytes, byte[] bytes) { 3206 // Sanity Check Inputs 3207 if (register < 0 || register > DCCppConstants.MAX_MAIN_REGISTERS || numBytes < 2 || numBytes > 5) { 3208 return (null); 3209 } 3210 3211 DCCppMessage m = new DCCppMessage(DCCppConstants.WRITE_DCC_PACKET_PROG); 3212 m.myMessage.append(" ").append(register); 3213 for (int k = 0; k < numBytes; k++) { 3214 m.myMessage.append(" ").append(jmri.util.StringUtil.twoHexFromInt(bytes[k])); 3215 } 3216 m.myRegex = DCCppConstants.WRITE_DCC_PACKET_PROG_REGEX; 3217 return (m); 3218 3219 } 3220 3221// public static DCCppMessage makeCheckFreeMemMsg() { 3222// return (new DCCppMessage(DCCppConstants.GET_FREE_MEMORY, DCCppConstants.GET_FREE_MEMORY_REGEX)); 3223// } 3224// 3225 public static DCCppMessage makeListRegisterContentsMsg() { 3226 return (new DCCppMessage(DCCppConstants.LIST_REGISTER_CONTENTS, 3227 DCCppConstants.LIST_REGISTER_CONTENTS_REGEX)); 3228 } 3229 /** 3230 * Request LCD Messages used for Virtual LCD Display 3231 * <p> 3232 * Format: {@code <@>} 3233 * <p> 3234 * tells EX_CommandStation to send any LCD message updates to this instance of JMRI 3235 * @return the formatted message to send 3236 */ 3237 public static DCCppMessage makeLCDRequestMsg() { 3238 return (new DCCppMessage(DCCppConstants.LCD_TEXT_CMD, DCCppConstants.LCD_TEXT_CMD_REGEX)); 3239 } 3240 3241 3242 /** 3243 * This implementation of equals is targeted to the background function 3244 * refreshing in SerialDCCppPacketizer. To keep only one function group in 3245 * the refresh queue the logic is as follows. Two messages are equal if they 3246 * are: 3247 * <ul> 3248 * <li>actually identical, or</li> 3249 * <li>a function call to the same address and same function group</li> 3250 * </ul> 3251 */ 3252 @Override 3253 public boolean equals(final Object obj) { 3254 if (obj == null) { 3255 return false; 3256 } 3257 3258 if (!(obj instanceof DCCppMessage)) { 3259 return false; 3260 } 3261 3262 final DCCppMessage other = (DCCppMessage) obj; 3263 3264 final String myCmd = this.toString(); 3265 final String otherCmd = other.toString(); 3266 3267 if (myCmd.equals(otherCmd)) { 3268 return true; 3269 } 3270 3271 if (!(myCmd.charAt(0) == DCCppConstants.FUNCTION_CMD) || !(otherCmd.charAt(0) == DCCppConstants.FUNCTION_CMD)) { 3272 return false; 3273 } 3274 3275 final int mySpace1 = myCmd.indexOf(' ', 2); 3276 final int otherSpace1 = otherCmd.indexOf(' ', 2); 3277 3278 if (mySpace1 != otherSpace1) { 3279 return false; 3280 } 3281 3282 if (!myCmd.subSequence(2, mySpace1).equals(otherCmd.subSequence(2, otherSpace1))) { 3283 return false; 3284 } 3285 3286 int mySpace2 = myCmd.indexOf(' ', mySpace1 + 1); 3287 if (mySpace2 < 0) { 3288 mySpace2 = myCmd.length(); 3289 } 3290 3291 int otherSpace2 = otherCmd.indexOf(' ', otherSpace1 + 1); 3292 if (otherSpace2 < 0) { 3293 otherSpace2 = otherCmd.length(); 3294 } 3295 3296 final int myBaseFunction = Integer.parseInt(myCmd.substring(mySpace1 + 1, mySpace2)); 3297 final int otherBaseFunction = Integer.parseInt(otherCmd.substring(otherSpace1 + 1, otherSpace2)); 3298 3299 if (myBaseFunction == otherBaseFunction) { 3300 return true; 3301 } 3302 3303 return getFuncBaseByte1(myBaseFunction) == getFuncBaseByte1(otherBaseFunction); 3304 } 3305 3306 @Override 3307 public int hashCode() { 3308 return toString().hashCode(); 3309 } 3310 3311 /** 3312 * Get the function group from the first byte of the function setting call. 3313 * 3314 * @param byte1 first byte (mixed in with function bits for groups 1 to 3, 3315 * or standalone value for groups 4 and 5) 3316 * @return the base group 3317 */ 3318 private static int getFuncBaseByte1(final int byte1) { 3319 if (byte1 == DCCppConstants.FUNCTION_GROUP4_BYTE1 || byte1 == DCCppConstants.FUNCTION_GROUP5_BYTE1) { 3320 return byte1; 3321 } 3322 3323 if (byte1 < 160) { 3324 return 128; 3325 } 3326 3327 if (byte1 < 176) { 3328 return 160; 3329 } 3330 3331 return 176; 3332 } 3333 3334 /** 3335 * When is this message supposed to be resent? 3336 */ 3337 private long expireTime; 3338 3339 /** 3340 * Before adding the message to the delay queue call this method to set when 3341 * the message should be repeated. The only time guarantee is that it will 3342 * be repeated after <u>at least</u> this much time, but it can be 3343 * significantly longer until it is repeated, function of the message queue 3344 * length. 3345 * 3346 * @param millis milliseconds in the future 3347 */ 3348 public void delayFor(final long millis) { 3349 expireTime = System.currentTimeMillis() + millis; 3350 } 3351 3352 /** 3353 * Comparing two queued message for refreshing the function calls, based on 3354 * their expected execution time. 3355 */ 3356 @Override 3357 public int compareTo(@Nonnull final Delayed o) { 3358 final long diff = this.expireTime - ((DCCppMessage) o).expireTime; 3359 3360 if (diff < 0) { 3361 return -1; 3362 } 3363 3364 if (diff > 0) { 3365 return 1; 3366 } 3367 3368 return 0; 3369 } 3370 3371 /** 3372 * From the {@link Delayed} interface, how long this message still has until 3373 * it should be executed. 3374 */ 3375 @Override 3376 public long getDelay(final TimeUnit unit) { 3377 return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); 3378 } 3379 3380 // initialize logging 3381 private static final Logger log = LoggerFactory.getLogger(DCCppMessage.class); 3382 3383}