java/src/jmri/jmrix/dccpp/DCCppMessage.java
package jmri.jmrix.dccpp;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
* Represents a single command or response on the DCC++.
* <p>
* Content is represented with ints to avoid the problems with sign-extension
* that bytes have, and because a Java char is actually a variable number of
* bytes in Unicode.
*
* @author Bob Jacobsen Copyright (C) 2002
* @author Paul Bender Copyright (C) 2003-2010
* @author Mark Underwood Copyright (C) 2015
* @author Costin Grigoras Copyright (C) 2018
* @author Harald Barth Copyright (C) 2019
*
* Based on XNetMessage by Bob Jacobsen and Paul Bender
*/
/*
* A few words on implementation:
*
* DCCppMessage objects are (usually) created by calling one of the static makeMessageType()
* methods, and are then consumed by the TrafficController/Packetizer by being converted to
* a String and sent out the port.
* <p>
* Internally the DCCppMessage is actually stored as a String, and alongside that is kept
* a Regex for easy extraction of the values where needed in the code.
* <p>
* The various getParameter() type functions are mainly for convenience in places such as the
* port monitor where we want to be able to extract the /meaning/ of the DCCppMessage and
* present it in a human readable form. Using the getParameterType() methods insulates
* the higher level code from needing to know what order/format the actual message is
* in.
*/
public class DCCppMessage extends jmri.jmrix.AbstractMRMessage implements Delayed {
private static int _nRetries = 3;
/* According to the specification, DCC++ has a maximum timing
interval of 500 milliseconds during normal communications */
protected static final int DCCppProgrammingTimeout = 10000; // TODO: Appropriate value for DCC++?
private static int DCCppMessageTimeout = 5000; // TODO: Appropriate value for DCC++?
//private ArrayList<Integer> valueList = new ArrayList<>();
private StringBuilder myMessage;
private String myRegex;
private char opcode;
/**
* Create a new object, representing a specific-length message.
*
* @param len Total bytes in message, including opcode and error-detection
* byte.
*/
//NOTE: Not used anywhere useful... consider removing.
public DCCppMessage(int len) {
super(len);
setBinary(false);
setRetries(_nRetries);
setTimeout(DCCppMessageTimeout);
if (len > DCCppConstants.MAX_MESSAGE_SIZE || len < 0) {
log.error("Invalid length in ctor: {}", len);
}
_nDataChars = len;
myRegex = "";
myMessage = new StringBuilder(len);
}
/**
* Create a new object, that is a copy of an existing message.
*
* @param message existing message.
*/
public DCCppMessage(DCCppMessage message) {
super(message);
setBinary(false);
setRetries(_nRetries);
setTimeout(DCCppMessageTimeout);
myRegex = message.myRegex;
myMessage = message.myMessage;
toStringCache = message.toStringCache;
}
/**
* Create an DCCppMessage from an DCCppReply.
* Not used. Really, not even possible. Consider removing.
* @param message existing reply to replicate.
*/
public DCCppMessage(DCCppReply message) {
super(message.getNumDataElements());
setBinary(false);
setRetries(_nRetries);
setTimeout(DCCppMessageTimeout);
for (int i = 0; i < message.getNumDataElements(); i++) {
setElement(i, message.getElement(i));
}
}
/**
* Create a DCCppMessage from a String containing bytes.
* <p>
* Since DCCppMessages are text, there is no Hex-to-byte conversion.
* <p>
* NOTE 15-Feb-17: un-Deprecating this function so that it can be used in
* the DCCppOverTCP server/client interface.
* Messages shouldn't be parsed, they are already in DCC++ format,
* so we need the string constructor to generate a DCCppMessage from
* the incoming byte stream.
* @param s message in string form.
*/
public DCCppMessage(String s) {
setBinary(false);
setRetries(_nRetries);
setTimeout(DCCppMessageTimeout);
myMessage = new StringBuilder(s); // yes, copy... or... maybe not.
toStringCache = s;
// gather bytes in result
setRegex();
_nDataChars = myMessage.length();
_dataChars = new int[_nDataChars];
}
// Partial constructor used in the static getMessageType() calls below.
protected DCCppMessage(char c) {
setBinary(false);
setRetries(_nRetries);
setTimeout(DCCppMessageTimeout);
opcode = c;
myMessage = new StringBuilder(Character.toString(c));
_nDataChars = myMessage.length();
}
protected DCCppMessage(char c, String regex) {
setBinary(false);
setRetries(_nRetries);
setTimeout(DCCppMessageTimeout);
opcode = c;
myRegex = regex;
myMessage = new StringBuilder(Character.toString(c));
_nDataChars = myMessage.length();
}
private void setRegex() {
switch (myMessage.charAt(0)) {
case DCCppConstants.THROTTLE_CMD:
if ((match(toString(), DCCppConstants.THROTTLE_CMD_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.THROTTLE_CMD_REGEX;
} else if ((match(toString(), DCCppConstants.THROTTLE_V3_CMD_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.THROTTLE_V3_CMD_REGEX;
}
break;
case DCCppConstants.FUNCTION_CMD:
myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
break;
case DCCppConstants.FUNCTION_V4_CMD:
myRegex = DCCppConstants.FUNCTION_V4_CMD_REGEX;
break;
case DCCppConstants.FORGET_CAB_CMD:
myRegex = DCCppConstants.FORGET_CAB_CMD_REGEX;
break;
case DCCppConstants.ACCESSORY_CMD:
myRegex = DCCppConstants.ACCESSORY_CMD_REGEX;
break;
case DCCppConstants.TURNOUT_CMD:
if ((match(toString(), DCCppConstants.TURNOUT_ADD_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.TURNOUT_ADD_REGEX;
} else if ((match(toString(), DCCppConstants.TURNOUT_ADD_DCC_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.TURNOUT_ADD_DCC_REGEX;
} else if ((match(toString(), DCCppConstants.TURNOUT_ADD_SERVO_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.TURNOUT_ADD_SERVO_REGEX;
} else if ((match(toString(), DCCppConstants.TURNOUT_ADD_VPIN_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.TURNOUT_ADD_VPIN_REGEX;
} else if ((match(toString(), DCCppConstants.TURNOUT_DELETE_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.TURNOUT_DELETE_REGEX;
} else if ((match(toString(), DCCppConstants.TURNOUT_LIST_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.TURNOUT_LIST_REGEX;
} else if ((match(toString(), DCCppConstants.TURNOUT_CMD_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.TURNOUT_CMD_REGEX;
} else if ((match(toString(), DCCppConstants.TURNOUT_IMPL_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.TURNOUT_IMPL_REGEX;
} else {
myRegex = "";
}
break;
case DCCppConstants.SENSOR_CMD:
if ((match(toString(), DCCppConstants.SENSOR_ADD_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.SENSOR_ADD_REGEX;
} else if ((match(toString(), DCCppConstants.SENSOR_DELETE_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.SENSOR_DELETE_REGEX;
} else if ((match(toString(), DCCppConstants.SENSOR_LIST_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.SENSOR_LIST_REGEX;
} else {
myRegex = "";
}
break;
case DCCppConstants.OUTPUT_CMD:
if ((match(toString(), DCCppConstants.OUTPUT_ADD_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.OUTPUT_ADD_REGEX;
} else if ((match(toString(), DCCppConstants.OUTPUT_DELETE_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.OUTPUT_DELETE_REGEX;
} else if ((match(toString(), DCCppConstants.OUTPUT_LIST_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.OUTPUT_LIST_REGEX;
} else if ((match(toString(), DCCppConstants.OUTPUT_CMD_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.OUTPUT_CMD_REGEX;
} else {
myRegex = "";
}
break;
case DCCppConstants.OPS_WRITE_CV_BYTE:
if ((match(toString(), DCCppConstants.PROG_WRITE_BYTE_V4_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.PROG_WRITE_BYTE_V4_REGEX;
} else {
myRegex = DCCppConstants.OPS_WRITE_BYTE_REGEX;
}
break;
case DCCppConstants.OPS_WRITE_CV_BIT:
myRegex = DCCppConstants.OPS_WRITE_BIT_REGEX;
break;
case DCCppConstants.PROG_WRITE_CV_BYTE:
myRegex = DCCppConstants.PROG_WRITE_BYTE_REGEX;
break;
case DCCppConstants.PROG_WRITE_CV_BIT:
if ((match(toString(), DCCppConstants.PROG_WRITE_BIT_V4_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.PROG_WRITE_BIT_V4_REGEX;
} else {
myRegex = DCCppConstants.PROG_WRITE_BIT_REGEX;
}
break;
case DCCppConstants.PROG_READ_CV:
if ((match(toString(), DCCppConstants.PROG_READ_CV_REGEX, "ctor")) != null) { //match from longest to shortest
myRegex = DCCppConstants.PROG_READ_CV_REGEX;
} else if ((match(toString(), DCCppConstants.PROG_READ_CV_V4_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.PROG_READ_CV_V4_REGEX;
} else {
myRegex = DCCppConstants.PROG_READ_LOCOID_REGEX;
}
break;
case DCCppConstants.PROG_VERIFY_CV:
myRegex = DCCppConstants.PROG_VERIFY_REGEX;
break;
case DCCppConstants.TRACK_POWER_ON:
case DCCppConstants.TRACK_POWER_OFF:
myRegex = DCCppConstants.TRACK_POWER_REGEX;
break;
case DCCppConstants.READ_TRACK_CURRENT:
myRegex = DCCppConstants.READ_TRACK_CURRENT_REGEX;
break;
case DCCppConstants.READ_CS_STATUS:
myRegex = DCCppConstants.READ_CS_STATUS_REGEX;
break;
case DCCppConstants.READ_MAXNUMSLOTS:
myRegex = DCCppConstants.READ_MAXNUMSLOTS_REGEX;
break;
case DCCppConstants.WRITE_TO_EEPROM_CMD:
myRegex = DCCppConstants.WRITE_TO_EEPROM_REGEX;
break;
case DCCppConstants.CLEAR_EEPROM_CMD:
myRegex = DCCppConstants.CLEAR_EEPROM_REGEX;
break;
case DCCppConstants.QUERY_SENSOR_STATES_CMD:
myRegex = DCCppConstants.QUERY_SENSOR_STATES_REGEX;
break;
case DCCppConstants.WRITE_DCC_PACKET_MAIN:
myRegex = DCCppConstants.WRITE_DCC_PACKET_MAIN_REGEX;
break;
case DCCppConstants.WRITE_DCC_PACKET_PROG:
myRegex = DCCppConstants.WRITE_DCC_PACKET_PROG_REGEX;
break;
case DCCppConstants.LIST_REGISTER_CONTENTS:
myRegex = DCCppConstants.LIST_REGISTER_CONTENTS_REGEX;
break;
case DCCppConstants.DIAG_CMD:
myRegex = DCCppConstants.DIAG_CMD_REGEX;
break;
case DCCppConstants.CONTROL_CMD:
myRegex = DCCppConstants.CONTROL_CMD_REGEX;
break;
case DCCppConstants.THROTTLE_COMMANDS:
if ((match(toString(), DCCppConstants.TURNOUT_IDS_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.TURNOUT_IDS_REGEX;
} else if ((match(toString(), DCCppConstants.TURNOUT_ID_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.TURNOUT_ID_REGEX;
} else if ((match(toString(), DCCppConstants.CLOCK_REQUEST_TIME_REGEX, "ctor")) != null) { //<JC>
myRegex = DCCppConstants.CLOCK_REQUEST_TIME_REGEX;
} else if ((match(toString(), DCCppConstants.CLOCK_SET_REGEX, "ctor")) != null) {
myRegex = DCCppConstants.CLOCK_SET_REGEX;
} else {
myRegex = "";
}
break;
case DCCppConstants.TRACKMANAGER_CMD:
myRegex = DCCppConstants.TRACKMANAGER_CMD_REGEX;
break;
default:
myRegex = "";
}
}
private String toStringCache = null;
/**
* Converts DCCppMessage to String format (without the {@code <>} brackets)
*
* @return String form of message.
*/
@Override
public String toString() {
if (toStringCache == null) {
toStringCache = myMessage.toString();
}
return toStringCache;
/*
String s = Character.toString(opcode);
for (int i = 0; i < valueList.size(); i++) {
s += " ";
s += valueList.get(i).toString();
}
return(s);
*/
}
/**
* Generate text translations of messages for use in the DCCpp monitor.
*
* @return representation of the DCCpp as a string.
*/
@Override
public String toMonitorString() {
// Beautify and display
String text;
switch (getOpCodeChar()) {
case DCCppConstants.THROTTLE_CMD:
if (isThrottleMessage()) {
text = "Throttle Cmd: ";
text += "Register: " + getRegisterString();
text += ", Address: " + getAddressString();
text += ", Speed: " + getSpeedString();
text += ", Direction: " + getDirectionString();
} else if (isThrottleV3Message()) {
text = "Throttle Cmd: ";
text += "Address: " + getAddressString();
text += ", Speed: " + getSpeedString();
text += ", Direction: " + getDirectionString();
} else {
text = "Invalid syntax: '" + toString() + "'";
}
break;
case DCCppConstants.FUNCTION_CMD:
text = "Function Cmd: ";
text += "Address: " + getFuncAddressString();
text += ", Byte 1: " + getFuncByte1String();
text += ", Byte 2: " + getFuncByte2String();
text += ", (No Reply Expected)";
break;
case DCCppConstants.FUNCTION_V4_CMD:
text = "Function Cmd: ";
if (isFunctionV4Message()) {
text += "CAB: " + getFuncV4CabString();
text += ", FUNC: " + getFuncV4FuncString();
text += ", State: " + getFuncV4StateString();
} else {
text += "Invalid syntax: '" + toString() + "'";
}
break;
case DCCppConstants.FORGET_CAB_CMD:
text = "Forget Cab: ";
if (isForgetCabMessage()) {
text += "CAB: " + (getForgetCabString().equals("")?"[ALL]":getForgetCabString());
text += ", (No Reply Expected)";
} else {
text += "Invalid syntax: '" + toString() + "'";
}
break;
case DCCppConstants.ACCESSORY_CMD:
text = "Accessory Decoder Cmd: ";
text += "Address: " + getAccessoryAddrString();
text += ", Subaddr: " + getAccessorySubString();
text += ", State: " + getAccessoryStateString();
break;
case DCCppConstants.TURNOUT_CMD:
if (isTurnoutAddMessage()) {
text = "Add Turnout: ";
text += "ID: " + getTOIDString();
text += ", Address: " + getTOAddressString();
text += ", Subaddr: " + getTOSubAddressString();
} else if (isTurnoutAddDCCMessage()) {
text = "Add Turnout DCC: ";
text += "ID:" + getTOIDString();
text += ", Address:" + getTOAddressString();
text += ", Subaddr:" + getTOSubAddressString();
} else if (isTurnoutAddServoMessage()) {
text = "Add Turnout Servo: ";
text += "ID:" + getTOIDString();
text += ", Pin:" + getTOPinInt();
text += ", ThrownPos:" + getTOThrownPositionInt();
text += ", ClosedPos:" + getTOClosedPositionInt();
text += ", Profile:" + getTOProfileInt();
} else if (isTurnoutAddVpinMessage()) {
text = "Add Turnout Vpin: ";
text += "ID:" + getTOIDString();
text += ", Pin:" + getTOPinInt();
} else if (isTurnoutDeleteMessage()) {
text = "Delete Turnout: ";
text += "ID: " + getTOIDString();
} else if (isListTurnoutsMessage()) {
text = "List Turnouts...";
} else if (isTurnoutCmdMessage()) {
text = "Turnout Cmd: ";
text += "ID: " + getTOIDString();
text += ", State: " + getTOStateString();
} else if (isTurnoutImplementationMessage()) {
text = "Request implementation for Turnout ";
text += getTOIDString();
} else {
text = "Unmatched Turnout Cmd: " + toString();
}
break;
case DCCppConstants.OUTPUT_CMD:
if (isOutputCmdMessage()) {
text = "Output Cmd: ";
text += "ID: " + getOutputIDString();
text += ", State: " + getOutputStateString();
} else if (isOutputAddMessage()) {
text = "Add Output: ";
text += "ID: " + getOutputIDString();
text += ", Pin: " + getOutputPinString();
text += ", IFlag: " + getOutputIFlagString();
} else if (isOutputDeleteMessage()) {
text = "Delete Output: ";
text += "ID: " + getOutputIDString();
} else if (isListOutputsMessage()) {
text = "List Outputs...";
} else {
text = "Invalid Output Command: " + toString();
}
break;
case DCCppConstants.SENSOR_CMD:
if (isSensorAddMessage()) {
text = "Add Sensor: ";
text += "ID: " + getSensorIDString();
text += ", Pin: " + getSensorPinString();
text += ", Pullup: " + getSensorPullupString();
} else if (isSensorDeleteMessage()) {
text = "Delete Sensor: ";
text += "ID: " + getSensorIDString();
} else if (isListSensorsMessage()) {
text = "List Sensors...";
} else {
text = "Unknown Sensor Cmd...";
}
break;
case DCCppConstants.OPS_WRITE_CV_BYTE:
text = "Ops Write Byte Cmd: "; // <w cab cv val>
text += "Address: " + getOpsWriteAddrString() + ", ";
text += "CV: " + getOpsWriteCVString() + ", ";
text += "Value: " + getOpsWriteValueString();
break;
case DCCppConstants.OPS_WRITE_CV_BIT: // <b cab cv bit val>
text = "Ops Write Bit Cmd: ";
text += "Address: " + getOpsWriteAddrString() + ", ";
text += "CV: " + getOpsWriteCVString() + ", ";
text += "Bit: " + getOpsWriteBitString() + ", ";
text += "Value: " + getOpsWriteValueString();
break;
case DCCppConstants.PROG_WRITE_CV_BYTE:
text = "Prog Write Byte Cmd: ";
text += "CV: " + getCVString();
text += ", Value: " + getProgValueString();
if (!isProgWriteByteMessageV4()) {
text += ", Callback Num: " + getCallbackNumString();
text += ", Sub: " + getCallbackSubString();
}
break;
case DCCppConstants.PROG_WRITE_CV_BIT:
text = "Prog Write Bit Cmd: ";
text += "CV: " + getCVString();
text += ", Bit: " + getBitString();
text += ", Value: " + getProgValueString();
if (!isProgWriteBitMessageV4()) {
text += ", Callback Num: " + getCallbackNumString();
text += ", Sub: " + getCallbackSubString();
}
break;
case DCCppConstants.PROG_READ_CV:
if (isProgReadCVMessage()) {
text = "Prog Read Cmd: ";
text += "CV: " + getCVString();
text += ", Callback Num: " + getCallbackNumString();
text += ", Sub: " + getCallbackSubString();
} else if (isProgReadCVMessageV4()) {
text = "Prog Read CV: ";
text += "CV:" + getCVString();
} else { // if (isProgReadLocoIdMessage())
text = "Prog Read LocoID Cmd";
}
break;
case DCCppConstants.PROG_VERIFY_CV:
text = "Prog Verify Cmd: ";
text += "CV: " + getCVString();
text += ", startVal: " + getProgValueString();
break;
case DCCppConstants.TRACK_POWER_ON:
text = "Track Power ON Cmd ";
break;
case DCCppConstants.TRACK_POWER_OFF:
text = "Track Power OFF Cmd ";
break;
case DCCppConstants.READ_TRACK_CURRENT:
text = "Read Track Current Cmd ";
break;
case DCCppConstants.READ_CS_STATUS:
text = "Status Cmd ";
break;
case DCCppConstants.READ_MAXNUMSLOTS:
text = "Get MaxNumSlots Cmd ";
break;
case DCCppConstants.WRITE_DCC_PACKET_MAIN:
text = "Write DCC Packet Main Cmd: ";
text += "Register: " + getRegisterString();
text += ", Packet:" + getPacketString();
break;
case DCCppConstants.WRITE_DCC_PACKET_PROG:
text = "Write DCC Packet Prog Cmd: ";
text += "Register: " + getRegisterString();
text += ", Packet:" + getPacketString();
break;
case DCCppConstants.LIST_REGISTER_CONTENTS:
text = "List Register Contents Cmd: ";
text += toString();
break;
case DCCppConstants.WRITE_TO_EEPROM_CMD:
text = "Write to EEPROM Cmd: ";
text += toString();
break;
case DCCppConstants.CLEAR_EEPROM_CMD:
text = "Clear EEPROM Cmd: ";
text += toString();
break;
case DCCppConstants.QUERY_SENSOR_STATES_CMD:
text = "Query Sensor States Cmd: '" + toString() + "'";
break;
case DCCppConstants.DIAG_CMD:
text = "Diag Cmd: '" + toString() + "'";
break;
case DCCppConstants.CONTROL_CMD:
text = "Control Cmd: '" + toString() + "'";
break;
case DCCppConstants.ESTOP_ALL_CMD:
text = "eStop All Locos Cmd: '" + toString() + "'";
break;
case DCCppConstants.THROTTLE_COMMANDS:
if (isTurnoutIDsMessage()) {
text = "Request Turnout ID list";
break;
} else if (isTurnoutIDMessage()) {
text = "Request details for Turnout " + getTOIDString();
break;
} else if (isClockRequestTimeMessage()) {
text = "Request clock update from CS";
break;
} else if (isClockSetTimeMessage()) {
String hhmm = String.format("%02d:%02d",
getClockMinutesInt() / 60,
getClockMinutesInt() % 60);
text = "FastClock Send: " + hhmm;
if (!getClockRateString().isEmpty()) {
text += ", Rate:" + getClockRateString();
if (getClockRateInt()==0) {
text += " (paused)";
}
}
break;
}
text = "Unknown Message: '" + toString() + "'";
break;
case DCCppConstants.TRACKMANAGER_CMD:
text = "Request TrackManager Config: '" + toString() + "'";
break;
case DCCppConstants.LCD_TEXT_CMD:
text = "Request LCD Messages: '" + toString() + "'";
break;
default:
text = "Unknown Message: '" + toString() + "'";
}
return text;
}
@Override
public int getNumDataElements() {
return (myMessage.length());
// return(_nDataChars);
}
@Override
public int getElement(int n) {
return (this.myMessage.charAt(n));
}
@Override
public void setElement(int n, int v) {
// We want the ASCII value, not the string interpretation of the int
char c = (char) (v & 0xFF);
if (n >= myMessage.length()) {
myMessage.append(c);
} else if (n > 0) {
myMessage.setCharAt(n, c);
}
toStringCache = null;
}
// For DCC++, the opcode is the first character in the
// command (after the < ).
// note that the opcode is part of the message, so we treat it
// directly
// WARNING: use this only with opcodes that have a variable number
// of arguments following included. Otherwise, just use setElement
@Override
public void setOpCode(int i) {
if (i > 0xFF || i < 0) {
log.error("Opcode invalid: {}", i);
}
opcode = (char) (i & 0xFF);
myMessage.setCharAt(0, opcode);
toStringCache = null;
}
@Override
public int getOpCode() {
return (opcode & 0xFF);
}
public char getOpCodeChar() {
//return(opcode);
return (myMessage.charAt(0));
}
private int getGroupCount() {
Matcher m = match(toString(), myRegex, "gvs");
assert m != null;
return m.groupCount();
}
public String getValueString(int idx) {
Matcher m = match(toString(), myRegex, "gvs");
if (m == null) {
log.error("DCCppMessage '{}' not matched by '{}'", this.toString(), myRegex);
return ("");
} else if (idx <= m.groupCount()) {
return (m.group(idx));
} else {
log.error("DCCppMessage value index too big. idx = {} msg = {}", idx, this);
return ("");
}
}
public int getValueInt(int idx) {
Matcher m = match(toString(), myRegex, "gvi");
if (m == null) {
log.error("DCCppMessage '{}' not matched by '{}'", this.toString(), myRegex);
return (0);
} else if (idx <= m.groupCount()) {
return (Integer.parseInt(m.group(idx)));
} else {
log.error("DCCppMessage value index too big. idx = {} msg = {}", idx, this);
return (0);
}
}
public boolean getValueBool(int idx) {
log.debug("msg = {}, regex = {}", this, myRegex);
Matcher m = match(toString(), myRegex, "gvb");
if (m == null) {
log.error("DCCppMessage '{}' not matched by '{}'", this.toString(), myRegex);
return (false);
} else if (idx <= m.groupCount()) {
return (!m.group(idx).equals("0"));
} else {
log.error("DCCppMessage value index too big. idx = {} msg = {}", idx, this);
return (false);
}
}
/**
* @return the message length
*/
public int length() {
return (myMessage.length());
}
/**
* Change the default number of retries for an DCC++ message.
*
* @param t number of retries to attempt
*/
public static void setDCCppMessageRetries(int t) {
_nRetries = t;
}
/**
* Change the default timeout for a DCC++ message.
*
* @param t Timeout in milliseconds
*/
public static void setDCCppMessageTimeout(int t) {
DCCppMessageTimeout = t;
}
//------------------------------------------------------
// Message Helper Functions
// Core methods
/**
* Returns true if this DCCppMessage is properly formatted (or will generate
* a properly formatted command when converted to String).
*
* @return boolean true/false
*/
public boolean isValidMessageFormat() {
return this.match(this.myRegex) != null;
}
/**
* Matches this DCCppMessage against the given regex 'pat'
*
* @param pat Regex
* @return Matcher or null if no match.
*/
private Matcher match(String pat) {
return (match(this.toString(), pat, "Validator"));
}
/**
* matches the given string against the given Regex pattern.
*
* @param s string to be matched
* @param pat Regex string to match against
* @param name Text name to use in debug messages.
* @return Matcher or null if no match
*/
@CheckForNull
private static Matcher match(String s, String pat, String name) {
try {
Pattern p = Pattern.compile(pat);
Matcher m = p.matcher(s);
if (!m.matches()) {
log.trace("No Match {} Command: '{}' Pattern: '{}'", name, s, pat);
return null;
}
return m;
} catch (PatternSyntaxException e) {
log.error("Malformed DCC++ message syntax! s = {}", pat);
return (null);
} catch (IllegalStateException e) {
log.error("Group called before match operation executed string= {}", s);
return (null);
} catch (IndexOutOfBoundsException e) {
log.error("Index out of bounds string= {}", s);
return (null);
}
}
// Identity Methods
public boolean isThrottleMessage() {
return (this.match(DCCppConstants.THROTTLE_CMD_REGEX) != null);
}
public boolean isThrottleV3Message() {
return (this.match(DCCppConstants.THROTTLE_V3_CMD_REGEX) != null);
}
public boolean isAccessoryMessage() {
return (this.getOpCodeChar() == DCCppConstants.ACCESSORY_CMD);
}
public boolean isFunctionMessage() {
return (this.getOpCodeChar() == DCCppConstants.FUNCTION_CMD);
}
public boolean isFunctionV4Message() {
return (this.match(DCCppConstants.FUNCTION_V4_CMD_REGEX) != null);
}
public boolean isForgetCabMessage() {
return (this.match(DCCppConstants.FORGET_CAB_CMD_REGEX) != null);
}
public boolean isTurnoutMessage() {
return (this.getOpCodeChar() == DCCppConstants.TURNOUT_CMD);
}
public boolean isSensorMessage() {
return (this.getOpCodeChar() == DCCppConstants.SENSOR_CMD);
}
public boolean isEEPROMWriteMessage() {
return (this.getOpCodeChar() == DCCppConstants.WRITE_TO_EEPROM_CMD);
}
public boolean isEEPROMClearMessage() {
return (this.getOpCodeChar() == DCCppConstants.CLEAR_EEPROM_CMD);
}
public boolean isOpsWriteByteMessage() {
return (this.getOpCodeChar() == DCCppConstants.OPS_WRITE_CV_BYTE);
}
public boolean isOpsWriteBitMessage() {
return (this.getOpCodeChar() == DCCppConstants.OPS_WRITE_CV_BIT);
}
public boolean isProgWriteByteMessage() {
return (this.getOpCodeChar() == DCCppConstants.PROG_WRITE_CV_BYTE);
}
public boolean isProgWriteByteMessageV4() {
return (this.match(DCCppConstants.PROG_WRITE_BYTE_V4_REGEX) != null);
}
public boolean isProgWriteBitMessage() {
return (this.getOpCodeChar() == DCCppConstants.PROG_WRITE_CV_BIT);
}
public boolean isProgWriteBitMessageV4() {
return (this.match(DCCppConstants.PROG_WRITE_BIT_V4_REGEX) != null);
}
public boolean isProgReadCVMessage() {
return (this.match(DCCppConstants.PROG_READ_CV_REGEX) != null);
}
public boolean isProgReadCVMessageV4() {
return (this.match(DCCppConstants.PROG_READ_CV_V4_REGEX) != null);
}
public boolean isProgReadLocoIdMessage() {
return (this.match(DCCppConstants.PROG_READ_LOCOID_REGEX) != null);
}
public boolean isProgVerifyMessage() {
return (this.getOpCodeChar() == DCCppConstants.PROG_VERIFY_CV);
}
public boolean isTurnoutCmdMessage() {
return (this.match(DCCppConstants.TURNOUT_CMD_REGEX) != null);
}
public boolean isTurnoutAddMessage() {
return (this.match(DCCppConstants.TURNOUT_ADD_REGEX) != null);
}
public boolean isTurnoutAddDCCMessage() {
return (this.match(DCCppConstants.TURNOUT_ADD_DCC_REGEX) != null);
}
public boolean isTurnoutAddServoMessage() {
return (this.match(DCCppConstants.TURNOUT_ADD_SERVO_REGEX) != null);
}
public boolean isTurnoutAddVpinMessage() {
return (this.match(DCCppConstants.TURNOUT_ADD_VPIN_REGEX) != null);
}
public boolean isTurnoutDeleteMessage() {
return (this.match(DCCppConstants.TURNOUT_DELETE_REGEX) != null);
}
public boolean isListTurnoutsMessage() {
return (this.match(DCCppConstants.TURNOUT_LIST_REGEX) != null);
}
public boolean isSensorAddMessage() {
return (this.match(DCCppConstants.SENSOR_ADD_REGEX) != null);
}
public boolean isSensorDeleteMessage() {
return (this.match(DCCppConstants.SENSOR_DELETE_REGEX) != null);
}
public boolean isListSensorsMessage() {
return (this.match(DCCppConstants.SENSOR_LIST_REGEX) != null);
}
//public boolean isOutputCmdMessage() { return(this.getOpCodeChar() == DCCppConstants.OUTPUT_CMD); }
public boolean isOutputCmdMessage() {
return (this.match(DCCppConstants.OUTPUT_CMD_REGEX) != null);
}
public boolean isOutputAddMessage() {
return (this.match(DCCppConstants.OUTPUT_ADD_REGEX) != null);
}
public boolean isOutputDeleteMessage() {
return (this.match(DCCppConstants.OUTPUT_DELETE_REGEX) != null);
}
public boolean isListOutputsMessage() {
return (this.match(DCCppConstants.OUTPUT_LIST_REGEX) != null);
}
public boolean isQuerySensorStatesMessage() {
return (this.match(DCCppConstants.QUERY_SENSOR_STATES_REGEX) != null);
}
public boolean isWriteDccPacketMessage() {
return ((this.getOpCodeChar() == DCCppConstants.WRITE_DCC_PACKET_MAIN) || (this.getOpCodeChar() == DCCppConstants.WRITE_DCC_PACKET_PROG));
}
public boolean isTurnoutIDsMessage() {
return (this.match(DCCppConstants.TURNOUT_IDS_REGEX) != null);
}
public boolean isTurnoutIDMessage() {
return (this.match(DCCppConstants.TURNOUT_ID_REGEX) != null);
}
public boolean isClockRequestTimeMessage() {
return (this.match(DCCppConstants.CLOCK_REQUEST_TIME_REGEX) != null);
}
public boolean isClockSetTimeMessage() {
return (this.match(DCCppConstants.CLOCK_SET_REGEX) != null);
}
public boolean isTrackManagerRequestMessage() {
return (this.match(DCCppConstants.TRACKMANAGER_CMD_REGEX) != null);
}
public boolean isTurnoutImplementationMessage() {
return (this.match(DCCppConstants.TURNOUT_IMPL_REGEX) != null);
}
//------------------------------------------------------
// Helper methods for Sensor Query Commands
public String getOutputIDString() {
if (this.isOutputAddMessage() || this.isOutputDeleteMessage() || this.isOutputCmdMessage()) {
return getValueString(1);
} else {
log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar());
return ("0");
}
}
public int getOutputIDInt() {
if (this.isOutputAddMessage() || this.isOutputDeleteMessage() || this.isOutputCmdMessage()) {
return (getValueInt(1)); // assumes stored as an int!
} else {
log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar());
return (0);
}
}
public String getOutputPinString() {
if (this.isOutputAddMessage()) {
return (getValueString(2));
} else {
log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar());
return ("0");
}
}
public int getOutputPinInt() {
if (this.isOutputAddMessage()) {
return (getValueInt(2));
} else {
log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar());
return (0);
}
}
public String getOutputIFlagString() {
if (this.isOutputAddMessage()) {
return (getValueString(3));
} else {
log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar());
return ("0");
}
}
public int getOutputIFlagInt() {
if (this.isOutputAddMessage()) {
return (getValueInt(3));
} else {
log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar());
return (0);
}
}
public String getOutputStateString() {
if (isOutputCmdMessage()) {
return (this.getOutputStateInt() == 1 ? "HIGH" : "LOW");
} else {
return ("Not a Turnout");
}
}
public int getOutputStateInt() {
if (isOutputCmdMessage()) {
return (getValueInt(2));
} else {
log.error("Output Parser called on non-Output message type {}", this.getOpCodeChar());
return (0);
}
}
public boolean getOutputStateBool() {
if (this.isOutputCmdMessage()) {
return (getValueInt(2) != 0);
} else {
log.error("Output Parser called on non-Output message type {} message {}", this.getOpCodeChar(), this);
return (false);
}
}
public String getSensorIDString() {
if (this.isSensorAddMessage()) {
return getValueString(1);
} else {
log.error("Sensor Parser called on non-Sensor message type {}", this.getOpCodeChar());
return ("0");
}
}
public int getSensorIDInt() {
if (this.isSensorAddMessage()) {
return (getValueInt(1)); // assumes stored as an int!
} else {
log.error("Sensor Parser called on non-Sensor message type {}", this.getOpCodeChar());
return (0);
}
}
public String getSensorPinString() {
if (this.isSensorAddMessage()) {
return (getValueString(2));
} else {
log.error("Sensor Parser called on non-Sensor message type {}", this.getOpCodeChar());
return ("0");
}
}
public int getSensorPinInt() {
if (this.isSensorAddMessage()) {
return (getValueInt(2));
} else {
log.error("Sensor Parser called on non-Sensor message type {}", this.getOpCodeChar());
return (0);
}
}
public String getSensorPullupString() {
if (isSensorAddMessage()) {
return (getValueBool(3) ? "PULLUP" : "NO PULLUP");
} else {
return ("Not a Sensor");
}
}
public int getSensorPullupInt() {
if (this.isSensorAddMessage()) {
return (getValueInt(3));
} else {
log.error("Sensor Parser called on non-Sensor message type {} message {}", this.getOpCodeChar(), this);
return (0);
}
}
public boolean getSensorPullupBool() {
if (this.isSensorAddMessage()) {
return (getValueBool(3));
} else {
log.error("Sensor Parser called on non-Sensor message type {} message {}", this.getOpCodeChar(), this);
return (false);
}
}
// Helper methods for Accessory Decoder Commands
public String getAccessoryAddrString() {
if (this.isAccessoryMessage()) {
return (getValueString(1));
} else {
log.error("Accessory Parser called on non-Accessory message type {}", this.getOpCodeChar());
return ("0");
}
}
public int getAccessoryAddrInt() {
if (this.isAccessoryMessage()) {
return (getValueInt(1));
} else {
log.error("Accessory Parser called on non-Accessory message type {}", this.getOpCodeChar());
return (0);
}
//return(Integer.parseInt(this.getAccessoryAddrString()));
}
public String getAccessorySubString() {
if (this.isAccessoryMessage()) {
return (getValueString(2));
} else {
log.error("Accessory Parser called on non-Accessory message type {} message {}", this.getOpCodeChar(), this);
return ("0");
}
}
public int getAccessorySubInt() {
if (this.isAccessoryMessage()) {
return (getValueInt(2));
} else {
log.error("Accessory Parser called on non-Accessory message type {} message {}", this.getOpCodeChar(), this);
return (0);
}
}
public String getAccessoryStateString() {
if (isAccessoryMessage()) {
return (this.getAccessoryStateInt() == 1 ? "ON" : "OFF");
} else {
return ("Not an Accessory Decoder");
}
}
public int getAccessoryStateInt() {
if (this.isAccessoryMessage()) {
return (getValueInt(3));
} else {
log.error("Accessory Parser called on non-Accessory message type {} message {}", this.getOpCodeChar(), this);
return (0);
}
}
//------------------------------------------------------
// Helper methods for Throttle Commands
public String getRegisterString() {
if (this.isThrottleMessage() || this.isWriteDccPacketMessage()) {
return (getValueString(1));
} else {
log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar());
return ("0");
}
}
public int getRegisterInt() {
if (this.isThrottleMessage()) {
return (getValueInt(1));
} else {
log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar());
return (0);
}
}
public String getAddressString() {
if (this.isThrottleMessage()) {
return (getValueString(2));
} else if (this.isThrottleV3Message()) {
return (getValueString(1));
} else {
log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar());
return ("0");
}
}
public int getAddressInt() {
if (this.isThrottleMessage()) {
return (getValueInt(2));
} else if (this.isThrottleV3Message()) {
return (getValueInt(1));
} else {
log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar());
return (0);
}
}
public String getSpeedString() {
if (this.isThrottleMessage()) {
return (getValueString(3));
} else if (this.isThrottleV3Message()) {
return (getValueString(2));
} else {
log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar());
return ("0");
}
}
public int getSpeedInt() {
if (this.isThrottleMessage()) {
return (getValueInt(3));
} else if (this.isThrottleV3Message()) {
return (getValueInt(2));
} else {
log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar());
return (0);
}
}
public String getDirectionString() {
if (this.isThrottleMessage() || this.isThrottleV3Message()) {
return (this.getDirectionInt() == 1 ? "Forward" : "Reverse");
} else {
log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar());
return ("Not a Throttle");
}
}
public int getDirectionInt() {
if (this.isThrottleMessage()) {
return (getValueInt(4));
} else if (this.isThrottleV3Message()) {
return (getValueInt(3));
} else {
log.error("Throttle Parser called on non-Throttle message type {}", this.getOpCodeChar());
return (0);
}
}
//------------------------------------------------------
// Helper methods for Function Commands
public String getFuncAddressString() {
if (this.isFunctionMessage()) {
return (getValueString(1));
} else {
log.error("Function Parser called on non-Function message type {}", this.getOpCodeChar());
return ("0");
}
}
public int getFuncAddressInt() {
if (this.isFunctionMessage()) {
return (getValueInt(1));
} else {
log.error("Function Parser called on non-Function message type {}", this.getOpCodeChar());
return (0);
}
}
public String getFuncByte1String() {
if (this.isFunctionMessage()) {
return (getValueString(2));
} else {
log.error("Function Parser called on non-Function message type {}", this.getOpCodeChar());
return ("0");
}
}
public int getFuncByte1Int() {
if (this.isFunctionMessage()) {
return (getValueInt(2));
} else {
log.error("Function Parser called on non-Function message type {}", this.getOpCodeChar());
return (0);
}
}
public String getFuncByte2String() {
if (this.isFunctionMessage()) {
return (getValueString(3));
} else {
log.error("Function Parser called on non-Function message type {}", this.getOpCodeChar());
return ("0");
}
}
public int getFuncByte2Int() {
if (this.isFunctionMessage()) {
return (getValueInt(3));
} else {
log.error("Function Parser called on non-Function message type {}", this.getOpCodeChar());
return (0);
}
}
public String getFuncV4CabString() {
if (this.isFunctionV4Message()) {
return (getValueString(1));
} else {
log.error("Function Parser called on non-Function V4 message type {}", this.getOpCodeChar());
return ("0");
}
}
public String getFuncV4FuncString() {
if (this.isFunctionV4Message()) {
return (getValueString(2));
} else {
log.error("Function Parser called on non-Function V4 message type {}", this.getOpCodeChar());
return ("0");
}
}
public String getFuncV4StateString() {
if (this.isFunctionV4Message()) {
return (getValueString(3));
} else {
log.error("Function Parser called on non-Function V4 message type {}", this.getOpCodeChar());
return ("0");
}
}
public String getForgetCabString() {
if (this.isForgetCabMessage()) {
return (getValueString(1));
} else {
log.error("Function Parser called on non-Forget Cab message type {}", this.getOpCodeChar());
return ("0");
}
}
//------------------------------------------------------
// Helper methods for Turnout Commands
public String getTOIDString() {
if (this.isTurnoutMessage() || isTurnoutIDMessage()) {
return (getValueString(1));
} else {
log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this);
return ("0");
}
}
public int getTOIDInt() {
if (this.isTurnoutMessage() || isTurnoutIDMessage()) {
return (getValueInt(1));
} else {
log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this);
return (0);
}
}
public String getTOStateString() {
if (isTurnoutMessage()) {
return (this.getTOStateInt() == 1 ? "THROWN" : "CLOSED");
} else {
return ("Not a Turnout");
}
}
public int getTOStateInt() {
if (this.isTurnoutMessage()) {
return (getValueInt(2));
} else {
log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this);
return (0);
}
}
public String getTOAddressString() {
if (this.isTurnoutAddMessage() || this.isTurnoutAddDCCMessage()) {
return (getValueString(2));
} else {
log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this);
return ("0");
}
}
public int getTOAddressInt() {
if (this.isTurnoutAddMessage() || this.isTurnoutAddDCCMessage()) {
return (getValueInt(2));
} else {
log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this);
return (0);
}
}
public String getTOSubAddressString() {
if (this.isTurnoutAddMessage() || this.isTurnoutAddDCCMessage()) {
return (getValueString(3));
} else {
log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this);
return ("0");
}
}
public int getTOSubAddressInt() {
if (this.isTurnoutAddMessage() || this.isTurnoutAddDCCMessage()) {
return (getValueInt(3));
} else {
log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this);
return (0);
}
}
public int getTOThrownPositionInt() {
if (this.isTurnoutAddServoMessage()) {
return (getValueInt(3));
} else {
log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this);
return (0);
}
}
public int getTOClosedPositionInt() {
if (this.isTurnoutAddServoMessage()) {
return (getValueInt(4));
} else {
log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this);
return (0);
}
}
public int getTOProfileInt() {
if (this.isTurnoutAddServoMessage()) {
return (getValueInt(5));
} else {
log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this);
return (0);
}
}
public int getTOPinInt() {
if (this.isTurnoutAddServoMessage() || this.isTurnoutAddVpinMessage()) {
return (getValueInt(2));
} else {
log.error("Turnout Parser called on non-Turnout message type {} message {}", this.getOpCodeChar(), this);
return (0);
}
}
public String getClockMinutesString() {
if (this.isClockSetTimeMessage()) {
return (this.getValueString(1));
} else {
log.error("getClockTimeString Parser called on non-getClockTimeString message type {}", this.getOpCodeChar());
return ("0");
}
}
public int getClockMinutesInt() {
return (Integer.parseInt(this.getClockMinutesString()));
}
public String getClockRateString() {
if (this.isClockSetTimeMessage()) {
return (this.getValueString(2));
} else {
log.error("getClockRateString Parser called on non-getClockRateString message type {}", this.getOpCodeChar());
return ("0");
}
}
public int getClockRateInt() {
return (Integer.parseInt(this.getClockRateString()));
}
//------------------------------------------------------
// Helper methods for Ops Write Byte Commands
public String getOpsWriteAddrString() {
if (this.isOpsWriteByteMessage() || this.isOpsWriteBitMessage()) {
return (getValueString(1));
} else {
return ("0");
}
}
public int getOpsWriteAddrInt() {
if (this.isOpsWriteByteMessage() || this.isOpsWriteBitMessage()) {
return (getValueInt(1));
} else {
return (0);
}
}
public String getOpsWriteCVString() {
if (this.isOpsWriteByteMessage() || this.isOpsWriteBitMessage()) {
return (getValueString(2));
} else {
return ("0");
}
}
public int getOpsWriteCVInt() {
if (this.isOpsWriteByteMessage() || this.isOpsWriteBitMessage()) {
return (getValueInt(2));
} else {
return (0);
}
}
public String getOpsWriteBitString() {
if (this.isOpsWriteBitMessage()) {
return (getValueString(3));
} else {
return ("0");
}
}
public int getOpsWriteBitInt() {
if (this.isOpsWriteBitMessage()) {
return (getValueInt(3));
} else {
return (0);
}
}
public String getOpsWriteValueString() {
if (this.isOpsWriteByteMessage()) {
return (getValueString(3));
} else if (this.isOpsWriteBitMessage()) {
return (getValueString(4));
} else {
log.error("Ops Program Parser called on non-OpsProgram message type {}", this.getOpCodeChar());
return ("0");
}
}
public int getOpsWriteValueInt() {
if (this.isOpsWriteByteMessage()) {
return (getValueInt(3));
} else if (this.isOpsWriteBitMessage()) {
return (getValueInt(4));
} else {
return (0);
}
}
// ------------------------------------------------------
// Helper methods for Prog Write and Read Byte Commands
public String getCVString() {
if (this.isProgWriteByteMessage() ||
this.isProgWriteBitMessage() ||
this.isProgReadCVMessage() ||
this.isProgReadCVMessageV4() ||
this.isProgVerifyMessage()) {
return (getValueString(1));
} else {
return ("0");
}
}
public int getCVInt() {
if (this.isProgWriteByteMessage() ||
this.isProgWriteBitMessage() ||
this.isProgReadCVMessage() ||
this.isProgReadCVMessageV4() ||
this.isProgVerifyMessage()) {
return (getValueInt(1));
} else {
return (0);
}
}
public String getCallbackNumString() {
int idx;
if (this.isProgWriteByteMessage()) {
idx = 3;
} else if (this.isProgWriteBitMessage()) {
idx = 4;
} else if (this.isProgReadCVMessage()) {
idx = 2;
} else {
return ("0");
}
return (getValueString(idx));
}
public int getCallbackNumInt() {
int idx;
if (this.isProgWriteByteMessage()) {
idx = 3;
} else if (this.isProgWriteBitMessage()) {
idx = 4;
} else if (this.isProgReadCVMessage()) {
idx = 2;
} else {
return (0);
}
return (getValueInt(idx));
}
public String getCallbackSubString() {
int idx;
if (this.isProgWriteByteMessage()) {
idx = 4;
} else if (this.isProgWriteBitMessage()) {
idx = 5;
} else if (this.isProgReadCVMessage()) {
idx = 3;
} else {
return ("0");
}
return (getValueString(idx));
}
public int getCallbackSubInt() {
int idx;
if (this.isProgWriteByteMessage()) {
idx = 4;
} else if (this.isProgWriteBitMessage()) {
idx = 5;
} else if (this.isProgReadCVMessage()) {
idx = 3;
} else {
return (0);
}
return (getValueInt(idx));
}
public String getProgValueString() {
int idx;
if (this.isProgWriteByteMessage() || this.isProgVerifyMessage()) {
idx = 2;
} else if (this.isProgWriteBitMessage()) {
idx = 3;
} else {
return ("0");
}
return (getValueString(idx));
}
public int getProgValueInt() {
int idx;
if (this.isProgWriteByteMessage() || this.isProgVerifyMessage()) {
idx = 2;
} else if (this.isProgWriteBitMessage()) {
idx = 3;
} else {
return (0);
}
return (getValueInt(idx));
}
//------------------------------------------------------
// Helper methods for Prog Write Bit Commands
public String getBitString() {
if (this.isProgWriteBitMessage()) {
return (getValueString(2));
} else {
log.error("PWBit Parser called on non-PWBit message type {}", this.getOpCodeChar());
return ("0");
}
}
public int getBitInt() {
if (this.isProgWriteBitMessage()) {
return (getValueInt(2));
} else {
return (0);
}
}
public String getPacketString() {
if (this.isWriteDccPacketMessage()) {
StringBuilder b = new StringBuilder();
for (int i = 2; i <= getGroupCount() - 1; i++) {
b.append(this.getValueString(i));
}
return (b.toString());
} else {
log.error("Write Dcc Packet parser called on non-Dcc Packet message type {}", this.getOpCodeChar());
return ("0");
}
}
//------------------------------------------------------
/*
* Most messages are sent with a reply expected, but
* we have a few that we treat as though the reply is always
* a broadcast message, because the reply usually comes to us
* that way.
*/
// TODO: Not sure this is useful in DCC++
@Override
public boolean replyExpected() {
boolean retv;
switch (this.getOpCodeChar()) {
case DCCppConstants.TURNOUT_CMD:
case DCCppConstants.SENSOR_CMD:
case DCCppConstants.PROG_WRITE_CV_BYTE:
case DCCppConstants.PROG_WRITE_CV_BIT:
case DCCppConstants.PROG_READ_CV:
case DCCppConstants.PROG_VERIFY_CV:
case DCCppConstants.TRACK_POWER_ON:
case DCCppConstants.TRACK_POWER_OFF:
case DCCppConstants.READ_TRACK_CURRENT:
case DCCppConstants.READ_CS_STATUS:
case DCCppConstants.READ_MAXNUMSLOTS:
case DCCppConstants.OUTPUT_CMD:
case DCCppConstants.LIST_REGISTER_CONTENTS:
retv = true;
break;
default:
retv = false;
}
return (retv);
}
// decode messages of a particular form
// create messages of a particular form
/*
* The next group of routines are used by Feedback and/or turnout
* control code. These are used in multiple places within the code,
* so they appear here.
*/
/**
* Stationary Decoder Message.
* <p>
* Note that many decoders and controllers combine the ADDRESS and
* SUBADDRESS into a single number, N, from 1 through a max of 2044, where
* <p>
* {@code N = (ADDRESS - 1) * 4 + SUBADDRESS + 1, for all ADDRESS>0}
* <p>
* OR
* <p>
* {@code ADDRESS = INT((N - 1) / 4) + 1}
* {@code SUBADDRESS = (N - 1) % 4}
*
* @param address the primary address of the decoder (0-511).
* @param subaddress the subaddress of the decoder (0-3).
* @param activate true on, false off.
* @return accessory decoder message.
*/
public static DCCppMessage makeAccessoryDecoderMsg(int address, int subaddress, boolean activate) {
// Sanity check inputs
if (address < 0 || address > DCCppConstants.MAX_ACC_DECODER_ADDRESS) {
return (null);
}
if (subaddress < 0 || subaddress > DCCppConstants.MAX_ACC_DECODER_SUBADDR) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.ACCESSORY_CMD);
m.myMessage.append(" ").append(address);
m.myMessage.append(" ").append(subaddress);
m.myMessage.append(" ").append(activate ? "1" : "0");
m.myRegex = DCCppConstants.ACCESSORY_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
public static DCCppMessage makeAccessoryDecoderMsg(int address, boolean activate) {
// Convert the single address to an address/subaddress pair:
// address = (address - 1) * 4 + subaddress + 1 for address>0;
int addr, subaddr;
if (address > 0) {
addr = ((address - 1) / (DCCppConstants.MAX_ACC_DECODER_SUBADDR + 1)) + 1;
subaddr = (address - 1) % (DCCppConstants.MAX_ACC_DECODER_SUBADDR + 1);
} else {
addr = subaddr = 0;
}
log.debug("makeAccessoryDecoderMsg address {}, addr {}, subaddr {}, activate {}", address, addr, subaddr, activate);
return (makeAccessoryDecoderMsg(addr, subaddr, activate));
}
/**
* Predefined Turnout Control Message.
*
* @param id the numeric ID (0-32767) of the turnout to control.
* @param thrown true thrown, false closed.
* @return message to set turnout.
*/
public static DCCppMessage makeTurnoutCommandMsg(int id, boolean thrown) {
// Sanity check inputs
if (id < 0 || id > DCCppConstants.MAX_TURNOUT_ADDRESS) {
return (null);
}
// Need to also validate whether turnout is predefined? Where to store the IDs?
// Turnout Command
DCCppMessage m = new DCCppMessage(DCCppConstants.TURNOUT_CMD);
m.myMessage.append(" ").append(id);
m.myMessage.append((thrown ? " 1" : " 0"));
m.myRegex = DCCppConstants.TURNOUT_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
public static DCCppMessage makeOutputCmdMsg(int id, boolean state) {
// Sanity check inputs
if (id < 0 || id > DCCppConstants.MAX_TURNOUT_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.OUTPUT_CMD);
m.myMessage.append(" ").append(id);
m.myMessage.append(" ").append(state ? "1" : "0");
m.myRegex = DCCppConstants.OUTPUT_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
public static DCCppMessage makeOutputAddMsg(int id, int pin, int iflag) {
// Sanity check inputs
if (id < 0 || id > DCCppConstants.MAX_TURNOUT_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.OUTPUT_CMD);
m.myMessage.append(" ").append(id);
m.myMessage.append(" ").append(pin);
m.myMessage.append(" ").append(iflag);
m.myRegex = DCCppConstants.OUTPUT_ADD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
public static DCCppMessage makeOutputDeleteMsg(int id) {
// Sanity check inputs
if (id < 0 || id > DCCppConstants.MAX_TURNOUT_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.OUTPUT_CMD);
m.myMessage.append(" ").append(id);
m.myRegex = DCCppConstants.OUTPUT_DELETE_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
public static DCCppMessage makeOutputListMsg() {
return (new DCCppMessage(DCCppConstants.OUTPUT_CMD, DCCppConstants.OUTPUT_LIST_REGEX));
}
public static DCCppMessage makeTurnoutAddMsg(int id, int addr, int subaddr) {
// Sanity check inputs
if (id < 0 || id > DCCppConstants.MAX_TURNOUT_ADDRESS) {
log.error("turnout Id {} must be between {} and {}", id, 0, DCCppConstants.MAX_TURNOUT_ADDRESS);
return (null);
}
if (addr < 0 || addr > DCCppConstants.MAX_ACC_DECODER_ADDRESS) {
log.error("turnout address {} must be between {} and {}", id, 0, DCCppConstants.MAX_ACC_DECODER_ADDRESS);
return (null);
}
if (subaddr < 0 || subaddr > DCCppConstants.MAX_ACC_DECODER_SUBADDR) {
log.error("turnout subaddress {} must be between {} and {}", id, 0, DCCppConstants.MAX_ACC_DECODER_SUBADDR);
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.TURNOUT_CMD);
m.myMessage.append(" ").append(id);
m.myMessage.append(" ").append(addr);
m.myMessage.append(" ").append(subaddr);
m.myRegex = DCCppConstants.TURNOUT_ADD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
public static DCCppMessage makeTurnoutDeleteMsg(int id) {
// Sanity check inputs
if (id < 0 || id > DCCppConstants.MAX_TURNOUT_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.TURNOUT_CMD);
m.myMessage.append(" ").append(id);
m.myRegex = DCCppConstants.TURNOUT_DELETE_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
public static DCCppMessage makeTurnoutListMsg() {
return (new DCCppMessage(DCCppConstants.TURNOUT_CMD, DCCppConstants.TURNOUT_LIST_REGEX));
}
public static DCCppMessage makeTurnoutIDsMsg() {
DCCppMessage m = makeMessage(DCCppConstants.TURNOUT_IDS); // <JT>
m.myRegex = DCCppConstants.TURNOUT_IDS_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
public static DCCppMessage makeTurnoutIDMsg(int id) {
DCCppMessage m = makeMessage(DCCppConstants.TURNOUT_IDS + " " + id); //<JT 123>
m.myRegex = DCCppConstants.TURNOUT_ID_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
public static DCCppMessage makeTurnoutImplMsg(int id) {
DCCppMessage m = makeMessage(DCCppConstants.TURNOUT_CMD + " " + id + " X"); //<T id X>
m.myRegex = DCCppConstants.TURNOUT_IMPL_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
public static DCCppMessage makeClockRequestTimeMsg() {
DCCppMessage m = makeMessage(DCCppConstants.CLOCK_REQUEST_TIME); // <JC>
m.myRegex = DCCppConstants.CLOCK_REQUEST_TIME_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
public static DCCppMessage makeClockSetMsg(int minutes, int rate) {
DCCppMessage m = makeMessage(DCCppConstants.CLOCK_REQUEST_TIME + " " + minutes + " " + rate); //<JC 123 12>
m.myRegex = DCCppConstants.CLOCK_SET_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
public static DCCppMessage makeClockSetMsg(int minutes) {
DCCppMessage m = makeMessage(DCCppConstants.CLOCK_REQUEST_TIME + " " + minutes); //<JC 123>
m.myRegex = DCCppConstants.CLOCK_SET_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
public static DCCppMessage makeTrackManagerRequestMsg() {
return (new DCCppMessage(DCCppConstants.TRACKMANAGER_CMD, DCCppConstants.TRACKMANAGER_CMD_REGEX));
}
public static DCCppMessage makeMessage(String msg) {
return (new DCCppMessage(msg));
}
/**
* Create/Delete/Query Sensor.
* <p>
* sensor, or {@code <X>} if no sensors defined.
* @param id pin pullup (0-32767).
* @param pin Arduino pin index of sensor.
* @param pullup true if use internal pullup for PIN, false if not.
* @return message to create the sensor.
*/
public static DCCppMessage makeSensorAddMsg(int id, int pin, int pullup) {
// Sanity check inputs
// TODO: Optional sanity check pin number vs. Arduino model.
if (id < 0 || id > DCCppConstants.MAX_SENSOR_ID) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.SENSOR_CMD);
m.myMessage.append(" ").append(id);
m.myMessage.append(" ").append(pin);
m.myMessage.append(" ").append(pullup);
m.myRegex = DCCppConstants.SENSOR_ADD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
public static DCCppMessage makeSensorDeleteMsg(int id) {
// Sanity check inputs
if (id < 0 || id > DCCppConstants.MAX_SENSOR_ID) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.SENSOR_CMD);
m.myMessage.append(" ").append(id);
m.myRegex = DCCppConstants.SENSOR_DELETE_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
public static DCCppMessage makeSensorListMsg() {
return (new DCCppMessage(DCCppConstants.SENSOR_CMD, DCCppConstants.SENSOR_LIST_REGEX));
}
/**
* Query All Sensors States.
*
* @return message to query all sensor states.
*/
public static DCCppMessage makeQuerySensorStatesMsg() {
return (new DCCppMessage(DCCppConstants.QUERY_SENSOR_STATES_CMD, DCCppConstants.QUERY_SENSOR_STATES_REGEX));
}
/**
* Write Direct CV Byte to Programming Track
* <p>
* Format: {@code <W CV VALUE CALLBACKNUM CALLBACKSUB>}
* <p>
* CV: the number of the Configuration Variable
* memory location in the decoder to write to (1-1024) VALUE: the value to
* be written to the Configuration Variable memory location (0-255)
* CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base
* Station and is simply echoed back in the output - useful for external
* programs that call this function CALLBACKSUB: a second arbitrary integer
* (0-32767) that is ignored by the Base Station and is simply echoed back
* in the output - useful for external programs (e.g. DCC++ Interface) that
* call this function
* <p>
* Note: The two-argument form embeds the opcode in CALLBACKSUB to aid in
* decoding the responses.
* <p>
* returns: {@code <r CALLBACKNUM|CALLBACKSUB|CV Value)} where VALUE is a
* number from 0-255 as read from the requested CV, or -1 if verification
* read fails
* @param cv CV index, 1-1024.
* @param val new CV value, 0-255.
* @return message to write Direct CV.
*/
public static DCCppMessage makeWriteDirectCVMsg(int cv, int val) {
return (makeWriteDirectCVMsg(cv, val, 0, DCCppConstants.PROG_WRITE_CV_BYTE));
}
public static DCCppMessage makeWriteDirectCVMsg(int cv, int val, int callbacknum, int callbacksub) {
// Sanity check inputs
if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
return (null);
}
if (val < 0 || val > DCCppConstants.MAX_DIRECT_CV_VAL) {
return (null);
}
if (callbacknum < 0 || callbacknum > DCCppConstants.MAX_CALLBACK_NUM) {
return (null);
}
if (callbacksub < 0 || callbacksub > DCCppConstants.MAX_CALLBACK_SUB) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_WRITE_CV_BYTE);
m.myMessage.append(" ").append(cv);
m.myMessage.append(" ").append(val);
m.myMessage.append(" ").append(callbacknum);
m.myMessage.append(" ").append(callbacksub);
m.myRegex = DCCppConstants.PROG_WRITE_BYTE_REGEX;
m._nDataChars = m.toString().length();
m.setTimeout(DCCppProgrammingTimeout);
return (m);
}
public static DCCppMessage makeWriteDirectCVMsgV4(int cv, int val) {
// Sanity check inputs
if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
return (null);
}
if (val < 0 || val > DCCppConstants.MAX_DIRECT_CV_VAL) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_WRITE_CV_BYTE);
m.myMessage.append(" ").append(cv);
m.myMessage.append(" ").append(val);
m.myRegex = DCCppConstants.PROG_WRITE_BYTE_V4_REGEX;
m._nDataChars = m.toString().length();
m.setTimeout(DCCppProgrammingTimeout);
return (m);
}
/**
* Write Direct CV Bit to Programming Track.
* <p>
* Format: {@code <B CV BIT VALUE CALLBACKNUM CALLBACKSUB>}
* <p>
* writes, and then verifies, a single bit within a Configuration Variable
* to the decoder of an engine on the programming track
* <p>
* CV: the number of the Configuration Variable memory location in the
* decoder to write to (1-1024) BIT: the bit number of the Configurarion
* Variable memory location to write (0-7) VALUE: the value of the bit to be
* written (0-1) CALLBACKNUM: an arbitrary integer (0-32767) that is ignored
* by the Base Station and is simply echoed back in the output - useful for
* external programs that call this function CALLBACKSUB: a second arbitrary
* integer (0-32767) that is ignored by the Base Station and is simply
* echoed back in the output - useful for external programs (e.g. DCC++
* Interface) that call this function
* <p>
* Note: The two-argument form embeds the opcode in CALLBACKSUB to aid in
* decoding the responses.
* <p>
* returns: {@code <r CALLBACKNUM|CALLBACKSUB|CV BIT VALUE)} where VALUE is
* a number from 0-1 as read from the requested CV bit, or -1 if
* verification read fails
* @param cv CV index, 1-1024.
* @param bit bit index, 0-7
* @param val bit value, 0-1.
* @return message to write direct CV bit.
*/
public static DCCppMessage makeBitWriteDirectCVMsg(int cv, int bit, int val) {
return (makeBitWriteDirectCVMsg(cv, bit, val, 0, DCCppConstants.PROG_WRITE_CV_BIT));
}
public static DCCppMessage makeBitWriteDirectCVMsg(int cv, int bit, int val, int callbacknum, int callbacksub) {
// Sanity Check Inputs
if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
return (null);
}
if (bit < 0 || bit > 7) {
return (null);
}
if (callbacknum < 0 || callbacknum > DCCppConstants.MAX_CALLBACK_NUM) {
return (null);
}
if (callbacksub < 0 || callbacksub > DCCppConstants.MAX_CALLBACK_SUB) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_WRITE_CV_BIT);
m.myMessage.append(" ").append(cv);
m.myMessage.append(" ").append(bit);
m.myMessage.append(" ").append(val == 0 ? "0" : "1");
m.myMessage.append(" ").append(callbacknum);
m.myMessage.append(" ").append(callbacksub);
m.myRegex = DCCppConstants.PROG_WRITE_BIT_REGEX;
m._nDataChars = m.toString().length();
m.setTimeout(DCCppProgrammingTimeout);
return (m);
}
public static DCCppMessage makeBitWriteDirectCVMsgV4(int cv, int bit, int val) {
// Sanity Check Inputs
if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
return (null);
}
if (bit < 0 || bit > 7) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_WRITE_CV_BIT);
m.myMessage.append(" ").append(cv);
m.myMessage.append(" ").append(bit);
m.myMessage.append(" ").append(val == 0 ? "0" : "1");
m.myRegex = DCCppConstants.PROG_WRITE_BIT_V4_REGEX;
m._nDataChars = m.toString().length();
m.setTimeout(DCCppProgrammingTimeout);
return (m);
}
/**
* Read Direct CV Byte from Programming Track.
* <p>
* Format: {@code <R CV CALLBACKNUM CALLBACKSUB>}
* <p>
* reads a Configuration Variable from the decoder of an engine on the
* programming track
* <p>
* CV: the number of the Configuration Variable memory location in the
* decoder to read from (1-1024) CALLBACKNUM: an arbitrary integer (0-32767)
* that is ignored by the Base Station and is simply echoed back in the
* output - useful for external programs that call this function
* CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the
* Base Station and is simply echoed back in the output - useful for
* external programs (e.g. DCC++ Interface) that call this function
* <p>
* Note: The two-argument form embeds the opcode in CALLBACKSUB to aid in
* decoding the responses.
* <p>
* returns: {@code <r CALLBACKNUM|CALLBACKSUB|CV VALUE>} where VALUE is a
* number from 0-255 as read from the requested CV, or -1 if read could not
* be verified
* @param cv CV index.
* @return message to send read direct CV.
*/
public static DCCppMessage makeReadDirectCVMsg(int cv) {
return (makeReadDirectCVMsg(cv, 0, DCCppConstants.PROG_READ_CV));
}
public static DCCppMessage makeReadDirectCVMsg(int cv, int callbacknum, int callbacksub) {
// Sanity check inputs
if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
return (null);
}
if (callbacknum < 0 || callbacknum > DCCppConstants.MAX_CALLBACK_NUM) {
return (null);
}
if (callbacksub < 0 || callbacksub > DCCppConstants.MAX_CALLBACK_SUB) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_READ_CV);
m.myMessage.append(" ").append(cv);
m.myMessage.append(" ").append(callbacknum);
m.myMessage.append(" ").append(callbacksub);
m.myRegex = DCCppConstants.PROG_READ_CV_REGEX;
m._nDataChars = m.toString().length();
m.setTimeout(DCCppProgrammingTimeout);
return (m);
}
/**
* Verify Direct CV Byte from Programming Track.
* <p>
* Format: {@code <V CV STARTVAL>}
* <p>
* Verifies a Configuration Variable from the decoder of an engine on the
* programming track. Returns the current value of that CV.
* Used as faster replacement for 'R'eadCV command
* <p>
* CV: the number of the Configuration Variable memory location in the
* decoder to read from (1-1024) STARTVAL: a "guess" as to the current
* value of the CV. DCC-EX will try this value first, then read and return
* the current value if different
* <p>
* returns: {@code <v CV VALUE>} where VALUE is a
* number from 0-255 as read from the requested CV, -1 if read could not
* be performed
* @param cv CV index.
* @param startVal "guess" as to current value
* @return message to send verify direct CV.
*/
public static DCCppMessage makeVerifyCVMsg(int cv, int startVal) {
// Sanity check inputs
if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.PROG_VERIFY_CV);
m.myMessage.append(" ").append(cv);
m.myMessage.append(" ").append(startVal);
m.myRegex = DCCppConstants.PROG_VERIFY_REGEX;
m._nDataChars = m.toString().length();
m.setTimeout(DCCppProgrammingTimeout);
return (m);
}
/**
* Write Direct CV Byte to Main Track
* <p>
* Format: {@code <w CAB CV VALUE>}
* <p>
* Writes, without any verification, a Configuration Variable to the decoder
* of an engine on the main operations track.
*
* @param address the short (1-127) or long (128-10293) address of the
* engine decoder.
* @param cv the number of the Configuration Variable memory location in the
* decoder to write to (1-1024).
* @param val the value to be written to the
* Configuration Variable memory location (0-255).
* @return message to Write CV in Ops Mode.
*/
@CheckForNull
public static DCCppMessage makeWriteOpsModeCVMsg(int address, int cv, int val) {
// Sanity check inputs
if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
return (null);
}
if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
return (null);
}
if (val < 0 || val > DCCppConstants.MAX_DIRECT_CV_VAL) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.OPS_WRITE_CV_BYTE);
m.myMessage.append(" ").append(address);
m.myMessage.append(" ").append(cv);
m.myMessage.append(" ").append(val);
m.myRegex = DCCppConstants.OPS_WRITE_BYTE_REGEX;
m._nDataChars = m.toString().length();
m.setTimeout(DCCppProgrammingTimeout);
return (m);
}
/**
* Write Direct CV Bit to Main Track.
* <p>
* Format: {@code <b CAB CV BIT VALUE>}
* <p>
* writes, without any verification, a single bit within a Configuration
* Variable to the decoder of an engine on the main operations track
* <p>
* CAB: the short (1-127) or long (128-10293) address of the engine decoder
* CV: the number of the Configuration Variable memory location in the
* decoder to write to (1-1024) BIT: the bit number of the Configuration
* Variable register to write (0-7) VALUE: the value of the bit to be
* written (0-1)
* <p>
* returns: NONE
* @param address loco cab address.
* @param cv CV index, 1-1024.
* @param bit bit index, 0-7.
* @param val bit value, 0 or 1.
* @return message to write direct CV bit to main track.
*/
public static DCCppMessage makeBitWriteOpsModeCVMsg(int address, int cv, int bit, int val) {
// Sanity Check Inputs
if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
return (null);
}
if (cv < 1 || cv > DCCppConstants.MAX_DIRECT_CV) {
return (null);
}
if (bit < 0 || bit > 7) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.OPS_WRITE_CV_BIT);
m.myMessage.append(" ").append(address);
m.myMessage.append(" ").append(cv);
m.myMessage.append(" ").append(bit);
m.myMessage.append(" ").append(val == 0 ? "0" : "1");
m.myRegex = DCCppConstants.OPS_WRITE_BIT_REGEX;
m._nDataChars = m.toString().length();
m.setTimeout(DCCppProgrammingTimeout);
return (m);
}
/**
* Set Track Power ON or OFF.
* <p>
* Format: {@code <1> (ON) or <0> (OFF)}
*
* @return message to send track power on or off.
* @param on true on, false off.
*/
public static DCCppMessage makeSetTrackPowerMsg(boolean on) {
return (new DCCppMessage((on ? DCCppConstants.TRACK_POWER_ON : DCCppConstants.TRACK_POWER_OFF),
DCCppConstants.TRACK_POWER_REGEX));
}
public static DCCppMessage makeTrackPowerOnMsg() {
return (makeSetTrackPowerMsg(true));
}
public static DCCppMessage makeTrackPowerOffMsg() {
return (makeSetTrackPowerMsg(false));
}
/**
* Read main operations track current
* <p>
* Format: {@code <c>}
*
* reads current being drawn on main operations track
*
* @return (for DCC-EX), 1 or more of {@code <c MeterName value C/V unit min max res warn>}
* where name and settings are used to define arbitrary meters on the DCC-EX side
* AND {@code <a CURRENT>} where CURRENT = 0-1024, based on
* exponentially-smoothed weighting scheme
*
*/
public static DCCppMessage makeReadTrackCurrentMsg() {
return (new DCCppMessage(DCCppConstants.READ_TRACK_CURRENT, DCCppConstants.READ_TRACK_CURRENT_REGEX));
}
/**
* Read DCC++ Base Station Status
* <p>
* Format: {@code <s>}
* <p>
* returns status messages containing track power status, throttle status,
* turn-out status, and a version number NOTE: this is very useful as a
* first command for an interface to send to this sketch in order to verify
* connectivity and update any GUI to reflect actual throttle and turn-out
* settings
*
* @return series of status messages that can be read by an interface to
* determine status of DCC++ Base Station and important settings
*/
public static DCCppMessage makeCSStatusMsg() {
return (new DCCppMessage(DCCppConstants.READ_CS_STATUS, DCCppConstants.READ_CS_STATUS_REGEX));
}
/**
* Get number of supported slots for this DCC++ Base Station Status
* <p>
* Format: {@code <N>}
* <p>
* returns number of slots NOTE: this is not implemented in older versions
* which then do not return anything at all
*
* @return status message with to get number of slots.
*/
public static DCCppMessage makeCSMaxNumSlotsMsg() {
return (new DCCppMessage(DCCppConstants.READ_MAXNUMSLOTS, DCCppConstants.READ_MAXNUMSLOTS_REGEX));
}
/**
* Generate a function message using the V4 'F' syntax supported by DCC-EX
* @param cab cab address to send function to
* @param func function number to set
* @param state new state of function 0/1
* @return function functionV4message
*/
public static DCCppMessage makeFunctionV4Message(int cab, int func, boolean state) {
// Sanity check inputs
if (cab < 0 || cab > DCCppConstants.MAX_LOCO_ADDRESS) {
return (null);
}
if (func < 0 || func > DCCppConstants.MAX_FUNCTION_NUMBER) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_V4_CMD);
m.myMessage.append(" ").append(cab);
m.myMessage.append(" ").append(func);
m.myMessage.append(" ").append(state?1:0); //1 or 0 for true or false
m.myRegex = DCCppConstants.FUNCTION_V4_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
/**
* Generate a "Forget Cab" message '-'
*
* @param cab cab address to send function to (or 0 for all)
* @return forget message to be sent
*/
public static DCCppMessage makeForgetCabMessage(int cab) {
// Sanity check inputs
if (cab < 0 || cab > DCCppConstants.MAX_LOCO_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.FORGET_CAB_CMD);
if (cab > 0) {
m.myMessage.append(" ").append(cab);
}
m.myRegex = DCCppConstants.FORGET_CAB_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
/**
* Generate an emergency stop for the specified address.
* <p>
* Note: This just sends a THROTTLE command with speed = -1
*
* @param register Register Number for the loco assigned address.
* @param address is the locomotive address.
* @return message to send e stop to the specified address.
*/
public static DCCppMessage makeAddressedEmergencyStop(int register, int address) {
// Sanity check inputs
if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.THROTTLE_CMD);
m.myMessage.append(" ").append(register);
m.myMessage.append(" ").append(address);
m.myMessage.append(" -1 1");
m.myRegex = DCCppConstants.THROTTLE_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
/**
* Generate an emergency stop for the specified address.
* <p>
* Note: This just sends a THROTTLE command with speed = -1
*
* @param address is the locomotive address.
* @return message to send e stop to the specified address.
*/
public static DCCppMessage makeAddressedEmergencyStop(int address) {
// Sanity check inputs
if (address < 0 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.THROTTLE_CMD);
m.myMessage.append(" ").append(address);
m.myMessage.append(" -1 1");
m.myRegex = DCCppConstants.THROTTLE_V3_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
/**
* Generate an emergency stop for all locos in reminder table.
* @return message to send e stop for all locos
*/
public static DCCppMessage makeEmergencyStopAllMsg() {
DCCppMessage m = new DCCppMessage(DCCppConstants.ESTOP_ALL_CMD);
m.myRegex = DCCppConstants.ESTOP_ALL_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
/**
* Generate a Speed and Direction Request message
*
* @param register is the DCC++ base station register assigned.
* @param address is the locomotive address
* @param speed a normalized speed value (a floating point number
* between 0 and 1). A negative value indicates emergency
* stop.
* @param isForward true for forward, false for reverse.
*
* Format: {@code <t REGISTER CAB SPEED DIRECTION>}
*
* sets the throttle for a given register/cab combination
*
* REGISTER: an internal register number, from 1 through MAX_MAIN_REGISTERS
* (inclusive), to store the DCC packet used to control this throttle
* setting
* CAB: the short (1-127) or long (128-10293) address of the engine decoder
* SPEED: throttle speed from 0-126, or -1 for emergency stop (resets SPEED to 0)
* DIRECTION: 1=forward, 0=reverse. Setting direction
* when speed=0 or speed=-1 only effects directionality of cab lighting for
* a stopped train
*
* @return {@code <T REGISTER CAB SPEED DIRECTION>}
*
*/
public static DCCppMessage makeSpeedAndDirectionMsg(int register, int address, float speed, boolean isForward) {
// Sanity check inputs
if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.THROTTLE_CMD);
m.myMessage.append(" ").append(register);
m.myMessage.append(" ").append(address);
if (speed < 0.0) {
m.myMessage.append(" -1");
} else {
int speedVal = java.lang.Math.round(speed * 126);
if (speed > 0 && speedVal == 0) {
speedVal = 1; // ensure non-zero input results in non-zero output
}
speedVal = Math.min(speedVal, DCCppConstants.MAX_SPEED);
m.myMessage.append(" ").append(speedVal);
}
m.myMessage.append(" ").append(isForward ? "1" : "0");
m.myRegex = DCCppConstants.THROTTLE_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
/**
* Generate a Speed and Direction Request message
*
* @param address is the locomotive address
* @param speed a normalized speed value (a floating point number
* between 0 and 1). A negative value indicates emergency
* stop.
* @param isForward true for forward, false for reverse.
*
* Format: {@code <t CAB SPEED DIRECTION>}
*
* sets the throttle for a given register/cab combination
*
* CAB: the short (1-127) or long (128-10293) address of the engine decoder
* SPEED: throttle speed from 0-126, or -1 for emergency stop (resets SPEED to 0)
* DIRECTION: 1=forward, 0=reverse. Setting direction
* when speed=0 or speed=-1 only effects directionality of cab lighting for
* a stopped train
*
* @return {@code <T CAB SPEED DIRECTION>}
*
*/
public static DCCppMessage makeSpeedAndDirectionMsg(int address, float speed, boolean isForward) {
// Sanity check inputs
if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.THROTTLE_CMD);
m.myMessage.append(" ").append(address);
if (speed < 0.0) {
m.myMessage.append(" -1");
} else {
int speedVal = java.lang.Math.round(speed * 126);
if (speed > 0 && speedVal == 0) {
speedVal = 1; // ensure non-zero input results in non-zero output
}
speedVal = Math.min(speedVal, DCCppConstants.MAX_SPEED);
m.myMessage.append(" ").append(speedVal);
}
m.myMessage.append(" ").append(isForward ? "1" : "0");
m.myRegex = DCCppConstants.THROTTLE_V3_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
/*
* Function Group Messages (common serial format)
* <p>
* Format: {@code <f CAB BYTE1 [BYTE2]>}
* <p>
* turns on and off engine decoder functions F0-F28 (F0 is sometimes called
* FL) NOTE: setting requests transmitted directly to mobile engine decoder
* --- current state of engine functions is not stored by this program
* <p>
* CAB: the short (1-127) or long (128-10293) address of the engine decoder
* <p>
* To set functions F0-F4 on (=1) or off (=0):
* <p>
* BYTE1: 128 + F1*1 + F2*2 + F3*4 + F4*8 + F0*16 BYTE2: omitted
* <p>
* To set functions F5-F8 on (=1) or off (=0):
* <p>
* BYTE1: 176 + F5*1 + F6*2 + F7*4 + F8*8 BYTE2: omitted
* <p>
* To set functions F9-F12 on (=1) or off (=0):
* <p>
* BYTE1: 160 + F9*1 +F10*2 + F11*4 + F12*8 BYTE2: omitted
* <p>
* To set functions F13-F20 on (=1) or off (=0):
* <p>
* BYTE1: 222 BYTE2: F13*1 + F14*2 + F15*4 + F16*8 + F17*16 + F18*32 +
* F19*64 + F20*128
* <p>
* To set functions F21-F28 on (=1) of off (=0):
* <p>
* BYTE1: 223 BYTE2: F21*1 + F22*2 + F23*4 + F24*8 + F25*16 + F26*32 +
* F27*64 + F28*128
* <p>
* returns: NONE
* <p>
*/
/**
* Generate a Function Group One Operation Request message.
*
* @param address is the locomotive address
* @param f0 is true if f0 is on, false if f0 is off
* @param f1 is true if f1 is on, false if f1 is off
* @param f2 is true if f2 is on, false if f2 is off
* @param f3 is true if f3 is on, false if f3 is off
* @param f4 is true if f4 is on, false if f4 is off
* @return message to set function group 1.
*/
public static DCCppMessage makeFunctionGroup1OpsMsg(int address,
boolean f0,
boolean f1,
boolean f2,
boolean f3,
boolean f4) {
// Sanity check inputs
if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
m.myMessage.append(" ").append(address);
int byte1 = 128 + (f0 ? 16 : 0);
byte1 += (f1 ? 1 : 0);
byte1 += (f2 ? 2 : 0);
byte1 += (f3 ? 4 : 0);
byte1 += (f4 ? 8 : 0);
m.myMessage.append(" ").append(byte1);
m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
/**
* Generate a Function Group One Set Momentary Functions message.
*
* @param address is the locomotive address
* @param f0 is true if f0 is momentary
* @param f1 is true if f1 is momentary
* @param f2 is true if f2 is momentary
* @param f3 is true if f3 is momentary
* @param f4 is true if f4 is momentary
* @return message to set momentary function group 1.
*/
public static DCCppMessage makeFunctionGroup1SetMomMsg(int address,
boolean f0,
boolean f1,
boolean f2,
boolean f3,
boolean f4) {
// Sanity check inputs
if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
m.myMessage.append(" ").append(address);
int byte1 = 128 + (f0 ? 16 : 0);
byte1 += (f1 ? 1 : 0);
byte1 += (f2 ? 2 : 0);
byte1 += (f3 ? 4 : 0);
byte1 += (f4 ? 8 : 0);
m.myMessage.append(" ").append(byte1);
m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
/**
* Generate a Function Group Two Operation Request message.
*
* @param address is the locomotive address
* @param f5 is true if f5 is on, false if f5 is off
* @param f6 is true if f6 is on, false if f6 is off
* @param f7 is true if f7 is on, false if f7 is off
* @param f8 is true if f8 is on, false if f8 is off
* @return message to set function group 2.
*/
public static DCCppMessage makeFunctionGroup2OpsMsg(int address,
boolean f5,
boolean f6,
boolean f7,
boolean f8) {
// Sanity check inputs
if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
m.myMessage.append(" ").append(address);
int byte1 = 176;
byte1 += (f5 ? 1 : 0);
byte1 += (f6 ? 2 : 0);
byte1 += (f7 ? 4 : 0);
byte1 += (f8 ? 8 : 0);
m.myMessage.append(" ").append(byte1);
m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
/**
* Generate a Function Group Two Set Momentary Functions message.
*
* @param address is the locomotive address
* @param f5 is true if f5 is momentary
* @param f6 is true if f6 is momentary
* @param f7 is true if f7 is momentary
* @param f8 is true if f8 is momentary
* @return message to set momentary function group 2.
*/
public static DCCppMessage makeFunctionGroup2SetMomMsg(int address,
boolean f5,
boolean f6,
boolean f7,
boolean f8) {
// Sanity check inputs
if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
m.myMessage.append(" ").append(address);
int byte1 = 176;
byte1 += (f5 ? 1 : 0);
byte1 += (f6 ? 2 : 0);
byte1 += (f7 ? 4 : 0);
byte1 += (f8 ? 8 : 0);
m.myMessage.append(" ").append(byte1);
m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
/**
* Generate a Function Group Three Operation Request message.
*
* @param address is the locomotive address
* @param f9 is true if f9 is on, false if f9 is off
* @param f10 is true if f10 is on, false if f10 is off
* @param f11 is true if f11 is on, false if f11 is off
* @param f12 is true if f12 is on, false if f12 is off
* @return message to set function group 3.
*/
public static DCCppMessage makeFunctionGroup3OpsMsg(int address,
boolean f9,
boolean f10,
boolean f11,
boolean f12) {
// Sanity check inputs
if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
m.myMessage.append(" ").append(address);
int byte1 = 160;
byte1 += (f9 ? 1 : 0);
byte1 += (f10 ? 2 : 0);
byte1 += (f11 ? 4 : 0);
byte1 += (f12 ? 8 : 0);
m.myMessage.append(" ").append(byte1);
m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
/**
* Generate a Function Group Three Set Momentary Functions message.
*
* @param address is the locomotive address
* @param f9 is true if f9 is momentary
* @param f10 is true if f10 is momentary
* @param f11 is true if f11 is momentary
* @param f12 is true if f12 is momentary
* @return message to set momentary function group 3.
*/
public static DCCppMessage makeFunctionGroup3SetMomMsg(int address,
boolean f9,
boolean f10,
boolean f11,
boolean f12) {
// Sanity check inputs
if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
m.myMessage.append(" ").append(address);
int byte1 = 160;
byte1 += (f9 ? 1 : 0);
byte1 += (f10 ? 2 : 0);
byte1 += (f11 ? 4 : 0);
byte1 += (f12 ? 8 : 0);
m.myMessage.append(" ").append(byte1);
m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
/**
* Generate a Function Group Four Operation Request message.
*
* @param address is the locomotive address
* @param f13 is true if f13 is on, false if f13 is off
* @param f14 is true if f14 is on, false if f14 is off
* @param f15 is true if f15 is on, false if f15 is off
* @param f16 is true if f18 is on, false if f16 is off
* @param f17 is true if f17 is on, false if f17 is off
* @param f18 is true if f18 is on, false if f18 is off
* @param f19 is true if f19 is on, false if f19 is off
* @param f20 is true if f20 is on, false if f20 is off
* @return message to set function group 4.
*/
public static DCCppMessage makeFunctionGroup4OpsMsg(int address,
boolean f13,
boolean f14,
boolean f15,
boolean f16,
boolean f17,
boolean f18,
boolean f19,
boolean f20) {
// Sanity check inputs
if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
m.myMessage.append(" ").append(address);
int byte2 = 0;
byte2 += (f13 ? 1 : 0);
byte2 += (f14 ? 2 : 0);
byte2 += (f15 ? 4 : 0);
byte2 += (f16 ? 8 : 0);
byte2 += (f17 ? 16 : 0);
byte2 += (f18 ? 32 : 0);
byte2 += (f19 ? 64 : 0);
byte2 += (f20 ? 128 : 0);
m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP4_BYTE1);
m.myMessage.append(" ").append(byte2);
m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
/**
* Generate a Function Group Four Set Momentary Function message.
*
* @param address is the locomotive address
* @param f13 is true if f13 is Momentary
* @param f14 is true if f14 is Momentary
* @param f15 is true if f15 is Momentary
* @param f16 is true if f18 is Momentary
* @param f17 is true if f17 is Momentary
* @param f18 is true if f18 is Momentary
* @param f19 is true if f19 is Momentary
* @param f20 is true if f20 is Momentary
* @return message to set momentary function group 4.
*/
public static DCCppMessage makeFunctionGroup4SetMomMsg(int address,
boolean f13,
boolean f14,
boolean f15,
boolean f16,
boolean f17,
boolean f18,
boolean f19,
boolean f20) {
// Sanity check inputs
if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
m.myMessage.append(" ").append(address);
int byte2 = 0;
byte2 += (f13 ? 1 : 0);
byte2 += (f14 ? 2 : 0);
byte2 += (f15 ? 4 : 0);
byte2 += (f16 ? 8 : 0);
byte2 += (f17 ? 16 : 0);
byte2 += (f18 ? 32 : 0);
byte2 += (f19 ? 64 : 0);
byte2 += (f20 ? 128 : 0);
m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP4_BYTE1);
m.myMessage.append(" ").append(byte2);
m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
/**
* Generate a Function Group Five Operation Request message.
*
* @param address is the locomotive address
* @param f21 is true if f21 is on, false if f21 is off
* @param f22 is true if f22 is on, false if f22 is off
* @param f23 is true if f23 is on, false if f23 is off
* @param f24 is true if f24 is on, false if f24 is off
* @param f25 is true if f25 is on, false if f25 is off
* @param f26 is true if f26 is on, false if f26 is off
* @param f27 is true if f27 is on, false if f27 is off
* @param f28 is true if f28 is on, false if f28 is off
* @return message to set function group 5.
*/
public static DCCppMessage makeFunctionGroup5OpsMsg(int address,
boolean f21,
boolean f22,
boolean f23,
boolean f24,
boolean f25,
boolean f26,
boolean f27,
boolean f28) {
// Sanity check inputs
if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
m.myMessage.append(" ").append(address);
int byte2 = 0;
byte2 += (f21 ? 1 : 0);
byte2 += (f22 ? 2 : 0);
byte2 += (f23 ? 4 : 0);
byte2 += (f24 ? 8 : 0);
byte2 += (f25 ? 16 : 0);
byte2 += (f26 ? 32 : 0);
byte2 += (f27 ? 64 : 0);
byte2 += (f28 ? 128 : 0);
log.debug("DCCppMessage: Byte2 = {}", byte2);
m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP5_BYTE1);
m.myMessage.append(" ").append(byte2);
m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
/**
* Generate a Function Group Five Set Momentary Function message.
*
* @param address is the locomotive address
* @param f21 is true if f21 is momentary
* @param f22 is true if f22 is momentary
* @param f23 is true if f23 is momentary
* @param f24 is true if f24 is momentary
* @param f25 is true if f25 is momentary
* @param f26 is true if f26 is momentary
* @param f27 is true if f27 is momentary
* @param f28 is true if f28 is momentary
* @return message to set momentary function group 5.
*/
public static DCCppMessage makeFunctionGroup5SetMomMsg(int address,
boolean f21,
boolean f22,
boolean f23,
boolean f24,
boolean f25,
boolean f26,
boolean f27,
boolean f28) {
// Sanity check inputs
if (address < 1 || address > DCCppConstants.MAX_LOCO_ADDRESS) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.FUNCTION_CMD);
m.myMessage.append(" ").append(address);
int byte2 = 0;
byte2 += (f21 ? 1 : 0);
byte2 += (f22 ? 2 : 0);
byte2 += (f23 ? 4 : 0);
byte2 += (f24 ? 8 : 0);
byte2 += (f25 ? 16 : 0);
byte2 += (f26 ? 32 : 0);
byte2 += (f27 ? 64 : 0);
byte2 += (f28 ? 128 : 0);
m.myMessage.append(" ").append(DCCppConstants.FUNCTION_GROUP5_BYTE1);
m.myMessage.append(" ").append(byte2);
m.myRegex = DCCppConstants.FUNCTION_CMD_REGEX;
m._nDataChars = m.toString().length();
return (m);
}
/*
* Build an Emergency Off Message
*/
/*
* Test Code Functions... not for normal use
*/
/**
* Write DCC Packet to a specified Register on the Main.
* <br>
* DCC++ BaseStation code appends its own error-correction byte so we must
* not provide one.
*
* @param register the DCC++ BaseStation main register number to use
* @param numBytes the number of bytes in the packet
* @param bytes byte array representing the packet. The first
* {@code num_bytes} are used.
* @return the formatted message to send
*/
public static DCCppMessage makeWriteDCCPacketMainMsg(int register, int numBytes, byte[] bytes) {
// Sanity Check Inputs
if (register < 0 || register > DCCppConstants.MAX_MAIN_REGISTERS || numBytes < 2 || numBytes > 5) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.WRITE_DCC_PACKET_MAIN);
m.myMessage.append(" ").append(register);
for (int k = 0; k < numBytes; k++) {
m.myMessage.append(" ").append(jmri.util.StringUtil.twoHexFromInt(bytes[k]));
}
m.myRegex = DCCppConstants.WRITE_DCC_PACKET_MAIN_REGEX;
return (m);
}
/**
* Write DCC Packet to a specified Register on the Programming Track.
* <br><br>
* DCC++ BaseStation code appends its own error-correction byte so we must
* not provide one.
*
* @param register the DCC++ BaseStation main register number to use
* @param numBytes the number of bytes in the packet
* @param bytes byte array representing the packet. The first
* {@code num_bytes} are used.
* @return the formatted message to send
*/
public static DCCppMessage makeWriteDCCPacketProgMsg(int register, int numBytes, byte[] bytes) {
// Sanity Check Inputs
if (register < 0 || register > DCCppConstants.MAX_MAIN_REGISTERS || numBytes < 2 || numBytes > 5) {
return (null);
}
DCCppMessage m = new DCCppMessage(DCCppConstants.WRITE_DCC_PACKET_PROG);
m.myMessage.append(" ").append(register);
for (int k = 0; k < numBytes; k++) {
m.myMessage.append(" ").append(jmri.util.StringUtil.twoHexFromInt(bytes[k]));
}
m.myRegex = DCCppConstants.WRITE_DCC_PACKET_PROG_REGEX;
return (m);
}
// public static DCCppMessage makeCheckFreeMemMsg() {
// return (new DCCppMessage(DCCppConstants.GET_FREE_MEMORY, DCCppConstants.GET_FREE_MEMORY_REGEX));
// }
//
public static DCCppMessage makeListRegisterContentsMsg() {
return (new DCCppMessage(DCCppConstants.LIST_REGISTER_CONTENTS,
DCCppConstants.LIST_REGISTER_CONTENTS_REGEX));
}
/**
* Request LCD Messages used for Virtual LCD Display
* <p>
* Format: {@code <@>}
* <p>
* tells EX_CommandStation to send any LCD message updates to this instance of JMRI
* @return the formatted message to send
*/
public static DCCppMessage makeLCDRequestMsg() {
return (new DCCppMessage(DCCppConstants.LCD_TEXT_CMD, DCCppConstants.LCD_TEXT_CMD_REGEX));
}
/**
* This implementation of equals is targeted to the background function
* refreshing in SerialDCCppPacketizer. To keep only one function group in
* the refresh queue the logic is as follows. Two messages are equal if they
* are:
* <ul>
* <li>actually identical, or</li>
* <li>a function call to the same address and same function group</li>
* </ul>
*/
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (!(obj instanceof DCCppMessage)) {
return false;
}
final DCCppMessage other = (DCCppMessage) obj;
final String myCmd = this.toString();
final String otherCmd = other.toString();
if (myCmd.equals(otherCmd)) {
return true;
}
if (!(myCmd.charAt(0) == DCCppConstants.FUNCTION_CMD) || !(otherCmd.charAt(0) == DCCppConstants.FUNCTION_CMD)) {
return false;
}
final int mySpace1 = myCmd.indexOf(' ', 2);
final int otherSpace1 = otherCmd.indexOf(' ', 2);
if (mySpace1 != otherSpace1) {
return false;
}
if (!myCmd.subSequence(2, mySpace1).equals(otherCmd.subSequence(2, otherSpace1))) {
return false;
}
int mySpace2 = myCmd.indexOf(' ', mySpace1 + 1);
if (mySpace2 < 0) {
mySpace2 = myCmd.length();
}
int otherSpace2 = otherCmd.indexOf(' ', otherSpace1 + 1);
if (otherSpace2 < 0) {
otherSpace2 = otherCmd.length();
}
final int myBaseFunction = Integer.parseInt(myCmd.substring(mySpace1 + 1, mySpace2));
final int otherBaseFunction = Integer.parseInt(otherCmd.substring(otherSpace1 + 1, otherSpace2));
if (myBaseFunction == otherBaseFunction) {
return true;
}
return getFuncBaseByte1(myBaseFunction) == getFuncBaseByte1(otherBaseFunction);
}
@Override
public int hashCode() {
return toString().hashCode();
}
/**
* Get the function group from the first byte of the function setting call.
*
* @param byte1 first byte (mixed in with function bits for groups 1 to 3,
* or standalone value for groups 4 and 5)
* @return the base group
*/
private static int getFuncBaseByte1(final int byte1) {
if (byte1 == DCCppConstants.FUNCTION_GROUP4_BYTE1 || byte1 == DCCppConstants.FUNCTION_GROUP5_BYTE1) {
return byte1;
}
if (byte1 < 160) {
return 128;
}
if (byte1 < 176) {
return 160;
}
return 176;
}
/**
* When is this message supposed to be resent?
*/
private long expireTime;
/**
* Before adding the message to the delay queue call this method to set when
* the message should be repeated. The only time guarantee is that it will
* be repeated after <u>at least</u> this much time, but it can be
* significantly longer until it is repeated, function of the message queue
* length.
*
* @param millis milliseconds in the future
*/
public void delayFor(final long millis) {
expireTime = System.currentTimeMillis() + millis;
}
/**
* Comparing two queued message for refreshing the function calls, based on
* their expected execution time.
*/
@Override
public int compareTo(@Nonnull final Delayed o) {
final long diff = this.expireTime - ((DCCppMessage) o).expireTime;
if (diff < 0) {
return -1;
}
if (diff > 0) {
return 1;
}
return 0;
}
/**
* From the {@link Delayed} interface, how long this message still has until
* it should be executed.
*/
@Override
public long getDelay(final TimeUnit unit) {
return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
// initialize logging
private static final Logger log = LoggerFactory.getLogger(DCCppMessage.class);
}