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