001package jmri.jmrix.sprog; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import jmri.ProgrammingMode; 005import jmri.jmrix.sprog.SprogConstants.SprogState; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009/** 010 * Encode a message to an SPROG command station. 011 * <p> 012 * The {@link SprogReply} class handles the response from the command station. 013 * 014 * @author Bob Jacobsen Copyright (C) 2001 015 */ 016public class SprogMessage extends jmri.jmrix.AbstractMRMessage { 017 018 // Special characters (NOTE: microchip bootloader does not use standard ASCII) 019 public static final int STX = 15; 020 public static final int DLE = 5; 021 public static final int ETX = 4; 022 public static final int CR = 0x0d; 023 public static final int LF = 0x0a; 024 025 // bootloader commands 026 public static final int RD_VER = 0; 027 public static final int WT_FLASH = 2; 028 public static final int ER_FLASH = 3; 029 public static final int WT_EEDATA = 5; 030 031 // Longest boot message is 256bytes each preceded by DLE + 2xSTX + ETX 032 public static final int MAXSIZE = 515; 033 034 private static volatile int msgId = 0; 035 protected int _id = -1; 036 037 /** 038 * Get next message id 039 * 040 * For modules that need to match their own message/reply pairs in strict sequence, e.g., 041 * SprogCommandStation, return a unique message id. The id wraps at a suitably large 042 * value. 043 * 044 * @return the message id 045 */ 046 protected static synchronized int newMsgId() { 047 msgId = (msgId+1)%65536; 048 return msgId; 049 } 050 051 public int getId() { 052 return _id; 053 } 054 055 // create a new one 056 public SprogMessage(int i) { 057 if (i < 1) { 058 log.error("invalid length in call to ctor"); 059 } 060 _nDataChars = i; 061 _dataChars = new int[i]; 062 _id = newMsgId(); 063 } 064 065 /** 066 * Create a new SprogMessage containing a byte array to represent a packet 067 * to output. 068 * 069 * @param packet The contents of the packet 070 */ 071 public SprogMessage(byte[] packet) { 072 this(1 + (packet.length * 3)); 073 int i; // counter of byte in output message 074 int j; // counter of byte in input packet 075 076 i = 0; 077 this.setElement(i++, 'O'); // "O " starts output packet 078 079 // add each byte of the input message 080 for (j = 0; j < packet.length; j++) { 081 this.setElement(i++, ' '); 082 String s = Integer.toHexString(packet[j] & 0xFF).toUpperCase(); 083 if (s.length() == 1) { 084 this.setElement(i++, '0'); 085 this.setElement(i++, s.charAt(0)); 086 } else { 087 this.setElement(i++, s.charAt(0)); 088 this.setElement(i++, s.charAt(1)); 089 } 090 } 091 _id = newMsgId(); 092 } 093 094 // from String 095 public SprogMessage(String s) { 096 _nDataChars = s.length(); 097 _dataChars = new int[_nDataChars]; 098 for (int i = 0; i < _nDataChars; i++) { 099 _dataChars[i] = s.charAt(i); 100 } 101 _id = newMsgId(); 102 } 103 104 // copy one 105 public SprogMessage(SprogMessage m) { 106 if (m == null) { 107 log.error("copy ctor of null message"); 108 return; 109 } 110 _nDataChars = m._nDataChars; 111 _dataChars = new int[_nDataChars]; 112 for (int i = 0; i < _nDataChars; i++) { 113 _dataChars[i] = m._dataChars[i]; 114 } 115 // Copy has a unique id 116 _id = newMsgId(); 117 } 118 119 @Override 120 public void setElement(int n, int v) { 121 _dataChars[n] = v; 122 } 123 124 private void setLength(int i) { 125 _dataChars[1] = i; 126 } 127 128 private void setAddress(int i) { 129 _dataChars[2] = i & 0xff; 130 _dataChars[3] = (i >> 8) & 0xff; 131 _dataChars[4] = i >> 16; 132 } 133 134 private void setData(int[] d) { 135 for (int i = 0; i < d.length; i++) { 136 _dataChars[5 + i] = d[i]; 137 } 138 } 139 140 private void setChecksum() { 141 int checksum = 0; 142 for (int i = 0; i < _nDataChars - 1; i++) { 143 checksum += _dataChars[i]; 144 } 145 checksum = checksum & 0xff; 146 if (checksum > 0) { 147 checksum = 256 - checksum; 148 } 149 _dataChars[_nDataChars - 1] = checksum; 150 } 151 152 private SprogMessage frame() { 153 int j = 2; 154 // Create new message to hold the framed one 155 SprogMessage f = new SprogMessage(MAXSIZE); 156 f.setElement(0, STX); 157 f.setElement(1, STX); 158 // copy existing message adding DLE 159 for (int i = 0; i < _nDataChars; i++) { 160 if (_dataChars[i] == STX 161 || _dataChars[i] == ETX 162 || _dataChars[i] == DLE) { 163 f.setElement(j++, DLE); 164 } 165 f.setElement(j++, _dataChars[i]); 166 } 167 f.setElement(j++, ETX); 168 f._nDataChars = j; 169 // return new message 170 return f; 171 } 172 173 // display format 174 @Override 175 public String toString(){ 176 // default to not SIIBootMode being false. 177 return this.toString(false); 178 } 179 180 public String toString(boolean isSIIBootMode) { 181 StringBuffer buf = new StringBuffer(); 182 if (!isSIIBootMode) { 183 for (int i = 0; i < _nDataChars; i++) { 184 buf.append((char) _dataChars[i]); 185 } 186 } else { 187 for (int i = 0; i < _nDataChars; i++) { 188 //s+="<"+_dataChars[i]+">"; 189 buf.append("<"); 190 buf.append(_dataChars[i]); 191 buf.append(">"); 192 } 193 } 194 return buf.toString(); 195 } 196 197 /** 198 * Get formatted message for direct output to stream - this is the final 199 * format of the message as a byte array. 200 * 201 * @param sprogState a SprogState variable representing the current state of 202 * the Sprog 203 * @return the formatted message as a byte array 204 */ 205 public byte[] getFormattedMessage(SprogState sprogState) { 206 int len = this.getNumDataElements(); 207 208 // space for carriage return if required 209 int cr = 0; 210 if (sprogState != SprogState.SIIBOOTMODE) { 211 cr = 1; 212 } 213 214 byte msg[] = new byte[len + cr]; 215 216 for (int i = 0; i < len; i++) { 217 if (sprogState != SprogState.SIIBOOTMODE) { 218 msg[i] = (byte) ( this.getElement(i) & 0x7f); 219 } else { 220 msg[i] = (byte) ( this.getElement(i)); 221 } 222 } 223 if (sprogState != SprogState.SIIBOOTMODE) { 224 msg[len] = 0x0d; 225 } 226 return msg; 227 } 228 229 // diagnose format 230 public boolean isKillMain() { 231 return getOpCode() == '-'; 232 } 233 234 public boolean isEnableMain() { 235 return getOpCode() == '+'; 236 } 237 238 // static methods to return a formatted message 239 static public SprogMessage getEnableMain() { 240 SprogMessage m = new SprogMessage(1); 241 m.setOpCode('+'); 242 return m; 243 } 244 245 static public SprogMessage getKillMain() { 246 SprogMessage m = new SprogMessage(1); 247 m.setOpCode('-'); 248 return m; 249 } 250 251 static public SprogMessage getStatus() { 252 SprogMessage m = new SprogMessage(1); 253 m.setOpCode('S'); 254 return m; 255 } 256 257 /* 258 * SPROG uses same commands for reading and writing, with the number of 259 * parameters determining the action. Currently supports page mode and 260 * bit direct modes. A single parameter is taken as the CV address to read. 261 * Two parametes are taken as the CV address and data to be written. 262 */ 263 static public SprogMessage getReadCV(int cv, ProgrammingMode mode) { 264 SprogMessage m = new SprogMessage(6); 265 if (mode == ProgrammingMode.PAGEMODE) { 266 m.setOpCode('V'); 267 } else { // Bit direct mode 268 m.setOpCode('C'); 269 } 270 addSpace(m, 1); 271 addIntAsFour(cv, m, 2); 272 return m; 273 } 274 275 /* 276 * CV reads can pass a hint by using different commands. The hint will first 277 * be verified, potentially speeding up the read process. 278 * 279 * @param cv CV address 280 * @param mode Programming mode 281 * @param startVal Hint 282 * @return 283 */ 284 static public SprogMessage getReadCV(int cv, ProgrammingMode mode, int startVal) { 285 SprogMessage m = new SprogMessage(10); 286 if (mode == ProgrammingMode.PAGEMODE) { 287 m.setOpCode('U'); 288 } else { // Bit direct mode 289 m.setOpCode('D'); 290 } 291 addSpace(m, 1); 292 addIntAsFour(cv, m, 2); 293 addSpace(m, 6); 294 addIntAsThree(startVal, m, 7); 295 return m; 296 } 297 298 static public SprogMessage getWriteCV(int cv, int val, ProgrammingMode mode) { 299 SprogMessage m = new SprogMessage(10); 300 if (mode == ProgrammingMode.PAGEMODE) { 301 m.setOpCode('V'); 302 } else { // Bit direct mode 303 m.setOpCode('C'); 304 } 305 addSpace(m, 1); 306 addIntAsFour(cv, m, 2); 307 addSpace(m, 6); 308 addIntAsThree(val, m, 7); 309 return m; 310 } 311 312 // [AC] 11/09/2002 SPROG doesn't currently support registered mode 313 static public SprogMessage getReadRegister(int reg) { //Vx 314 SprogMessage m = new SprogMessage(1); 315 m.setOpCode(' '); 316 return m; 317 } 318 319 static public SprogMessage getWriteRegister(int reg, int val) { //Sx xx 320 SprogMessage m = new SprogMessage(1); 321 m.setOpCode(' '); 322 return m; 323 } 324 325 /** 326 * Get a message containing a DCC packet. 327 * 328 * @param bytes byte[] 329 * @return SprogMessage 330 */ 331 static public SprogMessage getPacketMessage(byte[] bytes) { 332 SprogMessage m = new SprogMessage(1 + 3 * bytes.length); 333 int i = 0; // counter to make it easier to format the message 334 335 m.setElement(i++, 'O'); // "O" Output DCC packet command 336 for (int j = 0; j < bytes.length; j++) { 337 m.setElement(i++, ' '); 338 m.addIntAsTwoHex(bytes[j] & 0xFF, i); 339 i = i + 2; 340 } 341 return m; 342 } 343 344 // Bootloader messages are initially created long enough for 345 // the message and checksum. The message is then framed with control 346 // characters before being returned 347 static public SprogMessage getReadBootVersion() { 348 SprogMessage m = new SprogMessage(3); 349 m.setOpCode(RD_VER); 350 m.setLength(2); 351 m.setChecksum(); 352 return m.frame(); 353 } 354 355 static public SprogMessage getWriteFlash(int addr, int[] data, int blockLen) { 356 int l = data.length; 357 int offset; 358 // Writes are rounded up to multiples of blockLen 359 if (l % blockLen != 0) { 360 l = l + (blockLen - l % blockLen); 361 } 362 // and data padded with erased condition 363 int padded[] = new int[l]; 364 for (int i = 0; i < l; i++) { 365 padded[i] = 0xff; 366 } 367 // Address is masked to start on blockLen boundary 368 if (blockLen == 16) { 369 offset = addr & 0xF; 370 addr = addr & 0xFFFFFFF0; 371 } else { 372 offset = addr & 0x7; 373 addr = addr & 0xFFFFFFF8; 374 } 375 // Copy data into padded array at address offset 376 for (int i = 0; i < data.length; i++) { 377 padded[i + offset] = data[i]; 378 } 379 SprogMessage m = new SprogMessage(6 + l); 380 m.setOpCode(WT_FLASH); 381 // length is number of blockLen blocks 382 m.setLength(l / blockLen); 383 m.setAddress(addr); 384 m.setData(padded); 385 m.setChecksum(); 386 return m.frame(); 387 } 388 389 static public SprogMessage getEraseFlash(int addr, int rows) { 390 SprogMessage m = new SprogMessage(6); 391 m.setOpCode(ER_FLASH); 392 // Erase a number of 64 byte rows 393 m.setLength(rows); 394 m.setAddress(addr); 395 m.setChecksum(); 396 return m.frame(); 397 } 398 399 static public SprogMessage getWriteEE(int addr, int[] data) { 400 SprogMessage m = new SprogMessage(6 + data.length); 401 m.setOpCode(WT_EEDATA); 402 m.setLength(data.length); 403 m.setAddress(addr & 0xff); 404 m.setData(data); 405 m.setChecksum(); 406 return m.frame(); 407 } 408 409 static public SprogMessage getReset() { 410 SprogMessage m = new SprogMessage(3); 411 m.setOpCode(0); 412 m.setLength(0); 413 m.setChecksum(); 414 return m.frame(); 415 } 416 417 // [AC] 11/09/2002 418 private static String addSpace(SprogMessage m, int offset) { 419 String s = " "; 420 m.setElement(offset, ' '); 421 return s; 422 } 423 424 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification="was previously marked with @SuppressWarnings, reason unknown") 425 private static String addIntAsTwo(int val, SprogMessage m, int offset) { 426 String s = "" + val; 427 if (s.length() != 2) { 428 s = "0" + s; // handle <10 429 } 430 m.setElement(offset, s.charAt(0)); 431 m.setElement(offset + 1, s.charAt(1)); 432 return s; 433 } 434 435 private static String addIntAsThree(int val, SprogMessage m, int offset) { 436 String s = "" + val; 437 if (s.length() != 3) { 438 s = "0" + s; // handle <10 439 } 440 if (s.length() != 3) { 441 s = "0" + s; // handle <100 442 } 443 m.setElement(offset, s.charAt(0)); 444 m.setElement(offset + 1, s.charAt(1)); 445 m.setElement(offset + 2, s.charAt(2)); 446 return s; 447 } 448 449 private static String addIntAsFour(int val, SprogMessage m, int offset) { 450 String s = "" + val; 451 if (s.length() != 4) { 452 s = "0" + s; // handle <10 453 } 454 if (s.length() != 4) { 455 s = "0" + s; // handle <100 456 } 457 if (s.length() != 4) { 458 s = "0" + s; // handle <1000 459 } 460 m.setElement(offset, s.charAt(0)); 461 m.setElement(offset + 1, s.charAt(1)); 462 m.setElement(offset + 2, s.charAt(2)); 463 m.setElement(offset + 3, s.charAt(3)); 464 return s; 465 } 466 467 private final static Logger log = LoggerFactory.getLogger(SprogMessage.class); 468 469}