001package jmri.jmrix.sprog; 002 003import jmri.jmrix.AbstractMRReply; 004import org.slf4j.Logger; 005import org.slf4j.LoggerFactory; 006 007/** 008 * Carries the reply to a SprogMessage. 009 * 010 * @author Bob Jacobsen Copyright (C) 2001 011 * @author Andrew Berridge - refactored, cleaned up, Feb 2010 012 */ 013public class SprogReply extends AbstractMRReply { 014 015 // Longest boot reply is 256bytes each preceded by DLE + 2xSTX + ETX 016 static public final int maxSize = 515; 017 private boolean _isBoot = false; 018 protected int _id = -1; 019 020 // create a new one 021 public SprogReply() { 022 super(); 023 } 024 025 public void setId(int id) { 026 _id = id; 027 } 028 029 public int getId() { 030 return _id; 031 } 032 033 // no need to do anything 034 @Override 035 protected int skipPrefix(int index) { 036 return index; 037 } 038 039 /** 040 * Create a new SprogReply as a deep copy of an existing SprogReply. 041 * 042 * @param m the SprogReply to copy 043 */ 044 public SprogReply(SprogReply m) { 045 this(); 046 if (m == null) { 047 log.error("copy ctor of null message"); 048 return; 049 } 050 _nDataChars = m._nDataChars; 051 _isBoot = m._isBoot; 052 if (m.isUnsolicited()) { 053 super.setUnsolicited(); 054 } 055 for (int i = 0; i < _nDataChars; i++) { 056 _dataChars[i] = m._dataChars[i]; 057 } 058 _id = m._id; 059 } 060 061 /** 062 * Create a SprogReply from a String. 063 * 064 * @param replyString a String containing the contents of the reply 065 * @param isBoot a boolean indicating if this is a boot reply 066 */ 067 public SprogReply(String replyString, boolean isBoot) { 068 this(replyString); 069 _isBoot = isBoot; 070 } 071 072 public SprogReply(String replyString) { 073 super(replyString); 074 } 075 076 /** 077 * Is this reply indicating that an overload condition was detected? 078 * 079 * @return boolean true for overload 080 */ 081 public boolean isOverload() { 082 return (this.toString().contains("!O")); 083 } 084 085 /** 086 * Is this reply indicating that a general error has occurred? 087 * 088 * @return boolean true for error message 089 */ 090 public boolean isError() { 091 return (this.toString().contains("!E")); 092 } 093 094 /** 095 * Check and strip framing characters and DLE from a SPROG bootloader reply. 096 * 097 * @return boolean result of message validation 098 */ 099 public boolean strip() { 100 char tmp[] = new char[_nDataChars]; 101 int j = 0; 102 _isBoot = true; // definitely a boot message 103 // Check framing characters 104 if (_dataChars[0] != SprogMessage.STX) { 105 return false; 106 } 107 if (_dataChars[1] != SprogMessage.STX) { 108 return false; 109 } 110 if (_dataChars[_nDataChars - 1] != SprogMessage.ETX) { 111 return false; 112 } 113 114 // Ignore framing characters and strip DLEs 115 for (int i = 2; i < _nDataChars - 1; i++) { 116 if (_dataChars[i] == SprogMessage.DLE) { 117 i++; 118 } 119 tmp[j++] = (char) _dataChars[i]; 120 } 121 122 // Copy back to original SprogReply 123 for (int i = 0; i < j; i++) { 124 _dataChars[i] = tmp[i]; 125 } 126 _nDataChars = j; 127 return true; 128 } 129 130 /** 131 * Check and strip checksum from a SPROG bootloader reply. 132 * <p> 133 * Assumes framing and DLE chars have been stripped 134 * 135 * @return boolean result of checksum validation 136 */ 137 public boolean getChecksum() { 138 int checksum = 0; 139 for (int i = 0; i < _nDataChars; i++) { 140 checksum += _dataChars[i] & 0xff; 141 } 142 _nDataChars--; 143 return ((checksum & 0xff) == 0); 144 } 145 146 /** 147 * Return a string representation of this SprogReply. 148 * 149 * @return String The string representation 150 */ 151 @Override 152 public String toString() { 153 //String s = ""; 154 StringBuffer buf = new StringBuffer(); 155 if (_isBoot || (_dataChars[0] == SprogMessage.STX)) { 156 for (int i = 0; i < _nDataChars; i++) { 157 //s+="<"+(((char)_dataChars[i]) & 0xff)+">"; 158 buf.append("<"); 159 buf.append(_dataChars[i]); 160 buf.append(">"); 161 } 162 } else { 163 for (int i = 0; i < _nDataChars; i++) { 164 //s+=; 165 buf.append((char) _dataChars[i]); 166 } 167 } 168 return buf.toString(); 169 } 170 171 /** 172 * Extract Read-CV returned value from a message. 173 * <p> 174 * SPROG is assumed to not be echoing commands. A reply to a command may 175 * include the prompt that was printed after the previous command. 176 * <p> 177 * Reply to a CV read is of the form " = hvv" where vv is the CV value in hex 178 * 179 * @return -1 if message can't be parsed 180 */ 181 @Override 182 public int value() { 183 int index = 0; 184 index = skipWhiteSpace(index); 185 index = skipEqual(index); 186 index = skipWhiteSpace(index); 187 String s1 = "" + (char) getElement(index); 188 String s2 = "" + (char) getElement(index + 1); 189 int val = -1; 190 try { 191 int sum = Integer.valueOf(s2, 16); 192 sum += 16 * Integer.valueOf(s1, 16); 193 val = sum; // don't do this assign until now in case the conversion throws 194 } catch (NumberFormatException e) { 195 log.error("Unable to get number from reply: \"{}{}\" index: {} message: \"{}\"", s1, s2, index, toString()); 196 } 197 return val; 198 } 199 200 /** 201 * Find a specific string in the reply. 202 * 203 * @param s string to look for 204 * @return index of String s in the reply 205 */ 206 @Override 207 public int match(String s) { 208 String rep = new String(_dataChars, 0, _nDataChars); 209 return rep.indexOf(s); 210 } 211 212 private int skipEqual(int index) { 213 // start at index, skip over the equals and hex prefix 214 int len = "= h".length(); 215 if (getNumDataElements() >= index + len - 1 216 && '=' == (char) getElement(index) 217 && ' ' == (char) getElement(index + 1) 218 && 'h' == (char) getElement(index + 2)) { 219 index += len; 220 } 221 return index; 222 } 223 224 /** 225 * Normal SPROG replies will end with the prompt for the next command. 226 * 227 * @return true if end of normal reply is found 228 */ 229 public boolean endNormalReply() { 230 // Detect that the reply buffer ends with "P> " or "R> " (note ending space) 231 int num = this.getNumDataElements(); 232 if (num >= 3) { 233 // ptr is offset of last element in SprogReply 234 int ptr = num - 1; 235 if (this.getElement(ptr) != ' ') { 236 return false; 237 } 238 if (this.getElement(ptr - 1) != '>') { 239 return false; 240 } 241 if ((this.getElement(ptr - 2) != 'P') && (this.getElement(ptr - 2) != 'R')) { 242 return false; 243 } 244 // Now see if it's unsolicited !O for overload 245 if (num >= 5) { 246 for (int i = 0; i < num - 1; i++) { 247 if ((this.getElement(i) == '!')) { 248 super.setUnsolicited(); 249 } 250 } 251 } 252 return true; 253 } else { 254 return false; 255 } 256 } 257 258 /** 259 * Bootloader will end with ETX with no preceding DLE. 260 * 261 * @return true if end of bootloader reply is found 262 */ 263 public boolean endBootReply() { 264 // Detect that the reply buffer ends with ETX with no preceding DLE. 265 // This is the end of a SPROG II bootloader reply or the end of 266 // a SPROG v4 echoing the bootloader version request 267 int num = this.getNumDataElements(); 268 if (num >= 2) { 269 // ptr is offset of last element in SprogReply 270 int ptr = num - 1; 271 if ((this.getElement(ptr) & 0xff) != SprogMessage.ETX) { 272 return false; 273 } 274 if ((this.getElement(ptr - 1) & 0xff) == SprogMessage.DLE) { 275 return false; 276 } 277 return true; 278 } else { 279 return false; 280 } 281 } 282 283 private final static Logger log = LoggerFactory.getLogger(SprogReply.class); 284 285}